В качестве примера возьмём программу вычисления факториала из книги Programming from the Ground Up и исследуем стек программы во время её выполнения.
.text # Здесь начинается текст программы. .globl _start # Указываем, где расположена точка входа. .type factorial, @function # Помечаем factorial как функцию. factorial: # (S2), (S7) nop # Нулевая операция, удобно для отладки. pushl %ebp # Сохраняем %ebp в стек. (S3), (S8) movl %esp, %ebp # Теперь %ebp указывает на вершину стека. movl 8(%ebp), %eax # Помещаем в %eax 1-й аргумент функции. cmpl $1, %eax # Если он равен 1 (базовый случай), je end_factorial # то переходим к выходу из функции. decl %eax # Уменьшаем на единицу аргумент функции и pushl %eax # помещаем его в стэк. (S6) call factorial # Вызываем саму себя из функции. movl 8(%ebp), %ebx # Помещаем 1-й аргумент функции в %ebx. imull %ebx, %eax # В %eax помещаем результат %eax * %ebx. end_factorial: movl %ebp, %esp # Делаем %ebp вершиной стека. (S12) popl %ebp # Помещаем из стека в %ebp. (S10), (S13) ret # Выходим из функции. (S11), (S14) _start: pushl $2 # Помещаем аргумент функции factorial # в стек. (S1) call factorial # Вызываем функцию factorial. addl $4, %esp # Очищаем стек от аргумента функции. (S15) movl %eax, %ebx # Результат функции используем как код # выхода из программы. movl $1, %eax # Заказываем системный вызов exit int $0x80 # и вызываем его.Собираем и запускаем программу:
as --gstabs -o fact.o fact.s && ld -o func fact.o && ./funcИзучаем стек с использованием отладчика: $ gdb func.
Шаг 1
Запускаем программу и помещаем аргумент функции в стек. Для простоты
изучения будем исследовать 2!.
изучения будем исследовать 2!.
(gdb) disassemble _start Dump of assembler code for function _start: 0x08048071 <+0>: push $0x2 => 0x08048073 <+2>: call 0x80480540x08048078 <+7>: add $0x4,%esp 0x0804807b <+10>: mov %eax,%ebx 0x0804807d <+12>: mov $0x1,%eax 0x08048082 <+17>: int $0x80 End of assembler dump. ******************** Stack content *********************************** (gdb) x/8xw $esp |-- arg1 0xbfffef9c: 0x00000002 0x00000001 0xbffff199 0x00000000 0xbfffefac: 0xbffff1bd 0xbffff1d2 0xbffff1dd 0xbffff1ef ********************************************************************** (gdb) print $esp $1 = (void *) 0xbfffef9c (gdb) print $ebp $2 = (void *) 0x0
Шаг 2
Вызываем функцию. Находимся внутри функции.
******************** Stack content *********************************** (gdb) x/8xw $esp |-- ret1 |-- arg1 0xbfffef98: 0x08048078 0x00000002 0x00000001 0xbffff199 0xbfffefa8: 0x00000000 0xbffff1bd 0xbffff1d2 0xbffff1dd ********************************************************************** (gdb) print $esp $3 = (void *) 0xbfffef98 - адрес стека уменьшился на 4 байта (gdb) print $ebp $2 = (void *) 0x0
Шаг 3
Помещаем %ebp в стек.
(gdb) disassemble factorial Dump of assembler code for function factorial: 0x08048054 <+0>: nop 0x08048055 <+1>: push %ebp => 0x08048056 <+2>: mov %esp,%ebp 0x08048058 <+4>: mov 0x8(%ebp),%eax 0x0804805b <+7>: cmp $0x1,%eax 0x0804805e <+10>: je 0x804806d0x08048060 <+12>: dec %eax 0x08048061 <+13>: push %eax 0x08048062 <+14>: call 0x8048054 0x08048067 <+19>: mov 0x8(%ebp),%ebx 0x0804806a <+22>: imul %ebx,%eax End of assembler dump. ******************** Stack content *********************************** (gdb) x/8xw $esp |-- %ebp |-- ret 1 |-- arg 1 0xbfffef94: 0x00000000 0x08048078 0x00000002 0x00000001 0xbfffefa4: 0xbffff199 0x00000000 0xbffff1bd 0xbffff1d2 ********************************************************************** (gdb) print $esp $6 = (void *) 0xbfffef94 (gdb) print $ebp $7 = (void *) 0x0
Шаг 4
Делаем %ebp вершиной стека.
(gdb) x/8xw $esp 0xbfffef94: 0x00000000 0x08048078 0x00000002 0x00000001 0xbfffefa4: 0xbffff199 0x00000000 0xbffff1bd 0xbffff1d2 (gdb) x/8xw $ebp 0xbfffef94: 0x00000000 0x08048078 0x00000002 0x00000001 0xbfffefa4: 0xbffff199 0x00000000 0xbffff1bd 0xbffff1d2
Шаг 5
Помещаем первый аргумент в %eax.
(gdb) print $eax $10 = 2
Шаг 6
Помещаем уменьшенный на единицу %eax в стек.
(gdb) x/8xw $esp |-- arg1-1 |-- %ebp |-- ret 1 |-- arg1 0xbfffef90: 0x00000001 0x00000000 0x08048078 0x00000002 0xbfffefa0: 0x00000001 0xbffff199 0x00000000 0xbffff1bd (gdb) print $esp $11 = (void *) 0xbfffef90 (gdb) print $ebp $12 = (void *) 0xbfffef94
Шаг 7
Вызываем функцию снова.
(gdb) print $esp $16 = (void *) 0xbfffef8c (gdb) print $ebp $17 = (void *) 0xbfffef94 (gdb) x/8xw $esp |-- ret 2 |-- arg1-1 |-- %ebp |-- ret 1 0xbfffef8c: 0x08048067 0x00000001 0x00000000 0x08048078 |-- arg1 0xbfffef9c: 0x00000002 0x00000001 0xbffff199 0x00000000
Шаг 8
Помещаем %ebp в стек.
(gdb) print $esp $18 = (void *) 0xbfffef88 (gdb) print $ebp $19 = (void *) 0xbfffef94 (gdb) x/8xw $esp %eax - keep it usually |-- %ebp_2 |-- ret 2 |-- arg1-1 |-- %ebp_1 0xbfffef88: 0xbfffef94 0x08048067 0x00000001 0x00000000 |-- ret 1 |-- arg1 0xbfffef98: 0x08048078 0x00000002 0x00000001 0xbffff19
Шаг 9
%ebp теперь указывает на вершину стека.
(gdb) print $esp $20 = (void *) 0xbfffef88 (gdb) print $ebp $21 = (void *) 0xbfffef88
Шаг 10
Забираем %ebp из стека.
(gdb) print $esp $27 = (void *) 0xbfffef8c (gdb) print $ebp $28 = (void *) 0xbfffef94 (gdb) x/8xw $esp |-- ret 2 |-- arg1-1 |-- %ebp |-- ret 1 0xbfffef8c: 0x08048067 0x00000001 0x00000000 0x08048078 |-- arg1 0xbfffef9c: 0x00000002 0x00000001 0xbffff199 0x00000000
Шаг 11
Первый выход из программы.
(gdb) print $esp $29 = (void *) 0xbfffef90 (gdb) print $ebp $30 = (void *) 0xbfffef94 (gdb) x/8xw $esp |-- arg1-1 |-- %ebp |-- ret 1 |-- arg1 0xbfffef90: 0x00000001 0x00000000 0x08048078 0x00000002 0xbfffefa0: 0x00000001 0xbffff199 0x00000000 0xbffff1bd
Шаг 12
Восстанавливаем значение %esp, сохранённое в %ebp.
(gdb) x/8xw $esp |-- %ebp |-- ret 1 |-- arg1 0xbfffef94: 0x00000000 0x08048078 0x00000002 0x00000001 0xbfffefa4: 0xbffff199 0x00000000 0xbffff1bd 0xbffff1d2
Шаг 13
Забираем %ebp из стека.
(gdb) x/8xw $esp |-- ret 1 |-- arg1 0xbfffef98: 0x08048078 0x00000002 0x00000001 0xbffff199 0xbfffefa8: 0x00000000 0xbffff1bd 0xbffff1d2 0xbffff1dd
Шаг 14
Выходим из функции окончательно и получаем результат.
(gdb) x/8xw $esp 0xbfffef9c: 0x00000002 0x00000001 0xbffff199 0x00000000 0xbfffefac: 0xbffff1bd 0xbffff1d2 0xbffff1dd 0xbffff1ef
Шаг 15
Возвращаем стек в первоначальное состояние.
(gdb) x/8xw $esp 0xbfffefa0: 0x00000001 0xbffff199 0x00000000 0xbffff1bd 0xbfffefb0: 0xbffff1d2 0xbffff1dd 0xbffff1ef 0xbffff206
Схематичное изображение стека
SN - номер шага N.Pushing #################################################################### | ARG | | RET1 | | %EBP_1 | | ARG - 1 | | RET2 | | %EBP_2 | | ... | | ARG | | RET1 | | %EBP | | ARG - 1 | | RET2 | | ... | | ARG | | RET1 | | %EBP | | ARG - 1 | | ... | | ARG | | RET1 | | %EBP | | ... | | ARG | | RET1 | | ... | | ARG | | ... | S1 S2 S3 S6 S7 S8 Fetching ############################################################### | RET2 | | ARG - 1 | | %EBP | | RET1 | | ARG | | ARG - 1 | | %EBP | | RET1 | | ARG | | ... | | %EBP | | RET1 | | ARG | | ... | | RET1 | | ARG | | ... | | ARG | | ... | | ... | S10 S11 S12 S13 S14
Комментариев нет:
Отправить комментарий