290 lines
6.4 KiB
C
290 lines
6.4 KiB
C
// 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;
|
|
}
|