| author | |
| committer | |
| log | 099a95041054e456ebefbd75f6a4f9f6961002be |
| tree | 6293805ebdf2664d5b5906e614297972a7c8da6f |
| parent | 9c1821d3bfadc5eddd4dff271a4920c03ee0ffea |
| signature |
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 | } |
| 240 | 240 | ||
| 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. |
| 243 | pub inline fn getSelfDebugInfo() !*SelfInfo { | 242 | pub 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 | } |
| 1171 | 1170 | ||
| 1172 | /// TODO multithreaded awareness | 1171 | /// 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. | ||
| 1173 | fn getDebugInfoAllocator() Allocator { | 1173 | fn 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 allocator | 1178 | // 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 | } |
| 1185 | 1185 | ||
| 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 | } |
| 348 | 348 | ||
| 349 | pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { | 349 | pub 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 is | 350 | // 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 of | 351 | // 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"); |
| 18 | 18 | ||
| 19 | const SelfInfo = @This(); | 19 | const SelfInfo = @This(); |
| 20 | 20 | ||
| 21 | modules: 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. | ||
| 27 | modules_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. | ||
| 35 | modules: if (target_supported) std.AutoArrayHashMapUnmanaged(usize, *Module.DebugInfo) else void, | ||
| 22 | lookup_cache: if (target_supported) Module.LookupCache else void, | 36 | lookup_cache: if (target_supported) Module.LookupCache else void, |
| 23 | 37 | ||
| 24 | pub const Error = error{ | 38 | pub 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 |
| 43 | pub const UnwindContext = if (supports_unwinding) Module.UnwindContext; | 57 | pub const UnwindContext = if (supports_unwinding) Module.UnwindContext; |
| 44 | 58 | ||
| 45 | pub const init: SelfInfo = .{ | 59 | pub 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 | }; |
| 49 | 64 | ||
| 50 | pub fn deinit(self: *SelfInfo, gpa: Allocator) void { | 65 | pub 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 { |
| 56 | pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { | 74 | pub 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 | } |
| 65 | 90 | ||
| 66 | pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol { | 91 | pub 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 | } |
| 75 | 107 | ||
| 76 | pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { | 108 | pub 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 each | 120 | /// 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 | } |
| 254 | pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol { | 254 | pub 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 | } |
| 343 | fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize { | 352 | fn 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 | }; | ||
| 346 | 359 | ||
| 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 | } |
| 651 | pub const DebugInfo = struct { | 664 | pub 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, |
| 654 | 672 | ||
| 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, |
| 7 | pub const LookupCache = void; | 7 | pub const LookupCache = void; |
| 8 | 8 | ||
| 9 | pub const DebugInfo = struct { | 9 | pub 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 | }; |
| 22 | 32 | ||
| ... | @@ -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 | } |
| 147 | pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol { | 157 | pub 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 | } |
| 233 | pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize { | 250 | pub 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 one | 261 | 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 loaded | 11 | // 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 | } |
| 32 | pub fn getSymbolAtAddress(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, address: usize) std.debug.SelfInfo.Error!std.debug.Symbol { | 32 | pub 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 module | 45 | // 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; |
| 41 | 47 | ||
| ... | @@ -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 |
| 50 | 56 | ||
| 51 | return error.MissingDebugInfo; | 57 | return error.MissingDebugInfo; |
| 52 | } | 58 | } |
| 53 | fn lookupInCache(cache: *const LookupCache, address: usize) ?WindowsModule { | 59 | fn 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 | } |
| 184 | pub const LookupCache = struct { | 192 | pub 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 | }; |
| 191 | pub const DebugInfo = struct { | 203 | pub const DebugInfo = struct { |
| 204 | mutex: std.Thread.Mutex, | ||
| 205 | |||
| 192 | loaded: bool, | 206 | loaded: bool, |
| 193 | 207 | ||
| 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, |
| 206 | 220 | ||
| 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, |