Repository URL to install this package:
Version:
9.0~241217-1.fc42 ▾
|
idapro-debugsource
/
usr
/
src
/
debug
/
idapro-9.0~241217-1.fc42.x86_64
/
dbg
/
linux
/
linux_debmod.cpp
|
---|
/*
* 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 <sys/syscall.h>
#include <pthread.h>
#include <dirent.h>
#include <pro.h>
#include <prodir.h>
#include <fpro.h>
#include <err.h>
#include <ida.hpp>
#include <idp.hpp>
#include <idd.hpp>
#include <name.hpp>
#include <bytes.hpp>
#include <loader.hpp>
#include <diskio.hpp>
#include <network.hpp>
#include "symelf.hpp"
#include "linux_debmod.h"
#include "linux_rpc.h"
#ifdef __ANDROID__
# include <elf.h>
# include <sys/procfs.h>
# include "android.hpp"
# include "android.cpp"
#else
# include <link.h>
#endif
#if defined(__ARM__) && defined(__EA64__)
# include <asm/ptrace.h>
#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)
{
prochandle.pid = NO_PROCESS;
set_platform("linux");
}
#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 == nullptr )
{
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 nullptr;
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<<i) ) // Hardware breakpoint 'i'
{
if ( hwbpt_ea[i] == get_dr(event->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 != nullptr )
*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 == nullptr )
{
// 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 == nullptr )
{
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, nullptr, &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 != nullptr )
{
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 != nullptr && 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 == nullptr || !ei->handle() )
code = event->exc().code;
}
}
}
}
thif = get_thread(tid);
if ( thif == nullptr )
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 != nullptr )
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 == nullptr //-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<thread_info_t *> queue_t;
queue_t queue;
qvector<thid_t> 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 == nullptr )
return DRC_FAILED;
int tid = event->tid;
thread_info_t *t = get_thread(tid);
if ( t == nullptr && 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 != nullptr )
{
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 ( !removed_bpts.empty() && npending_signals == 0 && handling_lowcnds.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") != nullptr;
// 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 != nullptr )
{
const char *supp = per_pid_elf_dbgdir_resolver(pid);
if ( supp != nullptr )
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 == nullptr, 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 != nullptr && 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 == nullptr )
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 != nullptr )
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(nullptr);
process_handle = INVALID_HANDLE_VALUE;
thread_handle = INVALID_HANDLE_VALUE;
threads_collected = false;
is_dll = false;
requested_to_suspend = false;
in_event = false;
threads.clear();
dlls.clear();
dlls_to_import.clear();
events.clear();
if ( mapfp != nullptr )
{
qfclose(mapfp);
mapfp = nullptr;
}
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 first mapping of shared lib in the memory information array
static const memory_info_t *find_first_mapping(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 nullptr;
}
//--------------------------------------------------------------------------
static memory_info_t *find_first_mapping(meminfo_vec_t &miv, const char *name) //lint !e1764 could be reference to const
{
return CONST_CAST(memory_info_t *)(find_first_mapping(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_first_mapping(miv, interp.c_str());
if ( mi == nullptr )
{
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",
nullptr
};
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<threads_t::iterator, bool> 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;
threads_collected = false;
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 == nullptr )
{
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
set_addr_size(4);
if ( get_memory_info(miv, false) <= 0 )
INTERR(30065);
init_dynamic_regs();
const memory_info_t *mi = find_first_mapping(miv, mi_ps.name.c_str());
if ( mi != nullptr )
{
mi_ps.base = mi->start_ea;
mi_ps.size = calc_module_size(miv, mi);
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", nullptr) )
{
// 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,
launch_env_t *envs,
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, envs, startdir, flags,
input_path, input_file_crc32, &child_pid,
errbuf);
if ( drc > 0
&& child_pid != nullptr
&& !handle_process_start(size_t(child_pid), AMT_NO_ATTACH) )
{
dbg_exit_process(nullptr);
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, nullptr, nullptr) == 0
&& handle_process_start(_pid, AMT_ATTACH_NORMAL) )
{
gen_library_events(_pid); // detect all loaded libraries
return DRC_OK;
}
qptrace(PTRACE_DETACH, _pid, nullptr, nullptr);
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, nullptr, nullptr) == 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, nullptr, nullptr) == 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;
threads_collected = false;
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;
threads_collected = false;
}
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(nullptr, 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 != nullptr && 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), nullptr) == 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, nullptr)
&& dbg_write_memory(ea, bptcode, len, nullptr) == 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), nullptr) <= 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 != nullptr )
{
if ( dbg_write_memory(ea, orig_bytes, len, nullptr) == 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 == nullptr )
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 == nullptr )
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 == nullptr )
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 = get_addr_size();
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;
#ifdef __HAVE_ARM_NEON__
// struct user_fpsimd_struct
// {
// __uint128_t vregs[32];
// uint32_t fpsr;
// uint32_t fpcr;
// };
// typedef struct user_fpsimd_struct fpregset_t;
// typedef fpregset_t elf_fpregset_t;
elf_fpregset_t neon_regs;
#endif
// clsmask helpers
bool clsmask_regs = false;
#ifdef __HAVE_ARM_NEON__
bool clsmask_neon = false;
#endif
regctx_t(dynamic_register_set_t &_idaregs);
bool init();
bool load();
bool store();
};
//--------------------------------------------------------------------------
regctx_t::regctx_t(dynamic_register_set_t &_idaregs)
: regctx_base_t(_idaregs)
{
memset(®s, 0, sizeof(regs));
#ifdef __HAVE_ARM_NEON__
memset(&neon_regs, 0, sizeof(neon_regs));
#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_NEON__
clsmask_neon = (clsmask & ARM_RC_NEON) != 0;
#endif
return true;
}
//--------------------------------------------------------------------------
bool regctx_t::load(void)
{
if ( !init() )
return false;
if ( clsmask_regs )
if ( !qptrace_get_prstatus(®s, tid) )
return false;
#ifdef __HAVE_ARM_NEON__
if ( clsmask_neon )
{
struct iovec iovec;
iovec.iov_base = &neon_regs;
iovec.iov_len = sizeof(neon_regs);
if ( qptrace(PTRACE_GETREGSET, tid, (void *)NT_FPREGSET, &iovec) != 0 )
return false; // Unable to fetch FP/SIMD registers
}
#endif
return true;
}
//--------------------------------------------------------------------------
bool regctx_t::store(void)
{
if ( clsmask_regs )
if ( !qptrace_set_regset(NT_PRSTATUS, tid, ®s, sizeof(regs)) )
return false;
#ifdef __HAVE_ARM_NEON__
if ( clsmask_neon )
{
struct iovec iovec;
iovec.iov_base = &neon_regs;
iovec.iov_len = sizeof(neon_regs);
if ( qptrace(PTRACE_SETREGSET, tid, (void *)NT_FPREGSET, &iovec) != 0 )
return false; // Unable to store FP/SIMD registers
}
#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, 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
#ifdef __HAVE_ARM_NEON__
size_t offset = qoffsetof2(neon_regs, vregs);
for ( size_t i = R_V0; i <= R_V31; i++, offset += sizeof(__uint128_t) )
reg_ctx->add_data(arch_registers[i], offset, sizeof(__uint128_t));
reg_ctx->add_ival(arch_registers[R_FPSR], offset_size(neon_regs, fpsr));
reg_ctx->add_ival(arch_registers[R_FPCR], offset_size(neon_regs, fpcr));
#endif
}
# 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);
virtual bool init() override;
virtual bool load() override;
virtual bool store() override;
};
//--------------------------------------------------------------------------
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()
{
reg_ctx = new regctx_t(idaregs);
// Populate register context
reg_ctx->add_ival(arch_registers[ARM_R32_R0], offset_size(regs, uregs[0]));
reg_ctx->add_ival(arch_registers[ARM_R32_R1], offset_size(regs, uregs[1]));
reg_ctx->add_ival(arch_registers[ARM_R32_R2], offset_size(regs, uregs[2]));
reg_ctx->add_ival(arch_registers[ARM_R32_R3], offset_size(regs, uregs[3]));
reg_ctx->add_ival(arch_registers[ARM_R32_R4], offset_size(regs, uregs[4]));
reg_ctx->add_ival(arch_registers[ARM_R32_R5], offset_size(regs, uregs[5]));
reg_ctx->add_ival(arch_registers[ARM_R32_R6], offset_size(regs, uregs[6]));
reg_ctx->add_ival(arch_registers[ARM_R32_R7], offset_size(regs, uregs[7]));
reg_ctx->add_ival(arch_registers[ARM_R32_R8], offset_size(regs, uregs[8]));
reg_ctx->add_ival(arch_registers[ARM_R32_R9], offset_size(regs, uregs[9]));
reg_ctx->add_ival(arch_registers[ARM_R32_R10], offset_size(regs, uregs[10]));
reg_ctx->add_ival(arch_registers[ARM_R32_R11], offset_size(regs, uregs[11]));
reg_ctx->add_ival(arch_registers[ARM_R32_R12], offset_size(regs, uregs[12]));
sp_idx = reg_ctx->add_ival(arch_registers[ARM_R32_SP], offset_size(regs, uregs[13]));
lr_idx = reg_ctx->add_ival(arch_registers[ARM_R32_LR], offset_size(regs, uregs[14]));
pc_idx = reg_ctx->add_ival(arch_registers[ARM_R32_PC], offset_size(regs, uregs[15]));
sr_idx = reg_ctx->add_ival(arch_registers[ARM_R32_PSR], offset_size(regs, uregs[16]));
#ifdef __HAVE_ARM_VFP__
size_t offset = qoffsetof2(vfp_regs, fpregs);
for ( size_t i = ARM_R32_D0; i <= ARM_R32_D31; i++, offset += sizeof(int64) )
reg_ctx->add_data(arch_registers[i], offset, sizeof(int64));
reg_ctx->add_ival(arch_registers[ARM_R32_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);
virtual bool init() override;
virtual bool load() override;
virtual bool store() override;
};
//--------------------------------------------------------------------------
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()
{
reg_ctx = new regctx_t(idaregs);
// Populate register context
// order of registers corresponds to register_x86_t / register_x86_x86_t
size_t offset = 0;
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);
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)));
#ifdef __EA64__
bool is_64 = is_64bit_app();
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));
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));
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);
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__
//--------------------------------------------------------------------------
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_first_mapping(miv, src.name.c_str());
if ( target != nullptr )
{
// 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_first_mapping(miv, fname) == nullptr )
{
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());
}
activate_multithreading();
}
//--------------------------------------------------------------------------
// 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;
set_addr_size(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 == nullptr )
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') != nullptr )
mi.perm |= SEGPERM_READ;
if ( strchr(me.perm, 'w') != nullptr )
mi.perm |= SEGPERM_WRITE;
if ( strchr(me.perm, 'x') != nullptr )
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;
}
#ifdef HAVE_UPDATE_CALL_STACK
// define for libunwind include: remote is for unwinding another process
#define UNW_REMOTE_ONLY
#include <libunwind-ptrace.h>
// Prototypes of functions we use from libunwind.so.8:
typedef int unw_init_remote_t(unw_cursor_t *, unw_addr_space_t, void *);
typedef int unw_step_t(unw_cursor_t *);
typedef int unw_get_reg_t(unw_cursor_t *, int, unw_word_t *);
typedef unw_addr_space_t unw_create_addr_space_t(unw_accessors_t *, int);
typedef void unw_destroy_addr_space_t(unw_addr_space_t as);
// Prototypes of functions we use from libunwind-ptrace.so.0:
typedef void *_UPT_create_t(thid_t);
typedef void *_UPT_destroy_t(void *);
//-------------------------------------------------------------------------
//-V:libunwind_access_t:730 not all members of a class are initialized inside the constructor
struct libunwind_access_t
{
void *libx86_64; // lib unwind ptr
void *libptrace; // liwbunwind-ptrace ptr
void *context;
// Pointers to imported functions libunwind-x86_64.so
unw_init_remote_t *p_unw_init_remote;
unw_step_t *p_unw_step;
unw_get_reg_t *p_unw_get_reg;
unw_create_addr_space_t *p_unw_create_addr_space;
unw_destroy_addr_space_t *p_unw_destroy_addr_space;
// Pointers to imported functions:
_UPT_create_t *p_ptrace_create;
_UPT_destroy_t *p_ptrace_destroy;
unw_accessors_t *p_ptrace_accessor;
// address space for libunwind
//lint -e1401 member 'as' not initialized by constructor
unw_addr_space_t as;
struct symbol_resolve_info_t
{
const char *name;
void **ptr;
};
symbol_resolve_info_t unwsyms[5] =
{
{ "_Ux86_64_init_remote", (void**)&p_unw_init_remote },
{ "_Ux86_64_step", (void**)&p_unw_step },
{ "_Ux86_64_get_reg", (void**)&p_unw_get_reg },
{ "_Ux86_64_create_addr_space", (void**)&p_unw_create_addr_space },
{ "_Ux86_64_destroy_addr_space", (void**)&p_unw_destroy_addr_space }
};
symbol_resolve_info_t ptracesyms[3] =
{
{ "_UPT_create", (void**)&p_ptrace_create },
{ "_UPT_destroy", (void**)&p_ptrace_destroy },
{ "_UPT_accessors", (void**)&p_ptrace_accessor }
};
enum load_libunwind_status_t
{
UNW_LOADED = 1, // libunwind is loaded and address space created
UNW_UKNOWN = 0, // libuniwnd is not yet initiliazes
UNW_NOT_ALLOWED = -1, // libunwind is not allowed by env variable
};
load_libunwind_status_t unw_status;
libunwind_access_t() : libx86_64(nullptr),
libptrace(nullptr),
context(nullptr),
p_unw_init_remote(nullptr),
p_unw_step(nullptr),
p_unw_get_reg(nullptr),
p_unw_create_addr_space(nullptr),
p_unw_destroy_addr_space(nullptr),
p_ptrace_create(nullptr),
p_ptrace_destroy(nullptr),
p_ptrace_accessor(nullptr),
unw_status(UNW_UKNOWN) {}
~libunwind_access_t();
bool load_library_pair(qstring *out, const libunwind_pair_name_t &pair, const char *path);
void init(qstring *out, const char *path, thid_t tid);
bool load_lib_so(
void** lib,
const symbol_resolve_info_t symbol_table[],
int sym_table_length,
const char *libname) const;
void close_lib(
void** lib,
const symbol_resolve_info_t symbol_table[],
int sym_table_length) const;
};
static libunwind_access_t *p_libaccess = nullptr;
//-------------------------------------------------------------------------
//lint -esym(1540,libunwind_access_t::*) not deallocated nor zeroed by destructor
libunwind_access_t::~libunwind_access_t()
{
if ( unw_status == UNW_LOADED )
{
if ( context != nullptr )
p_ptrace_destroy(context);
p_unw_destroy_addr_space(as);
close_lib(&libptrace, ptracesyms, qnumber(ptracesyms));
close_lib(&libx86_64, unwsyms, qnumber(unwsyms));
}
unw_status = UNW_UKNOWN;
}
//-------------------------------------------------------------------------
static void append_load_failure(qstring *out, const qstring &path)
{
out->append("libunwind: failed to load ");
out->append(path);
out->append(".\n");
}
//-------------------------------------------------------------------------
bool libunwind_access_t::load_library_pair(qstring *out, const libunwind_pair_name_t &pair, const char *path)
{
qstring buf;
bool isabspath = qisabspath(path);
char dirname[QMAXPATH];
if ( isabspath && qdirname(dirname, sizeof(dirname), path) )
{
buf = dirname;
buf.append("/");
}
buf.append(pair.libx86_64_name);
if ( !load_lib_so(&libx86_64, unwsyms, qnumber(unwsyms), buf.c_str()) )
{
append_load_failure(out, buf);
return false;
}
buf.clear();
if ( isabspath )
{
buf = dirname;
buf.append("/");
}
buf.append(pair.libptrace_name);
if ( !load_lib_so(&libptrace, ptracesyms, qnumber(ptracesyms), buf.c_str()) )
{
append_load_failure(out, buf);
close_lib(&libx86_64, unwsyms, qnumber(unwsyms));
return false;
}
if ( dlinfo(libx86_64, RTLD_DI_ORIGIN, dirname) == 0 )
{
out->append("libunwind: sucessfully loaded ");
out->append(dirname);
out->append("/");
out->append(pair.libx86_64_name);
out->append(" and ");
out->append(dirname);
out->append("/");
out->append(pair.libptrace_name);
out->append(".");
}
return true;
}
//-------------------------------------------------------------------------
void libunwind_access_t::init(qstring *out, const char *path, thid_t tid)
{
out->clear();
if ( unw_status != UNW_UKNOWN )
return;
if ( path[0] == '\0' )
{
out->append("libunwind: no valid path provided.");
unw_status = UNW_NOT_ALLOWED;
return;
}
for ( int i = 0; i < qnumber(libunwind_pair_name); i++ )
{
if ( streq(libunwind_pair_name[i].libx86_64_name, qbasename(path)) )
{
if ( !load_library_pair(out, libunwind_pair_name[i], path) )
{
unw_status = UNW_NOT_ALLOWED;
return;
}
break;
}
}
as = p_unw_create_addr_space(p_ptrace_accessor, 0);
context = p_ptrace_create(tid);
unw_status = UNW_LOADED;
}
//-------------------------------------------------------------------------
void libunwind_access_t::close_lib(
void** lib,
const symbol_resolve_info_t symbol_table[],
int sym_table_length) const
{
for ( int i=0; i < sym_table_length; i++ )
*symbol_table[i].ptr = nullptr;
if ( *lib != nullptr )
dlclose(*lib);
*lib = nullptr;
}
//-------------------------------------------------------------------------
bool libunwind_access_t::load_lib_so(
void** lib,
const symbol_resolve_info_t symbol_table[],
int sym_table_length,
const char *libname) const
{
if ( *lib == nullptr )
{
*lib = dlopen(libname, RTLD_NOW|RTLD_GLOBAL);
if ( *lib == nullptr )
{
ldeb("dlopen(%s) failed: %s\n", libname, dlerror());
return false;
}
for ( int i=0; i < sym_table_length; i++ )
{
*symbol_table[i].ptr = dlsym(*lib, symbol_table[i].name); //-V595 The 'symbol_table[i].ptr' pointer was utilized before it was verified against nullptr
if ( symbol_table[i].ptr == nullptr )
{
ldeb("dlsym(%s.%s) failed: %s\n", libname, symbol_table[i].name, dlerror());
close_lib(lib, symbol_table, sym_table_length);
*lib = nullptr;
return false;
}
}
}
return true;
}
//--------------------------------------------------------------------------
drc_t idaapi linux_debmod_t::dbg_update_call_stack(thid_t tid, call_stack_t * trace)
{
// it is possible to analyse 32 bits binaries with debugger 64 bits. We cannot
// use libunwind for this case as we only use the 64 bits version of libunwind.
if ( !is_64bit_app() )
return DRC_FAILED;
if ( libunwind_path.empty() )
return DRC_FAILED;
if ( p_libaccess == nullptr )
p_libaccess = new libunwind_access_t;
qstring out;
if ( p_libaccess->unw_status == libunwind_access_t::UNW_UKNOWN )
p_libaccess->init(&out, libunwind_path.c_str(), tid);
dmsg("%s\n", out.c_str());
if ( p_libaccess->unw_status == libunwind_access_t::UNW_NOT_ALLOWED )
{
delete p_libaccess;
p_libaccess = nullptr;
return DRC_FAILED;
}
unw_cursor_t cursor;
if ( p_libaccess->p_unw_init_remote(&cursor, p_libaccess->as, p_libaccess->context) != 0 )
{
dmsg("libunwind: failed to initialize remote context\n");
delete p_libaccess;
p_libaccess = nullptr;
return DRC_FAILED;
}
do
{
unw_word_t rip;
if ( p_libaccess->p_unw_get_reg(&cursor, UNW_REG_IP, &rip) )
{
dmsg("libunwind: failed to retrieve stack trace\n");
delete p_libaccess;
p_libaccess = nullptr;
return DRC_FAILED;
}
call_stack_info_t si;
si.callea = rip;
// TODO : determine how to have frame pointer.
si.fp = BADADDR;
si.funcea = BADADDR;
trace->push_back(si);
}
while ( p_libaccess->p_unw_step(&cursor) > 0 );
return DRC_OK;
}
#endif // HAVE_UPDATE_CALL_STACK
//--------------------------------------------------------------------------
linux_debmod_t::~linux_debmod_t()
{
mapfp = nullptr;
ta = nullptr;
}
//--------------------------------------------------------------------------
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)
{
#ifdef HAVE_UPDATE_CALL_STACK
if ( p_libaccess != nullptr )
delete p_libaccess;
p_libaccess = nullptr;
#endif
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 *buf,
size_t size,
void ** /*poutbuf*/,
ssize_t * /*poutsize*/)
{
#ifdef HAVE_UPDATE_CALL_STACK
if ( fn == LINUX_IOCTL_LIBUNWIND_PATH )
{
memory_deserializer_t mmdsr(buf, size);
char *path = mmdsr.unpack_ds();
libunwind_path.clear();
libunwind_path.append(path);
return 1;
}
#else
qnotused(fn);
qnotused(buf);
qnotused(size);
#endif // HAVE_UPDATE_CALL_STACK
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()
{
qatexit(kill_all_processes);
linux_debmod_t::reuse_broken_connections = true;
return true;
}
//--------------------------------------------------------------------------
bool term_subsystem()
{
del_qatexit(kill_all_processes);
term_multithreading();
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 == nullptr )
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();
}