int proc_bkptset(struct proc_handle *phdl, uintptr_t address, unsigned long *saved) { struct ptrace_io_desc piod; unsigned long paddr, caddr; int ret = 0; *saved = 0; if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD || phdl->status == PS_IDLE) { errno = ENOENT; return (-1); } DPRINTFX("adding breakpoint at 0x%lx", address); if (phdl->status != PS_STOP) if (proc_stop(phdl) != 0) return (-1); /* * Read the original instruction. */ caddr = address; paddr = 0; piod.piod_op = PIOD_READ_I; piod.piod_offs = (void *)caddr; piod.piod_addr = &paddr; piod.piod_len = BREAKPOINT_INSTR_SZ; if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) { DPRINTF("ERROR: couldn't read instruction at address 0x%" PRIuPTR, address); ret = -1; goto done; } *saved = paddr; /* * Write a breakpoint instruction to that address. */ caddr = address; paddr = BREAKPOINT_INSTR; piod.piod_op = PIOD_WRITE_I; piod.piod_offs = (void *)caddr; piod.piod_addr = &paddr; piod.piod_len = BREAKPOINT_INSTR_SZ; if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) { DPRINTF("ERROR: couldn't write instruction at address 0x%" PRIuPTR, address); ret = -1; goto done; } done: if (phdl->status != PS_STOP) /* Restart the process if we had to stop it. */ proc_cont(phdl); return (ret); }
int proc_regset(struct proc_handle *phdl, proc_reg_t reg, unsigned long regvalue) { struct reg regs; if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD || phdl->status == PS_IDLE) { errno = ENOENT; return (-1); } if (ptrace(PT_GETREGS, proc_getpid(phdl), (caddr_t)®s, 0) < 0) return (-1); switch (reg) { case REG_PC: #if defined(__aarch64__) regs.elr = regvalue; #elif defined(__amd64__) regs.r_rip = regvalue; #elif defined(__arm__) regs.r_pc = regvalue; #elif defined(__i386__) regs.r_eip = regvalue; #elif defined(__mips__) regs.r_regs[PC] = regvalue; #elif defined(__powerpc__) regs.pc = regvalue; #elif defined(__riscv__) regs.sepc = regvalue; #endif break; case REG_SP: #if defined(__aarch64__) regs.sp = regvalue; #elif defined(__amd64__) regs.r_rsp = regvalue; #elif defined(__arm__) regs.r_sp = regvalue; #elif defined(__i386__) regs.r_esp = regvalue; #elif defined(__mips__) regs.r_regs[PC] = regvalue; #elif defined(__powerpc__) regs.fixreg[1] = regvalue; #elif defined(__riscv__) regs.sp = regvalue; #endif break; default: DPRINTFX("ERROR: no support for reg number %d", reg); return (-1); } if (ptrace(PT_SETREGS, proc_getpid(phdl), (caddr_t)®s, 0) < 0) return (-1); return (0); }
int proc_bkptdel(struct proc_handle *phdl, uintptr_t address, unsigned long saved) { struct ptrace_io_desc piod; unsigned long paddr, caddr; if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD || phdl->status == PS_IDLE) { errno = ENOENT; return (-1); } DPRINTF("removing breakpoint at 0x%lx\n", address); /* * Overwrite the breakpoint instruction that we setup previously. */ caddr = address; paddr = saved; piod.piod_op = PIOD_WRITE_I; piod.piod_offs = (void *)caddr; piod.piod_addr = &paddr; piod.piod_len = BREAKPOINT_INSTR_SZ; if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) { DPRINTF("ERROR: couldn't write instruction at address 0x%" PRIuPTR, address); return (-1); } return (0); }
int proc_regget(struct proc_handle *phdl, proc_reg_t reg, unsigned long *regvalue) { struct reg regs; if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD || phdl->status == PS_IDLE) { errno = ENOENT; return (-1); } memset(®s, 0, sizeof(regs)); if (ptrace(PT_GETREGS, proc_getpid(phdl), (caddr_t)®s, 0) < 0) return (-1); switch (reg) { case REG_PC: #if defined(__amd64__) *regvalue = regs.r_rip; #elif defined(__i386__) *regvalue = regs.r_eip; #endif break; case REG_SP: #if defined(__amd64__) *regvalue = regs.r_rsp; #elif defined(__i386__) *regvalue = regs.r_esp; #endif break; default: warn("ERROR: no support for reg number %d", reg); return (-1); } return (0); }
dt_proc_t * dt_proc_lookup(dtrace_hdl_t *dtp, struct ps_prochandle *P, int remove) { dt_proc_hash_t *dph = dtp->dt_procs; #if defined(sun) pid_t pid = Pstatus(P)->pr_pid; #else pid_t pid = proc_getpid(P); #endif dt_proc_t *dpr, **dpp = &dph->dph_hash[pid & (dph->dph_hashlen - 1)]; for (dpr = *dpp; dpr != NULL; dpr = dpr->dpr_hash) { if (dpr->dpr_pid == pid) break; else dpp = &dpr->dpr_hash; } assert(dpr != NULL); assert(dpr->dpr_proc == P); if (remove) *dpp = dpr->dpr_hash; /* remove from pid hash chain */ return (dpr); }
int proc_bkptset(struct proc_handle *phdl, uintptr_t address, unsigned long *saved) { struct ptrace_io_desc piod; unsigned long paddr, caddr; *saved = 0; if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD || phdl->status == PS_IDLE) { errno = ENOENT; return (-1); } /* * Read the original instruction. */ caddr = address; paddr = 0; piod.piod_op = PIOD_READ_I; piod.piod_offs = (void *)caddr; piod.piod_addr = &paddr; piod.piod_len = BREAKPOINT_INSTR_SZ; if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) { DPRINTF("ERROR: couldn't read instruction at address 0x%" PRIuPTR, address); return (-1); } *saved = paddr; /* * Write a breakpoint instruction to that address. */ caddr = address; paddr = BREAKPOINT_INSTR; piod.piod_op = PIOD_WRITE_I; piod.piod_offs = (void *)caddr; piod.piod_addr = &paddr; piod.piod_len = BREAKPOINT_INSTR_SZ; if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) { warn("ERROR: couldn't write instruction at address 0x%" PRIuPTR, address); return (-1); } return (0); }
static int proc_stop(struct proc_handle *phdl) { int status; if (kill(proc_getpid(phdl), SIGSTOP) == -1) { DPRINTF("kill %d", proc_getpid(phdl)); return (-1); } else if (waitpid(proc_getpid(phdl), &status, WSTOPPED) == -1) { DPRINTF("waitpid %d", proc_getpid(phdl)); return (-1); } else if (!WIFSTOPPED(status)) { DPRINTFX("waitpid: unexpected status 0x%x", status); return (-1); } return (0); }
static int dt_pid_has_jump_table(struct ps_prochandle *P, dtrace_hdl_t *dtp, uint8_t *text, fasttrap_probe_spec_t *ftp, const GElf_Sym *symp) { ulong_t i; int size; #if defined(sun) pid_t pid = Pstatus(P)->pr_pid; char dmodel = Pstatus(P)->pr_dmodel; #else pid_t pid = proc_getpid(P); #if __i386__ char dmodel = PR_MODEL_ILP32; #elif __amd64__ char dmodel = PR_MODEL_LP64; #endif #endif /* * Take a pass through the function looking for a register-dependant * jmp instruction. This could be a jump table so we have to be * ultra conservative. */ for (i = 0; i < ftp->ftps_size; i += size) { size = dt_instr_size(&text[i], dtp, pid, symp->st_value + i, dmodel); /* * Assume the worst if we hit an illegal instruction. */ if (size <= 0) { dt_dprintf("error at %#lx (assuming jump table)\n", i); return (1); } #ifdef notyet /* * Register-dependant jmp instructions start with a 0xff byte * and have the modrm.reg field set to 4. They can have an * optional REX prefix on the 64-bit ISA. */ if ((text[i] == 0xff && DT_MODRM_REG(text[i + 1]) == 4) || (dmodel == PR_MODEL_LP64 && (text[i] & 0xf0) == 0x40 && text[i + 1] == 0xff && DT_MODRM_REG(text[i + 2]) == 4)) { dt_dprintf("found a suspected jump table at %s:%lx\n", ftp->ftps_func, i); return (1); } #endif } return (0); }
struct ps_prochandle * dtrace_proc_create(dtrace_hdl_t *dtp, const char *file, char *const *argv) { dt_ident_t *idp = dt_idhash_lookup(dtp->dt_macros, "target"); struct ps_prochandle *P = dt_proc_create(dtp, file, argv); if (P != NULL && idp != NULL && idp->di_id == 0) #if defined(sun) idp->di_id = Pstatus(P)->pr_pid; /* $target = created pid */ #else idp->di_id = proc_getpid(P); /* $target = created pid */ #endif return (P); }
prmap_t * proc_name2map(struct proc_handle *p, const char *name) { size_t i; int cnt; prmap_t *map; char tmppath[MAXPATHLEN]; struct kinfo_vmentry *kves, *kve; rd_loadobj_t *rdl; /* * If we haven't iterated over the list of loaded objects, * librtld_db isn't yet initialized and it's very likely * that librtld_db called us. We need to do the heavy * lifting here to find the symbol librtld_db is looking for. */ if (p->nobjs == 0) { if ((kves = kinfo_getvmmap(proc_getpid(p), &cnt)) == NULL) return (NULL); for (i = 0; i < (size_t)cnt; i++) { kve = kves + i; basename_r(kve->kve_path, tmppath); if (strcmp(tmppath, name) == 0) { map = proc_addr2map(p, kve->kve_start); free(kves); return (map); } } free(kves); return (NULL); } if (name == NULL || strcmp(name, "a.out") == 0) { map = proc_addr2map(p, p->rdobjs[0].rdl_saddr); return (map); } for (i = 0; i < p->nobjs; i++) { rdl = &p->rdobjs[i]; basename_r(rdl->rdl_path, tmppath); if (strcmp(tmppath, name) == 0) { if ((map = malloc(sizeof(*map))) == NULL) return (NULL); proc_rdl2prmap(rdl, map); return (map); } } return (NULL); }
int proc_bkptdel(struct proc_handle *phdl, uintptr_t address, unsigned long saved) { struct ptrace_io_desc piod; uintptr_t caddr; int ret = 0, stopped; instr_t instr; if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD || phdl->status == PS_IDLE) { errno = ENOENT; return (-1); } DPRINTFX("removing breakpoint at 0x%lx", address); stopped = 0; if (phdl->status != PS_STOP) { if (proc_stop(phdl) != 0) return (-1); stopped = 1; } /* * Overwrite the breakpoint instruction that we setup previously. */ caddr = address; instr = saved; piod.piod_op = PIOD_WRITE_I; piod.piod_offs = (void *)caddr; piod.piod_addr = &instr; piod.piod_len = BREAKPOINT_INSTR_SZ; if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) { DPRINTF("ERROR: couldn't write instruction at address 0x%" PRIuPTR, address); ret = -1; } if (stopped) /* Restart the process if we had to stop it. */ proc_continue(phdl); return (ret); }
/* * Step over the breakpoint. */ int proc_bkptexec(struct proc_handle *phdl, unsigned long saved) { unsigned long pc; unsigned long samesaved; int status; if (proc_regget(phdl, REG_PC, &pc) < 0) { warn("ERROR: couldn't get PC register"); return (-1); } proc_bkptregadj(&pc); if (proc_bkptdel(phdl, pc, saved) < 0) { warn("ERROR: couldn't delete breakpoint"); return (-1); } /* * Go back in time and step over the new instruction just * set up by proc_bkptdel(). */ proc_regset(phdl, REG_PC, pc); if (ptrace(PT_STEP, proc_getpid(phdl), (caddr_t)1, 0) < 0) { warn("ERROR: ptrace step failed"); return (-1); } proc_wstatus(phdl); status = proc_getwstat(phdl); if (!WIFSTOPPED(status)) { warn("ERROR: don't know why process stopped"); return (-1); } /* * Restore the breakpoint. The saved instruction should be * the same as the one that we were passed in. */ if (proc_bkptset(phdl, pc, &samesaved) < 0) { warn("ERROR: couldn't restore breakpoint"); return (-1); } assert(samesaved == saved); return (0); }
static int dt_pid_create_usdt_probes(dtrace_probedesc_t *pdp, dtrace_hdl_t *dtp, dt_pcb_t *pcb, dt_proc_t *dpr) { struct ps_prochandle *P = dpr->dpr_proc; int ret = 0; assert(DT_MUTEX_HELD(&dpr->dpr_lock)); #if defined(sun) (void) Pupdate_maps(P); if (Pobject_iter(P, dt_pid_usdt_mapping, P) != 0) { ret = -1; (void) dt_pid_error(dtp, pcb, dpr, NULL, D_PROC_USDT, "failed to instantiate probes for pid %d: %s", #if defined(sun) (int)Pstatus(P)->pr_pid, strerror(errno)); #else (int)proc_getpid(P), strerror(errno)); #endif }
/*ARGSUSED*/ int dt_pid_create_return_probe(struct ps_prochandle *P, dtrace_hdl_t *dtp, fasttrap_probe_spec_t *ftp, const GElf_Sym *symp, uint64_t *stret) { uint8_t *text; ulong_t i, end; int size; #if defined(sun) pid_t pid = Pstatus(P)->pr_pid; char dmodel = Pstatus(P)->pr_dmodel; #else pid_t pid = proc_getpid(P); #if __i386__ char dmodel = PR_MODEL_ILP32; #elif __amd64__ char dmodel = PR_MODEL_LP64; #endif #endif /* * We allocate a few extra bytes at the end so we don't have to check * for overrunning the buffer. */ if ((text = calloc(1, symp->st_size + 4)) == NULL) { dt_dprintf("mr sparkle: malloc() failed\n"); return (DT_PROC_ERR); } if (Pread(P, text, symp->st_size, symp->st_value) != symp->st_size) { dt_dprintf("mr sparkle: Pread() failed\n"); free(text); return (DT_PROC_ERR); } ftp->ftps_type = DTFTP_RETURN; ftp->ftps_pc = (uintptr_t)symp->st_value; ftp->ftps_size = (size_t)symp->st_size; ftp->ftps_noffs = 0; /* * If there's a jump table in the function we're only willing to * instrument these specific (and equivalent) instruction sequences: * leave * [rep] ret * and * movl %ebp,%esp * popl %ebp * [rep] ret * * We do this to avoid accidentally interpreting jump table * offsets as actual instructions. */ if (dt_pid_has_jump_table(P, dtp, text, ftp, symp)) { for (i = 0, end = ftp->ftps_size; i < end; i += size) { size = dt_instr_size(&text[i], dtp, pid, symp->st_value + i, dmodel); /* bail if we hit an invalid opcode */ if (size <= 0) break; if (text[i] == DT_LEAVE && text[i + 1] == DT_RET) { dt_dprintf("leave/ret at %lx\n", i + 1); ftp->ftps_offs[ftp->ftps_noffs++] = i + 1; size = 2; } else if (text[i] == DT_LEAVE && text[i + 1] == DT_REP && text[i + 2] == DT_RET) { dt_dprintf("leave/rep ret at %lx\n", i + 1); ftp->ftps_offs[ftp->ftps_noffs++] = i + 1; size = 3; } else if (*(uint16_t *)&text[i] == DT_MOVL_EBP_ESP && text[i + 2] == DT_POPL_EBP && text[i + 3] == DT_RET) { dt_dprintf("movl/popl/ret at %lx\n", i + 3); ftp->ftps_offs[ftp->ftps_noffs++] = i + 3; size = 4; } else if (*(uint16_t *)&text[i] == DT_MOVL_EBP_ESP && text[i + 2] == DT_POPL_EBP && text[i + 3] == DT_REP && text[i + 4] == DT_RET) { dt_dprintf("movl/popl/rep ret at %lx\n", i + 3); ftp->ftps_offs[ftp->ftps_noffs++] = i + 3; size = 5; } } } else { for (i = 0, end = ftp->ftps_size; i < end; i += size) { size = dt_instr_size(&text[i], dtp, pid, symp->st_value + i, dmodel); /* bail if we hit an invalid opcode */ if (size <= 0) break; /* ordinary ret */ if (size == 1 && text[i] == DT_RET) goto is_ret; /* two-byte ret */ if (size == 2 && text[i] == DT_REP && text[i + 1] == DT_RET) goto is_ret; /* ret <imm16> */ if (size == 3 && text[i] == DT_RET16) goto is_ret; /* two-byte ret <imm16> */ if (size == 4 && text[i] == DT_REP && text[i + 1] == DT_RET16) goto is_ret; /* 32-bit displacement jmp outside of the function */ if (size == 5 && text[i] == DT_JMP32 && symp->st_size <= (uintptr_t)(i + size + *(int32_t *)&text[i + 1])) goto is_ret; /* 8-bit displacement jmp outside of the function */ if (size == 2 && text[i] == DT_JMP8 && symp->st_size <= (uintptr_t)(i + size + *(int8_t *)&text[i + 1])) goto is_ret; /* 32-bit disp. conditional jmp outside of the func. */ if (size == 6 && DT_ISJ32(*(uint16_t *)&text[i]) && symp->st_size <= (uintptr_t)(i + size + *(int32_t *)&text[i + 2])) goto is_ret; /* 8-bit disp. conditional jmp outside of the func. */ if (size == 2 && DT_ISJ8(text[i]) && symp->st_size <= (uintptr_t)(i + size + *(int8_t *)&text[i + 1])) goto is_ret; continue; is_ret: dt_dprintf("return at offset %lx\n", i); ftp->ftps_offs[ftp->ftps_noffs++] = i; } } free(text); if (ftp->ftps_noffs > 0) { if (ioctl(dtp->dt_ftfd, FASTTRAPIOC_MAKEPROBE, ftp) != 0) { dt_dprintf("fasttrap probe creation ioctl failed: %s\n", strerror(errno)); return (dt_set_errno(dtp, errno)); } } return (ftp->ftps_noffs); }
/*ARGSUSED*/ static void prochandler(struct ps_prochandle *P, const char *msg, void *arg) { #if defined(sun) const psinfo_t *prp = Ppsinfo(P); int pid = Pstatus(P)->pr_pid; char name[SIG2STR_MAX]; #else int wstatus = proc_getwstat(P); int pid = proc_getpid(P); #endif if (msg != NULL) { notice("pid %d: %s\n", pid, msg); return; } #if defined(sun) switch (Pstate(P)) { #else switch (proc_state(P)) { #endif case PS_UNDEAD: #if defined(sun) /* * Ideally we would like to always report pr_wstat here, but it * isn't possible given current /proc semantics. If we grabbed * the process, Ppsinfo() will either fail or return a zeroed * psinfo_t depending on how far the parent is in reaping it. * When /proc provides a stable pr_wstat in the status file, * this code can be improved by examining this new pr_wstat. */ if (prp != NULL && WIFSIGNALED(prp->pr_wstat)) { notice("pid %d terminated by %s\n", pid, proc_signame(WTERMSIG(prp->pr_wstat), name, sizeof (name))); #else if (WIFSIGNALED(wstatus)) { notice("pid %d terminated by %d\n", pid, WTERMSIG(wstatus)); #endif #if defined(sun) } else if (prp != NULL && WEXITSTATUS(prp->pr_wstat) != 0) { notice("pid %d exited with status %d\n", pid, WEXITSTATUS(prp->pr_wstat)); #else } else if (WEXITSTATUS(wstatus) != 0) { notice("pid %d exited with status %d\n", pid, WEXITSTATUS(wstatus)); #endif } else { notice("pid %d has exited\n", pid); } g_pslive--; break; case PS_LOST: notice("pid %d exec'd a set-id or unobservable program\n", pid); g_pslive--; break; } } /*ARGSUSED*/ static int errhandler(const dtrace_errdata_t *data, void *arg) { error(data->dteda_msg); return (DTRACE_HANDLE_OK); } /*ARGSUSED*/ static int drophandler(const dtrace_dropdata_t *data, void *arg) { error(data->dtdda_msg); return (DTRACE_HANDLE_OK); }
/*ARGSUSED*/ int dt_pid_create_glob_offset_probes(struct ps_prochandle *P, dtrace_hdl_t *dtp, fasttrap_probe_spec_t *ftp, const GElf_Sym *symp, const char *pattern) { uint8_t *text; int size; ulong_t i, end = symp->st_size; #if defined(sun) pid_t pid = Pstatus(P)->pr_pid; char dmodel = Pstatus(P)->pr_dmodel; #else pid_t pid = proc_getpid(P); #if __i386__ char dmodel = PR_MODEL_ILP32; #elif __amd64__ char dmodel = PR_MODEL_LP64; #endif #endif ftp->ftps_type = DTFTP_OFFSETS; ftp->ftps_pc = (uintptr_t)symp->st_value; ftp->ftps_size = (size_t)symp->st_size; ftp->ftps_noffs = 0; if ((text = malloc(symp->st_size)) == NULL) { dt_dprintf("mr sparkle: malloc() failed\n"); return (DT_PROC_ERR); } if (Pread(P, text, symp->st_size, symp->st_value) != symp->st_size) { dt_dprintf("mr sparkle: Pread() failed\n"); free(text); return (DT_PROC_ERR); } /* * We can't instrument offsets in functions with jump tables as * we might interpret a jump table offset as an instruction. */ if (dt_pid_has_jump_table(P, dtp, text, ftp, symp)) { free(text); return (0); } if (strcmp("*", pattern) == 0) { for (i = 0; i < end; i += size) { ftp->ftps_offs[ftp->ftps_noffs++] = i; size = dt_instr_size(&text[i], dtp, pid, symp->st_value + i, dmodel); /* bail if we hit an invalid opcode */ if (size <= 0) break; } } else { char name[sizeof (i) * 2 + 1]; for (i = 0; i < end; i += size) { (void) snprintf(name, sizeof (name), "%lx", i); if (gmatch(name, pattern)) ftp->ftps_offs[ftp->ftps_noffs++] = i; size = dt_instr_size(&text[i], dtp, pid, symp->st_value + i, dmodel); /* bail if we hit an invalid opcode */ if (size <= 0) break; } } free(text); if (ftp->ftps_noffs > 0) { if (ioctl(dtp->dt_ftfd, FASTTRAPIOC_MAKEPROBE, ftp) != 0) { dt_dprintf("fasttrap probe creation ioctl failed: %s\n", strerror(errno)); return (dt_set_errno(dtp, errno)); } } return (ftp->ftps_noffs); }
/* * Main loop for all victim process control threads. We initialize all the * appropriate /proc control mechanisms, and then enter a loop waiting for * the process to stop on an event or die. We process any events by calling * appropriate subroutines, and exit when the victim dies or we lose control. * * The control thread synchronizes the use of dpr_proc with other libdtrace * threads using dpr_lock. We hold the lock for all of our operations except * waiting while the process is running: this is accomplished by writing a * PCWSTOP directive directly to the underlying /proc/<pid>/ctl file. If the * libdtrace client wishes to exit or abort our wait, SIGCANCEL can be used. */ static void * dt_proc_control(void *arg) { dt_proc_control_data_t *datap = arg; dtrace_hdl_t *dtp = datap->dpcd_hdl; dt_proc_t *dpr = datap->dpcd_proc; dt_proc_hash_t *dph = dpr->dpr_hdl->dt_procs; struct ps_prochandle *P = dpr->dpr_proc; #if defined(sun) int pfd = Pctlfd(P); const long wstop = PCWSTOP; #endif int notify = B_FALSE; /* * We disable the POSIX thread cancellation mechanism so that the * client program using libdtrace can't accidentally cancel our thread. * dt_proc_destroy() uses SIGCANCEL explicitly to simply poke us out * of PCWSTOP with EINTR, at which point we will see dpr_quit and exit. */ (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); dpr->dpr_pid = proc_getpid(P); int pid = dpr->dpr_pid; /* * Set up the corresponding process for tracing by libdtrace. We want * to be able to catch breakpoints and efficiently single-step over * them, and we need to enable librtld_db to watch libdl activity. */ do_ptrace(__func__, PTRACE_ATTACH, dpr->dpr_pid, 0, 0); (void) pthread_mutex_lock(&dpr->dpr_lock); (void) Punsetflags(P, PR_ASYNC); /* require synchronous mode */ (void) Psetflags(P, PR_BPTADJ); /* always adjust eip on x86 */ (void) Punsetflags(P, PR_FORK); /* do not inherit on fork */ (void) Pfault(P, FLTBPT, B_TRUE); /* always trace breakpoints */ (void) Pfault(P, FLTTRACE, B_TRUE); /* always trace single-step */ /* * We must trace exit from exec() system calls so that if the exec is * successful, we can reset our breakpoints and re-initialize libproc. */ (void) Psysexit(P, SYS_exec, B_TRUE); (void) Psysexit(P, SYS_execve, B_TRUE); /* * We must trace entry and exit for fork() system calls in order to * disable our breakpoints temporarily during the fork. We do not set * the PR_FORK flag, so if fork succeeds the child begins executing and * does not inherit any other tracing behaviors or a control thread. */ (void) Psysentry(P, SYS_vfork, B_TRUE); (void) Psysexit(P, SYS_vfork, B_TRUE); (void) Psysentry(P, SYS_fork1, B_TRUE); (void) Psysexit(P, SYS_fork1, B_TRUE); (void) Psysentry(P, SYS_forkall, B_TRUE); (void) Psysexit(P, SYS_forkall, B_TRUE); (void) Psysentry(P, SYS_forksys, B_TRUE); (void) Psysexit(P, SYS_forksys, B_TRUE); Psync(P); /* enable all /proc changes */ dt_proc_attach(dpr, B_FALSE); /* enable rtld breakpoints */ /* * If PR_KLC is set, we created the process; otherwise we grabbed it. * Check for an appropriate stop request and wait for dt_proc_continue. */ dpr->dpr_stop |= DT_PROC_STOP_CREATE; if (Pstatus(P)->pr_flags & PR_KLC) dt_proc_stop(dpr, DT_PROC_STOP_CREATE); else dt_proc_stop(dpr, DT_PROC_STOP_GRAB); if (Psetrun(P, 0, 0) == -1) { dt_dprintf("pid %d: failed to set running: %s\n", (int)dpr->dpr_pid, strerror(errno)); } (void) pthread_mutex_unlock(&dpr->dpr_lock); /* * Wait for the process corresponding to this control thread to stop, * process the event, and then set it running again. We want to sleep * with dpr_lock *unheld* so that other parts of libdtrace can use the * ps_prochandle in the meantime (e.g. ustack()). To do this, we write * a PCWSTOP directive directly to the underlying /proc/<pid>/ctl file. * Once the process stops, we wake up, grab dpr_lock, and then call * Pwait() (which will return immediately) and do our processing. */ //printf("%s: waiting to quit\n", __func__); while (!dpr->dpr_quit) { const lwpstatus_t *psp; #if defined(sun) if (write(pfd, &wstop, sizeof (wstop)) == -1 && errno == EINTR) continue; /* check dpr_quit and continue waiting */ #else /* Wait for the process to report status. */ proc_wait(P); #endif (void) pthread_mutex_lock(&dpr->dpr_lock); pwait_locked: if (Pstopstatus(P, PCNULL, 0) == -1 && errno == EINTR) { //printf("%s stopstatus (loop) pr_pid pid=%d\n", __func__, Pstatus(dpr->dpr_proc)->pr_pid); (void) pthread_mutex_unlock(&dpr->dpr_lock); continue; /* check dpr_quit and continue waiting */ } switch (Pstate(P)) { case PS_STOP: psp = &Pstatus(P)->pr_lwp; dt_dprintf("pid %d: proc stopped showing %d/%d\n", pid, psp->pr_why, psp->pr_what); #if defined(sun) /* * If the process stops showing PR_REQUESTED, then the * DTrace stop() action was applied to it or another * debugging utility (e.g. pstop(1)) asked it to stop. * In either case, the user's intention is for the * process to remain stopped until another external * mechanism (e.g. prun(1)) is applied. So instead of * setting the process running ourself, we wait for * someone else to do so. Once that happens, we return * to our normal loop waiting for an event of interest. */ if (psp->pr_why == PR_REQUESTED) { dt_proc_waitrun(dpr); (void) pthread_mutex_unlock(&dpr->dpr_lock); continue; } /* * If the process stops showing one of the events that * we are tracing, perform the appropriate response. * Note that we ignore PR_SUSPENDED, PR_CHECKPOINT, and * PR_JOBCONTROL by design: if one of these conditions * occurs, we will fall through to Psetrun() but the * process will remain stopped in the kernel by the * corresponding mechanism (e.g. job control stop). */ if (psp->pr_why == PR_FAULTED && psp->pr_what == FLTBPT) dt_proc_bpmatch(dtp, dpr); else if (psp->pr_why == PR_SYSENTRY && IS_SYS_FORK(psp->pr_what)) dt_proc_bpdisable(dpr); else if (psp->pr_why == PR_SYSEXIT && IS_SYS_FORK(psp->pr_what)) dt_proc_bpenable(dpr); else if (psp->pr_why == PR_SYSEXIT && IS_SYS_EXEC(psp->pr_what)) dt_proc_attach(dpr, B_TRUE); #endif //printf("In PS_STOP dpr_stop=%x\n", dpr->dpr_stop); break; case PS_LOST: //printf("in PS_LOST\n"); if (Preopen(P) == 0) goto pwait_locked; dt_dprintf("pid %d: proc lost: %s\n", pid, strerror(errno)); dpr->dpr_quit = B_TRUE; notify = B_TRUE; break; case PS_UNDEAD: case PS_DEAD: dt_dprintf("pid %d: proc died\n", pid); dpr->dpr_quit = B_TRUE; notify = B_TRUE; break; } if (Pstate(P) != PS_UNDEAD && Psetrun(P, 0, 0) == -1) { dt_dprintf("pid %d: failed to set running: %s\n", (int)dpr->dpr_pid, strerror(errno)); } (void) pthread_mutex_unlock(&dpr->dpr_lock); } /* * If the control thread detected PS_UNDEAD or PS_LOST, then enqueue * the dt_proc_t structure on the dt_proc_hash_t notification list. */ if (notify) dt_proc_notify(dtp, dph, dpr, NULL); /* * Destroy and remove any remaining breakpoints, set dpr_done and clear * dpr_tid to indicate the control thread has exited, and notify any * waiting thread in dt_proc_destroy() that we have succesfully exited. */ (void) pthread_mutex_lock(&dpr->dpr_lock); dt_proc_bpdestroy(dpr, B_TRUE); dpr->dpr_done = B_TRUE; dpr->dpr_tid = 0; (void) pthread_cond_broadcast(&dpr->dpr_cv); (void) pthread_mutex_unlock(&dpr->dpr_lock); return (NULL); }
static void proc_cont(struct proc_handle *phdl) { ptrace(PT_CONTINUE, proc_getpid(phdl), (caddr_t)1, 0); }
static int dt_pid_per_sym(dt_pid_probe_t *pp, const GElf_Sym *symp, const char *func) { dtrace_hdl_t *dtp = pp->dpp_dtp; dt_pcb_t *pcb = pp->dpp_pcb; dt_proc_t *dpr = pp->dpp_dpr; fasttrap_probe_spec_t *ftp; uint64_t off; char *end; uint_t nmatches = 0; ulong_t sz; int glob, err; int isdash = strcmp("-", func) == 0; pid_t pid; #if defined(sun) pid = Pstatus(pp->dpp_pr)->pr_pid; #else pid = proc_getpid(pp->dpp_pr); #endif dt_dprintf("creating probe pid%d:%s:%s:%s\n", (int)pid, pp->dpp_obj, func, pp->dpp_name); sz = sizeof (fasttrap_probe_spec_t) + (isdash ? 4 : (symp->st_size - 1) * sizeof (ftp->ftps_offs[0])); if ((ftp = dt_alloc(dtp, sz)) == NULL) { dt_dprintf("proc_per_sym: dt_alloc(%lu) failed\n", sz); return (1); /* errno is set for us */ } ftp->ftps_pid = pid; (void) strncpy(ftp->ftps_func, func, sizeof (ftp->ftps_func)); dt_pid_objname(ftp->ftps_mod, sizeof (ftp->ftps_mod), pp->dpp_lmid, pp->dpp_obj); if (!isdash && gmatch("return", pp->dpp_name)) { if (dt_pid_create_return_probe(pp->dpp_pr, dtp, ftp, symp, pp->dpp_stret) < 0) { return (dt_pid_error(dtp, pcb, dpr, ftp, D_PROC_CREATEFAIL, "failed to create return probe " "for '%s': %s", func, dtrace_errmsg(dtp, dtrace_errno(dtp)))); } nmatches++; } if (!isdash && gmatch("entry", pp->dpp_name)) { if (dt_pid_create_entry_probe(pp->dpp_pr, dtp, ftp, symp) < 0) { return (dt_pid_error(dtp, pcb, dpr, ftp, D_PROC_CREATEFAIL, "failed to create entry probe " "for '%s': %s", func, dtrace_errmsg(dtp, dtrace_errno(dtp)))); } nmatches++; } glob = strisglob(pp->dpp_name); if (!glob && nmatches == 0) { off = strtoull(pp->dpp_name, &end, 16); if (*end != '\0') { return (dt_pid_error(dtp, pcb, dpr, ftp, D_PROC_NAME, "'%s' is an invalid probe name", pp->dpp_name)); } if (off >= symp->st_size) { return (dt_pid_error(dtp, pcb, dpr, ftp, D_PROC_OFF, "offset 0x%llx outside of function '%s'", (u_longlong_t)off, func)); } err = dt_pid_create_offset_probe(pp->dpp_pr, pp->dpp_dtp, ftp, symp, off); if (err == DT_PROC_ERR) { return (dt_pid_error(dtp, pcb, dpr, ftp, D_PROC_CREATEFAIL, "failed to create probe at " "'%s+0x%llx': %s", func, (u_longlong_t)off, dtrace_errmsg(dtp, dtrace_errno(dtp)))); } if (err == DT_PROC_ALIGN) { return (dt_pid_error(dtp, pcb, dpr, ftp, D_PROC_ALIGN, "offset 0x%llx is not aligned on an instruction", (u_longlong_t)off)); } nmatches++; } else if (glob && !isdash) { if (dt_pid_create_glob_offset_probes(pp->dpp_pr, pp->dpp_dtp, ftp, symp, pp->dpp_name) < 0) { return (dt_pid_error(dtp, pcb, dpr, ftp, D_PROC_CREATEFAIL, "failed to create offset probes in '%s': %s", func, dtrace_errmsg(dtp, dtrace_errno(dtp)))); } nmatches++; } pp->dpp_nmatches += nmatches; dt_free(dtp, ftp); return (0); }
/*ARGSUSED*/ int dt_pid_create_offset_probe(struct ps_prochandle *P, dtrace_hdl_t *dtp, fasttrap_probe_spec_t *ftp, const GElf_Sym *symp, ulong_t off) { ftp->ftps_type = DTFTP_OFFSETS; ftp->ftps_pc = (uintptr_t)symp->st_value; ftp->ftps_size = (size_t)symp->st_size; ftp->ftps_noffs = 1; if (strcmp("-", ftp->ftps_func) == 0) { ftp->ftps_offs[0] = off; } else { uint8_t *text; ulong_t i; int size; #if defined(sun) pid_t pid = Pstatus(P)->pr_pid; char dmodel = Pstatus(P)->pr_dmodel; #else pid_t pid = proc_getpid(P); #if __i386__ char dmodel = PR_MODEL_ILP32; #elif __amd64__ char dmodel = PR_MODEL_LP64; #endif #endif if ((text = malloc(symp->st_size)) == NULL) { dt_dprintf("mr sparkle: malloc() failed\n"); return (DT_PROC_ERR); } if (Pread(P, text, symp->st_size, symp->st_value) != symp->st_size) { dt_dprintf("mr sparkle: Pread() failed\n"); free(text); return (DT_PROC_ERR); } /* * We can't instrument offsets in functions with jump tables * as we might interpret a jump table offset as an * instruction. */ if (dt_pid_has_jump_table(P, dtp, text, ftp, symp)) { free(text); return (0); } for (i = 0; i < symp->st_size; i += size) { if (i == off) { ftp->ftps_offs[0] = i; break; } /* * If we've passed the desired offset without a * match, then the given offset must not lie on a * instruction boundary. */ if (i > off) { free(text); return (DT_PROC_ALIGN); } size = dt_instr_size(&text[i], dtp, pid, symp->st_value + i, dmodel); /* * If we hit an invalid instruction, bail as if we * couldn't find the offset. */ if (size <= 0) { free(text); return (DT_PROC_ALIGN); } } free(text); } if (ioctl(dtp->dt_ftfd, FASTTRAPIOC_MAKEPROBE, ftp) != 0) { dt_dprintf("fasttrap probe creation ioctl failed: %s\n", strerror(errno)); return (dt_set_errno(dtp, errno)); } return (ftp->ftps_noffs); }