IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

x86 32-bits / 64-bits Assembleur Discussion :

[Windows] Possible de mettre un EXCEPTION_REGISTRATION en dehors de la pile ?


Sujet :

x86 32-bits / 64-bits Assembleur

  1. #1
    Expert éminent sénior

    Avatar de sjrd
    Homme Profil pro
    Directeur de projet
    Inscrit en
    Juin 2004
    Messages
    4 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : Suisse

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2004
    Messages : 4 517
    Points : 10 154
    Points
    10 154
    Par défaut [Windows] Possible de mettre un EXCEPTION_REGISTRATION en dehors de la pile ?
    Windows - ASM inline de Delphi

    Bonjour,

    Je suis en train de développer des thunks en assembleur qui doivent ajouter un paramètre à un appel de méthode, et ce dans les quatre principales conventions d'appel de Delphi : register, stdcall, pascal et cdecl. Pour les trois premières, aucun problème. Pour la cdecl, c'est plus délicat

    Je dois en effet gérer les exceptions qui pourraient être déclenchées dans la méthode appelée, afin de libérer correctement mes infos. Cependant, je ne peux pas me permettre de stocker la EXCEPTION_REGISTRATION dans la pile, tout simplement car je ne peux pas modifier la pile (en dehors du paramètre que j'ajoute) !

    En fouillant pas mal sur le net, j'ai appris beaucoup sur la gestion des exceptions en assembleur, et je suis arrivé à ce code-ci (assembleur inline du Delphi) :
    Code Delphi : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    type
      TCDeclReturnInfo = packed record
        PreviousExceptReg : Pointer;
        Handler : Pointer;
        EBPContents : LongWord;
        ReturnAddress : Pointer;
      end;
     
    procedure CDeclExceptHandler;
    const
      cUnwindInProgress = 6;
    asm
            { ->    [ESP+ 4] excPtr: PExceptionRecord       }
            {       [ESP+ 8] errPtr: PExcFrame              }
            {       [ESP+12] ctxPtr: Pointer                }
            {       [ESP+16] dspPtr: Pointer                }
            { <-    EAX return value - always one           }
     
            MOV     EAX,[ESP+4]
            TEST    [EAX].TExceptionRecord.ExceptionFlags,cUnwindInProgress
            JNE     @@exit
            MOV     EAX,[ESP+8]
            CALL    System.@FreeMem
    @@exit:
            MOV     EAX,1
    end;
     
    // PreCDeclCall sets up the exception handler, and saves the return value
    procedure PreCDeclCall;
    asm
            { ->    [ESP+ 4] ReturnAddress: Pointer }
     
            MOV     EAX,TYPE TCDeclReturnInfo
            CALL    System.@GetMem
            XOR     EDX,EDX
            MOV     ECX,FS:[EDX]
            MOV     [EAX].TCDeclReturnInfo.PreviousExceptReg,ECX
            MOV     [EAX].TCDeclReturnInfo.Handler,OFFSET CDeclExceptHandler
            MOV     [EAX].TCDeclReturnInfo.EBPContents,EBP
            MOV     ECX,[ESP+4]
            MOV     [EAX].TCDeclReturnInfo.ReturnAddress,ECX
            MOV     FS:[EDX],EAX
            RET     4
    end;
     
    // PostCDeclCall tears down the exception handler, and returns to the caller
    procedure PostCDeclCall;
    asm
            { ->    EAX return value of the method }
            { <-    EAX the same return value      }
     
            PUSH    EAX
            XOR     EDX,EDX
            MOV     EAX,FS:[EDX]
            MOV     ECX,[EAX].TCDeclReturnInfo.ReturnAddress
            MOV     [ESP+4],ECX
            MOV     ECX,[EAX].TCDeclReturnInfo.PreviousExceptReg
            MOV     FS:[EDX],ECX
            CALL    System.@FreeMem
            POP     EAX
    end;
     
    var
      GlobObjAddress : Pointer;
     
    // Sample of a procedure which will be generated at runtime
    // (GlobObjAddress and TMyClass.SomeCDecl will then be hard-coded)
    procedure ProcForCDecl;
    asm
            CALL    PreCDeclCall
            PUSH    GlobObjAddress
            CALL    TMyClass.SomeCDecl
            JMP     PostCDeclCall
    end;
    Ca fonctionne très bien... Tant qu'aucune exception n'est lancée

    Il semble que Windows ne soit pas très d'accord avec l'idée d'un enregistrement EXCEPTION_REGISTRATION situé en dehors de la pile (ici dans le tas, alloué par le CALL System.@GetMem). Car le code ne parvient jamais dans ma CDeclExceptHandler en cas d'exception : j'ai directement droit à une erreur système.

    Et de fait, dans cet article, vers la fin, l'auteur indique que l'OS vérifie que cet enregistrement est bien dans la pile

    Auriez-vous une idée de comment je pourrais m'y prendre ?
    d'avance

    (j'espère que je suis assez clair dans mes explications )
    sjrd, ancien rédacteur/modérateur Delphi.
    Auteur de Scala.js, le compilateur de Scala vers JavaScript, et directeur technique du Scala Center à l'EPFL.
    Découvrez Mes tutoriels.

  2. #2
    Rédacteur
    Avatar de Neitsa
    Homme Profil pro
    Chercheur sécurité informatique
    Inscrit en
    Octobre 2003
    Messages
    1 041
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Chercheur sécurité informatique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 1 041
    Points : 1 956
    Points
    1 956
    Par défaut
    Bonjour,

    Je dois en effet gérer les exceptions qui pourraient être déclenchées dans la méthode appelée, afin de libérer correctement mes infos. Cependant, je ne peux pas me permettre de stocker la EXCEPTION_REGISTRATION dans la pile, tout simplement car je ne peux pas modifier la pile (en dehors du paramètre que j'ajoute) !

    [...]

    Il semble que Windows ne soit pas très d'accord avec l'idée d'un enregistrement EXCEPTION_REGISTRATION situé en dehors de la pile (ici dans le tas, alloué par le CALL System.@GetMem). Car le code ne parvient jamais dans ma CDeclExceptHandler en cas d'exception : j'ai directement droit à une erreur système.
    Malheureusement il est impossible de se passer de la pile pour la structure EXCEPTION_REGISTRATION.

    En fait c'est notamment parce que Windows maintient la chaîne des SEHs sur la pile (ce qui lui permet de "répondre" aux questions : lorsque l'on ajoute un SEH quel était le précédent sur la pile ? Lorsque l'on enlève un SEH, y'a t'il encore un SEH juste avant ?).

    Un exemple pour que ce soit plus clair :

    Voilà l'état de la pile avant qu'une seule instruction ait été exécutée :

    (gauche : adresse ; milieu : valeur ; droite : commentaire )

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    0012FFC4   7C816FF7  RETURN to kernel32.7C816FF7 (pointé par ESP)
    0012FFC8   7C910738  ntdll.7C910738
    0012FFCC   FFFFFFFF
    0012FFD0   7FFD9000
    0012FFD4   8054AD38
    0012FFD8   0012FFC8
    0012FFDC   8580C478
    0012FFE0   FFFFFFFF  End of SEH chain (pas de gestionnaire précédent !)
    0012FFE4   7C839A30  SE handler (gestionnaire SEH système [gestionnaire actuel] )
    0012FFE8   7C817000  kernel32.7C817000
    0012FFEC   00000000
    0012FFF0   00000000
    0012FFF4   00000000
    0012FFF8   00401000  test.<ModuleEntryPoint>
    0012FFFC   00000000

    Code Asm : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    PUSH offset MonGestionnaireSEH ; adresse de mon gestionnaire SEH
    PUSH offset FS:[0] ; gestionnaire SEH actuellement en cours
     
    mov	FS:[0], esp ; mon gestionnaire SEH devient le gestionnaire SEH actuel.

    la pile après cela :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    0012FFBC   0012FFE0  Pointer to next SEH record (pointe sur la précédente EXCEPTION_REGISTRATION)
    0012FFC0   00401029  SE handler (offset MonGestionnaireSEH [gestionnaire actuel])
    0012FFC4   7C816FF7  RETURN to kernel32.7C816FF7
    0012FFC8   7C910738  ntdll.7C910738
    0012FFCC   FFFFFFFF
    0012FFD0   7FFD9000
    0012FFD4   8054AD38
    0012FFD8   0012FFC8
    0012FFDC   8580C478
    0012FFE0   FFFFFFFF  End of SEH chain (pas de gestionnaire avant cela)
    0012FFE4   7C839A30  SE handler (gestionnaire SEH système [gestionnaire précédent] )
    0012FFE8   7C817000  kernel32.7C817000
    0012FFEC   00000000
    0012FFF0   00000000
    0012FFF4   00000000
    0012FFF8   00401000  test.<ModuleEntryPoint>
    0012FFFC   00000000
    Comme on le voit à l'adresse 0x12FFBC, Windows maintient un pointeur sur la structure EXCEPTION_REGISTRATION précédente.

    Si on alloue en mémoire ou sur le tas un bloc pour cette structure plutôt que de la pousser sur la pile on rencontre deux problèmes : d'une part Windows va chercher le SEH courant sur la pile (comparaison avec celui en FS:[0]) il va donc ne pas trouver le bon, d'autre part Windows n'est alors plus en mesure de retrouver la structure précédente, il est donc "perdu" (il n'est plus en mesure de la retrouver).

    Comme il ne sait pas comment faire, il bloque sur l'exception.

    Au final : tu n'as pas d'autres moyen que de pousser la structure sur la pile afin de maintenir la liste chaînée des SEHs...

  3. #3
    Expert éminent sénior

    Avatar de sjrd
    Homme Profil pro
    Directeur de projet
    Inscrit en
    Juin 2004
    Messages
    4 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : Suisse

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2004
    Messages : 4 517
    Points : 10 154
    Points
    10 154
    Par défaut
    Merci pour cette réponse bien développée

    Il y a juste un truc qui m'embête :
    Citation Envoyé par Neitsa
    Si on alloue en mémoire ou sur le tas un bloc pour cette structure plutôt que de la pousser sur la pile on rencontre deux problèmes : d'une part Windows va chercher le SEH courant sur la pile (comparaison avec celui en FS:[0]) il va donc ne pas trouver le bon, d'autre part Windows n'est alors plus en mesure de retrouver la structure précédente, il est donc "perdu" (il n'est plus en mesure de la retrouver).
    Il me semblait avoir compris que le premier champ de la EXECTION_REGISTRATION était justement un pointeur vers la précédente, ce qui forme une liste chaînée. Or en 32 bits protégé, les pointeurs se baladent entre segments sans le moindre problème, et donc aussi bien entre pile et tas.
    Donc je ne vois pas pourquoi Windows ne parviendrait pas à "suivre" le pointeur FS:[0] en dehors de la pile, pour ensuite y revenir avec le champ prev de ma structure ?

    Je refuse peut-être l'évidence, mais je suppose que tu dois savoir que, quand on fait de l'assembleur, on aime comprendre

    En déboguant attentivement ce qui se passe après l'exception, je suis tombé sur ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ; Avant : ebx = EXCEPTION_REGISTRATION
    ;         ebp = FS:0
    ;         => [ebp-08] = bas de la pile
    ;            [ebp-0C] = haut de la pile
    cmp     ebx,[ebp-08]
    jb      @invalid
    lea     eax,[ebx+08]
    cmp     eax,[ebp-0C]
    ja      @invalid
    test    bl,03
    jnz     @invalid
    Apparemment, c'est juste que Windows teste arbitrairement que l'exception se trouve bien dans la pile. Mais à part ça, rien ne l'empêcherait de faire avec. Il ne libère pas explicitement la pile, seulement en modifiant esp. Il ne libère pas non plus le tas : je m'en occuppe.

    Où donc est le problème ? Windows ne veut juste pas qu'on fasse d'une autre manière que celle qu'il a prévue ?
    Citation Envoyé par CleeM
    Au final : tu n'as pas d'autres moyen que de pousser la structure sur la pile afin de maintenir la liste chaînée des SEHs...
    Si donc c'est bien le cas - ce qui semble, vue ce que je viens de montrer -, aurais-tu une idée sur la façon dont je pourrais faire ?
    Un autre endroit où je pourrais stocker cette fameuse adresse de retour, pour laquelle je dois faire tout ça ? Sachant que ce doit être thread-safe (c'est pour ça que je trouvais l'endroit où l'on stocke les exceptions idéal pour le faire).
    À la limite, s'il doit y avoir une fuite mémoire en cas d'exception, tant pis, on peut faire avec.

    beaucoup en tous cas On voit que tu gères
    sjrd, ancien rédacteur/modérateur Delphi.
    Auteur de Scala.js, le compilateur de Scala vers JavaScript, et directeur technique du Scala Center à l'EPFL.
    Découvrez Mes tutoriels.

  4. #4
    Rédacteur
    Avatar de Neitsa
    Homme Profil pro
    Chercheur sécurité informatique
    Inscrit en
    Octobre 2003
    Messages
    1 041
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Chercheur sécurité informatique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 1 041
    Points : 1 956
    Points
    1 956
    Par défaut
    Citation Envoyé par sjrd
    Il y a juste un truc qui m'embête :
    Il me semblait avoir compris que le premier champ de la EXECTION_REGISTRATION était justement un pointeur vers la précédente, ce qui forme une liste chaînée.
    Exactement !

    Or en 32 bits protégé, les pointeurs se baladent entre segments sans le moindre problème, et donc aussi bien entre pile et tas.
    Même si ça n'est pas le fond du problème, en mode protégé sous Windows, la base des segments et la taille de ceux-ci sont égales pour CS, DS, SS et ES mais pas FS (et ni GS mais lui n'est pas utilisé...).

    FS abrite un zone mémoire particulière qui enferme des informations propres aux thread et au processus. C'est un segment particulier sous Windows.

    Donc je ne vois pas pourquoi Windows ne parviendrait pas à "suivre" le pointeur FS:[0] en dehors de la pile, pour ensuite y revenir avec le champ prev de ma structure ?

    Je refuse peut-être l'évidence, mais je suppose que tu dois savoir que, quand on fait de l'assembleur, on aime comprendre
    Oui effectivement, je vois le fond du problème. Tu as tout à fait raison d'un point de vue logique. On est tout à fait en droit d'attendre le fait que si l'on met FS:[0] à la bonne valeur, Windows saura où trouver le reste des structures EXCEPTION_REGISTRATION en suivant simplement le pointeur (qui est bien un PEXCEPTION_REGISTRATION)...

    Mais en fait, non... Windows ne suit pas ce pointeur lors du déclenchement d'exception... (j'avoue que c'est mal fichu).

    En déboguant attentivement ce qui se passe après l'exception, je suis tombé sur ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ; Avant : ebx = EXCEPTION_REGISTRATION
    ;         ebp = FS:0
    ;         => [ebp-08] = bas de la pile
    ;            [ebp-0C] = haut de la pile
    cmp     ebx,[ebp-08]
    jb      @invalid
    lea     eax,[ebx+08]
    cmp     eax,[ebp-0C]
    ja      @invalid
    test    bl,03
    jnz     @invalid
    Apparemment, c'est juste que Windows teste arbitrairement que l'exception se trouve bien dans la pile. Mais à part ça, rien ne l'empêcherait de faire avec. Il ne libère pas explicitement la pile, seulement en modifiant esp. Il ne libère pas non plus le tas : je m'en occuppe.
    Il fallait regarder deux CALLs plus bas, mais tu n'étais vraiment pas loin :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    PUSH DWORD PTR DS:[EBX+4] ;adresse du SEH courant sur la pile !!!
    LEA EAX,DWORD PTR SS:[EBP-14]
    PUSH EAX
    PUSH DWORD PTR SS:[EBP+C]
    PUSH EBX
    PUSH ESI
    CALL ntdll.7C903753
    Ensuite tu traces dans ce CALL jusqu'au CALL ECX qui appelle la routine (celle qu'on a mis en place) du SEH.

    On s'aperçoit là que si l'on met en place en SEH qui n'a pas été "posé" sur la pile les paramètre passés à ce CALL sont mauvais. C'est là que Windows aurait dû utiliser le pointeur partant de FS:[0] et non pas sur la pile... Mais là, on ne peut vraiment rien y faire du tout...

    Où donc est le problème ? Windows ne veut juste pas qu'on fasse d'une autre manière que celle qu'il a prévue ?
    Voilà, tout est dit

    Si donc c'est bien le cas - ce qui semble, vue ce que je viens de montrer -, aurais-tu une idée sur la façon dont je pourrais faire ?
    Un autre endroit où je pourrais stocker cette fameuse adresse de retour, pour laquelle je dois faire tout ça ? Sachant que ce doit être thread-safe (c'est pour ça que je trouvais l'endroit où l'on stocke les exceptions idéal pour le faire).
    À la limite, s'il doit y avoir une fuite mémoire en cas d'exception, tant pis, on peut faire avec.
    Tu peux toujours essayer de mettre un handler final avec l'API SetUnhandledExceptionFilter :
    http://msdn2.microsoft.com/en-us/library/ms680634.aspx

    (par contre on ne peut pas débugger avec cette API, le handler n'est pas pris en compte dans ce cas là).

    Sinon sous les derniers noyaux il y a les VEHs (Vectored Exception Handler) :
    http://msdn2.microsoft.com/en-us/library/ms681420.aspx

    P.S : par contre je ne comprend pas très bien pourquoi tu ne peux pas faire les deux PUSHs nécessaires à mettre un SEH (enfin EXCEPTION_REGISTRATION) sur la pile...

  5. #5
    Expert éminent sénior

    Avatar de sjrd
    Homme Profil pro
    Directeur de projet
    Inscrit en
    Juin 2004
    Messages
    4 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : Suisse

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2004
    Messages : 4 517
    Points : 10 154
    Points
    10 154
    Par défaut
    Citation Envoyé par Neitsa
    Même si ça n'est pas le fond du problème, en mode protégé sous Windows, la base des segments et la taille de ceux-ci sont égales pour CS, DS, SS et ES mais pas FS (et ni GS mais lui n'est pas utilisé...).

    FS abrite un zone mémoire particulière qui enferme des informations propres aux thread et au processus. C'est un segment particulier sous Windows.
    J'avais bien compris. Mais il n'empêche que le EXCEPTION_REGISTRATION n'est pas stocké dans le segment FS, seul un pointeur vers celui-ci y est stocké. Donc je ne vois pas en quoi ça pose problème.
    Citation Envoyé par Neitsa
    Il fallait regarder deux CALLs plus bas, mais tu n'étais vraiment pas loin :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    PUSH DWORD PTR DS:[EBX+4] ;adresse du SEH courant sur la pile !!!
    LEA EAX,DWORD PTR SS:[EBP-14]
    PUSH EAX
    PUSH DWORD PTR SS:[EBP+C]
    PUSH EBX
    PUSH ESI
    CALL ntdll.7C903753
    Ensuite tu traces dans ce CALL jusqu'au CALL ECX qui appelle la routine (celle qu'on a mis en place) du SEH.

    On s'aperçoit là que si l'on met en place en SEH qui n'a pas été "posé" sur la pile les paramètre passés à ce CALL sont mauvais. C'est là que Windows aurait dû utiliser le pointeur partant de FS:[0] et non pas sur la pile... Mais là, on ne peut vraiment rien y faire du tout...
    Même chose, j'ai l'impression que le problème n'est pas là : ici on passe simplement en paramètre stdcall à ntdll.7C903753, un pointeur vers le handler. Non ?


    Enfin dans tous les cas, il semble donc que ce que je veux faire est totalement impossible. Que Windows ait raison de le bloquer ou pas, il le bloque. Il faut donc chercher ailleurs.
    Citation Envoyé par Neitsa
    Tu peux toujours essayer de mettre un handler final avec l'API SetUnhandledExceptionFilter :
    http://msdn2.microsoft.com/en-us/library/ms680634.aspx

    (par contre on ne peut pas débugger avec cette API, le handler n'est pas pris en compte dans ce cas là).
    Hum... Je ne suis pas sûr que cela réponde à mon besoin. Ce handler "par défaut" n'est appelé que si on sort complètement de l'imbrication des try..except, ce qui n'arrive jamais en Delphi , car il y en a toujours un au plus haut niveau.
    Citation Envoyé par Neitsa
    Sinon sous les derniers noyaux il y a les VEHs (Vectored Exception Handler) :
    http://msdn2.microsoft.com/en-us/library/ms681420.aspx
    Je vais regarder ça.
    Citation Envoyé par Neitsa
    P.S : par contre je ne comprend pas très bien pourquoi tu ne peux pas faire les deux PUSHs nécessaires à mettre un SEH (enfin EXCEPTION_REGISTRATION) sur la pile...
    Comme je l'ai dit au début, mon but est d'ajouter un paramètre à un appel de méthode. Je dois ajouter ce paramètre en première place - ça tombe bien en cdecl c'est sur la tête de la pile. Mais je ne dois pas toucher aux autres paramètres, qui doivent garder leur adresse relative +4 (pour le paramètre ajouté) par rapport à esp à l'entrée dans la méthode.
    Et surtout : je dois libérer ce paramètre en sortie de la méthode, avant de retourner dans le code appelant, parce que c'est comme ça que fonctionne le cdecl.

    C'est pour ça que je ne peux absolument pas changer la pile en dehors de l'adresse de retour et du paramètre que j'ajoute. Donc je n'ai absolument pas la place pour ajouter mon EXCEPTION_REGISTRATION.

    En plus, comble de tout, je ne peux même pas aller la placer avant tous les paramètres, car les routines cdecl ont la facheuse propriété de pouvoir avoir des paramètres variables

    J'avais essayé aussi d'aller la placer "assez loin" (256 octets plus haut dans la pile), en déplaçant tout le contenu de la pile à cet endroit. Et même en prenant la précaution de ne pas aller plus loin que le dernier gestionnaire d'exception enregistré.
    Le problème, c'est que je déplace de la mémoire dont on peut avoir donné l'adresse Ce qui évidemment plante tout

    Ma dernière solution en date est d'utiliser un Thread Local Storage pour stocker une liste chaînée des appels effectués, en "tolérant" des éléments conservés "en trop" - à cause d'exceptions déclenchées dans la méthode appelée, qui court-circuitent le pop de mes éléments dans la liste.

    Ca marche pas encore tout à fait, mais j'ai de l'espoir. Je vous tiens au courant de toutes façons
    sjrd, ancien rédacteur/modérateur Delphi.
    Auteur de Scala.js, le compilateur de Scala vers JavaScript, et directeur technique du Scala Center à l'EPFL.
    Découvrez Mes tutoriels.

  6. #6
    Rédacteur
    Avatar de Neitsa
    Homme Profil pro
    Chercheur sécurité informatique
    Inscrit en
    Octobre 2003
    Messages
    1 041
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Chercheur sécurité informatique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 1 041
    Points : 1 956
    Points
    1 956
    Par défaut
    Même chose, j'ai l'impression que le problème n'est pas là : ici on passe simplement en paramètre stdcall à ntdll.7C903753, un pointeur vers le handler. Non ?

    Enfin dans tous les cas, il semble donc que ce que je veux faire est totalement impossible. Que Windows ait raison de le bloquer ou pas, il le bloque. Il faut donc chercher ailleurs.
    Oui c'est bien ça, mais comme tu le dis, Windows veut absolument un pointeur sur la pile... Donc on est bloqué.

    C'est là que je me suis dit qu'il y avait moyen de "ruser". En allant chercher tout en haut de la pile (donc vers les adresses les plus basses), on peut y mettre notre handler et la routine de vérification du SEH n'y verra que du feu :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    	mov eax, fs:[18h]; EAX = pointeur sur TEB (NT_TIB)
    	mov eax, [eax+8]; stack limit 	
    	mov edx, fs:[0] ; obtient le handler actuel
    	mov [eax], edx ; écrit tout en haut de la pile (EXCEPTION_REGISTRATION.PreviousHandler)
    	mov fs:[0],eax ; sauve le handler atcuel
    	mov ebx, offset MonHandlerdeSEH	; adresse handler perso
    	mov [eax+4], ebx ; sauve l'adresse du handler perso sur la pile (EXCEPTION_REGISTRATION.Handler)
    Ce bout de code (testé et vérifié) va chercher la limite de la pile et y écrit la structure EXCEPTION_REGISTRATION avec notre handler perso. Comme ça reste sur la pile, Windows n'a rien à dire.

    L'avantage est que l'on est vraiment tout en haut de la pile, donc il y a peu de chance que le handler soit effacé et ça ne change en rien le stack frame courant.

    A voir si ce bout de code te convient .

    [Edit]

    Mais en fait, j'y pense, si tu faisais quelque chose comme ça (je n'y connais pas grand chose en Delphi) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    CALL SetSEH
    ...
     
    Procedure SetSeh
     
    // mise en place du SEH
    CALL UneAutreFonction  //mise en place d'un autre stack frame dans cette PROC
    end;
    Si tu appelles tes autres fonction depuis celle qui met en place le SEH, celle-ci auront leur propre fenêtre de pile (stack frame). Du coup tu ne sera pas gêné par la structure EXCEPTION_REGISTRATION sur la pile...

  7. #7
    Expert éminent sénior

    Avatar de sjrd
    Homme Profil pro
    Directeur de projet
    Inscrit en
    Juin 2004
    Messages
    4 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : Suisse

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2004
    Messages : 4 517
    Points : 10 154
    Points
    10 154
    Par défaut
    Eh ! C'est pas mal, ça

    Mais comment fais-tu dans le cas d'appels récursifs à des routines de ce type : il faut construire une pile... au bout de la pile Pas facile à gérer... Surtout si un autre sous-système utilise la même ruse
    Et puis la pile n'a pas sa taille définitive dès le début. Elle peut s'agrandir au fur et à mesure de l'exécution, selon les besoins de l'application.

    Pendant ce temps, je n'ai pas chômé : j'ai poursuivi mon idée dans la TLS, et ça fonctione plutôt pas mal Mis à part que je peux avoir de légères fuites mémoires en cas d'exception dans une méthode appelée, dans un thread, et si aucune routine truckée de cette façon n'est rappelée d'ici la fin du thread. On peut aussi appeler une routine ad hoc en fin de thread pour s'assurer que tout est libéré, si on a la main sur le top-level du thread.

    Ca donne ça (désolé il y a moins d'assembleur )
    Code Delphi : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    type
      PCDeclCallInfo = ^TCDeclCallInfo;
      TCDeclCallInfo = packed record
        Previous : PCDeclCallInfo;
        StackPointer : Pointer;
        ReturnAddress : Pointer;
      end;
     
    var
      /// Index de TLS pour les infos sur les appels cdecl dans le thread
      CDeclCallInfoTLSIndex : Cardinal = 0;
     
    {*
      Trouve la dernière info de routine cdecl valide
      @param StackPointer   Valeur du registre esp au moment de l'appel
      @param AllowSame      True pour permettre un StackPointer égal, False sinon
      @return Pointeur sur la dernière info de routine cdecl valide
    *}
    function GetLastValidCDeclCallInfo(StackPointer : Pointer;
      AllowSame : boolean) : PCDeclCallInfo;
    var Previous : PCDeclCallInfo;
    begin
      Result := TlsGetValue(CDeclCallInfoTLSIndex);
      while (Result <> nil) and
        (Cardinal(Result.StackPointer) <= Cardinal(StackPointer)) do
      begin
        if AllowSame and (Result.StackPointer = StackPointer) then
          Break;
        Previous := Result.Previous;
        Dispose(Result);
        Result := Previous;
      end;
    end;
     
    {*
      S'assure que toutes les informations de routines cdecl sont supprimées
      Cette routine devrait être appelée à la fin de chaque thread susceptible
      d'utiliser des routines issues de méthodes cdecl.
      Si ce n'est pas possible, ce n'est pas dramatique, mais l'application
      s'expose à de légères fuites mémoire en cas d'exception à l'intérieur de
      telles méthodes.
      Pour le thread principal, ce n'est pas nécessaire, le code de finalisation de
      ScDelphiLanguage.pas s'en charge.
    *}
    procedure ClearCDeclCallInfo;
    begin
      GetLastValidCDeclCallInfo(Pointer($FFFFFFFF), False);
      TlsSetValue(CDeclCallInfoTLSIndex, nil);
    end;
     
    {*
      Ajoute une adresse de retour
      @param ReturnAddress   Adresse à ajouter
    *}
    procedure StoreCDeclReturnAddress(
      StackPointer, ReturnAddress : Pointer); stdcall;
    var LastInfo, CurInfo : PCDeclCallInfo;
    begin
      LastInfo := GetLastValidCDeclCallInfo(StackPointer, False);
     
      New(CurInfo);
      CurInfo.Previous := LastInfo;
      CurInfo.StackPointer := StackPointer;
      CurInfo.ReturnAddress := ReturnAddress;
      TlsSetValue(CDeclCallInfoTLSIndex, CurInfo);
    end;
     
    {*
      Récupère une adresse de retour
      @return L'adresse de retour qui a été ajoutée en dernier
    *}
    function GetCDeclReturnAddress(StackPointer : Pointer) : Pointer; stdcall;
    var LastInfo : PCDeclCallInfo;
    begin
      LastInfo := GetLastValidCDeclCallInfo(StackPointer, True);
     
      if (LastInfo = nil) or (LastInfo.StackPointer <> StackPointer) then
      begin
        TlsSetValue(CDeclCallInfoTLSIndex, LastInfo);
        Assert(False);
      end;
     
      TlsSetValue(CDeclCallInfoTLSIndex, LastInfo.Previous);
      Result := LastInfo.ReturnAddress;
      Dispose(LastInfo);
    end;
     
    procedure ProcForCDecl3;
    asm
            PUSH    ESP
            CALL    StoreCDeclReturnAddress
            PUSH    GlobObjAddress
            CALL    TMyClass.SomeCDecl
            MOV     [ESP],EAX
            PUSH    ESP
            CALL    GetCDeclReturnAddress
            XCHG    EAX,[ESP]
    end;
     
    initialization
      CDeclCallInfoTLSIndex := TlsAlloc;
    finalization
      ClearCDeclCallInfo;
      TlsFree(CDeclCallInfoTLSIndex);
    end.
    sjrd, ancien rédacteur/modérateur Delphi.
    Auteur de Scala.js, le compilateur de Scala vers JavaScript, et directeur technique du Scala Center à l'EPFL.
    Découvrez Mes tutoriels.

  8. #8
    Expert éminent sénior

    Avatar de sjrd
    Homme Profil pro
    Directeur de projet
    Inscrit en
    Juin 2004
    Messages
    4 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : Suisse

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2004
    Messages : 4 517
    Points : 10 154
    Points
    10 154
    Par défaut
    Citation Envoyé par Neitsa
    Si tu appelles tes autres fonction depuis celle qui met en place le SEH, celle-ci auront leur propre fenêtre de pile (stack frame). Du coup tu ne sera pas gêné par la structure EXCEPTION_REGISTRATION sur la pile...
    Le problème, c'est que je ne connais pas à l'avance les méthodes employées. Je ne peux donc pas créer une routine pour chaque méthode. Je crée cette petite routine en mémoire au moment de l'exécution, en fonction de la méthode à appeler, et du premier paramètre à ajouter (qui sont alors toutes les deux hard-codées dans les opcodes que je génère).

    La vraie routine que j'utilise à la fin est celle-ci :
    Code Delphi : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    {*
      Construit une routine équivalente à une méthode cdecl
      MakeProcOfMethod permet d'obtenir un pointeur sur une routine, construite
      dynamiquement, qui équivaut à une méthode. Ce qui signifie que la routine
      renvoyée commence par ajouter un paramètre supplémentaire, avant d'appeler
      la méthode initiale.
      La procédure devra être libérée avec FreeProcOfMethod une fois utilisée.
      @param Method         Méthode à convertir
    *}
    function MakeProcOfCDeclMethod(const Method : TMethod) : Pointer;
     
    type
      PCDeclRedirector = ^TCDeclRedirector;
      TCDeclRedirector = packed record
        PushESP : Byte;
        CallStoreAddress : Byte;
        StoreAddressOffset : integer;
        PushObj : Byte;
        ObjAddress : Pointer;
        CallMethod : Byte;
        MethodCodeOffset : integer;
        MovESPEAX : array[0..2] of Byte;
        PushESP2 : Byte;
        CallGetAddress : Byte;
        GetAddressOffset : integer;
        Xchg : array[0..2] of Byte;
        Ret : Byte;
      end;
     
    const
      Code : array[0..28] of Byte = (
        $54,                     // PUSH    ESP
        $E8, $FF, $FF, $FF, $FF, // CALL    StoreCDeclReturnAddress
        $68, $EE, $EE, $EE, $EE, // PUSH    ObjAddress
        $E8, $DD, $DD, $DD, $DD, // CALL    MethodCode
        $89, $04, $24,           // MOV     [ESP],EAX
        $54,                     // PUSH    ESP
        $E8, $CC, $CC, $CC, $CC, // CALL    PopCDeclReturnAddress
        $87, $04, $24,           // XCHG    EAX,[ESP]
        $C3                      // RET
      );
     
    begin
      GetMem(Result, sizeof(Code));
      Move(Code[0], Result^, sizeof(Code));
     
      with PCDeclRedirector(Result)^ do
      begin
        StoreAddressOffset := JmpArgument(@CallStoreAddress,
          @StoreCDeclReturnAddress);
        ObjAddress := Method.Data;
        MethodCodeOffset := JmpArgument(@CallMethod, Method.Code);
        GetAddressOffset := JmpArgument(@CallGetAddress, @GetCDeclReturnAddress);
      end;
    end;
    sjrd, ancien rédacteur/modérateur Delphi.
    Auteur de Scala.js, le compilateur de Scala vers JavaScript, et directeur technique du Scala Center à l'EPFL.
    Découvrez Mes tutoriels.

Discussions similaires

  1. Réponses: 3
    Dernier message: 23/03/2007, 16h23
  2. Réponses: 31
    Dernier message: 06/10/2006, 12h34
  3. Réponses: 1
    Dernier message: 14/08/2006, 11h35
  4. Est il possible de mettre de la couleur?
    Par miron dans le forum C
    Réponses: 14
    Dernier message: 20/06/2006, 12h20
  5. Réponses: 2
    Dernier message: 02/02/2006, 09h07

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo