mirror of
https://github.com/sstefani/mtrace.git
synced 2025-12-06 16:56:41 +08:00
625 lines
14 KiB
C
625 lines
14 KiB
C
/*
|
|
* This file is part of mtrace.
|
|
* Copyright (C) 2015 Stefani Seibold <stefani@seibold.net>
|
|
* This file is based on the ltrace source
|
|
*
|
|
* This work was sponsored by Rohde & Schwarz GmbH & Co. KG, Munich.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#define _GNU_SOURCE /* For getline. */
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/types.h>
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <link.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "backend.h"
|
|
#include "breakpoint.h"
|
|
#include "config.h"
|
|
#include "debug.h"
|
|
#include "event.h"
|
|
#include "library.h"
|
|
#include "mtelf.h"
|
|
#include "task.h"
|
|
|
|
/* /proc/pid doesn't exist just after the fork, and sometimes `mtrace'
|
|
* couldn't open it to find the executable. So it may be necessary to
|
|
* have a bit delay
|
|
*/
|
|
|
|
#define PROC_PID_FILE(VAR, FORMAT, PID) \
|
|
char VAR[strlen(FORMAT) + 6]; \
|
|
sprintf(VAR, FORMAT, PID)
|
|
|
|
/*
|
|
* Returns a (malloc'd) file name corresponding to a running pid
|
|
*/
|
|
char *pid2name(pid_t pid)
|
|
{
|
|
PROC_PID_FILE(proc_exe, "/proc/%d/exe", pid);
|
|
|
|
if (kill(pid, 0))
|
|
return NULL;
|
|
|
|
return strdup(proc_exe);
|
|
}
|
|
|
|
/*
|
|
* Returns a (malloc'd) file name corresponding to a running pid
|
|
*/
|
|
char *pid2cwd(pid_t pid)
|
|
{
|
|
int ret;
|
|
char fname[PATH_MAX];
|
|
PROC_PID_FILE(proc_cwd, "/proc/%d/cwd", pid);
|
|
|
|
ret = readlink(proc_cwd, fname, sizeof(fname) - 1);
|
|
if (ret == -1)
|
|
return NULL;
|
|
|
|
fname[ret] = 0;
|
|
|
|
return strdup(fname);
|
|
}
|
|
|
|
static FILE *open_status_file(pid_t pid)
|
|
{
|
|
PROC_PID_FILE(fn, "/proc/%d/status", pid);
|
|
/* Don't complain if we fail. This would typically happen
|
|
when the process is about to terminate, and these files are
|
|
not available anymore. This function is called from the
|
|
event loop, and we don't want to clutter the output just
|
|
because the process terminates. */
|
|
return fopen(fn, "r");
|
|
}
|
|
|
|
static char *find_line_starting(FILE *file, const char *prefix, size_t len)
|
|
{
|
|
char *line = NULL;
|
|
size_t line_len = 0;
|
|
while (!feof(file)) {
|
|
if (getline(&line, &line_len, file) < 0)
|
|
return NULL;
|
|
if (strncmp(line, prefix, len) == 0)
|
|
return line;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void each_line_starting(FILE *file, const char *prefix, void (*cb)(const char *line, const char *prefix, void *data), void *data)
|
|
{
|
|
size_t len = strlen(prefix);
|
|
char *line;
|
|
|
|
while ((line = find_line_starting(file, prefix, len)) != NULL) {
|
|
(*cb)(line, prefix, data);
|
|
free(line);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void process_leader_cb(const char *line, const char *prefix, void *data)
|
|
{
|
|
*(pid_t *)data = atoi(line + strlen(prefix));
|
|
}
|
|
|
|
pid_t process_leader(pid_t pid)
|
|
{
|
|
pid_t tgid = 0;
|
|
FILE *file = open_status_file(pid);
|
|
|
|
if (file != NULL) {
|
|
each_line_starting(file, "Tgid:\t", &process_leader_cb, &tgid);
|
|
fclose(file);
|
|
}
|
|
return tgid;
|
|
}
|
|
|
|
static int all_digits(const char *str)
|
|
{
|
|
while (isdigit(*str))
|
|
str++;
|
|
return !*str;
|
|
}
|
|
|
|
int process_tasks(pid_t pid, pid_t ** ret_tasks, size_t *ret_n)
|
|
{
|
|
pid_t *tasks = NULL;
|
|
size_t n = 0;
|
|
size_t alloc = 0;
|
|
|
|
PROC_PID_FILE(fn, "/proc/%d/task", pid);
|
|
DIR *d = opendir(fn);
|
|
|
|
if (!d)
|
|
return -1;
|
|
|
|
for(;;) {
|
|
struct dirent entry;
|
|
struct dirent *result;
|
|
|
|
if (readdir_r(d, &entry, &result) != 0) {
|
|
free(tasks);
|
|
return -1;
|
|
}
|
|
|
|
if (result == NULL)
|
|
break;
|
|
|
|
if (result->d_type == DT_DIR && all_digits(result->d_name)) {
|
|
pid_t npid = atoi(result->d_name);
|
|
|
|
if (n >= alloc) {
|
|
pid_t *ntasks;
|
|
|
|
alloc = n > 0 ? (2 * n) : 8;
|
|
|
|
ntasks = realloc(tasks, sizeof(*tasks) * alloc);
|
|
if (!ntasks) {
|
|
free(tasks);
|
|
return -1;
|
|
}
|
|
tasks = ntasks;
|
|
}
|
|
tasks[n++] = npid;
|
|
}
|
|
}
|
|
|
|
closedir(d);
|
|
|
|
*ret_tasks = tasks;
|
|
*ret_n = n;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* On native 64-bit system, we need to be careful when handling cross
|
|
* tracing. This select appropriate pointer depending on host and
|
|
* target architectures. XXX Really we should abstract this into the
|
|
* ABI object, as theorized about somewhere on pmachata/revamp
|
|
* branch. */
|
|
static void *select_32_64(struct task *task, void *p32, void *p64)
|
|
{
|
|
if (sizeof(long) == 4 || !task_is_64bit(task))
|
|
return p32;
|
|
else
|
|
return p64;
|
|
}
|
|
|
|
static int fetch_dyn64(struct task *task, arch_addr_t *addr, Elf64_Dyn *ret)
|
|
{
|
|
if (copy_from_proc(task, *addr, ret, sizeof(*ret)) != sizeof(*ret))
|
|
return -1;
|
|
*addr += sizeof(*ret);
|
|
return 0;
|
|
}
|
|
|
|
static int fetch_dyn32(struct task *task, arch_addr_t *addr, Elf64_Dyn *ret)
|
|
{
|
|
Elf32_Dyn dyn;
|
|
|
|
if (copy_from_proc(task, *addr, &dyn, sizeof(dyn)) != sizeof(dyn))
|
|
return -1;
|
|
|
|
*addr += sizeof(dyn);
|
|
ret->d_tag = dyn.d_tag;
|
|
ret->d_un.d_val = dyn.d_un.d_val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int (*dyn_fetcher(struct task *task)) (struct task *, arch_addr_t *, Elf64_Dyn *)
|
|
{
|
|
return select_32_64(task, fetch_dyn32, fetch_dyn64);
|
|
}
|
|
|
|
static int process_find_dynamic_entry_addr(struct task *leader, arch_addr_t src_addr, int d_tag, arch_addr_t *ret)
|
|
{
|
|
for(;;) {
|
|
Elf64_Dyn entry;
|
|
|
|
if (dyn_fetcher(leader) (leader, &src_addr, &entry) < 0 || entry.d_tag == DT_NULL) {
|
|
debug(DEBUG_FUNCTION, "Couldn't find address for dtag!");
|
|
return -1;
|
|
}
|
|
|
|
if (entry.d_tag == d_tag) {
|
|
*ret = ARCH_ADDR_T(entry.d_un.d_val);
|
|
|
|
debug(DEBUG_FUNCTION, "found address: %#lx in dtag %d", *ret, d_tag);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Our own type for representing 32-bit linkmap. We can't rely on the
|
|
* definition in link.h, because that's only accurate for our host
|
|
* architecture, not for target architecture (where the traced process
|
|
* runs). */
|
|
#define LT_LINK_MAP(BITS) \
|
|
{ \
|
|
Elf##BITS##_Addr l_addr; \
|
|
Elf##BITS##_Addr l_name; \
|
|
Elf##BITS##_Addr l_ld; \
|
|
Elf##BITS##_Addr l_next; \
|
|
Elf##BITS##_Addr l_prev; \
|
|
}
|
|
|
|
struct lt_link_map_32 LT_LINK_MAP(32);
|
|
struct lt_link_map_64 LT_LINK_MAP(64);
|
|
|
|
static int fetch_lm64(struct task *task, arch_addr_t addr, struct lt_link_map_64 *ret)
|
|
{
|
|
if (copy_from_proc(task, addr, ret, sizeof(*ret)) != sizeof(*ret))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int fetch_lm32(struct task *task, arch_addr_t addr, struct lt_link_map_64 *ret)
|
|
{
|
|
struct lt_link_map_32 lm;
|
|
|
|
if (copy_from_proc(task, addr, &lm, sizeof(lm)) != sizeof(lm))
|
|
return -1;
|
|
|
|
ret->l_addr = lm.l_addr;
|
|
ret->l_name = lm.l_name;
|
|
ret->l_ld = lm.l_ld;
|
|
ret->l_next = lm.l_next;
|
|
ret->l_prev = lm.l_prev;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int (*lm_fetcher(struct task *task)) (struct task *, arch_addr_t, struct lt_link_map_64 *)
|
|
{
|
|
return select_32_64(task, fetch_lm32, fetch_lm64);
|
|
}
|
|
|
|
/* The same as above holds for struct r_debug. */
|
|
#define LT_R_DEBUG(BITS) \
|
|
{ \
|
|
int r_version; \
|
|
Elf##BITS##_Addr r_map; \
|
|
Elf##BITS##_Addr r_brk; \
|
|
int r_state; \
|
|
Elf##BITS##_Addr r_ldbase; \
|
|
}
|
|
|
|
struct lt_r_debug_32 LT_R_DEBUG(32);
|
|
struct lt_r_debug_64 LT_R_DEBUG(64);
|
|
|
|
static int fetch_rd64(struct task *task, arch_addr_t addr, struct lt_r_debug_64 *ret)
|
|
{
|
|
if (copy_from_proc(task, addr, ret, sizeof(*ret)) != sizeof(*ret))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int fetch_rd32(struct task *task, arch_addr_t addr, struct lt_r_debug_64 *ret)
|
|
{
|
|
struct lt_r_debug_32 rd;
|
|
|
|
if (copy_from_proc(task, addr, &rd, sizeof(rd)) != sizeof(rd))
|
|
return -1;
|
|
|
|
ret->r_version = rd.r_version;
|
|
ret->r_map = rd.r_map;
|
|
ret->r_brk = rd.r_brk;
|
|
ret->r_state = rd.r_state;
|
|
ret->r_ldbase = rd.r_ldbase;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int (*rdebug_fetcher(struct task *task))(struct task *, arch_addr_t, struct lt_r_debug_64 *)
|
|
{
|
|
return select_32_64(task, fetch_rd32, fetch_rd64);
|
|
}
|
|
|
|
static int fetch_auxv64_entry(int fd, Elf64_auxv_t *ret)
|
|
{
|
|
/* Reaching EOF is as much problem as not reading whole
|
|
* entry. */
|
|
return read(fd, ret, sizeof(*ret)) == sizeof(*ret) ? 0 : -1;
|
|
}
|
|
|
|
static int fetch_auxv32_entry(int fd, Elf64_auxv_t *ret)
|
|
{
|
|
Elf32_auxv_t auxv;
|
|
|
|
if (read(fd, &auxv, sizeof(auxv)) != sizeof(auxv))
|
|
return -1;
|
|
|
|
ret->a_type = auxv.a_type;
|
|
ret->a_un.a_val = auxv.a_un.a_val;
|
|
return 0;
|
|
}
|
|
|
|
static int (*auxv_fetcher(struct task *task)) (int, Elf64_auxv_t *) {
|
|
return select_32_64(task, fetch_auxv32_entry, fetch_auxv64_entry);
|
|
}
|
|
|
|
static void linkmap_add(struct task *task, struct lt_r_debug_64 *dbg)
|
|
{
|
|
struct library *lib;
|
|
|
|
debug(DEBUG_FUNCTION, "pid=%d", task->pid);
|
|
|
|
if (!dbg || !dbg->r_map) {
|
|
debug(DEBUG_FUNCTION, "Debug structure or it's linkmap are NULL!");
|
|
return;
|
|
}
|
|
|
|
arch_addr_t addr = ARCH_ADDR_T(dbg->r_map);
|
|
|
|
while (addr) {
|
|
struct lt_link_map_64 rlm;
|
|
|
|
if (lm_fetcher(task) (task, addr, &rlm) < 0) {
|
|
fprintf(stderr, "Unable to read link map\n");
|
|
return;
|
|
}
|
|
|
|
addr = ARCH_ADDR_T(rlm.l_next);
|
|
|
|
if (rlm.l_name == 0) {
|
|
fprintf(stderr, "Name of mapped library is NULL\n");
|
|
continue;
|
|
}
|
|
|
|
/* Do we have that library already? */
|
|
lib = library_find_with_key(&task->libraries_list, ARCH_ADDR_T(rlm.l_addr));
|
|
if (lib)
|
|
continue;
|
|
|
|
char lib_name[PATH_MAX];
|
|
|
|
copy_str_from_proc(task, ARCH_ADDR_T(rlm.l_name), lib_name, sizeof(lib_name));
|
|
|
|
if (
|
|
strcmp(lib_name, "") == 0 ||
|
|
strcmp(lib_name, "linux-vdso.so.1") == 0 ||
|
|
strcmp(lib_name, "linux-gate.so.1") == 0 ||
|
|
strcmp(lib_name, "linux-vdso32.so.1") == 0 ||
|
|
strcmp(lib_name, "linux-vdso64.so.1") == 0
|
|
)
|
|
continue;
|
|
|
|
struct library *lib = library_new();
|
|
|
|
if (!lib) {
|
|
fprintf(stderr, "Couldn't instance library object %s\n", lib_name);
|
|
continue;
|
|
}
|
|
|
|
if (elf_read_library(task, lib, lib_name, rlm.l_addr) < 0) {
|
|
library_destroy(task, lib);
|
|
fprintf(stderr, "Couldn't load ELF object %s\n", lib_name);
|
|
continue;
|
|
}
|
|
|
|
lib->key = ARCH_ADDR_T(rlm.l_addr);
|
|
|
|
library_add(task, lib);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void linkmap_del(struct task *task, struct lt_r_debug_64 *dbg)
|
|
{
|
|
struct library *lib;
|
|
|
|
debug(DEBUG_FUNCTION, "pid=%d", task->pid);
|
|
|
|
if (!dbg || !dbg->r_map) {
|
|
debug(DEBUG_FUNCTION, "Debug structure or it's linkmap are NULL!");
|
|
return;
|
|
}
|
|
|
|
LIST_HEAD(tmp_list);
|
|
|
|
list_splice_init(&task->libraries_list, &tmp_list);
|
|
|
|
/* first entry in the map list is the binary itself */
|
|
list_move(tmp_list.next, &task->libraries_list);
|
|
|
|
arch_addr_t addr = ARCH_ADDR_T(dbg->r_map);
|
|
|
|
while (addr) {
|
|
struct lt_link_map_64 rlm;
|
|
|
|
if (lm_fetcher(task) (task, addr, &rlm) < 0) {
|
|
fprintf(stderr, "Unable to read link map\n");
|
|
return;
|
|
}
|
|
|
|
addr = ARCH_ADDR_T(rlm.l_next);
|
|
|
|
lib = library_find_with_key(&tmp_list, ARCH_ADDR_T(rlm.l_addr));
|
|
if (lib)
|
|
list_move_tail(&lib->list, &task->libraries_list);
|
|
}
|
|
|
|
library_delete_list(task, &tmp_list);
|
|
|
|
return;
|
|
}
|
|
|
|
static int load_debug_struct(struct task *task, arch_addr_t debug_addr, struct lt_r_debug_64 *ret)
|
|
|
|
{
|
|
debug(DEBUG_FUNCTION, "pid=%d", task->pid);
|
|
|
|
if (rdebug_fetcher(task) (task, debug_addr, ret) < 0) {
|
|
debug(DEBUG_FUNCTION, "This process does not have a debug structure!");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rdebug_bp_on_hit(struct task *task, struct breakpoint *bp)
|
|
{
|
|
struct lt_r_debug_64 rdbg;
|
|
|
|
debug(DEBUG_FUNCTION, "pid=%d", task->pid);
|
|
|
|
if (load_debug_struct(task, task->os.debug_addr, &rdbg) < 0)
|
|
return 0;
|
|
|
|
if (rdbg.r_state == RT_CONSISTENT) {
|
|
debug(DEBUG_FUNCTION, "Linkmap is now consistent");
|
|
switch (task->os.debug_state) {
|
|
case RT_ADD:
|
|
linkmap_add(task, &rdbg);
|
|
break;
|
|
case RT_DELETE:
|
|
linkmap_del(task, &rdbg);
|
|
break;
|
|
default:
|
|
debug(DEBUG_FUNCTION, "Unexpected debug state!");
|
|
}
|
|
}
|
|
|
|
task->os.debug_state = rdbg.r_state;
|
|
return 0;
|
|
}
|
|
|
|
int linkmap_init(struct task *task, arch_addr_t dyn_addr)
|
|
{
|
|
struct lt_r_debug_64 rdbg;
|
|
struct breakpoint *bp;
|
|
arch_addr_t debug_addr;
|
|
|
|
debug(DEBUG_FUNCTION, "pid=%d, dyn_addr=%#lx", task->pid, dyn_addr);
|
|
|
|
if (task->os.debug_addr)
|
|
return 0;
|
|
|
|
if (process_find_dynamic_entry_addr(task, dyn_addr, DT_DEBUG, &debug_addr) == -1) {
|
|
debug(DEBUG_FUNCTION, "Couldn't find debug structure!");
|
|
return -1;
|
|
}
|
|
|
|
if (!debug_addr)
|
|
return -1;
|
|
|
|
if (load_debug_struct(task, debug_addr, &rdbg) < 0)
|
|
return -1;
|
|
|
|
arch_addr_t addr = ARCH_ADDR_T(rdbg.r_brk);
|
|
|
|
bp = breakpoint_new(task, addr, NULL, 0);
|
|
if (!bp)
|
|
return -1;
|
|
|
|
bp->on_hit = rdebug_bp_on_hit;
|
|
bp->locked = 1;
|
|
|
|
breakpoint_enable(task, bp);
|
|
|
|
task->os.debug_addr = debug_addr;
|
|
|
|
if (rdbg.r_state == RT_CONSISTENT)
|
|
linkmap_add(task, &rdbg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int process_get_entry(struct task *task, unsigned long *entryp, unsigned long *interp_biasp)
|
|
{
|
|
PROC_PID_FILE(fn, "/proc/%d/auxv", task->pid);
|
|
int fd, ret;
|
|
|
|
fd = open(fn, O_RDONLY);
|
|
if (fd == -1)
|
|
goto fail;
|
|
|
|
GElf_Addr at_entry = 0;
|
|
GElf_Addr at_bias = 0;
|
|
|
|
while (1) {
|
|
Elf64_auxv_t entry = { };
|
|
if (auxv_fetcher(task)(fd, &entry) < 0)
|
|
goto fail;
|
|
|
|
if (entry.a_type == AT_NULL)
|
|
break;
|
|
|
|
switch (entry.a_type) {
|
|
case AT_BASE:
|
|
at_bias = entry.a_un.a_val;
|
|
break;
|
|
case AT_ENTRY:
|
|
at_entry = entry.a_un.a_val;
|
|
break;
|
|
case AT_NULL:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (entryp != NULL)
|
|
*entryp = at_entry;
|
|
if (interp_biasp != NULL)
|
|
*interp_biasp = at_bias;
|
|
|
|
ret = 0;
|
|
goto done;
|
|
fail:
|
|
fprintf(stderr, "couldn't read %s: %s", fn, strerror(errno));
|
|
ret = -1;
|
|
done:
|
|
if (fd != -1)
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
|
|
int os_task_init(struct task *task)
|
|
{
|
|
task->os.debug_addr = 0;
|
|
task->os.debug_state = RT_ADD;
|
|
return 0;
|
|
}
|
|
|
|
void os_task_destroy(struct task *task)
|
|
{
|
|
}
|
|
|
|
int os_task_clone(struct task *retp, struct task *task)
|
|
{
|
|
retp->os = task->os;
|
|
return 0;
|
|
}
|
|
|