Я унаследовал некоторый умный x64 машинный код для GNU/Linux, который создает оболочку машинного кода вокруг вызова функции c. Я предполагаю, что в более высоких языковых терминах код можно назвать декоратором или закрытием. Код функционирует хорошо, но с неудачным артефактом, который когда вызывается оболочка, он поглощает трассировку стека в gdb.Как использовать gdb stacktrace с заданным временем машинного кода?
Из того, что я узнал из сети, gdb использует https://en.wikipedia.org/wiki/DWARF в качестве руководства для разделения кадров стека в стеке. Это хорошо работает для статического кода, но, очевидно, код, сгенерированный и вызываемый во время выполнения, не зарегистрирован в структуре DWARF.
Вопрос в том, есть ли способ спасти трассировку стека в этой ситуации?
Вот такой же c-код, который показывает проблему.
typedef int (*ftype)(int x);
int wuz(int x) { return x + 7; }
int wbar(int x) { return wuz(x)+5; }
int main(int argc, char **argv)
{
const unsigned char wbarcode[] = {
0x55 , // push %rbp
0x48,0x89,0xe5 , // mov %rsp,%rbp
0x48,0x83,0xec,0x08 , // sub $0x8,%rsp
0x89,0x7d,0xfc , // mov %edi,-0x4(%rbp)
0x8b,0x45,0xfc , // mov -0x4(%rbp),%eax
0x89,0xc7 , // mov %eax,%edi
0x48,0xc7,0xc0,0xf6,0x04,0x40,00, // mov $0x4004f6,%rax
0xff,0xd0, // callq *%rax
0x83,0xc0,0x05 , // add $0x5,%eax
0xc9 , // leaveq
0xc3 // retq
};
int wb = wbar(5);
ftype wf = (ftype)wbarcode;
int fwb = wf(5);
}
Собирать по:
gcc -g -o mcode mcode.c
execstack -s mcode
и запустить его в БГД по:
$ gdb mcode
(gdb) break wuz
Если разбирать wbar мы получаем что-то очень похожее на последовательности байтов в wbarcode[]
. Единственное различие заключается в том, что я изменил соглашение о вызове для вызова wuz()
.
(gdb) disas/r wbar
Dump of assembler code for function wbar:
0x0000000000400505 <+0>: 55 push %rbp
0x0000000000400506 <+1>: 48 89 e5 mov %rsp,%rbp
0x0000000000400509 <+4>: 48 83 ec 08 sub $0x8,%rsp
0x000000000040050d <+8>: 89 7d fc mov %edi,-0x4(%rbp)
0x0000000000400510 <+11>: 8b 45 fc mov -0x4(%rbp),%eax
0x0000000000400513 <+14>: 89 c7 mov %eax,%edi
0x0000000000400515 <+16>: e8 dc ff ff ff callq 0x4004f6 <wuz>
0x000000000040051a <+21>: 83 c0 05 add $0x5,%eax
0x000000000040051d <+24>: c9 leaveq
0x000000000040051e <+25>: c3 retq
End of assembler dump.
Если мы сейчас запустим программу, она будет остановлена дважды в wuz(). В первый раз через наш c-вызов, и мы можем запросить трассировку стека через bt.
Breakpoint 3, wuz (x=5) at mcode.c:2
=> 0x00000000004004fd <wuz+7>: 8b 45 fc mov -0x4(%rbp),%eax
0x0000000000400500 <wuz+10>: 83 c0 07 add $0x7,%eax
0x0000000000400503 <wuz+13>: 5d pop %rbp
0x0000000000400504 <wuz+14>: c3 retq
(gdb) bt
#0 wuz (x=5) at mcode.c:2
#1 0x000000000040051a in wbar (x=5) at mcode.c:3
#2 0x00000000004005b0 in main (argc=1, argv=0x7fffffffe528) at mcode.c:20
Это нормальный трассировки стека показывает, что мы получили от main()
→ wbar()
→ wuz()
.
Но если мы теперь продолжаем мы достигаем wuz()
во второй раз, и мы снова запрос трассировки стека:
(gdb) c
Continuing.
Breakpoint 3, wuz (x=5) at mcode.c:2
=> 0x00000000004004fd <wuz+7>: 8b 45 fc mov -0x4(%rbp),%eax
0x0000000000400500 <wuz+10>: 83 c0 07 add $0x7,%eax
0x0000000000400503 <wuz+13>: 5d pop %rbp
0x0000000000400504 <wuz+14>: c3 retq
(gdb) bt
#0 wuz (x=5) at mcode.c:2
#1 0x00007fffffffe419 in ??()
#2 0x0000000500000001 in ??()
#3 0x00007fffffffe440 in ??()
#4 0x00000000004005c6 in main (argc=0, argv=0xffffffff) at mcode.c:22
Backtrace stopped: frame did not save the PC
Несмотря на то, что мы сделали те же две иерархические вызовы, мы получаем трассировки стека, что содержит неправильные фреймы. В моем первоначальном унаследованном коде обертки ситуация была еще хуже, так как трассировка стеки закончилась после того, как 5 кадров с верхним уровнем, имеющими адрес 0.
Таким образом, вопрос опять же, есть ли дополнительный код, который может быть добавлен до wbarcode[]
, что приведет к тому, что gdb выдаст действительную стеклу? Или есть ли какая-либо другая техника времени выполнения, которая может быть использована для создания gdb распознавания фреймов стека?
Хм, динамически генерируемая функция 'wbarcode' делает традиционный стек кадров (нажатие'% rbp'). Я думаю, это не поможет, если вызывающий был скомпилирован с помощью '-fomit-frame-pointer' по умолчанию, хотя, поскольку вызывающий' wbarcode', вероятно, не сохранит его '% rsp'-on-entry в' % rbp'. ** Что произойдет, если вы скомпилируете все это с помощью '-fno-omit-frame-pointer' **? IDK, если все еще будет раздел '.eh_frame_hdr', который gdb все равно захочет использовать ... –
Не уверен, что это умный код, так как он использует жесткий кодированный адрес в кодированном ассемблере. –
@MichaelPetch: он сказал, что это всего лишь MCVE, а не то, что на самом деле это похоже на его фактическую динамически генерируемую функцию. –