implementando alloca
en realidad requiere la ayuda del compilador. Unas pocas personas aquí están diciendo que es tan fácil como:
sub esp, <size>
que es, lamentablemente, sólo la mitad de la imagen. Sí, eso "asignaría espacio en la pila", pero hay un par de trampas.
si el compilador había emitido código que hace referencia a otras variables relativa a esp
en lugar de ebp
(típico si se compila con ningún puntero marco). Entonces esas referencias deben ajustarse. Incluso con punteros de marco, los compiladores hacen esto a veces.
más importante, por definición, el espacio asignado con alloca
debe ser "liberado" cuando la función sale.
El grande es el punto # 2. Porque necesita el compilador para emitir código para agregar simétricamente <size>
a esp
en cada punto de salida de la función.
El caso más probable es que el compilador ofrece algunas características intrínsecas que permiten a los autores de la biblioteca solicitar al compilador la ayuda necesaria.
EDIT:
De hecho, en glibc (implementación de libc de GNU). La implementación de alloca
es simplemente esto:
#ifdef __GNUC__
# define __alloca(size) __builtin_alloca (size)
#endif /* GCC. */
EDIT:
después de pensarlo, el mínimo Creo que se necesitaría sería que el compilador para siempre usar el puntero en cualquier funciones que usa alloca
, independientemente de la configuración de optimización. Esto permitiría hacer referencia a todos los locales a través del ebp
de forma segura y la limpieza del marco se manejaría restaurando el puntero del marco al esp
.
EDIT:
Así que he hecho un poco de experimentar con cosas como esta:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define __alloca(p, N) \
do { \
__asm__ __volatile__(\
"sub %1, %%esp \n" \
"mov %%esp, %0 \n" \
: "=m"(p) \
: "i"(N) \
: "esp"); \
} while(0)
int func() {
char *p;
__alloca(p, 100);
memset(p, 0, 100);
strcpy(p, "hello world\n");
printf("%s\n", p);
}
int main() {
func();
}
que por desgracia no funciona correctamente. Después de analizar la salida de ensamblaje por gcc. Parece que las optimizaciones se interponen. El problema parece ser que, dado que el optimizador del compilador desconoce por completo mi ensamblado en línea, tiene la costumbre de hacer las cosas en un orden inesperado y todavía haciendo referencia a cosas a través de esp
.
Aquí está la ASM resultante:
8048454: push ebp
8048455: mov ebp,esp
8048457: sub esp,0x28
804845a: sub esp,0x64 ; <- this and the line below are our "alloc"
804845d: mov DWORD PTR [ebp-0x4],esp
8048460: mov eax,DWORD PTR [ebp-0x4]
8048463: mov DWORD PTR [esp+0x8],0x64 ; <- whoops! compiler still referencing via esp
804846b: mov DWORD PTR [esp+0x4],0x0 ; <- whoops! compiler still referencing via esp
8048473: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
8048476: call 8048338 <[email protected]>
804847b: mov eax,DWORD PTR [ebp-0x4]
804847e: mov DWORD PTR [esp+0x8],0xd ; <- whoops! compiler still referencing via esp
8048486: mov DWORD PTR [esp+0x4],0x80485a8 ; <- whoops! compiler still referencing via esp
804848e: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
8048491: call 8048358 <[email protected]>
8048496: mov eax,DWORD PTR [ebp-0x4]
8048499: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
804849c: call 8048368 <[email protected]>
80484a1: leave
80484a2: ret
Como se puede ver, no es tan simple. Desafortunadamente, estoy de acuerdo con mi afirmación original de que necesita ayuda del compilador.
Creo que estás bien allí; los accesos ESP escriben args antes de las llamadas a funciones, y ESP-relative es correcto. Puedes probar '-fno-accumulate-outgoing-args' o lo que sea y args relacionados para que gcc use PUSH en lugar de usar MOV para modificar la parte inferior de la pila. –
Pero en realidad, tratar de implementar alloca detrás del compilador es una terrible * idea *, como usted señala en la primera parte de esta excelente respuesta. Hay muchas maneras de que salga mal, y no hay razón para hacerlo. Si la gente quiere escribir asm y hacer su propia asignación de pila, simplemente escriba en puro asm en lugar de abusar de inline-asm en C++. –
@PeterCordes cierto que la mayoría de las referencias ESP son argumentos funcionales, pero como intentó preasignar el espacio ** antes ** de la "alloca", esos movimientos pisotearán el "espacio asignado" del usuario. Lo cual está roto si tengo la intención de usar ese espacio. Cambiarlos a empujes correctos arreglaría la mayor parte de eso. También la última referencia esp es almacenar un resultado en una variable local, y una vez más pisoteará la "matriz". Va mal bastante rápido. –