2009-12-04 12 views
14

Estoy en el proceso de portar una aplicación de x86 a x64. Estoy usando Visual Studio 2009; la mayor parte del código es C++ y algunas partes son C. La palabra clave __asm ​​no es compatible cuando se compila hacia x64 y nuestra aplicación contiene algunas partes del ensamblador en línea. No escribí este código, así que no sé exactamente lo que se supone et hacer:Cómo obtener la dirección del puntero de la pila base

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

EBP es el puntero de base a la pila de la función actual. ¿Hay alguna forma de obtener el puntero de la pila sin usar el asm en línea? He estado observando las características intrínsecas que ofrece Microsoft como sustituto del asm en línea, pero no pude encontrar nada que me diera algo útil. ¿Algunas ideas?

Andreas preguntó qué cosas se hacen con pFrame. Aquí está la función completa:

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 no se utiliza. Parece que esta función baja la pila hasta que falla. Supone que no puede leer fuera de la pila de aplicaciones, por lo que cuando falla, mide la profundidad de la pila de llamadas. Este código no necesita compilarse en el compilador _EVERY_SINGLE. Solo VS2009. La aplicación no necesita ejecutarse en EVERY_SINGLE computadora por ahí. Tenemos control total de la implementación ya que la instalamos/configuramos nosotros mismos y la entregamos a nuestros clientes.

+0

qué pasta está hecho con pFrame? –

Respuesta

10

Lo correcto es reescribir cualquiera que sea la función para que no requiera acceso al puntero de marco real. Eso es definitivamente un mal comportamiento.

Pero, para hacer lo que busca usted debería ser capaz de hacer:

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 razón por la que esto funciona es que en C por lo general lo primero que una función que hace es guardar fuera de la ubicación de la base puntero (ebp) antes de asignar variables locales. Al crear una variable local (Frame) y luego obtener la dirección de if, estamos obteniendo la dirección del inicio del stack frame de esta función.

Nota: Algunas optimizaciones pueden hacer que se elimine la variable "Marco". Probablemente no, pero ten cuidado.

Segunda Nota: Su código original y también este código manipula los datos apuntados por "pFrame" cuando "pFrame" está en la pila. Es posible sobrescribir pFrame aquí por accidente y entonces tendría un puntero malo y podría tener un comportamiento extraño. Tenga especialmente en cuenta esto al pasar de x86 a x64, porque pFrame ahora tiene 8 bytes en lugar de 4, así que si su antiguo código "do stuff with pFrame" contabiliza el tamaño de Frame y pFrame antes de jugar con la memoria, tendrá necesita dar cuenta del tamaño nuevo y más grande.

+6

No creo que la variable se pueda eliminar si toma su dirección. Sin embargo, el compilador puede reordenar variables de la manera que quiera. Técnicamente, no hay garantía de nivel de lenguaje. Frame incluso está ubicado en la pila (pero en la práctica, espero que funcione bien). –

+0

Mi pensamiento, exactamente, estaba en camino a enviar una respuesta similar pero me detuve porque se siente tan frágil. –

+3

¿Qué pasa si lo haces 'volátil'? –

0

Si necesita el "puntero de base" preciso, entonces el ensamblaje en línea es el único camino a seguir.

Sorprendentemente, es posible escribir código que envuelve la pila con relativamente poco código específico de la plataforma, pero es difícil evitar el ensamblaje (dependiendo de lo que esté haciendo).

Si todo lo que intenta hacer es evitar desbordar la pila, puede simplemente tomar la dirección de cualquier variable local.

+0

Parece que tendrá que usar un ensamblador dedicado (ml64) para crear una rutina que inspeccione el puntero de pila, descubra la dirección del marco de pila anterior (para tener en cuenta el movimiento en el puntero de pila cuando llame a su rutina de ensamblador). y lo devuelve (no sé cómo harías esto). Luego, vincúlelo a su programa C. –

4

Puede usar el _AddressOfReturnAddress() intrínseco para determinar una ubicación en el puntero de marco actual, suponiendo que no se haya optimizado completamente. Supongo que el compilador evitará que esa función optimice el puntero del marco si se refiere explícitamente a él.O bien, si solo usa un solo hilo, puede usar IMAGE_NT_HEADER.OptionalHeader.SizeOfStackReserve y IMAGE_NT_HEADER.OptionalHeader.SizeOfStackCommit para determinar el tamaño de la pila del hilo principal. Consulte this para saber cómo acceder al IMAGE_NT_HEADER para obtener la imagen actual.

También recomendaría no usar IsBadWritePtr para determinar el final de la pila. Por lo menos, es probable que la pila crezca hasta que llegues a la reserva, ya que dispararás una página de guardia. Si realmente desea encontrar el tamaño actual de la pila, use VirtualQuery con la dirección que está verificando.

Y si el uso original es caminar la pila, puede usar StackWalk64 para eso.

+0

No se trata de ver cuánto espacio queda, sino de volver a recorrer la cadena de fotogramas previos de la pila. Básicamente haciendo un seguimiento. – caf

+0

Si ese es el caso, hay una API para eso: StackWalk64. – MSN

+0

Sugiero que lo agregue como una nueva respuesta, parece que podría ser lo que el OP necesita. – caf

0
.code 

PUBLIC getStackFrameADDR _getStackFrameADDR 
getStackFrameADDR: 
    mov RAX, RBP 
    ret 0 

END 

Algo así podría funcionar para usted.

Compilarlo con ml64 o jwasm y llámelo utilizando su código extern "C" void getstackFrameADDR (void);

+1

x86-64 código no suele tener un puntero de marco por defecto. Tanto GCC como MSVC compilan sin una. –

0

No hay garantía de que RBP (el equivalente de x64 de EBP) sea realmente un puntero al fotograma actual en la pila de llamadas. Supongo que Microsoft decidió que a pesar de varios nuevos registros de propósito general, que necesitaban otro liberado, RBP solo se usa como framepointer en funciones que llaman alloca() y en algunos otros casos. Entonces, incluso si el ensamblado en línea fuera compatible, no sería el camino a seguir.

Si solo desea rastrear, necesita usar StackWalk64 en dbghelp.dll. Está en el dbghelp.dll que se envía con XP, y antes de XP no había soporte de 64 bits, por lo que no debería tener que enviar el dll con su aplicación.

Para su versión de 32 bits, simplemente use su método actual. Es probable que sus propios métodos sean más pequeños que la biblioteca de importación para dbghelp, y mucho menos el dll real en la memoria, por lo que es una optimización definitiva (experiencia personal: he implementado un backtrace al estilo Glibc y backtrace_symbols para x86 en menos de uno) décimo del tamaño de la biblioteca de importación dbghelp).

Además, si está utilizando esto para la depuración durante el proceso o la generación de informes de fallos posteriores a la publicación, recomendaría simplemente trabajar con la estructura CONTEXT proporcionada al manejador de excepciones.

Tal vez algún día decida enfocarme seriamente en el x64, y encontraré una manera económica de usar StackWalk64 que pueda compartir, pero dado que todavía estoy apuntando a x86 para todos mis proyectos, no me molesté.

1

Microsoft proporciona una biblioteca (DbgHelp) que se ocupa del stack walking, y debe utilizar en lugar de confiar en los trucos de ensamblaje. Por ejemplo, si los archivos PDB están presentes, también puede recorrer cuadros de pila optimizados (aquellos que no usan EBP).

CodeProject tiene un artículo que explica cómo usarlo:

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

Cuestiones relacionadas