authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-10-09 23:48:47+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-10-10 15:12:27+02:00
log9aeabad519159a89220e475a6a544cd7d3a5db37
tree7ee4c18a00f8adfdc3a6d6666308e99e5e990c8a
parentf010a3131976b2e85aadccaaeda5c03d2288849f

std.debug.Dwarf.SelfUnwinder: assume same-value rule by default for all columns

This fixes leaf function unwinding, presumably among other things.

1 files changed, 21 insertions(+), 26 deletions(-)

lib/std/debug/Dwarf/SelfUnwinder.zig+21-26
......@@ -175,7 +175,6 @@ pub fn next(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEn
175175
176176fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) !usize {
177177 const format = cache_entry.cie.format;
178 const return_address_register = cache_entry.cie.return_address_register;
179178
180179 const cfa = switch (cache_entry.cfa_rule) {
181180 .none => return error.InvalidDebugInfo,
......@@ -202,23 +201,15 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE
202201 },
203202 };
204203
205 // If unspecified, we'll use the default rule for the return address register, which is
206 // typically equivalent to `.undefined` (meaning there is no return address), but may be
207 // overriden by ABIs.
208 var has_return_address: bool = switch (builtin.cpu.arch) {
209 // DWARF for the Arm 64-bit Architecture (AArch64) §4.3, p1
210 .aarch64, .aarch64_be => return_address_register >= 19 and return_address_register <= 28,
211 // ELF ABI s390x Supplement §1.6.4
212 .s390x => return_address_register >= 6 and return_address_register <= 15,
213 else => false,
214 };
215
216204 // Create a copy of the CPU state, to which we will apply the new rules.
217205 var new_cpu_state = unwinder.cpu_state;
218206
219207 // On all implemented architectures, the CFA is defined to be the previous frame's SP
220208 (try regNative(&new_cpu_state, sp_reg_num)).* = cfa;
221209
210 const return_address_register = cache_entry.cie.return_address_register;
211 var has_return_address = true;
212
222213 const rules_len = cache_entry.num_rules;
223214 for (cache_entry.rules_regs[0..rules_len], cache_entry.rules[0..rules_len]) |register, rule| {
224215 const new_val: union(enum) {
......@@ -228,13 +219,14 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE
228219 bytes: []const u8,
229220 } = switch (rule) {
230221 .default => val: {
231 // The default rule is typically equivalent to `.undefined`, but ABIs may override it.
232 switch (builtin.target.cpu.arch) {
233 .aarch64, .aarch64_be => if (register >= 19 and register <= 28) break :val .same,
234 .s390x => if (register >= 6 and register <= 15) break :val .same,
235 else => {},
236 }
237 break :val .undefined;
222 // The way things are supposed to work is that `.undefined` is the default rule
223 // unless an ABI says otherwise (e.g. aarch64, s390x).
224 //
225 // Unfortunately, at some point, a decision was made to have libgcc's unwinder
226 // assume `.same` as the default for all registers. Compilers then started depending
227 // on this, and the practice was carried forward to LLVM's libunwind and some of its
228 // backends.
229 break :val .same;
238230 },
239231 .undefined => .undefined,
240232 .same_value => .same,
......@@ -273,6 +265,12 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE
273265 .undefined => {
274266 const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register));
275267 @memset(dest, undefined);
268
269 // If the return address register is explicitly set to `.undefined`, it means that
270 // there are no more frames to unwind.
271 if (register == return_address_register) {
272 has_return_address = false;
273 }
276274 },
277275 .val => |val| {
278276 const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register));
......@@ -286,15 +284,12 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE
286284 @memcpy(dest, src);
287285 },
288286 }
289 if (register == return_address_register) {
290 has_return_address = new_val != .undefined;
291 }
292287 }
293288
294 const return_address: usize = if (has_return_address) pc: {
295 const raw_ptr = try regNative(&new_cpu_state, return_address_register);
296 break :pc stripInstructionPtrAuthCode(raw_ptr.*);
297 } else 0;
289 const return_address = if (has_return_address)
290 stripInstructionPtrAuthCode((try regNative(&new_cpu_state, return_address_register)).*)
291 else
292 0;
298293
299294 (try regNative(&new_cpu_state, ip_reg_num)).* = return_address;
300295