/* * This is a userland linux debugger module * * Functions unique for Linux * * It can be compiled by gcc * */ //#define LDEB // enable debug print in this module #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "symelf.hpp" #include "linux_debmod.h" #ifdef __ANDROID__ # include # include "android.hpp" # include "android.cpp" #else # include #endif //-------------------------------------------------------------------------- // Load IDA register sets. #ifdef __ARM__ # include "arm_regs.hpp" # define arch_registers arm_registers #else # include "pc_regs.hpp" # define arch_registers x86_registers #endif //-------------------------------------------------------------------------- // Define some ptrace() requests if they're not available. #ifndef PTRACE_GETREGSET # define PTRACE_GETREGSET __ptrace_request(0x4204) # define PTRACE_SETREGSET __ptrace_request(0x4205) #endif #ifdef __HAVE_ARM_VFP__ # ifndef PTRACE_GETVFPREGS # define PTRACE_GETVFPREGS __ptrace_request(27) # define PTRACE_SETVFPREGS __ptrace_request(28) # endif #endif #if !defined(__ARM__) && !defined(__X86__) && !defined(PTRACE_ARCH_PRCTL) # define PTRACE_ARCH_PRCTL __ptrace_request(30) #endif //-------------------------------------------------------------------------- // ARM breakpoint codes. #ifdef __ARM__ static const uchar thumb16_bpt[] = { 0x10, 0xDE }; // UND #10 // we must use 32-bit breakpoints for 32bit instructions inside IT blocks (thumb mode) // if we use a 16-bit breakpoint and the processor decides to skip it // because the condition codes are not satisfied, we will end up skipping // only half of the original 32-bit instruction static const uchar thumb32_bpt[] = { 0xF0, 0xF7, 0x00, 0xA0 }; // This bit is combined with the software bpt size to indicate // that 32bit bpt code should be used. #define USE_THUMB32_BPT 0x80 static const uchar aarch64_bpt[] = AARCH64_BPT_CODE; #endif //-------------------------------------------------------------------------- #if defined(__HAVE_ARM_VFP__) && !defined(__ANDROID__) struct user_vfp { int64 fpregs[32]; int32 fpscr; }; #endif //-------------------------------------------------------------------------- #ifdef __ARM__ # if defined(__X86__) # define user_regs_struct user_regs # define PCREG uregs[15] # else // arm64 # define user_regs_struct user_pt_regs # define LRREG_IDX 30 # endif #else // __ARM__ # if defined(__X86__) # define SPREG esp # define PCREG eip # define XMM_STRUCT x387 # define TAGS_REG twd # define INTEL_REG(reg) e##reg # define INTEL_SREG(reg) x##reg # else // x64 # define SPREG rsp # define PCREG rip # define XMM_STRUCT i387 # define TAGS_REG ftw # define INTEL_REG(reg) r##reg # define INTEL_SREG(reg) reg # endif #endif //-------------------------------------------------------------------------- #ifdef TESTABLE_BUILD typedef const char *per_pid_elf_dbgdir_resolver_t(int pid); static per_pid_elf_dbgdir_resolver_t *per_pid_elf_dbgdir_resolver = nullptr; #endif //-------------------------------------------------------------------------- // ptrace() uses long as part of its API and we want to use that, so we // tell lint to ignore it. //lint -esym(970,long) use of modifier or type 'long' outside of a typedef //-------------------------------------------------------------------------- linux_debmod_t::linux_debmod_t(void) : ta(nullptr), complained_shlib_bpt(false), process_handle(INVALID_HANDLE_VALUE), thread_handle(INVALID_HANDLE_VALUE), exited(false), mapfp(nullptr), npending_signals(0), may_run(false), requested_to_suspend(false), in_event(false), nptl_base(BADADDR), reg_ctx(nullptr) { prochandle.pid = NO_PROCESS; set_platform("linux"); } #pragma GCC diagnostic ignored "-Wswitch" // case values do not belong to... #ifdef LDEB //-------------------------------------------------------------------------- const char *get_ptrace_name(__ptrace_request request) { switch ( request ) { case PTRACE_TRACEME: return "PTRACE_TRACEME"; /* Indicate that the process making this request should be traced. All signals received by this process can be intercepted by its parent, and its parent can use the other `ptrace' requests. */ case PTRACE_PEEKTEXT: return "PTRACE_PEEKTEXT"; /* Return the word in the process's text space at address ADDR. */ case PTRACE_PEEKDATA: return "PTRACE_PEEKDATA"; /* Return the word in the process's data space at address ADDR. */ case PTRACE_PEEKUSER: return "PTRACE_PEEKUSER"; /* Return the word in the process's user area at offset ADDR. */ case PTRACE_POKETEXT: return "PTRACE_POKETEXT"; /* Write the word DATA into the process's text space at address ADDR. */ case PTRACE_POKEDATA: return "PTRACE_POKEDATA"; /* Write the word DATA into the process's data space at address ADDR. */ case PTRACE_POKEUSER: return "PTRACE_POKEUSER"; /* Write the word DATA into the process's user area at offset ADDR. */ case PTRACE_CONT: return "PTRACE_CONT"; /* Continue the process. */ case PTRACE_KILL: return "PTRACE_KILL"; /* Kill the process. */ case PTRACE_SINGLESTEP: return "PTRACE_SINGLESTEP"; /* Single step the process. This is not supported on all machines. */ #if !defined(__ARM__) || defined(__X86__) case PTRACE_GETREGS: return "PTRACE_GETREGS"; /* Get all general purpose registers used by a processes. This is not supported on all machines. */ case PTRACE_SETREGS: return "PTRACE_SETREGS"; /* Set all general purpose registers used by a processes. This is not supported on all machines. */ case PTRACE_GETFPREGS: return "PTRACE_GETFPREGS"; /* Get all floating point registers used by a processes. This is not supported on all machines. */ case PTRACE_SETFPREGS: return "PTRACE_SETFPREGS"; /* Set all floating point registers used by a processes. This is not supported on all machines. */ #endif case PTRACE_ATTACH: return "PTRACE_ATTACH"; /* Attach to a process that is already running. */ case PTRACE_DETACH: return "PTRACE_DETACH"; /* Detach from a process attached to with PTRACE_ATTACH. */ #if !defined(__ARM__) case PTRACE_GETFPXREGS: return "PTRACE_GETFPXREGS"; /* Get all extended floating point registers used by a processes. This is not supported on all machines. */ case PTRACE_SETFPXREGS: return "PTRACE_SETFPXREGS"; /* Set all extended floating point registers used by a processes. This is not supported on all machines. */ #endif case PTRACE_SYSCALL: return "PTRACE_SYSCALL"; /* Continue and stop at the next (return from) syscall. */ #if defined(__ARM__) && defined(__X86__) case PTRACE_GETVFPREGS: return "PTRACE_GETVFPREGS"; /* Get all vfp registers used by a processes. This is not supported on all machines. */ case PTRACE_SETVFPREGS: return "PTRACE_SETVFPREGS"; /* Set all vfp registers used by a processes. This is not supported on all machines. */ #endif case PTRACE_SETOPTIONS: return "PTRACE_SETOPTIONS"; /* Set ptrace filter options. */ case PTRACE_GETEVENTMSG: return "PTRACE_GETEVENTMSG"; /* Get last ptrace message. */ case PTRACE_GETSIGINFO: return "PTRACE_GETSIGINFO"; /* Get siginfo for process. */ case PTRACE_SETSIGINFO: return "PTRACE_SETSIGINFO"; /* Set new siginfo for process. */ case PTRACE_GETREGSET: return "PTRACE_GETREGSET"; /* Get register content. */ case PTRACE_SETREGSET: return "PTRACE_SETREGSET"; /* Set register content. */ #ifdef PTRACE_SEIZE case PTRACE_SEIZE: return "PTRACE_SEIZE"; /* Like PTRACE_ATTACH, but do not force tracee to trap and do not affect signal or group stop state. */ case PTRACE_INTERRUPT: return "PTRACE_INTERRUPT"; /* Trap seized tracee. */ case PTRACE_LISTEN: return "PTRACE_LISTEN"; /* Wait for next group event. */ case PTRACE_PEEKSIGINFO: return "PTRACE_PEEKSIGINFO"; /* Wait for next group event. */ case PTRACE_GETSIGMASK: return "PTRACE_GETSIGMASK"; /* Wait for next group event. */ case PTRACE_SETSIGMASK: return "PTRACE_SETSIGMASK"; /* Wait for next group event. */ #endif #if !defined(__ARM__) && !defined(__X86__) case PTRACE_ARCH_PRCTL: return "PTRACE_ARCH_PRCTL"; //lint !e2444 case value is not in enumeration #endif default: static char buf[MAXSTR]; qsnprintf(buf, sizeof(buf), "%d", request); return buf; } } #endif //-------------------------------------------------------------------------- // fixme: can we use peeksize_t instead? static long qptrace(__ptrace_request request, pid_t pid, void *addr, void *data) { long code = ptrace(request, pid, addr, data); #ifdef LDEB if ( request != PTRACE_PEEKTEXT && request != PTRACE_PEEKUSER && (request != PTRACE_POKETEXT && request != PTRACE_POKEDATA #if !defined(__ARM__) || defined(__X86__) && request != PTRACE_GETREGS && request != PTRACE_SETREGS && request != PTRACE_GETFPREGS && request != PTRACE_SETFPREGS #endif #if defined(__ARM__) && defined(__X86__) && request != PTRACE_GETVFPREGS && request != PTRACE_SETVFPREGS #endif #if !defined(__ARM__) && request != PTRACE_SETFPXREGS && request != PTRACE_GETFPXREGS #endif || code != 0) ) { // int saved_errno = errno; // msg("%s(%u, 0x%X, 0x%X) => 0x%X\n", get_ptrace_name(request), pid, addr, data, code); // errno = saved_errno; } #endif return code; } //-------------------------------------------------------------------------- #ifdef LDEB GCC_DIAG_OFF(format-nonliteral); void linux_debmod_t::log(thid_t tid, const char *format, ...) { if ( tid != -1 ) { thread_info_t *thif = get_thread(tid); if ( thif == NULL ) { msg(" %d: ** missing **\n", tid); } else { const char *name = "?"; switch ( thif->state ) { case RUNNING: name = "RUN "; break; case STOPPED: name = "STOP"; break; case DYING: name = "DYIN"; break; case DEAD: name = "DEAD"; break; } msg(" %d: %s %c%c S=%d U=%d ", thif->tid, name, thif->waiting_sigstop ? 'W' : ' ', thif->got_pending_status ? 'P' : ' ', thif->suspend_count, thif->user_suspend); } } va_list va; va_start(va, format); vmsg(format, va); va_end(va); } static const char *strevent(int status) { int event = status >> 16; if ( WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP && event != 0 ) { switch ( event ) { case PTRACE_EVENT_FORK: return " event=PTRACE_EVENT_FORK"; case PTRACE_EVENT_VFORK: return " event=PTRACE_EVENT_VFORK"; case PTRACE_EVENT_CLONE: return " event=PTRACE_EVENT_CLONE"; case PTRACE_EVENT_EXEC: return " event=PTRACE_EVENT_EXEC"; case PTRACE_EVENT_VFORK_DONE: return " event=PTRACE_EVENT_VFORK_DONE"; case PTRACE_EVENT_EXIT: return " event=PTRACE_EVENT_EXIT"; default: return " UNKNOWN event"; } } return ""; } static char *status_dstr(int status) { static char buf[80]; if ( WIFSTOPPED(status) ) { int sig = WSTOPSIG(status); ::qsnprintf(buf, sizeof(buf), "stopped(%s)%s", strsignal(sig), strevent(status)); } else if ( WIFSIGNALED(status) ) { int sig = WTERMSIG(status); ::qsnprintf(buf, sizeof(buf), "terminated(%s)", strsignal(sig)); } else if ( WIFEXITED(status) ) { int code = WEXITSTATUS(status); ::qsnprintf(buf, sizeof(buf), "exited(%d)", code); } else { ::qsnprintf(buf, sizeof(buf), "status=%x\n", status); } return buf; } static void ldeb(const char *format, ...) { va_list va; va_start(va, format); vmsg(format, va); va_end(va); } GCC_DIAG_OFF(format-nonliteral); #else //lint -estring(750,status_dstr, strevent) not referenced #define log(tid, format, args...) #define ldeb(format, args...) do {} while ( 0 ) #define status_dstr(status) "?" #define strevent(status) "" #endif //-------------------------------------------------------------------------- static int qkill(int pid, int signo) { ldeb("%d: sending signal %s\n", pid, signo == SIGSTOP ? "SIGSTOP" : signo == SIGKILL ? "SIGKILL" : ""); int ret; errno = 0; static bool tkill_failed = false; if ( !tkill_failed ) { ret = syscall(__NR_tkill, pid, signo); if ( ret != 0 && errno == ENOSYS ) { errno = 0; tkill_failed = true; } } if ( tkill_failed ) ret = kill(pid, signo); if ( ret != 0 ) ldeb(" %s\n", strerror(errno)); return ret; } //-------------------------------------------------------------------------- inline thread_info_t *linux_debmod_t::get_thread(thid_t tid) { threads_t::iterator p = threads.find(tid); if ( p == threads.end() ) return NULL; return &p->second; } #define X86_XSTATE_SSE_SIZE 576 // #define X86_XSTATE_AVX_SIZE 832 // #define X86_XSTATE_BNDREGS_SIZE 1024 // #define X86_XSTATE_BNDCFG_SIZE 1088 // #define X86_XSTATE_AVX512_SIZE 2688 // #define X86_XSTATE_PKRU_SIZE 2696 #define X86_XSTATE_MAX_SIZE 2696 //------------------------------------------------------------------------- static int _has_ptrace_getregset = -1; static bool has_ptrace_getregset(thid_t tid) { if ( _has_ptrace_getregset < 0 ) { #if defined(__ARM__) uint8_t xstateregs[sizeof(struct user_regs_struct)]; # define _TEST_PTRACE_OP NT_PRSTATUS #else uint8_t xstateregs[X86_XSTATE_SSE_SIZE]; # define _TEST_PTRACE_OP NT_X86_XSTATE #endif struct iovec iov; iov.iov_base = xstateregs; iov.iov_len = sizeof(xstateregs); _has_ptrace_getregset = qptrace(PTRACE_GETREGSET, tid, (void *) _TEST_PTRACE_OP, &iov) == 0; #undef _TEST_PTRACE_OP } return _has_ptrace_getregset > 0; } //------------------------------------------------------------------------- inline bool qptrace_get_regset(struct iovec *out, size_t what, thid_t tid) { if ( !has_ptrace_getregset(tid) ) return false; return qptrace(PTRACE_GETREGSET, tid, (void *) what, out) == 0; } //------------------------------------------------------------------------- inline bool qptrace_get_regset(void *out, size_t outsz, size_t what, thid_t tid) { struct iovec iov = { out, outsz }; return qptrace_get_regset(&iov, what, tid); } //------------------------------------------------------------------------- inline bool qptrace_set_regset(size_t what, thid_t tid, struct iovec &iov) { // we'll assume that if a platform exposes 'PTRACE_GETREGSET' // it also exposes 'PTRACE_SETREGSET'. if ( !has_ptrace_getregset(tid) ) return false; return qptrace(PTRACE_SETREGSET, tid, (void *) what, &iov) == 0; } //------------------------------------------------------------------------- inline bool qptrace_set_regset(size_t what, thid_t tid, void *in, size_t insz) { struct iovec iov = { in, insz }; return qptrace_set_regset(what, tid, iov); } //-------------------------------------------------------------------------- #if defined(__ARM__) && !defined(__X86__) inline bool qptrace_get_prstatus(struct user_regs_struct *regset, thid_t tid) { return qptrace_get_regset(regset, sizeof(struct user_regs_struct), NT_PRSTATUS, tid); } #endif //-------------------------------------------------------------------------- static ea_t get_ip(thid_t tid) { ea_t ea; #if defined(__ARM__) && !defined(__X86__) struct user_regs_struct regset; ea = qptrace_get_prstatus(®set, tid) ? regset.pc : BADADDR; #else const size_t pcreg_off = qoffsetof(user, regs) + qoffsetof(user_regs_struct, PCREG); // In case 64bit IDA (__EA64__=1) is debugging a 32bit process: // - size of ea_t is 64 bit // - qptrace() returns a 32bit long value // Here we cast the return value to unsigned long to prevent // extending of the sign bit when convert 32bit long value to 64bit ea_t ea = (unsigned long)qptrace(PTRACE_PEEKUSER, tid, (void *)pcreg_off, 0); #endif return ea; } #include "linux_threads.cpp" //-------------------------------------------------------------------------- #ifndef __ARM__ static unsigned long get_dr(thid_t tid, int idx) { uchar *offset = (uchar *)qoffsetof(user, u_debugreg) + idx*sizeof(unsigned long int); unsigned long value = qptrace(PTRACE_PEEKUSER, tid, (void *)offset, 0); // msg("dr%d => %a\n", idx, value); return value; } //-------------------------------------------------------------------------- static bool set_dr(thid_t tid, int idx, ea_t ea) { uchar *offset = (uchar *)qoffsetof(user, u_debugreg) + idx*sizeof(unsigned long int); if ( ea == BADADDR ) ea = 0; // linux does not accept too high values unsigned long value = ea; // msg("dr%d <= %a\n", idx, value); return qptrace(PTRACE_POKEUSER, tid, offset, (void *)value) == 0; } #endif //-------------------------------------------------------------------------- bool linux_debmod_t::del_pending_event(event_id_t id, const char *module_name) { for ( eventlist_t::iterator p=events.begin(); p != events.end(); ++p ) { if ( p->eid() == id && p->modinfo().name == module_name ) { events.erase(p); return true; } } return false; } //-------------------------------------------------------------------------- void linux_debmod_t::enqueue_event(const debug_event_t &ev, queue_pos_t pos) { if ( ev.eid() != NO_EVENT ) { events.enqueue(ev, pos); may_run = false; ldeb("enqueued event, may not run!\n"); } } //-------------------------------------------------------------------------- static inline void resume_dying_thread(int tid, int) { qptrace(PTRACE_CONT, tid, 0, (void *)0); } //-------------------------------------------------------------------------- // we got a signal that does not belong to our thread. find the target thread // and store the signal there void linux_debmod_t::store_pending_signal(int _pid, int status) { struct ida_local linux_signal_storer_t : public debmod_visitor_t { int pid; int status; linux_signal_storer_t(int p, int s) : pid(p), status(s) {} virtual int visit(debmod_t *debmod) override { linux_debmod_t *ld = (linux_debmod_t *)debmod; threads_t::iterator p = ld->threads.find(pid); if ( p != ld->threads.end() ) { thread_info_t &ti = p->second; // normally we should not receive a new signal unless the process or the thread // exited. the exit signals may occur even if there is a pending signal. QASSERT(30185, !ti.got_pending_status || ld->exited || WIFEXITED(status)); if ( ti.waiting_sigstop && WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP ) { ti.waiting_sigstop = false; ld->set_thread_state(ti, STOPPED); } else { ti.got_pending_status = true; ti.pending_status = status; ld->npending_signals++; } return 1; // stop } else { // we are handling an event from a thread we recently removed, ignore this if ( ld->deleted_threads.has(pid) ) { // do not store the signal but resume the thread and let it finish resume_dying_thread(pid, status); return 1; } } return 0; // continue } }; linux_signal_storer_t lss(_pid, status); if ( !for_all_debuggers(lss) ) // uses lock_begin(), lock_end() to protect common data { if ( WIFSTOPPED(status) ) { // we can get SIGSTOP for the new-born lwp before the parent get it // store pid to mark that we should not wait for SIGSTOP anymore seen_threads.push_back(_pid); } else if ( !WIFSIGNALED(status) ) { // maybe it comes from a zombie? // if we terminate the process, there might be some zombie threads remaining(?) msg(" %d: failed to store pending status %x, killing unknown thread\n", _pid, status); qptrace(PTRACE_KILL, _pid, 0, 0); } } } //-------------------------------------------------------------------------- inline bool is_bpt_status(int status) { if ( !WIFSTOPPED(status) ) return false; int sig = WSTOPSIG(status); #ifdef __ARM__ return sig == SIGTRAP || sig == SIGILL; #else return sig == SIGTRAP; #endif } //-------------------------------------------------------------------------- // check if there are any pending signals for our process bool linux_debmod_t::retrieve_pending_signal(pid_t *p_pid, int *status) { if ( npending_signals == 0 ) return false; lock_begin(); // try to stick to the same thread as before threads_t::iterator p = threads.find(last_event.tid); if ( p != threads.end() ) { thread_info_t &ti = p->second; if ( !ti.got_pending_status || ti.user_suspend > 0 || ti.suspend_count > 0 ) p = threads.end(); } // find a thread with a signal. if ( p == threads.end() ) { for ( int i=0; i < 3; i++ ) { for ( p=threads.begin(); p != threads.end(); ++p ) { thread_info_t &ti = p->second; if ( ti.user_suspend > 0 || ti.suspend_count > 0 ) continue; if ( ti.got_pending_status ) { // signal priorities: STEP, SIGTRAP, others if ( i == 0 ) { if ( !ti.single_step ) continue; } else if ( i == 1 ) { if ( !is_bpt_status(ti.pending_status) ) continue; } break; } } } } bool got_pending_signal = false; if ( p != threads.end() ) { *p_pid = p->first; *status = p->second.pending_status; p->second.got_pending_status = false; got_pending_signal = true; npending_signals--; QASSERT(30186, npending_signals >= 0); ldeb("-------------------------------\n"); log(p->first, "qwait (pending signal): %s (may_run=%d)\n", status_dstr(*status), may_run); } lock_end(); return got_pending_signal; } //-------------------------------------------------------------------------- // read a zero terminated string. try to avoid reading unreadable memory bool linux_debmod_t::read_asciiz(tid_t tid, ea_t ea, char *buf, size_t bufsize, bool suspend) { while ( bufsize > 0 ) { int pagerest = 4096 - (ea % 4096); // number of bytes remaining on the page int nread = qmin(pagerest, bufsize); if ( !suspend && nread > 128 ) nread = 128; // most paths are short, try to read only 128 bytes nread = _read_memory(tid, ea, buf, nread, suspend); if ( nread < 0 ) return false; // failed // did we read a zero byte? for ( int i=0; i < nread; i++ ) if ( buf[i] == '\0' ) return true; ea += nread; buf += nread; bufsize -= nread; } return true; // odd, we did not find any zero byte. should we report success? } //-------------------------------------------------------------------------- // may add/del threads! bool linux_debmod_t::gen_library_events(int /*tid*/) { int s = events.size(); meminfo_vec_t miv; if ( get_memory_info(miv, false) == 1 ) handle_dll_movements(miv); return events.size() != s; } //-------------------------------------------------------------------------- bool linux_debmod_t::handle_hwbpt(debug_event_t *event) { #ifdef __ARM__ qnotused(event); #else uint32 dr6_value = get_dr(event->tid, 6); for ( int i=0; i < MAX_BPT; i++ ) { if ( dr6_value & (1<tid, i) ) { bptaddr_t &addr = event->set_bpt(); addr.hea = hwbpt_ea[i]; addr.kea = BADADDR; set_dr(event->tid, 6, 0); // Clear the status bits return true; } } } #endif return false; } //-------------------------------------------------------------------------- inline ea_t calc_bpt_event_ea(const debug_event_t *event) { #ifdef __ARM__ if ( event->exc().code == SIGTRAP || event->exc().code == SIGILL ) return event->ea; #else if ( event->exc().code == SIGTRAP ) // || event->exc().code == SIGSEGV ) // NB: there was a bug in linux 2.6.10 when int3 was reported as SIGSEGV instead of SIGTRAP { return event->ea - 1; // x86 reports the address after the bpt } #endif return BADADDR; } //-------------------------------------------------------------------------- inline void linux_debmod_t::set_thread_state(thread_info_t &ti, thstate_t state) const { ti.state = state; } //-------------------------------------------------------------------------- static __inline void clear_tbit(thid_t tid) { #ifdef __ARM__ qnotused(tid); return; #else struct user_regs_struct regs; if ( qptrace(PTRACE_GETREGS, tid, 0, ®s) != 0 ) { msg("clear_tbit: error reading registers for thread %d\n", tid); return; } if ( (regs.eflags & 0x100) != 0 ) { regs.eflags &= ~0x100; if ( qptrace(PTRACE_SETREGS, tid, 0, ®s) != 0 ) msg("clear_tbit: error writting registers for thread %d\n", tid); } #endif } //-------------------------------------------------------------------------- bool linux_debmod_t::check_for_new_events(chk_signal_info_t *csi, bool *event_prepared) { if ( event_prepared != NULL ) *event_prepared = false; while ( true ) { // even if we have pending events, check for new events first. // this improves multithreaded debugging experience because // we stick to the same thread (hopefully a new event arrives fast enough // if we are single stepping). if we first check pending events, // the user will be constantly switched from one thread to another. csi->pid = check_for_signal(&csi->status, -1, 0); if ( csi->pid <= 0 ) { // no new events, do we have any pending events? if ( retrieve_pending_signal(&csi->pid, &csi->status) ) { // check for extended event, // if any the debugger event can be prepared handle_extended_wait(event_prepared, *csi); break; } // if the timeout was zero, nothing else to do if ( csi->timeout_ms == 0 ) return false; // ok, we will wait for new events for a while csi->pid = check_for_signal(&csi->status, -1, csi->timeout_ms); if ( csi->pid <= 0 ) return false; } ldeb("-------------------------------\n"); log(csi->pid, " => qwait: %s\n", status_dstr(csi->status)); // check for extended event, // if any the debugger event can be prepared handle_extended_wait(event_prepared, *csi); if ( threads.find(csi->pid) != threads.end() ) break; // when an application creates many short living threads we may receive events // from a thread we already removed so, do not store this pending signal, just // ignore it if ( !deleted_threads.has(csi->pid) ) { // we are not interested in this pid log(csi->pid, "storing status %d\n", csi->status); store_pending_signal(csi->pid, csi->status); } else { // do not store the signal but resume the thread and let it finish resume_dying_thread(csi->pid, csi->status); } csi->timeout_ms = 0; } return true; } //-------------------------------------------------------------------------- // timeout in microseconds // 0 - no timeout, return immediately // -1 - wait forever // returns: 1-ok, 0-failed int linux_debmod_t::get_debug_event(debug_event_t *event, int timeout_ms) { chk_signal_info_t csi(timeout_ms); // even if we have pending events, check for new events first. bool event_ready = false; if ( !check_for_new_events(&csi, &event_ready) ) return false; pid_t tid = csi.pid; int status = csi.status; thread_info_t *thif = get_thread(tid); if ( thif == NULL ) { // not our thread?! debdeb("EVENT FOR UNKNOWN THREAD %d, IGNORED...\n", tid); size_t sig = WIFSTOPPED(status) ? WSTOPSIG(status) : 0; qptrace(PTRACE_CONT, tid, 0, (void*)(sig)); return false; } QASSERT(30057, thif->state != STOPPED || exited || WIFEXITED(status) || WIFSIGNALED(status)); event->tid = NO_EVENT; // start with empty event // if there was a pending event, it means that previously we did not resume // any threads, all of them are suspended set_thread_state(*thif, STOPPED); dbg_freeze_threads(NO_THREAD); may_run = false; // debugger event could be prepared during the check_for_new_events if ( event_ready ) goto EVENT_READY; // report empty event to get called back immediately // dbg_freeze_threads may delete some threads and render our 'thif' pointer invalid thif = get_thread(tid); if ( thif == NULL ) { debdeb("thread %d disappeared after freezing?!...\n", tid); goto EVENT_READY; // report empty event to get called back immediately } event->pid = process_handle; event->tid = tid; if ( exited ) { event->ea = BADADDR; } else if ( WIFSIGNALED(status) ) { siginfo_t info; qptrace(PTRACE_GETSIGINFO, tid, NULL, &info); event->ea = (ea_t)(size_t)info.si_addr; } else { event->ea = get_ip(event->tid); } event->handled = false; if ( WIFSTOPPED(status) ) { ea_t proc_ip; bool suspend; const exception_info_t *ei; int code = WSTOPSIG(status); excinfo_t &exc = event->set_exception(); exc.code = code; exc.can_cont = true; exc.ea = BADADDR; if ( code == SIGSTOP ) { if ( thif->waiting_sigstop ) { log(tid, "got pending SIGSTOP!\n"); thif->waiting_sigstop = false; goto RESUME; // silently resume the application } // convert SIGSTOP into simple PROCESS_SUSPENDED, this will avoid // a dialog box about the signal. I'm not sure that this is a good thing // (probably better to report exceptions in the output windows rather than // in dialog boxes), so I'll comment it out for the moment. //event->eid = PROCESS_SUSPENDED; } ei = find_exception(code); if ( ei != NULL ) { exc.info.sprnt("got %s signal (%s)", ei->name.c_str(), ei->desc.c_str()); suspend = should_suspend_at_exception(event, ei); if ( !suspend && ei->handle() ) code = 0; // mask the signal } else { exc.info.sprnt("got unknown signal #%d", code); suspend = true; } proc_ip = calc_bpt_event_ea(event); // if bpt, calc its address from event->ea if ( proc_ip != BADADDR ) { // this looks like a bpt-related exception. it occurred either because // of our bpt either it was generated by the app. // by default, reset the code so we don't send any SIGTRAP signal to the debugged // process *except* in the case where the program generated the signal by // itself code = 0; if ( proc_ip == shlib_bpt.bpt_addr && shlib_bpt.bpt_addr != 0 ) { log(tid, "got shlib bpt %a\n", proc_ip); // emulate return from function if ( !emulate_retn(tid) ) { msg("%a: could not return from the shlib breakpoint!\n", proc_ip); return true; } if ( !gen_library_events(tid) ) // something has changed in shared libraries? { // no, nothing has changed log(tid, "nothing has changed in dlls\n"); RESUME: if ( !requested_to_suspend && !in_event ) { ldeb("autoresuming\n"); // QASSERT(30177, thif->state == STOPPED); resume_app(NO_THREAD); return false; } log(tid, "app may not run, keeping it suspended (%s)\n", requested_to_suspend ? "requested_to_suspend" : in_event ? "in_event" : "has_pending_events"); event->set_eid(PROCESS_SUSPENDED); return true; } log(tid, "gen_library_events ok\n"); event->set_eid(NO_EVENT); } else if ( (proc_ip == birth_bpt.bpt_addr && birth_bpt.bpt_addr != 0) || (proc_ip == death_bpt.bpt_addr && death_bpt.bpt_addr != 0) ) { log(tid, "got thread bpt %a (%s)\n", proc_ip, proc_ip == birth_bpt.bpt_addr ? "birth" : "death"); size_t s = events.size(); thread_handle = tid; // for ps_pdread // NB! if we don't do this, some running threads can interfere with thread_db tdb_handle_messages(tid); // emulate return from function if ( !emulate_retn(tid) ) { msg("%a: could not return from the thread breakpoint!\n", proc_ip); return true; } if ( s == events.size() ) { log(tid, "resuming after thread_bpt\n"); goto RESUME; } event->set_eid(NO_EVENT); } else { // according to the requirement of commdbg a LIB_LOADED event // should not be reported with the same thread/IP immediately after // a BPT-related event (see idd.hpp) // Here we put to the queue all already loaded (but not reported) // libraries to be sent _before_ BPT (do it only if ELF interpreter // is not yet loaded, otherwise LIB_LOADED events will be generated // by shlib_bpt and thus they cannot conflict with regular BPTs if ( interp.empty() ) { gen_library_events(tid); thif = get_thread(tid); } if ( !handle_hwbpt(event) ) { if ( bpts.find(proc_ip) != bpts.end() && !handling_lowcnds.has(proc_ip) ) { bptaddr_t &bpta = event->set_bpt(); bpta.hea = BADADDR; bpta.kea = BADADDR; event->ea = proc_ip; } else if ( thif != NULL && thif->single_step ) { event->set_eid(STEP); } else { // in case of unknown breakpoints (icebp, int3, etc...) we must remember the signal // unless it should be masked if ( ei == NULL || !ei->handle() ) code = event->exc().code; } } } } thif = get_thread(tid); if ( thif == NULL ) goto EVENT_READY; // report empty event to get called back immediately thif->child_signum = code; if ( !requested_to_suspend && evaluate_and_handle_lowcnd(event) ) return false; if ( !suspend && event->eid() == EXCEPTION ) { log_exception(event, ei); log(tid, "resuming after exception %d\n", code); goto RESUME; } } else { int exit_code; if ( WIFSIGNALED(status) ) { int sig = WTERMSIG(status); debdeb("SIGNALED pid=%d tid=%d signal='%s'(%d) pc=%a\n", event->pid, event->tid, strsignal(sig), sig, event->ea); exit_code = sig; } else { exit_code = WEXITSTATUS(status); } if ( threads.size() <= 1 || thif->tid == process_handle ) { event->set_exit_code(PROCESS_EXITED, exit_code); exited = true; } else { log(tid, "got a thread exit\n"); event->clear(); dead_thread(event->tid, DEAD); } } EVENT_READY: log(tid, "low got event: %s, signum=%d\n", debug_event_str(event), thif->child_signum); thif = get_thread(event->tid); if ( thif != NULL ) thif->single_step = false; last_event = *event; return true; } //-------------------------------------------------------------------------- gdecode_t idaapi linux_debmod_t::dbg_get_debug_event(debug_event_t *event, int timeout_ms) { QASSERT(30059, !in_event || exited); while ( true ) { // are there any pending events? if ( !events.empty() ) { // get the first event and return it *event = events.front(); events.pop_front(); if ( event->eid() == NO_EVENT ) continue; log(-1, "GDE1(handling_lowcnds.size()=%" FMT_Z "): %s\n", handling_lowcnds.size(), debug_event_str(event)); in_event = true; if ( handling_lowcnds.empty() ) { ldeb("requested_to_suspend := 0\n"); requested_to_suspend = false; } return events.empty() ? GDE_ONE_EVENT : GDE_MANY_EVENTS; } debug_event_t ev; if ( !get_debug_event(&ev, timeout_ms) ) break; enqueue_event(ev, IN_BACK); } return GDE_NO_EVENT; } //-------------------------------------------------------------------------- // R is running // S is sleeping in an interruptible wait // D is waiting in uninterruptible disk sleep // Z is zombie // T is traced or stopped (on a signal) // W is paging static char getstate(int tid) { char buf[QMAXPATH]; qsnprintf(buf, sizeof(buf), "/proc/%u/status", tid); FILE *fp = fopenRT(buf); qstring line; if ( fp == NULL //-V501 identical sub-expressions || qgetline(&line, fp) < 0 || qgetline(&line, fp) < 0 ) { // no file or file read error (e.g. was deleted after successful fopenRT()) return ' '; } char st; if ( qsscanf(line.c_str(), "State: %c", &st) != 1 ) INTERR(30060); qfclose(fp); return st; } //-------------------------------------------------------------------------- bool linux_debmod_t::has_pending_events(void) { if ( !events.empty() ) return true; for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) { thread_info_t &ti = p->second; if ( ti.got_pending_status && ti.user_suspend == 0 && ti.suspend_count == 0 ) return true; } return false; } //-------------------------------------------------------------------------- int linux_debmod_t::dbg_freeze_threads(thid_t tid, bool exclude) { ldeb(" freeze_threads(%s %d) handling_lowcnds.size()=%" FMT_Z "\n", exclude ? "exclude" : "only", tid, handling_lowcnds.size()); // first send all threads the SIGSTOP signal, as fast as possible typedef qvector queue_t; queue_t queue; qvector deadtids; for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) { if ( (p->first == tid) == exclude ) continue; thread_info_t &ti = p->second; if ( ti.is_running() ) { if ( qkill(ti.tid, SIGSTOP) != 0 ) { // In some cases the thread may already be dead but we are not aware // of it (for example, if many threads died at once, the events // will be queued and not processed yet. if ( errno == ESRCH ) deadtids.push_back(ti.tid); else dmsg("failed to send SIGSTOP to thread %d: %s\n", ti.tid, strerror(errno)); continue; } queue.push_back(&ti); ti.waiting_sigstop = true; } ti.suspend_count++; } // then wait for the SIGSTOP signals to arrive while ( !queue.empty() ) { int status = 0; int stid = check_for_signal(&status, -1, exited ? -1 : 0); if ( stid > 0 ) { // if more signals are to arrive, enable the waiter for ( queue_t::iterator p=queue.begin(); p != queue.end(); ++p ) { thread_info_t &ti = **p; if ( ti.tid == stid ) { if ( WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP ) { // suspended successfully ti.waiting_sigstop = false; set_thread_state(ti, STOPPED); } else { // got another signal, SIGSTOP will arrive later store_pending_signal(stid, status); } stid = -1; queue.erase(p); break; } } } if ( stid > 0 ) // got a signal for some other thread store_pending_signal(stid, status); } // clean up dead threads for ( int i=0; i < deadtids.size(); i++ ) dead_thread(deadtids[i], DEAD); #ifdef LDEB for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) { if ( (p->first == tid) != exclude ) { thid_t tid2 = p->first; log(tid2, "suspendd (ip=%08a)\n", get_ip(tid2)); } } #endif return 1; } //-------------------------------------------------------------------------- int linux_debmod_t::dbg_thaw_threads(thid_t tid, bool exclude) { int ok = 1; ldeb(" thaw_threads(%s %d), may_run=%d handlng_lowcnd.size()=%" FMT_Z " npending_signals=%d\n", exclude ? "exclude" : "only", tid, may_run, handling_lowcnds.size(), npending_signals); for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) { if ( (p->first == tid) == exclude ) continue; thread_info_t &ti = p->second; log(ti.tid, "(ip=%08a) ", get_ip(ti.tid)); if ( ti.is_running() ) { QASSERT(30188, ti.suspend_count == 0); ldeb("already running\n"); continue; } if ( ti.suspend_count > 0 && --ti.suspend_count > 0 ) { ldeb("suspended\n"); continue; } if ( ti.user_suspend > 0 ) { ldeb("user suspended\n"); continue; } if ( ti.got_pending_status ) { ldeb("have pending signal\n"); continue; } if ( (!may_run && ti.state != DYING) || exited ) { ldeb("!may_run\n"); continue; } if ( ti.state == STOPPED || ti.state == DYING ) { __ptrace_request request = ti.single_step ? PTRACE_SINGLESTEP : PTRACE_CONT; #ifdef LDEB char ostate = getstate(ti.tid); #endif ldeb("really resuming\n"); if ( qptrace(request, ti.tid, 0, (void *)(size_t)(ti.child_signum)) != 0 && ti.state != DYING ) //lint !e571 cast results in sign extension { ldeb(" !! failed to resume thread (error %d)\n", errno); if ( getstate(ti.tid) != 'Z' ) { ok = 0; continue; } // we have a zombie thread // report its death dead_thread(ti.tid, DYING); } if ( ti.state == DYING ) { set_thread_state(ti, DEAD); } else { QASSERT(30178, ti.state == STOPPED); //-V547 is always true set_thread_state(ti, RUNNING); } ldeb("PTRACE_%s, signum=%d, old_state: '%c', new_state: '%c'\n", request == PTRACE_SINGLESTEP ? "SINGLESTEP" : "CONT", ti.child_signum, ostate, getstate(ti.tid)); } else { ldeb("ti.state is not stopped or dying\n"); } } return ok; } //-------------------------------------------------------------------------- bool linux_debmod_t::suspend_all_threads(void) { return dbg_freeze_threads(NO_THREAD); } //-------------------------------------------------------------------------- bool linux_debmod_t::resume_all_threads(void) { return dbg_thaw_threads(NO_THREAD); } //-------------------------------------------------------------------------- drc_t idaapi linux_debmod_t::dbg_continue_after_event(const debug_event_t *event) { if ( event == NULL ) return DRC_FAILED; int tid = event->tid; thread_info_t *t = get_thread(tid); if ( t == NULL && event->eid() != THREAD_EXITED && !exited ) { dwarning("could not find thread %d!\n", tid); return DRC_FAILED; } ldeb("continue after event %s%s\n", debug_event_str(event), has_pending_events() ? " (there are pending events)" : ""); if ( t != NULL ) { if ( event->eid() != THREAD_STARTED && event->eid() != THREAD_EXITED && event->eid() != LIB_LOADED && event->eid() != LIB_UNLOADED && (event->eid() != EXCEPTION || event->handled) ) { t->child_signum = 0; } if ( t->state == DYING ) { // this thread is about to exit; resume it so it can do so t->suspend_count = 0; t->user_suspend = 0; dbg_thaw_threads(t->tid, false); } else if ( t->state == DEAD ) { // remove from internal list del_thread(event->tid); } // ensure TF bit is not set (if we aren't single stepping) after a SIGTRAP // because TF bit may still be set if ( event->eid() == EXCEPTION && !t->single_step && event->exc().code == SIGTRAP && event->handled ) clear_tbit(event->tid); } in_event = false; return resume_app(NO_THREAD) ? DRC_OK : DRC_FAILED; } //-------------------------------------------------------------------------- // if tid is specified, resume only it. bool linux_debmod_t::resume_app(thid_t tid) { may_run = !handling_lowcnds.empty() || !has_pending_events(); if ( may_run && handling_lowcnds.empty() ) { if ( !removed_bpts.empty() ) { for ( easet_t::iterator p=removed_bpts.begin(); p != removed_bpts.end(); ++p ) bpts.erase(*p); removed_bpts.clear(); } } return tid == NO_THREAD ? resume_all_threads() : dbg_thaw_threads(tid, false); } //-------------------------------------------------------------------------- // PTRACE_PEEKTEXT / PTRACE_POKETEXT operate on unsigned long values! (i.e. 4 bytes on x86 and 8 bytes on x64) typedef unsigned long peeksize_t; #define PEEKSIZE sizeof(peeksize_t) //-------------------------------------------------------------------------- int linux_debmod_t::_read_memory(int tid, ea_t ea, void *buffer, int size, bool suspend) { if ( exited || process_handle == INVALID_HANDLE_VALUE ) return 0; // stop all threads before accessing the process memory if ( suspend ) suspend_all_threads(); if ( tid == -1 ) tid = process_handle; int read_size = 0; bool tried_mem = false; bool tried_peek = false; // don't use memory for short reads if ( size > 3 * PEEKSIZE ) { TRY_MEMFILE: #ifndef __ANDROID__ char filename[64]; qsnprintf (filename, sizeof(filename), "/proc/%d/mem", tid); int fd = open(filename, O_RDONLY | O_LARGEFILE); if ( fd != -1 ) { read_size = pread64(fd, buffer, size, ea); close(fd); } // msg("%d: pread64 %d:%a:%d => %d\n", tid, fd, ea, size, read_size); #ifdef LDEB if ( read_size < size ) perror("read_memory: pread64 failed"); #endif #endif tried_mem = true; } if ( read_size != size && !tried_peek ) { uchar *ptr = (uchar *)buffer; read_size = 0; tried_peek = true; while ( read_size < size ) { const int shift = ea & (PEEKSIZE-1); int nbytes = shift == 0 ? PEEKSIZE : PEEKSIZE - shift; if ( nbytes > (size - read_size) ) nbytes = size - read_size; errno = 0; unsigned long v = qptrace(PTRACE_PEEKTEXT, tid, (void *)(size_t)(ea-shift), 0); if ( errno != 0 ) { ldeb("PEEKTEXT %d:%a => %s\n", tid, ea-shift, strerror(errno)); break; } else { //msg("PEEKTEXT %d:%a => OK\n", tid, ea-shift); } if ( nbytes == PEEKSIZE ) { *(unsigned long*)ptr = v; //lint !e433 !e415 allocated area not large enough for pointer } else { v >>= (shift*8); for ( int i=0; i < nbytes; i++ ) { ptr[i] = uchar(v); v >>= 8; } } ptr += nbytes; ea += nbytes; read_size += nbytes; } } // sometimes PEEKTEXT fails but memfile succeeds... so try both if ( read_size < size && !tried_mem ) goto TRY_MEMFILE; if ( suspend ) resume_all_threads(); // msg("READ MEMORY (%d): %d\n", tid, read_size); return read_size > 0 ? read_size : 0; } //-------------------------------------------------------------------------- int linux_debmod_t::_write_memory(int tid, ea_t ea, const void *buffer, int size, bool suspend) { if ( exited || process_handle == INVALID_HANDLE_VALUE ) return 0; #ifndef LDEB if ( debug_debugger ) #endif { show_hex(buffer, size, "WRITE MEMORY %a %d bytes:\n", ea, size); } // stop all threads before accessing the process memory if ( suspend ) suspend_all_threads(); if ( tid == -1 ) tid = process_handle; int ok = size; const uchar *ptr = (const uchar *)buffer; errno = 0; while ( size > 0 ) { const int shift = ea & (PEEKSIZE-1); int nbytes = shift == 0 ? PEEKSIZE : PEEKSIZE - shift; if ( nbytes > size ) nbytes = size; unsigned long word; memcpy(&word, ptr, qmin(sizeof(word), nbytes)); // use memcpy() to read unaligned bytes if ( nbytes != PEEKSIZE ) { unsigned long old = qptrace(PTRACE_PEEKTEXT, tid, (void *)(size_t)(ea-shift), 0); if ( errno != 0 ) { ok = 0; break; } unsigned long mask = ~0; mask >>= ((PEEKSIZE - nbytes)*8); mask <<= (shift*8); word <<= (shift*8); word &= mask; word |= old & ~mask; } errno = 0; qptrace(PTRACE_POKETEXT, process_handle, (void *)(size_t)(ea-shift), (void *)word); if ( errno ) { errno = 0; qptrace(PTRACE_POKEDATA, process_handle, (void *)(size_t)(ea-shift), (void *)word); } if ( errno ) { ok = 0; break; } ptr += nbytes; ea += nbytes; size -= nbytes; } if ( suspend ) resume_all_threads(); return ok; } //-------------------------------------------------------------------------- ssize_t idaapi linux_debmod_t::dbg_write_memory(ea_t ea, const void *buffer, size_t size, qstring * /*errbuf*/) { return _write_memory(-1, ea, buffer, size, true); } //-------------------------------------------------------------------------- ssize_t idaapi linux_debmod_t::dbg_read_memory(ea_t ea, void *buffer, size_t size, qstring * /*errbuf*/) { return _read_memory(-1, ea, buffer, size, true); } //-------------------------------------------------------------------------- void linux_debmod_t::add_dll(ea_t base, asize_t size, const char *modname, const char *soname) { debdeb("%a: new dll %s (soname=%s)\n", base, modname, soname); debug_event_t ev; modinfo_t &mi_ll = ev.set_modinfo(LIB_LOADED); ev.pid = process_handle; ev.tid = process_handle; ev.ea = base; ev.handled = true; mi_ll.name = modname; mi_ll.base = base; mi_ll.size = size; mi_ll.rebase_to = BADADDR; if ( is_dll && input_file_path == modname ) mi_ll.rebase_to = base; enqueue_event(ev, IN_FRONT); image_info_t ii(base, ev.modinfo().size, modname, soname); dlls.insert(make_pair(ii.base, ii)); dlls_to_import.insert(ii.base); } #define LOOK_FOR_DEBUG_FILE_DEBUG_FLAG IDA_DEBUG_DEBUGGER #include "../../plugins/dwarf/look_for_debug_file.cpp" //-------------------------------------------------------------------------- void linux_debmod_t::_import_symbols_from_file(name_info_t *out, image_info_t &ii) { struct dll_symbol_importer_t : public symbol_visitor_t { linux_debmod_t *ld; image_info_t ⅈ name_info_t *out; dll_symbol_importer_t(linux_debmod_t *_ld, name_info_t *_out, image_info_t &_ii) : symbol_visitor_t(VISIT_SYMBOLS|VISIT_BUILDID|VISIT_DBGLINK), ld(_ld), ii(_ii), out(_out) {} virtual int visit_symbol(ea_t ea, const char *name) override { ea += ii.base; out->addrs.push_back(ea); out->names.push_back(qstrdup(name)); ii.names[ea] = name; // every 10000th name send a message to ida - we are alive! if ( (out->addrs.size() % 10000) == 0 ) ld->dmsg(""); return 0; } virtual int visit_buildid(const char *buildid) override { ii.buildid = buildid; ld->debdeb("Build ID '%s' of '%s'\n", buildid, ii.fname.c_str()); return 0; } virtual int visit_debuglink(const char *debuglink, uint32 crc) override { ii.debuglink = debuglink; ii.dl_crc = crc; ld->debdeb("debuglink '%s' of '%s'\n", debuglink, ii.fname.c_str()); return 0; } }; if ( ii.base == BADADDR ) { debdeb("Can't import symbols from %s: no imagebase\n", ii.fname.c_str()); return; } dll_symbol_importer_t dsi(this, out, ii); load_elf_symbols(ii.fname.c_str(), dsi); } //------------------------------------------------------------------------- void linux_debmod_t::_import_dll(image_info_t &ii) { bool is_libpthread = stristr(ii.soname.c_str(), "libpthread") != NULL; // keep nptl names in a separate list to be able to resolve them any time name_info_t *storage = is_libpthread ? &nptl_names : &pending_names; if ( is_libpthread ) nptl_base = ii.base; _import_symbols_from_file(storage, ii); // Try to locate file with the separate debug info. // FIXME: should we check that libpthread lacks symbols for libthread_db? // Library.so usually contains debuglink which points to itself, // so we need to avoid to load library.so another time. const char *elf_dbgdir = get_elf_debug_file_directory(); #ifdef TESTABLE_BUILD if ( per_pid_elf_dbgdir_resolver != NULL ) { const char *supp = per_pid_elf_dbgdir_resolver(pid); if ( supp != NULL ) elf_dbgdir = supp; } #endif debug_info_file_visitor_t dif( elf_dbgdir, /*from envvar=*/ true, ii.fname.c_str(), ii.debuglink.c_str(), ii.dl_crc, ii.buildid.c_str()); if ( dif.accept() != 0 && ii.fname != dif.fullpath ) { debdeb("load separate debug info '%s'\n", dif.fullpath); image_info_t ii_deb(ii.base, 0, dif.fullpath, ""); _import_symbols_from_file(storage, ii_deb); } if ( is_libpthread ) { pending_names.addrs.insert(pending_names.addrs.end(), nptl_names.addrs.begin(), nptl_names.addrs.end()); pending_names.names.insert(pending_names.names.end(), nptl_names.names.begin(), nptl_names.names.end()); for ( int i=0; i < nptl_names.names.size(); i++ ) nptl_names.names[i] = qstrdup(nptl_names.names[i]); } } //-------------------------------------------------------------------------- // enumerate names from the specified shared object and save the results // we'll need to send it to IDA later // if libname == NULL, enum all modules void linux_debmod_t::enum_names(const char *libname) { if ( dlls_to_import.empty() ) return; for ( easet_t::iterator p=dlls_to_import.begin(); p != dlls_to_import.end(); ) { images_t::iterator q = dlls.find(*p); if ( q != dlls.end() ) { image_info_t &ii = q->second; if ( libname != NULL && ii.soname != libname ) { ++p; continue; } _import_dll(ii); } p = dlls_to_import.erase(p); } } //-------------------------------------------------------------------------- ea_t linux_debmod_t::find_pending_name(const char *name) { if ( name == NULL ) return BADADDR; // enumerate pending names in reverse order. we need this to find the latest // resolved address for a name (on android, pthread_..() functions exist twice) for ( int i=pending_names.addrs.size()-1; i >= 0; --i ) if ( streq(pending_names.names[i], name) ) return pending_names.addrs[i]; for ( int i=0; i < nptl_names.addrs.size(); ++i ) if ( streq(nptl_names.names[i], name) ) return nptl_names.addrs[i]; return BADADDR; } //-------------------------------------------------------------------------- void idaapi linux_debmod_t::dbg_stopped_at_debug_event(import_infos_t *, bool dlls_added, thread_name_vec_t *thr_names) { if ( dlls_added ) { // we will take advantage of this event to import information // about the exported functions from the loaded dlls enum_names(); name_info_t &ni = *get_debug_names(); ni = pending_names; // NB: ownership of name pointers is transferred pending_names.clear(); } if ( thr_names != NULL ) update_thread_names(thr_names); } //-------------------------------------------------------------------------- void linux_debmod_t::cleanup(void) { // if the process is still running, kill it, otherwise it runs uncontrolled // normally the process is dead at this time but may survive if we arrive // here after an interr. if ( process_handle != INVALID_HANDLE_VALUE ) dbg_exit_process(NULL); process_handle = INVALID_HANDLE_VALUE; thread_handle = INVALID_HANDLE_VALUE; is_dll = false; requested_to_suspend = false; in_event = false; threads.clear(); dlls.clear(); dlls_to_import.clear(); events.clear(); if ( mapfp != NULL ) { qfclose(mapfp); mapfp = NULL; } complained_shlib_bpt = false; bpts.clear(); tdb_delete(); erase_internal_bp(birth_bpt); erase_internal_bp(death_bpt); erase_internal_bp(shlib_bpt); npending_signals = 0; interp.clear(); exe_path.qclear(); exited = false; for ( int i=0; i < nptl_names.names.size(); i++ ) qfree(nptl_names.names[i]); nptl_names.clear(); inherited::cleanup(); } //-------------------------------------------------------------------------- // // DEBUGGER INTERFACE FUNCTIONS // //-------------------------------------------------------------------------- inline const char *skipword(const char *ptr) { while ( !qisspace(*ptr) && *ptr != '\0' ) ptr++; return ptr; } //-------------------------------------------------------------------------- // find a dll in the memory information array static const memory_info_t *find_dll(const meminfo_vec_t &miv, const char *name) { for ( int i=0; i < miv.size(); i++ ) if ( miv[i].name == name ) return &miv[i]; return NULL; } //-------------------------------------------------------------------------- static memory_info_t *find_dll(meminfo_vec_t &miv, const char *name) //lint !e1764 could be reference to const { return CONST_CAST(memory_info_t *)(find_dll(CONST_CAST(const meminfo_vec_t &)(miv), name)); } //-------------------------------------------------------------------------- bool linux_debmod_t::add_shlib_bpt(const meminfo_vec_t &miv, bool attaching) { if ( shlib_bpt.bpt_addr != 0 ) return true; qstring interp_soname; if ( interp.empty() ) { // find out the loader name struct interp_finder_t : public symbol_visitor_t { qstring interp; interp_finder_t(void) : symbol_visitor_t(VISIT_INTERP) {} virtual int visit_symbol(ea_t, const char *) override { return 0; } // unused virtual int visit_interp(const char *name) override { interp = name; return 2; } }; interp_finder_t itf; const char *exename = exe_path.c_str(); int code = load_elf_symbols(exename, itf); if ( code == 0 ) { // no interpreter if ( !complained_shlib_bpt ) { complained_shlib_bpt = true; dwarning("AUTOHIDE DATABASE\n%s:\n" "Could not find the elf interpreter name,\n" "shared object events will not be reported", exename); } return false; } if ( code != 2 ) { dwarning("%s: could not read symbols on remote computer", exename); return false; } char path[QMAXPATH]; qmake_full_path(path, sizeof(path), itf.interp.c_str()); interp_soname.swap(itf.interp); interp = path; } else { interp_soname = qbasename(interp.c_str()); } // check if it is present in the memory map (normally it is) debdeb("INTERP: %s, SONAME: %s\n", interp.c_str(), interp_soname.c_str()); const memory_info_t *mi = find_dll(miv, interp.c_str()); if ( mi == NULL ) { dwarning("%s: could not find in process memory", interp.c_str()); return false; } asize_t size = calc_module_size(miv, mi); add_dll(mi->start_ea, size, interp.c_str(), interp_soname.c_str()); // set bpt at r_brk enum_names(interp_soname.c_str()); // update the name list const char *bpt_name = "_r_debug"; ea_t ea = find_pending_name(bpt_name); if ( ea != BADADDR ) { struct r_debug rd; if ( _read_memory(-1, ea, &rd, sizeof(rd), false) == sizeof(rd) ) { if ( rd.r_brk != 0 ) { if ( !add_internal_bp(shlib_bpt, rd.r_brk) ) { ea_t ea1 = rd.r_brk; debdeb("%a: could not set shlib bpt\n", ea1); } } } } if ( shlib_bpt.bpt_addr == 0 ) { static const char *const shlib_bpt_names[] = { "r_debug_state", "_r_debug_state", "_dl_debug_state", "rtld_db_dlactivity", "_rtld_debug_state", NULL }; for ( int i=0; i < qnumber(shlib_bpt_names); i++ ) { bpt_name = shlib_bpt_names[i]; ea = find_pending_name(bpt_name); if ( ea != BADADDR && ea != 0 ) { if ( add_internal_bp(shlib_bpt, ea) ) break; debdeb("%a: could not set shlib bpt (name=%s)\n", ea, bpt_name); } } if ( shlib_bpt.bpt_addr == 0 ) { #if defined(__ANDROID__) && defined(__X86__) // Last attempt for old Android, // the modern Android doesn't need the special handling return add_android_shlib_bpt(miv, attaching); #else qnotused(attaching); return false; #endif } } debdeb("%a: added shlib bpt (%s)\n", shlib_bpt.bpt_addr, bpt_name); return true; } //-------------------------------------------------------------------------- thread_info_t &linux_debmod_t::add_thread(int tid) { std::pair ret = threads.insert(std::make_pair(tid, thread_info_t(tid))); thread_info_t &ti = ret.first->second; get_thread_name(&ti.name, tid); return ti; } //-------------------------------------------------------------------------- void linux_debmod_t::del_thread(int tid) { threads_t::iterator p = threads.find(tid); QASSERT(30064, p != threads.end()); if ( p->second.got_pending_status ) npending_signals--; threads.erase(p); if ( deleted_threads.size() >= 10 ) deleted_threads.erase(deleted_threads.begin()); deleted_threads.push_back(tid); } //-------------------------------------------------------------------------- bool linux_debmod_t::handle_process_start(pid_t _pid, attach_mode_t attaching) { pid = _pid; deleted_threads.clear(); process_handle = pid; add_thread(pid); int status; int options = 0; if ( attaching == AMT_ATTACH_BROKEN ) options = WNOHANG; qwait(&status, pid, options); // (should succeed) consume SIGSTOP debdeb("process pid/tid: %d\n", pid); may_run = false; char fname[QMAXPATH]; debug_event_t ev; modinfo_t &mi_ps = ev.set_modinfo(PROCESS_STARTED); ev.pid = pid; ev.tid = pid; ev.ea = get_ip(pid); ev.handled = true; get_exec_fname(pid, fname, sizeof(fname)); mi_ps.name = fname; mi_ps.base = BADADDR; mi_ps.size = 0; mi_ps.rebase_to = BADADDR; qsnprintf(fname, sizeof(fname), "/proc/%u/maps", pid); mapfp = fopenRT(fname); if ( mapfp == NULL ) { dmsg("%s: %s\n", fname, winerr(errno)); return false; // if fails, the process did not start } exe_path = mi_ps.name.c_str(); if ( !is_dll ) input_file_path = exe_path; // find the executable base meminfo_vec_t miv; // init debapp_attrs.addrsize: 32bit application by default // get_memory_info() may correct it if meets a 64-bit address debapp_attrs.addrsize = 4; if ( get_memory_info(miv, false) <= 0 ) INTERR(30065); term_reg_ctx(); init_reg_ctx(); const memory_info_t *mi = find_dll(miv, mi_ps.name.c_str()); if ( mi != NULL ) { mi_ps.base = mi->start_ea; mi_ps.size = mi->end_ea - mi->start_ea; if ( !is_dll ) // exe files: rebase idb to the loaded address mi_ps.rebase_to = mi->start_ea; } else { if ( !is_dll ) dmsg("%s: nowhere in the process memory?!\n", mi_ps.name.c_str()); } if ( !add_shlib_bpt(miv, attaching) ) dmsg("Could not set the shlib bpt, shared object events will not be handled\n"); enqueue_event(ev, IN_BACK); if ( attaching != AMT_NO_ATTACH ) { modinfo_t &mi_pa = ev.set_modinfo(PROCESS_ATTACHED); enqueue_event(ev, IN_BACK); if ( !qgetenv("IDA_SKIP_SYMS", NULL) ) { // collect exported names from the main module qstring soname; get_soname(mi_pa.name.c_str(), &soname); image_info_t ii(mi_pa.base, mi_pa.size, mi_pa.name.c_str(), soname); _import_dll(ii); } } return true; } //-------------------------------------------------------------------------- static void idaapi kill_all_processes(void) { struct ida_local process_killer_t : public debmod_visitor_t { virtual int visit(debmod_t *debmod) override { linux_debmod_t *ld = (linux_debmod_t *)debmod; if ( ld->process_handle != INVALID_HANDLE_VALUE ) qkill(ld->process_handle, SIGKILL); return 0; } }; process_killer_t pk; for_all_debuggers(pk); } //-------------------------------------------------------------------------- drc_t idaapi linux_debmod_t::dbg_start_process( const char *path, const char *args, const char *startdir, int flags, const char *input_path, uint32 input_file_crc32, qstring *errbuf) { void *child_pid; drc_t drc = maclnx_launch_process(this, path, args, startdir, flags, input_path, input_file_crc32, &child_pid, errbuf); if ( drc > 0 && child_pid != NULL && !handle_process_start(size_t(child_pid), AMT_NO_ATTACH) ) { dbg_exit_process(NULL); drc = DRC_NETERR; } return drc; } //-------------------------------------------------------------------------- // 1-ok, 0-failed drc_t idaapi linux_debmod_t::dbg_attach_process(pid_t _pid, int /*event_id*/, int flags, qstring * /*errbuf*/) { is_dll = (flags & DBG_PROC_IS_DLL) != 0; if ( qptrace(PTRACE_ATTACH, _pid, NULL, NULL) == 0 && handle_process_start(_pid, AMT_ATTACH_NORMAL) ) { gen_library_events(_pid); // detect all loaded libraries return DRC_OK; } qptrace(PTRACE_DETACH, _pid, NULL, NULL); return DRC_FAILED; } //-------------------------------------------------------------------------- void linux_debmod_t::cleanup_signals(void) { for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) { // cannot leave pending sigstop, try to recieve and handle it if ( p->second.waiting_sigstop ) { thread_info_t &ti = p->second; ldeb("cleanup_signals:\n"); log(ti.tid, "must be STOPPED\n"); QASSERT(30181, ti.state == STOPPED); qptrace(PTRACE_CONT, ti.tid, 0, 0); int status; int tid = check_for_signal(&status, ti.tid, -1); if ( tid != ti.tid ) msg("%d: failed to clean up pending SIGSTOP\n", tid); } } } //-------------------------------------------------------------------------- void linux_debmod_t::cleanup_breakpoints(void) { erase_internal_bp(birth_bpt); erase_internal_bp(death_bpt); erase_internal_bp(shlib_bpt); } //-------------------------------------------------------------------------- drc_t idaapi linux_debmod_t::dbg_detach_process(void) { // restore only internal breakpoints and signals cleanup_breakpoints(); cleanup_signals(); bool had_pid = false; bool ok = true; log(-1, "detach all threads.\n"); for ( threads_t::iterator p=threads.begin(); ok && p != threads.end(); ++p ) { thread_info_t &ti = p->second; if ( ti.tid == process_handle ) had_pid = true; ok = qptrace(PTRACE_DETACH, ti.tid, NULL, NULL) == 0; log(-1, "detach tid %d: ok=%d\n", ti.tid, ok); } if ( ok && !had_pid ) { // if pid was not in the thread list, detach it separately ok = qptrace(PTRACE_DETACH, process_handle, NULL, NULL) == 0; log(-1, "detach pid %d: ok=%d\n", process_handle, ok); } if ( ok ) { debug_event_t ev; ev.set_eid(PROCESS_DETACHED); ev.pid = process_handle; ev.tid = process_handle; ev.ea = BADADDR; ev.handled = true; enqueue_event(ev, IN_BACK); in_event = false; exited = true; threads.clear(); process_handle = INVALID_HANDLE_VALUE; return DRC_OK; } return DRC_FAILED; } //-------------------------------------------------------------------------- // if we have to do something as soon as we noticed the connection // broke, this is the correct place bool idaapi linux_debmod_t::dbg_prepare_broken_connection(void) { broken_connection = true; return true; } //-------------------------------------------------------------------------- // 1-ok, 0-failed drc_t idaapi linux_debmod_t::dbg_prepare_to_pause_process(qstring * /*errbuf*/) { if ( events.empty() ) { qkill(process_handle, SIGSTOP); thread_info_t &ti = threads.begin()->second; ti.waiting_sigstop = true; } may_run = false; requested_to_suspend = true; ldeb("requested_to_suspend := 1\n"); return DRC_OK; } //-------------------------------------------------------------------------- drc_t idaapi linux_debmod_t::dbg_exit_process(qstring * /*errbuf*/) { ldeb("------- exit process\n"); bool ok = true; // suspend all threads to avoid problems (for example, killing a // thread may resume another thread and it can throw an exception because // of that) suspend_all_threads(); for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) { thread_info_t &ti = p->second; if ( ti.state == STOPPED ) { if ( qptrace(PTRACE_KILL, ti.tid, 0, (void*)SIGKILL) != 0 && errno != ESRCH ) { dmsg("PTRACE_KILL %d: %s\n", ti.tid, strerror(errno)); ok = false; } } else { if ( ti.tid != INVALID_HANDLE_VALUE && qkill(ti.tid, SIGKILL) != 0 && errno != ESRCH ) { dmsg("SIGKILL %d: %s\n", ti.tid, strerror(errno)); ok = false; } } if ( ok ) { set_thread_state(ti, RUNNING); ti.suspend_count = 0; } } if ( ok ) process_handle = INVALID_HANDLE_VALUE; may_run = true; exited = true; return ok ? DRC_OK : DRC_FAILED; } //-------------------------------------------------------------------------- // Set hardware breakpoints for one thread bool linux_debmod_t::set_hwbpts(HANDLE hThread) const { #ifdef __ARM__ qnotused(hThread); return false; #else bool ok = set_dr(hThread, 0, hwbpt_ea[0]) && set_dr(hThread, 1, hwbpt_ea[1]) && set_dr(hThread, 2, hwbpt_ea[2]) && set_dr(hThread, 3, hwbpt_ea[3]) && set_dr(hThread, 6, 0) && set_dr(hThread, 7, dr7); // msg("set_hwbpts: DR0=%a DR1=%a DR2=%a DR3=%a DR7=%a => %d\n", // hwbpt_ea[0], // hwbpt_ea[1], // hwbpt_ea[2], // hwbpt_ea[3], // dr7, // ok); return ok; #endif } //-------------------------------------------------------------------------- bool linux_debmod_t::refresh_hwbpts(void) { for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) if ( !set_hwbpts(p->second.tid) ) return false; return true; } //-------------------------------------------------------------------------- bool linux_debmod_t::erase_internal_bp(internal_bpt &bp) { bool ok = bp.bpt_addr == 0 || dbg_del_bpt(BPT_SOFT, bp.bpt_addr, bp.saved, bp.nsaved); bp.bpt_addr = 0; bp.nsaved = 0; return ok; } //-------------------------------------------------------------------------- bool linux_debmod_t::add_internal_bp(internal_bpt &bp, ea_t addr) { int len = -1; int nread = sizeof(bp.saved); #ifdef __ARM__ if ( (addr & 1) != 0 ) { len = 2; addr--; } else { len = 4; } CASSERT(sizeof(bp.saved) >= 4); nread = len; #endif if ( _read_memory(-1, addr, bp.saved, nread) == nread ) { if ( dbg_add_bpt(NULL, BPT_SOFT, addr, len) ) { bp.bpt_addr = addr; bp.nsaved = nread; return true; } } return false; } //-------------------------------------------------------------------------- // 1-ok, 0-failed, -2-read failed int idaapi linux_debmod_t::dbg_add_bpt( bytevec_t *orig_bytes, bpttype_t type, ea_t ea, int len) { #if defined(__ARM__) && defined(__X86__) bool is_thumb32_bpt = false; if ( len == (2 | USE_THUMB32_BPT) ) { is_thumb32_bpt = true; len = 4; } #endif ldeb("%a: add bpt (size=%d)\n", ea, len); if ( type == BPT_SOFT ) { if ( orig_bytes != NULL && read_bpt_orgbytes(orig_bytes, ea, len) < 0 ) return -2; const uchar *bptcode = bpt_code.begin(); #ifdef __ARM__ # ifndef __X86__ if ( len < 0 ) len = bpt_code.size(); bptcode = aarch64_bpt; # else if ( len < 0 ) { // unknown mode. we have to decide between thumb and arm bpts // ideally we would decode the instruction and try to determine its mode // unfortunately we do not have instruction decoder in arm server. // besides, it cannot really help. // just check for some known opcodes. this is bad but i do not know // how to do better. len = 4; // default to arm mode uchar opcodes[2]; if ( dbg_read_memory(ea, opcodes, sizeof(opcodes), NULL) == sizeof(opcodes) ) { static const uchar ins1[] = { 0x70, 0x47 }; // BX LR static const uchar ins3[] = { 0x00, 0xB5 }; // PUSH {LR} static const uchar ins2[] = { 0x00, 0xBD }; // POP {PC} static const uchar *const ins[] = { ins1, ins2, ins3 }; for ( int i=0; i < qnumber(ins); i++ ) { const uchar *p = ins[i]; if ( opcodes[0] == p[0] && opcodes[1] == p[1] ) { len = 2; break; } } } } if ( len == 2 ) bptcode = thumb16_bpt; else if ( len == 4 && is_thumb32_bpt ) bptcode = thumb32_bpt; # endif #else if ( len < 0 ) len = bpt_code.size(); #endif QASSERT(30066, len > 0 && len <= bpt_code.size()); debmod_bpt_t dbpt(ea, len); if ( dbg_read_memory(ea, dbpt.saved, len, NULL) && dbg_write_memory(ea, bptcode, len, NULL) == len ) { bpts[ea] = dbpt; removed_bpts.erase(ea); return 1; } } #ifndef __ARM__ if ( add_hwbpt(type, ea, len) ) return 1; #endif return 0; } //-------------------------------------------------------------------------- #ifdef __ARM__ void linux_debmod_t::adjust_swbpt(ea_t *p_ea, int *p_len) { inherited::adjust_swbpt(p_ea, p_len); // for thumb mode we have to decide between 16-bit and 32-bit bpt if ( *p_len == 2 ) { uint16 opcode; if ( dbg_read_memory(*p_ea, &opcode, sizeof(opcode), NULL) <= 0 ) return; if ( is_32bit_thumb_insn(opcode) ) *p_len |= USE_THUMB32_BPT; // ask for thumb32 bpt } } #endif //-------------------------------------------------------------------------- // 1-ok, 0-failed int idaapi linux_debmod_t::dbg_del_bpt(bpttype_t type, ea_t ea, const uchar *orig_bytes, int len) { #if defined(__ARM__) && defined(__X86__) if ( len == (2 | USE_THUMB32_BPT) ) len = 4; #endif ldeb("%a: del bpt (size=%d) exited=%d\n", ea, len, exited); if ( orig_bytes != NULL ) { if ( dbg_write_memory(ea, orig_bytes, len, NULL) == len ) { removed_bpts.insert(ea); return true; } } #ifdef __ARM__ qnotused(type); return false; #else return del_hwbpt(ea, type); #endif } //-------------------------------------------------------------------------- drc_t idaapi linux_debmod_t::dbg_thread_get_sreg_base(ea_t *pea, thid_t tid, int sreg_value, qstring * /*errbuf*/) { #ifdef __ARM__ qnotused(tid); qnotused(sreg_value); qnotused(pea); return DRC_FAILED; #else *pea = 0; // all other selectors (cs, ds) usually have base of 0... // since we do not receive the segment register id we need to retrieve, we // rely on the register value, which is not great. for example, // on x64 fs==gs==0, and when IDA passes sreg_value==0, we return the // base of fs. if ( sreg_value != 0 ) { // find out which selector we're asked to retrieve struct user_regs_struct regs; memset(®s, -1, sizeof(regs)); if ( qptrace(PTRACE_GETREGS, tid, 0, ®s) != 0 ) return DRC_FAILED; if ( sreg_value == regs.INTEL_SREG(fs) ) return thread_get_fs_base(tid, fs_idx, pea) ? DRC_OK : DRC_FAILED; else if ( sreg_value == regs.INTEL_SREG(gs) ) return thread_get_fs_base(tid, gs_idx, pea) ? DRC_OK : DRC_FAILED; } return DRC_OK; #endif } //-------------------------------------------------------------------------- drc_t idaapi linux_debmod_t::dbg_thread_suspend(thid_t tid) { thread_info_t *ti = get_thread(tid); if ( ti == NULL ) return DRC_FAILED; if ( !dbg_freeze_threads(tid, false) ) return DRC_FAILED; ti->user_suspend++; return DRC_OK; } //-------------------------------------------------------------------------- drc_t idaapi linux_debmod_t::dbg_thread_continue(thid_t tid) { thread_info_t *ti = get_thread(tid); if ( ti == NULL ) return DRC_FAILED; if ( ti->user_suspend > 0 ) { if ( --ti->user_suspend > 0 ) return DRC_OK; } return dbg_thaw_threads(tid, false) ? DRC_OK : DRC_FAILED; } //-------------------------------------------------------------------------- drc_t idaapi linux_debmod_t::dbg_set_resume_mode(thid_t tid, resume_mode_t resmod) { if ( resmod != RESMOD_INTO ) return DRC_FAILED; // not supported thread_info_t *t = get_thread(tid); if ( t == NULL ) return DRC_FAILED; t->single_step = true; return DRC_OK; } //-------------------------------------------------------------------------- bool linux_debmod_t::emulate_retn(int tid) { #ifdef __ARM__ # ifndef __X86__ struct user_regs_struct regs; if ( !qptrace_get_prstatus(®s, tid) ) return false; // emulate BX LR regs.pc = regs.regs[LRREG_IDX]; // PC <- LR return qptrace_set_regset(NT_PRSTATUS, tid, ®s, sizeof(regs)); # else struct user_regs_struct regs; qptrace(PTRACE_GETREGS, tid, 0, ®s); // emulate BX LR int tbit = regs.uregs[14] & 1; regs.PCREG = regs.uregs[14] & ~1; // PC <- LR setflag(regs.uregs[16], 1<<5, tbit); // Set/clear T bit in PSR return qptrace(PTRACE_SETREGS, tid, 0, ®s) == 0; # endif #else struct user_regs_struct regs; qptrace(PTRACE_GETREGS, tid, 0, ®s); size_t sizeof_pcreg = debapp_attrs.addrsize; if ( _read_memory(tid, regs.SPREG, ®s.PCREG, sizeof_pcreg, false) != sizeof_pcreg ) { log(-1, "%d: reading return address from %a failed\n", tid, ea_t(regs.SPREG)); if ( tid == process_handle ) return false; if ( _read_memory(process_handle, regs.SPREG, ®s.PCREG, sizeof_pcreg, false) != sizeof_pcreg ) { log(-1, "%d: reading return address from %a failed (2)\n", process_handle, ea_t(regs.SPREG)); return false; } } regs.SPREG += sizeof_pcreg; log(-1, "%d: retn to %a\n", tid, ea_t(regs.PCREG)); return qptrace(PTRACE_SETREGS, tid, 0, ®s) == 0; #endif } //-------------------------------------------------------------------------- #define qoffsetof2(s, f) (qoffsetof(regctx_t, s) + qoffsetof(decltype(regctx_t::s), f)) #define offset_size(s, f) qoffsetof2(s, f), sizeof(decltype(regctx_t::s)::f) #ifdef __ARM__ # ifndef __X86__ //-------------------------------------------------------------------------- // AArch64 //-------------------------------------------------------------------------- struct regctx_t : public regctx_base_t { struct user_regs_struct regs; // clsmask helpers bool clsmask_regs; regctx_t(dynamic_register_set_t &_idaregs); bool init(void); bool load(void); bool store(void); }; //-------------------------------------------------------------------------- regctx_t::regctx_t(dynamic_register_set_t &_idaregs) : regctx_base_t(_idaregs) { memset(®s, 0, sizeof(regs)); clsmask_regs = 0; idaregs.set_regclasses(arm_register_classes); } //-------------------------------------------------------------------------- bool regctx_t::init(void) { if ( (clsmask & ARM_RC_ALL) == 0 ) return false; // setup clsmask helpers clsmask_regs = (clsmask & ARM_RC_GENERAL) != 0; return true; } //-------------------------------------------------------------------------- bool regctx_t::load(void) { if ( !init() ) return false; if ( clsmask_regs ) if ( !qptrace_get_prstatus(®s, tid) ) return false; return true; } //-------------------------------------------------------------------------- bool regctx_t::store(void) { if ( clsmask_regs ) if ( !qptrace_set_regset(NT_PRSTATUS, tid, ®s, sizeof(regs)) ) return false; return true; } //-------------------------------------------------------------------------- void linux_debmod_t::init_reg_ctx(void) { reg_ctx = new regctx_t(idaregs); // Populate register context reg_ctx->add_ival(arch_registers[R_R0], offset_size(regs, regs[0])); reg_ctx->add_ival(arch_registers[R_R1], offset_size(regs, regs[1])); reg_ctx->add_ival(arch_registers[R_R2], offset_size(regs, regs[2])); reg_ctx->add_ival(arch_registers[R_R3], offset_size(regs, regs[3])); reg_ctx->add_ival(arch_registers[R_R4], offset_size(regs, regs[4])); reg_ctx->add_ival(arch_registers[R_R5], offset_size(regs, regs[5])); reg_ctx->add_ival(arch_registers[R_R6], offset_size(regs, regs[6])); reg_ctx->add_ival(arch_registers[R_R7], offset_size(regs, regs[7])); reg_ctx->add_ival(arch_registers[R_R8], offset_size(regs, regs[8])); reg_ctx->add_ival(arch_registers[R_R9], offset_size(regs, regs[9])); reg_ctx->add_ival(arch_registers[R_R10], offset_size(regs, regs[10])); reg_ctx->add_ival(arch_registers[R_R11], offset_size(regs, regs[11])); reg_ctx->add_ival(arch_registers[R_R12], offset_size(regs, regs[12])); reg_ctx->add_ival(arch_registers[R_R13], offset_size(regs, regs[13])); reg_ctx->add_ival(arch_registers[R_R14], offset_size(regs, regs[14])); reg_ctx->add_ival(arch_registers[R_R15], offset_size(regs, regs[15])); reg_ctx->add_ival(arch_registers[R_R16], offset_size(regs, regs[16])); reg_ctx->add_ival(arch_registers[R_R17], offset_size(regs, regs[17])); reg_ctx->add_ival(arch_registers[R_R18], offset_size(regs, regs[18])); reg_ctx->add_ival(arch_registers[R_R19], offset_size(regs, regs[19])); reg_ctx->add_ival(arch_registers[R_R20], offset_size(regs, regs[20])); reg_ctx->add_ival(arch_registers[R_R21], offset_size(regs, regs[21])); reg_ctx->add_ival(arch_registers[R_R22], offset_size(regs, regs[22])); reg_ctx->add_ival(arch_registers[R_R23], offset_size(regs, regs[23])); reg_ctx->add_ival(arch_registers[R_R24], offset_size(regs, regs[24])); reg_ctx->add_ival(arch_registers[R_R25], offset_size(regs, regs[25])); reg_ctx->add_ival(arch_registers[R_R26], offset_size(regs, regs[26])); reg_ctx->add_ival(arch_registers[R_R27], offset_size(regs, regs[27])); reg_ctx->add_ival(arch_registers[R_R28], offset_size(regs, regs[28])); reg_ctx->add_ival(arch_registers[R_R29], offset_size(regs, regs[29])); lr_idx = reg_ctx->add_ival(arch_registers[R_LR], offset_size(regs, regs[30])); sp_idx = reg_ctx->add_ival(arch_registers[R_SP], offset_size(regs, sp)); pc_idx = reg_ctx->add_ival(arch_registers[R_PC], offset_size(regs, pc)); sr_idx = reg_ctx->add_ival(arch_registers[R_PSR], offset_size(regs, pstate)); // 32-bit } # else // __ARM__ && __X86__ //-------------------------------------------------------------------------- // ARM (32-bit) //-------------------------------------------------------------------------- struct regctx_t : public regctx_base_t { struct user_regs_struct regs; #ifdef __HAVE_ARM_VFP__ struct user_vfp vfp_regs; #endif // clsmask helpers bool clsmask_regs; #ifdef __HAVE_ARM_VFP__ bool clsmask_vfp; #endif regctx_t(dynamic_register_set_t &_idaregs); bool init(void); bool load(void); bool store(void); }; //-------------------------------------------------------------------------- regctx_t::regctx_t(dynamic_register_set_t &_idaregs) : regctx_base_t(_idaregs) { memset(®s, 0, sizeof(regs)); #ifdef __HAVE_ARM_VFP__ memset(&vfp_regs, 0, sizeof(vfp_regs)); #endif clsmask_regs = 0; #ifdef __HAVE_ARM_VFP__ clsmask_vfp = 0; #endif idaregs.set_regclasses(arm_register_classes); } //-------------------------------------------------------------------------- bool regctx_t::init(void) { if ( (clsmask & ARM_RC_ALL) == 0 ) return false; // setup clsmask helpers clsmask_regs = (clsmask & ARM_RC_GENERAL) != 0; #ifdef __HAVE_ARM_VFP__ clsmask_vfp = (clsmask & ARM_RC_VFP) != 0; #endif return true; } //-------------------------------------------------------------------------- bool regctx_t::load(void) { if ( !init() ) return false; if ( clsmask_regs ) if ( qptrace(PTRACE_GETREGS, tid, 0, ®s) != 0 ) return false; #ifdef __HAVE_ARM_VFP__ if ( clsmask_vfp ) if ( qptrace(PTRACE_GETVFPREGS, tid, 0, &vfp_regs) != 0 ) return false; #endif return true; } //-------------------------------------------------------------------------- bool regctx_t::store(void) { if ( clsmask_regs ) if ( qptrace(PTRACE_SETREGS, tid, 0, ®s) != 0 ) return false; #ifdef __HAVE_ARM_VFP__ if ( clsmask_vfp ) if ( qptrace(PTRACE_SETVFPREGS, tid, 0, &vfp_regs) != 0 ) return false; #endif return true; } //-------------------------------------------------------------------------- void linux_debmod_t::init_reg_ctx(void) { reg_ctx = new regctx_t(idaregs); // Populate register context reg_ctx->add_ival(arch_registers[R_R0], offset_size(regs, uregs[0])); reg_ctx->add_ival(arch_registers[R_R1], offset_size(regs, uregs[1])); reg_ctx->add_ival(arch_registers[R_R2], offset_size(regs, uregs[2])); reg_ctx->add_ival(arch_registers[R_R3], offset_size(regs, uregs[3])); reg_ctx->add_ival(arch_registers[R_R4], offset_size(regs, uregs[4])); reg_ctx->add_ival(arch_registers[R_R5], offset_size(regs, uregs[5])); reg_ctx->add_ival(arch_registers[R_R6], offset_size(regs, uregs[6])); reg_ctx->add_ival(arch_registers[R_R7], offset_size(regs, uregs[7])); reg_ctx->add_ival(arch_registers[R_R8], offset_size(regs, uregs[8])); reg_ctx->add_ival(arch_registers[R_R9], offset_size(regs, uregs[9])); reg_ctx->add_ival(arch_registers[R_R10], offset_size(regs, uregs[10])); reg_ctx->add_ival(arch_registers[R_R11], offset_size(regs, uregs[11])); reg_ctx->add_ival(arch_registers[R_R12], offset_size(regs, uregs[12])); sp_idx = reg_ctx->add_ival(arch_registers[R_SP], offset_size(regs, uregs[13])); lr_idx = reg_ctx->add_ival(arch_registers[R_LR], offset_size(regs, uregs[14])); pc_idx = reg_ctx->add_ival(arch_registers[R_PC], offset_size(regs, uregs[15])); sr_idx = reg_ctx->add_ival(arch_registers[R_PSR], offset_size(regs, uregs[16])); #ifdef __HAVE_ARM_VFP__ size_t offset = qoffsetof2(vfp_regs, fpregs); for ( size_t i = R_D0; i <= R_D31; i++, offset += sizeof(int64) ) reg_ctx->add_data(arch_registers[i], offset, sizeof(int64)); reg_ctx->add_ival(arch_registers[R_FPSCR], offset_size(vfp_regs, fpscr)); #endif } # endif #else // !__ARM__ //-------------------------------------------------------------------------- // X86/X64 //------------------------------------------------------------------------- //-------------------------------------------------------------------------- //lint -esym(749,TAG_*) local enumeration constant '' not referenced enum { TAG_VALID = 0, TAG_ZERO = 1, TAG_SPECIAL = 2, TAG_EMPTY = 3, }; //------------------------------------------------------------------------- // Intel® 64 and IA-32 Architectures Software Developer's Manual // Volume 1: Basic Architecture // 253665-070US May 2019 // 13.1 XSAVE-SUPPORTED FEATURES AND STATE-COMPONENT BITMAPS // Bit 1 corresponds to the state component used for registers used by the // streaming SIMD extensions (SSE state). #define X86_XSTATE_SSE (1ULL << 1) // Bit 2 corresponds to the state component used for the additional register // state used by the Intel® Advanced Vector Extensions (AVX state). #define X86_XSTATE_AVX (1ULL << 2) // 13.4 XSAVE AREA // The legacy region of an XSAVE area comprises the 512 bytes starting at the // area's base address. [...] The XSAVE feature set uses the legacy area for // x87 state (state component 0) and SSE state (state component 1). #define XSAVE_LEGACY_REGION_OFFSET 0 // The XSAVE header of an XSAVE area comprises the 64 bytes starting at // offset 512 from the area’s base address: #define XSAVE_HEADER_OFFSET 512 // The extended region of an XSAVE area starts at an offset of 576 bytes from // the area's base address. #define XSAVE_EXTENDED_REGION_OFFSET 576 // 13.4.2 XSAVE Header // Bytes 7:0 of the XSAVE header is a state-component bitmap (see Section // 13.1) called XSTATE_BV. #define XSAVE_XSTATE_BV XSAVE_HEADER_OFFSET // 13.5.2 SSE State // Bytes 287:160 are used for the registers XMM0–XMM7. // Bytes 415:288 are used for the registers XMM8–XMM15. #define XSAVE_XMM_OFFSET_BASE (XSAVE_LEGACY_REGION_OFFSET + 160) // 13.5.3 AVX State // Bytes 127:0 of the AVX-state section are used for YMM0_H–YMM7_H. // Bytes 255:128 are used for YMM8_H–YMM15_H. #define XSAVE_YMMH_OFFSET_BASE XSAVE_EXTENDED_REGION_OFFSET //-------------------------------------------------------------------------- struct regctx_t : public regctx_base_t { struct user_regs_struct regs; struct user_fpregs_struct i387; #ifdef __X86__ struct user_fpxregs_struct x387; #endif uint8_t xstate[X86_XSTATE_MAX_SIZE]; struct iovec ymm_iov; // clsmask helpers bool clsmask_regs; bool clsmask_fpregs; #ifdef __X86__ bool clsmask_fpxregs; #endif bool clsmask_ymm; regctx_t(dynamic_register_set_t &_idaregs); bool init(void); bool load(void); bool store(void); }; //-------------------------------------------------------------------------- regctx_t::regctx_t(dynamic_register_set_t &_idaregs) : regctx_base_t(_idaregs) { memset(®s, 0, sizeof(regs)); memset(&i387, 0, sizeof(i387)); #ifdef __X86__ memset(&x387, 0, sizeof(x387)); #endif memset(xstate, 0, sizeof(xstate)); clsmask_regs = 0; clsmask_fpregs = 0; #ifdef __X86__ clsmask_fpxregs = 0; #endif clsmask_ymm = 0; ymm_iov.iov_base = xstate; ymm_iov.iov_len = sizeof(xstate); idaregs.set_regclasses(x86_register_classes); } //-------------------------------------------------------------------------- bool regctx_t::init(void) { if ( (clsmask & X86_RC_ALL) == 0 ) return false; // setup clsmask helpers clsmask_regs = (clsmask & (X86_RC_GENERAL|X86_RC_SEGMENTS)) != 0; #ifdef __X86__ // 32-bit version uses two different structures to return xmm & fpu clsmask_fpregs = (clsmask & (X86_RC_FPU|X86_RC_MMX)) != 0; clsmask_fpxregs = (clsmask & X86_RC_XMM) != 0; #else // 64-bit version uses one struct to return xmm & fpu clsmask_fpregs = (clsmask & (X86_RC_FPU|X86_RC_MMX|X86_RC_XMM)) != 0; #endif clsmask_ymm = (clsmask & X86_RC_YMM) != 0; return true; } //-------------------------------------------------------------------------- bool regctx_t::load(void) { if ( !init() ) return false; if ( clsmask_regs ) if ( qptrace(PTRACE_GETREGS, tid, 0, ®s) != 0 ) return false; // Note: On linux kernels older than 4.8, the ptrace call to fetch // registers from xstate did not sanitize the state before // copying data to user-space. If only the YMM register class // was requested (and not fp or fpx), this could lead to IDA // having stale data on the lower half of the YMM registers. // The ptrace calls to fetch fp or fpx registers do sanitize // the state. This is the only reason we may also get the fp // registers when the YMM register class is requested, but // the fp and fpx registers were not requested. The order is // important in this case (first fp or fpx, then xstate). #ifdef __X86__ bool xstate_sanitized = clsmask_fpregs || clsmask_fpxregs; #else bool xstate_sanitized = clsmask_fpregs; #endif if ( clsmask_fpregs || (clsmask_ymm && !xstate_sanitized) ) if ( qptrace(PTRACE_GETFPREGS, tid, 0, &i387) != 0 ) return false; #ifdef __X86__ if ( clsmask_fpxregs ) if ( qptrace(PTRACE_GETFPXREGS, tid, 0, &x387) != 0 ) return false; #endif if ( clsmask_ymm ) if ( !qptrace_get_regset(&ymm_iov, NT_X86_XSTATE, tid) ) return false; return true; } //-------------------------------------------------------------------------- bool regctx_t::store(void) { if ( clsmask_regs ) if ( qptrace(PTRACE_SETREGS, tid, 0, ®s) != 0 ) return false; // The order of the following calls is VERY IMPORTANT so as // PTRACE_SETFPXREGS can spoil FPU registers. // The subsequent call to PTRACE_SETFPREGS will correct them. // Could it be better to get rid of PTRACE_SETFPREGS and use // PTRACE_SETFPXREGS for both FPU and XMM registers instead? if ( clsmask_ymm ) if ( !qptrace_set_regset(NT_X86_XSTATE, tid, ymm_iov) ) return false; #ifdef __X86__ if ( clsmask_fpxregs ) if ( qptrace(PTRACE_SETFPXREGS, tid, 0, &x387) != 0 ) return false; #endif if ( clsmask_fpregs ) if ( qptrace(PTRACE_SETFPREGS, tid, 0, &i387) != 0 ) return false; return true; } //-------------------------------------------------------------------------- static void ftag_read(const regctx_t *ctx, regval_t *value, void * /*user_data*/) { uint32_t ival = ctx->i387.TAGS_REG; #ifndef __X86__ // fix 'ftag': // --- // Byte 4 is used for an abridged version of the x87 FPU Tag // Word (FTW). The following items describe its usage: // — For each j, 0 <= j <= 7, FXSAVE saves a 0 into bit j of // byte 4 if x87 FPU data register STj has a empty tag; // otherwise, FXSAVE saves a 1 into bit j of byte 4. // (...) // --- // See also the opposite conversion when writing registers // (look for 'abridged'.) uint8_t abridged = ival; int top = (ctx->i387.swd >> 11) & 0x7; uint16_t ftag = 0; for ( int st_idx = 7; st_idx >= 0; --st_idx ) { uint16_t tag = TAG_EMPTY; if ( (abridged & (1 << st_idx)) != 0 ) { int actual_st = (st_idx + 8 - top) % 8; const uint8_t *p = ((const uint8_t *) ctx->i387.st_space) + actual_st * (sizeof(ctx->i387.st_space)/8); //-V706 Suspicious division bool integer = (p[7] & 0x80) != 0; uint32 exp = ((p[9] & 0x7f) << 8) | p[8]; //-V557 Array overrun is possible uint32 frac0 = ((p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]); uint32 frac1 = (((p[7] & 0x7f) << 24) | (p[6] << 16) | (p[5] << 8) | p[4]); if ( exp == 0x7fff ) tag = TAG_SPECIAL; else if ( exp == 0 ) tag = (frac0 == 0 && frac1 == 0 && !integer) ? TAG_ZERO : TAG_SPECIAL; else tag = integer ? TAG_VALID : TAG_SPECIAL; } ftag |= tag << (2 * st_idx); } ival = ftag; #endif value->ival = ival; } //-------------------------------------------------------------------------- static void ftag_write(regctx_t *ctx, const regval_t *value, void * /*user_data*/) { #ifndef __X86__ // => abridged // See also the opposite conversion when reading registers // (look for 'abridged'.) // // NOTE: This assumes that i387.swd _IS UP-TO-DATE_. // If it has to be overwritten later in the same batch of // updates, its new value won't be used here. uint16_t expanded = value->ival; uint8_t tags = 0; int top = (ctx->i387.swd >> 11) & 0x7; for ( int st_idx = 7; st_idx >= 0; --st_idx ) if ( ((expanded >> (2 * st_idx)) & 3) != TAG_EMPTY ) tags |= uint8_t(1 << ((st_idx + 8 - top) % 8)); ctx->i387.TAGS_REG = tags; #else ctx->i387.TAGS_REG = value->ival; #endif } //-------------------------------------------------------------------------- static void ymm_read(const regctx_t *ctx, regval_t *value, void *user_data) { size_t ymm_reg_idx = size_t(user_data); const uint128 *ptrl = (uint128 *) &ctx->xstate[XSAVE_XMM_OFFSET_BASE]; const uint128 *ptrh = (uint128 *) &ctx->xstate[XSAVE_YMMH_OFFSET_BASE]; uint8_t ymm[32]; *(uint128 *) &ymm[ 0] = ptrl[ymm_reg_idx]; *(uint128 *) &ymm[16] = ptrh[ymm_reg_idx]; value->set_bytes(ymm, sizeof(ymm)); } //-------------------------------------------------------------------------- static void ymm_write(regctx_t *ctx, const regval_t *value, void *user_data) { size_t ymm_reg_idx = size_t(user_data); const uint8_t *ymm = (const uint8_t *) value->get_data(); uint128 *ptrl = (uint128 *) &ctx->xstate[XSAVE_XMM_OFFSET_BASE]; uint128 *ptrh = (uint128 *) &ctx->xstate[XSAVE_YMMH_OFFSET_BASE]; ptrl[ymm_reg_idx] = *(uint128 *) &ymm[ 0]; ptrh[ymm_reg_idx] = *(uint128 *) &ymm[16]; ctx->xstate[XSAVE_XSTATE_BV] |= X86_XSTATE_SSE | X86_XSTATE_AVX; } //-------------------------------------------------------------------------- void linux_debmod_t::init_reg_ctx(void) { reg_ctx = new regctx_t(idaregs); // Populate register context size_t offset = 0; #ifdef __EA64__ bool is_64 = debapp_attrs.addrsize > 4; if ( is_64 ) { reg_ctx->add_ival(r_rax, offset_size(regs, INTEL_REG(ax))); reg_ctx->add_ival(r_rbx, offset_size(regs, INTEL_REG(bx))); reg_ctx->add_ival(r_rcx, offset_size(regs, INTEL_REG(cx))); reg_ctx->add_ival(r_rdx, offset_size(regs, INTEL_REG(dx))); reg_ctx->add_ival(r_rsi, offset_size(regs, INTEL_REG(si))); reg_ctx->add_ival(r_rdi, offset_size(regs, INTEL_REG(di))); reg_ctx->add_ival(r_rbp, offset_size(regs, INTEL_REG(bp))); sp_idx = reg_ctx->add_ival(r_rsp, offset_size(regs, INTEL_REG(sp))); pc_idx = reg_ctx->add_ival(r_rip, offset_size(regs, INTEL_REG(ip))); reg_ctx->add_ival(r_r8, offset_size(regs, r8)); reg_ctx->add_ival(r_r9, offset_size(regs, r9)); reg_ctx->add_ival(r_r10, offset_size(regs, r10)); reg_ctx->add_ival(r_r11, offset_size(regs, r11)); reg_ctx->add_ival(r_r12, offset_size(regs, r12)); reg_ctx->add_ival(r_r13, offset_size(regs, r13)); reg_ctx->add_ival(r_r14, offset_size(regs, r14)); reg_ctx->add_ival(r_r15, offset_size(regs, r15)); } else #endif { reg_ctx->add_ival(r_eax, offset_size(regs, INTEL_REG(ax))); reg_ctx->add_ival(r_ebx, offset_size(regs, INTEL_REG(bx))); reg_ctx->add_ival(r_ecx, offset_size(regs, INTEL_REG(cx))); reg_ctx->add_ival(r_edx, offset_size(regs, INTEL_REG(dx))); reg_ctx->add_ival(r_esi, offset_size(regs, INTEL_REG(si))); reg_ctx->add_ival(r_edi, offset_size(regs, INTEL_REG(di))); reg_ctx->add_ival(r_ebp, offset_size(regs, INTEL_REG(bp))); sp_idx = reg_ctx->add_ival(r_esp, offset_size(regs, INTEL_REG(sp))); pc_idx = reg_ctx->add_ival(r_eip, offset_size(regs, INTEL_REG(ip))); } sr_idx = reg_ctx->add_ival(arch_registers[R_EFLAGS], offset_size(regs, eflags)); cs_idx = reg_ctx->add_ival(arch_registers[R_CS], offset_size(regs, INTEL_SREG(cs))); ds_idx = reg_ctx->add_ival(arch_registers[R_DS], offset_size(regs, INTEL_SREG(ds))); es_idx = reg_ctx->add_ival(arch_registers[R_ES], offset_size(regs, INTEL_SREG(es))); fs_idx = reg_ctx->add_ival(arch_registers[R_FS], offset_size(regs, INTEL_SREG(fs))); gs_idx = reg_ctx->add_ival(arch_registers[R_GS], offset_size(regs, INTEL_SREG(gs))); ss_idx = reg_ctx->add_ival(arch_registers[R_SS], offset_size(regs, INTEL_SREG(ss))); offset = qoffsetof2(i387, st_space); for ( size_t i = R_ST0; i <= R_ST7; i++, offset += sizeof(regctx_t::i387.st_space)/8 ) //-V706 Suspicious division reg_ctx->add_fval(arch_registers[i], offset, 10); reg_ctx->add_ival(arch_registers[R_CTRL], offset_size(i387, cwd)); reg_ctx->add_ival(arch_registers[R_STAT], offset_size(i387, swd)); reg_ctx->add_func(arch_registers[R_TAGS], ftag_read, ftag_write); offset = qoffsetof2(i387, st_space); for ( size_t i = R_MMX0; i <= R_MMX7; i++, offset += sizeof(regctx_t::i387.st_space)/8 ) //-V706 Suspicious division reg_ctx->add_data(arch_registers[i], offset, 8); offset = qoffsetof2(XMM_STRUCT, xmm_space); for ( size_t i = R_XMM0; i <= R_LAST_XMM; i++, offset += 16 ) { #ifdef __EA64__ if ( !is_64 && i >= R_XMM8 ) break; #endif reg_ctx->add_data(arch_registers[i], offset, 16); } reg_ctx->add_ival(arch_registers[R_MXCSR], offset_size(XMM_STRUCT, mxcsr)); for ( size_t i = R_YMM0; i <= R_LAST_YMM; i++ ) { #ifdef __EA64__ if ( !is_64 && i >= R_YMM8 ) break; #endif reg_ctx->add_func(arch_registers[i], ymm_read, ymm_write, (void *) (i - R_YMM0)); } } #endif // !__ARM__ //-------------------------------------------------------------------------- void linux_debmod_t::term_reg_ctx(void) { if ( reg_ctx != nullptr ) { delete reg_ctx; idaregs.clear(); } reg_ctx = nullptr; } //-------------------------------------------------------------------------- drc_t idaapi linux_debmod_t::dbg_read_registers( thid_t tid, int clsmask, regval_t *values, qstring * /*errbuf*/) { if ( values == nullptr ) return DRC_FAILED; reg_ctx->setup(tid, clsmask); if ( !reg_ctx->load() ) return DRC_FAILED; reg_ctx->read_all(values); return DRC_OK; } //-------------------------------------------------------------------------- drc_t idaapi linux_debmod_t::dbg_write_register( thid_t tid, int reg_idx, const regval_t *value, qstring * /*errbuf*/) { if ( value == nullptr ) return DRC_FAILED; reg_ctx->setup(tid); reg_ctx->setup_reg(reg_idx); if ( !reg_ctx->load() ) return DRC_FAILED; if ( reg_idx == pc_idx ) ldeb("NEW EIP: %08" FMT_64 "X\n", value->ival); if ( !reg_ctx->patch(reg_idx, value) ) return DRC_FAILED; if ( !reg_ctx->store() ) return DRC_FAILED; return DRC_OK; } //-------------------------------------------------------------------------- bool idaapi linux_debmod_t::write_registers( thid_t tid, int start, int count, const regval_t *values) { if ( values == nullptr ) return false; reg_ctx->setup(tid); for ( size_t i = 0; i < count; i++ ) reg_ctx->setup_reg(start + i); if ( !reg_ctx->load() ) return false; for ( size_t i = 0; i < count; i++, values++ ) if ( !reg_ctx->patch(start + i, values) ) return false; if ( !reg_ctx->store() ) return false; return true; } //-------------------------------------------------------------------------- // find DT_SONAME of a elf image directly from the memory bool linux_debmod_t::get_soname(const char *fname, qstring *soname) const { struct dll_soname_finder_t : public symbol_visitor_t { qstring *soname; dll_soname_finder_t(qstring *res) : symbol_visitor_t(VISIT_DYNINFO), soname(res) {} virtual int visit_dyninfo(uint64 tag, const char *name, uint64 /*value*/) override { if ( tag == DT_SONAME ) { *soname = name; return 1; } return 0; } }; dll_soname_finder_t dsf(soname); return load_elf_symbols(fname, dsf) == 1; } //-------------------------------------------------------------------------- asize_t linux_debmod_t::calc_module_size(const meminfo_vec_t &miv, const memory_info_t *mi) const { QASSERT(30067, miv.begin() <= mi && mi < miv.end()); ea_t start = mi->start_ea; ea_t end = mi->end_ea; if ( end == 0 ) return 0; // unknown size const qstring &name = mi->name; while ( ++mi != miv.end() ) { if ( name != mi->name ) break; end = mi->end_ea; } QASSERT(30068, end > start); return end - start; } //-------------------------------------------------------------------------- // may add/del threads! void linux_debmod_t::handle_dll_movements(const meminfo_vec_t &_miv) { ldeb("handle_dll_movements\n"); // first, merge memory ranges by module meminfo_vec_t miv; for ( size_t i = 0, n = _miv.size(); i < n; ++i ) { const memory_info_t &src = _miv[i]; // See if we already registered a module with that name. memory_info_t *target = find_dll(miv, src.name.c_str()); if ( target != NULL ) { // Found one. Let's make sure it contains our addresses. target->extend(src.start_ea); target->extend(src.end_ea); } else { miv.push_back(src); } } // unload missing dlls images_t::iterator p; for ( p=dlls.begin(); p != dlls.end(); ) { image_info_t &ii = p->second; const char *fname = ii.fname.c_str(); if ( find_dll(miv, fname) == NULL ) { if ( !del_pending_event(LIB_LOADED, fname) ) { debug_event_t ev; ev.set_info(LIB_UNLOADED) = fname; ev.pid = process_handle; ev.tid = process_handle; ev.ea = BADADDR; ev.handled = true; enqueue_event(ev, IN_FRONT); } p = dlls.erase(p); } else { ++p; } } // load new dlls int n = miv.size(); for ( int i=0; i < n; i++ ) { // ignore unnamed dlls if ( miv[i].name.empty() ) continue; // ignore the input file if ( !is_dll && miv[i].name == input_file_path ) continue; // ignore if dll already exists ea_t base = miv[i].start_ea; p = dlls.find(base); if ( p != dlls.end() ) continue; // ignore memory chunks which do not correspond to an ELF header char magic[4]; if ( _read_memory(-1, base, magic, 4, false) != 4 ) continue; if ( memcmp(magic, "\x7F\x45\x4C\x46", 4) != 0 ) continue; qstring soname; const char *modname = miv[i].name.c_str(); get_soname(modname, &soname); asize_t size = calc_module_size(miv, &miv[i]); add_dll(base, size, modname, soname.c_str()); } if ( !dlls_to_import.empty() ) tdb_new(); } //-------------------------------------------------------------------------- // this function has a side effect: it sets debapp_attrs.addrsize to 8 // if founds a 64-bit address in the mapfile bool linux_debmod_t::read_mapping(mapfp_entry_t *me) { qstring line; if ( qgetline(&line, mapfp) <= 0 ) return false; me->ea1 = BADADDR; me->bitness = 0; int len = 0; me->perm[7] = '\0'; me->device[7] = '\0'; CASSERT(sizeof(me->perm) == 8); CASSERT(sizeof(me->device) == 8); int code = qsscanf(line.begin(), "%a-%a %7s %a %7s %" FMT_64 "x%n", &me->ea1, &me->ea2, me->perm, &me->offset, me->device, &me->inode, &len); if ( code == 6 ) { me->bitness = 1; size_t pos = line.find('-'); if ( pos != qstring::npos && pos > 8 ) { me->bitness = 2; debapp_attrs.addrsize = 8; } char *ptr = line.begin() + len; ptr = skip_spaces(ptr); // remove trailing spaces and eventual (deleted) suffix static const char delsuff[] = " (deleted)"; const int suflen = sizeof(delsuff) - 1; char *end = tail(ptr); while ( end > ptr && qisspace(end[-1]) ) *--end = '\0'; if ( end-ptr > suflen && strncmp(end-suflen, delsuff, suflen) == 0 ) end[-suflen] = '\0'; me->fname = ptr; } return me->ea1 != BADADDR; } //-------------------------------------------------------------------------- drc_t linux_debmod_t::get_memory_info(meminfo_vec_t &miv, bool suspend) { ldeb("get_memory_info(suspend=%d)\n", suspend); if ( exited || mapfp == NULL ) return DRC_NOPROC; if ( suspend ) suspend_all_threads(); rewind(mapfp); mapfp_entry_t me; qstrvec_t possible_interp; int bitness = 1; while ( read_mapping(&me) ) { // skip empty ranges if ( me.empty() ) continue; if ( interp.empty() && !me.fname.empty() && !possible_interp.has(me.fname) ) { //check for [.../]ld-XXX.so" size_t pos = me.fname.find("ld-"); if ( pos != qstring::npos && (pos == 0 || me.fname[pos-1] == '/') ) possible_interp.push_back(me.fname); } // for some reason linux lists some ranges twice // ignore them int i; for ( i=0; i < miv.size(); i++ ) if ( miv[i].start_ea == me.ea1 ) break; if ( i != miv.size() ) continue; memory_info_t &mi = miv.push_back(); mi.start_ea = me.ea1; mi.end_ea = me.ea2; mi.name.swap(me.fname); #ifdef __ANDROID__ // android reports simple library names without path. try to find it. make_android_abspath(&mi.name); #endif mi.bitness = me.bitness; //msg("%s: %a..%a. Bitness: %d\n", mi.name.c_str(), mi.start_ea, mi.end_ea, mi.bitness); if ( bitness < mi.bitness ) bitness = mi.bitness; if ( strchr(me.perm, 'r') != NULL ) mi.perm |= SEGPERM_READ; if ( strchr(me.perm, 'w') != NULL ) mi.perm |= SEGPERM_WRITE; if ( strchr(me.perm, 'x') != NULL ) mi.perm |= SEGPERM_EXEC; } if ( !possible_interp.empty() ) { bool ok = false; for ( size_t i = 0; i < possible_interp.size(); ++i ) { interp = possible_interp[i]; debdeb("trying potential interpreter %s\n", interp.c_str()); if ( add_shlib_bpt(miv, true) ) { ok = true; dmsg("Found a valid interpeter in %s, will report shared library events!\n", interp.c_str()); handle_dll_movements(miv); } } if ( !ok ) interp.qclear(); } // During the parsing of each memory segment we had just guessed the bitness. // So fix now bitness of all memory segments for ( int i = 0; i < miv.size(); i++ ) miv[i].bitness = bitness; if ( suspend ) resume_all_threads(); return DRC_OK; } //-------------------------------------------------------------------------- drc_t idaapi linux_debmod_t::dbg_get_memory_info(meminfo_vec_t &ranges, qstring * /*errbuf*/) { drc_t drc = get_memory_info(ranges, false); if ( drc == DRC_OK ) { if ( same_as_oldmemcfg(ranges) ) drc = DRC_NOCHG; else save_oldmemcfg(ranges); } return drc; } //-------------------------------------------------------------------------- linux_debmod_t::~linux_debmod_t() { term_reg_ctx(); mapfp = NULL; ta = NULL; } //-------------------------------------------------------------------------- void idaapi linux_debmod_t::dbg_set_debugging(bool _debug_debugger) { debug_debugger = _debug_debugger; } //-------------------------------------------------------------------------- drc_t idaapi linux_debmod_t::dbg_init(uint32_t *flags2, qstring * /*errbuf*/) { dbg_term(); // initialize various variables if ( flags2 != nullptr ) *flags2 = DBG_HAS_GET_PROCESSES | DBG_HAS_DETACH_PROCESS; return DRC_OK; } //-------------------------------------------------------------------------- void idaapi linux_debmod_t::dbg_term(void) { cleanup(); cleanup_hwbpts(); } //-------------------------------------------------------------------------- //lint -save -esym(818,pea) could be pointer to const bool idaapi linux_debmod_t::thread_get_fs_base(thid_t tid, int reg_idx, ea_t *pea) const { #if !defined(__ARM__) && !defined(__X86__) /* The following definitions come from prctl.h, but may be absent for certain configurations. */ #ifndef ARCH_GET_FS #define ARCH_SET_GS 0x1001 #define ARCH_SET_FS 0x1002 #define ARCH_GET_FS 0x1003 #define ARCH_GET_GS 0x1004 #endif if ( reg_idx == fs_idx ) { if ( qptrace(PTRACE_ARCH_PRCTL, tid, pea, (void *) ARCH_GET_FS) == 0 ) return true; } else if ( reg_idx == gs_idx ) { if ( qptrace(PTRACE_ARCH_PRCTL, tid, pea, (void *) ARCH_GET_GS) == 0 ) return true; } else if ( reg_idx == cs_idx || reg_idx == ds_idx || reg_idx == es_idx || reg_idx == ss_idx ) { *pea = 0; return true; } return false; #else qnotused(tid); qnotused(reg_idx); qnotused(pea); return false; #endif } //lint -restore //-------------------------------------------------------------------------- int idaapi linux_debmod_t::handle_ioctl(int fn, const void *in, size_t, void **, ssize_t *) { if ( fn == 0 ) // chmod +x { // this call is not used anymore char *fname = (char *)in; qstatbuf st; qstat(fname, &st); int mode = st.qst_mode | S_IXUSR|S_IXGRP|S_IXOTH; chmod(fname, mode); } return 0; } //-------------------------------------------------------------------------- // recovering from a broken session consists in the following steps: // // 1 - Cleanup dlls previously recorded. // 2 - Do like if we were attaching (calling handle_process_start(attaching=>AMT_ATTACH_BROKEN)) // 3 - Generate library events. // 4 - Restore RIP/EIP if we stopped in a breakpoint. // bool idaapi linux_debmod_t::dbg_continue_broken_connection(pid_t _pid) { debmod_t::dbg_continue_broken_connection(_pid); bool ret = in_event = false; // cleanup previously recorded information dlls.clear(); // restore broken breakpoints and continue like a normal attach if ( restore_broken_breakpoints() && handle_process_start(_pid, AMT_ATTACH_BROKEN) ) { // generate all library events gen_library_events(_pid); // fix instruction pointer in case we're at a breakpoint if ( !fix_instruction_pointer() ) dmsg("Debugger failed to correctly restore the instruction pointer after recovering from a broken connection.\n"); // and finally pause the process ret = true; } return ret; } //-------------------------------------------------------------------------- // if the process was stopped at a breakpoint and then the connections goes // down, when re-attaching the process we may be at EIP+1 (Intel procs) so // we need to change EIP to EIP-1 bool linux_debmod_t::fix_instruction_pointer(void) const { bool ret = true; #if !defined(__ARM__) if ( last_event.eid() == BREAKPOINT ) { ret = false; struct user_regs_struct regs; if ( qptrace(PTRACE_GETREGS, last_event.tid, 0, ®s) == 0 ) { if ( last_event.ea == regs.PCREG-1 ) regs.PCREG--; ret = qptrace(PTRACE_SETREGS, last_event.tid, 0, ®s) == 0; } } #endif return ret; } //-------------------------------------------------------------------------- bool init_subsystem() { tdb_init(); qatexit(kill_all_processes); linux_debmod_t::reuse_broken_connections = true; return true; } //-------------------------------------------------------------------------- bool term_subsystem() { del_qatexit(kill_all_processes); tdb_term(); return true; } //-------------------------------------------------------------------------- debmod_t *create_debug_session(void *params) { #ifdef TESTABLE_BUILD // new_client_handler(), and thus create_debug_session() is called from // the main thread, so we don't risk a race for setting up the resolver. if ( per_pid_elf_dbgdir_resolver == NULL ) per_pid_elf_dbgdir_resolver = (per_pid_elf_dbgdir_resolver_t *) params; //lint !e611 cast between pointer to function type '' and pointer to object type 'void *' #else qnotused(params); #endif return new linux_debmod_t(); }