6
votes

Création de fil d'accrochage / terminaison

est-il possible de connecter la terminaison de fil sur Windows? Je voudrais être informé si un fil à l'intérieur du processus (non intéressé par d'autres processus et de leurs threads) s'est terminé (normalement ou - plus important - avec force).

Alternativement, l'accrochage dans la création de fil ferait également.

Justification: J'ai une bibliothèque qui gère des informations sur la base de thread (y penser comme un cache par thread à l'échelle du processus pour certaines informations). Lorsqu'un thread est terminé, je dois supprimer toutes les informations spécifiques à thread du cache. [Les associations de cache sont implémentées à l'aide d'un identifiant de thread qui peut être réutilisé pour les futurs threads.]

Il n'y a pas de problème avec l'ordre d'exécution "normal" car l'utilisateur de la bibliothèque détachera le fil actuel de la bibliothèque qui effacera l'état. Les problèmes commencent à apparaître si quelqu'un tue le fil de la ressource mise en cache.


0 commentaires

9 Réponses :


4
votes

1 commentaires

Dll_thread_attach fonctionnerait si la bibliothèque en question a été mise en œuvre comme une DLL, mais ce n'est pas le cas. La terminaison d'accrochage est une possibilité, convenue.



1
votes

Chris 'Mention de dll_thread_attach m'a donné une idée ...

Fondamentalement, l'association de cache avec un identifiant de thread est une mauvaise chose. Je dois retravailler ma bibliothèque afin qu'un thread établisse initialement une sorte de manipulation puis gérera ensuite des associations à l'aide de cette poignée.


0 commentaires

0
votes

boost fournit Boost :: this_thread :: at_ththread_exit () qui vous permet de fournir un code arbitraire à exécuter lorsque le fil actuel sort. Si vous appelez ceci sur chaque thread, alors quand il quitte normalement le code sera exécuté. Si un thread est terminé de force avec TerminaTéthread , plus aucun code n'est exécuté sur ce thread, de sorte que les fonctions at_thread_exit ne sont pas appelées. Le seul moyen de gérer de tels cas serait de crochet TerminaTéthread , bien que cela ne gère pas nécessairement le cas qu'un autre processus termine vos threads.


0 commentaires

0
votes

Le seul moyen de de manière fiable Faites ceci est dans une DLL qui croche dll_thread_attach et dll_thread_detach. Voir la discussion précédente ici .


0 commentaires

6
votes

Le meilleur moyen est d'appeler WaitforSingObject avec la poignée du fil (appelez OPENHREAD à l'aide de l'ID de thread pour obtenir la poignée).


1 commentaires

Très intéressant en effet. Un tel événement sera-t-il un incendie sur la terminaison de fil ou dans d'autres situations également (ce qui pourrait être)?



1
votes

Je suppose que si vous voulez vraiment assez mal, vous pouvez utiliser l'API de débogage (par exemple, WaitFordebugevent , ContinueDeBuVENT ) ,. Vous obtiendrez une sortie_thread_debug_event lorsqu'un fil est sorti.

Je ne peux pas dire que c'est exactement un moyen simple ou simple de le faire, mais si vous ne pouvez pas venir avec quoi que ce soit d'autre, c'est probablement mieux que rien.


0 commentaires

5
votes

Si votre programme est dans une DLL, vous pouvez configurer pour gérer la méthode DLLMAIN. Ceci est appelé lorsqu'un thread ou un processus commence / se termine / se termine.

Par exemple, P>

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.


11 commentaires

Désolé, c'est faux. Il est seulement appelé lorsqu'un processus ou un thread qui charge la DLL commence ou terminé. Sauf si chaque fil de l'application charge cette DLL, elle ne sera pas appelée (et si la DLL peut être chargée par chaque thread, il existe de meilleurs moyens de se faire avertir quand ils se terminent).


Ken, tu as mal compris. Ceci est une API Windows standard - la méthode est appelée lorsque tout thread sort du processus que la DLL est chargée. La DLL est chargée dans le processus, non dans le thread, une fois que le processus charge la DLL (par n'importe quel fil), DLLMAIN est appelé à chaque fil de sortie. Voir msdn.microsoft.com/en-us/ Bibliothèque / MS682583 (v = vs.85) .aspx en particulier la partie DLL_Thread_Detatch.


@MDMA: Je ne pense pas. Veuillez vous reporter à la première phrase de votre sujet lié ( emphasis mine ): "Un point d'entrée optionnel dans une bibliothèque de liaison dynamique (DLL). Lorsque le système démarre ou termine un processus ou un thread, il appelle la Fonction d'entrée-point pour chaque DLL chargée à l'aide du premier fil du processus . " Notez le "premier fil" dans cette phrase. Je ne sais pas comment cela peut être plus clair, mais je serais prêt à admettre que j'avais tort si vous avez quelque chose qui démontre le contraire ...


La documentation a changé au fil des ans. Maintenant ce n'est pas si clair. Bien que ce ne soit pas expressément écrit, cela signifie le DLL_PROCESS_ATTACH, qui est appelé à partir du premier fil du processus. Je suis d'accord avec le texte n'est pas clair, mais c'est ce que cela signifie - dll_thread_detach est appelé à chaque fil qui quitte le processus. Sinon, quelle serait la différence entre un détachement de fil et le processus? S'il vous plaît essayez-le si vous ne me croyez pas. J'ai codé de nombreuses dlls "colle" qui utilisent une fonctionnalité spécifique.


@MDMA: Je ne pense pas que cette question répond à la question de Mason (car elle ne fournit pas l'information qu'il recherche, à savoir le threadid du fil de sortie). Cependant, je retire mon vote en désaccord parce que très techniquement parler, dllmain fait est appelé comme vous l'avez dit (mais en utilisant le premier fil, comme je l'ai mentionné dans mon commentaire) . Par conséquent, très techniquement parler, vous n'étiez pas faux. Néanmoins, ne pensez pas que c'est une réponse à la question posée, cependant :) - ne méritait tout simplement pas un vote au bas.


@ken - J'ai répondu à la question. L'appel est effectué dans le contexte du thread de sortie afin que vous puissiez obtenir l'ID de thread. De nombreuses implémentations de variables locales de fil s'appuient sur cette fonctionnalité.


@MDMA: Je vois ça. :) Nice Travail - Vous avez inversé un vote en panne et avez fait un vote en place. Première fois que j'ai jamais fait que .


Merci, bien que je n'ai changé que ma réponse légèrement - le changement plus important était de votre compréhension, bien sûr, des malentendus se produisent. Je n'ai réussi que de vous amener à inverser le bowvote parce que j'ai passé le temps de défendre ma réponse. Vous dites que c'est la première fois que vous avez inversé un vote - je l'apprécie - mais peut-être que d'autres que vous avez évité, vous avez corrigé, mais ne prendrait pas le temps de défendre leurs réponses. J'espère que, à l'avenir, vous ne baisserez que lorsque vous êtes totalement sûr de quelque chose et que vous avez une expérience empirique pour la corroborer. De cette façon, alors sera une place plus heureuse pour tous.


Flamewars privé de côté, ce n'est vraiment pas une réponse parce que je ne cours pas à l'intérieur d'une DLL.


? 'Flamewars "? Je vois une discussion sur une réponse. Où est la flamme?


+1 pour la discussion constructive et pour la solution. @ Mason, prenez l'indice de MDMA: Quote: J'ai codé de nombreuses dlls "colle" qui utilisent une fonctionnalité spécifique. . Écrivez une petite dll qui vous rappelle quand un fil sort.



3
votes
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 commentaires

Semble très gentil. Toute façon de le faire sans la bibliothèque exclusive?


Oui, c'est un peu plus compliqué, mais faisable. Fondamentalement, vous devez écraser le premier couple d'octets de LDRSHUTTODTODHADAD avec une instruction de saut à votre propre code, puis faites ce que vous voulez faire et enfin faire quelles que soient les instructions que vous avez écrasées et retournez dans l'original LDRSHUTTODTHADAD après l'instruction de saut. Le problème est que vous ne pouvez pas simplement couper des instructions en deux, vous devez donc démonter le code d'origine de LDRSHUTTODODADAD pour vous assurer de copier un certain nombre d'instructions complètes. Il commence la même chose sur toutes les versions du système d'exploitation que cela devient plus facile ...


Une recherche rapide trouve ceci: sites.google.com/site/delphibasics/home/ Delphibasicssnippets / ... Après un coup d'œil rapide au code, cela va faire plus que ce dont vous avez besoin (il est écrit de pouvoir injecter des API de DLL et de crochet dans d'autres processus) afin que vous puissiez pouvoir extraire le quantité minimale de code pour celui-ci que vous devez simplement accrocher l'API dans votre propre processus.


Il semble que Jeff Atwood lui-même a fusionné la question de Mason avec Gabr sur. Une étape que je dois dire que je suis fortement en désaccord parce que les deux questions ne sont vraiment pas identiques du tout.


@Thorstenengler est-il possible de gérer rtluserthreadstart, baséhreadinitthunk ? Pour commencer, initialisation?



5
votes

Vous pouvez utiliser le win32_threadstoptrace événement WMI Pour détecter la résiliation de tout thread dans le système.

Pour commencer à surveiller cet événement, vous devez écrire une phrase wql comme ceci xxx

chèque Cet exemple xxx

maintenant Pour utiliser ce fil dans votre application, vous devez l'appeler de cette manière xxx

et dans le Fonction de rappel xxx


2 commentaires

+1 pour encore une autre réponse WMI de Rruz. WMI est-il une solution raisonnable dans ce cas? Les frais généraux ne sont-ils pas trop grains pour un travail aussi bas? Remarque: Je ne vais passer que par le nom, l'instrumentation de gestion de Windows, et elle ne semble pas être utilisée comme utilisée pour libérer des variables de fil, par exemple.


@Cosmin, je sais qui n'est pas très courant d'utilisation un objet COM avec des tâches de faible niveau, telles que la surveillance des threads et du processus. Dans une autre main, le WMI est principalement connu en récupérant des informations système telles que le matériel installé et réfléchit comme ça, mais ce n'est qu'une petite partie de son pouvoir. Personnellement, j'ai écrit de nombreuses applications pour surveiller les systèmes distants avec succès à l'aide de l'OMM. Avec le WMI Vous pouvez détecter le début des threads, le processus, les pannes matérielles, le nouveau matériel connecté ou déconnecté, etc. En résumé comme réponse à votre question est une solution raisonnable dans ce cas? , oui c'est.