标 题: 调试器设计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
待续……
- 标 题:调试器设计3
- 作 者:Tweek
- 时 间:2010-03-01 15:48:59
- 链 接:http://bbs.pediy.com/showthread.php?t=108011