authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-02-21 02:00:52-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-02-21 02:00:52-05:00
log884b5fb4cfa81fba863f24cf5c6d9d7c2a21d11f
tree39d7e4f1b945d562c68702233877f62279e9bf89
parentbde15cf0806f2b8d6fb0c90602b42a74863ec515
parent623466762eba820f263a40622d70dc46ba0cb8ab

Merge branch 'bnoordhuis-macho'


4 files changed, 265 insertions(+), 57 deletions(-)

CMakeLists.txt+1
......@@ -386,6 +386,7 @@ set(ZIG_STD_FILES
386386 "index.zig"
387387 "io.zig"
388388 "linked_list.zig"
389 "macho.zig"
389390 "math/acos.zig"
390391 "math/acosh.zig"
391392 "math/asin.zig"
std/debug/index.zig+92-57
......@@ -5,6 +5,7 @@ const io = std.io;
55const os = std.os;
66const elf = std.elf;
77const DW = std.dwarf;
8const macho = std.macho;
89const ArrayList = std.ArrayList;
910const builtin = @import("builtin");
1011
......@@ -180,43 +181,57 @@ pub fn writeCurrentStackTrace(out_stream: var, allocator: &mem.Allocator,
180181}
181182
182183fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: var, address: usize) !void {
183 if (builtin.os == builtin.Os.windows) {
184 return error.UnsupportedDebugInfo;
185 }
186184 // TODO we really should be able to convert @sizeOf(usize) * 2 to a string literal
187185 // at compile time. I'll call it issue #313
188186 const ptr_hex = if (@sizeOf(usize) == 4) "0x{x8}" else "0x{x16}";
189187
190 const compile_unit = findCompileUnit(debug_info, address) catch {
191 try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n",
192 address);
193 return;
194 };
195 const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name);
196 if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| {
197 defer line_info.deinit();
198 try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++
199 DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n",
200 line_info.file_name, line_info.line, line_info.column,
201 address, compile_unit_name);
202 if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) {
203 if (line_info.column == 0) {
204 try out_stream.write("\n");
205 } else {
206 {var col_i: usize = 1; while (col_i < line_info.column) : (col_i += 1) {
207 try out_stream.writeByte(' ');
208 }}
209 try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n");
188 switch (builtin.os) {
189 builtin.Os.windows => return error.UnsupportedDebugInfo,
190 builtin.Os.macosx => {
191 // TODO(bnoordhuis) It's theoretically possible to obtain the
192 // compilation unit from the symbtab but it's not that useful
193 // in practice because the compiler dumps everything in a single
194 // object file. Future improvement: use external dSYM data when
195 // available.
196 const unknown = macho.Symbol { .name = "???", .address = address };
197 const symbol = debug_info.symbol_table.search(address) ?? &unknown;
198 try out_stream.print(WHITE ++ "{}" ++ RESET ++ ": " ++
199 DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n",
200 symbol.name, address);
201 },
202 else => {
203 const compile_unit = findCompileUnit(debug_info, address) catch {
204 try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n",
205 address);
206 return;
207 };
208 const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name);
209 if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| {
210 defer line_info.deinit();
211 try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++
212 DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n",
213 line_info.file_name, line_info.line, line_info.column,
214 address, compile_unit_name);
215 if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) {
216 if (line_info.column == 0) {
217 try out_stream.write("\n");
218 } else {
219 {var col_i: usize = 1; while (col_i < line_info.column) : (col_i += 1) {
220 try out_stream.writeByte(' ');
221 }}
222 try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n");
223 }
224 } else |err| switch (err) {
225 error.EndOfFile => {},
226 else => return err,
227 }
228 } else |err| switch (err) {
229 error.MissingDebugInfo, error.InvalidDebugInfo => {
230 try out_stream.print(ptr_hex ++ " in ??? ({})\n", address, compile_unit_name);
231 },
232 else => return err,
210233 }
211 } else |err| switch (err) {
212 error.EndOfFile => {},
213 else => return err,
214 }
215 } else |err| switch (err) {
216 error.MissingDebugInfo, error.InvalidDebugInfo => {
217 try out_stream.print(ptr_hex ++ " in ??? ({})\n", address, compile_unit_name);
218234 },
219 else => return err,
220235 }
221236}
222237
......@@ -224,6 +239,7 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace {
224239 switch (builtin.object_format) {
225240 builtin.ObjectFormat.elf => {
226241 const st = try allocator.create(ElfStackTrace);
242 errdefer allocator.destroy(st);
227243 *st = ElfStackTrace {
228244 .self_exe_file = undefined,
229245 .elf = undefined,
......@@ -249,12 +265,22 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace {
249265 try scanAllCompileUnits(st);
250266 return st;
251267 },
268 builtin.ObjectFormat.macho => {
269 var exe_file = try os.openSelfExe();
270 defer exe_file.close();
271
272 const st = try allocator.create(ElfStackTrace);
273 errdefer allocator.destroy(st);
274
275 *st = ElfStackTrace {
276 .symbol_table = try macho.loadSymbols(allocator, &io.FileInStream.init(&exe_file)),
277 };
278
279 return st;
280 },
252281 builtin.ObjectFormat.coff => {
253282 return error.TodoSupportCoffDebugInfo;
254283 },
255 builtin.ObjectFormat.macho => {
256 return error.TodoSupportMachoDebugInfo;
257 },
258284 builtin.ObjectFormat.wasm => {
259285 return error.TodoSupportCOFFDebugInfo;
260286 },
......@@ -297,31 +323,40 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: var, line_info: &con
297323 }
298324}
299325
300pub const ElfStackTrace = struct {
301 self_exe_file: os.File,
302 elf: elf.Elf,
303 debug_info: &elf.SectionHeader,
304 debug_abbrev: &elf.SectionHeader,
305 debug_str: &elf.SectionHeader,
306 debug_line: &elf.SectionHeader,
307 debug_ranges: ?&elf.SectionHeader,
308 abbrev_table_list: ArrayList(AbbrevTableHeader),
309 compile_unit_list: ArrayList(CompileUnit),
310
311 pub fn allocator(self: &const ElfStackTrace) &mem.Allocator {
312 return self.abbrev_table_list.allocator;
313 }
326pub const ElfStackTrace = switch (builtin.os) {
327 builtin.Os.macosx => struct {
328 symbol_table: macho.SymbolTable,
314329
315 pub fn readString(self: &ElfStackTrace) ![]u8 {
316 var in_file_stream = io.FileInStream.init(&self.self_exe_file);
317 const in_stream = &in_file_stream.stream;
318 return readStringRaw(self.allocator(), in_stream);
319 }
330 pub fn close(self: &ElfStackTrace) void {
331 self.symbol_table.deinit();
332 }
333 },
334 else => struct {
335 self_exe_file: os.File,
336 elf: elf.Elf,
337 debug_info: &elf.SectionHeader,
338 debug_abbrev: &elf.SectionHeader,
339 debug_str: &elf.SectionHeader,
340 debug_line: &elf.SectionHeader,
341 debug_ranges: ?&elf.SectionHeader,
342 abbrev_table_list: ArrayList(AbbrevTableHeader),
343 compile_unit_list: ArrayList(CompileUnit),
344
345 pub fn allocator(self: &const ElfStackTrace) &mem.Allocator {
346 return self.abbrev_table_list.allocator;
347 }
320348
321 pub fn close(self: &ElfStackTrace) void {
322 self.self_exe_file.close();
323 self.elf.close();
324 }
349 pub fn readString(self: &ElfStackTrace) ![]u8 {
350 var in_file_stream = io.FileInStream.init(&self.self_exe_file);
351 const in_stream = &in_file_stream.stream;
352 return readStringRaw(self.allocator(), in_stream);
353 }
354
355 pub fn close(self: &ElfStackTrace) void {
356 self.self_exe_file.close();
357 self.elf.close();
358 }
359 },
325360};
326361
327362const PcRange = struct {
std/index.zig+2
......@@ -21,6 +21,7 @@ pub const endian = @import("endian.zig");
2121pub const fmt = @import("fmt/index.zig");
2222pub const heap = @import("heap.zig");
2323pub const io = @import("io.zig");
24pub const macho = @import("macho.zig");
2425pub const math = @import("math/index.zig");
2526pub const mem = @import("mem.zig");
2627pub const net = @import("net.zig");
......@@ -51,6 +52,7 @@ test "std" {
5152 _ = @import("endian.zig");
5253 _ = @import("fmt/index.zig");
5354 _ = @import("io.zig");
55 _ = @import("macho.zig");
5456 _ = @import("math/index.zig");
5557 _ = @import("mem.zig");
5658 _ = @import("heap.zig");
std/macho.zig created+170
......@@ -0,0 +1,170 @@
1const builtin = @import("builtin");
2const std = @import("index.zig");
3const io = std.io;
4const mem = std.mem;
5
6const MH_MAGIC_64 = 0xFEEDFACF;
7const MH_PIE = 0x200000;
8const LC_SYMTAB = 2;
9
10const MachHeader64 = packed struct {
11 magic: u32,
12 cputype: u32,
13 cpusubtype: u32,
14 filetype: u32,
15 ncmds: u32,
16 sizeofcmds: u32,
17 flags: u32,
18 reserved: u32,
19};
20
21const LoadCommand = packed struct {
22 cmd: u32,
23 cmdsize: u32,
24};
25
26const SymtabCommand = packed struct {
27 symoff: u32,
28 nsyms: u32,
29 stroff: u32,
30 strsize: u32,
31};
32
33const Nlist64 = packed struct {
34 n_strx: u32,
35 n_type: u8,
36 n_sect: u8,
37 n_desc: u16,
38 n_value: u64,
39};
40
41pub const Symbol = struct {
42 name: []const u8,
43 address: u64,
44
45 fn addressLessThan(lhs: &const Symbol, rhs: &const Symbol) bool {
46 return lhs.address < rhs.address;
47 }
48};
49
50pub const SymbolTable = struct {
51 allocator: &mem.Allocator,
52 symbols: []const Symbol,
53 strings: []const u8,
54
55 // Doubles as an eyecatcher to calculate the PIE slide, see loadSymbols().
56 // Ideally we'd use _mh_execute_header because it's always at 0x100000000
57 // in the image but as it's located in a different section than executable
58 // code, its displacement is different.
59 pub fn deinit(self: &SymbolTable) void {
60 self.allocator.free(self.symbols);
61 self.symbols = []const Symbol {};
62
63 self.allocator.free(self.strings);
64 self.strings = []const u8 {};
65 }
66
67 pub fn search(self: &const SymbolTable, address: usize) ?&const Symbol {
68 var min: usize = 0;
69 var max: usize = self.symbols.len - 1; // Exclude sentinel.
70 while (min < max) {
71 const mid = min + (max - min) / 2;
72 const curr = &self.symbols[mid];
73 const next = &self.symbols[mid + 1];
74 if (address >= next.address) {
75 min = mid + 1;
76 } else if (address < curr.address) {
77 max = mid;
78 } else {
79 return curr;
80 }
81 }
82 return null;
83 }
84};
85
86pub fn loadSymbols(allocator: &mem.Allocator, in: &io.FileInStream) !SymbolTable {
87 var file = in.file;
88 try file.seekTo(0);
89
90 var hdr: MachHeader64 = undefined;
91 try readOneNoEof(in, MachHeader64, &hdr);
92 if (hdr.magic != MH_MAGIC_64) return error.MissingDebugInfo;
93 const is_pie = MH_PIE == (hdr.flags & MH_PIE);
94
95 var pos: usize = @sizeOf(@typeOf(hdr));
96 var ncmd: u32 = hdr.ncmds;
97 while (ncmd != 0) : (ncmd -= 1) {
98 try file.seekTo(pos);
99 var lc: LoadCommand = undefined;
100 try readOneNoEof(in, LoadCommand, &lc);
101 if (lc.cmd == LC_SYMTAB) break;
102 pos += lc.cmdsize;
103 } else {
104 return error.MissingDebugInfo;
105 }
106
107 var cmd: SymtabCommand = undefined;
108 try readOneNoEof(in, SymtabCommand, &cmd);
109
110 try file.seekTo(cmd.symoff);
111 var syms = try allocator.alloc(Nlist64, cmd.nsyms);
112 defer allocator.free(syms);
113 try readNoEof(in, Nlist64, syms);
114
115 try file.seekTo(cmd.stroff);
116 var strings = try allocator.alloc(u8, cmd.strsize);
117 errdefer allocator.free(strings);
118 try in.stream.readNoEof(strings);
119
120 var nsyms: usize = 0;
121 for (syms) |sym| if (isSymbol(sym)) nsyms += 1;
122 if (nsyms == 0) return error.MissingDebugInfo;
123
124 var symbols = try allocator.alloc(Symbol, nsyms + 1); // Room for sentinel.
125 errdefer allocator.free(symbols);
126
127 var pie_slide: usize = 0;
128 var nsym: usize = 0;
129 for (syms) |sym| {
130 if (!isSymbol(sym)) continue;
131 const start = sym.n_strx;
132 const end = ??mem.indexOfScalarPos(u8, strings, start, 0);
133 const name = strings[start..end];
134 const address = sym.n_value;
135 symbols[nsym] = Symbol { .name = name, .address = address };
136 nsym += 1;
137 if (is_pie and mem.eql(u8, name, "_SymbolTable_deinit")) {
138 pie_slide = @ptrToInt(SymbolTable.deinit) - address;
139 }
140 }
141
142 // Effectively a no-op, lld emits symbols in ascending order.
143 std.sort.insertionSort(Symbol, symbols[0..nsyms], Symbol.addressLessThan);
144
145 // Insert the sentinel. Since we don't know where the last function ends,
146 // we arbitrarily limit it to the start address + 4 KB.
147 const top = symbols[nsyms - 1].address + 4096;
148 symbols[nsyms] = Symbol { .name = "", .address = top };
149
150 if (pie_slide != 0) {
151 for (symbols) |*symbol| symbol.address += pie_slide;
152 }
153
154 return SymbolTable {
155 .allocator = allocator,
156 .symbols = symbols,
157 .strings = strings,
158 };
159}
160
161fn readNoEof(in: &io.FileInStream, comptime T: type, result: []T) !void {
162 return in.stream.readNoEof(([]u8)(result));
163}
164fn readOneNoEof(in: &io.FileInStream, comptime T: type, result: &T) !void {
165 return readNoEof(in, T, result[0..1]);
166}
167
168fn isSymbol(sym: &const Nlist64) bool {
169 return sym.n_value != 0 and sym.n_desc == 0;
170}