是否可以挂钩到Windows上的线程终止? IOW,如果进程中的一个线程(对其他进程及其线程不感兴趣)已经终止(通常或者 - 更重要 - 强制),我希望得到通知。
或者,挂钩创建线程也可以。
基本原理:我有一个库,可以在每个线程的基础上管理一些信息(将其视为某些信息的进程范围的每线程缓存)。线程终止时,我必须从缓存中删除所有特定于线程的信息。 [缓存关联使用线程ID实现,可能会在将来的线程中重用。]
“正常”执行顺序没有问题,因为库用户将从库中分离当前线程以清除状态。如果有人杀死拥有缓存资源的线程,问题就会开始出现。
答案 0 :(得分:6)
最好的方法是打电话 WaitForSingleObject与线程的HANDLE(使用线程id调用OpenThread来获取HANDLE)。
答案 1 :(得分:5)
如果您的程序在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。
答案 2 :(得分:5)
您可以使用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;
答案 3 :(得分:4)
您可以使用类似Detours之类的内容来执行TerminateThread
等Win32 API的API级挂钩。
如果是这样,当您收到DllMain
事件时,您是否只能清除DLL_THREAD_ATTACH
中的缓存关联?这基本上是你的新线程通知。此时,您知道有一个新线程,因此清除现有的关联缓存是否安全?
可能有效的另一种选择是thread-local storage(TLS)。您可以使用TlsAlloc
/ TlsSetValue
等Win32 API来存储特定于线程的信息。您还可以使用__declspec(thread)
定义变量,以便编译器为您管理TLS。这样,每个线程都维护自己的缓存。每个线程的代码保持不变,但数据访问是相对于线程的。
答案 4 :(得分:3)
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 := GetModuleHandle(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 := GetModuleHandle(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.
答案 5 :(得分:1)
Chris提到DLL_THREAD_ATTACH给了我一个想法......
基本上,将缓存与线程ID相关联是一件坏事。我必须重新编写我的库,以便线程最初建立某种句柄,然后使用此句柄管理关联。
答案 6 :(得分:1)
我想如果你真的非常想要,你可以使用调试API(例如,WaitForDebugEvent
,ContinueDebugEvent
),.当线程退出时,你会得到一个EXIT_THREAD_DEBUG_EVENT。
我不能说这是一种简单直接或干净的方式,但如果你不能提出任何其他的东西,它可能总比没有好。
答案 7 :(得分:0)
Boost提供boost::this_thread::at_thread_exit()
,允许您提供在当前线程退出时运行的任意代码。如果你在每个线程上调用它,那么当它正常退出时,代码将会运行。如果使用TerminateThread
强制终止某个线程,则该线程上不再运行任何代码,因此不会调用at_thread_exit
函数。处理这种情况的唯一方法是挂钩TerminateThread
,尽管这不一定能处理另一个进程终止你的线程的情况。
答案 8 :(得分:0)
可靠执行此操作的唯一方法是在DLL中挂钩DLL_THREAD_ATTACH和DLL_THREAD_DETACH。请参阅先前的讨论here。