挂钩线程创build/终止

是否有可能挂钩到Windows上的线程终止? IOW,如果进程中的线程(对其他进程和线程不感兴趣)已经终止(通常或者更重要 – 强制),我希望得到通知。

另外,挂钩到线程创build也可以。

理由:我有一个基于每个线程pipe理一些信息的库(把它想象成一个进程范围内的每个线程caching来获取一些信息)。 当线程被终止时,我必须从caching中删除所有线程特定的信息。 [caching关联是通过使用线程ID来实现的,这些线程可能会被重用于将来的线程。]

“正常”执行顺序没有问题,因为库用户将从库中分离当前线程,这将清除状态。 如果有人杀死拥有caching资源的线程,就会出现问题。

你可以使用类似Detours的东西做像TerminateThread的Win32 API的API级挂钩。

不过,我不明白你为什么需要这样做。 这听起来像你需要清除线程的关联缓存时,线程死亡,所以你可以重新使用该插槽,如果有另一个相同的ID线程出现。 它是否正确?

如果是这样,当你得到DLL_THREAD_ATTACH事件时,你不能只清除DllMain的缓存关联吗? 这实质上是你的新线程通知。 在这一点上,你知道你有一个新的线程,所以清除现有的相关缓存不安全吗?

另一个可能的工作是线程本地存储 (TLS)。 您可以使用像TlsAlloc / TlsSetValue这样的Win32 API来存储特定于线程的信息。 你也可以用__declspec(thread)定义一个变量来让编译器为你管理TLS。 这样,每个线程维护自己的缓存。 每个线程的代码保持不变,但是数据访问是相对于线程的。

最好的方法是用线程的HANDLE调用WaitForSingleObject(使用线程ID调用OpenThread来获得HANDLE)。

如果您的程序在一个DLL中,您可以设置处理DllMain方法。 这在线程或进程开始/结束时被调用。

例如,

 library MyDLL; uses SysUtils, Windows; procedure DllMain(reason: integer) ; var dyingThreadId: Cardinal; begin case reason of DLL_THREAD_DETACH: begin dyingThreadId := GetCurrentThreadId(); // handle thread exit with thread id end; end; end; begin DllProc := @DllMain; end. 

编辑:调用是在退出线程的上下文,所以你可以调用GetCurrentThreadId()获取线程的ID。

您可以使用Win32_ThreadStopTrace WMI事件来检测系统中任何线程的终止。

要开始监视这个事件,你必须写这样一个WQL语句

 Select * from Win32_ThreadStopTrace Within 1 Where ProcessID=PID_Of_Your_App 

检查这个样本

 uses Classes; type TProcWmiEventThreadeCallBack = procedure(AObject: OleVariant) of object; TWmiEventThread = class(TThread) private Success : HResult; FSWbemLocator: OleVariant; FWMIService : OleVariant; FEventSource : OleVariant; FWbemObject : OleVariant; FCallBack : TProcWmiEventThreadeCallBack; FWQL : string; Fserver : string; FUser : string; FPassword : string; FNameSpace : string; TimeoutMs : Integer; procedure RunCallBack; public Constructor Create(CallBack : TProcWmiEventThreadeCallBack;const server,User,PassWord,NameSpace,WQL:string;iTimeoutMs : Integer); overload; destructor Destroy; override; procedure Execute; override; end; implementation uses SysUtils, ComObj, Variants, ActiveX; constructor TWmiEventThread.Create(CallBack : TProcWmiEventThreadeCallBack;const server,User,PassWord,NameSpace,WQL:string;iTimeoutMs : Integer); begin inherited Create(False); FreeOnTerminate := True; FCallBack := CallBack; FWQL := WQL; Fserver := server; FUser := User; FPassword := PassWord; FNameSpace := NameSpace; TimeoutMs := iTimeoutMs; end; destructor TWmiEventThread.Destroy; begin FSWbemLocator:=Unassigned; FWMIService :=Unassigned; FEventSource :=Unassigned; FWbemObject :=Unassigned; inherited; end; procedure TWmiEventThread.Execute; const wbemErrTimedout = $80043001; begin Success := CoInitialize(nil); //CoInitializeEx(nil, COINIT_MULTITHREADED); try FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator'); FWMIService := FSWbemLocator.Connectserver(Fserver, FNameSpace, FUser, FPassword); FEventSource := FWMIService.ExecNotificationQuery(FWQL); while not Terminated do begin try FWbemObject := FEventSource.NextEvent(TimeoutMs); //set the max time to wait (ms) except on E:EOleException do if EOleException(E).ErrorCode=HRESULT(wbemErrTimedout) then //Check for the timeout exception and ignore if exist FWbemObject:=Null else raise; end; if FindVarData(FWbemObject)^.VType <> varNull then Synchronize(RunCallBack); FWbemObject:=Unassigned; end; finally case Success of S_OK, S_FALSE: CoUninitialize; end; end; end; procedure TWmiEventThread.RunCallBack; begin FCallBack(FWbemObject); end; 

现在要在你的应用程序中使用这个线程,你必须这样调用它

 WmiThread:=TWmiEventThread.Create( Log, '.', '', '', 'root\cimv2', Format('Select * from Win32_ThreadStopTrace Within 1 Where ProcessID=%d',[GetCurrentProcessId]),1); 

并在回调函数中

 procedure TForm1.Log(AObject: OleVariant); begin { The OleVariant parameter has these properties uint32 ProcessID; uint8 SECURITY_DESCRIPTOR[]; uint32 ThreadID; uint64 TIME_CREATED; } //do your stuff here Memo1.Lines.Add(Format('Thread %s terminated ',[AObject.ThreadID])); end; 
 program ThreadExitHook; {$APPTYPE CONSOLE} uses Windows, Classes, madCodeHook; type TLdrShutdownThread = procedure; stdcall; var LdrShutdownThreadNext : TLdrShutdownThread; procedure LdrShutdownThreadCallback; stdcall; begin WriteLn('Thread terminating:', GetCurrentThreadId); LdrShutdownThreadNext; end; begin HookAPI('ntdll.dll', 'LdrShutdownThread', @LdrShutdownThreadCallback, @LdrShutdownThreadNext); TThread.CreateAnonymousThread(procedure begin WriteLn('Hello from Thread'); Sleep(1000); end).Start; ReadLn; UnhookAPI(@LdrShutdownThreadNext); end. 

这是一个不依赖于任何外部库的版本:

 program Project7; {$APPTYPE CONSOLE} uses Windows, Classes; {==============================================================================} function IsWin9x: Boolean; asm MOV EAX, FS:[030H] TEST EAX, EAX SETS AL end; {------------------------------------------------------------------------------} function CalcJump(Src, Dest: DWORD): DWORD; begin if (Dest < Src) then begin Result := Src - Dest; Result := $FFFFFFFF - Result; Result := Result - 4; end else begin Result := Dest - Src; Result := Result - 5; end; end; {------------------------------------------------------------------------------} function OpCodeLength(Address: DWORD): DWORD; cdecl; assembler; const O_UNIQUE = 0; O_PREFIX = 1; O_IMM8 = 2; O_IMM16 = 3; O_IMM24 = 4; O_IMM32 = 5; O_IMM48 = 6; O_MODRM = 7; O_MODRM8 = 8; O_MODRM32 = 9; O_EXTENDED = 10; O_WEIRD = 11; O_ERROR = 12; asm pushad cld xor edx, edx mov esi, Address mov ebp, esp push 1097F71Ch push 0F71C6780h push 17389718h push 101CB718h push 17302C17h push 18173017h push 0F715F547h push 4C103748h push 272CE7F7h push 0F7AC6087h push 1C121C52h push 7C10871Ch push 201C701Ch push 4767602Bh push 20211011h push 40121625h push 82872022h push 47201220h push 13101419h push 18271013h push 28858260h push 15124045h push 5016A0C7h push 28191812h push 0F2401812h push 19154127h push 50F0F011h mov ecx, 15124710h push ecx push 11151247h push 10111512h push 47101115h mov eax, 12472015h push eax push eax push 12471A10h add cl, 10h push ecx sub cl, 20h push ecx xor ecx, ecx dec ecx @@ps: inc ecx mov edi, esp @@go: lodsb mov bh, al @@ft: mov ah, [edi] inc edi shr ah, 4 sub al, ah jnc @@ft mov al, [edi-1] and al, 0Fh cmp al, O_ERROR jnz @@i7 pop edx not edx @@i7: inc edx cmp al, O_UNIQUE jz @@t_exit cmp al, O_PREFIX jz @@ps add edi, 51h cmp al, O_EXTENDED jz @@go mov edi, [ebp+((1+8)*4)+4] @@i6: inc edx cmp al, O_IMM8 jz @@t_exit cmp al, O_MODRM jz @@t_modrm cmp al, O_WEIRD jz @@t_weird @@i5: inc edx cmp al, O_IMM16 jz @@t_exit cmp al, O_MODRM8 jz @@t_modrm @@i4: inc edx cmp al, O_IMM24 jz @@t_exit @@i3: inc edx @@i2: inc edx pushad mov al, 66h repnz scasb popad jnz @@c32 @@d2: dec edx dec edx @@c32: cmp al, O_MODRM32 jz @@t_modrm sub al, O_IMM32 jz @@t_imm32 @@i1: inc edx @@t_exit: jmp @@ASMEnded @@t_modrm: lodsb mov ah, al shr al, 7 jb @@prmk jz @@prm add dl, 4 pushad mov al, 67h repnz scasb popad jnz @@prm @@d3: sub dl, 3 dec al @@prmk:jnz @@t_exit inc edx inc eax @@prm: and ah, 00000111b pushad mov al, 67h repnz scasb popad jz @@prm67chk cmp ah, 04h jz @@prmsib cmp ah, 05h jnz @@t_exit @@prm5chk: dec al jz @@t_exit @@i42: add dl, 4 jmp @@t_exit @@prm67chk: cmp ax, 0600h jnz @@t_exit inc edx jmp @@i1 @@prmsib: cmp al, 00h jnz @@i1 lodsb and al, 00000111b sub al, 05h jnz @@i1 inc edx jmp @@i42 @@t_weird: test byte ptr [esi], 00111000b jnz @@t_modrm mov al, O_MODRM8 shr bh, 1 adc al, 0 jmp @@i5 @@t_imm32: sub bh, 0A0h cmp bh, 04h jae @@d2 pushad mov al, 67h repnz scasb popad jnz @@chk66t @@d4: dec edx dec edx @@chk66t: pushad mov al, 66h repnz scasb popad jz @@i1 jnz @@d2 @@ASMEnded: mov esp, ebp mov [result+(9*4)], edx popad end; {------------------------------------------------------------------------------} function ApiHook(ModName, ApiName: PChar; FuncAddr, HookedApi: Pointer; var MainApi: Pointer): Boolean; var dwCount, Cnt, i, jmp: DWORD; P: Pointer; hMod, OldP, TMP: Cardinal; begin Result := False; if IsWin9x then Exit; P := FuncAddr; if P = nil then begin hMod := GetmoduleeHandle(ModName); if hMod = 0 then hMod := LoadLibrary(ModName); P := GetProcAddress(hMod, ApiName); end; if (P = nil) or (HookedApi = nil) then Exit; if not VirtualProtect(P, $40, PAGE_EXECUTE_READWRITE, @OldP) then Exit; if ((Byte(P^) = $68) and (DWORD(Pointer(DWORD(P) + 1)^) = DWORD(HookedApi))) then Exit; MainApi := VirtualAlloc(nil, $1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if MainApi = nil then Exit; Cnt := 0; for dwCount := 0 to $3F do begin Inc(Cnt, OpCodeLength(DWORD(P) + Cnt)); for i := 0 to Cnt - 1 do PByte(MainApi)[i] := PByte(P)[i]; if Cnt > 5 then Break; end; PByte(MainApi)[Cnt] := $68; DWORD(Pointer(DWORD(MainApi) + Cnt + 1)^) := DWORD(P) + Cnt; PByte(MainApi)[Cnt + 5] := $C3; PByte(MainApi)[Cnt + 6] := $99; if (OpCodeLength(DWORD(MainApi)) = 5) and ((Byte(MainApi^) = $E8) or (Byte(MainApi^) = $E9)) then begin jmp := DWORD(P) + DWORD(Pointer(DWORD(MainApi) + 1)^) + 5; DWORD(Pointer(DWORD(MainApi) + 1)^) := CalcJump(DWORD(MainApi), jmp); end; PByte(P)[0] := $68; DWORD(Pointer(DWORD(P) + 1)^) := DWORD(HookedApi); PByte(P)[5] := $C3; VirtualProtect(P, $40, OldP, @TMP); Result := True; end; {------------------------------------------------------------------------------} function ApiUnHook(ModName, ApiName: PChar; FuncAddr, HookedApi: Pointer; var MainApi: Pointer): Boolean; var dwCount, Cnt, i, jmp: DWORD; P: Pointer; hMod, OldP, TMP: Cardinal; begin Result := False; if IsWin9x then Exit; P := FuncAddr; if P = nil then begin hMod := GetmoduleeHandle(ModName); P := GetProcAddress(hMod, ApiName); end; if (P = nil) or (MainApi = nil) or (HookedApi = nil) then Exit; if not VirtualProtect(P, $40, PAGE_EXECUTE_READWRITE, @OldP) then Exit; if ((Byte(P^) <> $68) or (DWORD(Pointer(DWORD(P) + 1)^) <> DWORD(HookedApi))) then Exit; Cnt := 0; for dwCount := 0 to $3F do begin Inc(Cnt, OpCodeLength(DWORD(MainApi) + Cnt)); if (Byte(Pointer(DWORD(MainApi) + Cnt)^) = $C3) and (Byte(Pointer(DWORD(MainApi) + Cnt + 1)^) = $99) then Break; for i := 0 to Cnt - 1 do PByte(P)[i] := PByte(MainApi)[i]; end; if (OpCodeLength(DWORD(P)) = 5) and ((Byte(P^) = $E8) or (byte(P^) = $E9)) then begin jmp := DWORD(MainApi) + DWORD(Pointer(DWORD(MainApi) + 1)^) + 5; DWORD(Pointer(DWORD(P) + 1)^) := CalcJump(DWORD(P), jmp); end; VirtualProtect(P, $40, OldP, @TMP); VirtualFree(MainApi, 0, MEM_RELEASE); Result := True; end; {==============================================================================} type TLdrShutdownThread = procedure; stdcall; var LdrShutdownThreadNext : TLdrShutdownThread; procedure LdrShutdownThreadCallback; stdcall; begin WriteLn('Thread terminating:', GetCurrentThreadId); LdrShutdownThreadNext; end; begin ApiHook('ntdll.dll', 'LdrShutdownThread', nil, @LdrShutdownThreadCallback, @LdrShutdownThreadNext); TThread.CreateAnonymousThread(procedure begin WriteLn('Hello from Thread'); Sleep(1000); WriteLn('Waking up'); end).Start; ReadLn; ApiUnHook('ntdll.dll', 'LdrShutdownThread', nil, @LdrShutdownThreadCallback, @LdrShutdownThreadNext); TThread.CreateAnonymousThread(procedure begin WriteLn('Hello from Thread'); Sleep(1000); WriteLn('Waking up'); end).Start; ReadLn; end. 

Chris提到DLL_THREAD_ATTACH给了我一个想法…

基本上,将缓存与线程ID关联是一件坏事。 我必须重写我的库,以便一个线程最初将建立某种句柄,然后使用此句柄管理关联。

我想如果你真的想做得不够好,你可以使用调试API(例如, WaitForDebugEventContinueDebugEvent )。 当一个线程退出时,你会得到一个EXIT_THREAD_DEBUG_EVENT。

我不能说这完全是一个简单或干净的方式来做到这一点,但如果你不能拿出任何其他的东西,那可能比没有好。

Boost提供了boost::this_thread::at_thread_exit() ,它允许您提供任意代码在当前线程退出时运行。 如果你在每个线程上调用它,那么当它正常退出时,代码将被运行。 如果使用TerminateThread强制TerminateThread一个线程,那么不会在该线程上运行更多的代码,因此不会调用at_thread_exit函数。 处理这种情况的唯一方法是钩住TerminateThread ,虽然这不一定会处理另一个进程终止你的线程的情况。

唯一能够可靠地执行此操作的方法是挂载DLL_THREAD_ATTACH和DLL_THREAD_DETACH的DLL。 请参阅前面的讨论。