Может ли кто нибудь объяснить на низком уровне, пошагово, то как работают исключения в с++?
Вот описание части Itanium ABI связанной с раскруткой стека:
https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html
ABI процессоров других архитектур устроены в этом месте точно так же.
Вот подробное описание структур, используемых при раскрутке стека в коде генерируемом gcc:
www.airs.com/blog/archives/460
www.airs.com/blog/archives/464
Документы по ссылкам достаточно сложны для восприятия. Для облегчения понимания можно откомпилировать простой С++ код выбрасывающий и ловящий исключение и найти в нем части описанные в первом документе.
Например
void e_destructor();
void s_destructor();
struct E {
int code;
E(int c): code(c)
{
}
~E()
{
e_destructor();
}
};
struct S {
~S()
{
s_destructor();
}
};
void f(int v)
{
throw E(v);
}
int g(void (*p)(int v), int v)
{
try {
struct S s;
p(v);
} catch(struct E e) {
return e.code;
} catch (int i) {
return i;
} catch (...) {
throw;
}
return 0;
}
после g++ -O2 -S превращается в следующие фрагменты:
функция f:
_Z1fi:
.LFB9:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movl %edi, %ebx
movl $4, %edi
call __cxa_allocate_exception
movl $_ZN1ED1Ev, %edx
movl %ebx, (%rax) <---- инициализация E::code
movl $_ZTI1E, %esi
movq %rax, %rdi
call __cxa_throw
.cfi_endproc
Здесь видны вызовы __cxa_allocate_exception, конструктора объекта класса E и __cxa_throw
функция g:
_Z1gPFviEi:
.LFB10:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
.cfi_lsda 0x3,.LLSDA10
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
pushq %rbx
.cfi_def_cfa_offset 24
.cfi_offset 3, -24
movq %rdi, %rax
movl %esi, %edi
subq $8, %rsp
.cfi_def_cfa_offset 32
.LEHB0:
call *%rax <--- вызов функции по указателю
.LEHE0:
.LEHB1:
call _Z12s_destructorv <--- вызов деструктора объекта s при нормальном выходе из блока try
.LEHE1:
xorl %eax, %eax
.L17:
addq $8, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 24
popq %rbx
.cfi_def_cfa_offset 16
popq %rbp
.cfi_def_cfa_offset 8
ret
Хвост с обработчиками исключений:
.L13:
.cfi_restore_state
movq %rdx, %rbx
movq %rax, %rbp
call _Z12s_destructorv
movq %rbx, %rdx
.L6:
cmpq $1, %rdx
je .L8
cmpq $2, %rdx
jne .L22
movq %rbp, %rdi
call __cxa_begin_catch
movl (%rax), %ebx
call __cxa_end_catch
movl %ebx, %eax
jmp .L17
.L14:
movq %rax, %rbp
jmp .L6
.L22:
movq %rbp, %rdi
call __cxa_begin_catch
.LEHB2:
call __cxa_rethrow
.LEHE2:
.L8:
movq %rbp, %rdi
call __cxa_get_exception_ptr
movq %rbp, %rdi
movl (%rax), %ebx
call __cxa_begin_catch
.LEHB3:
call _Z12e_destructorv
.LEHE3:
.LEHB4:
call __cxa_end_catch
.LEHE4:
movl %ebx, %eax
jmp .L17
.L16:
movq %rax, %rbx
call __cxa_end_catch
movq %rbx, %rdi
.LEHB5:
call _Unwind_Resume
.LEHE5:
.L15:
movq %rax, %rbx
call __cxa_end_catch
movq %rbx, %rdi
.LEHB6:
call _Unwind_Resume
.LEHE6:
.cfi_endproc
Здесь видны вызовы __cxa_begin_catch и __cxa_end_catch, __cxa_rethrow повторно выбрасывающий пойманное исключение, __cxa_get_exception_ptr и _Unwind_Resume, вызываемый если блок catch не ловит это исключение.
Дальше идёт структура LSDA описанная в третьем документе.
Сама раскрутка стека в этом коде отсутствует. Она выполняется следующим кодом libgcc:
фаза 1 и
фаза 2.