Cómo obtener la dirección del puntero de pila base

Cómo obtener la dirección del puntero de pila base

Lo realmente correcto sería reescribir lo que sea que haga esta función para que no requiera acceso al puntero de marco real. Definitivamente es un mal comportamiento.

Pero, para hacer lo que buscas, deberías poder 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 normalmente lo primero que hace una función es guardar la ubicación del puntero base (ebp) antes de asignar variables locales. Al crear una variable local (Frame) y luego obtener la dirección de if, en realidad estamos obteniendo la dirección del inicio del marco de la pila de esta función.

Nota:Algunas optimizaciones podrían hacer que se elimine la variable "Frame". Probablemente no, pero ten cuidado.

Segunda nota:su código original y también este código manipulan los datos a los que apunta "pFrame" cuando "pFrame" está en la pila. Es posible sobrescribir pFrame aquí por accidente y luego tendría un puntero incorrecto y podría tener un comportamiento extraño. Tenga esto especialmente en cuenta cuando pase de x86 a x64, porque pFrame ahora tiene 8 bytes en lugar de 4, por lo que si su antiguo código "hacer cosas con pFrame" tenía en cuenta el tamaño de Frame y pFrame antes de jugar con la memoria, debe tener en cuenta el tamaño nuevo y más grande.


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

También recomendaría no usar IsBadWritePtr para determinar el final de la pila. Como mínimo, probablemente hará que la pila crezca hasta que llegue a la reserva, ya que tropezará con una página de guardia. Si realmente desea encontrar el tamaño actual de la pila, use VirtualQuery con la dirección que está comprobando.

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


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

Si solo desea retroceder, debe usar StackWalk64 en dbghelp.dll. Está en el dbghelp.dll que se envió con XP, y antes de XP no había compatibilidad con 64 bits, por lo que no debería necesitar 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, mucho menos el dll real en la memoria, por lo que es una optimización definitiva (experiencia personal:he implementado un backtrace estilo Glibc y backtrace_symbols para x86 en menos de uno- una décima parte del tamaño de la biblioteca de importación dbghelp).

Además, si está utilizando esto para la depuración en proceso o la generación de informes de fallas posteriores al lanzamiento, le recomiendo que solo trabaje con la estructura CONTEXT proporcionada al controlador de excepciones.

Tal vez algún día decida enfocarme seriamente en el x64 y descubra una forma económica de usar StackWalk64 que pueda compartir, pero como sigo apuntando a x86 para todos mis proyectos, no me he molestado.