Stack Unwinding on AArch64: Frame‑Pointer and DWARF .eh_frame/.debug_frame Implementations
The article explains how AArch64 stack unwinding works using both frame‑pointer based methods and DWARF .eh_frame/.debug_frame CFI data, detailing the calling convention, compiler effects, CIE/FDE structures, and their integration into Android’s simpleperf profiler for accurate back‑traces.
In software development, stack unwinding (also called stack back‑trace) is essential for debugging and exception handling. Understanding its implementation on the AArch64 architecture helps developers trace program execution and diagnose errors more effectively.
This article introduces two typical stack‑unwinding methods for AArch64:
Frame‑pointer (FP) based unwinding
ELF/DWARF .eh_frame/.debug_frame based unwinding
2. Frame‑Pointer based unwinding
2.1 AArch64 calling convention – The frame pointer (x29) points to the current stack frame. At function entry the compiler sets FP to the base of the frame, allowing easy access to the previous frame’s return address and arguments. The layout of FP (x29) and LR (x30) is shown in the diagram.
Example of a simple C function compiled to AArch64 assembly:
stp x29, x30, [sp, #-48]!
mov x29, sp
str wzr, [sp, #40]
str wzr, [sp, #44]
...
ldr w1, [sp, #44]
add x0, sp, #0x28
bl 0x1234 <_func>During execution the FP always points to the current frame, and the stack pointer (sp) can be used interchangeably when the frame size is fixed.
2.2 Impact of compiler optimizations – Optimizations may omit the frame pointer (e.g., gcc/clang with -O2+). To force FP preservation, compile with -fno-omit-frame-pointer or add the option in Android.bp.
3. .eh_frame/.debug_frame parsing
3.1 ELF & DWARF – DWARF is a standard debugging information format. In ELF files, DWARF data resides in sections such as .debug_aranges, .debug_frame, .debug_info, .debug_line, etc. The .debug_frame section contains Call Frame Information (CFI) entries that describe how to unwind from one frame to the previous one.
The CFI consists of Common Information Entries (CIE) and Frame Description Entries (FDE). A typical CIE looks like:
Version: 1
Augmentation: "zR"
Code alignment factor: 1
Data alignment factor: -4
Return address column: 30
Augmentation data: 1b
DW_CFA_def_cfa: r31 (sp) ofs 0An example FDE for the function create_android_logger (address range 0x5020‑0x5070) is shown below:
00000050: FDE cie=0 pc=0x5020..0x5070
00000050: paciasp
00000052: stp x29, x30, [sp, #-32]!
00000058: mov x29, sp
0000005c: stp x20, x19, [sp, #16]
00000060: mov w19, w0
...
00000068: autiasp
0000006c: retThe FDE contains directives such as DW_CFA_def_cfa , DW_CFA_offset , and DW_CFA_restore that define the Canonical Frame Address (CFA) and the locations of saved registers.
4. DWARF‑based unwinding in simpleperf
4.1 simpleperf overview – simpleperf is a Google‑maintained profiling tool for Android, built on top of Linux perf. It captures hardware PMU events, kernel tracepoints, and perf events, and can generate flame graphs.
When a perf event occurs, simpleperf reads the current thread’s registers, the process’s memory map (/proc/PID/maps), and the thread’s stack memory. It then uses the DWARF information in .eh_frame/.debug_frame (via libunwindstack) to reconstruct the call stack.
4.2 Unwind code flow – The unwind process follows the CFI directives to compute the CFA, locate saved registers, and walk back through frames until the root is reached.
Images and diagrams in the original article illustrate the stack layout, CFI structures, and simpleperf’s unwind pipeline.
References include the AAPCS64 specification, DWARF documentation, and various online articles.
OPPO Kernel Craftsman
Sharing Linux kernel-related cutting-edge technology, technical articles, technical news, and curated tutorials
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.