2009-12-04 14 views
14

Je suis en train de porter une application de x86 à x64. J'utilise Visual Studio 2009; la majeure partie du code est en C++ et certaines parties sont en langage C. Le mot-clé __asm ​​n'est pas supporté lors de la compilation vers x64 et notre application contient quelques parties de l'assembleur en ligne. Je n'ai pas écrit ce code, donc je ne sais pas exactement ce que et est censé faire:Comment obtenir l'adresse du pointeur de pile de base

int CallStackSize() { 
    DWORD Frame; 
    PDWORD pFrame; 
    __asm 
     { 
      mov EAX, EBP 
      mov Frame, EAX 
     } 
    pFrame = (PDWORD)Frame; 
    /*... do stuff with pFrame here*/ 
} 

est le pointeur RASE de base à la pile de la fonction en cours. Est-il possible d'obtenir le pointeur de la pile sans utiliser inline asm? J'ai regardé l'intrinsèque que Microsoft propose comme un substitut à l'asm inline mais je n'ai pas trouvé quelque chose qui m'a donné quelque chose d'utile. Des idées? Andreas a demandé ce qui était fait avec pFrame. Voici la fonction complète:

int CallStackSize(DWORD frameEBP = 0) 
{ 
    DWORD pc; 
    int tmpint = 0; 
    DWORD Frame; 
    PDWORD pFrame, pPrevFrame; 

    if(!frameEBP) // No frame supplied. Use current. 
    { 
     __asm 
     { 
      mov EAX, EBP 
      mov Frame, EAX 
     } 
    } 
    else Frame = frameEBP; 

    pFrame = (PDWORD)Frame; 
    do 
    { 
     pc = pFrame[1]; 
     pPrevFrame = pFrame; 
     pFrame = (PDWORD)pFrame[0]; // precede to next higher frame on stack 

     if ((DWORD)pFrame & 3) // Frame pointer must be aligned on a DWORD boundary. Bail if not so. 
     break; 

     if (pFrame <= pPrevFrame) 
     break; 

     // Can two DWORDs be read from the supposed frame address? 
     if(IsBadWritePtr(pFrame, sizeof(PVOID)*2)) 
     break; 

     tmpint++; 
    } while (true); 
    return tmpint; 
} 

La variable pc n'est pas utilisée. Il semble que cette fonction descende la pile jusqu'à ce qu'elle échoue. Il suppose qu'il ne peut pas lire en dehors de la pile des applications, donc quand il échoue, il a mesuré la profondeur de la pile d'appels. Ce code n'a pas besoin de compiler sur le compilateur _EVERY_SINGLE là-bas. Juste VS2009. L'application n'a pas besoin de fonctionner sur EVERY_SINGLE ordinateur là-bas. Nous avons un contrôle complet du déploiement puisque nous l'installons/le configurons nous-mêmes et livrons le tout à nos clients.

+0

Que des choses se fait avec pFrame? –

Répondre

10

La chose vraiment juste à faire serait de réécrire ce que cette fonction le fait qu'il ne nécessite pas l'accès au pointeur de trame réelle. C'est définitivement un mauvais comportement.

Mais, pour faire ce que vous cherchez, vous devriez être en mesure de le faire:

int CallStackSize() { 
    __int64 Frame = 0; /* MUST be the very first thing in the function */ 
    PDWORD pFrame; 

    Frame++; /* make sure that Frame doesn't get optimized out */ 

    pFrame = (PDWORD)(&Frame); 
    /*... do stuff with pFrame here*/ 
} 

La raison pour laquelle cela fonctionne est que dans C généralement la première chose fonction n'est à mettre hors circuit l'emplacement de la base pointeur (ebp) avant d'allouer des variables locales. En créant une variable locale (Frame) et en obtenant l'adresse de if, nous obtenons vraiment l'adresse du début de la pile de la fonction.

Remarque: Certaines optimisations peuvent entraîner la suppression de la variable "Frame". Probablement pas, mais faites attention.

Deuxième Note: Votre code original et aussi ce code manipule les données pointées par « pFrame » quand « pFrame » est lui-même sur la pile. Il est possible d'écraser pFrame ici par accident et alors vous auriez un mauvais pointeur, et pourriez avoir un comportement bizarre. Soyez particulièrement attentif à cela lorsque vous passez de x86 à x64, car pFrame a maintenant 8 octets au lieu de 4, donc si votre vieux code "faire avec pFrame" représentait la taille de Frame et pFrame avant de jouer avec la mémoire, besoin de tenir compte de la nouvelle, plus grande taille.

+6

Je ne pense pas que la variable peut être supprimée si vous prenez son adresse. Cependant, le compilateur est libre de réorganiser les variables comme bon lui semble. Techniquement, il n'y a pas de garantie au niveau de la langue. Le cadre est même situé sur la pile (mais en pratique je m'attendrais à ce que cela fonctionne bien). –

+0

Ma pensée exactement, j'étais sur mon chemin pour soumettre une réponse similaire mais je me suis arrêté parce que c'est si fragile. –

+3

Et si vous le rendiez volatile? –

0

Si vous avez besoin de précision « pointeur de base », puis l'assembleur en ligne est la seule façon d'aller.

Il est, étonnamment, possible d'écrire du code qui munit la pile avec relativement peu de code spécifique à la plate-forme, mais il est difficile d'éviter complètement l'assemblage (en fonction de ce que vous faites).

Si tout ce que vous essayez de faire est d'éviter débordement de la pile, vous pouvez simplement prendre l'adresse d'une variable locale.

+0

Il semble que vous deviez utiliser un assembleur dédié (ml64) pour créer une routine qui inspecte le pointeur de pile, découvre l'adresse de la pile précédente (pour tenir compte du mouvement dans le pointeur de la pile) et le renvoie (je ne sais pas comment vous feriez cela). Puis liez-le à votre programme C. –

4

Vous pouvez utiliser la _AddressOfReturnAddress() intrinsèque pour déterminer un emplacement dans le pointeur de la trame en cours, en supposant qu'il n'a pas été complètement optimisé loin. Je suppose que le compilateur empêchera cette fonction d'optimiser le pointeur de cadre si vous vous y référez explicitement.Ou, si vous n'utilisez qu'un seul thread, vous pouvez utiliser IMAGE_NT_HEADER.OptionalHeader.SizeOfStackReserve et IMAGE_NT_HEADER.OptionalHeader.SizeOfStackCommit pour déterminer la taille de la pile du thread principal. Voir this pour savoir comment accéder au IMAGE_NT_HEADER pour l'image actuelle.

je recommande également contre l'utilisation IsBadWritePtr pour déterminer la fin de la pile. À tout le moins, vous ferez probablement croître la pile jusqu'à ce que vous atteigniez la réserve, car vous déclencherez une page de garde. Si vous voulez vraiment trouver la taille actuelle de la pile, utilisez VirtualQuery avec l'adresse que vous vérifiez.

Et si l'utilisation originale est de marcher la pile, vous pouvez utiliser StackWalk64 pour cela.

+0

Il ne voit pas l'espace restant, il remonte la chaîne des cadres de pile précédents. Fondamentalement faire un backtrace. – caf

+0

Si c'est le cas, il existe une API pour cela: StackWalk64. – MSN

+0

Je suggère que vous ajoutiez cela comme une nouvelle réponse alors, il semble que ce pourrait bien être ce dont le PO a besoin. – caf

0
.code 

PUBLIC getStackFrameADDR _getStackFrameADDR 
getStackFrameADDR: 
    mov RAX, RBP 
    ret 0 

END 

Quelque chose comme ça pourrait fonctionner pour vous.

Compilez-le avec ml64 ou jwasm et appelez-le en utilisant ceci dans votre code extern "C" void getstackFrameADDR (void);

+1

Le code x86-64 n'a généralement pas de pointeur de trame par défaut. Les deux GCC et MSVC compiler sans un. –

0

Il n'y a aucune garantie que RBP (équivalent du x64 de RASE) est en fait un pointeur sur l'image en cours dans le callstack. Je suppose que Microsoft a décidé qu'en dépit de plusieurs nouveaux registres généraux, qu'ils en avaient besoin d'un autre, RBP est seulement utilisé comme framepointer dans les fonctions qui appellent alloca(), et dans certains autres cas. Donc, même si l'assemblage en ligne était supporté, ce ne serait pas le chemin à parcourir.

Si vous voulez juste BACKTRACE, vous devez utiliser StackWalk64 dans dbghelp.dll. C'est dans le dbghelp.dll qui est livré avec XP, et pré-XP il n'y avait pas de support 64 bits, donc vous ne devriez pas avoir besoin d'expédier la DLL avec votre application.

Pour votre version 32 bits, utilisez simplement votre méthode actuelle. Vos propres méthodes seront probablement plus petites que la bibliothèque d'importation pour dbghelp, et encore moins la DLL réelle en mémoire, donc c'est une optimisation définitive (expérience personnelle: j'ai implémenté un backtrace de style Glibc et backtrace_symbols pour x86 en moins d'un dixième de la taille de la bibliothèque d'importation dbghelp).

En outre, si vous utilisez ceci pour le débogage in-process ou la génération de rapports de plantage post-release, je vous recommande fortement de travailler avec la structure CONTEXT fournie au gestionnaire d'exceptions. Peut-être un jour je déciderai de cibler le x64 sérieusement, et de trouver un moyen pas cher d'utiliser StackWalk64 que je peux partager, mais comme je cible toujours x86 pour tous mes projets, je n'ai pas pris la peine de le faire.

1

Microsoft fournit une bibliothèque (DbgHelp) qui prend en charge le stack walking et que vous devez utiliser au lieu de compter sur les astuces d'assemblage. Par exemple, si les fichiers PDB sont présents, il peut également parcourir les trames de pile optimisées (celles qui n'utilisent pas EBP).

CodeProject a un article qui explique comment l'utiliser:

http://www.codeproject.com/KB/threads/StackWalker.aspx

Questions connexes