- UID
- 2
- 精华
- 积分
- 7736
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
楼主 |
发表于 2014-7-3 21:16:39
|
显示全部楼层
逻辑死锁
死锁也可以在只有一个锁或者无锁的情况下发生。这是由于windows线程不仅在要获得已经被其他线程占用的锁时会发生阻塞,在等待线程/进程退出事件、异步I/O完成事件、或者显式调用通知内核事件对象(SetEvent)等逻辑事件发生的时候,也会发生死锁。下面是实例:
dll代码
- [/color][/size]
- #include <stdio.h>
- #include <windows.h>
- DWORD WINAPI WorkerThread(LPVOID pParam)
- {
- wprintf(L"Inside WorkerThread.\n");
- return 0;
- }
- BOOL APIENTRY DllMain( HANDLE hInstance,
- DWORD dwReason,
- LPVOID lpReserved
- )
- {
- HANDLE shThread;
- switch(dwReason)
- {
- case DLL_PROCESS_ATTACH:
- printf("DLL_PROCESS_ATTACH begin");
- ::DisableThreadLibraryCalls((HINSTANCE)hInstance);
- shThread=CreateThread(NULL,0,WorkerThread,NULL,0,NULL);
- WaitForSingleObject(shThread,INFINITE);
- printf("DLL_PROCESS_ATTACH end");
- break;
- default:
- printf("DLL_DEFAULT\n");
- break;
- }
- return TRUE;
- }
- [size=3][color=black]
复制代码
exe代码
- [/color][/size]
- #include <windows.h>
- #include <stdio.h>
- void main()
- {
- HINSTANCE lib=LoadLibrary("dll\\debug\\dll.dll");
- printf("main end\n");
- }
- [size=3][color=black]
复制代码
加载dll时,会发现程序处于阻塞状态,无法打印信息。
windbg调试死锁:
0 Id: 1c1c.1404 Suspend: 1 Teb: 7efdd000 Unfrozen
ChildEBP RetAddr
0018faa4 7621149d ntdll!NtWaitForSingleObject+0x15
0018fb10 765a1194 KERNELBASE!WaitForSingleObjectEx+0x98
0018fb28 765a1148 kernel32!WaitForSingleObjectExImplementation+0x75
0018fb3c 100010d2 kernel32!WaitForSingleObject+0x12
0018fba0 10001480 dll!DllMain+0x62 [D:\temp\test\dll\dll.cpp @ 28]
0018fbb8 77989950 dll!_DllMainCRTStartup+0x80 [dllcrt0.c @ 237]
0018fbd8 7798d8c9 ntdll!LdrpCallInitRoutine+0x14
0018fccc 7798d78c ntdll!LdrpRunInitializeRoutines+0x26f
0018fe38 7798c4d5 ntdll!LdrpLoadDll+0x4d1
0018fe70 76212c95 ntdll!LdrLoadDll+0xaa
0018feac 76212cf2 KERNELBASE!LoadLibraryExW+0x1f1
0018fecc 765a49f0 KERNELBASE!LoadLibraryExA+0x26
0018feec 00401045 kernel32!LoadLibraryA+0xba
0018ff48 00401309 test!main+0x25 [d:\temp\test\test.cpp @ 7]
0018ff88 765a33aa test!mainCRTStartup+0xe9 [crt0.c @ 206]
0018ff94 77989ef2 kernel32!BaseThreadInitThunk+0xe
0018ffd4 77989ec5 ntdll!__RtlUserThreadStart+0x70
0018ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
1 Id: 1c1c.1c18 Suspend: 1 Teb: 7efda000 Unfrozen
ChildEBP RetAddr
007dfb94 77988df4 ntdll!NtWaitForSingleObject+0x15
007dfbf8 77988cd8 ntdll!RtlpWaitOnCriticalSection+0x13e
007dfc20 7798a0a9 ntdll!RtlEnterCriticalSection+0x150
007dfcb4 77989e4c ntdll!LdrpInitializeThread+0xc6
007dfd00 77989e79 ntdll!_LdrpInitialize+0x1ad
007dfd10 00000000 ntdll!LdrInitializeThunk+0x10
# 2 Id: 1c1c.1fcc Suspend: 1 Teb: 7efd7000 Unfrozen
ChildEBP RetAddr
009aff58 779ef896 ntdll!DbgBreakPoint
009aff88 765a33aa ntdll!DbgUiRemoteBreakin+0x3c
009aff94 77989ef2 kernel32!BaseThreadInitThunk+0xe
009affd4 77989ec5 ntdll!__RtlUserThreadStart+0x70
009affec 00000000 ntdll!_RtlUserThreadStart+0x1b
查看线程0在kernel32!WaitForSingleObject处的调用:
0:002> dd 0x0018fb3c
0018fb3c 0018fba0 100010d2 00000038 ffffffff
0018fb4c 0018fc88 0018fbcc 00000001 cccccccc
0018fb5c cccccccc cccccccc cccccccc cccccccc
0018fb6c cccccccc cccccccc cccccccc cccccccc
0018fb7c cccccccc cccccccc cccccccc cccccccc
0018fb8c cccccccc cccccccc cccccccc 00000001
0018fb9c 00000038 0018fbb8 10001480 10000000
0018fbac 00000001 00000000 00000001 0018fbd8
发现句柄值为0x38
0:002> !handle 0x38 f
Handle 38
Type Thread
Attributes 0
GrantedAccess 0x1fffff:
Delete,ReadControl,WriteDac,WriteOwner,Synch
Terminate,Suspend,Alert,GetContext,SetContext,SetInfo,QueryInfo,SetToken,Impersonate,DirectImpersonate
HandleCount 4
PointerCount 7
Name <none>
Object Specific Information
Thread Id 1c1c.1c18
Priority 10
Base Priority 0
Start Address 1000100a dll!ILT+5(?WorkerThreadYGKPAXZ)
0:002> ~
0 Id: 1c1c.1404 Suspend: 1 Teb: 7efdd000 Unfrozen
1 Id: 1c1c.1c18 Suspend: 1 Teb: 7efda000 Unfrozen
. 2 Id: 1c1c.1fcc Suspend: 1 Teb: 7efd7000 Unfrozen
可见线程0在等待线程1,再来看线程1的ntdll!RtlEnterCriticalSection
0:002> dd 0x007dfc20
007dfc20 007dfcb4 7798a0a9 77a520c0 76ca45f8
007dfc30 7efda000 7efde000 77a5206c 00000000
007dfc40 00000000 00000000 00000000 00000000
007dfc50 00000000 00000000 00000000 00000000
007dfc60 00000000 00000000 00000000 00000000
007dfc70 00000000 00000000 00000000 00000000
007dfc80 00000000 00000000 00000000 00000000
007dfc90 00000000 7efde000 00000000 007dfc2c
得到的
0:002> !cs 77a520c0
-----------------------------------------
Critical section = 0x77a520c0 (ntdll!LdrpLoaderLock+0x0)
DebugInfo = 0x77a54360
LOCKED
LockCount = 0x1
WaiterWoken = No
OwningThread = 0x00001404
RecursionCount = 0x1
LockSemaphore = 0x3C
SpinCount = 0x00000000
可见这个临界区的宿主线程时线程0,这样就产生了线程0和线程1的循环依赖,导致他们都停了下来。
上例的ntdll!LdrpLoaderLock是一个重要的系统锁,称为“加载器锁”,这种锁由系统模块加载器ntdll.dll中的代码用来进行DllMain函数的同步调用,即便用户代码中没有显式获取该锁,系统加载器也会在调用DllMain函数之前加载该锁。这种行为确保了DllMain区域代码的串行化执行,提供了一种线程安全的方式完成用户模式DLL模块初始化工作。当新线程创建后,模块DllMain例程被调用之前,系统加载器就会尝试获取相同的加载器锁,让该例程知道进程中产生了新线程(DLL_THREAD_ATTACH)。即使通过DisableThreadLibraryCalls拒绝接受这类消息,其他系统DLL模块仍希望接收到该消息,系统加载器也总是在创建了新线程后获取加载器锁。这就是为什么线程1处于阻塞状态,等待线程0退出DllMain并显式释放绑定的加载器锁全局变量。
PS:因此DllMain中不能放入如WaitForSingleObject阻塞函数
一个重要的步骤是DllMain总是在加了系统加载器锁之后执行,这样的话DllMain中的代码应该尽量简洁,开发者需要了解所有DllMain中调用的函数所进行的所有潜移默化的操作。以下几点要牢记:
不能在DllMain中创建新线程,或者调用任何可能创建新线程的函数。例如,DllMain中使用COM对象是非法的,因为这样做会创建新进程(ole32.dll)。
当显式获得锁时,DllMain要尊守其他的通用规则,尤其是避免执行网络操作或其他耗时操作,因为会占用加载器锁很长时间。通常的准则是,你需要限制DllMain所做的工作,使之简单的初始化DLL模块的全局变量。
DLL模块中的C++静态(全局)变量也会加了系统加载器锁的状态下,在DllMain例程执行之前由C运行库初始化,这样,C++全局对象的构造函数代码也要遵循这种规则。
加载器锁和调试器注入代码
如果创建新线程需要获取加载器锁,为什么在主线程(线程0)被锁时,调试器能注入附加到的远程线程(线程2)?如果你使用Windows XP运行这个实验,试着在死锁的时候进入执行代码,你会发现windbg.exe等了一会,之后强制挂起线程。
Break-in sent, waiting 30 seconds...
WARNING: Break-in timed out, suspending.
This is usually caused by another thread holding the loader lock
这种做法已经在win7得到了改进,Windbg现在可以使用特殊的系统功能注入远程线程。这种特殊的功能和常规的使用Win32 API创建线程的过程相比独特之处在于,他会请求内核在不执行常规初始化的情况下创建线程,这种初始化不需要得到加载器锁。在TEB结构体中,线程2里你可以看到很多地方都是未初始化的。
0:002> dt ntdll!_TEB @$teb
...
+0x02c ThreadLocalStoragePointer : (null) ...
+0x03c CsrClientThread : (null)
+0x040 Win32ThreadInfo : (null) ...
+0x1a8 ActivationContextStackPointer : (null)
0:002> q
特别的,该线程没有在Windows 服务器/客户端 子系统进程 csrss.exe中注册,这也意味着他不能创建UI窗口,也不能使用局部线程存储空间,然而,在上例中这种情况并不大碍,因为此时调试器在注入线程时只需要执行int3指令的调试断点。
|
|