Почему в кадре стека функций есть промежутки между аргументами, переменными и указателем кадра? - PullRequest
0 голосов
/ 01 октября 2018

У меня есть следующая программа c:

void function(int a, int b, int c) {
  char buffer1[]="aaaaa";
  char buffer2[]="bbbbbbbbbb";
}
int main() {
  function(1,2,3);
  return 0;

}

Когда я печатаю информацию о кадре при выполнении функции, я получаю следующий вывод GDB:

(gdb) info frame
Stack level 0, frame at 0x7fffffffe1c0:
 rip = 0x40119b in function (ss1.c:4); saved rip = 0x4011ca
 called by frame at 0x7fffffffe1d0
 source language c.
 Arglist at 0x7fffffffe1b0, args: a=1, b=2, c=3
 Locals at 0x7fffffffe1b0, Previous frame's sp is 0x7fffffffe1c0
 Saved registers:
  rbp at 0x7fffffffe1b0, rip at 0x7fffffffe1b8
(gdb) 

При выводе адресов аргументов функции и локальных переменных я получаю:

(gdb) p/x &c
$65 = 0x7fffffffe184
(gdb) p/x &b
$66 = 0x7fffffffe188
(gdb) p/x &a
$67 = 0x7fffffffe18c
(gdb) p/x &buffer1
$68 = 0x7fffffffe197
(gdb) p/x &buffer2
$69 = 0x7fffffffe19d
  1. Почему между адресами аргументов arg a и var есть разрыв в 11 байтовbuffer1 - а не просто пробел в 4 байта, который имеет размер?

  2. Почему между адресом buffer2 и указателем кадра существует промежуток в 19 байтов (0x7fffffffe1b0) -а не просто пробел в 11 байт, который является размером буфера2?

Спасибо

Ответы [ 3 ]

0 голосов
/ 03 октября 2018

Это должно поставить вас на правильный путь, но не отвечает фактическому разрыву:

  • Стек растет вниз, и вы пытаетесь прочитать его вверх
  • Что вы&a, &b и &c - это не переданные параметры, а локальное хранилище, которое можно использовать, если (например) вы скажете a=1 в пределах function().Я считаю, что они оптимизируются, если вы не сделаете -O0
  • a, b и c передаются через регистры в функцию вместо стека, поэтому вы не найдетеих там дважды.Т.е. вызывающая сторона не помещает их в стек.
  • buffer1 и buffer2 не выровнены в стеке и упакованы вместе в виде строк.

Например, сразу после (до) buffer2 вы можете найти сохраненное значение RBP и затем обратный адрес.Для меня:

(gdb) p &buffer1
$102 = (char (*)[6]) 0x7fffffffde82
(gdb) p &buffer2
$103 = (char (*)[11]) 0x7fffffffde77

(buffer1 оканчивается на 0x7fffffffde87)

А потом сохраняется RBP:

(gdb) p/x (char [8]) *0x7fffffffde88
$104 = {0xb0, 0xde, 0xff, 0xff, 0xff, 0x7f, 0x0, 0x0}

А потом обратный адрес:

(gdb) p/x (char [8]) *0x7fffffffde90
$105 = {0x80, 0x51, 0x55, 0x55, 0x55, 0x55, 0x0, 0x0}

Что вы также можете увидеть из gdb:

(gdb) info frame
Stack level 0, frame at 0x7fffffffde98:
 rip = 0x55555555513f in function (c.c:3); saved rip = 0x555555555180
                                                       ^^^^^^^^^^^^^^
 called by frame at 0x7fffffffdec0
 source language c.
 Arglist at 0x7fffffffde88, args: a=1, b=2, c=3
 Locals at 0x7fffffffde88, Previous frame's sp is 0x7fffffffde98
 Saved registers:
  rbp at 0x7fffffffde88, rip at 0x7fffffffde90
  ^^^^^^^^^^^^^^^^^^^^^^

Вы также можете увидеть это, посмотрев код сборки:

gcc -S c.c -o c.s

или есливы предпочитаете intel:

gcc -masm=intel -S c.c -o c.s

Я не знаю, почему gcc оставляет этот пробел, хотя:

    mov     DWORD PTR -36[rbp], edi
    mov     DWORD PTR -40[rbp], esi
    mov     DWORD PTR -44[rbp], edx
    mov     DWORD PTR -6[rbp], 1633771873  <-- aaaa
    mov     WORD PTR -2[rbp], 97           <-- a\0
    movabs  rax, 7089336938131513954       <-- bbbbbbbb
    mov     QWORD PTR -17[rbp], rax
    mov     WORD PTR -9[rbp], 25186        <-- bb
    mov     BYTE PTR -7[rbp], 0            <-- \0
0 голосов
/ 05 октября 2018

Компилятор обычно соблюдает спецификацию ABI, определяемую эффективностью, чтобы оптимизировать передачу параметров через регистры, выравнивание и пространство для возможных глубоких вложенных выражений внутри кода.Например, в спецификации Intel ABI говорится, что указатель стека расширяется при вызове функции с параметрами, кратными 16 байтам, поэтому все виды выравнивания в порядке.Таким образом, вполне нормально видеть, что при входе пространство для локальных переменных резервируется только с одним вычитанием SP, а затем начинается выполнение кода, пока нам не понадобится еще один кусок пространства.В спецификации ABI говорится, какие регистры используются для передачи параметров, какие из них вы должны соблюдать при вызове, а какие вы можете уничтожить, какие используются для связывания кадров стека (обычно это EBP в Intel) и т. Д. Это учитывает взаимозависимость компилятора (интерфейс междуязыки) и в то же время внедрить оптимизированный код и обеспечить эффективность программы.По этой причине вы видите некоторую очевидную потерю стековой памяти при вызовах процедур входа / выхода.

0 голосов
/ 02 октября 2018

просто запустите простую программу:

#include <stdio.h>

void function(int a, int b, int c) 
{
  char buffer1[]="aaaaa";
  char buffer2[]="bbbbbbbbbb";

  printf("%p = &a\n", &a);
  printf("%p = &b\n", &b);
  printf("%p = &c\n", &c);
  printf("%p = &buffer1 sizeof(buffer1) = %zu\n", buffer1, sizeof(buffer1));
  printf("%p = &buffer2 sizeof(buffer2) = %zu\n", buffer2, sizeof(buffer2));
  printf("%zu = &buffer - &a\n", (char *)buffer1 - (char *)&a);
}

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

и результат в точности соответствует ожидаемому.

0x7fff9d9d830c = &a                                                                                                                                                                                                                                           
0x7fff9d9d8308 = &b                                                                                                                                                                                                                                           
0x7fff9d9d8304 = &c                                                                                                                                                                                                                                           
0x7fff9d9d8310 = &buffer1 sizeof(buffer1) = 6                                                                                                                                                                                                                 
0x7fff9d9d8320 = &buffer2 sizeof(buffer2) = 11                                                                                                                                                                                                                
4 = &buffer - &a 

Попробуйте запустить его в своей системе.

...