Почему 32-битные и 64-битные скомпилированные версии этой программы заполняют память таким образом? - PullRequest
1 голос
/ 14 июля 2011

Я пытаюсь лучше понять, как работают стек и куча.Я столкнулся с проблемой при сравнении 32-битных и 64-битных скомпилированных версий одной и той же программы.В обоих случаях я использовал гостевую виртуальную машину Fedora 15 (32 и 64), gcc для компиляции, gdb для отладки и одно и то же оборудование хоста.Рассматриваемая программа очень проста и сразу ниже:

Программа C

void function(int a, int b, int c, int d){
    int value;
    char buffer[10];

    value = 1234;
    buffer[0] = 'A';
}

int main(){
    function(1, 2, 3, 4);
}

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

32-битная скомпилированная программа:

Параметры 4 (0xbffff3e4), 3 (0xbffff3e0),2 (0xbffff3dc) и 1 (0xbffff3d8) сначала помещаются в стек.Затем расположение инструкции после вызова функции () - или адреса возврата - помещается в стек (0x080483d1).Затем значение базового указателя для предыдущего стека (0xbffff3e8) помещается в стек.

(gdb) x/16xw $esp
0xbffff3c0: 0x00000000  0x410759c3  0x4105d237  0x00000000
0xbffff3d0: 0xbffff3e8  0x080483d1  0x00000001  0x00000002//pointers
0xbffff3e0: 0x00000003  0x00000004  0x00000000  0x4105d413//followed by params
0xbffff3f0: 0x00000001  0xbffff484  0xbffff48c  0x41040fc4

64-разрядная скомпилированная программа:

Однако;здесь значения 4, 3, 2 и 1 нигде не видно.Все, что я вижу, независимо от того, как далеко я смотрю в стеке, это адрес возврата (0x4004ae) и базовый указатель предыдущего кадра стека (0x7fffffffe210).

(gdb) x/16xg $rsp
0x7fffffffe200: 0x00007fffffffe210  0x00000000004004ae //pointers
0x7fffffffe210: 0x0000000000000000  0x00000036d042139d
0x7fffffffe220: 0x0000000000000000  0x00007fffffffe2f8
0x7fffffffe230: 0x0000000100000000  0x0000000000400491
0x7fffffffe240: 0x0000000000000000  0x7ade47f577d82f75
0x7fffffffe250: 0x0000000000400390  0x00007fffffffe2f0
0x7fffffffe260: 0x0000000000000000  0x0000000000000000
0x7fffffffe270: 0x8521b80ab3982f75  0x7ab3e77151682f75

64-битная скомпилированная программа с оператором print:

Теперь, после добавления простого оператора печати:

printf("%d, %c\n", flag, buffer[0]);

в функции (), я могу видеть странные параметры (см. Ниже, 0x7fffffffe1e0-0x7fffffffe1ec).Я также вижу Базовый указатель из предыдущего стекового кадра, 0x7fffffffe210 (в 0x7fffffffe200) и обратный адрес 0x400520 (в 0x7fffffffe208).Я полагаю, что это изменилось из-за нового заявления печати. Почему 4, 3, 2 и 1 не видны без оператора печати в этом случае?Является ли 64-битная реализация компилятора gcc достаточно умной, чтобы не «тратить» память на параметры и локальные переменные, которые никогда не используются?

(gdb) x/16xg $rsp
0x7fffffffe1e0: 0x0000000300000004  0x0000000100000002 //parameters
0x7fffffffe1f0: 0x0000000000000000  0x00000000004003e0
0x7fffffffe200: 0x00007fffffffe210  0x0000000000400520 //pointers
0x7fffffffe210: 0x0000000000000000  0x00000036d042139d
0x7fffffffe220: 0x0000000000000000  0x00007fffffffe2f8
0x7fffffffe230: 0x0000000100000000  0x0000000000400503
0x7fffffffe240: 0x0000000000000000  0xd3c0c92559feaed9
0x7fffffffe250: 0x00000000004003e0  0x00007fffffffe2f0

Наконец, почему 32-битныйОС помещает параметры 4, 3, 2 и 1 выше в стек, чем это делало ранее упомянутые указатели.И почему 64-битная ОС вместо этого помещает параметры ниже в стек, чем указанные указатели? У меня сложилось впечатление, что переданные параметры всегда помещаются в стек первыми (и, следовательно, будут вадрес памяти большего значения, так как стек увеличивается в сторону меньших адресов).Затем следовали сохраненный базовый указатель и адрес возврата (чтобы базовый указатель мог быть сброшен до своего предыдущего значения, а вызывающая функция могла быть возвращена).Такое поведение я наблюдаю в 32-битном скомпилированном коде, но не в 64-битной версии.Что я недопонимаю?Я ценю любое понимание этого вопроса и прошу прощения, если мои вопросы неясны.Пожалуйста, дайте мне знать, как я могу быть более кратким (или если я на самом деле ошибаюсь в любой момент).

Заранее спасибо.

1 Ответ

7 голосов
/ 14 июля 2011

64-битный ABI, используемый в Linux , значительно отличается от 32-битного ABI: в 64-битном мире аргументы часто передаются в регистрах, а не в стеке.

Перед добавлением printf() вы не находите аргументы в стеке, потому что первые (до) 6 аргументов целого числа или указателя передаются в регистрах (в порядке %rdi, %rsi, %rdx, %rcx, %r8, %r9).

После добавления printf() они, вероятно, сохраняются в стеке в процессе перемещения содержимого регистров для вызова printf() - взгляните на сборку;это, вероятно, очевидно, когда вы знаете, как выглядит ABI.

...