// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) /* Copyright (c) 2021 Google LLC. */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #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; }