Pregunta Encontrar el tamaño del marco de la pila


El marco de pila de una función de llamada se puede obtener fácilmente a través de __builtin_frame_address(1), pero ¿qué pasa con el tamaño del marco de pila?

¿Hay alguna función que me permita saber qué tan grande es el marco de pila de la función de llamada?


13
2018-02-01 17:31


origen


Respuestas:


Mi primera reacción habría sido, ¿por qué alguien querría esto? Se debe considerar una mala práctica para una función C determinar dinámicamente el tamaño del marco de la pila. El objetivo de cdecl (el convención clásica de llamadas C) es que la función misma (el 'llamado') no tiene conocimiento del tamaño del marco de la pila. Cualquier desviación de esa filosofía puede hacer que su código se rompa al cambiar a una plataforma diferente, un tamaño de dirección diferente (por ejemplo, de 32 bits a 64 bits), un compilador diferente o incluso configuraciones de compilador diferentes (en particular optimizaciones).

Por otro lado, desde gcc ya ofrece esta función __builtin_frame_address, será interesante ver cuánta información puede derivarse de allí.

Desde el documentación:

La dirección de marco normalmente es la dirección de la primera palabra que la función empuja a la pila.

En x86, una función generalmente comienza con:

push ebp       ; bp for 16-bit, ebp for 32-bit, rbp for 64-bit

En otras palabras, __builtin_frame_address devuelve el puntero de base del marco de pila de la persona que llama. Desafortunadamente, el puntero base dice poco o nada acerca de dónde comienza o termina cualquier estructura de pila; el puntero base apunta a una ubicación que está en algún lugar en el medio del marco de la pila (entre parámetros y el variables locales)

Si solo está interesado en la parte del marco de pila que contiene las variables locales, entonces la función en sí tiene todo el conocimiento. El tamaño de esa parte es la diferencia entre el puntero de pila y el puntero de base.

register char * const basepointer  asm("ebp");
register char * const stackpointer asm("esp");

size_localvars = basepointer - stackpointer;

Tenga en cuenta que gcc parece asignar espacio en la pila desde el principio que se utiliza para mantener los parámetros de otras funciones llamadas desde el interior del destinatario. Estrictamente hablando, ese espacio pertenece a los marcos de pila de esas otras funciones, pero el límite no está claro. Si esto es un problema, depende de su propósito; ¿Qué vas a hacer con el tamaño del marco de pila calculado?

En cuanto a la otra parte (los parámetros), eso depende. Si su función tiene un número fijo de parámetros, entonces simplemente podría medir el tamaño de los parámetros (formales). No garantiza que la persona que llama en realidad empujó la misma cantidad de parámetros en la pila, pero suponiendo que la persona que llama compiló sin advertencias contra el prototipo de Callee, debería estar bien.

void callee(int a, int b, int c, int d)
{
    size_params = sizeof d + (char *)&d - (char *)&a;
}

Puede combinar las dos técnicas para obtener el fotograma completo (incluida la dirección de retorno y el puntero base guardado):

register char * const stackpointer asm("esp");

void callee(int a, int b, int c, int d)
{
    total_size = sizeof d + (char *)&d - stackpointer;
}

Sin embargo, si su función tiene un número variable de parámetros (una 'elipsis', como printf tiene), entonces el tamaño de los parámetros es conocido solo por la persona que llama. A menos que el destinatario tenga una forma de derivar el tamaño y el número de parámetros (en el caso de un printfla función de estilo, mediante el análisis de la cadena de formato), debería permitir que la persona que llama transmita esa información al destinatario.

EDITAR: Tenga en cuenta que esto solo funciona para permitir que una función mida su propio marco de pila. Un destinatario no puede calcular el tamaño del marco de pila de su interlocutor; Callee tendrá que pedirle a la persona que llama esa información.

Sin embargo, callee poder hacer una conjetura sobre el tamaño de las variables locales de la persona que llama. Este bloque comienza donde terminan los parámetros de llamada (sizeof d + (char *)&d), y termina en el puntero de la base del llamante (__builtin_frame_address(1)) La dirección de inicio puede ser un poco inexacta debido a la alineación de direcciones impuesta por el compilador; el tamaño calculado puede incluir una pieza de espacio de pila no utilizado.

void callee(int a, int b, int c, int d)
{
   size_localvars_of_caller = __builtin_frame_address(1) - sizeof d - (char *)&d;
}

16
2018-02-01 23:04