authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-19 13:35:12+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:55+01:00
log099a95041054e456ebefbd75f6a4f9f6961002be
tree6293805ebdf2664d5b5906e614297972a7c8da6f
parent9c1821d3bfadc5eddd4dff271a4920c03ee0ffea
signaturelock-open Commit is signed but in an unrecognized format.

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.

6 files changed, 136 insertions(+), 44 deletions(-)

lib/std/debug.zig+5-5
...@@ -238,7 +238,6 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {...@@ -238,7 +238,6 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
238 nosuspend bw.print(fmt, args) catch return;238 nosuspend bw.print(fmt, args) catch return;
239}239}
240240
241/// TODO multithreaded awareness
242/// Marked `inline` to propagate a comptime-known error to callers.241/// Marked `inline` to propagate a comptime-known error to callers.
243pub inline fn getSelfDebugInfo() !*SelfInfo {242pub inline fn getSelfDebugInfo() !*SelfInfo {
244 if (!SelfInfo.target_supported) return error.UnsupportedTarget;243 if (!SelfInfo.target_supported) return error.UnsupportedTarget;
...@@ -1169,7 +1168,8 @@ test printLineFromFile {...@@ -1169,7 +1168,8 @@ test printLineFromFile {
1169 }1168 }
1170}1169}
11711170
1172/// TODO multithreaded awareness1171/// The returned allocator should be thread-safe if the compilation is multi-threaded, because
1172/// multiple threads could capture and/or print stack traces simultaneously.
1173fn getDebugInfoAllocator() Allocator {1173fn getDebugInfoAllocator() Allocator {
1174 // Allow overriding the debug info allocator by exposing `root.debug.getDebugInfoAllocator`.1174 // Allow overriding the debug info allocator by exposing `root.debug.getDebugInfoAllocator`.
1175 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getDebugInfoAllocator")) {1175 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getDebugInfoAllocator")) {
...@@ -1177,10 +1177,10 @@ fn getDebugInfoAllocator() Allocator {...@@ -1177,10 +1177,10 @@ fn getDebugInfoAllocator() Allocator {
1177 }1177 }
1178 // Otherwise, use a global arena backed by the page allocator1178 // Otherwise, use a global arena backed by the page allocator
1179 const S = struct {1179 const S = struct {
1180 var arena: ?std.heap.ArenaAllocator = null;1180 var arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
1181 var ts_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena.allocator() };
1181 };1182 };
1182 if (S.arena == null) S.arena = .init(std.heap.page_allocator);1183 return S.ts_arena.allocator();
1183 return S.arena.?.allocator();
1184}1184}
11851185
1186/// Whether or not the current target can print useful debug information when a segfault occurs.1186/// Whether or not the current target can print useful debug information when a segfault occurs.
lib/std/debug/Dwarf.zig+1-1
...@@ -346,7 +346,7 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void {...@@ -346,7 +346,7 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void {
346 di.* = undefined;346 di.* = undefined;
347}347}
348348
349pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 {349pub fn getSymbolName(di: *const Dwarf, address: u64) ?[]const u8 {
350 // Iterate the function list backwards so that we see child DIEs before their parents. This is350 // Iterate the function list backwards so that we see child DIEs before their parents. This is
351 // important because `DW_TAG_inlined_subroutine` DIEs will have a range which is a sub-range of351 // important because `DW_TAG_inlined_subroutine` DIEs will have a range which is a sub-range of
352 // their caller, and we want to return the callee's name, not the caller's.352 // their caller, and we want to return the callee's name, not the caller's.
lib/std/debug/SelfInfo.zig+47-12
...@@ -18,7 +18,21 @@ const root = @import("root");...@@ -18,7 +18,21 @@ const root = @import("root");
1818
19const SelfInfo = @This();19const SelfInfo = @This();
2020
21modules: if (target_supported) std.AutoArrayHashMapUnmanaged(usize, Module.DebugInfo) else void,21/// Locks access to `modules`. However, does *not* lock the `Module.DebugInfo`, nor `lookup_cache`
22/// the implementation is responsible for locking as needed in its exposed methods.
23///
24/// TODO: to allow `SelfInfo` to work on freestanding, we currently just don't use this mutex there.
25/// That's a bad solution, but a better one depends on the standard library's general support for
26/// "bring your own OS" being improved.
27modules_mutex: switch (builtin.os.tag) {
28 else => std.Thread.Mutex,
29 .freestanding, .other => struct {
30 fn lock(_: @This()) void {}
31 fn unlock(_: @This()) void {}
32 },
33},
34/// Value is allocated into gpa to give it a stable pointer.
35modules: if (target_supported) std.AutoArrayHashMapUnmanaged(usize, *Module.DebugInfo) else void,
22lookup_cache: if (target_supported) Module.LookupCache else void,36lookup_cache: if (target_supported) Module.LookupCache else void,
2337
24pub const Error = error{38pub const Error = error{
...@@ -43,12 +57,16 @@ pub const supports_unwinding: bool = target_supported and Module.supports_unwind...@@ -43,12 +57,16 @@ pub const supports_unwinding: bool = target_supported and Module.supports_unwind
43pub const UnwindContext = if (supports_unwinding) Module.UnwindContext;57pub const UnwindContext = if (supports_unwinding) Module.UnwindContext;
4458
45pub const init: SelfInfo = .{59pub const init: SelfInfo = .{
60 .modules_mutex = .{},
46 .modules = .empty,61 .modules = .empty,
47 .lookup_cache = if (Module.LookupCache != void) .init,62 .lookup_cache = if (Module.LookupCache != void) .init,
48};63};
4964
50pub fn deinit(self: *SelfInfo, gpa: Allocator) void {65pub fn deinit(self: *SelfInfo, gpa: Allocator) void {
51 for (self.modules.values()) |*di| di.deinit(gpa);66 for (self.modules.values()) |di| {
67 di.deinit(gpa);
68 gpa.destroy(di);
69 }
52 self.modules.deinit(gpa);70 self.modules.deinit(gpa);
53 if (Module.LookupCache != void) self.lookup_cache.deinit(gpa);71 if (Module.LookupCache != void) self.lookup_cache.deinit(gpa);
54}72}
...@@ -56,21 +74,35 @@ pub fn deinit(self: *SelfInfo, gpa: Allocator) void {...@@ -56,21 +74,35 @@ pub fn deinit(self: *SelfInfo, gpa: Allocator) void {
56pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {74pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
57 comptime assert(supports_unwinding);75 comptime assert(supports_unwinding);
58 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);76 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
59 const gop = try self.modules.getOrPut(gpa, module.key());77 const di: *Module.DebugInfo = di: {
60 self.modules.lockPointers();78 self.modules_mutex.lock();
61 defer self.modules.unlockPointers();79 defer self.modules_mutex.unlock();
62 if (!gop.found_existing) gop.value_ptr.* = .init;80 const gop = try self.modules.getOrPut(gpa, module.key());
63 return module.unwindFrame(gpa, gop.value_ptr, context);81 if (gop.found_existing) break :di gop.value_ptr.*;
82 errdefer _ = self.modules.pop().?;
83 const di = try gpa.create(Module.DebugInfo);
84 di.* = .init;
85 gop.value_ptr.* = di;
86 break :di di;
87 };
88 return module.unwindFrame(gpa, di, context);
64}89}
6590
66pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {91pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {
67 comptime assert(target_supported);92 comptime assert(target_supported);
68 const module: Module = try .lookup(&self.lookup_cache, gpa, address);93 const module: Module = try .lookup(&self.lookup_cache, gpa, address);
69 const gop = try self.modules.getOrPut(gpa, module.key());94 const di: *Module.DebugInfo = di: {
70 self.modules.lockPointers();95 self.modules_mutex.lock();
71 defer self.modules.unlockPointers();96 defer self.modules_mutex.unlock();
72 if (!gop.found_existing) gop.value_ptr.* = .init;97 const gop = try self.modules.getOrPut(gpa, module.key());
73 return module.getSymbolAtAddress(gpa, gop.value_ptr, address);98 if (gop.found_existing) break :di gop.value_ptr.*;
99 errdefer _ = self.modules.pop().?;
100 const di = try gpa.create(Module.DebugInfo);
101 di.* = .init;
102 gop.value_ptr.* = di;
103 break :di di;
104 };
105 return module.getSymbolAtAddress(gpa, di, address);
74}106}
75107
76pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {108pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {
...@@ -88,6 +120,9 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)...@@ -88,6 +120,9 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)
88/// be valid to consider the entire application one module, or on the other hand to consider each120/// be valid to consider the entire application one module, or on the other hand to consider each
89/// object file a module.121/// object file a module.
90///122///
123/// Because different threads can collect stack traces concurrently, the implementation must be able
124/// to tolerate concurrent calls to any method it implements.
125///
91/// This type must must expose the following declarations:126/// This type must must expose the following declarations:
92///127///
93/// ```128/// ```
lib/std/debug/SelfInfo/DarwinModule.zig+22-2
...@@ -252,6 +252,15 @@ fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO...@@ -252,6 +252,15 @@ fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO
252 };252 };
253}253}
254pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {254pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {
255 // We need the lock for a few things:
256 // * loading the Mach-O module
257 // * loading the referenced object file
258 // * scanning the DWARF of that object file
259 // * building the line number table of that object file
260 // That's enough that it doesn't really seem worth scoping the lock more tightly than the whole function..
261 di.mutex.lock();
262 defer di.mutex.unlock();
263
255 if (di.loaded_macho == null) di.loaded_macho = module.loadMachO(gpa) catch |err| switch (err) {264 if (di.loaded_macho == null) di.loaded_macho = module.loadMachO(gpa) catch |err| switch (err) {
256 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| return e,265 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| return e,
257 else => return error.ReadFailed,266 else => return error.ReadFailed,
...@@ -341,8 +350,12 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -341,8 +350,12 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
341 };350 };
342}351}
343fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {352fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
344 if (di.unwind == null) di.unwind = module.loadUnwindInfo();353 const unwind: *const DebugInfo.Unwind = u: {
345 const unwind = &di.unwind.?;354 di.mutex.lock();
355 defer di.mutex.unlock();
356 if (di.unwind == null) di.unwind = module.loadUnwindInfo();
357 break :u &di.unwind.?;
358 };
346359
347 const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo;360 const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo;
348 if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo;361 if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo;
...@@ -649,10 +662,17 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -649,10 +662,17 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
649 return ret_addr;662 return ret_addr;
650}663}
651pub const DebugInfo = struct {664pub const DebugInfo = struct {
665 /// Held while checking and/or populating `unwind` or `loaded_macho`.
666 /// Once a field is populated and the pointer `&di.loaded_macho.?` or `&di.unwind.?` has been
667 /// gotten, the lock is released; i.e. it is not held while *using* the loaded info.
668 mutex: std.Thread.Mutex,
669
652 unwind: ?Unwind,670 unwind: ?Unwind,
653 loaded_macho: ?LoadedMachO,671 loaded_macho: ?LoadedMachO,
654672
655 pub const init: DebugInfo = .{673 pub const init: DebugInfo = .{
674 .mutex = .{},
675
656 .unwind = null,676 .unwind = null,
657 .loaded_macho = null,677 .loaded_macho = null,
658 };678 };
lib/std/debug/SelfInfo/ElfModule.zig+42-20
...@@ -7,16 +7,26 @@ gnu_eh_frame: ?[]const u8,...@@ -7,16 +7,26 @@ gnu_eh_frame: ?[]const u8,
7pub const LookupCache = void;7pub const LookupCache = void;
88
9pub const DebugInfo = struct {9pub const DebugInfo = struct {
10 /// Held while checking and/or populating `loaded_elf`/`scanned_dwarf`/`unwind`.
11 /// Once data is populated and a pointer to the field has been gotten, the lock
12 /// is released; i.e. it is not held while *using* the loaded debug info.
13 mutex: std.Thread.Mutex,
14
10 loaded_elf: ?ElfFile,15 loaded_elf: ?ElfFile,
11 scanned_dwarf: bool,16 scanned_dwarf: bool,
12 unwind: [2]?Dwarf.Unwind,17 unwind: [2]?Dwarf.Unwind,
13 pub const init: DebugInfo = .{18 pub const init: DebugInfo = .{
19 .mutex = .{},
14 .loaded_elf = null,20 .loaded_elf = null,
15 .scanned_dwarf = false,21 .scanned_dwarf = false,
16 .unwind = @splat(null),22 .unwind = @splat(null),
17 };23 };
18 pub fn deinit(di: *DebugInfo, gpa: Allocator) void {24 pub fn deinit(di: *DebugInfo, gpa: Allocator) void {
19 if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa);25 if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa);
26 for (di.unwind) |*opt_unwind| {
27 const unwind = &(opt_unwind orelse continue);
28 unwind.deinit(gpa);
29 }
20 }30 }
21};31};
2232
...@@ -145,34 +155,41 @@ fn loadElf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void...@@ -145,34 +155,41 @@ fn loadElf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void
145 }155 }
146}156}
147pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {157pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {
148 if (di.loaded_elf == null) try module.loadElf(gpa, di);
149 const vaddr = address - module.load_offset;158 const vaddr = address - module.load_offset;
150 if (di.loaded_elf.?.dwarf) |*dwarf| {159 {
151 if (!di.scanned_dwarf) {160 di.mutex.lock();
152 dwarf.open(gpa, native_endian) catch |err| switch (err) {161 defer di.mutex.unlock();
162 if (di.loaded_elf == null) try module.loadElf(gpa, di);
163 const loaded_elf = &di.loaded_elf.?;
164 // We need the lock if using DWARF, as we might scan the DWARF or build a line number table.
165 if (loaded_elf.dwarf) |*dwarf| {
166 if (!di.scanned_dwarf) {
167 dwarf.open(gpa, native_endian) catch |err| switch (err) {
168 error.InvalidDebugInfo,
169 error.MissingDebugInfo,
170 error.OutOfMemory,
171 => |e| return e,
172 error.EndOfStream,
173 error.Overflow,
174 error.ReadFailed,
175 error.StreamTooLong,
176 => return error.InvalidDebugInfo,
177 };
178 di.scanned_dwarf = true;
179 }
180 return dwarf.getSymbol(gpa, native_endian, vaddr) catch |err| switch (err) {
153 error.InvalidDebugInfo,181 error.InvalidDebugInfo,
154 error.MissingDebugInfo,182 error.MissingDebugInfo,
155 error.OutOfMemory,183 error.OutOfMemory,
156 => |e| return e,184 => |e| return e,
185 error.ReadFailed,
157 error.EndOfStream,186 error.EndOfStream,
158 error.Overflow,187 error.Overflow,
159 error.ReadFailed,
160 error.StreamTooLong,188 error.StreamTooLong,
161 => return error.InvalidDebugInfo,189 => return error.InvalidDebugInfo,
162 };190 };
163 di.scanned_dwarf = true;
164 }191 }
165 return dwarf.getSymbol(gpa, native_endian, vaddr) catch |err| switch (err) {192 // Otherwise, we're just going to scan the symtab, which we don't need the lock for; fall out of this block.
166 error.InvalidDebugInfo,
167 error.MissingDebugInfo,
168 error.OutOfMemory,
169 => |e| return e,
170 error.ReadFailed,
171 error.EndOfStream,
172 error.Overflow,
173 error.StreamTooLong,
174 => return error.InvalidDebugInfo,
175 };
176 }193 }
177 // When there's no DWARF available, fall back to searching the symtab.194 // When there's no DWARF available, fall back to searching the symtab.
178 return di.loaded_elf.?.searchSymtab(gpa, vaddr) catch |err| switch (err) {195 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...@@ -231,9 +248,14 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro
231 }248 }
232}249}
233pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {250pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
234 if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);251 const unwinds: *const [2]?Dwarf.Unwind = u: {
235 std.debug.assert(di.unwind[0] != null);252 di.mutex.lock();
236 for (&di.unwind) |*opt_unwind| {253 defer di.mutex.unlock();
254 if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);
255 std.debug.assert(di.unwind[0] != null);
256 break :u &di.unwind;
257 };
258 for (unwinds) |*opt_unwind| {
237 const unwind = &(opt_unwind.* orelse break);259 const unwind = &(opt_unwind.* orelse break);
238 return context.unwindFrame(gpa, unwind, module.load_offset, null) catch |err| switch (err) {260 return context.unwindFrame(gpa, unwind, module.load_offset, null) catch |err| switch (err) {
239 error.MissingDebugInfo => continue, // try the next one261 error.MissingDebugInfo => continue, // try the next one
lib/std/debug/SelfInfo/WindowsModule.zig+19-4
...@@ -9,14 +9,14 @@ pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) std.debug.Sel...@@ -9,14 +9,14 @@ pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) std.debug.Sel
9 if (lookupInCache(cache, address)) |m| return m;9 if (lookupInCache(cache, address)) |m| return m;
10 {10 {
11 // Check a new module hasn't been loaded11 // Check a new module hasn't been loaded
12 cache.rwlock.lock();
13 defer cache.rwlock.unlock();
12 cache.modules.clearRetainingCapacity();14 cache.modules.clearRetainingCapacity();
13
14 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);15 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
15 if (handle == windows.INVALID_HANDLE_VALUE) {16 if (handle == windows.INVALID_HANDLE_VALUE) {
16 return windows.unexpectedError(windows.GetLastError());17 return windows.unexpectedError(windows.GetLastError());
17 }18 }
18 defer windows.CloseHandle(handle);19 defer windows.CloseHandle(handle);
19
20 var entry: windows.MODULEENTRY32 = undefined;20 var entry: windows.MODULEENTRY32 = undefined;
21 entry.dwSize = @sizeOf(windows.MODULEENTRY32);21 entry.dwSize = @sizeOf(windows.MODULEENTRY32);
22 if (windows.kernel32.Module32First(handle, &entry) != 0) {22 if (windows.kernel32.Module32First(handle, &entry) != 0) {
...@@ -30,12 +30,18 @@ pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) std.debug.Sel...@@ -30,12 +30,18 @@ pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) std.debug.Sel
30 return error.MissingDebugInfo;30 return error.MissingDebugInfo;
31}31}
32pub fn getSymbolAtAddress(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, address: usize) std.debug.SelfInfo.Error!std.debug.Symbol {32pub fn getSymbolAtAddress(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, address: usize) std.debug.SelfInfo.Error!std.debug.Symbol {
33 // The `Pdb` API doesn't really allow us *any* thread-safe access, and the `Dwarf` API isn't
34 // great for it either; just lock the whole thing.
35 di.mutex.lock();
36 defer di.mutex.unlock();
37
33 if (!di.loaded) module.loadDebugInfo(gpa, di) catch |err| switch (err) {38 if (!di.loaded) module.loadDebugInfo(gpa, di) catch |err| switch (err) {
34 error.OutOfMemory, error.InvalidDebugInfo, error.MissingDebugInfo, error.Unexpected => |e| return e,39 error.OutOfMemory, error.InvalidDebugInfo, error.MissingDebugInfo, error.Unexpected => |e| return e,
35 error.FileNotFound => return error.MissingDebugInfo,40 error.FileNotFound => return error.MissingDebugInfo,
36 error.UnknownPDBVersion => return error.UnsupportedDebugInfo,41 error.UnknownPDBVersion => return error.UnsupportedDebugInfo,
37 else => return error.ReadFailed,42 else => return error.ReadFailed,
38 };43 };
44
39 // Translate the runtime address into a virtual address into the module45 // Translate the runtime address into a virtual address into the module
40 const vaddr = address - module.base_address;46 const vaddr = address - module.base_address;
4147
...@@ -50,7 +56,9 @@ pub fn getSymbolAtAddress(module: *const WindowsModule, gpa: Allocator, di: *Deb...@@ -50,7 +56,9 @@ pub fn getSymbolAtAddress(module: *const WindowsModule, gpa: Allocator, di: *Deb
5056
51 return error.MissingDebugInfo;57 return error.MissingDebugInfo;
52}58}
53fn lookupInCache(cache: *const LookupCache, address: usize) ?WindowsModule {59fn lookupInCache(cache: *LookupCache, address: usize) ?WindowsModule {
60 cache.rwlock.lockShared();
61 defer cache.rwlock.unlockShared();
54 for (cache.modules.items) |*entry| {62 for (cache.modules.items) |*entry| {
55 const base_address = @intFromPtr(entry.modBaseAddr);63 const base_address = @intFromPtr(entry.modBaseAddr);
56 if (address >= base_address and address < base_address + entry.modBaseSize) {64 if (address >= base_address and address < base_address + entry.modBaseSize) {
...@@ -182,13 +190,19 @@ fn loadDebugInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !...@@ -182,13 +190,19 @@ fn loadDebugInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !
182 di.loaded = true;190 di.loaded = true;
183}191}
184pub const LookupCache = struct {192pub const LookupCache = struct {
193 rwlock: std.Thread.RwLock,
185 modules: std.ArrayListUnmanaged(windows.MODULEENTRY32),194 modules: std.ArrayListUnmanaged(windows.MODULEENTRY32),
186 pub const init: LookupCache = .{ .modules = .empty };195 pub const init: LookupCache = .{
196 .rwlock = .{},
197 .modules = .empty,
198 };
187 pub fn deinit(lc: *LookupCache, gpa: Allocator) void {199 pub fn deinit(lc: *LookupCache, gpa: Allocator) void {
188 lc.modules.deinit(gpa);200 lc.modules.deinit(gpa);
189 }201 }
190};202};
191pub const DebugInfo = struct {203pub const DebugInfo = struct {
204 mutex: std.Thread.Mutex,
205
192 loaded: bool,206 loaded: bool,
193207
194 coff_image_base: u64,208 coff_image_base: u64,
...@@ -205,6 +219,7 @@ pub const DebugInfo = struct {...@@ -205,6 +219,7 @@ pub const DebugInfo = struct {
205 coff_section_headers: []coff.SectionHeader,219 coff_section_headers: []coff.SectionHeader,
206220
207 pub const init: DebugInfo = .{221 pub const init: DebugInfo = .{
222 .mutex = .{},
208 .loaded = false,223 .loaded = false,
209 .coff_image_base = undefined,224 .coff_image_base = undefined,
210 .mapped_file = null,225 .mapped_file = null,