mirror of
https://github.com/cheat-engine/cheat-engine
synced 2026-08-15 02:26:08 -04:00
4738 lines
115 KiB
C
Executable file
4738 lines
115 KiB
C
Executable file
/*
|
|
* api.c
|
|
*
|
|
* Created on: Jul 20, 2011
|
|
* Author: erich
|
|
*
|
|
* This unit will implement the api's CE uses
|
|
* This will be the main point of interest when porting to another system
|
|
*/
|
|
|
|
//#define _XOPEN_SOURCE 500
|
|
|
|
//todo for in the far future: Hook syscalls
|
|
|
|
|
|
#define TRACEPTRACE
|
|
|
|
#define _FILE_OFFSET_BITS 64
|
|
#ifndef _LARGEFILE64_SOURCE
|
|
#define _LARGEFILE64_SOURCE
|
|
#endif
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <pthread.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
|
|
#include <stdint.h>
|
|
#include <dirent.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <sys/un.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include <strings.h>
|
|
|
|
#include <sys/mman.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/syscall.h>
|
|
#include <signal.h>
|
|
|
|
#ifdef __ANDROID__
|
|
|
|
#ifndef SUN_LEN //missing in android (copy from linux sys/un.h)
|
|
# include <string.h> /* For prototype of `strlen'. */
|
|
|
|
/* Evaluate to actual length of the `sockaddr_un' structure. */
|
|
# define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \
|
|
+ strlen ((ptr)->sun_path))
|
|
#endif
|
|
#if defined (__arm__) || defined(__aarch64__)
|
|
#include <arm-linux-androideabi/asm/ptrace.h>
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef __x86_64__
|
|
//#include <asm/signal.h>
|
|
#endif
|
|
|
|
|
|
#include <sys/eventfd.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <semaphore.h>
|
|
#include <sys/queue.h>
|
|
#include <limits.h>
|
|
|
|
#include <sys/ptrace.h>
|
|
|
|
//#ifndef __x86_64__
|
|
#include <linux/elf.h>
|
|
//#include <linux/uio.h>
|
|
//#endif
|
|
|
|
|
|
#ifdef __arm__
|
|
#ifndef __ANDROID__
|
|
#include <linux/user.h>
|
|
#endif
|
|
#endif
|
|
|
|
|
|
#include <dlfcn.h>
|
|
|
|
//blatantly stolen from the kernel source
|
|
#define PTRACE_GETHBPREGS 29
|
|
#define PTRACE_SETHBPREGS 30
|
|
|
|
/* Breakpoint */
|
|
#define ARM_BREAKPOINT_EXECUTE 0
|
|
|
|
/* Watchpoints */
|
|
#define ARM_BREAKPOINT_LOAD 1
|
|
#define ARM_BREAKPOINT_STORE 2
|
|
|
|
/* Privilege Levels */
|
|
#define ARM_BREAKPOINT_PRIV 1
|
|
#define ARM_BREAKPOINT_USER 2
|
|
|
|
/* Lengths */
|
|
#define ARM_BREAKPOINT_LEN_1 0x1
|
|
#define ARM_BREAKPOINT_LEN_2 0x3
|
|
#define ARM_BREAKPOINT_LEN_4 0xf
|
|
#define ARM_BREAKPOINT_LEN_8 0xff
|
|
|
|
#if defined (__arm__) || defined(__aarch64__)
|
|
static inline unsigned int encode_ctrl_reg(int mismatch, int len, int type, int privilege, int enabled)
|
|
{
|
|
return (mismatch << 22) | (len << 5) | (type << 3) | (privilege << 1) | enabled;
|
|
}
|
|
#endif
|
|
|
|
#ifndef __ANDROID__
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
#include <sys/user.h>
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
#include "api.h"
|
|
#include "porthelp.h"
|
|
#include "ceserver.h"
|
|
#include "threads.h"
|
|
#include "symbols.h"
|
|
#include "context.h"
|
|
|
|
|
|
PROCESS_VM_WRITEV process_vm_writev=NULL;
|
|
PROCESS_VM_READV process_vm_readv=NULL;
|
|
|
|
//#include <vector>
|
|
sem_t sem_DebugThreadEvent;
|
|
|
|
pthread_mutex_t memorymutex;
|
|
pthread_mutex_t debugsocketmutex;
|
|
//pthread_mutex_t mut_RPM;
|
|
|
|
|
|
|
|
int VerboseLevel=0;
|
|
|
|
int MEMORY_SEARCH_OPTION = 2; //0=file, 1=ptrace, 2=use process_vm_readv, 3=use process_vm_readv2
|
|
int ATTACH_PID = 0;
|
|
int ATTACH_TO_ACCESS_MEMORY = 0;
|
|
int ATTACH_TO_WRITE_MEMORY = 1;
|
|
unsigned char SPECIFIED_ARCH = 9;
|
|
|
|
#ifdef SYS_process_vm_readv
|
|
//BEcause of comment: "Please implement this version of process_vm_readv as the original is detected" I doubt this would work better, but whatever, here it is
|
|
ssize_t process_vm_readv2(pid_t process_id, struct iovec *io_local, struct iovec *io_remote, int len, int flags) {
|
|
if (process_id < 0) return FALSE;
|
|
return syscall(SYS_process_vm_readv, process_id, io_local, len, io_remote, len, 0);
|
|
}
|
|
|
|
ssize_t process_vm_writev2(pid_t process_id, struct iovec *io_local, struct iovec *io_remote, int len, int flags) {
|
|
if (process_id < 0) return FALSE;
|
|
return syscall(SYS_process_vm_writev, process_id, io_local, len, io_remote, len, 0);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
//Implementation for shared library version ceserver.
|
|
int debug_log(const char * format , ...)
|
|
{
|
|
va_list list;
|
|
va_start(list,format);
|
|
int ret = vprintf(format,list);
|
|
va_end(list);
|
|
|
|
|
|
#ifdef __ANDROID__
|
|
va_start(list,format);
|
|
LOGD(format,list);
|
|
va_end(list);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef TRACEPTRACE
|
|
char *PTraceToString(int request)
|
|
{
|
|
switch (request)
|
|
{
|
|
case PTRACE_ATTACH: return "PTRACE_ATTACH";
|
|
case PTRACE_DETACH: return "PTRACE_DETACH";
|
|
case PTRACE_PEEKDATA: return "PTRACE_PEEKDATA";
|
|
case PTRACE_POKEDATA: return "PTRACE_POKEDATA";
|
|
case PTRACE_CONT: return "PTRACE_CONT";
|
|
case PTRACE_GETSIGINFO: return "PTRACE_GETSIGINFO";
|
|
case PTRACE_GETREGSET: return "PTRACE_GETREGSET";
|
|
case PTRACE_SINGLESTEP: return "PTRACE_SINGLESTEP";
|
|
case PTRACE_SETREGS: return "PTRACE_SETREGS";
|
|
case PTRACE_GETREGS: return "PTRACE_GETREGS";
|
|
case PTRACE_GETFPREGS: return "PTRACE_GETFPREGS";
|
|
#ifdef PT_GETFPXREGS
|
|
case PTRACE_GETFPXREGS: return "PTRACE_GETFPXREGS";
|
|
#endif
|
|
default:
|
|
return "";
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
//Implementation for consistency with Android Studio.
|
|
uintptr_t safe_ptrace(int request, pid_t pid, void * addr, void * data)
|
|
{
|
|
#ifdef TRACEPTRACE
|
|
// debug_log("ATTACH_TO_ACCESS_MEMORY=%d\n", ATTACH_TO_ACCESS_MEMORY);
|
|
if (threadname)
|
|
debug_log("%s: ptrace called (%s(%x), %d, %p, %p)\n",threadname, PTraceToString(request),request, pid, addr, data);
|
|
else
|
|
debug_log("ptrace called (%s(%x), %d, %p, %p)\n",PTraceToString(request),request, pid, addr, data);
|
|
|
|
|
|
#endif
|
|
uintptr_t result;
|
|
errno = 0;
|
|
result = ptrace(request, pid, addr, data);
|
|
if(errno != 0)
|
|
{
|
|
debug_log("ptrace error(%s (%d))!\n",strerror(errno), errno);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int ptrace_attach_andwait(int pid)
|
|
//call this for quick attach/detach purposes. returns <0 on error, else the attached tid (usually just pid)
|
|
{
|
|
if (safe_ptrace(PTRACE_ATTACH, pid,0,0)==0)
|
|
{
|
|
int status;
|
|
while (1)
|
|
{
|
|
pid=waitpid(-1, &status,0);
|
|
if (WIFSTOPPED(status))
|
|
{
|
|
if (WSTOPSIG(status)==SIGSTOP)
|
|
return pid; //proper stop
|
|
|
|
//not a sigstop
|
|
debug_log("ptrace_attach_andwait:Received stop with signal %d instead of %d\n", WSTOPSIG(status), SIGSTOP);
|
|
safe_ptrace(PTRACE_CONT, pid, (void*)0, (void*)(uint64_t)WSTOPSIG(status));
|
|
continue;
|
|
}
|
|
|
|
if (WIFCONTINUED(status))
|
|
{
|
|
debug_log("ptrace_attach_andwait:It already continued?\n");
|
|
continue;
|
|
}
|
|
|
|
if (WIFEXITED(status))
|
|
{
|
|
debug_log("ptrace_attach_andwait:Target terminated with code %d\n", WEXITSTATUS(status));
|
|
return -2; //target exit
|
|
}
|
|
|
|
if (WIFSIGNALED(status))
|
|
{
|
|
debug_log("trace_attach_andwait:Target received a ");
|
|
|
|
if (WTERMSIG(status))
|
|
debug_log("terminate signal");
|
|
|
|
if (WCOREDUMP(status))
|
|
debug_log("core Dump");
|
|
|
|
debug_log("\n");
|
|
return -3;
|
|
}
|
|
|
|
|
|
debug_log("ptrace_attach_andwait: Unexpected status: %x\n", status);
|
|
return -4;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
debug_log("ptrace_attach_andwait: ptrace attach failed\n");
|
|
return -1; //ptrace attach failed
|
|
}
|
|
|
|
}
|
|
|
|
int WakeDebuggerThread()
|
|
{
|
|
return sem_post(&sem_DebugThreadEvent);
|
|
}
|
|
|
|
void mychildhandler(int signal, struct siginfo *info, void *context)
|
|
{
|
|
//only call re-entrant functions
|
|
|
|
int orig_errno = errno;
|
|
WakeDebuggerThread();
|
|
errno = orig_errno;
|
|
}
|
|
|
|
int windowsProtectionToLinux(uint32_t windowsprotection)
|
|
{
|
|
int newprotection=0;
|
|
switch (windowsprotection)
|
|
{
|
|
case PAGE_EXECUTE_READWRITE: newprotection=PROT_WRITE | PROT_READ | PROT_EXEC; break;
|
|
case PAGE_EXECUTE_READ: newprotection=PROT_READ | PROT_EXEC; break;
|
|
case PAGE_EXECUTE: newprotection=PROT_EXEC; break;
|
|
case PAGE_READWRITE: newprotection=PROT_READ | PROT_WRITE; break;
|
|
case PAGE_READONLY: newprotection=PROT_READ; break;
|
|
default:
|
|
newprotection=0;
|
|
}
|
|
|
|
return newprotection;
|
|
}
|
|
|
|
uint32_t linuxProtectionToWindows(int prot)
|
|
{
|
|
int r=0, w=0, x=0;
|
|
|
|
r=prot & PROT_READ;
|
|
w=prot & PROT_WRITE;
|
|
x=prot & PROT_EXEC;
|
|
|
|
if (r && w && x)
|
|
return PAGE_EXECUTE_READWRITE;
|
|
|
|
if (r && x)
|
|
return PAGE_EXECUTE_READ;
|
|
|
|
if (x)
|
|
return PAGE_EXECUTE;
|
|
|
|
if (r && w)
|
|
return PAGE_READWRITE;
|
|
|
|
if (r)
|
|
return PAGE_READONLY;
|
|
|
|
return PAGE_NOACCESS;
|
|
|
|
}
|
|
|
|
int getArchitecture(HANDLE hProcess)
|
|
{
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
if (p->is64bit)
|
|
#if defined (__arm__) || defined(__aarch64__)
|
|
return 3;
|
|
else
|
|
return 2;
|
|
#else
|
|
return 1;
|
|
else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int GetDebugPort(HANDLE hProcess)
|
|
//return the debugserver fd
|
|
{
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
if (p->isDebugged)
|
|
{
|
|
return p->debuggerServer;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
int FindPausedThread(PProcessData p)
|
|
/*
|
|
* Scan the threadlist and return the threadid of the thread that is paused. Return 0 if no thread is paused
|
|
*/
|
|
{
|
|
int i;
|
|
for (i=0; i<p->threadlistpos; i++)
|
|
if (p->threadlist[i].isPaused)
|
|
return p->threadlist[i].tid;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int getBreakpointCapabilities(int tid, uint8_t *maxBreakpointCount, uint8_t *maxWatchpointCount, uint8_t *maxSharedBreakpoints)
|
|
//Only called when the thread is suspended
|
|
{
|
|
|
|
*maxBreakpointCount=0;
|
|
*maxWatchpointCount=0;
|
|
*maxSharedBreakpoints=0;
|
|
|
|
|
|
#ifdef __arm__
|
|
HBP_RESOURCE_INFO hwbpcap;
|
|
|
|
memset(&hwbpcap, 0, sizeof(HBP_RESOURCE_INFO));
|
|
if (safe_ptrace(PTRACE_GETHBPREGS, tid, 0, &hwbpcap)==0)
|
|
{
|
|
|
|
*maxBreakpointCount=hwbpcap.num_brps;
|
|
*maxWatchpointCount=hwbpcap.num_wrps;
|
|
*maxSharedBreakpoints=0;
|
|
|
|
return 1;
|
|
}
|
|
else
|
|
return 0;
|
|
#endif
|
|
|
|
#ifdef __aarch64__
|
|
struct iovec iov;
|
|
struct user_hwdebug_state hwd;
|
|
memset(&hwd, 0, sizeof(hwd));
|
|
|
|
iov.iov_base=&hwd;
|
|
iov.iov_len=sizeof(hwd);
|
|
|
|
if (safe_ptrace(PTRACE_GETREGSET, tid, (void*)NT_ARM_HW_WATCH, &iov)==0)
|
|
{
|
|
debug_log("NT_ARM_HW_WATCH: dbg_info=%x:\n", hwd.dbg_info);
|
|
*maxWatchpointCount=hwd.dbg_info & 0xf;
|
|
}
|
|
else
|
|
{
|
|
debug_log("NT_ARM_HW_WATCH: Failure getting watch breakpoint information\n");
|
|
*maxWatchpointCount=0;
|
|
}
|
|
|
|
iov.iov_base=&hwd;
|
|
iov.iov_len=sizeof(hwd);
|
|
if (safe_ptrace(PTRACE_GETREGSET, tid, (void*)NT_ARM_HW_BREAK, &iov)==0)
|
|
{
|
|
debug_log("NT_ARM_HW_BREAK: dbg_info=%x:\n", hwd.dbg_info);
|
|
*maxBreakpointCount=hwd.dbg_info & 0xf;
|
|
}
|
|
else
|
|
{
|
|
debug_log("NT_ARM_HW_BREAK: Failure getting breakpoint information\n");
|
|
*maxBreakpointCount=0;
|
|
}
|
|
|
|
return 1;
|
|
#endif
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
*maxBreakpointCount=0;
|
|
*maxWatchpointCount=0;
|
|
*maxSharedBreakpoints=4;
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
int StartDebug(HANDLE hProcess)
|
|
{
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
|
|
struct sigaction childactionhandler;
|
|
if (p->isDebugged)
|
|
{
|
|
debug_log("Trying to start debugging a process that is already debugged. (Close ceserver and try again if you had to force close CE earlier)\n");
|
|
}
|
|
|
|
//attach to each task
|
|
//first get a threadlist
|
|
InitializeProcessThreadlist(p);
|
|
|
|
//setup an event
|
|
memset(&childactionhandler, 0, sizeof(childactionhandler));
|
|
childactionhandler.sa_handler=(void *)mychildhandler;
|
|
childactionhandler.sa_flags=SA_SIGINFO;
|
|
|
|
|
|
|
|
sigaction(SIGCHLD, &childactionhandler, NULL);
|
|
|
|
|
|
|
|
|
|
//read the taskid's from /proc/pid/task
|
|
char _taskdir[255];
|
|
DIR *taskdir;
|
|
|
|
sprintf(_taskdir, "/proc/%d/task", p->pid);
|
|
|
|
taskdir=opendir(_taskdir);
|
|
|
|
if (taskdir)
|
|
{
|
|
struct dirent *d;
|
|
|
|
d=readdir(taskdir);
|
|
while (d)
|
|
{
|
|
int tid=atoi(d->d_name);
|
|
|
|
if (tid)
|
|
{
|
|
ThreadData td;
|
|
memset(&td, 0, sizeof(td));
|
|
td.tid=tid;
|
|
td.isPaused=0;
|
|
AddThreadToProcess(p, &td);
|
|
|
|
pthread_mutex_lock(&memorymutex); //so there's no ptrace_attach busy when attaching after opening and reading memory
|
|
|
|
|
|
if (safe_ptrace(PTRACE_ATTACH, tid,0,0)<0)
|
|
debug_log("Failed to attach to thread %d\n", tid);
|
|
else
|
|
{
|
|
DebugEvent createThreadEvent;
|
|
|
|
|
|
|
|
if (p->isDebugged==0)
|
|
{
|
|
p->isDebugged=1; //at least one made it...
|
|
p->debuggedThreadEvent.threadid=0; //none yet
|
|
p->debuggerThreadID=pthread_self();
|
|
|
|
threadname="CEServer Debugger Thread";
|
|
|
|
socketpair(PF_LOCAL, SOCK_STREAM, 0, &p->debuggerServer); //also sets debuggerClient
|
|
|
|
//first event, create process
|
|
DebugEvent createProcessEvent;
|
|
|
|
#if defined(__arm__) || defined (__aarch64__)
|
|
|
|
if (WaitForDebugEventNative(p, &createProcessEvent, tid, -1))
|
|
{
|
|
//get the debug capabilities
|
|
|
|
getBreakpointCapabilities(tid, &createProcessEvent.maxBreakpointCount, &createProcessEvent.maxWatchpointCount, &createProcessEvent.maxSharedBreakpoints);
|
|
|
|
debug_log("hwbpcap:\n");
|
|
debug_log("number of instruction breakpoints: %d\n", createProcessEvent.maxBreakpointCount);
|
|
debug_log("number of data breakpoints: %d\n", createProcessEvent.maxWatchpointCount);
|
|
|
|
safe_ptrace(PTRACE_CONT, createProcessEvent.threadid, 0,0);
|
|
|
|
PThreadData _td=GetThreadData(p, createProcessEvent.threadid);
|
|
|
|
if (_td)
|
|
_td->isPaused=0;
|
|
else
|
|
debug_log("Invalid first debug thread\n");
|
|
}
|
|
else
|
|
{
|
|
debug_log("Failure waiting for create event");
|
|
createProcessEvent.maxBreakpointCount=0;
|
|
createProcessEvent.maxWatchpointCount=0;
|
|
createProcessEvent.maxSharedBreakpoints=4;
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
//4 breakpoints, hybrid
|
|
createProcessEvent.maxBreakpointCount=0;
|
|
createProcessEvent.maxWatchpointCount=0;
|
|
createProcessEvent.maxSharedBreakpoints=4;
|
|
#endif
|
|
|
|
createProcessEvent.debugevent=-2; //create process
|
|
createProcessEvent.threadid=p->pid;
|
|
|
|
AddDebugEventToQueue(p, &createProcessEvent);
|
|
|
|
}
|
|
|
|
createThreadEvent.debugevent=-1; //create thread event (virtual event)
|
|
createThreadEvent.threadid=tid;
|
|
AddDebugEventToQueue(p, &createThreadEvent);
|
|
|
|
|
|
// p->debuggerThreadID=syscall(SYS_gettid);
|
|
}
|
|
|
|
pthread_mutex_unlock(&memorymutex);
|
|
|
|
}
|
|
|
|
d=readdir(taskdir);
|
|
}
|
|
|
|
closedir(taskdir);
|
|
|
|
}
|
|
else
|
|
{
|
|
debug_log("Failure opening %s",_taskdir);
|
|
}
|
|
|
|
|
|
return p->isDebugged;
|
|
|
|
}
|
|
else
|
|
{
|
|
//printf("Invalid handle\n");
|
|
return FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
int SetBreakpoint(HANDLE hProcess, int tid, int debugreg, void *address, int bptype, int bpsize)
|
|
/*
|
|
* Sets a breakpoint of the specifed type at the given address
|
|
* tid of -1 means ALL threads
|
|
* bptype: 0 = Execute
|
|
* bptype: 1 = write
|
|
* bptype: 2 = read (only)
|
|
* bptype: 3 = Read/Write
|
|
*
|
|
* returns TRUE if the breakpoint was set *
|
|
*/
|
|
{
|
|
int result=FALSE;
|
|
|
|
|
|
debug_log("SetBreakpoint(%d, %d, %d, %p, %d, %d)\n", hProcess, tid, debugreg, address, bptype, bpsize);
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
|
|
|
|
|
|
if (p->debuggerThreadID==pthread_self())
|
|
{
|
|
|
|
int isdebugged=FindPausedThread(p);
|
|
int wtid;
|
|
DebugEvent de;
|
|
|
|
|
|
|
|
debug_log("SetBreakpoint from debuggerthread\n");
|
|
|
|
if (tid==-1)
|
|
{
|
|
int i,r;
|
|
debug_log("Calling SetBreakpoint for all threads\n");
|
|
|
|
for (i=0; i<p->threadlistpos; i++)
|
|
{
|
|
r=SetBreakpoint(hProcess, p->threadlist[i].tid, debugreg, address, bptype, bpsize);
|
|
if (r)
|
|
result=TRUE; //at least one thread succeeded
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
PThreadData td=GetThreadData(p, tid);
|
|
int wasPaused;
|
|
|
|
if (td==NULL) //should NEVER happen
|
|
return FALSE;
|
|
|
|
wasPaused=td->isPaused;
|
|
|
|
debug_log("Calling setbreakpoint for thread :%d\n", tid);
|
|
|
|
debug_log("isdebugged=%d\n", isdebugged);
|
|
|
|
if (wasPaused==0)
|
|
{
|
|
//manual
|
|
debug_log("Target thread wasn't stopped yet\n");
|
|
|
|
int k=0;
|
|
|
|
debug_log("td=%p\n", td);
|
|
debug_log("td->isPaused=%d\n", td->isPaused);
|
|
|
|
|
|
|
|
|
|
wtid=tid;
|
|
while ((td) && (td->isPaused==0) && (k<10))
|
|
{
|
|
debug_log("Not yet paused\n");
|
|
syscall(__NR_tkill, tid, SIGSTOP);
|
|
|
|
if (WaitForDebugEventNative(p, &de, tid, 100))
|
|
{
|
|
wtid=de.threadid;
|
|
break;
|
|
}
|
|
k++;
|
|
}
|
|
|
|
|
|
|
|
if (wtid!=tid)
|
|
{
|
|
debug_log("<<<================UNEXPECTED TID (wtid=%d tid=%d)================>>>\n", wtid, tid);
|
|
}
|
|
|
|
debug_log("k=%d (number of tries)\n", k);
|
|
|
|
if (k==10)
|
|
{
|
|
debug_log("Timeout when waiting for thread\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
debug_log("The thread I wanted to break was already broken. Yeeeh\n");
|
|
wtid=isdebugged;
|
|
}
|
|
|
|
//debugging the given tid
|
|
debug_log("Setting breakpoint in thread %d\n", wtid);
|
|
#ifdef __aarch64__
|
|
struct user_pt_regs regset;
|
|
|
|
int armbpsize=ARM_BREAKPOINT_LEN_4;
|
|
|
|
struct iovec iov;
|
|
int maxWatchCount=0;
|
|
int maxBreakCount=0;
|
|
|
|
debug_log("ceserver compiled for aarch64\n");
|
|
|
|
memset(®set, 0, sizeof(regset));
|
|
memset(&iov, 0, sizeof(iov));
|
|
iov.iov_base=®set;
|
|
iov.iov_len=sizeof(regset);
|
|
int i=safe_ptrace(PTRACE_GETREGSET, wtid, (void*)NT_PRSTATUS, &iov);
|
|
|
|
debug_log("PTRACE_GETREGSET returned %d\n", i);
|
|
debug_log("iov.iov_len=%d\n", (int)iov.iov_len); //272=64 bit app. 72=32 bit app
|
|
|
|
|
|
if (iov.iov_len==72)
|
|
{
|
|
struct pt_regs32 {
|
|
uint32_t uregs[18];
|
|
};
|
|
|
|
struct pt_regs32 *regset32; //can't use struct pt_regs as that uses long, which is 8 bytes
|
|
|
|
regset32=iov.iov_base;
|
|
debug_log("This is a 32 bit target\n");
|
|
|
|
debug_log("pc=%x\n", regset32->ARM_pc);
|
|
debug_log("r0 orig=%x\n", regset32->ARM_ORIG_r0);
|
|
debug_log("r0=%x\n", regset32->ARM_r0);
|
|
debug_log("r1=%x\n", regset32->ARM_r1);
|
|
debug_log("r2=%x\n", regset32->ARM_r2);
|
|
|
|
int i;
|
|
for (i=0; i<18; i++)
|
|
debug_log("uregs[%d]=%x\n", i, regset32->uregs[i]);
|
|
|
|
if (((uintptr_t)address) & 1) //CE uses bit 1 to tell ceserver it's thumb
|
|
{
|
|
armbpsize=ARM_BREAKPOINT_LEN_2;
|
|
address=(void *)((uintptr_t)address & 0xfffffffe); //ce might have set a bit
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
debug_log("pc=%lx\n", regset.pc);
|
|
debug_log("x0=%llx\n", regset.regs[0]);
|
|
debug_log("x1=%llx\n", regset.regs[1]);
|
|
debug_log("x2=%llx\n", regset.regs[2]);
|
|
debug_log("x3=%llx\n", regset.regs[3]);
|
|
}
|
|
|
|
|
|
|
|
|
|
struct user_hwdebug_state hwd;
|
|
memset(&hwd, 0, sizeof(hwd));
|
|
|
|
|
|
iov.iov_base=&hwd;
|
|
iov.iov_len=sizeof(hwd);
|
|
i=safe_ptrace(PTRACE_GETREGSET, wtid, (void*)NT_ARM_HW_WATCH, &iov);
|
|
|
|
if (i==0)
|
|
maxWatchCount=hwd.dbg_info & 0xf;
|
|
|
|
debug_log("iov.iov_len=%d\n", (int)iov.iov_len); //272=64 bit app. 72=32 bit app
|
|
debug_log("PTRACE_GETREGSET for NT_ARM_HW_WATCH returned %d %d\n", i,errno);
|
|
|
|
iov.iov_base=&hwd;
|
|
iov.iov_len=sizeof(hwd);
|
|
i=safe_ptrace(PTRACE_GETREGSET, wtid, (void*)NT_ARM_HW_BREAK, &iov);
|
|
if (i==0)
|
|
maxBreakCount=hwd.dbg_info & 0xf;
|
|
|
|
debug_log("PTRACE_GETREGSET for NT_ARM_HW_BREAK returned %d %d\n", i,errno);
|
|
|
|
debug_log("iov.iov_len=%d\n", (int)iov.iov_len); //272=64 bit app. 72=32 bit app
|
|
|
|
|
|
|
|
int btype=0;
|
|
int bplist=NT_ARM_HW_BREAK;
|
|
|
|
int listsize;
|
|
|
|
if (bptype==0)
|
|
{
|
|
//execute bp
|
|
debug_log("Execute BP\n");
|
|
bplist=NT_ARM_HW_BREAK;
|
|
|
|
btype=ARM_BREAKPOINT_EXECUTE;
|
|
listsize=maxBreakCount;
|
|
|
|
//armbpsize is already set properly
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
//watchpoint
|
|
debug_log("Watchpoint\n");
|
|
bplist=NT_ARM_HW_WATCH;
|
|
if (bptype==1)
|
|
btype=ARM_BREAKPOINT_STORE;
|
|
else
|
|
if (bptype==2)
|
|
btype=ARM_BREAKPOINT_LOAD;
|
|
else
|
|
if (bptype==3)
|
|
btype=ARM_BREAKPOINT_STORE | ARM_BREAKPOINT_LOAD;
|
|
|
|
listsize=maxWatchCount;
|
|
|
|
|
|
//watchpoints have variable sizes
|
|
if (bpsize<=1)
|
|
armbpsize=ARM_BREAKPOINT_LEN_1;
|
|
else if (bpsize<=2)
|
|
armbpsize=ARM_BREAKPOINT_LEN_2;
|
|
else if (bpsize<=4)
|
|
armbpsize=ARM_BREAKPOINT_LEN_4;
|
|
else
|
|
armbpsize=ARM_BREAKPOINT_LEN_8;
|
|
}
|
|
|
|
|
|
debug_log("Caling PTRACE_GETREGSET for bplist %d\n", bplist);
|
|
debug_log("iov.iov_len=%d\n", iov.iov_len);
|
|
i=safe_ptrace(PTRACE_GETREGSET, wtid, (void*)(size_t)bplist, &iov);
|
|
|
|
debug_log("PTRACE_GETREGSET returned %d\n", i);
|
|
|
|
debug_log("debugreg=%d\n", debugreg);
|
|
debug_log("Before:\n");
|
|
|
|
iov.iov_len=8+16*listsize;
|
|
|
|
for (i=0; i<listsize; i++)
|
|
{
|
|
if (hwd.dbg_regs[i].addr) //issue: PTRACE_GETREGSET bplist returns all debug registers as disabled. Assume those with a proper address to not be disabled (so make sure to 0 the address when disabling)
|
|
{
|
|
if (bplist==NT_ARM_HW_BREAK)
|
|
hwd.dbg_regs[i].ctrl=encode_ctrl_reg(0, ARM_BREAKPOINT_LEN_2, ARM_BREAKPOINT_EXECUTE, 0, 1);//hwd.dbg_regs[i].ctrl | 1; //encode_ctrl_reg(0, ARM_BREAKPOINT_LEN_4, btype, 0, 1);
|
|
else
|
|
hwd.dbg_regs[i].ctrl=hwd.dbg_regs[i].ctrl | 1;
|
|
}
|
|
|
|
if (i==debugreg) debug_log("*");
|
|
debug_log("%p - %x\n", (void*)hwd.dbg_regs[i].addr, hwd.dbg_regs[i].ctrl);
|
|
}
|
|
|
|
|
|
|
|
|
|
hwd.dbg_regs[debugreg].addr=(uintptr_t)address;
|
|
hwd.dbg_regs[debugreg].ctrl=encode_ctrl_reg(0, armbpsize, btype, 0, 1);
|
|
|
|
debug_log("setting hwd.dbg_regs[%d].addr to %p\n",debugreg, hwd.dbg_regs[debugreg].addr);
|
|
debug_log("setting hwd.dbg_regs[%d].ctrl to %x\n",debugreg, hwd.dbg_regs[debugreg].ctrl);
|
|
|
|
//iov.iov_len=8+16*(debugreg+1); //sizeof(hwd);
|
|
|
|
|
|
|
|
debug_log("iov.iov_len=%d\n", iov.iov_len);
|
|
|
|
|
|
debug_log("Caling PTRACE_SETREGSET\n", bplist);
|
|
|
|
|
|
i=safe_ptrace(PTRACE_SETREGSET, wtid, (void*)(size_t)bplist, &iov);
|
|
|
|
debug_log("set=%d",i);
|
|
if (i==-1)
|
|
{
|
|
debug_log(" Error=%s",strerror(errno));
|
|
|
|
}
|
|
else
|
|
result=TRUE; //success at least once
|
|
|
|
debug_log("\n");
|
|
|
|
memset(&hwd, 0, sizeof(hwd));
|
|
|
|
i=safe_ptrace(PTRACE_GETREGSET, wtid, (void*)(size_t)bplist, &iov);
|
|
|
|
debug_log("after:\n");
|
|
if (bplist==NT_ARM_HW_BREAK)
|
|
{
|
|
int i;
|
|
for (i=0; i<maxBreakCount; i++)
|
|
{
|
|
if (i==debugreg) debug_log("*");
|
|
debug_log("%p - %x\n", (void*)hwd.dbg_regs[i].addr, hwd.dbg_regs[i].ctrl);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
for (i=0; i<maxWatchCount; i++)
|
|
{
|
|
if (i==debugreg) debug_log("*");
|
|
debug_log("%p - %x\n", (void*)hwd.dbg_regs[i].addr, hwd.dbg_regs[i].ctrl);
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
#ifdef __arm__
|
|
//hwbps
|
|
unsigned long long val;
|
|
int bpindex=1+(2*debugreg);
|
|
|
|
|
|
|
|
debug_log("PTRACE_GETHBPREGS=%d\n",PTRACE_GETHBPREGS);
|
|
|
|
|
|
val=0;
|
|
|
|
if (safe_ptrace(PTRACE_GETHBPREGS, wtid, 0, &val)==0)
|
|
{
|
|
int i;
|
|
unsigned int hwbpreg;
|
|
debug_log("BPREG0 (Info)=%x\n", val);
|
|
|
|
debug_log("Setting bp address\n");
|
|
|
|
|
|
|
|
if (bptype==0)
|
|
{
|
|
//execute
|
|
void *rv=NULL;
|
|
// safe_ptrace(PTRACE_SETHBPREGS, wtid, bpindex, &rv);
|
|
// safe_ptrace(PTRACE_SETHBPREGS, wtid, bpindex+1, &rv);
|
|
|
|
|
|
i=safe_ptrace(PTRACE_GETHBPREGS, wtid, bpindex, &rv);
|
|
debug_log("%d: Before: %d=%p\n", i, bpindex, rv);
|
|
|
|
i=safe_ptrace(PTRACE_SETHBPREGS, wtid, bpindex, &address);
|
|
debug_log("i1=%d\n", i, hwbpreg);
|
|
|
|
i=safe_ptrace(PTRACE_GETHBPREGS, wtid, bpindex, &rv);
|
|
debug_log("%d: After: %d=%p\n", i, bpindex, rv);
|
|
|
|
|
|
//right now i'm not really sure how the breakpoint len is set and why it works in some cases and why not in other cases
|
|
result=i==0;
|
|
|
|
hwbpreg=encode_ctrl_reg(0, ARM_BREAKPOINT_LEN_4, ARM_BREAKPOINT_EXECUTE, 2, 1);
|
|
if (safe_ptrace(PTRACE_SETHBPREGS, wtid, bpindex+1, &hwbpreg)<0) //according to my guess, this should usually work, but just in case...
|
|
{
|
|
debug_log("f1\n");
|
|
hwbpreg=encode_ctrl_reg(0, ARM_BREAKPOINT_LEN_2, ARM_BREAKPOINT_EXECUTE, 2, 1);
|
|
if (safe_ptrace(PTRACE_SETHBPREGS, wtid, bpindex+1, &hwbpreg)<0)
|
|
{
|
|
debug_log("f2\n");
|
|
hwbpreg=encode_ctrl_reg(0, ARM_BREAKPOINT_LEN_1, ARM_BREAKPOINT_EXECUTE, 2, 1);
|
|
if (safe_ptrace(PTRACE_SETHBPREGS, wtid, bpindex+1, &hwbpreg)<0)
|
|
{
|
|
debug_log("f3\n");
|
|
//last try, 8 ?
|
|
hwbpreg=encode_ctrl_reg(0, ARM_BREAKPOINT_LEN_8, ARM_BREAKPOINT_EXECUTE, 2, 1);
|
|
if (safe_ptrace(PTRACE_SETHBPREGS, wtid, bpindex+1, &hwbpreg)<0)
|
|
{
|
|
debug_log("Failure to set breakpoint\n");
|
|
result=FALSE;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
debug_log("bpindex=%d bpindex+1=%d\n", bpindex, bpindex+1);
|
|
|
|
debug_log("hwbpreg=%x\n", hwbpreg);
|
|
|
|
i=safe_ptrace(PTRACE_GETHBPREGS, wtid, bpindex+1, &hwbpreg);
|
|
debug_log("after=%x\n", hwbpreg);
|
|
|
|
}
|
|
else
|
|
{
|
|
//watchpoint
|
|
//(negative)
|
|
int btype;
|
|
|
|
|
|
debug_log("watchpoint\n");
|
|
|
|
i=safe_ptrace(PTRACE_SETHBPREGS, wtid, -bpindex, &address);
|
|
debug_log("i1=%d\n", i, hwbpreg);
|
|
|
|
btype=0;
|
|
if (bptype==1)
|
|
btype=ARM_BREAKPOINT_STORE;
|
|
else
|
|
if (bptype==2)
|
|
btype=ARM_BREAKPOINT_LOAD;
|
|
else
|
|
if (bptype==3)
|
|
btype=ARM_BREAKPOINT_STORE | ARM_BREAKPOINT_LOAD;
|
|
|
|
hwbpreg=encode_ctrl_reg(0, ARM_BREAKPOINT_LEN_4, btype, 0, 1);
|
|
i=safe_ptrace(PTRACE_SETHBPREGS, wtid, -(bpindex+1), &hwbpreg);
|
|
|
|
debug_log("-bpindex=%d -(bpindex+1)=%d\n", -bpindex, -(bpindex+1));
|
|
// debug_log("i=%d (hwbpreg=%x)\n", i, hwbpreg);
|
|
result=i==0;
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
debug_log("Failure getting the debug capability register for thread %d (%d)\n", wtid, errno);
|
|
|
|
|
|
#endif
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
//debug regs
|
|
//PTRACE_SETREGS
|
|
int r,r2;
|
|
|
|
uintptr_t newdr7=safe_ptrace(PTRACE_PEEKUSER, wtid, (void *)(offsetof(struct user, u_debugreg[7])), 0);
|
|
|
|
|
|
newdr7=newdr7 | (1<<debugreg*2);
|
|
|
|
if (bptype==2) //x86 does not support read onlyhw bps
|
|
bptype=3;
|
|
|
|
newdr7=newdr7 | (bptype << (16+(debugreg*4))); //bptype
|
|
|
|
debug_log("Setting DR7 to %x\n", newdr7);
|
|
|
|
//bplen
|
|
if (bpsize<=1)
|
|
newdr7=newdr7 | (0 << (18+(debugreg*4)));
|
|
else
|
|
if (bpsize<=2)
|
|
newdr7=newdr7 | (1 << (18+(debugreg*4)));
|
|
else
|
|
newdr7=newdr7 | (3 << (18+(debugreg*4)));
|
|
|
|
|
|
r=safe_ptrace(PTRACE_POKEUSER, wtid, (void*)(offsetof(struct user, u_debugreg[debugreg])), address);
|
|
r2=safe_ptrace(PTRACE_POKEUSER, wtid, (void*)(offsetof(struct user, u_debugreg[7])), (void*)newdr7);
|
|
|
|
result=(r==0) && (r2==0);
|
|
if (!result)
|
|
{
|
|
debug_log("Failure setting breakpoint\n");
|
|
}
|
|
|
|
debug_log("result=%d (r=%d r2=%d)\n", result, r, r2);
|
|
|
|
|
|
#endif
|
|
|
|
//store this breakpoint in the list
|
|
|
|
|
|
if (wasPaused==0)
|
|
{
|
|
int r;
|
|
|
|
debug_log("Continue self broken thread\n");
|
|
|
|
if (de.debugevent!=SIGSTOP) //in case a breakpoint or something else happened before sigstop happened
|
|
{
|
|
debug_log("Not a SIGSTOP. Adding to queue and leave suspended\n");
|
|
td->isPaused=1; //mark as paused for other api's
|
|
AddDebugEventToQueue(p, &de);
|
|
}
|
|
else
|
|
{
|
|
td->isPaused=0;
|
|
r=safe_ptrace(PTRACE_CONT, wtid, 0,0);
|
|
debug_log("PTRACE_CONT=%d\n", r);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
debug_log("end of SetBreakpoint reached. result=%d\n", result);
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
//not the debugger thread. Send a message to the debuggerthread to execute this command
|
|
debug_log("SetBreakpoint from outside the debuggerthread. Waking debuggerthread\n");
|
|
//setup a sb command
|
|
#pragma pack(1)
|
|
struct
|
|
{
|
|
char command;
|
|
HANDLE hProcess;
|
|
int tid;
|
|
int debugreg;
|
|
uint64_t address;
|
|
int bptype;
|
|
int bpsize;
|
|
} sb;
|
|
#pragma pack()
|
|
|
|
sb.command=CMD_SETBREAKPOINT;
|
|
sb.hProcess=hProcess;
|
|
sb.tid=tid;
|
|
sb.debugreg=debugreg;
|
|
sb.address=(uintptr_t)address;
|
|
sb.bptype=bptype;
|
|
sb.bpsize=bpsize;
|
|
|
|
if (pthread_mutex_lock(&debugsocketmutex) == 0)
|
|
{
|
|
debug_log("Sending message to the debuggerthread\n");
|
|
|
|
sendall(p->debuggerClient, &sb, sizeof(sb), 0);
|
|
WakeDebuggerThread();
|
|
recvall(p->debuggerClient, &result, sizeof(result), MSG_WAITALL);
|
|
|
|
debug_log("Received reply from debugger thread: %d\n", result);
|
|
|
|
|
|
pthread_mutex_unlock(&debugsocketmutex);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// debug_log("Invalid handle\n");
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
int RemoveBreakpoint(HANDLE hProcess, int tid, int debugreg,int wasWatchpoint)
|
|
/*
|
|
* Removes the breakpoint with the provided address
|
|
*/
|
|
{
|
|
int result=FALSE;
|
|
|
|
debug_log("RemoveBreakpoint(%d, %d, %d, %d)\n", hProcess, tid, debugreg, wasWatchpoint);
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
if (p->isDebugged==0)
|
|
{
|
|
debug_log("The current process is not being debugged\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if (p->debuggerThreadID==pthread_self())
|
|
{
|
|
int isdebugged=p->debuggedThreadEvent.threadid;
|
|
int wtid;
|
|
DebugEvent de;
|
|
|
|
debug_log("Called from the debuggerthread itself\n");
|
|
|
|
if (tid==-1)
|
|
{
|
|
int i;
|
|
debug_log("Calling RemoveBreakpoint for all threads\n");
|
|
for (i=0; i<p->threadlistpos; i++)
|
|
{
|
|
if (RemoveBreakpoint(hProcess, p->threadlist[i].tid, debugreg, wasWatchpoint)==TRUE)
|
|
result=TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
debug_log("specific thread\n");
|
|
|
|
PThreadData td=GetThreadData(p, tid);
|
|
int wasPaused=td->isPaused;
|
|
|
|
if (wasPaused==0)
|
|
{
|
|
//manual
|
|
debug_log("Not currently paused\n");
|
|
debug_log("Going to kill and wait for this thread\n");
|
|
|
|
int k;
|
|
|
|
|
|
k=0;
|
|
while ((td) && (td->isPaused==0) && (k<10))
|
|
{
|
|
syscall(__NR_tkill, tid, SIGSTOP);
|
|
if (WaitForDebugEventNative(p, &de, tid, 100))
|
|
break;
|
|
k++;
|
|
}
|
|
|
|
wtid=de.threadid;
|
|
|
|
|
|
debug_log("----AFTER WAIT----\n");
|
|
|
|
debug_log("after wtid=%d\n", wtid);
|
|
debug_log("^^^^AFTER WAIT^^^^\n");
|
|
}
|
|
else
|
|
{
|
|
debug_log("The thread I wanted to break was already broken. Yeeeh\n");
|
|
wtid=isdebugged;
|
|
}
|
|
|
|
//debugging the given tid
|
|
debug_log("Removing breakpoint from thread %d\n", wtid);
|
|
|
|
|
|
#ifdef __arm__
|
|
int bpreg=0;
|
|
int i,i2,i3;
|
|
void *a=NULL;
|
|
|
|
int bpIndex=1+(2*debugreg);
|
|
|
|
debug_log("arm\n");
|
|
|
|
if (wasWatchpoint)
|
|
{
|
|
i=safe_ptrace(PTRACE_SETHBPREGS, wtid, -bpIndex, &bpreg);
|
|
i2=safe_ptrace(PTRACE_SETHBPREGS, wtid, -(bpIndex+1), &bpreg);
|
|
}
|
|
else
|
|
{
|
|
i=safe_ptrace(PTRACE_SETHBPREGS, wtid, bpIndex, &bpreg);
|
|
i2=safe_ptrace(PTRACE_SETHBPREGS, wtid, bpIndex+1, &bpreg);
|
|
}
|
|
|
|
|
|
|
|
debug_log("i1=%d\n", i);
|
|
|
|
debug_log("i2=%d\n", i2);
|
|
|
|
|
|
|
|
i3=safe_ptrace(PTRACE_SETHBPREGS, wtid, 1, &a);
|
|
|
|
result=(i==0) && (i2==0) && (i3==0);
|
|
#endif
|
|
|
|
#ifdef __aarch64__
|
|
int i;
|
|
struct user_hwdebug_state hwd;
|
|
struct iovec iov;
|
|
|
|
memset(&hwd, 0, sizeof(hwd));
|
|
memset(&iov, 0, sizeof(iov));
|
|
iov.iov_base=&hwd;
|
|
iov.iov_len=sizeof(hwd);
|
|
|
|
int bplist;
|
|
|
|
if (wasWatchpoint)
|
|
bplist=NT_ARM_HW_WATCH;
|
|
else
|
|
bplist=NT_ARM_HW_BREAK;
|
|
|
|
i=safe_ptrace(PTRACE_GETREGSET, wtid, (void*)(size_t)bplist, &iov);
|
|
if (i!=0)
|
|
debug_log("PTRACE_GETREGSET failed\n");
|
|
|
|
int listsize=hwd.dbg_info & 0xf;
|
|
|
|
|
|
for (i=0; i<listsize; i++)
|
|
{
|
|
if (hwd.dbg_regs[i].addr)
|
|
{
|
|
if (bplist==NT_ARM_HW_BREAK)
|
|
hwd.dbg_regs[i].ctrl=encode_ctrl_reg(0, ARM_BREAKPOINT_LEN_2, ARM_BREAKPOINT_EXECUTE, 0, 1);
|
|
else
|
|
hwd.dbg_regs[i].ctrl= hwd.dbg_regs[i].ctrl | 1;
|
|
}
|
|
|
|
if (i==debugreg) debug_log("*");
|
|
debug_log("%p - %x\n", (void*)hwd.dbg_regs[i].addr, hwd.dbg_regs[i].ctrl);
|
|
}
|
|
|
|
|
|
|
|
|
|
if (debugreg<listsize)
|
|
{
|
|
hwd.dbg_regs[debugreg].addr=0;
|
|
hwd.dbg_regs[debugreg].ctrl=0;
|
|
|
|
iov.iov_len=8+16*(hwd.dbg_info & 0xf);
|
|
|
|
i=safe_ptrace(PTRACE_SETREGSET, wtid, (void*)(size_t)bplist, (void*)&iov);
|
|
if (i!=0)
|
|
debug_log("PTRACE_SETREGSET failed :%s\n", strerror(errno));
|
|
}
|
|
else
|
|
{
|
|
debug_log("Error: Tried to remove a breakpoint index out of range\n");
|
|
i=1000;
|
|
}
|
|
|
|
|
|
|
|
result=i;
|
|
#endif
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
int r;
|
|
uintptr_t dr7=0;
|
|
debug_log("x86\n");
|
|
|
|
dr7=safe_ptrace(PTRACE_PEEKUSER, wtid, (void*)(offsetof(struct user, u_debugreg[7])), 0);
|
|
|
|
dr7&=~(3 << (debugreg*2)); //disable G# and L#
|
|
dr7&=~(15 << (16+debugreg*4)); //set len and type for this debugreg to 0
|
|
|
|
|
|
r=safe_ptrace(PTRACE_POKEUSER, wtid, (void*)(offsetof(struct user, u_debugreg[debugreg])), 0);
|
|
|
|
|
|
r=safe_ptrace(PTRACE_POKEUSER, wtid, (void*)(offsetof(struct user, u_debugreg[7])), (void*)dr7);
|
|
if (r==0)
|
|
result=TRUE;
|
|
else
|
|
debug_log("Failure removing breakpoint from thread %d\n", wtid);
|
|
|
|
|
|
#endif
|
|
|
|
if (wasPaused==0)
|
|
{
|
|
int r;
|
|
PThreadData td=GetThreadData(p, tid);
|
|
|
|
debug_log("Continue self broken thread\n");
|
|
|
|
if (de.debugevent!=SIGSTOP) //in case a breakpoint or something else happened before sigstop happened
|
|
{
|
|
debug_log("Not a SIGSTOP. Adding to queue and leave suspended\n");
|
|
td->isPaused=1;
|
|
AddDebugEventToQueue(p, &de);
|
|
}
|
|
else
|
|
{
|
|
r=safe_ptrace(PTRACE_CONT, wtid, 0,0);
|
|
debug_log("PTRACE_CONT=%d\n", r);
|
|
|
|
td->isPaused=0;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
debug_log("Called from a secondary thread\n");
|
|
#pragma pack(1)
|
|
struct
|
|
{
|
|
char command;
|
|
HANDLE hProcess;
|
|
int tid;
|
|
int debugreg;
|
|
int wasWatchpoint;
|
|
} rb;
|
|
#pragma pack()
|
|
|
|
rb.command=CMD_REMOVEBREAKPOINT;
|
|
rb.hProcess=hProcess;
|
|
rb.tid=tid;
|
|
rb.debugreg=debugreg;
|
|
rb.wasWatchpoint=wasWatchpoint;
|
|
|
|
|
|
if (pthread_mutex_lock(&debugsocketmutex) == 0)
|
|
{
|
|
|
|
debug_log("Sending message to the debuggerthread\n");
|
|
|
|
sendall(p->debuggerClient, &rb, sizeof(rb), 0);
|
|
WakeDebuggerThread();
|
|
recvall(p->debuggerClient, &result, sizeof(result), MSG_WAITALL);
|
|
|
|
|
|
pthread_mutex_unlock(&debugsocketmutex);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
debug_log("Invalid handle\n");
|
|
|
|
fflush(stdout);
|
|
|
|
return result;
|
|
}
|
|
|
|
BOOL GetThreadContext(HANDLE hProcess, int tid, PCONTEXT Context)
|
|
/*
|
|
* Gets the context of the given thread
|
|
* Freezes/Resumes the thread for you if it isn't suspended yet
|
|
|
|
*/
|
|
{
|
|
BOOL r=FALSE;
|
|
debug_log("GetThreadContext(%d)\n", tid);
|
|
|
|
|
|
|
|
if (tid<=0)
|
|
{
|
|
debug_log("Invalid tid\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
|
|
if (!p->isDebugged)
|
|
{
|
|
debug_log("GetThreadContext with no debugger attached\n");
|
|
int pid=ptrace_attach_andwait(tid);
|
|
int k=getContext(pid, Context);
|
|
|
|
safe_ptrace(PTRACE_DETACH, pid,0,0);
|
|
if (k==0)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
if (p->debuggerThreadID==pthread_self())
|
|
{
|
|
PThreadData td=GetThreadData(p, tid);
|
|
|
|
debug_log("Inside debuggerthread\n");
|
|
|
|
if (td)
|
|
{
|
|
DebugEvent de;
|
|
int wasPaused=td->isPaused;
|
|
int k=0;
|
|
|
|
|
|
while ((td->isPaused==0) && (k<10))
|
|
{
|
|
debug_log("This thread was not paused. Pausing it\n");
|
|
syscall(__NR_tkill, tid, SIGSTOP);
|
|
if (WaitForDebugEventNative(p, &de, tid, 100))
|
|
break;
|
|
|
|
k++;
|
|
}
|
|
|
|
//the thread is paused, so fetch the data
|
|
|
|
debug_log("Getting context of thread %d\n", tid);
|
|
|
|
k=getContext(tid, Context);
|
|
|
|
|
|
//k=safe_ptrace(PTRACE_GETREGS, tid, 0, &Context->regs);
|
|
debug_log("getRegisters() returned %d\n", k);
|
|
|
|
if (k==0)
|
|
r=TRUE;
|
|
else
|
|
r=FALSE;
|
|
|
|
|
|
if (!wasPaused)
|
|
{
|
|
//continue if sigstop
|
|
PThreadData td=GetThreadData(p, tid);
|
|
|
|
debug_log("The thread was not paused, so resuming it now\n");
|
|
|
|
if (de.debugevent!=SIGSTOP) //in case a breakpoint or something else happened before sigstop happened
|
|
{
|
|
debug_log("Not a SIGSTOP. Adding to queue and leave suspended\n");
|
|
td->isPaused=1;
|
|
AddDebugEventToQueue(p, &de);
|
|
|
|
}
|
|
else
|
|
{
|
|
r=(r && (safe_ptrace(PTRACE_CONT, de.threadid, 0,0)==0));
|
|
|
|
|
|
td->isPaused=0;
|
|
debug_log("r=%d\n", r);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
else
|
|
debug_log("Invalid tid\n");
|
|
|
|
}
|
|
else
|
|
{
|
|
debug_log("Not the debugger thread. Pass to serverthread");
|
|
#pragma pack(1)
|
|
struct
|
|
{
|
|
char command;
|
|
HANDLE hProcess;
|
|
int tid;
|
|
} gtc;
|
|
#pragma pack()
|
|
|
|
gtc.command=CMD_GETTHREADCONTEXT;
|
|
gtc.hProcess=hProcess;
|
|
gtc.tid=tid;
|
|
|
|
if (pthread_mutex_lock(&debugsocketmutex) == 0)
|
|
{
|
|
debug_log("Sending message to the debuggerthread\n");
|
|
|
|
sendall(p->debuggerClient, >c, sizeof(gtc), 0);
|
|
WakeDebuggerThread();
|
|
recvall(p->debuggerClient, &r, sizeof(r), MSG_WAITALL);
|
|
|
|
debug_log("Returned from the debuggerthread. result=%d\n",r);
|
|
|
|
if (r)
|
|
{
|
|
//followed by the contextsize
|
|
uint32_t structsize;
|
|
recvall(p->debuggerClient, &structsize, sizeof(structsize), MSG_WAITALL);
|
|
debug_log("structsize received from p->debuggerClient=%d\n", structsize);
|
|
|
|
recvall(p->debuggerClient, Context, structsize, MSG_WAITALL); //and context
|
|
|
|
debug_log("context->structsize received from p->debuggerClient=%d\n",Context->structsize);
|
|
}
|
|
|
|
|
|
pthread_mutex_unlock(&debugsocketmutex);
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
debug_log("invalid handle\n");
|
|
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* Sets the context of the given thread
|
|
* Fails if the thread is not suspended first
|
|
*/
|
|
BOOL SetThreadContext(HANDLE hProcess, int tid, PCONTEXT Context)
|
|
{
|
|
BOOL r=FALSE;
|
|
debug_log("SetThreadContext(%d)\n", tid);
|
|
|
|
|
|
|
|
if (tid<=0)
|
|
{
|
|
debug_log("Invalid tid\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
|
|
|
|
|
|
if (p->debuggerThreadID==pthread_self())
|
|
{
|
|
PThreadData td=GetThreadData(p, tid);
|
|
|
|
debug_log("Inside debuggerthread\n");
|
|
|
|
if (td)
|
|
{
|
|
DebugEvent de;
|
|
int wasPaused=td->isPaused;
|
|
int k=0;
|
|
|
|
|
|
while ((td->isPaused==0) && (k<10))
|
|
{
|
|
debug_log("This thread was not paused. Pausing it\n");
|
|
syscall(__NR_tkill, tid, SIGSTOP);
|
|
if (WaitForDebugEventNative(p, &de, tid, 100))
|
|
break;
|
|
|
|
k++;
|
|
}
|
|
|
|
//the thread is paused, so fetch the data
|
|
|
|
k=setContext(tid, Context);
|
|
|
|
|
|
//k=safe_ptrace(PTRACE_SETREGS, tid, 0, &Context->regs);
|
|
debug_log("setRegisters() returned %d\n", k);
|
|
|
|
if (k==0)
|
|
r=TRUE;
|
|
else
|
|
r=FALSE;
|
|
|
|
|
|
if (!wasPaused)
|
|
{
|
|
//continue if sigstop
|
|
PThreadData td=GetThreadData(p, tid);
|
|
|
|
debug_log("The thread was not paused, so resuming it now\n");
|
|
|
|
if (de.debugevent!=SIGSTOP) //in case a breakpoint or something else happened before sigstop happened
|
|
{
|
|
debug_log("Not a SIGSTOP. Adding to queue and leave suspended\n");
|
|
td->isPaused=1;
|
|
AddDebugEventToQueue(p, &de);
|
|
}
|
|
else
|
|
{
|
|
r=(r && (safe_ptrace(PTRACE_CONT, de.threadid, 0,0)==0));
|
|
|
|
|
|
td->isPaused=0;
|
|
debug_log("r=%d\n", r);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
else
|
|
debug_log("Invalid tid\n");
|
|
|
|
}
|
|
}
|
|
else
|
|
debug_log("invalid handle\n");
|
|
|
|
return r;
|
|
}
|
|
|
|
int SuspendThread(HANDLE hProcess, int tid)
|
|
/*
|
|
* Increase the suspendcount. If it was 0, stop the given thread if it isn't stopped yet
|
|
* If the thread was already stopped, look up this event in the queue and remove it
|
|
* if it's the currently debugged thread it will not be in the queue, but continueFromDebugEvent
|
|
* will see it is suspended, and as a result, NOT continue it, but store the debug event into the thread
|
|
* If it wasn't stopped, stop it, and fetch the debug event
|
|
*/
|
|
{
|
|
int result=-1;
|
|
|
|
debug_log("SuspendThread(%d)\n", tid);
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
PThreadData t=GetThreadData(p, tid);
|
|
|
|
if (t==NULL)
|
|
{
|
|
debug_log("Invalid thread\n");
|
|
return -1;
|
|
}
|
|
|
|
if (p->debuggerThreadID==pthread_self())
|
|
{
|
|
//inside the debuggerthrad
|
|
debug_log("Inside the debugger thread.\n");
|
|
|
|
if (t->isPaused)
|
|
{
|
|
debug_log("Already paused\n");
|
|
|
|
if (t->suspendCount==0)
|
|
{
|
|
//first time suspend. Gather the debug event and remove it from the queue (hardly ever, most of the time it's just the currently broken thread)
|
|
|
|
if (p->debuggedThreadEvent.threadid==tid) //it's the currently suspended thread
|
|
t->suspendedDevent=p->debuggedThreadEvent;
|
|
else
|
|
{
|
|
//go through the queuelist to find the debug event and remove it
|
|
t->suspendedDevent=*FindThreadDebugEventInQueue(p,tid);
|
|
RemoveThreadDebugEventFromQueue(p, tid);
|
|
}
|
|
}
|
|
|
|
t->suspendCount++;
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
debug_log("Not yet paused\n");
|
|
|
|
while (t->isPaused==0)
|
|
{
|
|
syscall(__NR_tkill, tid, SIGSTOP);
|
|
if (WaitForDebugEventNative(p, &t->suspendedDevent, tid, 100))
|
|
break;
|
|
|
|
}
|
|
|
|
t->suspendCount++;
|
|
|
|
}
|
|
|
|
return t->suspendCount;
|
|
|
|
|
|
//PThreadData t=GetThreadData(tid);
|
|
}
|
|
else
|
|
{
|
|
debug_log("Not from the debugger thread. Switching...\n");
|
|
#pragma pack(1)
|
|
struct
|
|
{
|
|
char command;
|
|
HANDLE hProcess;
|
|
int tid;
|
|
} st;
|
|
#pragma pack()
|
|
|
|
st.command=CMD_SUSPENDTHREAD;
|
|
st.hProcess=hProcess;
|
|
st.tid=tid;
|
|
|
|
if (pthread_mutex_lock(&debugsocketmutex) == 0)
|
|
{
|
|
sendall(p->debuggerClient, &st, sizeof(st), 0);
|
|
WakeDebuggerThread();
|
|
recvall(p->debuggerClient, &result, sizeof(result), MSG_WAITALL);
|
|
|
|
pthread_mutex_unlock(&debugsocketmutex);
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
debug_log("invalid handle\n");
|
|
result=-1;
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
int ResumeThread(HANDLE hProcess, int tid)
|
|
/*
|
|
* Decrease suspendcount. If 0, resume the thread by adding the stored debug event back to the queue
|
|
*/
|
|
{
|
|
int result=-1;
|
|
|
|
debug_log("ResumeThread(%d)\n", tid);
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
PThreadData t=GetThreadData(p, tid);
|
|
|
|
if (t==NULL)
|
|
{
|
|
debug_log("Invalid thread\n");
|
|
return -1;
|
|
}
|
|
|
|
if (p->debuggerThreadID==pthread_self())
|
|
{
|
|
//inside the debuggerthread
|
|
debug_log("Inside the debugger thread.\n");
|
|
|
|
if ((t->isPaused) && (t->suspendCount>0))
|
|
{
|
|
|
|
t->suspendCount--;
|
|
|
|
result=t->suspendCount;
|
|
|
|
|
|
if (t->suspendCount==0)
|
|
{
|
|
//reached 0, continue process if sigstop, else add to queue
|
|
PThreadData td=GetThreadData(p, tid);
|
|
debug_log("suspeneCount==0\n");
|
|
|
|
if (t->suspendedDevent.debugevent==SIGSTOP)
|
|
{
|
|
debug_log("SIGSTOP: Continue thread without queing\n");
|
|
safe_ptrace(PTRACE_CONT, t->suspendedDevent.threadid, 0,0);
|
|
td->isPaused=0;
|
|
}
|
|
else
|
|
{
|
|
debug_log("Not a SIGSTOP Add to the event queue\n");
|
|
td->isPaused=1;
|
|
AddDebugEventToQueue(p, &t->suspendedDevent);
|
|
WakeDebuggerThread();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
debug_log("Failure resuming this thread\n");
|
|
|
|
}
|
|
|
|
|
|
//PThreadData t=GetThreadData(tid);
|
|
}
|
|
else
|
|
{
|
|
debug_log("Not from the debugger thread. Switching...\n");
|
|
#pragma pack(1)
|
|
struct
|
|
{
|
|
char command;
|
|
HANDLE hProcess;
|
|
int tid;
|
|
} rt;
|
|
#pragma pack()
|
|
|
|
rt.command=CMD_RESUMETHREAD;
|
|
rt.hProcess=hProcess;
|
|
rt.tid=tid;
|
|
|
|
if (pthread_mutex_lock(&debugsocketmutex) == 0)
|
|
{
|
|
sendall(p->debuggerClient, &rt, sizeof(rt), 0);
|
|
WakeDebuggerThread();
|
|
recvall(p->debuggerClient, &result, sizeof(result), MSG_WAITALL);
|
|
|
|
pthread_mutex_unlock(&debugsocketmutex);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
debug_log("invalid handle\n");
|
|
|
|
return result;
|
|
}
|
|
|
|
int RemoveThreadDebugEventFromQueue(PProcessData p, int tid)
|
|
/*
|
|
* removes the debug event from the queue
|
|
*/
|
|
{
|
|
int result=FALSE;
|
|
struct DebugEventQueueElement *deqe;
|
|
|
|
pthread_mutex_lock(&p->debugEventQueueMutex);
|
|
|
|
// debug_log("RemoveThreadDebugEventFromQueue(%d)\n", tid);
|
|
|
|
deqe=p->debugEventQueue.tqh_first;
|
|
while (deqe)
|
|
{
|
|
// debug_log("deqe->de.threadid=%d (looking for %d)\n", deqe->de.threadid, tid);
|
|
if (deqe->de.threadid==tid)
|
|
{
|
|
//printf("Found. Removing it\n");
|
|
TAILQ_REMOVE(&p->debugEventQueue, deqe, entries);
|
|
|
|
free(deqe);
|
|
result=TRUE;
|
|
break;
|
|
}
|
|
|
|
// debug_log("Not what I wanted. Check next\n");
|
|
deqe=deqe->entries.tqe_next;
|
|
}
|
|
|
|
|
|
pthread_mutex_unlock(&p->debugEventQueueMutex);
|
|
return result;
|
|
}
|
|
|
|
PDebugEvent FindThreadDebugEventInQueue(PProcessData p, int tid)
|
|
/*
|
|
* Finds the DebugEvent for this specific thread if there is one
|
|
* Note that it returns a pointer to it. If you plan on removing it from the queue, copy the results manually
|
|
*/
|
|
{
|
|
PDebugEvent result=NULL;
|
|
struct DebugEventQueueElement *deqe;
|
|
pthread_mutex_lock(&p->debugEventQueueMutex);
|
|
|
|
deqe=p->debugEventQueue.tqh_first;
|
|
while (deqe)
|
|
{
|
|
if ((tid==-1) || (deqe->de.threadid==tid))
|
|
{
|
|
result=&deqe->de;
|
|
break;
|
|
}
|
|
|
|
deqe=deqe->entries.tqe_next;
|
|
}
|
|
|
|
|
|
pthread_mutex_unlock(&p->debugEventQueueMutex);
|
|
return result;
|
|
}
|
|
|
|
void AddDebugEventToQueue(PProcessData p, PDebugEvent devent)
|
|
{
|
|
struct DebugEventQueueElement *deqe;
|
|
|
|
if (devent->debugevent==SIGSTOP)
|
|
{
|
|
debug_log("<<<<<--------------------SIGSTOP ADDED TO THE QUEUE!\n");
|
|
}
|
|
|
|
pthread_mutex_lock(&p->debugEventQueueMutex);
|
|
|
|
deqe=malloc(sizeof(struct DebugEventQueueElement));
|
|
deqe->de=*devent;
|
|
|
|
TAILQ_INSERT_TAIL(&p->debugEventQueue, deqe, entries);
|
|
|
|
pthread_mutex_unlock(&p->debugEventQueueMutex);
|
|
}
|
|
|
|
int GetStopSignalFromThread(int tid)
|
|
{
|
|
siginfo_t si;
|
|
|
|
if (safe_ptrace(PTRACE_GETSIGINFO, tid, NULL, &si)==0)
|
|
return si.si_signo;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
int WaitForDebugEventNative(PProcessData p, PDebugEvent devent, int tid, int timeout)
|
|
/*
|
|
* Waits for a debug event for a specific thread, queues the devent if not the expected tid
|
|
* Only call this from a(the) debugger thread (NO OTHER THREAD MAY CALL THIS)
|
|
*/
|
|
{
|
|
int currentTID;
|
|
int status;
|
|
int r;
|
|
|
|
|
|
|
|
|
|
|
|
//printf("WaitForDebugEventNative (p=%p, devent=%p, tid=%d timeout=%d)\n", p, devent, tid, timeout);
|
|
|
|
|
|
//first check if there is already a thread waiting
|
|
currentTID=1;
|
|
while (currentTID>0)
|
|
{
|
|
currentTID=waitpid(tid, &status, __WALL | WNOHANG);
|
|
|
|
//printf("First waitid=%d\n", currentTID);
|
|
|
|
if (currentTID>0)
|
|
{
|
|
devent->threadid=currentTID;
|
|
devent->debugevent=GetStopSignalFromThread(devent->threadid);
|
|
|
|
PThreadData td=GetThreadData(p, currentTID);
|
|
if (td)
|
|
td->isPaused=1;
|
|
|
|
|
|
if ((tid==-1) || (currentTID==tid))
|
|
return TRUE;
|
|
|
|
//still here, this wasn't what I was looking for...
|
|
//add it to the queue
|
|
|
|
//printf("Unexpected event from thread %d while waiting for %d\n", currentTID, tid);
|
|
|
|
|
|
|
|
AddDebugEventToQueue(p, devent);
|
|
}
|
|
//try again, perhaps there is another one available right now
|
|
}
|
|
|
|
// debug_log("Checking for debug server command\n");
|
|
|
|
// fflush(stdout);
|
|
|
|
//still here
|
|
//CheckForAndDispatchCommand(p->debuggerServer);
|
|
|
|
|
|
// debug_log("After check and dispatch\n");
|
|
// fflush(stdout);
|
|
|
|
|
|
if (timeout>=0)
|
|
{
|
|
//wait till there is an event
|
|
|
|
if (timeout>0)
|
|
{
|
|
struct timespec abstime;
|
|
struct timeval current,wanted, diff;
|
|
int timedwait;
|
|
|
|
// debug_log("timed wait\n");
|
|
|
|
memset(&abstime, 0, sizeof(abstime));
|
|
gettimeofday(¤t,NULL);
|
|
|
|
diff.tv_sec=(timeout / 1000);
|
|
diff.tv_usec=(timeout % 1000)*1000;
|
|
|
|
timeradd(¤t, &diff, &wanted);
|
|
|
|
abstime.tv_sec=wanted.tv_sec;
|
|
abstime.tv_nsec=wanted.tv_usec*1000;
|
|
|
|
currentTID=-1;
|
|
while (currentTID<=0)
|
|
{
|
|
timedwait=sem_timedwait(&sem_DebugThreadEvent, &abstime);
|
|
// if (log==1)
|
|
// debug_log("log=1: sem_timedwait=%d\n", timedwait);
|
|
|
|
if (timedwait==0)
|
|
{
|
|
//it got signaled
|
|
//check if there is a debugger thread message waiting
|
|
// if (log==1)
|
|
// debug_log("Checking for dispatch command\n");
|
|
|
|
CheckForAndDispatchCommand(p->debuggerServer);
|
|
if (VerboseLevel>10)
|
|
debug_log("CheckForAndDispatchCommand returned\n");
|
|
|
|
//check if an event got queued for this thread by the dispatcher
|
|
PDebugEvent e=FindThreadDebugEventInQueue(p, tid);
|
|
if (e)
|
|
{
|
|
debug_log("There was a queued event after CheckForAndDispatchCommand. TID=%ld (wanted %d)\n", e->threadid, tid);
|
|
currentTID=e->threadid;
|
|
|
|
r=RemoveThreadDebugEventFromQueue(p, currentTID);
|
|
debug_log("RemoveThreadDebugEventFromQueue returned %d\n", r);
|
|
}
|
|
else
|
|
currentTID=waitpid(tid, &status, __WALL | WNOHANG);
|
|
|
|
if (VerboseLevel>10)
|
|
debug_log("currentTID = %d\n", currentTID);
|
|
|
|
if (currentTID>0)
|
|
{
|
|
devent->threadid=currentTID;
|
|
devent->debugevent=GetStopSignalFromThread(devent->threadid);
|
|
|
|
PThreadData td=GetThreadData(p, currentTID);
|
|
if (td)
|
|
td->isPaused=1;
|
|
|
|
if ((tid==-1) || (currentTID==tid))
|
|
return TRUE;
|
|
|
|
//still here
|
|
debug_log("Still here so currentTID(%d) is not the same as tid (%d)\n", currentTID, tid);
|
|
AddDebugEventToQueue(p, devent);
|
|
}
|
|
currentTID=-1; //retry
|
|
|
|
}
|
|
else
|
|
{
|
|
if (errno==ETIMEDOUT)
|
|
{
|
|
//printf("timeout\n");
|
|
return FALSE;
|
|
}
|
|
//else
|
|
// debug_log("Not a timeout. Retry\n");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//no wait
|
|
//printf("no wait\n");
|
|
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
//printf("Infinite wait\n");
|
|
currentTID=-1;
|
|
while (currentTID<0)
|
|
{
|
|
currentTID=waitpid(tid, &status, __WALL);
|
|
if (currentTID>0)
|
|
{
|
|
devent->threadid=currentTID;
|
|
devent->debugevent=GetStopSignalFromThread(devent->threadid);
|
|
|
|
PThreadData td=GetThreadData(p, currentTID);
|
|
if (td)
|
|
td->isPaused=1;
|
|
|
|
if ((tid==-1) || (currentTID==tid))
|
|
return TRUE;
|
|
|
|
//still here
|
|
AddDebugEventToQueue(p, devent);
|
|
}
|
|
|
|
if ((currentTID==-1) && (errno!=EINTR))
|
|
{
|
|
debug_log("WaitForDebugEventNative: Infinite wait: Could not wait for tid %d (errno=%d)\n", tid, errno);
|
|
return FALSE; //something bad happened
|
|
}
|
|
|
|
currentTID=-1;
|
|
}
|
|
|
|
}
|
|
|
|
//printf("Returning false\n");
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
int WaitForDebugEvent(HANDLE hProcess, PDebugEvent devent, int timeout)
|
|
/*
|
|
*Waits for the specified timeout in milliseconds, called by the ce gui
|
|
*Does not care about which thread to wait for
|
|
*/
|
|
{
|
|
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
|
|
|
|
|
|
if (p->debuggedThreadEvent.threadid==0)
|
|
{
|
|
|
|
int isdebugged=FindPausedThread(p);
|
|
if (isdebugged)
|
|
{
|
|
debug_log("Error: WaitForDebugEvent and FindPausedThread returned true and p->debuggedThreadEvent.threadid==0");
|
|
|
|
|
|
int i;
|
|
for (i=0; i<p->threadlistpos; i++)
|
|
{
|
|
if (p->threadlist[i].isPaused)
|
|
{
|
|
p->threadlist[i].isPaused=0;
|
|
debug_log("suspendcount=%d\n", p->threadlist[i].suspendCount);
|
|
debug_log("suspendedDevent.debugevent=%d\n", p->threadlist[i].suspendedDevent.debugevent);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
int r=0;
|
|
struct DebugEventQueueElement *de=NULL;
|
|
|
|
//check the queue (first one in the list)
|
|
pthread_mutex_lock(&p->debugEventQueueMutex);
|
|
de=TAILQ_FIRST(&p->debugEventQueue);
|
|
|
|
if (de)
|
|
TAILQ_REMOVE(&p->debugEventQueue, de, entries);
|
|
|
|
|
|
pthread_mutex_unlock(&p->debugEventQueueMutex);
|
|
|
|
if (de) //there was a queued event, return it
|
|
{
|
|
debug_log("Returning queued event (sig=%d, thread=%ld)\n", de->de.debugevent, de->de.threadid);
|
|
if (de->de.debugevent==SIGSTOP)
|
|
{
|
|
debug_log("<---Something queued a SIGSTOP--->\n");
|
|
}
|
|
|
|
*devent=de->de;
|
|
p->debuggedThreadEvent=*devent;
|
|
free(de);
|
|
|
|
r=1; //mark that a queued event happened
|
|
}
|
|
|
|
if (!r) //not a queued event
|
|
r=WaitForDebugEventNative(p, devent, -1, timeout);
|
|
|
|
|
|
if (r)
|
|
{
|
|
p->debuggedThreadEvent=*devent;
|
|
|
|
|
|
if (p->debuggedThreadEvent.debugevent==SIGTRAP)
|
|
{
|
|
siginfo_t si;
|
|
memset(&si, 0, sizeof(si));
|
|
debug_log("SIGTRAP\n");
|
|
|
|
//fill in the address
|
|
|
|
|
|
#if (defined(__arm__) || defined(__aarch64__))
|
|
//return si_addr of siginfo
|
|
if (safe_ptrace(PTRACE_GETSIGINFO, p->debuggedThreadEvent.threadid, NULL, &si)==0)
|
|
{
|
|
|
|
p->debuggedThreadEvent.address=(uintptr_t)si.si_addr;
|
|
debug_log("si.si_addr=%p\n", si.si_addr);
|
|
}
|
|
else
|
|
debug_log("Failure getting siginfo for this trap\n");
|
|
#endif
|
|
|
|
#if defined __i386__ || defined __x86_64__
|
|
//use DR6 to determine which bp (if possible)
|
|
uintptr_t DR0,DR1,DR2,DR3,DR7, IP, SP;
|
|
regDR6 DR6;
|
|
#if defined __i386__
|
|
IP=safe_ptrace(PTRACE_PEEKUSER, p->debuggedThreadEvent.threadid, offsetof(struct user, regs.eip), 0);
|
|
SP=safe_ptrace(PTRACE_PEEKUSER, p->debuggedThreadEvent.threadid, offsetof(struct user, regs.esp), 0);
|
|
#else
|
|
IP=safe_ptrace(PTRACE_PEEKUSER, p->debuggedThreadEvent.threadid, (void*)offsetof(struct user, regs.rip), 0);
|
|
SP=safe_ptrace(PTRACE_PEEKUSER, p->debuggedThreadEvent.threadid, (void*)offsetof(struct user, regs.rsp), 0);
|
|
#endif
|
|
DR0=safe_ptrace(PTRACE_PEEKUSER, p->debuggedThreadEvent.threadid, (void*)offsetof(struct user, u_debugreg[0]), 0);
|
|
DR1=safe_ptrace(PTRACE_PEEKUSER, p->debuggedThreadEvent.threadid, (void*)offsetof(struct user, u_debugreg[1]), 0);
|
|
DR2=safe_ptrace(PTRACE_PEEKUSER, p->debuggedThreadEvent.threadid, (void*)offsetof(struct user, u_debugreg[2]), 0);
|
|
DR3=safe_ptrace(PTRACE_PEEKUSER, p->debuggedThreadEvent.threadid, (void*)offsetof(struct user, u_debugreg[3]), 0);
|
|
|
|
DR6.value=safe_ptrace(PTRACE_PEEKUSER, p->debuggedThreadEvent.threadid, (void*)offsetof(struct user, u_debugreg[6]), 0);
|
|
DR7=safe_ptrace(PTRACE_PEEKUSER, p->debuggedThreadEvent.threadid, (void*)offsetof(struct user, u_debugreg[7]), 0);
|
|
|
|
debug_log("sizeof(dr0)=%d\n", sizeof(DR0));
|
|
debug_log("sizeof(long)=%d\n", sizeof(long));
|
|
debug_log("DR0=%p\n",(void*)DR0);
|
|
debug_log("DR1=%p\n",(void*)DR1);
|
|
debug_log("DR2=%p\n",(void*)DR2);
|
|
debug_log("DR3=%p\n",(void*)DR3);
|
|
debug_log("DR6=%p\n",(void*)(DR6.value));
|
|
debug_log("DR7=%p\n",(void*)DR7);
|
|
debug_log("IP=%p\n",(void*)IP);
|
|
debug_log("SP=%p\n",(void*)SP);
|
|
|
|
|
|
|
|
p->debuggedThreadEvent.address=0; //something unexpected
|
|
if (DR6.B0)
|
|
p->debuggedThreadEvent.address=DR0;
|
|
else
|
|
if (DR6.B1)
|
|
p->debuggedThreadEvent.address=DR1;
|
|
else
|
|
if (DR6.B2)
|
|
p->debuggedThreadEvent.address=DR2;
|
|
else
|
|
if (DR6.B3)
|
|
p->debuggedThreadEvent.address=DR3;
|
|
else
|
|
if (DR6.BS) //single step
|
|
p->debuggedThreadEvent.address=1;
|
|
|
|
|
|
|
|
#endif
|
|
debug_log("p->debuggedThreadEvent.address=%lx\n", p->debuggedThreadEvent.address);
|
|
|
|
devent->address=p->debuggedThreadEvent.address;
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
else
|
|
{
|
|
*devent=p->debuggedThreadEvent;
|
|
debug_log("Can not wait for a debug event when a thread is still paused\n");
|
|
debug_log("tid=%d debugevent=%d\n", (int)devent->threadid, devent->debugevent);
|
|
return 1; //success and just handle this event
|
|
}
|
|
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ContinueFromDebugEvent(HANDLE hProcess, int tid, int ignoresignal)
|
|
{
|
|
//printf("ContinueFromDebugEvent called (%d)\n",tid);
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
PThreadData td=GetThreadData(p, tid);
|
|
siginfo_t si;
|
|
|
|
//printf("td==%p\n", td);
|
|
|
|
if (p->debuggedThreadEvent.debugevent<0) //virtual debug event. Just ignore
|
|
{
|
|
debug_log("Virtual event. Ignore\n");
|
|
p->debuggedThreadEvent.threadid=0;
|
|
p->debuggedThreadEvent.debugevent=0;
|
|
|
|
if (td)
|
|
{
|
|
debug_log("td->isPaused was %d\n", td->isPaused);
|
|
}
|
|
|
|
return 1; //ignore it
|
|
}
|
|
|
|
|
|
if (td==NULL)
|
|
{
|
|
debug_log("Invalid thread\n");
|
|
p->debuggedThreadEvent.threadid=0;
|
|
|
|
return 0; //invalid thread
|
|
}
|
|
|
|
// debug_log("td->suspendcount=%d\n", td->suspendCount);
|
|
|
|
if (td->suspendCount>0)
|
|
{
|
|
debug_log("Tried to continue a suspended thread (suspendcount=%d)\n", td->suspendCount);
|
|
return 1; //keep it suspended
|
|
}
|
|
|
|
// debug_log("p->debuggedThreadEvent.debugevent=%d\n", p->debuggedThreadEvent.debugevent);
|
|
|
|
//if (p->debuggedThreadEvent.threadid!=tid)
|
|
// debug_log("Unexpected thread continue. Expected %d got %d\n", p->debuggedThreadEvent.threadid, tid);
|
|
|
|
|
|
|
|
|
|
if (safe_ptrace(PTRACE_GETSIGINFO, tid, NULL, &si)==0)
|
|
{
|
|
int signal=ignoresignal?0:si.si_signo;
|
|
|
|
// debug_log("si.si_signo=%d\n", si.si_signo);
|
|
// debug_log("si.si_code=%d\n", si.si_code);
|
|
|
|
|
|
|
|
if ((signal==19) || (signal==21)) //STOPPED
|
|
{
|
|
signal=0;
|
|
}
|
|
|
|
#if defined __i386__ || defined __x86_64__
|
|
safe_ptrace(PTRACE_POKEUSER, tid, (void*)offsetof(struct user, u_debugreg[6]), 0);
|
|
#endif
|
|
|
|
|
|
//printf("Continue %d with signal %d\n", tid, signal);
|
|
|
|
int result;
|
|
if (ignoresignal==2)
|
|
{
|
|
debug_log("ContinueFromDebugEvent(): Single step\n");
|
|
|
|
|
|
#ifdef __aarch64__
|
|
/*
|
|
//test: removing old breakpoints (In case CE hadn't done this beforehand...)
|
|
int i;
|
|
int maxWatchCount=0;
|
|
int maxBreakCount=0;
|
|
struct user_pt_regs regset;
|
|
|
|
struct iovec iov;
|
|
|
|
struct user_hwdebug_state watchhwd;
|
|
struct user_hwdebug_state breakhwd;
|
|
struct user_hwdebug_state temphwd;
|
|
memset(&watchhwd, 0, sizeof(watchhwd));
|
|
memset(&breakhwd, 0, sizeof(breakhwd));
|
|
|
|
iov.iov_base=&watchhwd;
|
|
iov.iov_len=sizeof(watchhwd);
|
|
|
|
result=safe_ptrace(PTRACE_GETREGSET, tid, (void*)NT_ARM_HW_WATCH, &iov);
|
|
if (result==0)
|
|
{
|
|
|
|
maxWatchCount=watchhwd.dbg_info & 0xf;
|
|
temphwd=watchhwd;
|
|
|
|
debug_log("Current watchlist:\n");
|
|
|
|
|
|
for (i=0; i<maxWatchCount; i++)
|
|
{
|
|
debug_log("%d: %x - %p:\n", i, watchhwd.dbg_regs[i].ctrl, (void*)watchhwd.dbg_regs[i].addr);
|
|
temphwd.dbg_regs[i].addr=0;
|
|
temphwd.dbg_regs[i].ctrl=0;
|
|
}
|
|
debug_log("Disabling watches for this thread:");
|
|
iov.iov_base=&temphwd;
|
|
iov.iov_len=8+16*maxWatchCount;
|
|
result=safe_ptrace(PTRACE_SETREGSET, tid, (void*)NT_ARM_HW_WATCH, &iov);
|
|
|
|
if (result==0)
|
|
debug_log("Success\n");
|
|
else
|
|
debug_log("Error: %s\n", strerror(errno));
|
|
|
|
}
|
|
|
|
iov.iov_base=&breakhwd;
|
|
iov.iov_len=sizeof(breakhwd);
|
|
i=safe_ptrace(PTRACE_GETREGSET, tid, (void*)NT_ARM_HW_BREAK, &iov);
|
|
if (i==0)
|
|
{
|
|
maxBreakCount=breakhwd.dbg_info & 0xf;
|
|
temphwd=breakhwd;
|
|
debug_log("Current breaklist:\n");
|
|
for (i=0; i<maxBreakCount; i++)
|
|
{
|
|
debug_log("%d: %x - %p:\n", i, breakhwd.dbg_regs[i].ctrl, (void*)breakhwd.dbg_regs[i].addr);
|
|
temphwd.dbg_regs[i].addr=0;
|
|
temphwd.dbg_regs[i].ctrl=0;
|
|
}
|
|
|
|
debug_log("Disabling breaks for this thread:");
|
|
iov.iov_len=8+16*maxBreakCount;
|
|
result=safe_ptrace(PTRACE_SETREGSET, tid, (void*)NT_ARM_HW_BREAK, &iov);
|
|
|
|
if (result==0)
|
|
debug_log("Success\n");
|
|
else
|
|
debug_log("Error: %s\n", strerror(errno));
|
|
}*/
|
|
|
|
//test2: use the Debug mask flag at bit 9 of PSTATE
|
|
|
|
#endif
|
|
|
|
result=safe_ptrace(PTRACE_SINGLESTEP, tid, 0,0);
|
|
|
|
#ifdef __aarch64__
|
|
|
|
if (result==0)
|
|
debug_log("PTRACE_SINGLESTEP returned success\n");
|
|
|
|
|
|
#endif
|
|
if (result!=0)
|
|
{
|
|
debug_log("PTRACE_SINGLESTEP failed (%d). Shit happens\n", errno);
|
|
result=safe_ptrace(PTRACE_CONT, tid, 0,(void*)(size_t)signal);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
result=safe_ptrace(PTRACE_CONT, tid, 0,(void*)(size_t)signal);
|
|
}
|
|
|
|
|
|
|
|
// debug_log("Continue result=%d\n", result);
|
|
|
|
if (td)
|
|
td->isPaused=0;
|
|
|
|
|
|
if (result<0)
|
|
{
|
|
debug_log("Failure to continue thread %d with signal %d\n", tid, signal);
|
|
RemoveThreadFromProcess(p, tid);
|
|
p->debuggedThreadEvent.threadid=0;
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
p->debuggedThreadEvent.threadid=0;
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
debug_log("Failure getting sig info\n");
|
|
|
|
}
|
|
else
|
|
debug_log("Invalid handle\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int StopDebug(HANDLE hProcess)
|
|
{
|
|
debug_log("StopDebug\n");
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
int i;
|
|
|
|
if (p)
|
|
{
|
|
for (i=0; i<p->threadlistpos;i++)
|
|
{
|
|
int tid;
|
|
int status;
|
|
|
|
//stop the thread (else you can't detach)
|
|
while (1)
|
|
{
|
|
syscall(__NR_tkill, p->threadlist[i].tid, SIGSTOP);
|
|
debug_log("Waiting for thread %d\n", p->threadlist[i].tid);
|
|
|
|
tid=waitpid(p->threadlist[i].tid, &status,0);
|
|
if (WIFSTOPPED(status))
|
|
{
|
|
if (WSTOPSIG(status)==SIGSTOP)
|
|
{
|
|
if (tid==p->threadlist[i].tid)
|
|
break;
|
|
else
|
|
{
|
|
debug_log("Got %d instead (status=%x)\n",tid, status);
|
|
safe_ptrace(PTRACE_CONT, tid, 0, 0);
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
debug_log("It stopped but with a wrong signal (%d)\n", WSTOPSIG(status));
|
|
|
|
safe_ptrace(PTRACE_CONT, tid, 0, (void*)(size_t)WSTOPSIG(status));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tid=0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tid)
|
|
{
|
|
debug_log("Detaching %d\n", tid);
|
|
safe_ptrace(PTRACE_DETACH, tid,0,0);
|
|
}
|
|
else
|
|
{
|
|
debug_log("Thread %d was already gone (%x)\n", p->threadlist[i].tid, status);
|
|
|
|
}
|
|
}
|
|
|
|
p->isDebugged=0;
|
|
if (p->debuggerClient)
|
|
{
|
|
close(p->debuggerClient);
|
|
p->debuggerClient=0;
|
|
}
|
|
if (p->debuggerServer)
|
|
{
|
|
close(p->debuggerServer);
|
|
p->debuggerServer=0;
|
|
}
|
|
}
|
|
else
|
|
debug_log("GetPointerFromHandle failed\n");
|
|
}
|
|
|
|
debug_log("after StopDebug\n");
|
|
return 1;
|
|
|
|
}
|
|
|
|
int WriteProcessMemoryDebug(HANDLE hProcess, PProcessData p, void *lpAddress, void *buffer, int size)
|
|
{
|
|
int byteswritten=0;
|
|
int i;
|
|
|
|
|
|
if (threadname)
|
|
debug_log("%s: WriteProcessMemoryDebug:",threadname);
|
|
else
|
|
debug_log("WriteProcessMemoryDebug:");
|
|
|
|
|
|
for (i=0; i<size; i++)
|
|
{
|
|
debug_log("%.2x ", ((unsigned char *)buffer)[i]);
|
|
}
|
|
|
|
debug_log("\n");
|
|
|
|
|
|
|
|
|
|
if (p->debuggerThreadID==pthread_self()) //this is the debugger thread
|
|
{
|
|
int isdebugged=FindPausedThread(p); //find a paused thread if there is one
|
|
DebugEvent event;
|
|
|
|
event.threadid=isdebugged;
|
|
|
|
if (!isdebugged)
|
|
{
|
|
debug_log("Not currently debugging a thread. Suspending a random thread\n");
|
|
kill(p->pid, SIGSTOP);
|
|
|
|
//printf("Going to wait for debug event\n");
|
|
WaitForDebugEventNative(p, &event, -1, -1); //wait for it myself
|
|
|
|
|
|
debug_log("After WaitForDebugEventNative (tid=%d)\n", event.threadid);
|
|
}
|
|
else
|
|
{
|
|
debug_log("isdebugged=%d\n", isdebugged);
|
|
|
|
}
|
|
|
|
|
|
|
|
//write the bytes
|
|
{
|
|
int offset=0;
|
|
int max=size-sizeof(long int);
|
|
|
|
long int *address=(long int *)buffer;
|
|
|
|
|
|
while (offset<max)
|
|
{
|
|
debug_log("offset=%d max=%d\n", offset, max);
|
|
safe_ptrace(PTRACE_POKEDATA, event.threadid, (void*)((uintptr_t)lpAddress+offset), (void *)*address);
|
|
|
|
address++;
|
|
offset+=sizeof(long int);
|
|
byteswritten+=sizeof(long int);
|
|
}
|
|
|
|
if (offset<size)
|
|
{
|
|
debug_log("WPMD: Still some bytes left: %d\n", size-offset);
|
|
//still a few bytes left
|
|
uintptr_t oldvalue=0;
|
|
oldvalue=safe_ptrace(PTRACE_PEEKDATA, event.threadid, (void *)(uintptr_t)lpAddress+offset, (void*)0);
|
|
#ifdef __x86_64__
|
|
//Even with 64 bits, peek_data can read only 4 bytes.
|
|
debug_log("64-bit: oldvalue=%lx\n", oldvalue);
|
|
//oldvalue += safe_ptrace(PTRACE_PEEKDATA, p->pid, (void *)(uintptr_t)lpAddress+offset+4, (void*)0)*0x100000000;
|
|
|
|
//debug_log("64-bit: oldvalue with full read=%lx\n", oldvalue);
|
|
#endif
|
|
unsigned char *oldbuf=(unsigned char *)&oldvalue;
|
|
unsigned char *newmem=(unsigned char *)address;
|
|
int i;
|
|
|
|
debug_log("oldvalue=%lx\n", oldvalue);
|
|
|
|
for (i=0; i< (size-offset); i++)
|
|
oldbuf[i]=newmem[i];
|
|
|
|
debug_log("newvalue=%lx\n", oldvalue);
|
|
|
|
|
|
i=safe_ptrace(PTRACE_POKEDATA, event.threadid, (void*)((uintptr_t)lpAddress+offset), (void *)oldvalue);
|
|
|
|
debug_log("ptrace poke returned %d\n", i);
|
|
if (i>=0)
|
|
byteswritten+=size-offset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isdebugged)
|
|
{
|
|
PThreadData td=GetThreadData(p, event.threadid);
|
|
|
|
//if a SIGSTOP happened just continue it, else the next time WaitForDebugEvent() runs it will get this event from the queue
|
|
if (event.debugevent==SIGSTOP)
|
|
{
|
|
|
|
// debug_log("Continue from sigstop\n");
|
|
|
|
if (td)
|
|
td->isPaused=0;
|
|
|
|
safe_ptrace(PTRACE_CONT, event.threadid, 0,0);
|
|
}
|
|
else
|
|
{
|
|
debug_log("WriteProcessMemoryDebug: Adding unexpected signal to eventqueue (event.debugevent=%d event.threadid)\n", event.debugevent, event.threadid);
|
|
|
|
if (td)
|
|
td->isPaused=1;
|
|
|
|
AddDebugEventToQueue(p, &event);
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
debug_log("WriteProcessMemoryDebug from outside the debuggerthread. Waking debuggerthread\n");
|
|
|
|
//setup a rpm command
|
|
#pragma pack(1)
|
|
struct
|
|
{
|
|
uint8_t command; //1
|
|
uint32_t pHandle; //5
|
|
uint64_t address; //13
|
|
uint32_t size; //17
|
|
} wpm;
|
|
#pragma pack()
|
|
|
|
unsigned char data [size];
|
|
debug_log("sizeof wpm=%d\n", sizeof(wpm));
|
|
wpm.command=CMD_WRITEPROCESSMEMORY;
|
|
wpm.pHandle=hProcess;
|
|
wpm.address=(uintptr_t)lpAddress;
|
|
wpm.size=size;
|
|
memcpy(data, buffer, size);
|
|
|
|
|
|
|
|
//and write it to the p->debuggerthreadfd and read out the data
|
|
//aquire lock (I don't want other threads messing with the client socket)
|
|
if (pthread_mutex_lock(&debugsocketmutex) == 0)
|
|
{
|
|
sendall(p->debuggerClient, &wpm, sizeof(wpm), 0);
|
|
sendall (p-> debuggerClient, &data, size, 0);
|
|
WakeDebuggerThread();
|
|
|
|
recvall(p->debuggerClient, &byteswritten, sizeof(byteswritten), MSG_WAITALL);
|
|
|
|
pthread_mutex_unlock(&debugsocketmutex);
|
|
|
|
debug_log("after recvall byteswritten=%d\n", byteswritten);
|
|
}
|
|
}
|
|
|
|
|
|
return byteswritten;
|
|
}
|
|
|
|
int WriteProcessMemory(HANDLE hProcess, void *lpAddress, void *buffer, int size)
|
|
{
|
|
int written=0;
|
|
|
|
debug_log("WriteProcessMemory(%d, %p, %p, %d)\n", hProcess, lpAddress, buffer, size);
|
|
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
|
|
if (p->isDebugged) //&& cannotdealwithotherthreads
|
|
{
|
|
debug_log("This process is being debugged\n");
|
|
//use the debugger specific writeProcessMemory implementation
|
|
return WriteProcessMemoryDebug(hProcess, p, lpAddress, buffer, size);
|
|
}
|
|
|
|
|
|
if (((MEMORY_SEARCH_OPTION == 2) && (process_vm_writev)) || (MEMORY_SEARCH_OPTION == 3))
|
|
{
|
|
struct iovec local;
|
|
struct iovec remote;
|
|
int attachedpid=-1;
|
|
|
|
debug_log("WPM: MEMORY_SEARCH_OPTION == 2\n");
|
|
|
|
if (ATTACH_TO_WRITE_MEMORY)
|
|
{
|
|
debug_log("WPM: ATTACH_TO_WRITE_MEMORY=%d\n",ATTACH_TO_WRITE_MEMORY);
|
|
attachedpid=ptrace_attach_andwait(p->pid);
|
|
}
|
|
|
|
local.iov_base=buffer;
|
|
local.iov_len=size;
|
|
|
|
remote.iov_base=lpAddress;
|
|
remote.iov_len=size;
|
|
|
|
PROCESS_VM_WRITEV write_v;
|
|
if (MEMORY_SEARCH_OPTION==2)
|
|
write_v=process_vm_writev;
|
|
else
|
|
write_v=process_vm_writev2;
|
|
|
|
|
|
written=write_v(p->pid,&local,1,&remote,1,0);
|
|
if (written==-1)
|
|
{
|
|
debug_log("process_vm_writev(%p, %d) failed: %s\n", lpAddress, size, strerror(errno));
|
|
written=0;
|
|
}
|
|
else
|
|
debug_log("Write successful\n");
|
|
|
|
if (attachedpid>=0)
|
|
{
|
|
debug_log("WPM: Detaching\n");
|
|
safe_ptrace(PTRACE_DETACH, attachedpid,0,0);
|
|
}
|
|
|
|
pthread_mutex_unlock(&memorymutex);
|
|
|
|
if ((written) || (size==0))
|
|
return written;
|
|
|
|
//else try the other writes
|
|
}
|
|
|
|
debug_log("aquiring memorymutex\n");
|
|
if (pthread_mutex_lock(&memorymutex) == 0)
|
|
{
|
|
debug_log("aquired memorymutex\n");
|
|
|
|
|
|
|
|
if (MEMORY_SEARCH_OPTION==2)
|
|
debug_log("process_vm_writev==NULL or failure. Using other method\n");
|
|
|
|
debug_log("WPM: ATTACH_TO_WRITE_MEMORY == %d\n",ATTACH_TO_WRITE_MEMORY);
|
|
debug_log("p->memrw=%d\n", p->memrw);
|
|
debug_log("p->mem=%d\n", p->mem);
|
|
|
|
if ((p->memrw==0) && (ATTACH_TO_WRITE_MEMORY==0))
|
|
{
|
|
debug_log("p->memrw is 0. Overriding ATTACH_TO_WRITE_MEMORY from 0 to 1\n");
|
|
ATTACH_TO_WRITE_MEMORY=1;
|
|
}
|
|
|
|
pid_t pid;
|
|
int canwrite=0;
|
|
|
|
if (ATTACH_TO_WRITE_MEMORY==0)
|
|
{
|
|
pid=p->pid;
|
|
canwrite=1;
|
|
|
|
}
|
|
else
|
|
{
|
|
pid=ptrace_attach_andwait(p->pid);
|
|
if (pid>0)
|
|
canwrite=1;
|
|
}
|
|
|
|
|
|
if (canwrite)
|
|
{
|
|
|
|
|
|
if ((MEMORY_SEARCH_OPTION == 0) && (p->memrw))
|
|
{
|
|
debug_log("WPM: MEMORY_SEARCH_OPTION == 0\n");
|
|
|
|
lseek64(p->mem, (uintptr_t)lpAddress, SEEK_SET);
|
|
written=write(p->mem, buffer, size);
|
|
if (written==-1)
|
|
{
|
|
debug_log("write() to address %p failed with error: %s\n", lpAddress, strerror(errno));
|
|
written=0;
|
|
}
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
debug_log("WPM: MEMORY_SEARCH_OPTION == %d p->memrw=%d\n", MEMORY_SEARCH_OPTION, p->memrw);
|
|
|
|
int offset=0;
|
|
int max=size-sizeof(long int);
|
|
|
|
long int *address=(long int *)buffer;
|
|
|
|
debug_log("start: offset=%d max=%d\n", offset, max);
|
|
|
|
while (offset<max)
|
|
{
|
|
debug_log("offset=%d max=%d\n", offset, max);
|
|
safe_ptrace(PTRACE_POKEDATA, pid, (void*)((uintptr_t)lpAddress+offset), (void *)*address);
|
|
|
|
address++;
|
|
offset+=sizeof(long int);
|
|
|
|
written+=sizeof(long int);
|
|
}
|
|
|
|
debug_log("after loop: offset=%d max=%d\n", offset, max);
|
|
|
|
if (offset<size)
|
|
{
|
|
debug_log("WPM: Still some bytes left: %d\n", size-offset);
|
|
//still a few bytes left
|
|
uintptr_t oldvalue=0;
|
|
oldvalue=safe_ptrace(PTRACE_PEEKDATA, pid, (void *)(uintptr_t)lpAddress+offset, (void*)0);
|
|
|
|
unsigned char *oldbuf=(unsigned char *)&oldvalue;
|
|
unsigned char *newmem=(unsigned char *)address;
|
|
int i;
|
|
|
|
debug_log("oldvalue=%lx\n", oldvalue);
|
|
|
|
for (i=0; i< (size-offset); i++)
|
|
oldbuf[i]=newmem[i];
|
|
|
|
debug_log("newvalue=%lx\n", oldvalue);
|
|
|
|
|
|
i=safe_ptrace(PTRACE_POKEDATA, pid, (void*)((uintptr_t)lpAddress+offset), (void *)oldvalue);
|
|
|
|
debug_log("ptrace poke returned %d\n", i);
|
|
if (i>=0)
|
|
written+=size-offset;
|
|
}
|
|
}
|
|
|
|
if (ATTACH_TO_WRITE_MEMORY)
|
|
safe_ptrace(PTRACE_DETACH, pid,0,0);
|
|
}
|
|
else
|
|
debug_log("PTRACE ATTACH FAILED\n");
|
|
|
|
|
|
pthread_mutex_unlock(&memorymutex);
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return written;
|
|
}
|
|
|
|
int ReadProcessMemoryDebug(HANDLE hProcess, PProcessData p, void *lpAddress, void *buffer, int size)
|
|
{
|
|
//problem: Only the thread that is currently debugging a stopped thread has read access
|
|
//work around: add a que to the debugger thread that fills in this
|
|
//que a ReadProcessMemory command with the debugger
|
|
|
|
//problem 2: If the debugger thread has broken on a breakpoint and another thread wants to read memory it can't
|
|
//e.g: t1:WaitForDebugEvent t1:Returned. T1: recv t2: ReadProcessMemory...
|
|
//solution, let the debuggerthread wait for a socket event with select on 2 fd's. One from the connected socket, and one from the que.
|
|
//the que will contain normal rpm command
|
|
|
|
|
|
int bytesread=0;
|
|
|
|
// debug_log("ReadProcessMemoryDebug\n");
|
|
// debug_log("lpAddress=%p\n", lpAddress);
|
|
|
|
|
|
|
|
if (p->debuggerThreadID==pthread_self()) //this is the debugger thread
|
|
{
|
|
int isdebugged=FindPausedThread(p); //find a paused thread if there is one
|
|
DebugEvent event;
|
|
|
|
event.threadid=isdebugged;
|
|
|
|
|
|
//printf("ReadProcessMemoryDebug inside debuggerthread (thread debbugged=%d)\n", isdebugged);
|
|
|
|
if (!isdebugged)
|
|
{
|
|
// debug_log("Not currently debugging a thread. Suspending a random thread\n");
|
|
kill(p->pid, SIGSTOP);
|
|
|
|
//printf("Going to wait for debug event\n");
|
|
if (WaitForDebugEventNative(p, &event, -1, -1)==FALSE) //wait for it myself
|
|
{
|
|
debug_log("WaitForDebugEventNative returned FALSE for a wait without timeout\n");
|
|
return 0;
|
|
}
|
|
|
|
// debug_log("After WaitForDebugEventNative (tid=%d)\n", event.threadid);
|
|
}
|
|
|
|
|
|
if(MEMORY_SEARCH_OPTION== 0)
|
|
{
|
|
// debug_log("ReadProcessMemoryDebug: MEMORY_SEARCH_OPTION==0");
|
|
|
|
int inflooptest=0;
|
|
|
|
bytesread=-1;
|
|
while (bytesread==-1)
|
|
{
|
|
inflooptest++;
|
|
|
|
if (inflooptest>10)
|
|
debug_log("FUUU");
|
|
|
|
|
|
|
|
//bytesread=pread(p->mem, buffer, size, (uintptr_t)lpAddress);
|
|
|
|
lseek64(p->mem, (uintptr_t)lpAddress, SEEK_SET);
|
|
bytesread=read(p->mem, buffer, size);
|
|
|
|
|
|
if ((bytesread<0) && (errno!=EINTR))
|
|
{
|
|
/*
|
|
debug_log("pread failed and not due to a signal: %d (isdebugged=%d)\n", errno, isdebugged);
|
|
if (isdebugged)
|
|
{
|
|
debug_log("event.threadid=%d devent=%d\n", (int)event.threadid, event.debugevent);
|
|
}
|
|
debug_log("lpAddress=%p\n", lpAddress);
|
|
debug_log("size=%d\n", size);
|
|
*/
|
|
|
|
bytesread=0;
|
|
|
|
if (isdebugged)
|
|
{
|
|
// debug_log("trying to read from specific task\n");
|
|
|
|
int f;
|
|
char mempath[255];
|
|
|
|
sprintf(mempath,"/proc/%d/task/%d/mem", p->pid, (int)event.threadid);
|
|
// debug_log("Opening %s\n", mempath);
|
|
f=open(mempath, O_RDONLY);
|
|
debug_log("f=%d\n", f);
|
|
if (f>=0)
|
|
{
|
|
//bytesread=pread(f, buffer, size, (uintptr_t)lpAddress);
|
|
lseek64(p->mem, (uintptr_t)lpAddress, SEEK_SET);
|
|
bytesread=read(p->mem, buffer, size);
|
|
|
|
if ((bytesread<0) && (errno!=EINTR))
|
|
{
|
|
// debug_log("Also failed on second try\n");
|
|
bytesread=0;
|
|
}
|
|
close(f);
|
|
}
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
int offset=0;
|
|
int max=size-sizeof(long int);
|
|
|
|
long int *address = (long int *)buffer;
|
|
|
|
long int value = 0;
|
|
|
|
int is_readable = 1;
|
|
|
|
while(offset<max)
|
|
{
|
|
errno = 0;
|
|
value = safe_ptrace(PTRACE_PEEKDATA, p->pid, (void*)((uintptr_t)lpAddress+offset), (void *)0);
|
|
|
|
if(errno == 0)
|
|
{
|
|
*address = value;
|
|
|
|
address++;
|
|
offset+=sizeof(long int);
|
|
|
|
bytesread+=sizeof(long int);
|
|
}
|
|
else
|
|
{
|
|
is_readable = 0;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if(offset < size && is_readable)
|
|
{
|
|
errno = 0;
|
|
value = safe_ptrace(PTRACE_PEEKDATA, p->pid, (void*)((uintptr_t)lpAddress+offset), (void *)0);
|
|
|
|
if(errno == 0)
|
|
{
|
|
memcpy(address,&value,size-offset);
|
|
|
|
int i = size-offset;
|
|
if(i>=0)
|
|
bytesread+=size-offset;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (!isdebugged)
|
|
{
|
|
PThreadData td=GetThreadData(p, event.threadid);
|
|
|
|
//if a SIGSTOP happened just continue it, else the next time WaitForDebugEvent() runs it will get this event from the queue
|
|
if (event.debugevent==SIGSTOP)
|
|
{
|
|
|
|
// debug_log("Continue from sigstop\n");
|
|
|
|
safe_ptrace(PTRACE_CONT, event.threadid, 0,0);
|
|
|
|
if (td)
|
|
td->isPaused=0;
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
debug_log("ReadProcessMemoryDebug: Adding unexpected signal to eventqueue (event.debugevent=%d event.threadid=%d)\n", event.debugevent, event.threadid);
|
|
|
|
if (td)
|
|
td->isPaused=1;
|
|
|
|
AddDebugEventToQueue(p, &event);
|
|
|
|
debug_log("After add\n");
|
|
|
|
VerboseLevel=1000000000;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
// debug_log("ReadProcessMemoryDebug from outside the debuggerthread. Waking debuggerthread\n");
|
|
|
|
//setup a rpm command
|
|
#pragma pack(1)
|
|
struct
|
|
{
|
|
uint8_t command;
|
|
uint32_t pHandle;
|
|
uint64_t address;
|
|
uint32_t size;
|
|
uint8_t compressed;
|
|
} rpm;
|
|
#pragma pack()
|
|
|
|
|
|
rpm.command=CMD_READPROCESSMEMORY;
|
|
rpm.pHandle=hProcess;
|
|
rpm.address=(uintptr_t)lpAddress;
|
|
rpm.size=size;
|
|
rpm.compressed=0;
|
|
|
|
//and write it to the p->debuggerthreadfd and read out the data
|
|
//aquire lock (I don't want other threads messing with the client socket)
|
|
if (pthread_mutex_lock(&debugsocketmutex) == 0)
|
|
{
|
|
// debug_log("Sending message to the debuggerthread\n");
|
|
|
|
sendall(p->debuggerClient, &rpm, sizeof(rpm), 0);
|
|
|
|
|
|
//log=1;
|
|
//printf("Waking debugger thread and waiting for result\n");
|
|
WakeDebuggerThread();
|
|
|
|
recvall(p->debuggerClient, &bytesread, sizeof(bytesread), MSG_WAITALL);
|
|
|
|
if (VerboseLevel>10)
|
|
debug_log("After waiting for debugger thread: bytesread=%d\n", bytesread);
|
|
|
|
if (bytesread>0)
|
|
recvall(p->debuggerClient, buffer, bytesread, MSG_WAITALL);
|
|
|
|
|
|
pthread_mutex_unlock(&debugsocketmutex);
|
|
}
|
|
|
|
//bytesread=read...
|
|
}
|
|
|
|
|
|
if (VerboseLevel>10)
|
|
debug_log("ReadProcessMemoryDebug returns %d\n", bytesread);
|
|
|
|
return bytesread;
|
|
}
|
|
|
|
int ReadProcessMemory(HANDLE hProcess, void *lpAddress, void *buffer, int size)
|
|
{
|
|
//idea in case this is too slow. always read a full page and keep the last 16 accessed pages.
|
|
//only on cache miss, or if the cache is older than 1000 milliseconds fetch the page.
|
|
//keep in mind that this routine can get called by multiple threads at the same time
|
|
|
|
if (lpAddress==NULL) //don't even bother
|
|
return 0;
|
|
|
|
|
|
|
|
// debug_log("ReadProcessMemory\n");
|
|
int bread=0;
|
|
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{ //valid handle
|
|
//debug_log("ReadProcessMemory(%d, %p, %p, %d) ATTACH_TO_ACCESS_MEMORY=%d MEMORY_SEARCH_OPTION=%d \n", (int)hProcess, lpAddress, buffer, size, ATTACH_TO_ACCESS_MEMORY, MEMORY_SEARCH_OPTION);
|
|
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
|
|
// debug_log("hProcess=%d, lpAddress=%p, buffer=%p, size=%d\n", hProcess, lpAddress, buffer, size);
|
|
|
|
if ((MEMORY_SEARCH_OPTION == 2) || (MEMORY_SEARCH_OPTION == 3))
|
|
{
|
|
|
|
PROCESS_VM_READV readv=NULL;
|
|
if (MEMORY_SEARCH_OPTION == 2)
|
|
readv=process_vm_readv;
|
|
else
|
|
readv=process_vm_readv2;
|
|
|
|
if (readv)
|
|
{
|
|
struct iovec local;
|
|
struct iovec remote;
|
|
|
|
local.iov_base=buffer;
|
|
local.iov_len=size;
|
|
|
|
remote.iov_base=lpAddress;
|
|
remote.iov_len=size;
|
|
|
|
int canreadnow=1;
|
|
pid_t pid;
|
|
|
|
if (ATTACH_TO_ACCESS_MEMORY)
|
|
{
|
|
canreadnow=0;
|
|
pid=ptrace_attach_andwait(p->pid);
|
|
if (pid>0)
|
|
canreadnow=1;
|
|
|
|
}
|
|
|
|
if (canreadnow)
|
|
{
|
|
bread=readv(p->pid,&local,1,&remote,1,0);
|
|
if (bread==-1)
|
|
{
|
|
// debug_log("process_vm_readv(%x, %d) failed: %s\n", lpAddress, size, strerror(errno));
|
|
bread=0;
|
|
}
|
|
}
|
|
|
|
if (ATTACH_TO_ACCESS_MEMORY)
|
|
safe_ptrace(PTRACE_DETACH, pid,0,0);
|
|
|
|
return bread;
|
|
}
|
|
else
|
|
MEMORY_SEARCH_OPTION=0;
|
|
}
|
|
|
|
|
|
if (p->isDebugged) //&& cannotdealwithotherthreads
|
|
{
|
|
// debug_log("RPM: This process is being debugged. Doing the Debug version\n");
|
|
//use the debugger specific readProcessMemory implementation
|
|
return ReadProcessMemoryDebug(hProcess, p, lpAddress, buffer, size);
|
|
}
|
|
|
|
|
|
|
|
if (pthread_mutex_lock(&memorymutex) == 0)
|
|
{
|
|
//debug_log("Read without debug. MEMORY_SEARCH_OPTION=%d ATTACH_TO_ACCESS_MEMORY=%d\n",MEMORY_SEARCH_OPTION, ATTACH_TO_ACCESS_MEMORY);
|
|
|
|
|
|
|
|
{
|
|
//usleep(100);
|
|
int canreadnow=0;
|
|
pid_t pid;
|
|
|
|
if (ATTACH_TO_ACCESS_MEMORY==0)
|
|
{
|
|
canreadnow=1;
|
|
pid=p->pid;
|
|
}
|
|
else
|
|
{
|
|
pid=ptrace_attach_andwait(p->pid);
|
|
if (pid>0)
|
|
canreadnow=1;
|
|
}
|
|
|
|
|
|
if (canreadnow)
|
|
{
|
|
if (MEMORY_SEARCH_OPTION == 0)
|
|
{
|
|
if (p->mem)
|
|
{
|
|
lseek64(p->mem, (uintptr_t)lpAddress, SEEK_SET);
|
|
bread=read(p->mem, buffer, size);
|
|
if (bread==-1)
|
|
{
|
|
bread=0;
|
|
//debug_log("pread error for address %p (error=%s) ", lpAddress, strerror(errno));
|
|
}
|
|
}
|
|
else
|
|
bread=0;
|
|
}
|
|
else
|
|
{
|
|
int offset=0;
|
|
int max=size-sizeof(long int);
|
|
|
|
long int *address = (long int *)buffer;
|
|
|
|
long int value = 0;
|
|
|
|
int is_readable = 1;
|
|
|
|
while(offset<max)
|
|
{
|
|
errno = 0;
|
|
value = safe_ptrace(PTRACE_PEEKDATA, pid, (void*)((uintptr_t)lpAddress+offset), (void *)0);
|
|
|
|
if(errno == 0)
|
|
{
|
|
*address = value;
|
|
|
|
address++;
|
|
offset+=sizeof(long int);
|
|
|
|
bread+=sizeof(long int);
|
|
}
|
|
else
|
|
{
|
|
is_readable = 0;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if(offset < size && is_readable)
|
|
{
|
|
errno = 0;
|
|
value = safe_ptrace(PTRACE_PEEKDATA, pid, (void*)((uintptr_t)lpAddress+offset), (void *)0);
|
|
|
|
if(errno == 0)
|
|
{
|
|
memcpy(address,&value,size-offset);
|
|
|
|
int i = size-offset;
|
|
if(i>=0)
|
|
bread+=size-offset;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ATTACH_TO_ACCESS_MEMORY)
|
|
{
|
|
int r=safe_ptrace(PTRACE_DETACH, pid,0,0);
|
|
//debug_log("PTRACE_DETACH returned %d\n", r);
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
debug_log("ptrace attach failed (pid=%d). You may not have proper rights, or there's something interfering\n", p->pid);
|
|
|
|
}
|
|
|
|
pthread_mutex_unlock(&memorymutex);
|
|
}
|
|
else
|
|
debug_log("For some reason I failed to obtain a lock\n");
|
|
}
|
|
//else
|
|
// debug_log("RPM: invalid handle\n");
|
|
|
|
// debug_log("Returned from rpm\n");
|
|
|
|
fflush(stdout);
|
|
|
|
return bread;
|
|
}
|
|
|
|
|
|
DWORD ProtectionStringToType(char *protectionstring)
|
|
{
|
|
if (strchr(protectionstring, 's'))
|
|
return MEM_MAPPED;
|
|
else
|
|
return MEM_PRIVATE;
|
|
}
|
|
|
|
uint32_t ProtectionStringToProtection(char *protectionstring)
|
|
{
|
|
int w,x;
|
|
|
|
if (strchr(protectionstring, 'x'))
|
|
x=1;
|
|
else
|
|
x=0;
|
|
|
|
if (strchr(protectionstring, 'w'))
|
|
w=1;
|
|
else
|
|
w=0;
|
|
|
|
if (x)
|
|
{
|
|
//executable
|
|
if (w)
|
|
return PAGE_EXECUTE_READWRITE;
|
|
else
|
|
return PAGE_EXECUTE_READ;
|
|
}
|
|
else
|
|
{
|
|
//not executable
|
|
if (w)
|
|
return PAGE_READWRITE;
|
|
else
|
|
return PAGE_READONLY;
|
|
}
|
|
}
|
|
|
|
void AddToRegionList(uint64_t base, uint64_t size, uint32_t type, uint32_t protection, RegionInfo **list, int *pos, int *max)
|
|
//helper function for VirtualQueryExFull to add new entries to the list
|
|
{
|
|
//printf("Calling AddToRegionList\n");
|
|
|
|
debug_log("++>%llx->%llx : (%llx) - %d\n", (unsigned long long)base, (unsigned long long)base+size, (unsigned long long)size, type);
|
|
|
|
(*list)[*pos].baseaddress=base;
|
|
(*list)[*pos].size=size;
|
|
(*list)[*pos].type=type;
|
|
(*list)[*pos].protection=protection;
|
|
|
|
(*pos)++;
|
|
|
|
if (*pos>=*max)
|
|
{
|
|
debug_log("resize list\n");
|
|
*max=(*max)*2;
|
|
*list=(RegionInfo *)realloc(*list, sizeof(RegionInfo)*(*max));
|
|
}
|
|
|
|
//printf("Returning from AddToRegionList\n");
|
|
}
|
|
|
|
int VirtualQueryExFull(HANDLE hProcess, uint32_t flags, RegionInfo **rinfo, uint32_t *count)
|
|
/*
|
|
* creates a full list of the maps file (less seeking)
|
|
*/
|
|
{
|
|
int pagedonly=flags & VQE_PAGEDONLY;
|
|
int dirtyonly=flags & VQE_DIRTYONLY;
|
|
int noshared=flags & VQE_NOSHARED;
|
|
|
|
debug_log("VirtualQueryExFull:\n");
|
|
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
|
|
char smaps_name[64];
|
|
char pagemap_name[64];
|
|
|
|
sprintf(smaps_name,"/proc/%d/smaps", p->pid);
|
|
sprintf(pagemap_name,"/proc/%d/pagemap", p->pid);
|
|
|
|
FILE *maps=fopen(smaps_name, "r");
|
|
int pagemap=-1;
|
|
|
|
uint64_t *pagemap_entries=NULL;
|
|
|
|
if (pagedonly)
|
|
{
|
|
debug_log("pagedonly\n");
|
|
pagemap=open(pagemap_name, O_RDONLY);
|
|
|
|
// debug_log("pagemap=%p\n", pagemap);
|
|
|
|
|
|
pagemap_entries=(uint64_t *)malloc(512*8);
|
|
|
|
debug_log("allocated pagemap_entries at %p\n", pagemap_entries);
|
|
}
|
|
|
|
|
|
if (maps && (!pagedonly || (pagemap>=0)))
|
|
{
|
|
|
|
|
|
unsigned long long start=0, stop=0;
|
|
char protectionstring[25];
|
|
char x[200];
|
|
int pos=0, max=pagedonly?64:128;
|
|
RegionInfo *r;
|
|
|
|
debug_log("going to allocate r\n");
|
|
r=(RegionInfo *)malloc(sizeof(RegionInfo)*max);
|
|
|
|
debug_log("Allocated r at %p\n", r);
|
|
|
|
int isdirty=0;
|
|
int end=0;
|
|
|
|
|
|
while (!end)
|
|
{
|
|
unsigned long long _start, _stop;
|
|
char temp[25];
|
|
|
|
end=fgets(x, 200, maps)==0;
|
|
|
|
|
|
//make sure the stream gets to tne next line. (long modulepaths can cause an issue)
|
|
|
|
//printf("%s\n",x);
|
|
|
|
if (x[strlen(x)-1]!='\n')
|
|
{
|
|
//need to go to the end of line first
|
|
|
|
char discard[100];
|
|
|
|
do
|
|
{
|
|
discard[99]=0;
|
|
fgets(discard, 99, maps);
|
|
} while (discard[99]!=0);
|
|
}
|
|
|
|
temp[0]=0;
|
|
sscanf(x, "%llx-%llx %24s", &_start, &_stop, temp);
|
|
|
|
if ((end) || (temp[0]!=0))
|
|
{
|
|
//new entry
|
|
if (start)
|
|
{
|
|
int passed=1;
|
|
DWORD type=ProtectionStringToType(protectionstring);
|
|
DWORD protection=ProtectionStringToProtection(protectionstring);
|
|
|
|
//a block was being processed
|
|
|
|
|
|
//some checks to see if it passed
|
|
if (noshared && (type==MEM_MAPPED)) //
|
|
passed=0;
|
|
|
|
if (dirtyonly && !isdirty)
|
|
passed=0;
|
|
|
|
//writable only as well ?
|
|
|
|
if (passed)
|
|
{
|
|
//printf("passed the initial check\n");
|
|
if (pagedonly)
|
|
{
|
|
//only add the pages
|
|
//todo: Add a dirtyonlyPlus which works in conjunction with softdirty pages (only works on newer kernels and requires a forced clear)
|
|
uint64_t current=start;
|
|
int pagesize;
|
|
#ifndef PAGESIZE
|
|
pagesize=getpagesize();
|
|
#else
|
|
pagesize=PAGESIZE;
|
|
#endif
|
|
while (current<stop)
|
|
{
|
|
int i;
|
|
int currentstart=-1;
|
|
off_t offset=(current / pagesize)*8;
|
|
size_t pagecount=(stop-current) / pagesize;
|
|
if (pagecount>512)
|
|
pagecount=512;
|
|
|
|
debug_log("-->%llx->%llx : %s (%llx)\n", start, stop, protectionstring, stop-start);
|
|
|
|
i=pread(pagemap, pagemap_entries, pagecount*8, offset);
|
|
if (i==-1)
|
|
{
|
|
debug_log("offset=%llx, pagecount=%d read=%d (%d) \n", (unsigned long long)offset, (int)pagecount, i/8, i);
|
|
//exit(12);
|
|
}
|
|
else
|
|
{
|
|
for (i=0; i<pagecount; i++)
|
|
{
|
|
if (pagemap_entries[i] >> 63) //present
|
|
{
|
|
if (currentstart==-1) //new entry
|
|
currentstart=i;
|
|
|
|
//total3+=pagesize;
|
|
}
|
|
else
|
|
{
|
|
if (currentstart!=-1)
|
|
{
|
|
//store this section (from currentstart to i-1)
|
|
AddToRegionList((uint64_t)(current+currentstart*pagesize), (i-currentstart)*pagesize, type, protection, &r, &pos, &max);
|
|
}
|
|
|
|
currentstart=-1;
|
|
}
|
|
}
|
|
|
|
if (currentstart!=-1)
|
|
{
|
|
//store this section (from currentstart to pagecount)
|
|
int count=pagecount-currentstart;
|
|
AddToRegionList((uint64_t)(current+currentstart*pagesize), count*pagesize, type, protection, &r, &pos, &max);
|
|
}
|
|
|
|
current+=pagecount*pagesize;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
AddToRegionList(start, stop-start, type, protection, &r, &pos, &max);
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
//declare a new clean block for the next iteration
|
|
isdirty=0;
|
|
strcpy(protectionstring, temp);
|
|
start=_start;
|
|
stop=_stop;
|
|
}
|
|
else
|
|
{
|
|
//one of the info lines
|
|
|
|
if (dirtyonly && (start!=0))
|
|
{
|
|
//figure out if it's something I am interested in (dirty pages)
|
|
int i;
|
|
int number;
|
|
char name[32];
|
|
i=sscanf(x, "%31[^:]: %d", name, &number);
|
|
if (i==2)
|
|
{
|
|
if ((number>0) && ((strcmp(name,"Shared_Dirty")==0) || (strcmp(name,"Private_Dirty")==0))) //the value is >0 and it's information about the dirty state
|
|
isdirty=1;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// debug_log("End of loop\n");
|
|
|
|
if (maps)
|
|
fclose(maps);
|
|
|
|
if (pagemap>=0)
|
|
close(pagemap);
|
|
|
|
|
|
*count=pos;
|
|
*rinfo=r;
|
|
|
|
if (pagemap_entries)
|
|
free(pagemap_entries);
|
|
|
|
fflush(stdout);
|
|
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
debug_log("Failure maps=%p pagemap=%d\n", maps, pagemap);
|
|
|
|
|
|
if (maps)
|
|
fclose(maps);
|
|
else
|
|
debug_log("Failure opening /proc/%d/smaps", p->pid);
|
|
|
|
if (pagemap>=0)
|
|
close(pagemap);
|
|
else
|
|
debug_log("Failure opening /proc/%d/pagemap", p->pid);
|
|
|
|
fflush(stdout);
|
|
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
else
|
|
return 0;
|
|
|
|
}
|
|
|
|
int VirtualQueryEx(HANDLE hProcess, void *lpAddress, PRegionInfo rinfo, char *mapsline)
|
|
{
|
|
/*
|
|
* Alternate method: read pagemaps and look up the pfn in /proc/kpageflags (needs to 2 files open and random seeks through both files, so not sure if slow or painfully slow...)
|
|
*/
|
|
|
|
//VirtualQueryEx stub port. Not a real port, and returns true if successful and false on error
|
|
int found=0;
|
|
|
|
//printf("VirtualQueryEx(%p)", lpAddress);
|
|
|
|
if (GetHandleType(hProcess) == htProcesHandle )
|
|
{
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(hProcess);
|
|
FILE *maps=fopen(p->maps, "r");
|
|
if (maps)
|
|
{
|
|
char x[200];
|
|
//printf("Opened %s\n", p->maps);
|
|
|
|
|
|
rinfo->protection=0;
|
|
rinfo->baseaddress=(uintptr_t)lpAddress & ~0xfff;
|
|
lpAddress=(void *)(uintptr_t)(rinfo->baseaddress);
|
|
|
|
while (fgets(x, 200, maps) )
|
|
{
|
|
unsigned long long start=0, stop=0;
|
|
char protectionstring[25];
|
|
|
|
x[199]=0;
|
|
|
|
if (x[strlen(x)-1]!='\n')
|
|
{
|
|
char discard[100];
|
|
|
|
do
|
|
{
|
|
discard[99]=0;
|
|
fgets(discard, 99, maps);
|
|
} while (discard[99]!=0);
|
|
}
|
|
|
|
|
|
|
|
|
|
sscanf(x, "%llx-%llx %s", &start, &stop, protectionstring);
|
|
// debug_log("%llx - %llx : %s\n", start,stop, protectionstring);
|
|
|
|
if (stop > ((uintptr_t)lpAddress) ) //we passed it
|
|
{
|
|
found=1;
|
|
|
|
if (((uintptr_t)lpAddress) >= start )
|
|
{
|
|
//it's inside the region, so useable
|
|
|
|
rinfo->protection=ProtectionStringToProtection(protectionstring);
|
|
rinfo->type=ProtectionStringToType(protectionstring);
|
|
rinfo->size=stop-rinfo->baseaddress;
|
|
}
|
|
else
|
|
{
|
|
rinfo->size=start-rinfo->baseaddress;
|
|
rinfo->protection=PAGE_NOACCESS;
|
|
rinfo->type=0;
|
|
}
|
|
|
|
if (mapsline!=NULL)
|
|
strcpy(mapsline, x);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(maps);
|
|
|
|
return found;
|
|
}
|
|
else
|
|
{
|
|
debug_log("failed opening %s\n", p->maps);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int SearchHandleListProcessCallback(PProcessData data, int *pid)
|
|
/*
|
|
* Callback. Called during the call to SearchHandleList
|
|
* Searchdata is a pointer to the processid to be looked for
|
|
*/
|
|
{
|
|
return (data->pid==*pid);
|
|
}
|
|
|
|
int CloseAllPipesCallback(void *data, void *searchdata)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
void CloseAllPipes()
|
|
{
|
|
HANDLE h;
|
|
do
|
|
{
|
|
h=SearchHandleList(htPipeHandle, CloseAllPipesCallback,0);
|
|
if (h)
|
|
CloseHandle(h);
|
|
|
|
} while (h);
|
|
}
|
|
|
|
HANDLE OpenPipe(char *pipename, int timeout) //the \\.\pipe\ part has already been stripped
|
|
{
|
|
int i;
|
|
int s;
|
|
int al;
|
|
char name[256];
|
|
debug_log("OpenPipe(\"%s\")", pipename);
|
|
s=socket(AF_UNIX, SOCK_STREAM, 0);
|
|
|
|
sprintf(name, " %s",pipename);
|
|
|
|
struct sockaddr_un address;
|
|
address.sun_family=AF_UNIX;
|
|
strcpy(address.sun_path, name);
|
|
|
|
al=SUN_LEN(&address);
|
|
|
|
address.sun_path[0]=0;
|
|
|
|
debug_log("trying to connect to %s\n", pipename);
|
|
i=connect(s, (struct sockaddr *)&address, al);
|
|
|
|
if ((i!=0) && (timeout))
|
|
{
|
|
uint64_t starttime=getTickCount();
|
|
while ((i!=0) && (getTickCount()<starttime+timeout))
|
|
{
|
|
usleep(50*1000);
|
|
i=connect(s, (struct sockaddr *)&address, al);
|
|
}
|
|
}
|
|
|
|
debug_log("connect returned %d\n", i);
|
|
|
|
if (i==0)
|
|
{
|
|
PPipeData pd=(PPipeData)malloc(sizeof(PipeData));
|
|
|
|
pd->socket=s;
|
|
pd->pipename=strdup(pipename);
|
|
|
|
debug_log("Successful connection to %s\n", pd->pipename);
|
|
|
|
return CreateHandleFromPointer(pd, htPipeHandle);
|
|
}
|
|
else
|
|
{
|
|
debug_log("Failed connecting to %s\n", pipename);
|
|
close(s);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int ReadPipe(HANDLE ph, void* destination, int size, int timeout) //todo: implement timeout
|
|
{
|
|
PPipeData pd=(PPipeData)GetPointerFromHandle(ph);
|
|
if (pd)
|
|
{
|
|
//debug_log("ReadPipe on socket %s\n", pd->pipename);
|
|
return recvall(pd->socket, destination, size,MSG_NOSIGNAL);
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
int WritePipe(HANDLE ph, void* source, int size, int timeout) //todo: implement timeout
|
|
{
|
|
// debug_log("WritePipe %d, %p, %d, %d\n", ph, source, size, timeout);
|
|
|
|
PPipeData pd=(PPipeData)GetPointerFromHandle(ph);
|
|
if (pd)
|
|
{
|
|
// debug_log("WritePipe on socket %s\n", pd->pipename);
|
|
return sendall(pd->socket, source, size,MSG_NOSIGNAL);
|
|
}
|
|
else
|
|
{
|
|
debug_log("WritePipe: invalid handle\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
HANDLE OpenProcess(DWORD pid)
|
|
{
|
|
//check if the process exists
|
|
char processpath[100];
|
|
int handle;
|
|
sprintf(processpath, "/proc/%d/", pid);
|
|
|
|
//check if this process has already been opened
|
|
handle=SearchHandleList(htProcesHandle, (HANDLESEARCHCALLBACK)SearchHandleListProcessCallback, &pid);
|
|
if (handle)
|
|
{
|
|
// debug_log("Already opened. Returning same handle\n");
|
|
PProcessData p=(PProcessData)GetPointerFromHandle(handle);
|
|
p->ReferenceCount++;
|
|
return handle;
|
|
}
|
|
|
|
//still here, so not opened yet
|
|
|
|
|
|
if (chdir(processpath)==0)
|
|
{
|
|
//success
|
|
|
|
//create a process info structure and return a handle to it
|
|
PProcessData p=(PProcessData)malloc(sizeof(ProcessData));
|
|
|
|
memset(p, 0, sizeof(ProcessData));
|
|
|
|
p->ReferenceCount=1;
|
|
p->pid=pid;
|
|
p->path=strdup(processpath);
|
|
|
|
sprintf(processpath,"/proc/%d/maps", pid);
|
|
p->maps=strdup(processpath);
|
|
|
|
if(MEMORY_SEARCH_OPTION == 0)
|
|
{
|
|
debug_log("Opening memory access\n");
|
|
sprintf(processpath,"/proc/%d/mem", pid);
|
|
|
|
p->memrw=1;
|
|
p->mem=open(processpath, O_RDWR);
|
|
if (p->mem==-1)
|
|
{
|
|
debug_log("Failure opening %s for RW access because of :%s\nTrying RO: ",processpath, strerror(errno));
|
|
p->mem=open(processpath, O_RDONLY);
|
|
p->memrw=0;
|
|
|
|
if (p->mem==-1)
|
|
{
|
|
debug_log("Also failed\n");
|
|
if (process_vm_readv && process_vm_writev)
|
|
{
|
|
debug_log("Falling back to MEMORY_SEARCH_OPTION=2 (process_vm_readv and process_vm_writev)\n");
|
|
MEMORY_SEARCH_OPTION=2;
|
|
}
|
|
else
|
|
{
|
|
MEMORY_SEARCH_OPTION=1;
|
|
ATTACH_TO_ACCESS_MEMORY=1;
|
|
debug_log("Falling back to MEMORY_SEARCH_OPTION=1 (ptrace)\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
debug_log("Success. ReadOnly access. Use an alternate write access routine\n");
|
|
ATTACH_TO_WRITE_MEMORY=1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
pthread_mutex_init(&p->extensionMutex, NULL);
|
|
p->hasLoadedExtension=0;
|
|
p->extensionFD=0;
|
|
|
|
|
|
p->isDebugged=0;
|
|
|
|
p->threadlistmax=0;
|
|
p->threadlistpos=0;
|
|
p->threadlist=NULL;
|
|
|
|
p->debuggedThreadEvent.threadid=0;
|
|
|
|
pthread_mutex_init(&p->debugEventQueueMutex, NULL);
|
|
|
|
/*
|
|
p->queuedDebugEventList=NULL;
|
|
p->queuedDebugEventListPos=0;
|
|
p->queuedDebugEventListMax=0;
|
|
*/
|
|
|
|
TAILQ_INIT(&p->debugEventQueue);
|
|
|
|
HANDLE result=CreateHandleFromPointer(p, htProcesHandle);
|
|
|
|
debug_log("Getting processdata for TH32CS_SNAPFIRSTMODULE\n");
|
|
|
|
HANDLE ths=CreateToolhelp32Snapshot(TH32CS_SNAPFIRSTMODULE, p->pid);
|
|
if (ths)
|
|
{
|
|
ModuleListEntry mle;
|
|
if (Module32First(ths, &mle))
|
|
{
|
|
p->is64bit=mle.is64bit;
|
|
|
|
if (p->is64bit)
|
|
debug_log("The opened process is 64-bit\n");
|
|
else
|
|
debug_log("The opened process is 32-bit\n");
|
|
}
|
|
else
|
|
{
|
|
debug_log("Module32First returned false\n"); //fall back on what ceserver is
|
|
#if defined(__aarch64__) || defined(__x86_64__)
|
|
p->is64bit=1;
|
|
#else
|
|
p->is64bit=0;
|
|
#endif
|
|
}
|
|
|
|
CloseHandle(ths);
|
|
}
|
|
else
|
|
{
|
|
debug_log("Failed creating toolhelp snapshot for firstmodule\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
debug_log("Failure opening the process");
|
|
return 0; //could not find the process
|
|
}
|
|
|
|
}
|
|
|
|
|
|
BOOL Process32Next(HANDLE hSnapshot, PProcessListEntry processentry)
|
|
{
|
|
//get the current iterator of the list and increase it. If the max has been reached, return false
|
|
// debug_log("Process32Next\n");
|
|
|
|
if (GetHandleType(hSnapshot) == htTHSProcess)
|
|
{
|
|
PProcessList pl=(PProcessList)GetPointerFromHandle(hSnapshot);
|
|
|
|
if (pl->processListIterator<pl->processCount)
|
|
{
|
|
processentry->PID=pl->processList[pl->processListIterator].PID;
|
|
processentry->ProcessName=pl->processList[pl->processListIterator].ProcessName; //no need to copy
|
|
pl->processListIterator++;
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL Process32First(HANDLE hSnapshot, PProcessListEntry processentry)
|
|
{
|
|
//Get a processentry from the processlist snapshot. fill the given processentry with the data.
|
|
|
|
// debug_log("Process32First\n");
|
|
if (GetHandleType(hSnapshot) == htTHSProcess)
|
|
{
|
|
PProcessList pl=(PProcessList)GetPointerFromHandle(hSnapshot);
|
|
pl->processListIterator=0;
|
|
return Process32Next(hSnapshot, processentry);
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL Module32Next(HANDLE hSnapshot, PModuleListEntry moduleentry)
|
|
{
|
|
//obsolete with the new createtoolhelpsnapshotex
|
|
|
|
//get the current iterator of the list and increase it. If the max has been reached, return false
|
|
// debug_log("Module32First/Next(%d)\n", hSnapshot);
|
|
|
|
if (GetHandleType(hSnapshot) == htTHSModule)
|
|
{
|
|
PModuleList ml=(PModuleList)GetPointerFromHandle(hSnapshot);
|
|
|
|
if (ml->moduleListIterator<ml->moduleCount)
|
|
{
|
|
moduleentry->baseAddress=ml->moduleList[ml->moduleListIterator].baseAddress;
|
|
moduleentry->moduleName=ml->moduleList[ml->moduleListIterator].moduleName;
|
|
moduleentry->moduleSize=ml->moduleList[ml->moduleListIterator].moduleSize;
|
|
moduleentry->part=ml->moduleList[ml->moduleListIterator].part;
|
|
moduleentry->fileOffset=ml->moduleList[ml->moduleListIterator].fileOffset;
|
|
moduleentry->is64bit=ml->moduleList[ml->moduleListIterator].is64bit;
|
|
|
|
ml->moduleListIterator++;
|
|
|
|
//printf("Module32First/Next: Returning %s size %x\n", moduleentry->moduleName, moduleentry->moduleSize);
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
//debug_log("Module32First/Next: Returning false because ml->moduleListIterator=%d and ml->moduleCount=%d\n", ml->moduleListIterator, ml->moduleCount);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
debug_log("Module32First/Next failed: Handle is not a htHTSModule handle: %d\n",GetHandleType(hSnapshot));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
BOOL Module32First(HANDLE hSnapshot, PModuleListEntry moduleentry)
|
|
{
|
|
|
|
//printf("Module32First\n");
|
|
if (GetHandleType(hSnapshot) == htTHSModule)
|
|
{
|
|
PModuleList ml=(PModuleList)GetPointerFromHandle(hSnapshot);
|
|
ml->moduleListIterator=0;
|
|
return Module32Next(hSnapshot, moduleentry);
|
|
}
|
|
else
|
|
{
|
|
debug_log("Module32First error. Handle is not a THSModule handle\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
BOOL Thread32Next(HANDLE hSnapshot, int* threadid)
|
|
{
|
|
if (GetHandleType(hSnapshot) == htTHSThread)
|
|
{
|
|
PThreadList tl=(PThreadList)GetPointerFromHandle(hSnapshot);
|
|
if (tl->threadListIterator>=tl->threadCount)
|
|
return FALSE;
|
|
|
|
*threadid=tl->threadList[tl->threadListIterator];
|
|
tl->threadListIterator++;
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL Thread32First(HANDLE hSnapshot, int* threadid)
|
|
{
|
|
if (GetHandleType(hSnapshot) == htTHSThread)
|
|
{
|
|
PThreadList tl=(PThreadList)GetPointerFromHandle(hSnapshot);
|
|
tl->threadListIterator=0;
|
|
return Thread32Next(hSnapshot, threadid);
|
|
}
|
|
else
|
|
{
|
|
debug_log("Module32First error. Handle is not a THSModule handle\n");
|
|
return FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
HANDLE CreateToolhelp32Snapshot(DWORD dwFlags, DWORD th32ProcessID)
|
|
{
|
|
|
|
if (dwFlags & TH32CS_SNAPPROCESS)
|
|
{
|
|
//create a processlist which process32first/process32next will make use of. Not often called so you may make it as slow as you wish
|
|
int max=2048;
|
|
PProcessList pl=(PProcessList)malloc(sizeof(ProcessList));
|
|
|
|
//printf("Creating processlist\n");
|
|
|
|
pl->ReferenceCount=1;
|
|
pl->processCount=0;
|
|
pl->processList=(PProcessListEntry)malloc(sizeof(ProcessListEntry)* max);
|
|
|
|
DIR *procfolder=opendir("/proc/");
|
|
|
|
struct dirent *currentfile;
|
|
|
|
while ((currentfile=readdir(procfolder)) != NULL)
|
|
{
|
|
|
|
if (strspn(currentfile->d_name, "1234567890")==strlen(currentfile->d_name))
|
|
{
|
|
int pid;
|
|
char exepath[512];
|
|
char processpath[512];
|
|
snprintf(exepath, 512, "/proc/%s/exe", currentfile->d_name);
|
|
exepath[511]=0; //'should' not be needed in linux, but I read that microsoft is an asshole with this function
|
|
|
|
int i=readlink(exepath, processpath, 254);
|
|
if (i != -1)
|
|
{
|
|
char extrafile[512];
|
|
int f;
|
|
|
|
if (i>254)
|
|
i=254;
|
|
|
|
processpath[i]=0;
|
|
|
|
snprintf(extrafile, 512, "/proc/%s/cmdline", currentfile->d_name);
|
|
extrafile[511]=0;
|
|
|
|
f=open(extrafile, O_RDONLY);
|
|
if (f!=-1)
|
|
{
|
|
i=read(f, extrafile, 255);
|
|
if (i>=0)
|
|
extrafile[i]=0;
|
|
else
|
|
extrafile[0]=0;
|
|
|
|
strcat(processpath," ");
|
|
strcat(processpath,extrafile);
|
|
|
|
close(f);
|
|
}
|
|
|
|
|
|
sscanf(currentfile->d_name, "%d", &pid);
|
|
// debug_log("%d - %s\n", pid, processpath);
|
|
|
|
//add this process to the list
|
|
pl->processList[pl->processCount].PID=pid;
|
|
pl->processList[pl->processCount].ProcessName=strdup(processpath);
|
|
|
|
pl->processCount++;
|
|
|
|
if (pl->processCount>=max)
|
|
{
|
|
max=max*2;
|
|
pl->processList=(PProcessListEntry)realloc(pl->processList, max*sizeof(ProcessListEntry));
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
closedir(procfolder);
|
|
|
|
return CreateHandleFromPointer(pl, htTHSProcess);
|
|
}
|
|
else
|
|
if (((dwFlags & TH32CS_SNAPMODULE) || (dwFlags & TH32CS_SNAPFIRSTMODULE) ) && (ATTACH_PID == 0 ||(ATTACH_PID != 0 && (th32ProcessID == ATTACH_PID))))
|
|
{
|
|
//make a list of all the modules loaded by processid th32ProcessID
|
|
//the module list
|
|
int max=64;
|
|
char mapfile[255];
|
|
FILE *f=NULL;
|
|
snprintf(mapfile, 255, "/proc/%d/maps", th32ProcessID);
|
|
|
|
PModuleList ml=(PModuleList)malloc(sizeof(ModuleList));
|
|
|
|
if (dwFlags & TH32CS_SNAPFIRSTMODULE)
|
|
debug_log("Creating 1-entry module list for process %d\n", th32ProcessID);
|
|
|
|
|
|
ml->ReferenceCount=1;
|
|
ml->moduleCount=0;
|
|
ml->moduleList=(PModuleListEntry)malloc(sizeof(ModuleListEntry)*max);
|
|
|
|
|
|
f=fopen(mapfile, "r");
|
|
|
|
|
|
if (f)
|
|
{
|
|
char s[512];
|
|
memset(s, 0, 512);
|
|
|
|
PModuleListEntry mle=NULL;
|
|
int phandle=OpenProcess(th32ProcessID);
|
|
|
|
|
|
|
|
while (fgets(s, 511, f)) //read a line into s
|
|
{
|
|
unsigned long long start, stop;
|
|
uint32_t fileoffset;
|
|
char protectionstring[32],modulepath[511];
|
|
unsigned char elfident[8];
|
|
|
|
modulepath[0]='\0';
|
|
memset(modulepath, 0, 255);
|
|
|
|
|
|
sscanf(s, "%llx-%llx %s %x %*s %*s %[^\t\n]\n", &start, &stop, protectionstring, &fileoffset, modulepath);
|
|
|
|
//if (ProtectionStringToType(protectionstring)==MEM_MAPPED)
|
|
// continue;
|
|
|
|
if (modulepath[0]) //it's something
|
|
{
|
|
int i;
|
|
if (strcmp(modulepath, "[heap]")==0) //not static enough to mark as a 'module'
|
|
continue;
|
|
|
|
if ((modulepath[0]=='/') && (modulepath[1]=='d') && (modulepath[2]=='e') && (modulepath[3]=='v') && (modulepath[4]=='/'))
|
|
continue; //no /dev/
|
|
|
|
|
|
// debug_log("Checking if %s is a module\n", modulepath);
|
|
|
|
if (strcmp(modulepath, "[vdso]")!=0) //temporary patch as to not rename vdso, because it is treated differently by the ce symbol loader
|
|
{
|
|
for (i=0; modulepath[i]; i++) //strip square brackets from the name (conflicts with pointer notations)
|
|
{
|
|
if ((modulepath[i]=='[') || (modulepath[i]==']'))
|
|
modulepath[i]='_';
|
|
}
|
|
}
|
|
|
|
//new module, or not linkable
|
|
|
|
mle=NULL;
|
|
|
|
|
|
//check if it's readable
|
|
|
|
if (start==0x7f9f9144e000)
|
|
{
|
|
debug_log("break");
|
|
}
|
|
|
|
i=ReadProcessMemory(phandle, (void *)start, elfident, 8); //only the first few bytes
|
|
if (i==0)
|
|
{
|
|
//debug_log("thread %d (%s): Failed to read the start of %s (address %llx)\n", getpid(), threadname, modulepath, start);
|
|
continue; //unreadable
|
|
}
|
|
//else
|
|
// debug_log("thread %d (%s): Successfully read the start of %s (address %llx)\n", getpid(), threadname, modulepath, start);
|
|
|
|
//check if this module is in the list. If so, mark it with a part tag
|
|
int part=0;
|
|
|
|
//this is going to be slower than the original implementation. But lets assume cpu's have gotten faster by now
|
|
|
|
for (i=ml->moduleCount-1; i>=0; i--)
|
|
{
|
|
if (strcmp(ml->moduleList[i].moduleName, modulepath)==0)
|
|
{
|
|
part=ml->moduleList[i].part+1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (((elfident[0]!=ELFMAG0) || (elfident[1]!=ELFMAG1) || (elfident[2]!=ELFMAG2) || (elfident[3]!=ELFMAG3) ) ) // 7f 45 4c 46 , not yet in the list, and not an ELF
|
|
{
|
|
// debug_log("%s is not an ELF (%p = %.2x %.2x %.2x %.2x)\n", modulepath, start, elfident[0], elfident[1], elfident[2], elfident[3] );
|
|
continue; //not an ELF
|
|
}
|
|
|
|
//it's either an ELF, or there is another entry with this name in the list that is an ELF
|
|
|
|
//e.g:
|
|
//71e8f0f86000-71e8f0fb5000 r-xp 00001000 08:13 5488717 /data/app/com.unciv.app-8m-YznaBZ84t5VpB6J6Stg==/split_config.x86_64.apk
|
|
|
|
|
|
//if (dwFlags & TH32CS_SNAPFIRSTMODULE)
|
|
// debug_log("Adding %s as a module\n", modulepath);
|
|
|
|
mle=&ml->moduleList[ml->moduleCount];
|
|
mle->moduleName=strdup(modulepath);
|
|
mle->baseAddress=start;
|
|
mle->fileOffset=fileoffset;
|
|
mle->moduleSize=/*stop-start;*/GetModuleSize(modulepath, fileoffset,0);
|
|
if (mle->moduleSize==-1)
|
|
mle->moduleSize=stop-start;
|
|
mle->part=part;
|
|
|
|
if (part==0)
|
|
mle->is64bit=elfident[EI_CLASS]==ELFCLASS64; //else is64bit is invalid
|
|
|
|
|
|
// debug_log("Setting size of %s to %x\n", modulepath, mle->moduleSize);
|
|
|
|
ml->moduleCount++;
|
|
|
|
if (ml->moduleCount>=max)
|
|
{
|
|
//printf("reallocate modulelist\n");
|
|
max=max*2;
|
|
ml->moduleList=(PModuleListEntry)realloc(ml->moduleList, max* sizeof(ModuleListEntry));
|
|
}
|
|
|
|
if (dwFlags & TH32CS_SNAPFIRSTMODULE)
|
|
break;
|
|
|
|
|
|
}
|
|
else
|
|
mle=NULL;
|
|
|
|
|
|
|
|
}
|
|
|
|
CloseHandle(phandle);
|
|
|
|
fclose(f);
|
|
|
|
return CreateHandleFromPointer(ml, htTHSModule);
|
|
}
|
|
else
|
|
{
|
|
if (dwFlags & TH32CS_SNAPFIRSTMODULE)
|
|
debug_log("Failed opening %s\n", mapfile);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
else
|
|
if (dwFlags & TH32CS_SNAPTHREAD)
|
|
{
|
|
int max=64;
|
|
char _taskdir[255];
|
|
DIR *taskdir;
|
|
|
|
debug_log("TH32CS_SNAPTHREAD\n");
|
|
if (th32ProcessID==0)
|
|
return 0; //not handled (unlike the windows TH32CS_SNAPTHREAD, this only gets the threads of the provided processid)
|
|
|
|
sprintf(_taskdir, "/proc/%d/task", th32ProcessID);
|
|
|
|
debug_log("reading %s\n",_taskdir);
|
|
|
|
taskdir=opendir(_taskdir);
|
|
if (taskdir)
|
|
{
|
|
struct dirent *d;
|
|
PThreadList tl=(PThreadList)malloc(sizeof(ThreadList));
|
|
|
|
tl->ReferenceCount=1;
|
|
tl->threadCount=0;
|
|
tl->threadList=(int*)malloc(max*sizeof(int));
|
|
|
|
d=readdir(taskdir);
|
|
while (d)
|
|
{
|
|
int tid=atoi(d->d_name);
|
|
|
|
if (tid)
|
|
{
|
|
debug_log("found threadid %d\n", tid);
|
|
tl->threadList[tl->threadCount]=tid;
|
|
tl->threadCount++;
|
|
if (tl->threadCount>=max)
|
|
{
|
|
max=max*2;
|
|
tl->threadList=(int*)realloc(tl->threadList, max*sizeof(int));
|
|
}
|
|
}
|
|
d=readdir(taskdir);
|
|
}
|
|
closedir(taskdir);
|
|
|
|
return CreateHandleFromPointer(tl, htTHSThread);
|
|
}
|
|
}
|
|
|
|
|
|
debug_log("Unhandled toolhelp32snapshot flags: %x\n", dwFlags);
|
|
return 0;
|
|
}
|
|
|
|
void CloseHandle(HANDLE h)
|
|
{
|
|
int i;
|
|
handleType ht=GetHandleType(h);
|
|
|
|
// debug_log("CloseHandle(%d)\n", h);
|
|
if (ht==htTHSModule)
|
|
{
|
|
ModuleList *ml=(PModuleList)GetPointerFromHandle(h);
|
|
ml->ReferenceCount--;
|
|
if (ml->ReferenceCount<=0)
|
|
{
|
|
//free all the processnames in the list
|
|
for (i=0; i<ml->moduleCount; i++)
|
|
free(ml->moduleList[i].moduleName);
|
|
|
|
free(ml->moduleList); //free the list
|
|
free(ml); //free the descriptor
|
|
|
|
RemoveHandle(h);
|
|
}
|
|
|
|
}
|
|
else
|
|
if (ht==htTHSProcess)
|
|
{
|
|
ProcessList *pl=(PProcessList)GetPointerFromHandle(h);
|
|
|
|
|
|
pl->ReferenceCount--;
|
|
|
|
if (pl->ReferenceCount<=0)
|
|
{
|
|
//free all the processnames in the list
|
|
for (i=0; i<pl->processCount; i++)
|
|
free(pl->processList[i].ProcessName);
|
|
|
|
free(pl->processList); //free the list
|
|
free(pl); //free the descriptor
|
|
|
|
RemoveHandle(h);
|
|
}
|
|
}
|
|
else
|
|
if (ht==htTHSThread)
|
|
{
|
|
ThreadList *tl=(PThreadList)GetPointerFromHandle(h);
|
|
|
|
tl->ReferenceCount--;
|
|
if (tl->ReferenceCount<=0)
|
|
{
|
|
free(tl->threadList);
|
|
RemoveHandle(h);
|
|
}
|
|
}
|
|
else
|
|
if (ht==htProcesHandle)
|
|
{
|
|
PProcessData pd=(PProcessData)GetPointerFromHandle(h);
|
|
|
|
pd->ReferenceCount--;
|
|
if (pd->ReferenceCount<=0)
|
|
{
|
|
free(pd->maps);
|
|
free(pd->path);
|
|
close(pd->mem);
|
|
free(pd);
|
|
|
|
RemoveHandle(h);
|
|
}
|
|
}
|
|
else
|
|
if (ht==htNativeThreadHandle)
|
|
{
|
|
uint64_t *th=GetPointerFromHandle(h);
|
|
debug_log("Closing thread handle\n");
|
|
|
|
free(th);
|
|
RemoveHandle(h);
|
|
}
|
|
else
|
|
if (ht==htPipeHandle)
|
|
{
|
|
debug_log("Closing pipe handle\n");
|
|
|
|
PPipeData pd=GetPointerFromHandle(h);
|
|
close(pd->socket);
|
|
free(pd->pipename);
|
|
free(pd);
|
|
RemoveHandle(h);
|
|
}
|
|
else
|
|
RemoveHandle(h); //no idea what it is...
|
|
|
|
|
|
}
|
|
|
|
uint64_t getTickCount()
|
|
{
|
|
struct timespec ts;
|
|
uint64_t r=0;
|
|
clock_gettime( CLOCK_MONOTONIC, &ts );
|
|
r = ts.tv_nsec / 1000000;
|
|
r += ts.tv_sec * 1000;
|
|
return r;
|
|
}
|
|
|
|
|
|
void initAPI()
|
|
{
|
|
pthread_mutex_init(&memorymutex, NULL);
|
|
pthread_mutex_init(&debugsocketmutex, NULL);
|
|
|
|
sem_init(&sem_DebugThreadEvent, 0, 0); //locked by default
|
|
|
|
void *libc=dlopen("libc.so",RTLD_NOW);
|
|
|
|
if (libc)
|
|
{
|
|
process_vm_readv=dlsym(libc,"process_vm_readv");
|
|
process_vm_writev=dlsym(libc,"process_vm_writev");
|
|
}
|
|
|
|
|
|
if (!process_vm_readv)
|
|
{
|
|
process_vm_readv=dlsym(0, "process_vm_readv");
|
|
process_vm_writev=dlsym(0, "process_vm_writev");
|
|
}
|
|
|
|
debug_log("process_vm_readv=%p\n",process_vm_readv);
|
|
debug_log("process_vm_writev=%p\n",process_vm_writev);
|
|
|
|
}
|