Может ли кто нибудь объяснить на низком уровне, пошагово, то как работают исключения в с++?
Вот описание части 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.