void ltrace_init(int argc, char **argv) { struct opt_p_t *opt_p_tmp; atexit(normal_exit); signal(SIGINT, signal_exit); /* Detach processes when interrupted */ signal(SIGTERM, signal_exit); /* ... or killed */ argv = process_options(argc, argv); init_global_config(); while (opt_F) { /* If filename begins with ~, expand it to the user's home */ /* directory. This does not correctly handle ~yoda, but that */ /* isn't as bad as it seems because the shell will normally */ /* be doing the expansion for us; only the hardcoded */ /* ~/.ltrace.conf should ever use this code. */ if (opt_F->filename[0] == '~') { char path[PATH_MAX]; char *home_dir = getenv("HOME"); if (home_dir) { strncpy(path, home_dir, PATH_MAX - 1); path[PATH_MAX - 1] = '\0'; strncat(path, opt_F->filename + 1, PATH_MAX - strlen(path) - 1); read_config_file(path); } } else { read_config_file(opt_F->filename); } struct opt_F_t *next = opt_F->next; if (opt_F->own_filename) free(opt_F->filename); free(opt_F); opt_F = next; } if (command) { /* Check that the binary ABI is supported before * calling execute_program. */ struct ltelf lte = {}; open_elf(<e, command); do_close_elf(<e); pid_t pid = execute_program(command, argv); struct Process *proc = open_program(command, pid); if (proc == NULL) { fprintf(stderr, "couldn't open program '%s': %s\n", command, strerror(errno)); exit(EXIT_FAILURE); } trace_set_options(proc); continue_process(pid); } opt_p_tmp = opt_p; while (opt_p_tmp) { open_pid(opt_p_tmp->pid); opt_p_tmp = opt_p_tmp->next; } }
void ltrace_init(int argc, char **argv) { setlocale(LC_ALL, ""); struct opt_p_t *opt_p_tmp; atexit(normal_exit); signal(SIGINT, signal_exit); /* Detach processes when interrupted */ signal(SIGTERM, signal_exit); /* ... or killed */ argv = process_options(argc, argv); init_global_config(); if (command) { /* Check that the binary ABI is supported before * calling execute_program. */ { struct ltelf lte; if (ltelf_init(<e, command) == 0) ltelf_destroy(<e); else exit(EXIT_FAILURE); } pid_t pid = execute_program(command, argv); struct process *proc = open_program(command, pid); if (proc == NULL) { fprintf(stderr, "couldn't open program '%s': %s\n", command, strerror(errno)); exit(EXIT_FAILURE); } trace_set_options(proc); continue_process(pid); } opt_p_tmp = opt_p; while (opt_p_tmp) { open_pid(opt_p_tmp->pid); opt_p_tmp = opt_p_tmp->next; } }
static int trace(pid_t first_proc, int *first_exit_code) { for(;;) { int status; pid_t tid; int cpu_time; struct Process *process; /* Wait for a process */ #if NO_WAIT3 tid = waitpid(-1, &status, __WALL); cpu_time = -1; #else { struct rusage res; tid = wait3(&status, __WALL, &res); cpu_time = (res.ru_utime.tv_sec * 1000 + res.ru_utime.tv_usec / 1000); } #endif if(tid == -1) { /* LCOV_EXCL_START : internal error: waitpid() won't fail unless we * mistakingly call it while there is no child to wait for */ log_critical(0, "waitpid failed: %s", strerror(errno)); return -1; /* LCOV_EXCL_END */ } if(WIFEXITED(status) || WIFSIGNALED(status)) { unsigned int nprocs, unknown; int exitcode; if(WIFSIGNALED(status)) /* exit codes are 8 bits */ exitcode = 0x0100 | WTERMSIG(status); else exitcode = WEXITSTATUS(status); if(tid == first_proc && first_exit_code != NULL) *first_exit_code = exitcode; process = trace_find_process(tid); if(process != NULL) { int cpu_time_val = -1; if(process->tid == process->threadgroup->tgid) cpu_time_val = cpu_time; if(db_add_exit(process->identifier, exitcode, cpu_time_val) != 0) return -1; trace_free_process(process); } trace_count_processes(&nprocs, &unknown); if(verbosity >= 2) log_info(tid, "process exited (%s %d), CPU time %.2f, " "%d processes remain", (exitcode & 0x0100)?"signal":"code", exitcode & 0xFF, cpu_time * 0.001f, (unsigned int)nprocs); if(nprocs <= 0) break; if(unknown >= nprocs) { /* LCOV_EXCL_START : This can't happen because UNKNOWN * processes are the forked processes whose creator has not * returned yet. Therefore, if there is an UNKNOWN process, its * creator has to exist as well (and it is not UNKNOWN). */ log_critical(0, "only UNKNOWN processes remaining (%d)", (unsigned int)nprocs); return -1; /* LCOV_EXCL_END */ } continue; } process = trace_find_process(tid); if(process == NULL) { if(verbosity >= 3) log_debug(tid, "process appeared"); process = trace_get_empty_process(); process->status = PROCSTAT_UNKNOWN; process->flags = 0; process->tid = tid; process->threadgroup = NULL; process->in_syscall = 0; trace_set_options(tid); /* Don't resume, it will be set to ATTACHED and resumed when fork() * returns */ continue; } else if(process->status == PROCSTAT_ALLOCATED) { process->status = PROCSTAT_ATTACHED; if(verbosity >= 3) log_debug(tid, "process attached"); trace_set_options(tid); ptrace(PTRACE_SYSCALL, tid, NULL, NULL); if(verbosity >= 2) { unsigned int nproc, unknown; trace_count_processes(&nproc, &unknown); log_info(0, "%d processes (inc. %d unattached)", nproc, unknown); } continue; } if(WIFSTOPPED(status) && WSTOPSIG(status) & 0x80) { size_t len = 0; #ifdef I386 struct i386_regs regs; #else /* def X86_64 */ struct x86_64_regs regs; #endif /* Try to use GETREGSET first, since iov_len allows us to know if * 32bit or 64bit mode was used */ #ifdef PTRACE_GETREGSET #ifndef NT_PRSTATUS #define NT_PRSTATUS 1 #endif { struct iovec iov; iov.iov_base = ®s; iov.iov_len = sizeof(regs); if(ptrace(PTRACE_GETREGSET, tid, NT_PRSTATUS, &iov) == 0) len = iov.iov_len; } if(len == 0) #endif /* GETREGSET undefined or call failed, fallback on GETREGS */ { /* LCOV_EXCL_START : GETREGSET was added by Linux 2.6.34 in * May 2010 (2225a122) */ ptrace(PTRACE_GETREGS, tid, NULL, ®s); /* LCOV_EXCL_END */ } #if defined(I386) if(!process->in_syscall) process->current_syscall = regs.orig_eax; if(process->in_syscall) get_i386_reg(&process->retvalue, regs.eax); else { get_i386_reg(&process->params[0], regs.ebx); get_i386_reg(&process->params[1], regs.ecx); get_i386_reg(&process->params[2], regs.edx); get_i386_reg(&process->params[3], regs.esi); get_i386_reg(&process->params[4], regs.edi); get_i386_reg(&process->params[5], regs.ebp); } process->mode = MODE_I386; #elif defined(X86_64) /* On x86_64, process might be 32 or 64 bits */ /* If len is known (not 0) and not that of x86_64 registers, * or if len is not known (0) and CS is 0x23 (not as reliable) */ if( (len != 0 && len != sizeof(regs)) || (len == 0 && regs.cs == 0x23) ) { /* 32 bit mode */ struct i386_regs *x86regs = (struct i386_regs*)®s; if(!process->in_syscall) process->current_syscall = x86regs->orig_eax; if(process->in_syscall) get_i386_reg(&process->retvalue, x86regs->eax); else { get_i386_reg(&process->params[0], x86regs->ebx); get_i386_reg(&process->params[1], x86regs->ecx); get_i386_reg(&process->params[2], x86regs->edx); get_i386_reg(&process->params[3], x86regs->esi); get_i386_reg(&process->params[4], x86regs->edi); get_i386_reg(&process->params[5], x86regs->ebp); } process->mode = MODE_I386; } else { /* 64 bit mode */ if(!process->in_syscall) process->current_syscall = regs.orig_rax; if(process->in_syscall) get_x86_64_reg(&process->retvalue, regs.rax); else { get_x86_64_reg(&process->params[0], regs.rdi); get_x86_64_reg(&process->params[1], regs.rsi); get_x86_64_reg(&process->params[2], regs.rdx); get_x86_64_reg(&process->params[3], regs.r10); get_x86_64_reg(&process->params[4], regs.r8); get_x86_64_reg(&process->params[5], regs.r9); } /* Might still be either native x64 or Linux's x32 layer */ process->mode = MODE_X86_64; } #endif if(syscall_handle(process) != 0) return -1; } /* Handle signals */ else if(WIFSTOPPED(status)) { int signum = WSTOPSIG(status) & 0x7F; /* Synthetic signal for ptrace event: resume */ if(signum == SIGTRAP && status & 0xFF0000) { int event = status >> 16; if(event == PTRACE_EVENT_EXEC) { log_debug(tid, "got EVENT_EXEC, an execve() was successful and " "will return soon"); if(syscall_execve_event(process) != 0) return -1; } else if( (event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_VFORK) || (event == PTRACE_EVENT_CLONE)) { if(syscall_fork_event(process, event) != 0) return -1; } ptrace(PTRACE_SYSCALL, tid, NULL, NULL); } else if(signum == SIGTRAP) { /* LCOV_EXCL_START : Processes shouldn't be getting SIGTRAPs */ log_error(0, "NOT delivering SIGTRAP to %d\n" " waitstatus=0x%X", tid, status); ptrace(PTRACE_SYSCALL, tid, NULL, NULL); /* LCOV_EXCL_END */ } /* Other signal, let the process handle it */ else { siginfo_t si; if(verbosity >= 2) log_info(tid, "caught signal %d", signum); if(ptrace(PTRACE_GETSIGINFO, tid, 0, (long)&si) >= 0) ptrace(PTRACE_SYSCALL, tid, NULL, signum); else { /* LCOV_EXCL_START : Not sure what this is for... doesn't * seem to happen in practice */ log_error(tid, " NOT delivering: %s", strerror(errno)); if(signum != SIGSTOP) ptrace(PTRACE_SYSCALL, tid, NULL, NULL); /* LCOV_EXCL_END */ } } }
int process_clone(struct Process *retp, struct Process *proc, pid_t pid) { if (process_bare_init(retp, proc->filename, pid, 0) < 0) { fail1: fprintf(stderr, "failed to clone process %d->%d : %s\n", proc->pid, pid, strerror(errno)); return -1; } retp->tracesysgood = proc->tracesysgood; retp->e_machine = proc->e_machine; retp->e_class = proc->e_class; /* For non-leader processes, that's all we need to do. */ if (retp->leader != retp) return 0; /* Clone symbols first so that we can clone and relink * breakpoints. */ struct library *lib; struct library **nlibp = &retp->libraries; for (lib = proc->leader->libraries; lib != NULL; lib = lib->next) { *nlibp = malloc(sizeof(**nlibp)); if (*nlibp == NULL || library_clone(*nlibp, lib) < 0) { fail2: process_bare_destroy(retp, 0); /* Error when cloning. Unroll what was done. */ for (lib = retp->libraries; lib != NULL; ) { struct library *next = lib->next; library_destroy(lib); free(lib); lib = next; } goto fail1; } nlibp = &(*nlibp)->next; } /* Now clone breakpoints. Symbol relinking is done in * clone_single_bp. */ struct clone_single_bp_data data = { .old_proc = proc, .new_proc = retp, .error = 0, }; dict_apply_to_all(proc->leader->breakpoints, &clone_single_bp, &data); if (data.error < 0) goto fail2; /* And finally the call stack. */ /* XXX clearly the callstack handling should be moved to a * separate module and this whole business extracted to * callstack_clone, or callstack_element_clone. */ memcpy(retp->callstack, proc->callstack, sizeof(retp->callstack)); retp->callstack_depth = proc->callstack_depth; size_t i; for (i = 0; i < retp->callstack_depth; ++i) { struct callstack_element *elem = &retp->callstack[i]; struct fetch_context *ctx = elem->fetch_context; if (ctx != NULL) { struct fetch_context *nctx = fetch_arg_clone(retp, ctx); if (nctx == NULL) { size_t j; fail3: for (j = 0; j < i; ++j) { nctx = elem->fetch_context; fetch_arg_done(nctx); elem->fetch_context = NULL; } goto fail2; } elem->fetch_context = nctx; } struct value_dict *args = elem->arguments; if (args != NULL) { struct value_dict *nargs = malloc(sizeof(*nargs)); if (nargs == NULL || val_dict_clone(nargs, args) < 0) { size_t j; for (j = 0; j < i; ++j) { nargs = elem->arguments; val_dict_destroy(nargs); free(nargs); elem->arguments = NULL; } /* Pretend that this round went well, * so that fail3 frees I-th * fetch_context. */ ++i; goto fail3; } elem->arguments = nargs; } /* If it's not a syscall, we need to find the * corresponding library symbol in the cloned * library. */ if (!elem->is_syscall && elem->c_un.libfunc != NULL) { struct library_symbol *libfunc = elem->c_un.libfunc; int rc = proc_find_symbol(retp, libfunc, NULL, &elem->c_un.libfunc); assert(rc == 0); } } /* At this point, retp is fully initialized, except for OS and * arch parts, and we can call private_process_destroy. */ if (os_process_clone(retp, proc) < 0) { private_process_destroy(retp, 0); return -1; } if (arch_process_clone(retp, proc) < 0) { os_process_destroy(retp); private_process_destroy(retp, 0); return -1; } return 0; } static int open_one_pid(pid_t pid) { Process *proc; char *filename; debug(DEBUG_PROCESS, "open_one_pid(pid=%d)", pid); /* Get the filename first. Should the trace_pid fail, we can * easily free it, untracing is more work. */ if ((filename = pid2name(pid)) == NULL || trace_pid(pid) < 0) { fail: free(filename); return -1; } proc = open_program(filename, pid); if (proc == NULL) goto fail; free(filename); trace_set_options(proc); return 0; }