Соглашения о вызовах x86_64 и стековые фреймы - PullRequest
13 голосов
/ 24 декабря 2011

Я пытаюсь разобраться в исполняемом коде, который 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's accounting.
pop %ebp
ret

, либо просто

leave
ret

, которая выполняет то же самое:

  • Установите указатель стека на верхнюю часть текущего кадра,чуть ниже обратного адреса
  • Восстановите старое значение Frame Pointer.

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

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

Спасибо.

Ответы [ 4 ]

5 голосов
/ 24 декабря 2011

Я думаю, что разница в том, что пропустить указатель кадра просто лучше в amd64.Сноска на странице 16 abi гласит:

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

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

3 голосов
/ 24 августа 2012

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

1 голос
/ 25 декабря 2011

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

extern void * __libc_stack_end;

void myfunction(void) {
  /* ... */
  off_t stack_hi = (off_t)__libc_stack_end;
  /* ... */
}
1 голос
/ 24 декабря 2011

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

...