aboutsummaryrefslogtreecommitdiff
path: root/lib/std/debug/SelfInfo/ElfModule.zig
diff options
context:
space:
mode:
authormlugg <mlugg@mlugg.co.uk>2025-09-19 13:35:12 +0100
committermlugg <mlugg@mlugg.co.uk>2025-09-30 13:44:55 +0100
commit099a95041054e456ebefbd75f6a4f9f6961002be (patch)
tree6293805ebdf2664d5b5906e614297972a7c8da6f /lib/std/debug/SelfInfo/ElfModule.zig
parent9c1821d3bfadc5eddd4dff271a4920c03ee0ffea (diff)
downloadzig-099a95041054e456ebefbd75f6a4f9f6961002be.tar.gz
zig-099a95041054e456ebefbd75f6a4f9f6961002be.zip
std.debug.SelfInfo: thread safety
This has been a TODO for ages, but in the past it didn't really matter because stack traces are typically printed to stderr for which a mutex is held so in practice there was a mutex guarding usage of `SelfInfo`. However, now that `SelfInfo` is also used for simply capturing traces, thread safety is needed. Instead of just a single mutex, though, there are a couple of different mutexes involved; this helps make critical sections smaller, particularly when unwinding the stack as `unwindFrame` doesn't typically need to hold any lock at all.
Diffstat (limited to 'lib/std/debug/SelfInfo/ElfModule.zig')
-rw-r--r--lib/std/debug/SelfInfo/ElfModule.zig62
1 files changed, 42 insertions, 20 deletions
diff --git a/lib/std/debug/SelfInfo/ElfModule.zig b/lib/std/debug/SelfInfo/ElfModule.zig
index 0812beaf7a..f80e33a7a0 100644
--- a/lib/std/debug/SelfInfo/ElfModule.zig
+++ b/lib/std/debug/SelfInfo/ElfModule.zig
@@ -7,16 +7,26 @@ gnu_eh_frame: ?[]const u8,
pub const LookupCache = void;
pub const DebugInfo = struct {
+ /// Held while checking and/or populating `loaded_elf`/`scanned_dwarf`/`unwind`.
+ /// Once data is populated and a pointer to the field has been gotten, the lock
+ /// is released; i.e. it is not held while *using* the loaded debug info.
+ mutex: std.Thread.Mutex,
+
loaded_elf: ?ElfFile,
scanned_dwarf: bool,
unwind: [2]?Dwarf.Unwind,
pub const init: DebugInfo = .{
+ .mutex = .{},
.loaded_elf = null,
.scanned_dwarf = false,
.unwind = @splat(null),
};
pub fn deinit(di: *DebugInfo, gpa: Allocator) void {
if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa);
+ for (di.unwind) |*opt_unwind| {
+ const unwind = &(opt_unwind orelse continue);
+ unwind.deinit(gpa);
+ }
}
};
@@ -145,34 +155,41 @@ fn loadElf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void
}
}
pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {
- if (di.loaded_elf == null) try module.loadElf(gpa, di);
const vaddr = address - module.load_offset;
- if (di.loaded_elf.?.dwarf) |*dwarf| {
- if (!di.scanned_dwarf) {
- dwarf.open(gpa, native_endian) catch |err| switch (err) {
+ {
+ di.mutex.lock();
+ defer di.mutex.unlock();
+ if (di.loaded_elf == null) try module.loadElf(gpa, di);
+ const loaded_elf = &di.loaded_elf.?;
+ // We need the lock if using DWARF, as we might scan the DWARF or build a line number table.
+ if (loaded_elf.dwarf) |*dwarf| {
+ if (!di.scanned_dwarf) {
+ dwarf.open(gpa, native_endian) catch |err| switch (err) {
+ error.InvalidDebugInfo,
+ error.MissingDebugInfo,
+ error.OutOfMemory,
+ => |e| return e,
+ error.EndOfStream,
+ error.Overflow,
+ error.ReadFailed,
+ error.StreamTooLong,
+ => return error.InvalidDebugInfo,
+ };
+ di.scanned_dwarf = true;
+ }
+ return dwarf.getSymbol(gpa, native_endian, vaddr) catch |err| switch (err) {
error.InvalidDebugInfo,
error.MissingDebugInfo,
error.OutOfMemory,
=> |e| return e,
+ error.ReadFailed,
error.EndOfStream,
error.Overflow,
- error.ReadFailed,
error.StreamTooLong,
=> return error.InvalidDebugInfo,
};
- di.scanned_dwarf = true;
}
- return dwarf.getSymbol(gpa, native_endian, vaddr) catch |err| switch (err) {
- error.InvalidDebugInfo,
- error.MissingDebugInfo,
- error.OutOfMemory,
- => |e| return e,
- error.ReadFailed,
- error.EndOfStream,
- error.Overflow,
- error.StreamTooLong,
- => return error.InvalidDebugInfo,
- };
+ // Otherwise, we're just going to scan the symtab, which we don't need the lock for; fall out of this block.
}
// When there's no DWARF available, fall back to searching the symtab.
return di.loaded_elf.?.searchSymtab(gpa, vaddr) catch |err| switch (err) {
@@ -231,9 +248,14 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro
}
}
pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
- if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);
- std.debug.assert(di.unwind[0] != null);
- for (&di.unwind) |*opt_unwind| {
+ const unwinds: *const [2]?Dwarf.Unwind = u: {
+ di.mutex.lock();
+ defer di.mutex.unlock();
+ if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);
+ std.debug.assert(di.unwind[0] != null);
+ break :u &di.unwind;
+ };
+ for (unwinds) |*opt_unwind| {
const unwind = &(opt_unwind.* orelse break);
return context.unwindFrame(gpa, unwind, module.load_offset, null) catch |err| switch (err) {
error.MissingDebugInfo => continue, // try the next one