bool thread_trap(thread_t * thread) { pt_regs regs; address_t ip, jump_target; if (unlikely(!thread_get_regs(thread, ®s))) { /* Thread died while reading registers. Consider this case as handled. */ return true; } ip = regs.pc; jump_target = jump_get(thread->process, ip); debug("Thread %s hit a break at %s.", str_thread(thread), str_address(thread->process, ip)); #if 0 /* enable to see full context on each hit */ dump_thread(thread); #endif if (likely(jump_target)) { /* Thread hit a tracepoint. Change the PC and let it continue. */ if (likely(!thread->process->stabilizing)) { debug("Thread %s jumping to %s.", str_thread(thread), str_address(thread->process, jump_target)); regs.pc = jump_target; if (likely(thread_set_regs(thread, ®s))) { thread_continue_or_stop(thread, 0); } } else { /* The process is currently stabilizing threads. The current thread just hit a tracepoint, but we don't * change its address to the trampoline, we just leave it stopped. If we continue the thread now, it * will hit the tracepoint again. */ info("Thread %s is stabilizing, deferring jump to %s.", str_thread(thread), str_address(thread->process, jump_target)); } return true; } /* Linker breakpoint */ if (likely(ip == thread->process->linker.bkpt)) { /* Linker breakpoint */ linker_notify(thread); regs.pc = regs.regs[30]; if (likely(thread_set_regs(thread, ®s))) { thread_continue_or_stop(thread, 0); } return true; } return false; }
thread_port_t tgdb_thread_create(vm_offset_t entry) { kern_return_t rc; mach_thread_t th; int i; for (th = &threads[0], i=0; i<MAX_THREADS; i++, th++) if (th->thread == MACH_PORT_NULL) break; if((rc = thread_create(mach_task_self(), &th->thread)) != KERN_SUCCESS) { printf("tgdb: thread_create returned %d\n", rc); return 0; } th->stack = stack_alloc(STACK_SIZE); set_thread_self(th); thread_set_regs(entry, th); return th->thread; }
/* Start a stopped thread and stop it again immediately. The function replaces * the instruction at the PC to a breakpoint instruction, starts the thread and * waits for the thread to receive a SIGTRAP (or SIGILL) signal. After the * thread stops, the patched instruction is reverted. * * The function checks if the address pointed by the PC is actually inside an * executable memory segment. If it isn't, the function restarts the thread * without patching any instructions. The address pointed by the PC is * illegal, so the thread should receive a SIGSEGV signal in this case. * * The function must be called with all threads of the process stopped. * Moreover, the given process must still be alive (i.e. not a zombie). */ static bool fncall_restop(thread_t * thread) { int patch; int signo; struct pt_regs regs[2]; /* Original instruction. */ insn_t insn; insn_kind_t kind; address_t address; assert(!thread->state.slavemode); thread->state.slavemode = true; info("Restopping thread %s...", str_thread(thread)); /* Save current register values. */ if (!thread_get_regs(thread, ®s[0])) goto fail; /* Get the current PC. */ address = instruction_pointer(®s[0]); patch = procfs_address_executable(thread, address); if (patch) { /* Read the original instruction. */ kind = is_thumb_mode(®s[0]) ? INSN_KIND_THUMB : INSN_KIND_ARM; if (!patch_read_insn_detect_kind(thread, address, &insn, &kind)) goto fail; verbose("The PC register in thread %s points at address %p, " "which contains %s instruction '%s'. It will " "be temporary replaced by a breakpoint instruction. The thread " "will then be restarted. A SIGILL or SIGTRAP signal should be " "received immediately after thread restart.", str_thread(thread), (void *) address, insn_kind_str(kind), arm_disassemble(insn, kind)); /* Patch instruction. */ if (!patch_insn(thread, address, kind, get_breakpoint_insn(kind))) goto fail; } else { /* The address pointed by the PC is invalid. Running the thread should * cause a SIGSEGV. */ verbose("The PC register in thread %s points at address %p, " "which is invalid (not inside a executable memory segment). " "It will be restarted without any patching. A SIGSEGV signal " "should be received immediately after thread restart.", str_thread(thread), (void *) address); } thread_continue(thread, 0); /* Wait until the program is stopped by a signal. */ while (1) { thread_wait(thread, false); if (thread->state.dead) goto fail; if ((thread->state.signo == SIGSEGV) || (thread->state.signo == SIGTRAP) || (thread->state.signo == SIGBUS)) { break; } warning("Unexpected signal %s received during restopping of thread %s. The signal will be delivered.", str_signal(thread->state.signo), str_thread(thread)); /* Deliver the signal. */ thread_continue(thread, 0); } signo = thread->state.signo; thread->state.signo = 0; /* The process stopped, read new register values. */ if (!thread_get_regs(thread, ®s[1])) goto fail; /* Warn about any abnormalities. */ if (regs[1].ARM_pc != regs[0].ARM_pc) { warning("Unexpected change of PC register during restopping of %s.", str_thread(thread)); } if (regs[1].ARM_lr != regs[0].ARM_lr) { warning("Unexpected change of LR register during restopping of %s.", str_thread(thread)); } if (regs[1].ARM_sp != regs[0].ARM_sp) { warning("Unexpected change of SP register during restopping of %s.", str_thread(thread)); } /* Restore old register values. */ if (!thread_set_regs(thread, ®s[0])) goto fail; if (patch) { if ((signo != SIGILL) && (signo != SIGTRAP)) { warning("Thread %s was stopped by unexpected signal %s. Ignoring.", str_thread(thread), str_signal(signo)); } /* Revert original instruction */ patch_insn(thread, address, kind, insn); } else { if ((signo != SIGSEGV)) { warning("%s was stopped by unexpected signal %s. Ignoring.", str_thread(thread), str_signal(signo)); } } info("Finished restop of %s.", str_thread(thread)); thread->state.slavemode = false; if (!thread->state.dead) return true; fail: assert(thread->state.dead); error("Thread %s died during restop operation.", str_thread(thread)); return false; }