почему стек вызовов настроен так? - PullRequest
5 голосов
/ 03 августа 2009

Я просто играл со стеком вызовов, пытался изменить адрес возврата функции и т. Д. И закончил писать эту программу на C:

#include<stdio.h>

void trace(int);
void func3(int);
void func2(int);
void func1(int);

int main(){

    int a = 0xAAAA1111;

    func1(0xFCFCFC01);

    return 0;

}

void func1(int a){

    int loc = 0xBBBB1111;

    func2(0xFCFCFC02);

}

void func2(int a){

    int loc1 = 0xCCCC1111;
    int loc2 = 0xCCCC2222;

    func3(0xFCFCFC03);

}

void func3(int a){

    int loc1 = 0xDDDD1111;
    int loc2 = 0xDDDD2222;
    int loc3 = 0xDDDD3333;

    trace(0xFCFCFC04);

}

void trace(int a){

    int loc = 0xEEEE1111;

    int *ptr = &loc;

    do {
    printf("0x%08X : %08X\n", ptr, *ptr, *ptr);
    } while(*(ptr++) != 0xAAAA1111);

}

(извините за длину)

Это привело к следующему выводу (с добавленными мной комментариями):

0xBF8144D4 : EEEE1111 //local int in trace
0xBF8144D8 : BF8144F8 //beginning of trace stack frame
0xBF8144DC : 0804845A //return address for trace to func3
0xBF8144E0 : FCFCFC04 //int passed to trace
0xBF8144E4 : 08048230 //(possibly) uninitialized padding
0xBF8144E8 : 00000000 //padding
0xBF8144EC : DDDD3333 //local int in func3
0xBF8144F0 : DDDD2222 //local int in func3
0xBF8144F4 : DDDD1111 //local int in func3
0xBF8144F8 : BF814518 //beginning of func3 stack frame
0xBF8144FC : 08048431 //return address for func3 to func2
0xBF814500 : FCFCFC03 //parameter passed to func3
0xBF814504 : 00000000 //padding
0xBF814508 : 00000000 //padding
0xBF81450C : 00000000 //padding
0xBF814510 : CCCC2222 //local int in func2
0xBF814514 : CCCC1111 //local int in func2
0xBF814518 : BF814538 //beginning of func2 stack frame
0xBF81451C : 0804840F //return address for func2 to func1
0xBF814520 : FCFCFC02 //parameter passed to func2
0xBF814524 : 00000000 //padding
0xBF814528 : BF816728 //uninitialized padding
0xBF81452C : B7DF3F4E //uninitialized padding
0xBF814530 : B7EA61D9 //uninitialized padding
0xBF814534 : BBBB1111 //local int in func1
0xBF814538 : BF814558 //beginning of func1 stack frame
0xBF81453C : 080483E8 //return address for func1 to main
0xBF814540 : FCFCFC01 //parameter passed to func1
0xBF814544 : 08049FF4 //(maybe) padding
0xBF814548 : BF814568 //(maybe) padding
0xBF81454C : 080484D9 //(maybe) padding
0xBF814550 : AAAA1111 //local int in main

Мне было интересно, если кто-нибудь мог бы заполнить меня здесь на пустых местах ... Я использую Ubuntu Linux, компилирующуюся с gcc 4.3.3 (на x86 - AMD Turion 64)

Что такое 0804 ... числа? Какой третий адрес снизу вверх? Это обратный адрес для основного? Если так, то почему он не в порядке по сравнению с остальной частью стека? Числа 0x0804 являются адресами возврата или указателями на код / ​​данные или что-то еще, а числа 0xBF814 являются указателями стека

Что это:

0xBF814524 : 00000000 //padding?
0xBF814528 : BF816728 //I have no idea
0xBF81452C : B7DF3F4E //????
0xBF814530 : B7EA61D9 //????

видели сразу после локального int в func1?

Хорошо, мой дамп стека почти полностью заполнен.

Похоже, что компилятор хочет поместить параметры в стек, начиная с адреса 0x ....... 0, и все, что находится между локальными переменными из предыдущей функции и первым параметром функции, являющейся Кажется, что он вызывается как заполнение (0x00000000 или какое-то неинициализированное значение). В некоторых из них я не уверен, потому что они выглядят как указатели на сегменты кода / данных, но я не вижу, чтобы они использовались в коде: они просто там, когда указатель стека уменьшается.

и я знаю это ОГРОМНЫЙ ноно, касающийся стека вызовов в любом проекте, но это нормально. Это весело, правда?

также

Грег хочет увидеть сборку, вот оно

    .file   "stack.c"
    .text
.globl main
    .type   main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ecx
    subl    $20, %esp
    movl    $-1431695087, -8(%ebp)
    movl    $-50529279, (%esp)
    call    func1
    movl    $0, %eax
    addl    $20, %esp
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret
    .size   main, .-main
.globl func1
    .type   func1, @function
func1:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $-1145368303, -4(%ebp)
    movl    $-50529278, (%esp)
    call    func2
    leave
    ret
    .size   func1, .-func1
.globl func2
    .type   func2, @function
func2:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $-859041519, -4(%ebp)
    movl    $-859037150, -8(%ebp)
    movl    $-50529277, (%esp)
    call    func3
    leave
    ret
    .size   func2, .-func2
.globl func3
    .type   func3, @function
func3:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $-572714735, -4(%ebp)
    movl    $-572710366, -8(%ebp)
    movl    $-572705997, -12(%ebp)
    movl    $-50529276, (%esp)
    call    trace
    leave
    ret
    .size   func3, .-func3
    .section    .rodata
.LC0:
    .string "0x%08X : %08X\n"
    .text
.globl trace
    .type   trace, @function
trace:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $40, %esp
    movl    $-286387951, -4(%ebp)
    leal    -4(%ebp), %eax
    movl    %eax, -8(%ebp)
.L10:
    movl    -8(%ebp), %eax
    movl    (%eax), %edx
    movl    -8(%ebp), %eax
    movl    (%eax), %eax
    movl    %edx, 12(%esp)
    movl    %eax, 8(%esp)
    movl    -8(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    $.LC0, (%esp)
    call    printf
    movl    -8(%ebp), %eax
    movl    (%eax), %eax
    cmpl    $-1431695087, %eax
    setne   %al
    addl    $4, -8(%ebp)
    testb   %al, %al
    jne .L10
    leave
    ret
    .size   trace, .-trace
    .ident  "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
    .section    .note.GNU-stack,"",@progbits

Ответы [ 7 ]

5 голосов
/ 03 августа 2009

Я предполагаю, что эти значения, начинающиеся с 0x0804, являются адресами в сегменте кода вашей программы (например, адреса возврата для вызовов функций). Те, которые начинаются с 0xBF814, которые вы пометили как адреса возврата, - это адреса в стеке - данные, а не код. Я предполагаю, что они, вероятно, указатели на фреймы.

5 голосов
/ 03 августа 2009

Скорее всего, это стека канареек . Ваш компилятор добавляет код для помещения дополнительных данных в стек и последующего их чтения для обнаружения переполнения стека.

5 голосов
/ 03 августа 2009

Такая проверка стека кажется одним шагом слишком далеко. Я мог бы предложить загрузить вашу программу в отладчике, переключиться на представление на ассемблере и пройти по каждой машинной инструкции. Понимание стека ЦП обязательно требует понимания действующих на нем машинных инструкций, и это будет более прямым способом увидеть, что происходит.

Как уже упоминалось, структура стека также сильно зависит от архитектуры процессора, с которым вы работаете.

3 голосов
/ 03 августа 2009

Как уже указывалось, 0xBF ... - это указатели кадра, а 0x08 ... обратные адреса.

Заполнение связано с проблемами выравнивания . Другие нераспознанные значения также заполняются, так как стек не инициализируется нулем или любым другим значением. Неинициализированные переменные и неиспользуемое пространство заполнения будут содержать все байты в этих ячейках памяти.

2 голосов
/ 03 августа 2009

Компилятор использует EBP для хранения базового адреса фрейма. Прошло какое-то время, поэтому я посмотрел на это, поэтому я могу немного ошибиться в деталях, но идея такова.

У вас есть три шага при вызове функции:

  1. Вызывающий объект помещает параметры функции в стек.
    • Вызывающая сторона использует инструкцию call, которая помещает адрес возврата в стек и переходит к новой функции.
    • Вызванная функция помещает EBP в стек и копирует ESP в EBP:
    • (Примечание: хорошо функционирующие функции также помещают все GPR в стек с помощью PUSHAD)
push EBP
mov EBP, ESP

Когда функция возвращает его:

  1. всплывает EBP
  2. выполняет инструкцию ret, которая выскакивает с адреса возврата и переходит туда.
pop EBP
ret

Вопрос в том, почему проталкивается EBP и почему в него копируется ESP?

При вводе функции ESP указывает на самую низкую точку в стеке для этой функции. Любые переменные, которые вы объявляете в стеке, могут быть доступны как [ESP + offset_to_variable]. Это просто! Но обратите внимание, что ESP должен всегда указывать на вершину стека, поэтому, когда вы объявляете новую переменную в стеке, ESP изменяется. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * '' '' '

Вместо того, чтобы сделать это, первое, что нужно сделать функции, это скопировать ESP в EBP. EBP не изменится в течение срока службы функции, поэтому вы можете получить доступ ко всем переменным, используя `[EBP + offset_to_variable]. Но теперь у вас есть другая проблема, потому что если вызываемые функции вызывают другую функцию, EBP будет перезаписан. Вот почему перед копированием EBP его необходимо сохранить в стек, чтобы его можно было восстановить до возврата к вызывающей функции.

2 голосов
/ 03 августа 2009

Адреса 0xBF ... будут ссылками на предыдущий кадр стека:

0xBF8144D8 : BF8144F8 //return address for trace
0xBF8144DC : 0804845A //

0xBF8144F8 : BF814518 //return address for func3
0xBF8144FC : 08048431 //????

0xBF814518 : BF814538 //return address for func2?
0xBF81451C : 0804840F //????

0xBF814538 : BF814558 //return address for func1
0xBF81453C : 080483E8 //????

Адреса 0x08 ... будут адресами кода, к которому нужно вернуться в каждом случае.

Я не могу говорить за другие вещи в стеке; вам придется пройтись по языку ассемблера и посмотреть, что именно он делает. Я предполагаю, что он выравнивает начало каждого кадра по определенному выравниванию, так что __attribute__((align)) (или как это называется в наши дни ...) работает.

1 голос
/ 03 августа 2009

Это отладочная или релизная сборка? Я бы ожидал дополнения отладочных сборок для обнаружения переполнения стека.

...