首页
社区
课程
招聘
[原创]调试器设计3
发表于: 2010-3-1 15:48 8655

[原创]调试器设计3

2010-3-1 15:48
8655

标 题: 调试器设计3
作 者: Tweek
时 间: 2010-3-1

最后一篇知识性介绍是关于调试API的内部实现的简单分析。

这样可以帮助我们进一步深入认识被调试程序。

调试API的内部实现原理分析:

我们先看看DebugActiveProcess内部结构
BOOL
WINAPI
DebugActiveProcess(IN DWORD dwProcessId)
{
NTSTATUS Status;
HANDLE Handle;
/* Connect to the debugger */
Status = DbgUiConnectToDbg();
if (!NT_SUCCESS(Status))
{
SetLastErrorByStatus(Status);
return FALSE;
}
/* Get the process handle */
Handle = ProcessIdToHandle(dwProcessId);
if (!Handle) return FALSE;
/* Now debug the process */
Status = DbgUiDebugActiveProcess(Handle);
NtClose(Handle);
/* Check if debugging worked */
if (!NT_SUCCESS(Status))
{
/* Fail */
SetLastErrorByStatus(Status);
return FALSE;
}
/* Success */
return TRUE;
}

简单的初始化,然后调用DbgUiDebugActiveProcess。

关于DbgUiDebugActiveProcess的功能嘛。内核需要和进程之间交流调试相关的信息。Xp后,调试部不再利用LPC,而是直接为调试建立Debug object。所以它至少建立了一个debug object。我们可以用NtQueryInformationXXX获取debug object的信息。

有了一些初步的认识,让我们再看看WaitForDebugEvent。

BOOL
WINAPI
WaitForDebugEvent(IN LPDEBUG_EVENT lpDebugEvent,
IN DWORD dwMilliseconds)
{
LARGE_INTEGER WaitTime;
PLARGE_INTEGER Timeout;
DBGUI_WAIT_STATE_CHANGE WaitStateChange;
NTSTATUS Status;
/* Check if this is an infinite wait */
if (dwMilliseconds == INFINITE)
{
/* Under NT, this means no timer argument */
Timeout = NULL;
}
else
{
/* Otherwise, convert the time to NT Format */
WaitTime.QuadPart = UInt32x32To64(-10000, dwMilliseconds);
Timeout = &WaitTime;
}
/* Loop while we keep getting interrupted */
do
{
/* Call the native API */
Status = DbgUiWaitStateChange(&WaitStateChange, Timeout);
} while ((Status == STATUS_ALERTED) || (Status == STATUS_USER_APC));
/* Check if the wait failed */
if (!(NT_SUCCESS(Status)) || (Status != DBG_UNABLE_TO_PROVIDE_HANDLE))
{
/* Set the error code and quit */
SetLastErrorByStatus(Status);
return FALSE;
}
/* Check if we timed out */
if (Status == STATUS_TIMEOUT)
{
/* Fail with a timeout error */
SetLastError(ERROR_SEM_TIMEOUT);
return FALSE;
}
/* Convert the structure */
Status = DbgUiConvertStateChangeStructure(&WaitStateChange, lpDebugEvent);
if (!NT_SUCCESS(Status))
{
/* Set the error code and quit */
SetLastErrorByStatus(Status);
return FALSE;
}
/* Check what kind of event this was */
switch (lpDebugEvent->dwDebugEventCode)
{
/* New thread was created */
case CREATE_THREAD_DEBUG_EVENT:
/* Setup the thread data */
SaveThreadHandle(lpDebugEvent->dwProcessId,
lpDebugEvent->dwThreadId,
lpDebugEvent->u.CreateThread.hThread);
break;
/* New process was created */
case CREATE_PROCESS_DEBUG_EVENT:
/* Setup the process data */
SaveProcessHandle(lpDebugEvent->dwProcessId,
lpDebugEvent->u.CreateProcessInfo.hProcess);
/* Setup the thread data */
SaveThreadHandle(lpDebugEvent->dwProcessId,
lpDebugEvent->dwThreadId,
lpDebugEvent->u.CreateThread.hThread);
break;
/* Process was exited */
case EXIT_PROCESS_DEBUG_EVENT:
/* Mark the thread data as such */
MarkProcessHandle(lpDebugEvent->dwProcessId);
break;
/* Thread was exited */
case EXIT_THREAD_DEBUG_EVENT:
/* Mark the thread data */
MarkThreadHandle(lpDebugEvent->dwThreadId);
break;
/* Nothing to do for anything else */
default:
break;
}
/* Return success */
return TRUE;
}

DbgUiWaitStateChange 检测debug object,看看是否有调试事件返回。

DbgUiConvertStateChange作用是为了版本的兼容,将以前系统的结构转换成现在可以识别的结构。

到了下面的switch了,里面的东西我们似乎可以通过名字看懂。但是,我们看看它的细节不是更好

VOID
WINAPI
SaveProcessHandle(IN DWORD dwProcessId,
IN HANDLE hProcess)
{
PDBGSS_THREAD_DATA ThreadData;
/* Allocate a thread structure */
ThreadData = RtlAllocateHeap(RtlGetProcessHeap(),
0,
sizeof(DBGSS_THREAD_DATA));
if (!ThreadData) return;
/* Fill it out */
ThreadData->ProcessHandle = hProcess;
ThreadData->ProcessId = dwProcessId;
ThreadData->ThreadId = 0;
ThreadData->ThreadHandle = NULL;
ThreadData->HandleMarked = FALSE;
/* Link it */
ThreadData->Next = DbgSsGetThreadData();
DbgSsSetThreadData(ThreadData);
}

出现了DBGSS_THREAD_DATA这个结构体。将当前的DBGSS_THREAD_DATA添加到原来的链表(暂且让我说他是链表)里面。然后将新的设置成链表表头。

再看看DBGSS_THREAD_DATA的定义

typedef struct _DBGSS_THREAD_DATA
{
struct _DBGSS_THREAD_DATA *Next;
HANDLE ThreadHandle;
HANDLE ProcessHandle;
DWORD ProcessId;
DWORD ThreadId;
BOOLEAN HandleMarked;
} DBGSS_THREAD_DATA, *PDBGSS_THREAD_DATA;

嗯,里面有进程和线程的句柄和ID。还有一个句柄的标记。应该是MarkProcess/ThreadHandle返回的。判断是否已经退出。

saveProcessHandle里面还有个这个
DbgSsSet/GetThreadData

让我们来看看他的实现

#define DbgSsSetThreadData(d) \
NtCurrentTeb()->DbgSsReserved[0] = d
#define DbgSsGetThreadData() \
((PDBGSS_THREAD_DATA)NtCurrentTeb()->DbgSsReserved[0])

原来是将TEB中的DbgSsReserved的第一个元素被赋予了ThreadData.

关于SaveThreadHandle的实现:

VOID
WINAPI
SaveThreadHandle(IN DWORD dwProcessId,
IN DWORD dwThreadId,
IN HANDLE hThread)
{
PDBGSS_THREAD_DATA ThreadData;
/* Allocate a thread structure */
ThreadData = RtlAllocateHeap(RtlGetProcessHeap(),
0,
sizeof(DBGSS_THREAD_DATA));
if (!ThreadData) return;
/* Fill it out */
ThreadData->ThreadHandle = hThread;
ThreadData->ProcessId = dwProcessId;
ThreadData->ThreadId = dwThreadId;
ThreadData->ProcessHandle = NULL;
ThreadData->HandleMarked = FALSE;
/* Link it */
ThreadData->Next = DbgSsGetThreadData();
DbgSsSetThreadData(ThreadData);
}

正如你所想。没有什么大的变化。

最后我们看看ContinueDebugEvent

BOOL
WINAPI
ContinueDebugEvent(IN DWORD dwProcessId,
IN DWORD dwThreadId,
IN DWORD dwContinueStatus)
{
CLIENT_ID ClientId;
NTSTATUS Status;
/* Set the Client ID */
ClientId.UniqueProcess = (HANDLE)dwProcessId;
ClientId.UniqueThread = (HANDLE)dwThreadId;
/* Continue debugging */
Status = DbgUiContinue(&ClientId, dwContinueStatus);
if (!NT_SUCCESS(Status))
{
/* Fail */
SetLastErrorByStatus(Status);
return FALSE;
}
/* Remove the process/thread handles */
RemoveHandles(dwProcessId, dwThreadId);
/* Success */
return TRUE;
}

噢,DbgUiContinue已经做了所有的事了。

最后,我们还是看看MS都为我们干了些什么:

首先,调试器访问在ntdll里面的接口DbgUi,然后,DbgUi通过dbgk建立了一个debug object(当然我们可以用NtQueryInformationXXX或者NtSetInformationXXX查看和修改它,修改只能针对部分位置。)然后,dbgk通过DBGSS_THREAD_DATA 将调试事件依次保存在一个”链表”里面。然后,teb的DbgSsReserved的第一个元素被赋予了那个链表。而DbgSsReserved的第二个元素则是指向调试事件。所以,我们可以IsDebugergPresent通过检测TEB,判断是否被调试,同时,我们解释了第二篇的那个例子程序里面为什么会在 不断的弹出大量框。而利用NtQueryInformationXXX和DebugPort则必须利用驱动程序内核完成。因为他们是查询的内核的object。

大家一定意识到debug object在调试中处于一个多么重要的地位。

那么,我们就来看看他到底是什么?

typedef struct _DEBUG_OBJECT
{
KEVENT EventsPresent;
FAST_MUTEX Mutex;
LIST_ENTRY EventList;
union
{
ULONG Flags;
struct
{
UCHAR DebuggerInactive:1;
UCHAR KillProcessOnExit:1;
};
};
} DEBUG_OBJECT, *PDEBUG_OBJECT;
没有想象的那么复杂吧。
LIST_ENTRY一个调试事件的列表。
Flag和调试会话相联系
其他的看名字就知道什么意思了吧!

到此,我们设计调试器需要的知识储备已经差不多了。一般性的问题,我们都可以给出解释了。
对调试API也有了比较深入的认识。

上面三篇介绍了一些关于调试器紧密相关的信息,都是介绍比较基本的。
如果希望更深入的了解相关信息。
可以:……
进程更加深入的信息 请参考windows核心编程
调试API更深入的细节。大家可以参考dbgk的介绍。

同时,xp 增加了一些调试API,虽然他们内部实现没有本质变化。
但是,却给调试器的设计给与了更多选择:
DebugActiveProcessStop,
DebugSetProcessKillOnExit,
DebugBreakProcess,
CheckRemoteDebuggerPresent.

具体使用方法请参考msdn

待续……


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 7
支持
分享
最新回复 (9)
雪    币: 2362
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
继续沙发 支持
2010-3-1 18:08
0
雪    币: 2362
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
就要开始设计调试器了
2010-3-1 18:34
0
雪    币: 2067
活跃值: (82)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
4
先取个名字吧. 有名字才不会半途而x
2010-3-1 19:02
0
雪    币: 2362
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
早就有了  zDBG
什么ZapPack  ZapProtect界面都画好了
就等大哥教我写代码啊
2010-3-1 19:12
0
雪    币: 615
活跃值: (1267)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
实际的开发其实很困难!
2010-3-1 19:20
0
雪    币: 2067
活跃值: (82)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
7
代码可以花钱请人写
我们只要出点子' 金子' 名字
2010-3-1 19:30
0
雪    币: 2362
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
我出名字 其它你出好不好
2010-3-1 20:17
0
雪    币: 13256
活跃值: (4252)
能力值: ( LV15,RANK:1673 )
在线值:
发帖
回帖
粉丝
9
有道理哦...取名先...
2010-3-1 21:00
0
雪    币: 169
活跃值: (52)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
叫ONLYDBG吧....
2010-3-2 11:39
0
游客
登录 | 注册 方可回帖
返回
//