第6篇中的完整源代码,简单起见写在一个C文件里。
编译时把文件名后缀改成.C。因为是C风格的,C++可能编译不过。
在MSVC++6(SP4)/GCC3.3.1、Windows2000(SP4)上编译运行均没问题。
/* ex2.c by Boco@USTC 2005-1-4 */
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define NO_TRACE /* 将此句注释,则详细查看调试器的工作 */
#define TF_BIT 0x100
#define INT3 0xCC
#define TASKBAR_CLASS "Shell_TrayWnd"
#define MAX_PROC 256
#define MAX_THRD 256
#define MAX_DLL 256
#define MAX_BP 8
#define HANDLE_EVENT(id,func) \
case id: func(); break;
#define HANDLE_DEFAULT() \
default: mark=DBG_CONTINUE;
#define alloc(ar,max) \
_alloc(ar,sizeof(ar[0]),max)
#define find(ar,max,id) \
_find(ar,sizeof(ar[0]),max,id)
#define suspend_except(ts,id) \
thread_except(ts,id,SuspendThread)
#define resume_except(ts,id) \
thread_except(ts,id,ResumeThread)
typedef struct process process;
typedef struct thread thread;
typedef struct module module;
typedef struct breakpoint breakpoint;
struct breakpoint /* 存储断点信息 */
{
BOOL f; /* 有效位,下同 */
DWORD addr; /* 断点物理位置 */
BYTE c; /* 保存被断点覆盖的代码 */
};
struct thread /* 存储线程信息 */
{
BOOL f;
DWORD id; /* 线程ID */
HANDLE h; /* 线程句柄 */
LPVOID addr; /* 线程执行的初始地址 */
};
struct module
{
BOOL f;
LPVOID base; /* 模块基地址 */
HANDLE h; /* 模块的文件句柄*/
};
struct process
{
BOOL f;
DWORD id; /* PID */
HANDLE h; /* 进程句柄 */
LPVOID base; /* 程序(EXE)模块基地址 */
LPVOID addr; /* 程序的启动地址 */
BOOL init_bp; /* 标识初始断点是否已经发生 */
breakpoint* write_back; /* 存放单步后准备写回的断点 */
thread ts[MAX_THRD]; /* 存放该进程内的线程 */
module ms[MAX_DLL]; /* 存放该进程内的模块 */
breakpoint bps[MAX_BP]; /* 存放该进程内的断点 */
};
/* declaration */
void on_create_process();
void on_exit_process();
void on_create_thread();
void on_exit_thread();
void on_load_dll();
void on_unload_dll();
void on_exception();
void on_bp(DWORD pid,DWORD tid,DWORD addr);
void* _alloc(void* ar,size_t cb,size_t max);
void* _find(void* ar,size_t cb,size_t max,DWORD id);
void safe_read(HANDLE p,DWORD addr,BYTE* c);
void safe_write(HANDLE p,DWORD addr,BYTE c);
void thread_except(thread ts[],DWORD id,DWORD (CALLBACK* func)(HANDLE));
void set_bp(DWORD pid,DWORD addr);
void my_init(DWORD pid);
process ps[MAX_PROC];
DEBUG_EVENT d;
DWORD mark;
void debug(DWORD id) /* debug loop */
{
if (!DebugActiveProcess(id))
assert(0);
memset(ps,0,sizeof(ps));
for (;;)
{
if (!WaitForDebugEvent(&d,INFINITE))
assert(0);
switch (d.dwDebugEventCode) /* 派发调试事件 */
{
HANDLE_EVENT(CREATE_PROCESS_DEBUG_EVENT,on_create_process);
HANDLE_EVENT(EXIT_PROCESS_DEBUG_EVENT,on_exit_process);
HANDLE_EVENT(CREATE_THREAD_DEBUG_EVENT,on_create_thread);
HANDLE_EVENT(EXIT_THREAD_DEBUG_EVENT,on_exit_thread);
HANDLE_EVENT(LOAD_DLL_DEBUG_EVENT,on_load_dll);
HANDLE_EVENT(UNLOAD_DLL_DEBUG_EVENT,on_unload_dll);
HANDLE_EVENT(EXCEPTION_DEBUG_EVENT,on_exception);
HANDLE_DEFAULT();
}
/* 继续程序执行 */
if (!ContinueDebugEvent(d.dwProcessId,d.dwThreadId,mark))
assert(0);
if (d.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT)
break;
}
}
/* event handlers */
void on_create_process()
{
process* p;
thread* t;
module* m;
CREATE_PROCESS_DEBUG_INFO* e;
e=&d.u.CreateProcessInfo;
p=alloc(ps,MAX_PROC); /* 产生进程对象 */
p->id=d.dwProcessId;
p->h=e->hProcess;
p->addr=e->lpStartAddress;
p->base=e->lpBaseOfImage;
t=alloc(p->ts,MAX_THRD); /* 产生进程里第一个线程对象 */
t->id=d.dwThreadId;
t->h=e->hThread;
t->addr=e->lpStartAddress;
m=alloc(p->ms,MAX_DLL); /* 产生进程里的程序模块对象 */
m->base=e->lpBaseOfImage;
m->h=e->hFile;
mark=DBG_CONTINUE;
#ifndef NO_TRACE
printf("process(%d) created\n",p->id);
printf("thread(%d) created\n",t->id);
printf("module loaded at 0x%08x\n",m->base);
#endif
}
void on_exit_process()
{
process* p;
int i;
p=find(ps,MAX_PROC,d.dwProcessId);
CloseHandle(p->h);
/* 结束调试前,把所有没有关闭的句柄关闭 */
for (i=0;i<MAX_THRD;i++)
{
if (p->ts[i].f)
{
CloseHandle(p->ts[i].h);
p->ts[i].f=FALSE;
}
}
for (i=0;i<MAX_DLL;i++)
{
if (p->ms[i].f)
{
CloseHandle(p->ms[i].h);
p->ms[i].f=FALSE;
}
}
mark=DBG_CONTINUE;
}
void on_create_thread()
{
process* p;
thread* t;
p=find(ps,MAX_PROC,d.dwProcessId);
t=alloc(ps->ts,MAX_THRD);
t->id=d.dwThreadId;
t->h=d.u.CreateThread.hThread;
t->addr=d.u.CreateThread.lpStartAddress;
mark=DBG_CONTINUE;
#ifndef NO_TRACE
printf("thread(%d) created\n",t->id);
#endif
}
void on_exit_thread()
{
process* p;
thread* t;
p=find(ps,MAX_PROC,d.dwProcessId);
t=find(ps->ts,MAX_THRD,d.dwThreadId);
CloseHandle(t->h);
t->f=FALSE;
mark=DBG_CONTINUE;
#ifndef NO_TRACE
printf("thread(%d) exit with code: %d\n",
t->id,d.u.ExitThread.dwExitCode);
#endif
}
void on_load_dll()
{
process* p;
module* m;
p=find(ps,MAX_PROC,d.dwProcessId);
m=alloc(ps->ms,MAX_DLL);
m->h=d.u.LoadDll.hFile;
m->base=d.u.LoadDll.lpBaseOfDll;
mark=DBG_CONTINUE;
#ifndef NO_TRACE
printf("module loaded at 0x%08x\n",m->base);
#endif
}
void on_unload_dll()
{
process* p;
module* m;
p=find(ps,MAX_PROC,d.dwProcessId);
m=find(ps->ms,MAX_DLL,(DWORD)d.u.UnloadDll.lpBaseOfDll);
CloseHandle(m->h);
m->f=FALSE;
mark=DBG_CONTINUE;
#ifndef NO_TRACE
printf("module unloaded at 0x%08x\n",m->base);
#endif
}
void on_exception()
{
process* p;
thread* t;
breakpoint* bp;
EXCEPTION_RECORD* r;
CONTEXT ctx;
BOOL f;
p=find(ps,MAX_PROC,d.dwProcessId); /* 找到发生异常的进程 */
if (!p->init_bp) /* 对初始断点特别关注 */
{
mark=DBG_CONTINUE;
p->init_bp=TRUE;
my_init(p->id);
}
else
{
mark=DBG_EXCEPTION_NOT_HANDLED;
r=&d.u.Exception.ExceptionRecord;
if (d.u.Exception.dwFirstChance) /* 异常是第一次触发吗?(详见第2篇) */
{
if (r->ExceptionCode==EXCEPTION_BREAKPOINT) /* 触发了断点中断 */
{
/* 根据中断地点查找断点记录 */
bp=find(ps->bps,MAX_BP,(DWORD)r->ExceptionAddress);
if (bp) /* 是调试器安置的断点 */
{
t=find(ps->ts,MAX_THRD,d.dwThreadId); /* 取线程上下文 */
memset(&ctx,0,sizeof(ctx));
ctx.ContextFlags=CONTEXT_FULL;
f=GetThreadContext(t->h,&ctx);
assert(f);
ctx.Eip--; /* 将EIP值减1 */
ctx.EFlags|=TF_BIT; /* 打开CPU单步标记 */
f=SetThreadContext(t->h,&ctx); /* 向目标写入修改过的上下文 */
assert(f);
ps->write_back=bp; /* 标记该进程在单步结束后准备写回的断点 */
safe_write(ps->h,bp->addr,bp->c); /* 写入被断点覆盖的代码 */
on_bp(d.dwProcessId,d.dwThreadId,bp->addr); /* 用户处理 */
/* 挂起除异常线程外的所有线程 */
suspend_except(ps->ts,d.dwThreadId);
mark=DBG_CONTINUE;
}
}
else if (r->ExceptionCode==EXCEPTION_SINGLE_STEP) /* 发生了单步中断 */
{
bp=ps->write_back; /* 获得准备写回的断点记录 */
if (bp) /* 如果bp==0则表示此单步中断不是调试器引起的 */
{
ps->write_back=NULL;
safe_write(ps->h,bp->addr,INT3); /* 写回断点 */
resume_except(ps->ts,d.dwThreadId); /* 恢复其它线程的运行 */
mark=DBG_CONTINUE;
}
}
}
else
{
assert(0); /* 如果运行到这里,表示目标程序出错了,咱不考虑…… */
}
}
}
/* 从数组中选(分配)一个未用的 */
void* _alloc(void* ar,size_t cb,size_t max)
{
size_t i;
for (i=0;i<max;i++,ar=(void*)((DWORD)ar+cb))
{
if (!*(BOOL*)ar)
{
*(BOOL*)ar=TRUE;
return ar;
}
}
return 0;
}
/* 从数组中查找id所对应的元素 */
void* _find(void* ar,size_t cb,size_t max,DWORD id)
{
size_t i;
for (i=0;i<max;i++,ar=(void*)((DWORD)ar+cb))
{
if (*(BOOL*)ar && *(DWORD*)((BOOL*)ar+1)==id)
return ar;
}
return 0;
}
/* 对所有ID不为id的线程调用func */
void thread_except(thread ts[],DWORD id,
DWORD (CALLBACK* func)(HANDLE))
{
int i;
for (i=0;i<MAX_THRD;i++)
{
if (ts[i].f && ts[i].id!=id)
func(ts[i].h);
}
}
void safe_read(HANDLE p,DWORD addr,BYTE* c)
{
DWORD r,old;
VirtualProtectEx(p,(LPVOID)addr,1,PAGE_READWRITE,&old);
ReadProcessMemory(p,(LPVOID)addr,c,1,&r);
VirtualProtectEx(p,(LPVOID)addr,1,old,&old);
assert(r==1);
}
void safe_write(HANDLE p,DWORD addr,BYTE c)
{
DWORD r,old;
VirtualProtectEx(p,(LPVOID)addr,1,PAGE_READWRITE,&old);
WriteProcessMemory(p,(LPVOID)addr,&c,1,&r);
VirtualProtectEx(p,(LPVOID)addr,1,old,&old);
assert(r==1);
}
/* 设置断点 */
void set_bp(DWORD pid,DWORD addr)
{
process* p;
breakpoint* bp;
p=find(ps,MAX_PROC,pid);
bp=alloc(ps->bps,MAX_BP);
bp->addr=addr;
safe_read(p->h,addr,&bp->c);
safe_write(p->h,addr,INT3);
}
/* main */
int main(int argc,char* argv[])
{
DWORD id;
/* 根据任务栏的窗口类名称查找explorer.exe的进程ID */
GetWindowThreadProcessId(FindWindow(TASKBAR_CLASS,""),&id);
printf("found explorer PID: %d\n",id);
debug(id);
return 0;
}
void my_init(DWORD pid)
{
/* 在CreateProcessW处设置断点。
注意:KERNEL32.dll模块的基地址是不变的,
所以调试器进程和目标进程的CreateProcessW地址一样*/
set_bp(pid,(DWORD)GetProcAddress(
GetModuleHandle("kernel32.dll"),"CreateProcessW"));
}
/* 从目标程序的地址空间里读一个Unicode字符串 */
void read_string(HANDLE h,DWORD addr,char* s,int max)
{
char* p;
int i;
p=(char*)malloc(sizeof(char)*max*2);
assert(p);
for (i=0;i<max-1;i++)
{
safe_read(h,addr++,p+2*i);
safe_read(h,addr++,p+2*i+1);
}
p[2*i]=p[2*i+1]=0;
WideCharToMultiByte(CP_ACP,0, /* 转换成ANSI字符串 */
(LPCWSTR)p,-1,s,max,NULL,NULL);
free(p);
}
/* 代表断点触发时的用户处理 */
void on_bp(DWORD pid,DWORD tid,DWORD addr)
{
char s[256];
process* p;
thread* t;
CONTEXT ctx;
DWORD a,r;
p=find(ps,MAX_PROC,pid); /* 根据进程ID查找进程 */
t=find(p->ts,MAX_THRD,tid); /* 根据线程ID查找触发断点的线程 */
memset(&ctx,0,sizeof(ctx));
ctx.ContextFlags=CONTEXT_FULL;
GetThreadContext(t->h,&ctx); /* 获得线程上下文 */
a=ctx.Esp+8; /* 取CreateProcessW的第二个参数 */
ReadProcessMemory(ps->h, /* 间址一次从栈里得到字符串首地址 */
(LPVOID)a,&a,4,&r);
assert(r==4);
read_string(ps->h,a,s,256); /* 根据参数读出字符串 */
printf("executing %s\n",s);
}