分类 调试 下的文章

我最近处理的一个棘手的死锁问题开始。让我先解释一下表面症状,然后我们就可以找到好东西了。当运行Vista的用户尝试启动internetexplorer时,任何事情都不会发生。所以他们再次双击图标,还是什么也没发生。所以用户会重新启动并返回他们的网络活动。
经过一些基本的故障排除后发现Iexplore.exe正在运行多次,每次尝试打开浏览器都会导致另一个Iexplore.exe实例。因为没有用户界面,所以我继续收集了一些Iexplore.exe进程转储以及完整的系统转储。
进程转储没有被证明是非常有用的,所以我决定转到完整的内存转储。
因为我知道我们有多个IExplore.exe运行的进程,我想先调查一下。

0: kd>!process 0 0

<snip>PROCESS 87289d90 SessionId:5 Cid: 0fdc Peb: 7ffdc000 ParentCid: 0968DirBase: 7d66c520 ObjectTable: a203a178 HandleCount:205.

Image: iexplore.exe

PROCESS 861a9d90 SessionId:
5Cid: 0d34 Peb: 7ffd9000 ParentCid: 0fdc

DirBase: 7d66c7a0 ObjectTable: a5651a98 HandleCount:
170.

Image: ieuser.exe

PROCESS 86c175b0 SessionId:
5 Cid: 1250 Peb: 7ffd9000 ParentCid: 0968DirBase: 7d66c940 ObjectTable: a0853f10 HandleCount:91.

Image: iexplore.exe

PROCESS 861ac7a8 SessionId:
5 Cid: 1024 Peb: 7ffde000 ParentCid: 0968DirBase: 7d66c620 ObjectTable: a83f7898 HandleCount:91.

Image: iexplore.exe
</snip>

WOW64(Windows-On-Windows 64bit)是X64 Windows操作系统的一个子系统,为32位应用程序提供运行环境。类似的还有WOW32子系统,负责在32位Windows系统上运行16位应用程序。

WoW64存在的原因还要从CPU的发展上开始说,X86指令集是一个指令集架构家族,最初在Intel 8086处理器中引入,开始它主要用于16位系统。从Intel 386处理器发布开始升级为32位,并且32位指令集一直保持了很久。了解32位系统的都知道32位CPU将内存空间限制到了4G(单一用户模式进程至少是这样)。随着RAM的越来越大,4G限制就成了瓶颈,系统无法使用更大的内存空间。于是2001年Intel发布了64位的IA64架构,它是一个全新的架构,架构设计非常清晰,比老的X86架构要更好。对于软件来说兼容性很重要,但是IA64处理器无法运行X86代码,这样问题就很严重了,已有的软件无法在新的CPU上运行。于是在2003年AMD发布了AMD64架构,它是对X86架构的增量更新,用于添加64位支持。这种架构的X64处理器可以执行X86代码,所以用户可以在X64处理器上运行现有的程序和操作系统。直到今天,X86和X64依旧是个人计算机和笔记本电脑使用CPU的主流。

如下为X64和X86指令,与X86指令相对应,X64指令需要增加额外的前缀字节(REX Prefix)表示使用64位寄存器。由于指针等数据大小翻倍,所以结构体中指针偏移大小也可能会增加。

1
2
3
4
5
// X86指令
0x8B, 0x52, 0x0C, // mov edx, dword ptr [edx+0Ch]

// X64指令
0x48, 0x8B, 0x52, 0x18, // mov rdx, qword ptr [rdx+18h]

一些指令在X86和X64上编码一致,比如短跳转。区分两种指令比较通用的方法是看指令是否携带了REX前缀字节(REX前缀字节用于表示使用64位寄存器或使用64位操作数)。REX前缀字节会覆盖一部分现存X86指令,因此在执行一块代码时需要告知X64处理器按照X86还是X64来解析指令。到底是怎么告知CPU要将代码按照X86解析还是按照X64解析呢?下面看Intel的白皮书给出的关于IA-32e如何区分兼容模式和64位模式。


图1.64位模式中的代码段描述符

简介

我最近研究了一个问题,Winlogon中两个线程的交互导致错误检查。一个线程是初始化GDI的Winlogon线程。这个场景的有趣之处在于另一个线程是如何在这个进程中结束的。

线程在干什么?

下面是线程堆栈的用户一半。线程试图加载DLL

ChildEBP RetAddr Args to Child

0058eaec 773901ad 773901d9 0058eafc
00240022 ntdll!KiFastSystemCallRet

0058eb0c 775d96f3 775d1808
00000000 77e6f032 USER32!NtUserRegisterWindowMessage+0xc0058ed24 775e475500000000 00000001 7c837512 comctl32!InitGlobalMetrics+0x440058ed3c 775e426a00000031 0058ed68 7763490c comctl32!_ProcessAttach+0x980058ed48 7763490c 775d000000000001 00000000 comctl32!DllMain+0x210058ed68 7c81a352 775d000000000001 00000000 comctl32!_DllMainCRTStartup+0x520058ed88 7c833465 776348ba 775d000000000001 ntdll!LdrpCallInitRoutine+0x140058ee90 7c83431100000000 00000000 7c8e2e58 ntdll!LdrpRunInitializeRoutines+0x3670058f124 7c83406500000000 00080e98 0058f3ec ntdll!LdrpLoadDll+0x3cd0058f3a0 77e41bf3 00080e98 0058f3ec 0058f3cc ntdll!LdrLoadDll+0x1980058f408 77e5c70b 7c8e2e5800000000 00000000 kernel32!LoadLibraryExW+0x1b20058f41c 7c92a6a1 7c8e2e5800000000 7c8e2e58 kernel32!LoadLibraryW+0x110058f454 7c92a65f 7c8e2e58 7c8d0000 7c9297b6 SHELL32!SHFusionLoadLibrary+0x2a0058f460 7c9297b600000020 00000008 0058f6a8 SHELL32!DelayLoadCC+0x150058f694 7c929728 0058f6a8 0000007c00000001 SHELL32!SHFusionInitializeIDCC+0x920058f8b4 7c92966f 7c8d0000 0000007c00000001 SHELL32!SHFusionInitializeFromModuleID+0x3a0058f8c8 7c92962c 7c8d000000000001 0058f8f8 SHELL32!_ProcessAttach+0x340058f8d8 7c92bb63 7c8d000000000001 00000000 SHELL32!DllMain+0x270058f8f8 7c81a352 7c8d000000000001 00000000 SHELL32!_DllMainCRTStartup+0x520058f918 7c833465 7c92bb1b 7c8d000000000001 ntdll!LdrpCallInitRoutine+0x140058fa20 7c83431100000000 00000000 00000004 ntdll!LdrpRunInitializeRoutines+0x367

因为CLR是一个托管环境,所以运行时中有几个组件需要在执行任何代码之前初始化。本文将介绍EE(执行引擎)启动程序,并详细检查初始化过程。68只是一个粗略的指南,它取决于您使用的运行时版本、启用了哪些功能以及其他一些东西。

样例代码

假设你有一个最简单的C#程序,在CLR将“Hello World”输出到控制台之前会发生什么?

usingSystem;namespaceConsoleApplication
{
public classProgram
{
public static void Main(string[] args)
{
Console.WriteLine(
"Hello World!");
}
}
}

说到现场调试,断点是最重要的。通常,在生产环境中解决一个非常复杂的问题需要在本地、非生产环境中调试我自己的一台测试机器。我通常会调试有问题的进程或代码,以便更好地了解它是如何工作的,以及在我进入时需要收集哪些数据。这通常涉及一些逆向工程,并对相关流程或模块进行代码审查。我们总是希望在进行实时调试时尽量减少对生产环境的影响。我称之为设计完美的断点。

我们通常使用几种类型的断点。

最常见的断点是bp,在windbg中设置它很简单:bp 0xaddress。请注意,当您进入用户模式进程时,它会停止进程的执行。当您进入内核调试器时,它会停止整个机器。在这种情况下,我们想知道一旦断点命中,我们将发出什么命令。然后,可以设置断点,以便在插入时自动发出命令。

在遇到断点时运行命令的语法很简单:bp 0xaddress “comand1;command2;…”。例如-bp 0xMYADDRESS “kv;dd g_myGlobal;g” 此命令将在0xMYADDRESS上中断,通过kv转储调用堆栈;将内存转储为从g_MyGlobal地址开始的dword;然后使用g命令继续执行。