lib/coz/src/debug.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const sys = @import("sys");
  3 
  4 const path_filter = @import("filter.zig");
  5 const source_map = @import("map.zig");
  6 
  7 pub const default_source_scope = [_][]const u8{"%"};
  8 
  9 pub const Scope = struct {
 10     patterns: []const []const u8 = &default_source_scope,
 11     allow_system_sources: bool = true,
 12 };
 13 
 14 pub const ResolveError = error{UnsupportedTarget} ||
 15     std.debug.SelfInfoError ||
 16     std.mem.Allocator.Error;
 17 
 18 pub fn resolveSelfAddress(
 19     allocator: std.mem.Allocator,
 20     map: *source_map.Index,
 21     address: usize,
 22     scope: Scope,
 23 ) ResolveError!?*source_map.Line {
 24     if (comptime std.debug.SelfInfo == void) return error.UnsupportedTarget;
 25     return resolveAddress(allocator, map, try std.debug.getSelfDebugInfo(), address, scope);
 26 }
 27 
 28 pub fn resolveAddress(
 29     allocator: std.mem.Allocator,
 30     map: *source_map.Index,
 31     debug_info: *std.debug.SelfInfo,
 32     address: usize,
 33     scope: Scope,
 34 ) ResolveError!?*source_map.Line {
 35     if (comptime std.debug.SelfInfo == void) {
 36         return error.UnsupportedTarget;
 37     }
 38 
 39     if (map.findLineByAddress(address)) |line| return line;
 40     if (map.isUnresolved(address)) return null;
 41 
 42     const scratch = sys.allocator.debugInfoScratchAllocator();
 43     var text_arena_state = std.heap.ArenaAllocator.init(scratch);
 44     defer text_arena_state.deinit();
 45 
 46     var symbols = try std.ArrayList(std.debug.Symbol).initCapacity(scratch, 1);
 47     defer symbols.deinit(scratch);
 48 
 49     try debug_info.getSymbols(
 50         sys.stdio.debugIo(),
 51         scratch,
 52         text_arena_state.allocator(),
 53         address,
 54         true,
 55         &symbols,
 56     );
 57 
 58     const line = try resolveSymbols(allocator, map, address, symbols.items, scope);
 59     if (line == null) map.markUnresolved(allocator, address) catch {};
 60     return line;
 61 }
 62 
 63 pub fn resolveSymbols(
 64     allocator: std.mem.Allocator,
 65     map: *source_map.Index,
 66     address: usize,
 67     symbols: []const std.debug.Symbol,
 68     scope: Scope,
 69 ) std.mem.Allocator.Error!?*source_map.Line {
 70     for (symbols) |symbol| {
 71         const location = symbol.source_location orelse continue;
 72         if (try resolveLocation(allocator, map, address, location, scope)) |line| return line;
 73     }
 74 
 75     return null;
 76 }
 77 
 78 pub fn resolveLocation(
 79     allocator: std.mem.Allocator,
 80     map: *source_map.Index,
 81     address: usize,
 82     location: std.debug.SourceLocation,
 83     scope: Scope,
 84 ) std.mem.Allocator.Error!?*source_map.Line {
 85     if (location.line == 0) return null;
 86     const in_scope = path_filter.fileMatchesScopeNormalized(location.file_name, scope.patterns, scope.allow_system_sources);
 87     if (!in_scope) return null;
 88     return try map.addRange(allocator, location.file_name, location.line, source_map.Interval.unit(address));
 89 }
 90 
 91 pub fn resolveSample(
 92     allocator: std.mem.Allocator,
 93     map: *source_map.Index,
 94     sample: source_map.Sample,
 95     scope: Scope,
 96 ) ResolveError!void {
 97     _ = try resolveSelfAddress(allocator, map, sample.ip, scope);
 98     for (sample.callchain) |pc| {
 99         _ = try resolveSelfAddress(allocator, map, pc -| 1, scope);
100     }
101 }
102 
103 test "self address resolution never allocates resolution scratch from the caller" {
104     if (comptime std.debug.SelfInfo == void) return error.SkipZigTest;
105 
106     var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 });
107     var map: source_map.Index = .{};
108     defer map.deinit(failing.allocator());
109 
110     const patterns = [_][]const u8{"/coz-scratch-test-no-such-scope/%"};
111     const line = resolveSelfAddress(
112         failing.allocator(),
113         &map,
114         @returnAddress(),
115         .{ .patterns = &patterns },
116     ) catch |err| switch (err) {
117         error.OutOfMemory => return error.ResolutionScratchUsedCallerAllocator,
118         else => return error.SkipZigTest,
119     };
120 
121     try std.testing.expect(line == null);
122     try std.testing.expectEqual(@as(usize, 0), map.ranges.items.len);
123 }
124 
125 test "self address resolution memoizes unresolvable addresses" {
126     if (comptime std.debug.SelfInfo == void) return error.SkipZigTest;
127 
128     var map: source_map.Index = .{};
129     defer map.deinit(std.testing.allocator);
130 
131     const patterns = [_][]const u8{"/coz-memo-test-no-such-scope/%"};
132     const address = @returnAddress();
133     const line = resolveSelfAddress(std.testing.allocator, &map, address, .{ .patterns = &patterns }) catch |err| switch (err) {
134         error.OutOfMemory => return err,
135         else => return error.SkipZigTest,
136     };
137     try std.testing.expect(line == null);
138     try std.testing.expect(map.isUnresolved(address));
139 
140     var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 });
141     const memoized = try resolveSelfAddress(failing.allocator(), &map, address, .{ .patterns = &patterns });
142     try std.testing.expect(memoized == null);
143 }
144 
145 test "debug symbols resolve source locations into the source map" {
146     var map: source_map.Index = .{};
147     defer map.deinit(std.testing.allocator);
148 
149     const symbols = [_]std.debug.Symbol{.{
150         .name = "sample",
151         .compile_unit_name = "unit",
152         .source_location = .{
153             .file_name = "/tmp/app/main.zig",
154             .line = 42,
155             .column = 7,
156         },
157     }};
158     const line = (try resolveSymbols(std.testing.allocator, &map, 0x1000, &symbols, .{})).?;
159 
160     try std.testing.expectEqualStrings("/tmp/app/main.zig", line.file.name);
161     try std.testing.expectEqual(@as(u64, 42), line.number);
162     try std.testing.expectEqual(line, map.findLineByAddress(0x1000).?);
163 }
164 
165 test "debug symbol resolution honors source scope filtering" {
166     var map: source_map.Index = .{};
167     defer map.deinit(std.testing.allocator);
168 
169     const patterns = [_][]const u8{"/definitely/not/coz/%"};
170     const line = try resolveLocation(
171         std.testing.allocator,
172         &map,
173         0x1000,
174         .{
175             .file_name = "/tmp/app/main.zig",
176             .line = 42,
177             .column = 7,
178         },
179         .{ .patterns = &patterns },
180     );
181 
182     try std.testing.expect(line == null);
183     try std.testing.expectEqual(@as(usize, 0), map.ranges.items.len);
184 }