assembly - GAS: Explicación de.cfi_def_cfa_offset
callstack dwarf (2)
Me gustaría obtener una explicación de los valores utilizados con las directivas .cfi_def_cfa_offset en el ensamblado generado por GCC. Sé vagamente que las directivas .cfi están involucradas en los marcos de llamada y el desenrollado de la pila, pero me gustaría una explicación más detallada de por qué, por ejemplo, los valores 16 y 8 se usan en el ensamblado generado por GCC al compilar el siguiente programa C. en mi máquina Ubuntu de 64 bits.
El programa C:
#include <stdio.h>
int main(int argc, char** argv)
{
printf("%d", 0);
return 0;
}
Invoqué a GCC en el archivo fuente test.c de la siguiente manera: gcc -S -O3 test.c
Sé que -O3 permite la optimización no estándar, pero quería limitar el tamaño del ensamblaje generado en aras de la brevedad.
El ensamblaje generado:
.file "test.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB22:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
xorl %edx, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
call __printf_chk
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE22:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
.section .note.GNU-stack,"",@progbits
¿Por qué se utilizan los valores 16 y 8 para las directivas .cfi_def_cfa_offset en el ensamblaje generado? Además, ¿por qué el número 22 utilizado para la función local comienza y las etiquetas de función final?
Me gustaría obtener una explicación de los valores utilizados con las directivas
.cfi_def_cfa_offset
en el ensamblado generado por GCC.
Matthew brindó una buena explicación. Aquí está la definición de la Sección 7.10 Directivas CFI en el manual de GAS:
.cfi_def_cfa_offset
modifica una regla para calcular CFA. El registro sigue siendo el mismo, pero el desplazamiento es nuevo. Tenga en cuenta que es el desplazamiento absoluto que se agregará a un registro definido para calcular la dirección CFA.
Y .cfi_adjust_cfa_offset
:
Igual que
.cfi_def_cfa_offset
pero el desplazamiento es un valor relativo que se suma / resta del desplazamiento anterior.
Como dice la especificación DWARF en la sección 6.4:
[...] El marco de llamada se identifica por una dirección en la pila. Nos referimos a esta dirección como Canonical Frame Address o CFA. Típicamente, el CFA se define como el valor del puntero de la pila en el sitio de la llamada en el cuadro anterior (que puede ser diferente de su valor en la entrada al cuadro actual).
main()
se llama desde otro lugar (en el código de compatibilidad de tiempo de ejecución de libc
C) y, en el momento en que se ejecuta la instrucción de call
, %rsp
apuntará a la parte superior de la pila (que es la dirección más baja: la pila crece hacia abajo ), sea lo que sea (exactamente lo que es no importa aquí):
: : ^
| whatever | <--- %rsp | increasing addresses
+----------------+ |
El valor de %rsp
en este punto es el "valor del puntero de la pila en el sitio de la llamada", es decir, el CFA tal como lo define la especificación.
A medida que se ejecuta la instrucción de call
, empujará una dirección de retorno de 64 bits (8 bytes) a la pila:
: :
| whatever | <--- CFA
+----------------+
| return address | <--- %rsp == CFA - 8
+----------------+
Ahora estamos ejecutando el código en main
, que ejecuta subq $8, %rsp
para reservar otros 8 bytes de stack por sí mismo:
: :
| whatever | <--- CFA
+----------------+
| return address |
+----------------+
| reserved space | <--- %rsp == CFA - 16
+----------------+
El cambio de puntero de pila se declara en la información de depuración utilizando la directiva .cfi_def_cfa_offset
, y puede ver que el CFA ahora tiene un desplazamiento de 16 bytes desde el puntero de pila actual.
Al final de la función, la addq $8, %rsp
cambia el puntero de la pila de nuevo, por .cfi_def_cfa_offset
que se inserta otra directiva .cfi_def_cfa_offset
para indicar que el CFA está ahora a un desplazamiento de solo 8 bytes del puntero de la pila.
(El número "22" en las etiquetas es solo un valor arbitrario. El compilador generará nombres de etiqueta únicos basados en algunos detalles de implementación, como su numeración interna de bloques básicos).