В этом случае вы можете сделать это достаточно надежно, так как этот код был скомпилирован с включенными указателями кадров.
Что нужно знать:
- EIP - это регистрэто указывает на следующую команду для выполнения.
При вызове функции аргументы, а затем EIP (чтобы вызываемая функция знала, куда вернуться) сохраняются в стеке.
Когда компилятору было сказано (явно или неявно) использовать указатели фреймов, он затем сохраняет указатель фрейма (в регистре EBP) в стеке (чтобы впоследствии он мог восстановить указатель фрейма на значениеон имеет на вызывающей функции), а затем устанавливает указатель кадра, чтобы указывать на текущую вершину стека.Это позволяет легко получить доступ к аргументам и локальным переменным из известной точки отсчета (указатель кадра) и значительно упрощает отладку.
- Затем пространство зарезервировано для локальных переменных, и функция выполняется.
- При возврате из функции предыдущий указатель кадра и указатель инструкции восстанавливаются.
Таким образом, чтобы создать обратную трассировку, вы должны следовать указателям кадра, просматривая соответствующие сохраненные EIPS,Итак:
current function was called from c0135351
follow f3e2de70 → was called from c02898b5
follow f3e2df18 → was called from c0283642
Конечно, это был легкий случай.Если у вас нет указателей фреймов, вам нужно угадать, соответствует ли данное значение в стеке указателю инструкций.
Недостающая часть - как перевести эти числа в имена функций.Наличие незарезанного vmlinux
(обратите внимание на x
, а не z
) файла неоценимо.System.map
содержит только некоторые символы, поэтому очень часто вы узнаете, что соответствующая функция была между функцией A и функцией B.
Edit:
Вызов функции на x86 выглядит примерно так:
...
int main() add $-0x8,%esp ; alignment
{ push $0x2 ; arg 2
... push $0x1 ; arg 1
func(1, 2); call func ; function call
... add $0x10,%esp ; pop args from stack
} ...
И вызываемая функция выглядит примерно так:
void func(int arg1, int arg2) push %ebp ;\
{ mov %esp,%ebp ;/ create stack frame
int local1; sub $0x18,%esp ; reserves space
... ...
} mov %ebp,%esp ;\
pop %ebp ;/ destroys frame
ret ; returns
Итак, стек будет выглядеть примерно так:
: :
+-----------+
: alignment :
+-----------+
12(%ebp) | arg2 |
+-----------+
8(%ebp) | arg1 |
+-----------+
4(%ebp) | ret | -----> return address
+-----------+
(%ebp) | ebp | -----> previous ebp
+-----------+
-4(%ebp) | local1 | -----> local vars
+-----------+
: alignment :
+-----------+
: :
(Нижние адреса ниже в ASCII-art)
Таким образом, если вы будете следовать сохраненным указателям EBP
, вы можете получить сохраненные EIP
указатели (ret
выше), которыеуказывать на инструкции в цепочке вызовов (если быть точным, в цепочке возврата).