分类 调试 下的文章

Visual Studio是一个很大的工具。很容易遗漏一些有用的特性。希望XSLT调试器不会错过。在下面,您可以看到调试器正在运行,因为xslt转换正在应用于某些XML。(请原谅我在示例中使用的毫无意义的XSLT。我有很长一段时间没有机会使用XSLT了。)

 

XSLT调试对我来说很有趣,因为它大部分是托管调试器。XSLT编译为msil,托管调试器用于提供断点和单步执行等功能。打开“模块”窗口可以看到这一点。在最后一列中,您将看到该进程是一个名为Microsoft.XSLDebugProxy.exe的托管进程,并且将有一些模块加载名称,如System.Xml.Xsl.CompiledQuery.1。所有调试器窗口都按预期工作:callstack、locals、immediate、threads、modules。您可以在下面的调用堆栈中看到我的无意义示例:

 

 然而!转到callstack窗口并右键单击以获取上下文菜单。将有一个名为Show External Code的项。选择这个选项,您可以在之前隐藏的callstack窗口中看到很多内容。灰色的帧是因为该程序集没有调试信息(pdb文件),这就是为什么这些帧以前被标记为“外部代码”的原因。已编译的查询帧被隐藏,因为XSLT编译器在它们上放置了一个属性,告诉调试器它们不感兴趣。使用扩展视图,您现在可以看到已编译的查询和调度它的代码。

 

然而,尽管这很有趣,但它不太可能帮助您了解XSLT中的错误。但是,“局部”窗口似乎是更有用的窗口之一,它可以帮助您查看应用变换时发生的情况。

我们将讨论调试器扩展,这些扩展允许您查看与LPC相关的问题。

LPC Kernel Debugger Extensions

Command

Description

!lpc

Display the list and description of all the !lpc commands

!lpc message [MessageId]

Display the message with a given ID and all related information by attempting to match the given Message ID to the EHTREAD->LpcReceivedMessageId and to the ETHREAD->LpcReplyMessageId of all threads in the system.

If the MessageId parameter is omitted then it attempts to display all the outstanding messages in the system by searching for the tag ‘LpcM’ in the pools.

!lpc port [PortAddress]

Displays port information. If a server connection port address is specified then only information about that port is displayed. If either a client or server communication port is specified it prints information about the specified communication port, the peer communication port and the server connection port.

If the PortAddress parameter is omitted then it attempts to walk the list of all objects of type “Port” and “WaitablePort” and display them. Note that for this feature to work the GFlags option “+otl” i.e. “Maintain a list of objects for each type” must be enabled.

!lpc scan PortAddress

Displays port information. It attempts to walk the list of all objects of type “Port” and “WaitablePort” and display the one matching the specified port address. Note that for this feature to work the GFlags option “+otl” i.e. “Maintain a list of objects for each type” must be enabled.

!lpc thread [ThreadAddr]

If ThreadAddr is specified it walks the list of threads in the ETHREAD-> LpcReplyChain to locate the list head i.e. a “Port” or “WaitablePort” object on which the thread is waiting for a reply.

If the ThreadAddr parameter is omitted then it attempts to find all LPC server threads by looking for threads with a non-NULL EHTREAD->LpcReceivedMessageId and all client threads by looking for threads with a non-NULL ETHREAD->LpcReplyMessageId and displays them.

!lpc PoolSearch

Toggles a setting that controls whether the “lpc message” command will search for LPC message tag (‘LpcM’) in the kernel pools or not.

LPC Kernel Debugger Extension Usage

来自调用堆栈的LPC连接端口信息
在LPC数据传输或LPC连接上阻塞的任何客户端或服务器线程的调用堆栈上,将有一个包含函数NtRequestWaitReplyPort()或NtReplyWaitReceivePortEx()之一的帧。其中任何一个函数的第一个参数是它们被阻塞的端口的句柄。

kd> !thread 810de2a8

THREAD 810de2a8 Cid 01dc.01f4 Teb: 7ffde000 Win32Thread:
00000000 WAIT: (WrLpcReceive) UserMode Non-Alertable81131188 Semaphore Limit 0x7fffffff810de398 NotificationTimer

Not impersonating

DeviceMap e196c460

Owning Process 810ddda0 Image: rpclpcs.exe

Wait Start TickCount
64666666 Ticks: 402 (0:00:00:04.025)

Context Switch Count
2UserTime00:00:00.000KernelTime00:00:00.000Win32 Start Address0x77e76bf0Start Address0x7c810856Stack Init f8e28000 Current f8e27c4c Base f8e28000 Limit f8e25000 Call0Priority8 BasePriority 8 PriorityDecrement 0 DecrementCount 0ChildEBP RetAddr Args to Child

f8e27c64 804dc6a6 810de318 810de2a8 804dc6f2 nt
!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])

f8e27c70 804dc6f2 e1084108 8055a540 e1084108 nt
!KiSwapThread+0x46 (FPO: [0,0,0])

f8e27c98 8056a50a
00000001 00000010 00000001 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])

f8e27d48 804df06b 000007c4 002bff70
00000000 nt!NtReplyWaitReceivePortEx+0x3dc (FPO: [Non-Fpo])

f8e27d48 7c90eb94 000007c4 002bff70
00000000 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f8e27d64)

002bff80
00000000 00000000 00000000 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])

kd
> !handle 7c4 3810ddda0

processor number
0, process 810ddda0

PROCESS 810ddda0 SessionId:
0Cid: 01dc Peb: 7ffd9000 ParentCid: 01b4

DirBase: 058cd000 ObjectTable: e1a13278 HandleCount:
18.

Image: rpclpcs.exe

Handle table at e107d000 with
18 Entries inuse

07c4: Object: e1084108 GrantedAccess: 001f0001 Entry: e107df88

Object: e1084108 Type: (812b5c80) Port

ObjectHeader: e10840f0 (old version)

HandleCount:
1 PointerCount: 4Directory Object: e14c72c8 Name: rpclpc

kd
> !lpc port e1084108

Server connection port e1084108 Name: rpclpc

Handles:
1 References: 4Server process : 810ddda0 (rpclpcs.exe)

Queue semaphore :
81131188Semaphore state0 (0x0)

The message queue is empty

The LpcDataInfoChainHead queue is empty

LPC Messages that are waiting to be picked up by the server thread

我想我不需要强调在调试时拥有有效的PDB文件有多重要。通常,PDB文件是由调试器静默加载的,并且您很高兴在modules窗口中看到解析的所有符号。不幸的是,您还可能遇到调试器找不到匹配符号的情况。其原因可能与断开的互联网连接或更复杂的签名不匹配一样微不足道。在本文中,我将向您展示如何在调试之前检查符号文件,以及如何从中提取源文件信息。由于有不同的方法(和工具)来操作符号文件,我将介绍我所知道的那些。

下载给定PE文件的PDB文件

由于PDB文件格式是微软的秘密,所以我介绍的所有工具都只是API的包装器。要处理PDB文件,我们首先需要获取它们。让我们列出能帮助我们的工具。

symchk.exe

符号检查器(Symchk.exe)是一个应用程序,它将可执行文件与符号文件进行比较,以验证匹配的符号是否可用。Symchk也可以用于填充符号缓存。它可以从PE文件(exe,dll)、转储文件和进程中读取符号信息。它还支持递归目录搜索和批处理文件。

我们将从加载kernel32.dll库的符号开始:


c:\Windows\System32>echo %_NT_SYMBOL_PATH%

什么是LPC

LPC(Local-Process-communicationandnotlocalprocedure-Calls)是一种在NT内核中实现的基于消息的高速通信机制。LPC可用于两个用户模式进程之间、用户模式进程与内核模式驱动程序之间或两个内核模式驱动程序之间的通信。一个例子是通过LPC通信的两个用户模式进程。像CSRSS.exe与SMSS.exe通信,在创建登录会话或任何与LSASS.exe。出于安全原因,通过lsa认证端口。用户模式进程与内核模式驱动程序通信的另一个例子是 KSecDD.sys司与LSASS.exe通信用于在读/写加密文件期间对EFS密钥进行加密和解密。
LPC使用两种不同的机制在客户端和服务器进程之间传递数据。它使用LPC消息缓冲区(对于小于304字节的数据),或者使用映射到客户机和服务器地址空间的共享内存部分(对于大于304字节的数据)。
除了用作在同一系统上运行的进程之间选择远程过程调用的协议外,LPC还用于整个系统,例如用于Win32应用程序与CSRSS.exe文件,安全参考监视器与LSASS的通信,WinLogon与LSASS的通信等。
LPC强制客户机进程和服务器进程之间的同步通信模型。Vista反对使用一种称为异步本地进程间通信(ALPC)的新机制来使用LPC。与LPC相比,ALPC有一个固有的优势,即从客户端到服务器的所有调用都是异步的,即客户端不需要阻塞/等待服务器响应消息。在Vista中,对lpcapis的旧应用程序调用会自动重定向到更新的alpcapi。

LPC APIs

LPC api是本机api,即它们在用户模式下通过NTDLL.dll在内核模式下NTOSKRNL.exe文件. lpcapis没有在Win32级别公开,因此Win32应用程序不能直接使用LPC工具。然而,在使用RPC时,Win32应用程序可以通过协议序列“ncalrpc”将LPC指定为其底层传输,从而间接地使用LPC。所有lpcapis都以单词“Port”结尾,这意味着LPC通信端点。

API

Description

NtCreatePort

Used by server to create a connection port

NtConnectPort

Used by client to connect to a connection port

NtListenPort

Used by server to listen for connection requests on the connection port.

NtAcceptConnectPort

Used by server to accept connection requests on the connection port

NtCompleteConnectPort

Used by server to complete the acceptance of a connection request

NtRequestPort

Used to send a datagram message that does not have a reply

NtRequestWaitReplyPort

Used to send a message and wait for a reply

NtReplyPort

Used to send a reply to a particular message

NtReplyWaitReplyPort

Used to send a reply to a particular message and wait for a reply to a previous message

NtReplyWaitReceivePort

Used by server to send a reply to the client and wait to receive a message from the client

NtImpersonateClientOfPort

Used by server thread to temporarily borrow the security context of a client thread

下图说明了LPC服务器进程侦听来自潜在客户端的连接请求所采取的步骤,以及客户端连接到侦听服务器所采取的步骤。

 

 

                              LPC客户机-服务器连接建立顺序

注意:许多服务器进程使用NtReplyWaitReceivePort()API而不是NtListenPort()。NtListenPort()除去连接请求之外的所有LPC消息。因此NtListenPort()只能用于第一个连接。对于以后的连接请求,使用NtReplyWaitReceivePort()。

下图说明了LPC客户机向已建立连接的LPC服务器发送请求所采取的步骤,以及服务器响应消息所采取的步骤。

 

 

                            客户端-服务器数据传输序列

 

LPC Data Structures

LPC Port Data Structure

LPC Port被称为端口。LPC实现使用相同的端口结构来表示各种类型的端口。LPC使用的端口是服务器连接端口,这些端口是由服务器进程创建的用于接受来自客户端的传入连接的命名端口。客户机通信端口由客户机进程创建以连接到服务器进程和服务器进程创建的服务器通信端口。

 

 

                   LPC端口类型及其关系

LPCP_PORT_OBJECT是LPC用来表示LPC端口的内部数据结构。LPCP_PORT_对象是从带有标记“PORT”的分页池中分配的。

kd> dt nt!_LPCP_PORT_OBJECT+0x000ConnectionPort : Ptr32 _LPCP_PORT_OBJECT+0x004ConnectedPort : Ptr32 _LPCP_PORT_OBJECT+0x008MsgQueue : _LPCP_PORT_QUEUE+0x018Creator : _CLIENT_ID+0x020ClientSectionBase : Ptr32 Void+0x024ServerSectionBase : Ptr32 Void+0x028PortContext : Ptr32 Void+0x02cClientThread : Ptr32 _ETHREAD+0x030SecurityQos : _SECURITY_QUALITY_OF_SERVICE+0x03cStaticSecurity : _SECURITY_CLIENT_CONTEXT+0x078LpcReplyChainHead : _LIST_ENTRY+0x080LpcDataInfoChainHead : _LIST_ENTRY+0x088ServerProcess : Ptr32 _EPROCESS+0x088MappingProcess : Ptr32 _EPROCESS+0x08cMaxMessageLength : Uint2B+0x08eMaxConnectionInfoLength : Uint2B+0x090Flags : Uint4B+0x094 WaitEvent : _KEVENT

我最近花了一些时间分析OutputDebugString方法。在我的另一个实验中,我需要一个仅依赖于本机API的OutputDebugString版本。在实现它的过程中,我发现了一些关于OutputDebugString的有趣的事实,也许您也会感兴趣。

OutputDebugString的工作原理

简而言之,OutputDebugString尝试将消息发送到附加到给定进程的调试器,如果没有调试器侦听,则尝试将全局节映射到进程内存中并将调试消息保存在其中。我使用本机API实现OutputDebugStringA(ANSI版本)的示例如下:

voidNTAPI RtlOutputDebugStringA(_In_opt_ LPCSTR OutputString) {if(OutputString) {
EXCEPTION_RECORD exceptionRecord{
0};

exceptionRecord.ExceptionCode
=DBG_PRINTEXCEPTION_C;
exceptionRecord.NumberParameters
= 2;
exceptionRecord.ExceptionInformation[
0] = strlen(OutputString) + 1;
exceptionRecord.ExceptionInformation[
1] = reinterpret_cast<ULONG_PTR>(OutputString);

__try {
RtlRaiseException(
&exceptionRecord);
} __except (EXCEPTION_EXECUTE_HANDLER) {
NotifyGlobalDebugOutputMonitor(OutputString);
}
}
}