authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-20 14:23:39+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:55+01:00
logc41bf996848a32c60e6f1dac89769d33a1b83178
tree6b33e8437265aa23157c147f9351086abc91ad11
parent099a95041054e456ebefbd75f6a4f9f6961002be
signaturelock-open Commit is signed but in an unrecognized format.

std.debug: don't assume return address register is defined if not specified

This logic was causing some occasional infinite looping on ARM, where the `.debug_frame` section is often incomplete since the `.exidx` section is used for unwind information. But the information we're getting from the compiler is totally *valid*: it's leaving the rule as the default, which is (as with most architectures) equivalent to `.undefined`!

1 files changed, 31 insertions(+), 12 deletions(-)

lib/std/debug/SelfInfo.zig+31-12
...@@ -243,6 +243,15 @@ pub const DwarfUnwindContext = struct {...@@ -243,6 +243,15 @@ pub const DwarfUnwindContext = struct {
243 return ptr.*;243 return ptr.*;
244 }244 }
245245
246 /// The default rule is typically equivalent to `.undefined`, but ABIs may define it differently.
247 fn defaultRuleBehavior(register: u8) enum { undefined, same_value } {
248 if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 28) {
249 // The default rule for callee-saved registers on AArch64 acts like the `.same_value` rule
250 return .same_value;
251 }
252 return .undefined;
253 }
254
246 /// Resolves the register rule and places the result into `out` (see regBytes). Returns `true`255 /// Resolves the register rule and places the result into `out` (see regBytes). Returns `true`
247 /// iff the rule was undefined. This is *not* the same as `col.rule == .undefined`, because the256 /// iff the rule was undefined. This is *not* the same as `col.rule == .undefined`, because the
248 /// default rule may be undefined.257 /// default rule may be undefined.
...@@ -256,17 +265,18 @@ pub const DwarfUnwindContext = struct {...@@ -256,17 +265,18 @@ pub const DwarfUnwindContext = struct {
256 switch (col.rule) {265 switch (col.rule) {
257 .default => {266 .default => {
258 const register = col.register orelse return error.InvalidRegister;267 const register = col.register orelse return error.InvalidRegister;
259 // The default type is usually undefined, but can be overriden by ABI authors.268 switch (defaultRuleBehavior(register)) {
260 // See the doc comment on `Dwarf.Unwind.VirtualMachine.RegisterRule.default`.269 .undefined => {
261 if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 28) {270 @memset(out, undefined);
262 // Callee-saved registers are initialized as if they had the .same_value rule271 return true;
263 const src = try context.cpu_context.dwarfRegisterBytes(register);272 },
264 if (src.len != out.len) return error.RegisterSizeMismatch;273 .same_value => {
265 @memcpy(out, src);274 const src = try context.cpu_context.dwarfRegisterBytes(register);
266 return false;275 if (src.len != out.len) return error.RegisterSizeMismatch;
276 @memcpy(out, src);
277 return false;
278 },
267 }279 }
268 @memset(out, undefined);
269 return true;
270 },280 },
271 .undefined => {281 .undefined => {
272 @memset(out, undefined);282 @memset(out, undefined);
...@@ -449,7 +459,9 @@ pub const DwarfUnwindContext = struct {...@@ -449,7 +459,9 @@ pub const DwarfUnwindContext = struct {
449459
450 expression_context.cfa = context.cfa;460 expression_context.cfa = context.cfa;
451461
452 var has_return_address = true;462 // If the rule for the return address register is 'undefined', that indicates there is no
463 // return address, i.e. this is the end of the stack.
464 var explicit_has_return_address: ?bool = null;
453465
454 // Create a copy of the CPU context, to which we will apply the new rules.466 // Create a copy of the CPU context, to which we will apply the new rules.
455 var new_cpu_context = context.cpu_context;467 var new_cpu_context = context.cpu_context;
...@@ -462,11 +474,18 @@ pub const DwarfUnwindContext = struct {...@@ -462,11 +474,18 @@ pub const DwarfUnwindContext = struct {
462 const dest = try new_cpu_context.dwarfRegisterBytes(register);474 const dest = try new_cpu_context.dwarfRegisterBytes(register);
463 const rule_undef = try context.resolveRegisterRule(gpa, column, expression_context, dest);475 const rule_undef = try context.resolveRegisterRule(gpa, column, expression_context, dest);
464 if (register == cie.return_address_register) {476 if (register == cie.return_address_register) {
465 has_return_address = !rule_undef;477 explicit_has_return_address = !rule_undef;
466 }478 }
467 }479 }
468 }480 }
469481
482 // If the return address register did not have an explicitly specified rules then it uses
483 // the default rule, which is usually equivalent to '.undefined', i.e. end-of-stack.
484 const has_return_address = explicit_has_return_address orelse switch (defaultRuleBehavior(cie.return_address_register)) {
485 .undefined => false,
486 .same_value => return error.InvalidDebugInfo, // this doesn't make sense, we would get stuck in an infinite loop
487 };
488
470 const return_address: usize = if (has_return_address) pc: {489 const return_address: usize = if (has_return_address) pc: {
471 const raw_ptr = try regNative(&new_cpu_context, cie.return_address_register);490 const raw_ptr = try regNative(&new_cpu_context, cie.return_address_register);
472 break :pc stripInstructionPtrAuthCode(raw_ptr.*);491 break :pc stripInstructionPtrAuthCode(raw_ptr.*);