GAS: Объяснение .cfi_def_cfa_offset

Я хотел бы объяснить значения, используемые в директивах .cfi_def_cfa_offset в сборке, сгенерированной GCC. Я смутно знаю, что директивы .cfi задействованы в кадрах вызовов и раскручивании пакетов, но я хотел бы получить более подробное объяснение того, почему, например, значения 16 и 8 используются в сборке, выводимой GCC, при компиляции следующей программы на C на моей 64-битной машине Ubuntu.

Программа C:

#include <stdio.h>

int main(int argc, char** argv)
{
        printf("%d", 0);
        return 0;
}

Я вывел GCC в исходный файл test.c следующим образом: gcc -S -O3 test.c. Я знаю, что -O3 допускает нестандартную оптимизацию, но я хотел бы ограничить размер сгенерированной сборки ради краткости.

Сгенерированная сборка:

        .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

Почему значения 16 и 8 используются для директив .cfi_def_cfa_offset в сгенерированной сборке? Кроме того, почему номер 22, используемый для запуска локальной функции, и метки конца функции?

Ответ 1

Как показывает спецификация DWARF в разделе 6.4:

[...] Кадр вызова идентифицированный по адресу в стеке. Мы ссылаемся на этот адрес как канонический кадр Адрес или CFA. Как правило, CFA определяется как значение указателя стека на сайта вызова в предыдущем кадре (который может отличаться от его значения при входе в текущий кадр).

main() вызывается из другого места (в коде поддержки libc C), а во время выполнения команды call %rsp указывает на верхнюю часть стека (которая самый низкий адрес - стек растет вниз), что бы это ни было (точно, что это не имеет значения здесь):

:                :                              ^
|    whatever    | <--- %rsp                    | increasing addresses
+----------------+                              |

Значение %rsp в этой точке - это "значение указателя стека на сайте вызова", то есть CFA, как определено спецификацией.

По мере выполнения команды call она вытолкнет 64-битный (8-байтовый) адрес возврата в стек:

:                :
|    whatever    | <--- CFA
+----------------+
| return address | <--- %rsp == CFA - 8
+----------------+

Теперь мы запускаем код в main, который выполняет subq $8, %rsp, чтобы зарезервировать еще 8 байтов стека для себя:

:                :
|    whatever    | <--- CFA
+----------------+
| return address |
+----------------+
| reserved space | <--- %rsp == CFA - 16
+----------------+

Изменение указателя стека объявляется в информации отладки с помощью директивы .cfi_def_cfa_offset, и вы можете видеть, что CFA теперь находится на расстоянии 16 байт от текущего указателя стека.

В конце функции команда addq $8, %rsp снова меняет указатель стека, поэтому добавляется другая директива .cfi_def_cfa_offset, указывающая, что CFA теперь находится на смещении всего 8 байтов от указателя стека.

(Число "22" в метках - это просто произвольное значение. Компилятор будет генерировать уникальные имена ярлыков на основе некоторых деталей реализации, таких как внутренняя нумерация базовых блоков.)

Ответ 2

Я хотел бы объяснить значения, используемые с директивами .cfi_def_cfa_offset в сборке, сгенерированной GCC.

Мэтью дал хорошее объяснение. Здесь определение из Раздел 7.10 Директивы CFI в руководстве GAS:

.cfi_def_cfa_offset изменяет правило вычисления CFA. Регистр остается тем же, но смещение является новым. Обратите внимание, что это абсолютное смещение, которое будет добавлено в определенный регистр для вычисления адреса CFA.

И .cfi_adjust_cfa_offset:

То же, что и .cfi_def_cfa_offset, но смещение - относительное значение, которое добавляется/вычитается из предыдущего смещения.