| 1 | const DebugSymbols = @This(); |
| 2 | |
| 3 | const std = @import("std"); |
| 4 | const Io = std.Io; |
| 5 | const assert = std.debug.assert; |
| 6 | const fs = std.fs; |
| 7 | const log = std.log.scoped(.link_dsym); |
| 8 | const macho = std.macho; |
| 9 | const makeStaticString = MachO.makeStaticString; |
| 10 | const math = std.math; |
| 11 | const mem = std.mem; |
| 12 | const Writer = std.Io.Writer; |
| 13 | const Allocator = std.mem.Allocator; |
| 14 | |
| 15 | const link = @import("../../link.zig"); |
| 16 | const MachO = @import("../MachO.zig"); |
| 17 | const StringTable = @import("../StringTable.zig"); |
| 18 | const Type = @import("../../Type.zig"); |
| 19 | const trace = @import("../../tracy.zig").trace; |
| 20 | const load_commands = @import("load_commands.zig"); |
| 21 | const padToIdeal = MachO.padToIdeal; |
| 22 | |
| 23 | io: Io, |
| 24 | allocator: Allocator, |
| 25 | file: ?Io.File, |
| 26 | |
| 27 | symtab_cmd: macho.symtab_command = .{}, |
| 28 | uuid_cmd: macho.uuid_command = .{ .uuid = @splat(0) }, |
| 29 | |
| 30 | segments: std.ArrayList(macho.segment_command_64) = .empty, |
| 31 | sections: std.ArrayList(macho.section_64) = .empty, |
| 32 | |
| 33 | dwarf_segment_cmd_index: ?u8 = null, |
| 34 | linkedit_segment_cmd_index: ?u8 = null, |
| 35 | |
| 36 | debug_info_section_index: ?u8 = null, |
| 37 | debug_abbrev_section_index: ?u8 = null, |
| 38 | debug_str_section_index: ?u8 = null, |
| 39 | debug_aranges_section_index: ?u8 = null, |
| 40 | debug_line_section_index: ?u8 = null, |
| 41 | debug_line_str_section_index: ?u8 = null, |
| 42 | debug_loclists_section_index: ?u8 = null, |
| 43 | debug_rnglists_section_index: ?u8 = null, |
| 44 | |
| 45 | relocs: std.ArrayList(Reloc) = .empty, |
| 46 | |
| 47 | /// Output synthetic sections |
| 48 | symtab: std.ArrayList(macho.nlist_64) = .empty, |
| 49 | strtab: std.ArrayList(u8) = .empty, |
| 50 | |
| 51 | pub const Reloc = struct { |
| 52 | type: enum { |
| 53 | direct_load, |
| 54 | got_load, |
| 55 | }, |
| 56 | target: u32, |
| 57 | offset: u64, |
| 58 | addend: u32, |
| 59 | }; |
| 60 | |
| 61 | /// You must call this function *after* `ZigObject.initMetadata()` |
| 62 | /// has been called to get a viable debug symbols output. |
| 63 | pub fn initMetadata(self: *DebugSymbols, macho_file: *MachO) !void { |
| 64 | try self.strtab.append(self.allocator, 0); |
| 65 | |
| 66 | { |
| 67 | self.dwarf_segment_cmd_index = @as(u8, @intCast(self.segments.items.len)); |
| 68 | |
| 69 | const page_size = macho_file.getPageSize(); |
| 70 | const off = @as(u64, @intCast(page_size)); |
| 71 | const ideal_size: u16 = 200 + 128 + 160 + 250; |
| 72 | const needed_size = mem.alignForward(u64, padToIdeal(ideal_size), page_size); |
| 73 | |
| 74 | log.debug("found __DWARF segment free space 0x{x} to 0x{x}", .{ off, off + needed_size }); |
| 75 | |
| 76 | try self.segments.append(self.allocator, .{ |
| 77 | .segname = makeStaticString("__DWARF"), |
| 78 | .vmsize = needed_size, |
| 79 | .fileoff = off, |
| 80 | .filesize = needed_size, |
| 81 | .cmdsize = @sizeOf(macho.segment_command_64), |
| 82 | }); |
| 83 | } |
| 84 | |
| 85 | self.debug_str_section_index = try self.createSection("__debug_str", 0); |
| 86 | self.debug_info_section_index = try self.createSection("__debug_info", 0); |
| 87 | self.debug_abbrev_section_index = try self.createSection("__debug_abbrev", 0); |
| 88 | self.debug_aranges_section_index = try self.createSection("__debug_aranges", 4); |
| 89 | self.debug_line_section_index = try self.createSection("__debug_line", 0); |
| 90 | self.debug_line_str_section_index = try self.createSection("__debug_line_str", 0); |
| 91 | self.debug_loclists_section_index = try self.createSection("__debug_loclists", 0); |
| 92 | self.debug_rnglists_section_index = try self.createSection("__debug_rnglists", 0); |
| 93 | |
| 94 | self.linkedit_segment_cmd_index = @intCast(self.segments.items.len); |
| 95 | try self.segments.append(self.allocator, .{ |
| 96 | .segname = makeStaticString("__LINKEDIT"), |
| 97 | .maxprot = .{ .READ = true }, |
| 98 | .initprot = .{ .READ = true }, |
| 99 | .cmdsize = @sizeOf(macho.segment_command_64), |
| 100 | }); |
| 101 | } |
| 102 | |
| 103 | fn createSection(self: *DebugSymbols, sectname: []const u8, alignment: u16) !u8 { |
| 104 | const segment = self.getDwarfSegmentPtr(); |
| 105 | var sect = macho.section_64{ |
| 106 | .sectname = makeStaticString(sectname), |
| 107 | .segname = segment.segname, |
| 108 | .@"align" = alignment, |
| 109 | }; |
| 110 | |
| 111 | log.debug("create {s},{s} section", .{ sect.segName(), sect.sectName() }); |
| 112 | |
| 113 | const index: u8 = @intCast(self.sections.items.len); |
| 114 | try self.sections.append(self.allocator, sect); |
| 115 | segment.cmdsize += @sizeOf(macho.section_64); |
| 116 | segment.nsects += 1; |
| 117 | |
| 118 | return index; |
| 119 | } |
| 120 | |
| 121 | pub fn growSection( |
| 122 | self: *DebugSymbols, |
| 123 | sect_index: u8, |
| 124 | needed_size: u64, |
| 125 | requires_file_copy: bool, |
| 126 | macho_file: *MachO, |
| 127 | ) !void { |
| 128 | const io = self.io; |
| 129 | const sect = self.getSectionPtr(sect_index); |
| 130 | |
| 131 | const allocated_size = self.allocatedSize(sect.offset); |
| 132 | if (needed_size > allocated_size) { |
| 133 | const existing_size = sect.size; |
| 134 | sect.size = 0; // free the space |
| 135 | const new_offset = try self.findFreeSpace(needed_size, 1); |
| 136 | |
| 137 | log.debug("moving {s} section: {} bytes from 0x{x} to 0x{x}", .{ |
| 138 | sect.sectName(), existing_size, sect.offset, new_offset, |
| 139 | }); |
| 140 | |
| 141 | if (requires_file_copy) { |
| 142 | const file = self.file.?; |
| 143 | try link.File.copyRangeAll2(io, file, file, sect.offset, new_offset, existing_size); |
| 144 | } |
| 145 | |
| 146 | sect.offset = @intCast(new_offset); |
| 147 | } else if (sect.offset + allocated_size == std.math.maxInt(u64)) { |
| 148 | try self.file.?.setLength(io, sect.offset + needed_size); |
| 149 | } |
| 150 | |
| 151 | sect.size = needed_size; |
| 152 | self.markDirty(sect_index, macho_file); |
| 153 | } |
| 154 | |
| 155 | pub fn markDirty(self: *DebugSymbols, sect_index: u8, macho_file: *MachO) void { |
| 156 | if (macho_file.getZigObject()) |zo| { |
| 157 | if (self.debug_info_section_index.? == sect_index) { |
| 158 | zo.debug_info_header_dirty = true; |
| 159 | } else if (self.debug_line_section_index.? == sect_index) { |
| 160 | zo.debug_line_header_dirty = true; |
| 161 | } else if (self.debug_abbrev_section_index.? == sect_index) { |
| 162 | zo.debug_abbrev_dirty = true; |
| 163 | } else if (self.debug_str_section_index.? == sect_index) { |
| 164 | zo.debug_strtab_dirty = true; |
| 165 | } else if (self.debug_aranges_section_index.? == sect_index) { |
| 166 | zo.debug_aranges_dirty = true; |
| 167 | } |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 { |
| 172 | const io = self.io; |
| 173 | var at_end = true; |
| 174 | const end = start + padToIdeal(size); |
| 175 | |
| 176 | for (self.sections.items) |section| { |
| 177 | const increased_size = padToIdeal(section.size); |
| 178 | const test_end = section.offset + increased_size; |
| 179 | if (start < test_end) { |
| 180 | if (end > section.offset) return test_end; |
| 181 | if (test_end < std.math.maxInt(u64)) at_end = false; |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | if (at_end) try self.file.?.setLength(io, end); |
| 186 | return null; |
| 187 | } |
| 188 | |
| 189 | fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) !u64 { |
| 190 | const segment = self.getDwarfSegmentPtr(); |
| 191 | var offset: u64 = segment.fileoff; |
| 192 | while (try self.detectAllocCollision(offset, object_size)) |item_end| { |
| 193 | offset = mem.alignForward(u64, item_end, min_alignment); |
| 194 | } |
| 195 | return offset; |
| 196 | } |
| 197 | |
| 198 | pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void { |
| 199 | const io = self.io; |
| 200 | const zo = macho_file.getZigObject().?; |
| 201 | for (self.relocs.items) |*reloc| { |
| 202 | const sym = zo.symbols.items[reloc.target]; |
| 203 | const sym_name = sym.getName(macho_file); |
| 204 | const addr = switch (reloc.type) { |
| 205 | .direct_load => sym.getAddress(.{}, macho_file), |
| 206 | .got_load => sym.getGotAddress(macho_file), |
| 207 | }; |
| 208 | const sect = &self.sections.items[self.debug_info_section_index.?]; |
| 209 | const file_offset = sect.offset + reloc.offset; |
| 210 | log.debug("resolving relocation: {d}@{x} ('{s}') at offset {x}", .{ |
| 211 | reloc.target, addr, sym_name, file_offset, |
| 212 | }); |
| 213 | try self.file.?.writePositionalAll(io, mem.asBytes(&addr), file_offset); |
| 214 | } |
| 215 | |
| 216 | self.finalizeDwarfSegment(macho_file); |
| 217 | try self.writeLinkeditSegmentData(macho_file); |
| 218 | |
| 219 | // Write load commands |
| 220 | const ncmds, const sizeofcmds = try self.writeLoadCommands(macho_file); |
| 221 | try self.writeHeader(macho_file, ncmds, sizeofcmds); |
| 222 | } |
| 223 | |
| 224 | pub fn deinit(self: *DebugSymbols) void { |
| 225 | const gpa = self.allocator; |
| 226 | const io = self.io; |
| 227 | if (self.file) |file| file.close(io); |
| 228 | self.segments.deinit(gpa); |
| 229 | self.sections.deinit(gpa); |
| 230 | self.relocs.deinit(gpa); |
| 231 | self.symtab.deinit(gpa); |
| 232 | self.strtab.deinit(gpa); |
| 233 | } |
| 234 | |
| 235 | pub fn swapRemoveRelocs(self: *DebugSymbols, target: u32) void { |
| 236 | // TODO re-implement using a hashmap with free lists |
| 237 | var last_index: usize = 0; |
| 238 | while (last_index < self.relocs.items.len) { |
| 239 | const reloc = self.relocs.items[last_index]; |
| 240 | if (reloc.target == target) { |
| 241 | _ = self.relocs.swapRemove(last_index); |
| 242 | } else { |
| 243 | last_index += 1; |
| 244 | } |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void { |
| 249 | const base_vmaddr = blk: { |
| 250 | // Note that we purposely take the last VM address of the MachO binary including |
| 251 | // the binary's LINKEDIT segment. This is in contrast to how dsymutil does it |
| 252 | // which overwrites the the address space taken by the original MachO binary, |
| 253 | // however at the cost of having LINKEDIT preceed DWARF in dSYM binary which we |
| 254 | // do not want as we want to be able to incrementally move DWARF sections in the |
| 255 | // file as we please. |
| 256 | const last_seg = macho_file.getLinkeditSegment(); |
| 257 | break :blk last_seg.vmaddr + last_seg.vmsize; |
| 258 | }; |
| 259 | const dwarf_segment = self.getDwarfSegmentPtr(); |
| 260 | |
| 261 | var file_size: u64 = 0; |
| 262 | for (self.sections.items) |header| { |
| 263 | file_size = @max(file_size, header.offset + header.size); |
| 264 | } |
| 265 | |
| 266 | const page_size = macho_file.getPageSize(); |
| 267 | const aligned_size = mem.alignForward(u64, file_size, page_size); |
| 268 | dwarf_segment.vmaddr = base_vmaddr; |
| 269 | dwarf_segment.filesize = aligned_size; |
| 270 | dwarf_segment.vmsize = aligned_size; |
| 271 | |
| 272 | const linkedit = self.getLinkeditSegmentPtr(); |
| 273 | linkedit.vmaddr = mem.alignForward( |
| 274 | u64, |
| 275 | dwarf_segment.vmaddr + aligned_size, |
| 276 | page_size, |
| 277 | ); |
| 278 | linkedit.fileoff = mem.alignForward( |
| 279 | u64, |
| 280 | dwarf_segment.fileoff + aligned_size, |
| 281 | page_size, |
| 282 | ); |
| 283 | log.debug("found __LINKEDIT segment free space at 0x{x}", .{linkedit.fileoff}); |
| 284 | } |
| 285 | |
| 286 | fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, usize } { |
| 287 | const io = self.io; |
| 288 | const gpa = self.allocator; |
| 289 | const needed_size = load_commands.calcLoadCommandsSizeDsym(macho_file, self); |
| 290 | const buffer = try gpa.alloc(u8, needed_size); |
| 291 | defer gpa.free(buffer); |
| 292 | |
| 293 | var writer: Writer = .fixed(buffer); |
| 294 | |
| 295 | var ncmds: usize = 0; |
| 296 | |
| 297 | // UUID comes first presumably to speed up lookup by the consumer like lldb. |
| 298 | @memcpy(&self.uuid_cmd.uuid, &macho_file.uuid_cmd.uuid); |
| 299 | try writer.writeStruct(self.uuid_cmd, .little); |
| 300 | ncmds += 1; |
| 301 | |
| 302 | // Segment and section load commands |
| 303 | { |
| 304 | // Write segment/section headers from the binary file first. |
| 305 | const slice = macho_file.sections.slice(); |
| 306 | var sect_id: usize = 0; |
| 307 | for (macho_file.segments.items, 0..) |seg, seg_id| { |
| 308 | if (seg_id == macho_file.linkedit_seg_index.?) break; |
| 309 | var out_seg = seg; |
| 310 | out_seg.fileoff = 0; |
| 311 | out_seg.filesize = 0; |
| 312 | try writer.writeStruct(out_seg, .little); |
| 313 | for (slice.items(.header)[sect_id..][0..seg.nsects]) |header| { |
| 314 | var out_header = header; |
| 315 | out_header.offset = 0; |
| 316 | try writer.writeStruct(out_header, .little); |
| 317 | } |
| 318 | sect_id += seg.nsects; |
| 319 | } |
| 320 | ncmds += macho_file.segments.items.len - 1; |
| 321 | |
| 322 | // Next, commit DSYM's __LINKEDIT and __DWARF segments headers. |
| 323 | sect_id = 0; |
| 324 | for (self.segments.items) |seg| { |
| 325 | try writer.writeStruct(seg, .little); |
| 326 | for (self.sections.items[sect_id..][0..seg.nsects]) |header| { |
| 327 | try writer.writeStruct(header, .little); |
| 328 | } |
| 329 | sect_id += seg.nsects; |
| 330 | } |
| 331 | ncmds += self.segments.items.len; |
| 332 | } |
| 333 | |
| 334 | try writer.writeStruct(self.symtab_cmd, .little); |
| 335 | ncmds += 1; |
| 336 | |
| 337 | assert(writer.end == needed_size); |
| 338 | |
| 339 | try self.file.?.writePositionalAll(io, buffer, @sizeOf(macho.mach_header_64)); |
| 340 | |
| 341 | return .{ ncmds, buffer.len }; |
| 342 | } |
| 343 | |
| 344 | fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void { |
| 345 | const io = self.io; |
| 346 | var header: macho.mach_header_64 = .{}; |
| 347 | header.filetype = macho.MH_DSYM; |
| 348 | |
| 349 | switch (macho_file.getTarget().cpu.arch) { |
| 350 | .aarch64 => { |
| 351 | header.cputype = macho.CPU_TYPE_ARM64; |
| 352 | header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL; |
| 353 | }, |
| 354 | .x86_64 => { |
| 355 | header.cputype = macho.CPU_TYPE_X86_64; |
| 356 | header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL; |
| 357 | }, |
| 358 | else => return error.UnsupportedCpuArchitecture, |
| 359 | } |
| 360 | |
| 361 | header.ncmds = @intCast(ncmds); |
| 362 | header.sizeofcmds = @intCast(sizeofcmds); |
| 363 | |
| 364 | log.debug("writing Mach-O header {}", .{header}); |
| 365 | |
| 366 | try self.file.?.writePositionalAll(io, mem.asBytes(&header), 0); |
| 367 | } |
| 368 | |
| 369 | fn allocatedSize(self: *DebugSymbols, start: u64) u64 { |
| 370 | if (start == 0) return 0; |
| 371 | const seg = self.getDwarfSegmentPtr(); |
| 372 | assert(start >= seg.fileoff); |
| 373 | var min_pos: u64 = std.math.maxInt(u64); |
| 374 | for (self.sections.items) |section| { |
| 375 | if (section.offset <= start) continue; |
| 376 | if (section.offset < min_pos) min_pos = section.offset; |
| 377 | } |
| 378 | return min_pos - start; |
| 379 | } |
| 380 | |
| 381 | fn writeLinkeditSegmentData(self: *DebugSymbols, macho_file: *MachO) !void { |
| 382 | const tracy = trace(@src()); |
| 383 | defer tracy.end(); |
| 384 | |
| 385 | const page_size = macho_file.getPageSize(); |
| 386 | const seg = &self.segments.items[self.linkedit_segment_cmd_index.?]; |
| 387 | |
| 388 | var off = math.cast(u32, seg.fileoff) orelse return error.Overflow; |
| 389 | off = try self.writeSymtab(off, macho_file); |
| 390 | off = mem.alignForward(u32, off, @alignOf(u64)); |
| 391 | off = try self.writeStrtab(off); |
| 392 | seg.filesize = off - seg.fileoff; |
| 393 | |
| 394 | const aligned_size = mem.alignForward(u64, seg.filesize, page_size); |
| 395 | seg.vmsize = aligned_size; |
| 396 | } |
| 397 | |
| 398 | pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 { |
| 399 | const tracy = trace(@src()); |
| 400 | defer tracy.end(); |
| 401 | |
| 402 | const io = self.io; |
| 403 | const gpa = self.allocator; |
| 404 | const cmd = &self.symtab_cmd; |
| 405 | cmd.nsyms = macho_file.symtab_cmd.nsyms; |
| 406 | cmd.strsize = macho_file.symtab_cmd.strsize; |
| 407 | cmd.symoff = off; |
| 408 | |
| 409 | try self.symtab.resize(gpa, cmd.nsyms); |
| 410 | try self.strtab.resize(gpa, cmd.strsize); |
| 411 | self.strtab.items[0] = 0; |
| 412 | |
| 413 | if (macho_file.getZigObject()) |zo| { |
| 414 | zo.writeSymtab(macho_file, self); |
| 415 | } |
| 416 | for (macho_file.objects.items) |index| { |
| 417 | macho_file.getFile(index).?.writeSymtab(macho_file, self); |
| 418 | } |
| 419 | for (macho_file.dylibs.items) |index| { |
| 420 | macho_file.getFile(index).?.writeSymtab(macho_file, self); |
| 421 | } |
| 422 | if (macho_file.getInternalObject()) |internal| { |
| 423 | internal.writeSymtab(macho_file, self); |
| 424 | } |
| 425 | |
| 426 | try self.file.?.writePositionalAll(io, @ptrCast(self.symtab.items), cmd.symoff); |
| 427 | |
| 428 | return off + cmd.nsyms * @sizeOf(macho.nlist_64); |
| 429 | } |
| 430 | |
| 431 | pub fn writeStrtab(self: *DebugSymbols, off: u32) !u32 { |
| 432 | const io = self.io; |
| 433 | const cmd = &self.symtab_cmd; |
| 434 | cmd.stroff = off; |
| 435 | try self.file.?.writePositionalAll(io, self.strtab.items, cmd.stroff); |
| 436 | return off + cmd.strsize; |
| 437 | } |
| 438 | |
| 439 | pub fn getSectionIndexes(self: *DebugSymbols, segment_index: u8) struct { start: u8, end: u8 } { |
| 440 | var start: u8 = 0; |
| 441 | const nsects: u8 = for (self.segments.items, 0..) |seg, i| { |
| 442 | if (i == segment_index) break @intCast(seg.nsects); |
| 443 | start += @intCast(seg.nsects); |
| 444 | } else 0; |
| 445 | return .{ .start = start, .end = start + nsects }; |
| 446 | } |
| 447 | |
| 448 | fn getDwarfSegmentPtr(self: *DebugSymbols) *macho.segment_command_64 { |
| 449 | const index = self.dwarf_segment_cmd_index.?; |
| 450 | return &self.segments.items[index]; |
| 451 | } |
| 452 | |
| 453 | fn getLinkeditSegmentPtr(self: *DebugSymbols) *macho.segment_command_64 { |
| 454 | const index = self.linkedit_segment_cmd_index.?; |
| 455 | return &self.segments.items[index]; |
| 456 | } |
| 457 | |
| 458 | pub fn getSectionPtr(self: *DebugSymbols, sect: u8) *macho.section_64 { |
| 459 | assert(sect < self.sections.items.len); |
| 460 | return &self.sections.items[sect]; |
| 461 | } |
| 462 | |
| 463 | pub fn getSection(self: DebugSymbols, sect: u8) macho.section_64 { |
| 464 | assert(sect < self.sections.items.len); |
| 465 | return self.sections.items[sect]; |
| 466 | } |