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

Как определить адрес возврата в стеке?

Я знаю, что если я внутри какой-то функции 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.

4b9b3361

Ответ 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