blob: 953cea1a4e040956f0a7efd32ef656bd71477f2f [file] [log] [blame]
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2021 Google LLC. */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <gelf.h>
#define warn(...) fprintf(stderr, __VA_ARGS__)
/*
* Returns 0 on success; -1 on failure. On sucess, returns via `path` the full
* path to the program for pid.
*/
int get_pid_binary_path(pid_t pid, char *path, size_t path_sz)
{
ssize_t ret;
char proc_pid_exe[32];
if (snprintf(proc_pid_exe, sizeof(proc_pid_exe), "/proc/%d/exe", pid)
>= sizeof(proc_pid_exe)) {
warn("snprintf /proc/PID/exe failed");
return -1;
}
ret = readlink(proc_pid_exe, path, path_sz);
if (ret < 0) {
warn("No such pid %d\n", pid);
return -1;
}
if (ret >= path_sz) {
warn("readlink truncation");
return -1;
}
path[ret] = '\0';
return 0;
}
/*
* Returns 0 on success; -1 on failure. On success, returns via `path` the full
* path to a library matching the name `lib` that is loaded into pid's address
* space.
*/
int get_pid_lib_path(pid_t pid, const char *lib, char *path, size_t path_sz)
{
FILE *maps;
char *p;
char proc_pid_maps[32];
char line_buf[1024];
if (snprintf(proc_pid_maps, sizeof(proc_pid_maps), "/proc/%d/maps", pid)
>= sizeof(proc_pid_maps)) {
warn("snprintf /proc/PID/maps failed");
return -1;
}
maps = fopen(proc_pid_maps, "r");
if (!maps) {
warn("No such pid %d\n", pid);
return -1;
}
while (fgets(line_buf, sizeof(line_buf), maps)) {
if (sscanf(line_buf, "%*x-%*x %*s %*x %*s %*u %s", path) != 1)
continue;
/* e.g. /usr/lib/x86_64-linux-gnu/libc-2.31.so */
p = strrchr(path, '/');
if (!p)
continue;
if (strncmp(p, "/lib", 4))
continue;
p += 4;
if (strncmp(lib, p, strlen(lib)))
continue;
p += strlen(lib);
/* libraries can have - or . after the name */
if (*p != '.' && *p != '-')
continue;
fclose(maps);
return 0;
}
warn("Cannot find library %s\n", lib);
fclose(maps);
return -1;
}
/*
* Returns 0 on success; -1 on failure. On success, returns via `path` the full
* path to the program.
*/
static int which_program(const char *prog, char *path, size_t path_sz)
{
FILE *which;
char cmd[100];
if (snprintf(cmd, sizeof(cmd), "which %s", prog) >= sizeof(cmd)) {
warn("snprintf which prog failed");
return -1;
}
which = popen(cmd, "r");
if (!which) {
warn("which failed");
return -1;
}
if (!fgets(path, path_sz, which)) {
warn("fgets which failed");
pclose(which);
return -1;
}
/* which has a \n at the end of the string */
path[strlen(path) - 1] = '\0';
pclose(which);
return 0;
}
/*
* Returns 0 on success; -1 on failure. On success, returns via `path` the full
* path to the binary for the given pid.
* 1) pid == x, binary == "" : returns the path to x's program
* 2) pid == x, binary == "foo" : returns the path to libfoo linked in x
* 3) pid == 0, binary == "" : failure: need a pid or a binary
* 4) pid == 0, binary == "bar" : returns the path to `which bar`
*
* For case 4), ideally we'd like to search for libbar too, but we don't support
* that yet.
*/
int resolve_binary_path(const char *binary, pid_t pid, char *path, size_t path_sz)
{
if (!strcmp(binary, "")) {
if (!pid) {
warn("Uprobes need a pid or a binary\n");
return -1;
}
return get_pid_binary_path(pid, path, path_sz);
}
if (pid)
return get_pid_lib_path(pid, binary, path, path_sz);
if (which_program(binary, path, path_sz)) {
/*
* If the user is tracing a program by name, we can find it.
* But we can't find a library by name yet. We'd need to parse
* ld.so.cache or something similar.
*/
warn("Can't find %s (Need a PID if this is a library)\n", binary);
return -1;
}
return 0;
}
/*
* Opens an elf at `path` of kind ELF_K_ELF. Returns NULL on failure. On
* success, close with close_elf(e, fd_close).
*/
Elf *open_elf(const char *path, int *fd_close)
{
int fd;
Elf *e;
if (elf_version(EV_CURRENT) == EV_NONE) {
warn("elf init failed\n");
return NULL;
}
fd = open(path, O_RDONLY);
if (fd < 0) {
warn("Could not open %s\n", path);
return NULL;
}
e = elf_begin(fd, ELF_C_READ, NULL);
if (!e) {
warn("elf_begin failed: %s\n", elf_errmsg(-1));
close(fd);
return NULL;
}
if (elf_kind(e) != ELF_K_ELF) {
warn("elf kind %d is not ELF_K_ELF\n", elf_kind(e));
elf_end(e);
close(fd);
return NULL;
}
*fd_close = fd;
return e;
}
Elf *open_elf_by_fd(int fd)
{
Elf *e;
if (elf_version(EV_CURRENT) == EV_NONE) {
warn("elf init failed\n");
return NULL;
}
e = elf_begin(fd, ELF_C_READ, NULL);
if (!e) {
warn("elf_begin failed: %s\n", elf_errmsg(-1));
close(fd);
return NULL;
}
if (elf_kind(e) != ELF_K_ELF) {
warn("elf kind %d is not ELF_K_ELF\n", elf_kind(e));
elf_end(e);
close(fd);
return NULL;
}
return e;
}
void close_elf(Elf *e, int fd_close)
{
elf_end(e);
close(fd_close);
}
/* Returns the offset of a function in the elf file `path`, or -1 on failure. */
off_t get_elf_func_offset(const char *path, const char *func)
{
off_t ret = -1;
int i, fd = -1;
Elf *e;
Elf_Scn *scn;
Elf_Data *data;
GElf_Ehdr ehdr;
GElf_Shdr shdr[1];
GElf_Phdr phdr;
GElf_Sym sym[1];
size_t shstrndx, nhdrs;
char *n;
e = open_elf(path, &fd);
if (!gelf_getehdr(e, &ehdr))
goto out;
if (elf_getshdrstrndx(e, &shstrndx) != 0)
goto out;
scn = NULL;
while ((scn = elf_nextscn(e, scn))) {
if (!gelf_getshdr(scn, shdr))
continue;
if (!(shdr->sh_type == SHT_SYMTAB || shdr->sh_type == SHT_DYNSYM))
continue;
data = NULL;
while ((data = elf_getdata(scn, data))) {
for (i = 0; gelf_getsym(data, i, sym); i++) {
n = elf_strptr(e, shdr->sh_link, sym->st_name);
if (!n)
continue;
if (GELF_ST_TYPE(sym->st_info) != STT_FUNC)
continue;
if (!strcmp(n, func)) {
ret = sym->st_value;
goto check;
}
}
}
}
check:
if (ehdr.e_type == ET_EXEC || ehdr.e_type == ET_DYN) {
if (elf_getphdrnum(e, &nhdrs) != 0) {
ret = -1;
goto out;
}
for (i = 0; i < (int)nhdrs; i++) {
if (!gelf_getphdr(e, i, &phdr))
continue;
if (phdr.p_type != PT_LOAD || !(phdr.p_flags & PF_X))
continue;
if (phdr.p_vaddr <= ret && ret < (phdr.p_vaddr + phdr.p_memsz)) {
ret = ret - phdr.p_vaddr + phdr.p_offset;
goto out;
}
}
ret = -1;
}
out:
close_elf(e, fd);
return ret;
}