Summary
The three StubEmitter implementations (X86_64, X86_32, ARM64) generate machine code only — no DWARF CFI (Call Frame Information) entries. The injected .vmpilot section has no corresponding .eh_frame (ELF) or __unwind_info (Mach-O).
Priority: Post-first-release. v1 error policy is abort() — no C++ exceptions cross the stub boundary. This becomes critical in v2 when NATIVE_CALL targets may throw.
Impact
| Scenario |
Result |
| Debugger backtrace |
gdb/lldb cannot unwind through the stub; bt terminates or shows garbage frames |
| C++ exception through native call |
If a NATIVE_CALL target throws, the unwinder finds no FDE for the stub PC, falls back to heuristics, reads wrong RSP offset → std::terminate() or segfault |
| Profiler (perf, VTune) |
Stack sampling breaks at stub → incomplete profiles |
| Crash dump analysis |
Coredump/minidump cannot reconstruct the call chain through the stub |
Technical Details
Each stub's stack frame (x86_64 example):
[ENDBR64] ← 4 bytes (CET landing pad)
[PUSH rbx..r15] ← 6 × 8 = 48 bytes (callee-saved)
[initial_regs array] ← sub rsp, 128 (16 × 8)
[VmStubArgs (64B)] ← sub rsp, 64
[CALL vm_stub_entry] ← indirect call through call_slot
[ADD rsp, 64+128] ← deallocate
[POP r15..rbx] ← restore
[JMP resume] ← branch to region end
Required .eh_frame FDE description:
- Initial CFA = rsp + 8 (return address)
- After each PUSH: CFA offset increments by 8, register saved at
[CFA - N]
- After
sub rsp, 128: CFA offset += 128
- After
sub rsp, 64: CFA offset += 64
- At CALL: all callee-saved locations described
Implementation Plan
-
StubEmitter: emit_entry_stub() returns a new Stub::eh_frame_fde field (raw FDE bytes) alongside the machine code. The FDE offsets are relative to the stub start.
-
PayloadBuilder: Collect all per-stub FDEs, prepend a shared CIE (Common Information Entry), build a complete .eh_frame section.
-
ELFEditor: When add_segment() is called, also create an .eh_frame section (SHT_PROGBITS, SHF_ALLOC) containing the CIE+FDE data. Register it with a PT_GNU_EH_FRAME segment if needed.
-
MachOEditor: Create compact unwind entries or __eh_frame section within the __VMPILOT segment.
-
PEEditor: Create .pdata (function table) + .xdata (unwind data) for x64 SEH.
Why Not Now
- v1 error policy:
vm_stub_entry calls abort() on any error (NDEBUG) or returns INT64_MIN (debug). No C++ exception crosses the stub boundary.
- DWARF CFI binary encoding is complex (ULEB128, register mappings per ABI, CIE augmentation strings). Getting it wrong is worse than not having it.
- Debugger backtrace quality is a developer convenience, not a correctness issue.
References
- D15 §5.2: Uniform 12-step pipeline (stubs are pre-pipeline entry points)
- D10 GAP5 (C5): "Exception/stack unwinding — stubs break DWARF unwinding"
- RM §5: "Register CFG/CFI/BTI valid targets" (partially done: landing pads present, unwind info missing)
Summary
The three StubEmitter implementations (
X86_64,X86_32,ARM64) generate machine code only — no DWARF CFI (Call Frame Information) entries. The injected.vmpilotsection has no corresponding.eh_frame(ELF) or__unwind_info(Mach-O).Priority: Post-first-release. v1 error policy is
abort()— no C++ exceptions cross the stub boundary. This becomes critical in v2 whenNATIVE_CALLtargets may throw.Impact
btterminates or shows garbage framesstd::terminate()or segfaultTechnical Details
Each stub's stack frame (x86_64 example):
Required
.eh_frameFDE description:[CFA - N]sub rsp, 128: CFA offset += 128sub rsp, 64: CFA offset += 64Implementation Plan
StubEmitter:
emit_entry_stub()returns a newStub::eh_frame_fdefield (raw FDE bytes) alongside the machine code. The FDE offsets are relative to the stub start.PayloadBuilder: Collect all per-stub FDEs, prepend a shared CIE (Common Information Entry), build a complete
.eh_framesection.ELFEditor: When
add_segment()is called, also create an.eh_framesection (SHT_PROGBITS,SHF_ALLOC) containing the CIE+FDE data. Register it with aPT_GNU_EH_FRAMEsegment if needed.MachOEditor: Create compact unwind entries or
__eh_framesection within the__VMPILOTsegment.PEEditor: Create
.pdata(function table) +.xdata(unwind data) for x64 SEH.Why Not Now
vm_stub_entrycallsabort()on any error (NDEBUG) or returnsINT64_MIN(debug). No C++ exception crosses the stub boundary.References