Подтвердить что ты не робот

Соглашения о вызовах x86_64 и кадры стека

Я пытаюсь понять смысл исполняемого кода, который генерирует GCC (4.4.3) для машины x86_64, работающей под Ubuntu Linux. В частности, я не понимаю, как код отслеживает кадры стека. В старые времена, в 32-битном коде, я привык видеть этот "пролог" почти в каждой функции:

push %ebp
movl %esp, %ebp

Тогда, в конце функции, появится "эпилог", либо

sub $xx, %esp   # Where xx is a number based on GCC accounting.
pop %ebp
ret

или просто

leave
ret

который выполняет одно и то же:

  • Установите указатель стека в верхнюю часть текущего кадра, чуть ниже обратный адрес
  • Восстановить прежнее значение указателя фрейма.

В 64-битном коде, поскольку я вижу это через дизассемблер objdump, многие функции не следуют этому соглашению - они не нажимают% rbp, а затем сохраняют% rsp на% rbp. Как отладчик вроде GDB создает трассировка?

Моя реальная цель здесь - попытаться выяснить разумный адрес, который будет рассмотрен как верхний (самый высокий адрес) пользовательского стека, когда выполнение достигнет начала произвольной функции дальше в программу, где, возможно, указатель стека двинулся вниз. Например, для "top" исходный адрес argv был бы идеальным, но у меня нет доступа к нему из произвольной функции, которая вызывает основные вызовы. Сначала я подумал, что могу использовать старый метод backtrace: преследуя сохраненные значения указателя кадров до тех пор, пока не будет сохранено значение 0, затем следующий, который после этого может считаться наивысшим практическим значением. (Это не то же самое, что получить адрес argv, но он будет делать - скажем, чтобы узнать значение указателя стека в _start или любые другие вызовы _start [например, __libc_start_main].) Теперь я не знаю, как получить эквивалентный адрес в 64-битном коде.

Спасибо.

4b9b3361

Ответ 1

Я думаю, что разница в том, что опускать указатель на фрейм просто более рекомендуется в amd64. В сноске на стр. 16 abi говорится

Традиционное использование% rbp в качестве указателя кадра для фрейма стека можно избежать, используя % rsp (указатель стека) для индексации в стек стека. Этот метод сохраняет две инструкции в прологе и эпилоге и делает возможным один дополнительный регистр общего назначения (% rbp).

Я не знаю, что делает GDB. Я предполагаю, что при компиляции с -g объекты имеют волшебную отладочную информацию, которая позволяет GDB восстанавливать то, что ему нужно. Я не думаю, что пробовал GDB на 64-битной машине без отладки информации.

Ответ 2

GDB использует DWARF CFI для размотки. Для нерасширенных двоичных файлов, скомпилированных с помощью -g, это будет в разделе .debug_info. Для разделенных бинарных файлов x86-64 разберите информацию в разделе .eh_frame. Это определено в x86-64 ABI, раздел 3.7, стр. 56. Обработка этой информации сама по себе довольно сложна, так как синтаксический разбор DWARF очень вовлечен, но я верю, что libunwind содержит поддержку для него.

Ответ 3

Если адрес argv - это то, что вы хотите, почему бы просто не сохранить указатель на него в главном?
Попытка раскрутить стек будет крайне неуправляемой, даже если вы ее заработаете.
Даже если вам удастся вернуться через стек, не очевидно, что первый указатель фрейма функции будет NULL. Первая функция в стеке не возвращается, но вызывает системный вызов для выхода, поэтому его указатель кадра никогда не используется. Нет никакой веской причины, почему он будет инициализирован NULL.

Ответ 4

Предполагая, что я связываюсь с glibc (что я делаю), похоже, что я могу решить эту проблему для практических целей с глобальным символом glibc __libc_stack_end:

extern void * __libc_stack_end;

void myfunction(void) {
  /* ... */
  off_t stack_hi = (off_t)__libc_stack_end;
  /* ... */
}