| ... | ... | @@ -938,6 +938,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { |
| 938 | 938 | if (self.requires_adhoc_codesig) { |
| 939 | 939 | try self.writeCodeSignature(); // code signing always comes last |
| 940 | 940 | } |
| 941 | |
| 942 | if (build_options.enable_link_snapshots) { |
| 943 | if (self.base.options.enable_link_snapshots) |
| 944 | try self.snapshotState(); |
| 945 | } |
| 941 | 946 | } |
| 942 | 947 | |
| 943 | 948 | cache: { |
| ... | ... | @@ -2424,6 +2429,14 @@ fn resolveSymbolsInObject(self: *MachO, object_id: u16) !void { |
| 2424 | 2429 | continue; |
| 2425 | 2430 | }, |
| 2426 | 2431 | .undef => { |
| 2432 | const undef = &self.undefs.items[resolv.where_index]; |
| 2433 | undef.* = .{ |
| 2434 | .n_strx = 0, |
| 2435 | .n_type = macho.N_UNDF, |
| 2436 | .n_sect = 0, |
| 2437 | .n_desc = 0, |
| 2438 | .n_value = 0, |
| 2439 | }; |
| 2427 | 2440 | _ = self.unresolved.fetchSwapRemove(resolv.where_index); |
| 2428 | 2441 | }, |
| 2429 | 2442 | } |
| ... | ... | @@ -4826,9 +4839,17 @@ fn writeSymbolTable(self: *MachO) !void { |
| 4826 | 4839 | } |
| 4827 | 4840 | } |
| 4828 | 4841 | |
| 4842 | var undefs = std.ArrayList(macho.nlist_64).init(self.base.allocator); |
| 4843 | defer undefs.deinit(); |
| 4844 | |
| 4845 | for (self.undefs.items) |sym| { |
| 4846 | if (sym.n_strx == 0) continue; |
| 4847 | try undefs.append(sym); |
| 4848 | } |
| 4849 | |
| 4829 | 4850 | const nlocals = locals.items.len; |
| 4830 | 4851 | const nexports = self.globals.items.len; |
| 4831 | | const nundefs = self.undefs.items.len; |
| 4852 | const nundefs = undefs.items.len; |
| 4832 | 4853 | |
| 4833 | 4854 | const locals_off = symtab.symoff; |
| 4834 | 4855 | const locals_size = nlocals * @sizeOf(macho.nlist_64); |
| ... | ... | @@ -4843,7 +4864,7 @@ fn writeSymbolTable(self: *MachO) !void { |
| 4843 | 4864 | const undefs_off = exports_off + exports_size; |
| 4844 | 4865 | const undefs_size = nundefs * @sizeOf(macho.nlist_64); |
| 4845 | 4866 | log.debug("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); |
| 4846 | | try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undefs.items), undefs_off); |
| 4867 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off); |
| 4847 | 4868 | |
| 4848 | 4869 | symtab.nsyms = @intCast(u32, nlocals + nexports + nundefs); |
| 4849 | 4870 | seg.inner.filesize += locals_size + exports_size + undefs_size; |
| ... | ... | @@ -5188,3 +5209,274 @@ pub fn findFirst(comptime T: type, haystack: []T, start: usize, predicate: anyty |
| 5188 | 5209 | } |
| 5189 | 5210 | return i; |
| 5190 | 5211 | } |
| 5212 | |
| 5213 | fn snapshotState(self: *MachO) !void { |
| 5214 | const emit = self.base.options.emit orelse { |
| 5215 | log.debug("no emit directory found; skipping snapshot...", .{}); |
| 5216 | return; |
| 5217 | }; |
| 5218 | |
| 5219 | const Snapshot = struct { |
| 5220 | const Node = struct { |
| 5221 | const Tag = enum { |
| 5222 | section_start, |
| 5223 | section_end, |
| 5224 | atom_start, |
| 5225 | atom_end, |
| 5226 | relocation, |
| 5227 | |
| 5228 | pub fn jsonStringify( |
| 5229 | tag: Tag, |
| 5230 | options: std.json.StringifyOptions, |
| 5231 | out_stream: anytype, |
| 5232 | ) !void { |
| 5233 | _ = options; |
| 5234 | switch (tag) { |
| 5235 | .section_start => try out_stream.writeAll("\"section_start\""), |
| 5236 | .section_end => try out_stream.writeAll("\"section_end\""), |
| 5237 | .atom_start => try out_stream.writeAll("\"atom_start\""), |
| 5238 | .atom_end => try out_stream.writeAll("\"atom_end\""), |
| 5239 | .relocation => try out_stream.writeAll("\"relocation\""), |
| 5240 | } |
| 5241 | } |
| 5242 | }; |
| 5243 | const Payload = struct { |
| 5244 | name: []const u8 = "", |
| 5245 | aliases: [][]const u8 = &[0][]const u8{}, |
| 5246 | is_global: bool = false, |
| 5247 | target: u64 = 0, |
| 5248 | }; |
| 5249 | address: u64, |
| 5250 | tag: Tag, |
| 5251 | payload: Payload, |
| 5252 | }; |
| 5253 | timestamp: i128, |
| 5254 | nodes: []Node, |
| 5255 | }; |
| 5256 | |
| 5257 | var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator); |
| 5258 | defer arena_allocator.deinit(); |
| 5259 | const arena = &arena_allocator.allocator; |
| 5260 | |
| 5261 | const out_file = try emit.directory.handle.createFile("snapshots.json", .{ |
| 5262 | .truncate = self.cold_start, |
| 5263 | .read = true, |
| 5264 | }); |
| 5265 | defer out_file.close(); |
| 5266 | |
| 5267 | if (out_file.seekFromEnd(-1)) { |
| 5268 | try out_file.writer().writeByte(','); |
| 5269 | } else |err| switch (err) { |
| 5270 | error.Unseekable => try out_file.writer().writeByte('['), |
| 5271 | else => |e| return e, |
| 5272 | } |
| 5273 | var writer = out_file.writer(); |
| 5274 | |
| 5275 | var snapshot = Snapshot{ |
| 5276 | .timestamp = std.time.nanoTimestamp(), |
| 5277 | .nodes = undefined, |
| 5278 | }; |
| 5279 | var nodes = std.ArrayList(Snapshot.Node).init(arena); |
| 5280 | |
| 5281 | for (self.section_ordinals.keys()) |key| { |
| 5282 | const seg = self.load_commands.items[key.seg].Segment; |
| 5283 | const sect = seg.sections.items[key.sect]; |
| 5284 | const sect_name = try std.fmt.allocPrint(arena, "{s},{s}", .{ |
| 5285 | commands.segmentName(sect), |
| 5286 | commands.sectionName(sect), |
| 5287 | }); |
| 5288 | try nodes.append(.{ |
| 5289 | .address = sect.addr, |
| 5290 | .tag = .section_start, |
| 5291 | .payload = .{ .name = sect_name }, |
| 5292 | }); |
| 5293 | |
| 5294 | var atom: *Atom = self.atoms.get(key) orelse { |
| 5295 | try nodes.append(.{ |
| 5296 | .address = sect.addr + sect.size, |
| 5297 | .tag = .section_end, |
| 5298 | .payload = .{}, |
| 5299 | }); |
| 5300 | continue; |
| 5301 | }; |
| 5302 | |
| 5303 | while (atom.prev) |prev| { |
| 5304 | atom = prev; |
| 5305 | } |
| 5306 | |
| 5307 | while (true) { |
| 5308 | const atom_sym = self.locals.items[atom.local_sym_index]; |
| 5309 | var node = Snapshot.Node{ |
| 5310 | .address = atom_sym.n_value, |
| 5311 | .tag = .atom_start, |
| 5312 | .payload = .{ |
| 5313 | .name = self.getString(atom_sym.n_strx), |
| 5314 | .is_global = self.symbol_resolver.contains(atom_sym.n_strx), |
| 5315 | }, |
| 5316 | }; |
| 5317 | |
| 5318 | var aliases = std.ArrayList([]const u8).init(arena); |
| 5319 | for (atom.aliases.items) |loc| { |
| 5320 | try aliases.append(self.getString(self.locals.items[loc].n_strx)); |
| 5321 | } |
| 5322 | node.payload.aliases = aliases.toOwnedSlice(); |
| 5323 | try nodes.append(node); |
| 5324 | |
| 5325 | var relocs = std.ArrayList(Snapshot.Node).init(arena); |
| 5326 | try relocs.ensureTotalCapacity(atom.relocs.items.len); |
| 5327 | for (atom.relocs.items) |rel| { |
| 5328 | const arch = self.base.options.target.cpu.arch; |
| 5329 | const source_addr = blk: { |
| 5330 | const sym = self.locals.items[atom.local_sym_index]; |
| 5331 | break :blk sym.n_value + rel.offset; |
| 5332 | }; |
| 5333 | const target_addr = blk: { |
| 5334 | const is_via_got = got: { |
| 5335 | switch (arch) { |
| 5336 | .aarch64 => break :got switch (@intToEnum(macho.reloc_type_arm64, rel.@"type")) { |
| 5337 | .ARM64_RELOC_GOT_LOAD_PAGE21, .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => true, |
| 5338 | else => false, |
| 5339 | }, |
| 5340 | .x86_64 => break :got switch (@intToEnum(macho.reloc_type_x86_64, rel.@"type")) { |
| 5341 | .X86_64_RELOC_GOT, .X86_64_RELOC_GOT_LOAD => true, |
| 5342 | else => false, |
| 5343 | }, |
| 5344 | else => unreachable, |
| 5345 | } |
| 5346 | }; |
| 5347 | |
| 5348 | if (is_via_got) { |
| 5349 | const got_atom = self.got_entries_map.get(rel.target).?; |
| 5350 | break :blk self.locals.items[got_atom.local_sym_index].n_value; |
| 5351 | } |
| 5352 | |
| 5353 | switch (rel.target) { |
| 5354 | .local => |sym_index| { |
| 5355 | const sym = self.locals.items[sym_index]; |
| 5356 | const is_tlv = is_tlv: { |
| 5357 | const source_sym = self.locals.items[atom.local_sym_index]; |
| 5358 | const match = self.section_ordinals.keys()[source_sym.n_sect - 1]; |
| 5359 | const match_seg = self.load_commands.items[match.seg].Segment; |
| 5360 | const match_sect = match_seg.sections.items[match.sect]; |
| 5361 | break :is_tlv commands.sectionType(match_sect) == macho.S_THREAD_LOCAL_VARIABLES; |
| 5362 | }; |
| 5363 | if (is_tlv) { |
| 5364 | const match_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; |
| 5365 | const base_address = inner: { |
| 5366 | if (self.tlv_data_section_index) |i| { |
| 5367 | break :inner match_seg.sections.items[i].addr; |
| 5368 | } else if (self.tlv_bss_section_index) |i| { |
| 5369 | break :inner match_seg.sections.items[i].addr; |
| 5370 | } else unreachable; |
| 5371 | }; |
| 5372 | break :blk sym.n_value - base_address; |
| 5373 | } |
| 5374 | break :blk sym.n_value; |
| 5375 | }, |
| 5376 | .global => |n_strx| { |
| 5377 | const resolv = self.symbol_resolver.get(n_strx).?; |
| 5378 | switch (resolv.where) { |
| 5379 | .global => break :blk self.globals.items[resolv.where_index].n_value, |
| 5380 | .undef => { |
| 5381 | break :blk if (self.stubs_map.get(n_strx)) |stub_atom| |
| 5382 | self.locals.items[stub_atom.local_sym_index].n_value |
| 5383 | else |
| 5384 | 0; |
| 5385 | }, |
| 5386 | } |
| 5387 | }, |
| 5388 | } |
| 5389 | }; |
| 5390 | |
| 5391 | relocs.appendAssumeCapacity(.{ |
| 5392 | .address = source_addr, |
| 5393 | .tag = .relocation, |
| 5394 | .payload = .{ .target = target_addr }, |
| 5395 | }); |
| 5396 | } |
| 5397 | |
| 5398 | if (atom.contained.items.len == 0) { |
| 5399 | try nodes.appendSlice(relocs.items); |
| 5400 | } else { |
| 5401 | // Need to reverse iteration order of relocs since by default for relocatable sources |
| 5402 | // they come in reverse. For linking, this doesn't matter in any way, however, for |
| 5403 | // arranging the memoryline for displaying it does. |
| 5404 | std.mem.reverse(Snapshot.Node, relocs.items); |
| 5405 | |
| 5406 | var next_i: usize = 0; |
| 5407 | var last_rel: usize = 0; |
| 5408 | while (next_i < atom.contained.items.len) : (next_i += 1) { |
| 5409 | const loc = atom.contained.items[next_i]; |
| 5410 | const cont_sym = self.locals.items[loc.local_sym_index]; |
| 5411 | const cont_sym_name = self.getString(cont_sym.n_strx); |
| 5412 | var contained_node = Snapshot.Node{ |
| 5413 | .address = cont_sym.n_value, |
| 5414 | .tag = .atom_start, |
| 5415 | .payload = .{ |
| 5416 | .name = cont_sym_name, |
| 5417 | .is_global = self.symbol_resolver.contains(cont_sym.n_strx), |
| 5418 | }, |
| 5419 | }; |
| 5420 | |
| 5421 | // Accumulate aliases |
| 5422 | var inner_aliases = std.ArrayList([]const u8).init(arena); |
| 5423 | while (true) { |
| 5424 | if (next_i + 1 >= atom.contained.items.len) break; |
| 5425 | const next_sym = self.locals.items[atom.contained.items[next_i + 1].local_sym_index]; |
| 5426 | if (next_sym.n_value != cont_sym.n_value) break; |
| 5427 | const next_sym_name = self.getString(next_sym.n_strx); |
| 5428 | if (self.symbol_resolver.contains(next_sym.n_strx)) { |
| 5429 | try inner_aliases.append(contained_node.payload.name); |
| 5430 | contained_node.payload.name = next_sym_name; |
| 5431 | contained_node.payload.is_global = true; |
| 5432 | } else try inner_aliases.append(next_sym_name); |
| 5433 | next_i += 1; |
| 5434 | } |
| 5435 | |
| 5436 | const cont_size = if (next_i + 1 < atom.contained.items.len) |
| 5437 | self.locals.items[atom.contained.items[next_i + 1].local_sym_index].n_value - cont_sym.n_value |
| 5438 | else |
| 5439 | atom_sym.n_value + atom.size - cont_sym.n_value; |
| 5440 | |
| 5441 | contained_node.payload.aliases = inner_aliases.toOwnedSlice(); |
| 5442 | try nodes.append(contained_node); |
| 5443 | |
| 5444 | for (relocs.items[last_rel..]) |rel, rel_i| { |
| 5445 | if (rel.address >= cont_sym.n_value + cont_size) { |
| 5446 | last_rel = rel_i; |
| 5447 | break; |
| 5448 | } |
| 5449 | try nodes.append(rel); |
| 5450 | } |
| 5451 | |
| 5452 | try nodes.append(.{ |
| 5453 | .address = cont_sym.n_value + cont_size, |
| 5454 | .tag = .atom_end, |
| 5455 | .payload = .{}, |
| 5456 | }); |
| 5457 | } |
| 5458 | } |
| 5459 | |
| 5460 | try nodes.append(.{ |
| 5461 | .address = atom_sym.n_value + atom.size, |
| 5462 | .tag = .atom_end, |
| 5463 | .payload = .{}, |
| 5464 | }); |
| 5465 | |
| 5466 | if (atom.next) |next| { |
| 5467 | atom = next; |
| 5468 | } else break; |
| 5469 | } |
| 5470 | |
| 5471 | try nodes.append(.{ |
| 5472 | .address = sect.addr + sect.size, |
| 5473 | .tag = .section_end, |
| 5474 | .payload = .{}, |
| 5475 | }); |
| 5476 | } |
| 5477 | |
| 5478 | snapshot.nodes = nodes.toOwnedSlice(); |
| 5479 | |
| 5480 | try std.json.stringify(snapshot, .{}, writer); |
| 5481 | try writer.writeByte(']'); |
| 5482 | } |