Как определить адрес возврата в стеке?
Я знаю, что если я внутри какой-то функции foo()
, которая называется где-то из bar()
, то этот адрес возврата помещается в стек.
#include <stdio.h>
void foo()
{
unsigned int x;
printf("inside foo %x\n", &x);
}
int main()
{
foo();
printf("in main\n");
return 0;
}
В вышеприведенном коде я получаю адрес первой нажатой локальной переменной в стеке, когда функция foo активна. Как я могу получить доступ к обратному адресу (main называется foo), который помещается где-то перед этой переменной в стек? Является ли это местоположение фиксированным и можно получить доступ к первой локальной переменной? Как я могу его изменить?
EDIT: Моя среда - Ubuntu 9.04 на процессоре x86 с компилятором gcc.
Ответы
Ответ 1
Для этого существует gcc: void * __builtin_return_address (unsigned int level)
См. http://gcc.gnu.org/onlinedocs/gcc/Return-Address.html
На некоторых архитектурах вы можете найти его в стеке относительно первого параметра. Например, в ia32 параметры выталкиваются (в противоположном порядке), а затем выполняется вызов, который будет выталкивать адрес возврата. Помните, что стек почти всегда (и на ia32) растет вниз. Хотя технически вам нужны соглашения об ABI или вызовах (иногда называемые соглашениями о связях) для вашей языковой и аппаратной платформы, на практике вы обычно можете догадаться, знаете ли вы, как работает машина вызова процедур.
Взаимосвязь между первым параметром функции и положением обратного адреса в стеке гораздо более вероятно будет достоверно фиксированным значением, чем отношение между локальным и обратным адресом. Тем не менее, вы можете, конечно, распечатать адрес локального и первого параметра, и вы часто найдете компьютер прямо между ними.
$ expand < ra.c
#include <stdio.h>
int main(int ac, char **av) {
printf("%p\n", __builtin_return_address(0));
return 0;
}
$ cc -Wall ra.c; ./a.out
0xb7e09775
$
Ответ 2
Когда вы объявляете локальные переменные, они также находятся в стеке - x, например.
Если затем объявить int * xptr
и инициализировать его до &x
, он будет указывать на x.
Ничто (много) не мешает вам уменьшить этот указатель, чтобы заглянуть немного раньше или увеличить его, чтобы посмотреть позже. Где-то рядом есть ваш обратный адрес.
Ответ 3
Чтобы узнать, где обратный адрес, вам нужно знать, что такое соглашение о вызовах. Обычно это задается компилятором и зависит от платформы, но вы можете принудительно использовать его в определенных платформах, например, используя __declspec(stdcall)
в окнах. Оптимизирующий компилятор может также изобретать свое собственное соглашение о вызове для функций, которые не имеют внешней области.
Запрет использования встроенных модулей компилятора для получения обратного адреса вам придется обратиться к встроенному ассемблеру, чтобы получить значение. Другие методы, которые, как представляется, работают при отладке, были бы очень воодушевлены для оптимизации компилятора, запуская их.
Ответ 4
Вы можете исследовать вокруг стека так
// assuming a 32 bit machine here
void digInStack(void) {
int i;
long sneak[1];
// feel free to adjust the search limits
for( i = -32; i <= 32; ++i) {
printf("offset %3d: data 0x%08X\n", i, sneak[i]);
}
}
Вы можете уйти от этого, потому что C славится тем, что не очень точно в том, как вы индексируете массив. Здесь вы объявляете фиктивный массив в стеке, а затем просматриваете +/- относительно этого.
Как заметил Роб Уокер, вам определенно нужно знать, что вы подписали соглашение о компиляторах, чтобы понять данные, на которые вы смотрите. Вы можете распечатать адрес нескольких функций и искать значения, которые находятся в аналогичном диапазоне, и интуитивно, где обратный адрес, относительно фиктивного массива.
Слово предостережения - прочитайте все, что хотите, но не изменяйте что-либо с помощью этого массива, если только вы не уверены(), что вы абсолютно уверены в том, какую часть стека вы модифицируете, или (b) просто хотите см. интересный/непредсказуемый аварийный режим.
Ответ 5
Также обратите внимание, что в общем случае на языке C нет гарантии, что ваш обратный адрес находится в стеке или даже где угодно в ОЗУ.
Существуют архитектуры процессоров, которые хранят обратный адрес в регистре, прибегая к ОЗУ только тогда, когда вызовы начинают гнездовать. Существуют и другие архитектуры, где есть отдельный стек для обратных адресов, который не читается ЦП. У обоих из них все еще есть компиляторы C для них.
Вот почему вам нужно быть более четким в своей среде.
Ответ 6
Попробуйте это
//test1.cc
//compile with
//g++ -g test1.cc -o test1
#include <stdio.h>
void
print_function(void *p) {
char cmd[128];
FILE *fp;
snprintf(cmd, sizeof(cmd), "addr2line -e %s -f %p", "test1", p);
fp = popen(cmd, "r");
if (fp) {
char buf[128];
while (fgets(buf, sizeof(buf), fp)) {
printf("%s", buf);
}
}
}
void
f2(void) {
print_function(__builtin_return_address(0));
}
void
f1(void) {
f2();
}
int
main(int argc, char *argv[]) {
f1();
return(0);
}
Результат должен выглядеть как
_Z2f1v
/home/<user>/<dir>/test1.cc:30