Когда я смотрю на символы в моей библиотеке, nm mylib.a
Я вижу несколько повторяющихся записей, которые выглядят так:
000000000002d130 S __ZN7quadmat11SpAddLeavesC1EPNS_14BlockContainerEPy
00000000000628a8 S __ZN7quadmat11SpAddLeavesC1EPNS_14BlockContainerEPy.eh
Когда по каналу c++filt
:
000000000002d130 S quadmat::SpAddLeaves::SpAddLeaves(quadmat::BlockContainer*, unsigned long long*)
00000000000628a8 S quadmat::SpAddLeaves::SpAddLeaves(quadmat::BlockContainer*, unsigned long long*) (.eh)
Что это .eh
значит, а для чего используется этот дополнительный символ?
Я вижу, что это как-то связано с обработкой исключений. Но почему это использует дополнительный символ?
(Я заметил это с лязгом)
Вот простой код:
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
искалеченное) имя. Эти директивы указывают, где в стеке сохраняются регистры процессора. Когда выдается исключение и стек разматывается, код в функции не должен выполняться (за исключением деструкторов локальных объектов), но регистры, которые были сохранены ранее, должны быть восстановлены. Эти таблицы хранят именно эту информацию.
Эти таблицы могут быть сброшены через 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]
Вот Вы можете узнать о формате этого блока. Вот немного больше и альтернативный более компактный способ представления той же информации. В основном, этот блок описывает, какие регистры и откуда в стеке извлекать во время разматывания стека.
Чтобы увидеть исходное содержание этих символов, вы можете перечислить все символы с их смещениями:
$ 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
, компилируя и просматривая вывод сборки.
дальнейшее чтение
Он обозначает подставку для обработчика исключений и обычно связан с информацией ниже:
Если вы используете список экспорта и создаете совместно используемую библиотеку или исполняемый файл, который будет использоваться с флагом ld’s -bundle_loader, вам необходимо включить символы для информации о фрейме исключения в список экспорта для ваших экспортируемых символов C ++. В противном случае они могут быть удалены. Эти символы заканчиваются на .eh; Вы можете просмотреть их с помощью инструмента nm.