Вот несколько простых код:
bool extenrnal_variable;
int f(...)
{
if (extenrnal_variable)
throw 0;
return 42;
}
int g()
{
return f(1, 2, 3);
}
Я добавил extenrnal_variable
, чтобы предотвратить компилятор от оптимизации всех ветвей прочь. f
имеет ...
для предотвращения вставки.
При компиляции с:
$ clang++ -S -O3 -m32 -o - eh.cpp | c++filt
он испускает следующий код для g()
(остальное опущено):
g(): ## @_Z1gv
.cfi_startproc
## BB#0:
pushl %ebp
Ltmp9:
.cfi_def_cfa_offset 8
Ltmp10:
.cfi_offset %ebp, -8
movl %esp, %ebp
Ltmp11:
.cfi_def_cfa_register %ebp
subl $24, %esp
movl $3, 8(%esp)
movl $2, 4(%esp)
movl $1, (%esp)
calll f(...)
movl $42, %eax
addl $24, %esp
popl %ebp
ret
.cfi_endproc
Всех этих .cfi_*
директив существуют для раскручивания стеки в случае исключение. Все они скомпилированы в блок FDE (запись описания кадра) и сохранены под именем g().eh
(__Z1gv.eh
). Эти директивы определяют, где в стеке регистры CPU сохраняются. Когда генерируется исключение, и стек разматывается, код в функции не должен выполняться (за исключением деструкторов локалей), но регистры, которые были сохранены ранее, должны быть восстановлены. Эти таблицы хранят именно эту информацию.
Эти таблицы могут быть сброшены с помощью инструмента dwarfdump
:
$ dwarfdump --eh-frame --english eh.o | c++filt
Выход:
0x00000018: FDE
length: 0x00000018
CIE_pointer: 0x00000000
start_addr: 0x00000000 f(...)
range_size: 0x0000004d (end_addr = 0x0000004d)
Instructions: 0x00000000: CFA=esp+4 eip=[esp]
0x00000001: CFA=esp+8 ebp=[esp] eip=[esp+4]
0x00000003: CFA=ebp+8 ebp=[ebp] eip=[ebp+4]
0x00000007: CFA=ebp+8 ebp=[ebp] esi=[ebp-4] eip=[ebp+4]
0x00000034: FDE
length: 0x00000018
CIE_pointer: 0x00000000
start_addr: 0x00000050 g()
range_size: 0x0000002c (end_addr = 0x0000007c)
Instructions: 0x00000050: CFA=esp+4 eip=[esp]
0x00000051: CFA=esp+8 ebp=[esp] eip=[esp+4]
0x00000053: CFA=ebp+8 ebp=[ebp] eip=[ebp+4]
Here вы могли бы узнать о формате этого блока. Here немного больше и несколько альтернативных более компактных способов представления одной и той же информации. В основном этот блок описывает, какие регистры и где из стека, чтобы поп во время разворачивания стека.
Чтобы увидеть необработанный содержание этих символов вы можете перечислить все символы со своими сдвигами:
$ nm -n eh.o
00000000 T __Z1fz
U __ZTIi
U ___cxa_allocate_exception
U ___cxa_throw
00000050 T __Z1gv
000000a8 s EH_frame0
000000c0 S __Z1fz.eh
000000dc S __Z1gv.eh
000000f8 S _extenrnal_variable
А потом сбросить (__TEXT,__eh_frame)
раздел:
$ otool -s __TEXT __eh_frame eh.o
eh.o:
Contents of (__TEXT,__eh_frame) section
000000a8 14 00 00 00 00 00 00 00 01 7a 52 00 01 7c 08 01
000000b8 10 0c 05 04 88 01 00 00 18 00 00 00 1c 00 00 00
000000c8 38 ff ff ff 4d 00 00 00 00 41 0e 08 84 02 42 0d
000000d8 04 44 86 03 18 00 00 00 38 00 00 00 6c ff ff ff
000000e8 2c 00 00 00 00 41 0e 08 84 02 42 0d 04 00 00 00
Путем сопоставления корректоры вы могли видеть как кодируется каждый символ.
Когда есть локальные переменные, они должны быть уничтожены во время разматывания стека. Для этого обычно обычно добавляется код в самих функциях и создаются дополнительные большие таблицы. Вы можете изучить это самостоятельно, добавив локальную переменную с нетривиальным деструктором в g
, компилируя и просматривая вывод сборки.
Дальнейшее чтение
обработка исключений? –
http://stackoverflow.com/a/5592898/1171191, также http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html – BoBTFish
Итак, что-то делать с исключениями? В чем его цель? – Adam