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