| ... | @@ -0,0 +1,1540 @@ |
| 1 | const DebugSymbols = @This(); |
| 2 | |
| 3 | const std = @import("std"); |
| 4 | const assert = std.debug.assert; |
| 5 | const fs = std.fs; |
| 6 | const log = std.log.scoped(.link); |
| 7 | const macho = std.macho; |
| 8 | const mem = std.mem; |
| 9 | const DW = std.dwarf; |
| 10 | const leb = std.leb; |
| 11 | const Allocator = mem.Allocator; |
| 12 | |
| 13 | const build_options = @import("build_options"); |
| 14 | const trace = @import("../../tracy.zig").trace; |
| 15 | const Module = @import("../../Module.zig"); |
| 16 | const Type = @import("../../type.zig").Type; |
| 17 | const link = @import("../../link.zig"); |
| 18 | const MachO = @import("../MachO.zig"); |
| 19 | const SrcFn = MachO.SrcFn; |
| 20 | const TextBlock = MachO.TextBlock; |
| 21 | const satMul = MachO.satMul; |
| 22 | const alloc_num = MachO.alloc_num; |
| 23 | const alloc_den = MachO.alloc_den; |
| 24 | const makeStaticString = MachO.makeStaticString; |
| 25 | |
| 26 | usingnamespace @import("commands.zig"); |
| 27 | |
| 28 | const page_size: u16 = 0x1000; |
| 29 | |
| 30 | base: *MachO, |
| 31 | file: fs.File, |
| 32 | |
| 33 | /// Mach header |
| 34 | header: ?macho.mach_header_64 = null, |
| 35 | |
| 36 | /// Table of all load commands |
| 37 | load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, |
| 38 | /// __PAGEZERO segment |
| 39 | pagezero_segment_cmd_index: ?u16 = null, |
| 40 | /// __TEXT segment |
| 41 | text_segment_cmd_index: ?u16 = null, |
| 42 | /// __DATA segment |
| 43 | data_segment_cmd_index: ?u16 = null, |
| 44 | /// __LINKEDIT segment |
| 45 | linkedit_segment_cmd_index: ?u16 = null, |
| 46 | /// __DWARF segment |
| 47 | dwarf_segment_cmd_index: ?u16 = null, |
| 48 | /// Symbol table |
| 49 | symtab_cmd_index: ?u16 = null, |
| 50 | /// UUID load command |
| 51 | uuid_cmd_index: ?u16 = null, |
| 52 | |
| 53 | /// Index into __TEXT,__text section. |
| 54 | text_section_index: ?u16 = null, |
| 55 | |
| 56 | linkedit_off: u16 = page_size, |
| 57 | linkedit_size: u16 = page_size, |
| 58 | |
| 59 | debug_info_section_index: ?u16 = null, |
| 60 | debug_abbrev_section_index: ?u16 = null, |
| 61 | debug_str_section_index: ?u16 = null, |
| 62 | debug_aranges_section_index: ?u16 = null, |
| 63 | debug_line_section_index: ?u16 = null, |
| 64 | |
| 65 | debug_abbrev_table_offset: ?u64 = null, |
| 66 | |
| 67 | /// A list of `SrcFn` whose Line Number Programs have surplus capacity. |
| 68 | /// This is the same concept as `text_block_free_list`; see those doc comments. |
| 69 | dbg_line_fn_free_list: std.AutoHashMapUnmanaged(*SrcFn, void) = .{}, |
| 70 | dbg_line_fn_first: ?*SrcFn = null, |
| 71 | dbg_line_fn_last: ?*SrcFn = null, |
| 72 | |
| 73 | /// A list of `TextBlock` whose corresponding .debug_info tags have surplus capacity. |
| 74 | /// This is the same concept as `text_block_free_list`; see those doc comments. |
| 75 | dbg_info_decl_free_list: std.AutoHashMapUnmanaged(*TextBlock, void) = .{}, |
| 76 | dbg_info_decl_first: ?*TextBlock = null, |
| 77 | dbg_info_decl_last: ?*TextBlock = null, |
| 78 | |
| 79 | /// Table of debug symbol names aka the debug string table. |
| 80 | debug_string_table: std.ArrayListUnmanaged(u8) = .{}, |
| 81 | |
| 82 | header_dirty: bool = false, |
| 83 | load_commands_dirty: bool = false, |
| 84 | string_table_dirty: bool = false, |
| 85 | debug_string_table_dirty: bool = false, |
| 86 | debug_abbrev_section_dirty: bool = false, |
| 87 | debug_aranges_section_dirty: bool = false, |
| 88 | debug_info_header_dirty: bool = false, |
| 89 | debug_line_header_dirty: bool = false, |
| 90 | |
| 91 | const abbrev_compile_unit = 1; |
| 92 | const abbrev_subprogram = 2; |
| 93 | const abbrev_subprogram_retvoid = 3; |
| 94 | const abbrev_base_type = 4; |
| 95 | const abbrev_pad1 = 5; |
| 96 | const abbrev_parameter = 6; |
| 97 | |
| 98 | /// The reloc offset for the virtual address of a function in its Line Number Program. |
| 99 | /// Size is a virtual address integer. |
| 100 | const dbg_line_vaddr_reloc_index = 3; |
| 101 | /// The reloc offset for the virtual address of a function in its .debug_info TAG_subprogram. |
| 102 | /// Size is a virtual address integer. |
| 103 | const dbg_info_low_pc_reloc_index = 1; |
| 104 | |
| 105 | const min_nop_size = 2; |
| 106 | |
| 107 | /// You must call this function *after* `MachO.populateMissingMetadata()` |
| 108 | /// has been called to get a viable debug symbols output. |
| 109 | pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void { |
| 110 | if (self.header == null) { |
| 111 | const base_header = self.base.header.?; |
| 112 | var header: macho.mach_header_64 = undefined; |
| 113 | header.magic = macho.MH_MAGIC_64; |
| 114 | header.cputype = base_header.cputype; |
| 115 | header.cpusubtype = base_header.cpusubtype; |
| 116 | header.filetype = macho.MH_DSYM; |
| 117 | // These will get populated at the end of flushing the results to file. |
| 118 | header.ncmds = 0; |
| 119 | header.sizeofcmds = 0; |
| 120 | header.flags = 0; |
| 121 | header.reserved = 0; |
| 122 | self.header = header; |
| 123 | self.header_dirty = true; |
| 124 | } |
| 125 | if (self.uuid_cmd_index == null) { |
| 126 | const base_cmd = self.base.load_commands.items[self.base.uuid_cmd_index.?]; |
| 127 | self.uuid_cmd_index = @intCast(u16, self.load_commands.items.len); |
| 128 | try self.load_commands.append(allocator, base_cmd); |
| 129 | self.header_dirty = true; |
| 130 | self.load_commands_dirty = true; |
| 131 | } |
| 132 | if (self.symtab_cmd_index == null) { |
| 133 | self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); |
| 134 | const base_cmd = self.base.load_commands.items[self.base.symtab_cmd_index.?].Symtab; |
| 135 | const symtab_size = base_cmd.nsyms * @sizeOf(macho.nlist_64); |
| 136 | const symtab_off = self.findFreeSpaceLinkedit(symtab_size, @sizeOf(macho.nlist_64)); |
| 137 | |
| 138 | log.debug("found dSym symbol table free space 0x{x} to 0x{x}", .{ symtab_off, symtab_off + symtab_size }); |
| 139 | |
| 140 | const strtab_off = self.findFreeSpaceLinkedit(base_cmd.strsize, 1); |
| 141 | |
| 142 | log.debug("found dSym string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + base_cmd.strsize }); |
| 143 | |
| 144 | try self.load_commands.append(allocator, .{ |
| 145 | .Symtab = .{ |
| 146 | .cmd = macho.LC_SYMTAB, |
| 147 | .cmdsize = @sizeOf(macho.symtab_command), |
| 148 | .symoff = @intCast(u32, symtab_off), |
| 149 | .nsyms = base_cmd.nsyms, |
| 150 | .stroff = @intCast(u32, strtab_off), |
| 151 | .strsize = base_cmd.strsize, |
| 152 | }, |
| 153 | }); |
| 154 | self.header_dirty = true; |
| 155 | self.load_commands_dirty = true; |
| 156 | self.string_table_dirty = true; |
| 157 | } |
| 158 | if (self.pagezero_segment_cmd_index == null) { |
| 159 | self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len); |
| 160 | const base_cmd = self.base.load_commands.items[self.base.pagezero_segment_cmd_index.?].Segment; |
| 161 | const cmd = try self.copySegmentCommand(allocator, base_cmd); |
| 162 | try self.load_commands.append(allocator, .{ .Segment = cmd }); |
| 163 | self.header_dirty = true; |
| 164 | self.load_commands_dirty = true; |
| 165 | } |
| 166 | if (self.text_segment_cmd_index == null) { |
| 167 | self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len); |
| 168 | const base_cmd = self.base.load_commands.items[self.base.text_segment_cmd_index.?].Segment; |
| 169 | const cmd = try self.copySegmentCommand(allocator, base_cmd); |
| 170 | try self.load_commands.append(allocator, .{ .Segment = cmd }); |
| 171 | self.header_dirty = true; |
| 172 | self.load_commands_dirty = true; |
| 173 | } |
| 174 | if (self.data_segment_cmd_index == null) outer: { |
| 175 | if (self.base.data_segment_cmd_index == null) break :outer; // __DATA is optional |
| 176 | self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len); |
| 177 | const base_cmd = self.base.load_commands.items[self.base.data_segment_cmd_index.?].Segment; |
| 178 | const cmd = try self.copySegmentCommand(allocator, base_cmd); |
| 179 | try self.load_commands.append(allocator, .{ .Segment = cmd }); |
| 180 | self.header_dirty = true; |
| 181 | self.load_commands_dirty = true; |
| 182 | } |
| 183 | if (self.linkedit_segment_cmd_index == null) { |
| 184 | self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len); |
| 185 | const base_cmd = self.base.load_commands.items[self.base.linkedit_segment_cmd_index.?].Segment; |
| 186 | var cmd = try self.copySegmentCommand(allocator, base_cmd); |
| 187 | cmd.inner.vmsize = self.linkedit_size; |
| 188 | cmd.inner.fileoff = self.linkedit_off; |
| 189 | cmd.inner.filesize = self.linkedit_size; |
| 190 | try self.load_commands.append(allocator, .{ .Segment = cmd }); |
| 191 | self.header_dirty = true; |
| 192 | self.load_commands_dirty = true; |
| 193 | } |
| 194 | if (self.dwarf_segment_cmd_index == null) { |
| 195 | self.dwarf_segment_cmd_index = @intCast(u16, self.load_commands.items.len); |
| 196 | |
| 197 | const linkedit = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; |
| 198 | const ideal_size: u16 = 200 + 128 + 160 + 250; |
| 199 | const needed_size = mem.alignForwardGeneric(u64, satMul(ideal_size, alloc_num) / alloc_den, page_size); |
| 200 | const off = linkedit.inner.fileoff + linkedit.inner.filesize; |
| 201 | const vmaddr = linkedit.inner.vmaddr + linkedit.inner.vmsize; |
| 202 | |
| 203 | log.debug("found dSym __DWARF segment free space 0x{x} to 0x{x}", .{ off, off + needed_size }); |
| 204 | |
| 205 | try self.load_commands.append(allocator, .{ |
| 206 | .Segment = SegmentCommand.empty(.{ |
| 207 | .cmd = macho.LC_SEGMENT_64, |
| 208 | .cmdsize = @sizeOf(macho.segment_command_64), |
| 209 | .segname = makeStaticString("__DWARF"), |
| 210 | .vmaddr = vmaddr, |
| 211 | .vmsize = needed_size, |
| 212 | .fileoff = off, |
| 213 | .filesize = needed_size, |
| 214 | .maxprot = 0, |
| 215 | .initprot = 0, |
| 216 | .nsects = 0, |
| 217 | .flags = 0, |
| 218 | }), |
| 219 | }); |
| 220 | self.header_dirty = true; |
| 221 | self.load_commands_dirty = true; |
| 222 | } |
| 223 | if (self.debug_str_section_index == null) { |
| 224 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 225 | self.debug_str_section_index = @intCast(u16, dwarf_segment.sections.items.len); |
| 226 | assert(self.debug_string_table.items.len == 0); |
| 227 | |
| 228 | try dwarf_segment.addSection(allocator, .{ |
| 229 | .sectname = makeStaticString("__debug_str"), |
| 230 | .segname = makeStaticString("__DWARF"), |
| 231 | .addr = dwarf_segment.inner.vmaddr, |
| 232 | .size = @intCast(u32, self.debug_string_table.items.len), |
| 233 | .offset = @intCast(u32, dwarf_segment.inner.fileoff), |
| 234 | .@"align" = 1, |
| 235 | .reloff = 0, |
| 236 | .nreloc = 0, |
| 237 | .flags = macho.S_REGULAR, |
| 238 | .reserved1 = 0, |
| 239 | .reserved2 = 0, |
| 240 | .reserved3 = 0, |
| 241 | }); |
| 242 | self.header_dirty = true; |
| 243 | self.load_commands_dirty = true; |
| 244 | self.debug_string_table_dirty = true; |
| 245 | } |
| 246 | if (self.debug_info_section_index == null) { |
| 247 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 248 | self.debug_info_section_index = @intCast(u16, dwarf_segment.sections.items.len); |
| 249 | |
| 250 | const file_size_hint = 200; |
| 251 | const p_align = 1; |
| 252 | const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null); |
| 253 | |
| 254 | log.debug("found dSym __debug_info free space 0x{x} to 0x{x}", .{ off, off + file_size_hint }); |
| 255 | |
| 256 | try dwarf_segment.addSection(allocator, .{ |
| 257 | .sectname = makeStaticString("__debug_info"), |
| 258 | .segname = makeStaticString("__DWARF"), |
| 259 | .addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff, |
| 260 | .size = file_size_hint, |
| 261 | .offset = @intCast(u32, off), |
| 262 | .@"align" = p_align, |
| 263 | .reloff = 0, |
| 264 | .nreloc = 0, |
| 265 | .flags = macho.S_REGULAR, |
| 266 | .reserved1 = 0, |
| 267 | .reserved2 = 0, |
| 268 | .reserved3 = 0, |
| 269 | }); |
| 270 | self.header_dirty = true; |
| 271 | self.load_commands_dirty = true; |
| 272 | self.debug_info_header_dirty = true; |
| 273 | } |
| 274 | if (self.debug_abbrev_section_index == null) { |
| 275 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 276 | self.debug_abbrev_section_index = @intCast(u16, dwarf_segment.sections.items.len); |
| 277 | |
| 278 | const file_size_hint = 128; |
| 279 | const p_align = 1; |
| 280 | const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null); |
| 281 | |
| 282 | log.debug("found dSym __debug_abbrev free space 0x{x} to 0x{x}", .{ off, off + file_size_hint }); |
| 283 | |
| 284 | try dwarf_segment.addSection(allocator, .{ |
| 285 | .sectname = makeStaticString("__debug_abbrev"), |
| 286 | .segname = makeStaticString("__DWARF"), |
| 287 | .addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff, |
| 288 | .size = file_size_hint, |
| 289 | .offset = @intCast(u32, off), |
| 290 | .@"align" = p_align, |
| 291 | .reloff = 0, |
| 292 | .nreloc = 0, |
| 293 | .flags = macho.S_REGULAR, |
| 294 | .reserved1 = 0, |
| 295 | .reserved2 = 0, |
| 296 | .reserved3 = 0, |
| 297 | }); |
| 298 | self.header_dirty = true; |
| 299 | self.load_commands_dirty = true; |
| 300 | self.debug_abbrev_section_dirty = true; |
| 301 | } |
| 302 | if (self.debug_aranges_section_index == null) { |
| 303 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 304 | self.debug_aranges_section_index = @intCast(u16, dwarf_segment.sections.items.len); |
| 305 | |
| 306 | const file_size_hint = 160; |
| 307 | const p_align = 16; |
| 308 | const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null); |
| 309 | |
| 310 | log.debug("found dSym __debug_aranges free space 0x{x} to 0x{x}", .{ off, off + file_size_hint }); |
| 311 | |
| 312 | try dwarf_segment.addSection(allocator, .{ |
| 313 | .sectname = makeStaticString("__debug_aranges"), |
| 314 | .segname = makeStaticString("__DWARF"), |
| 315 | .addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff, |
| 316 | .size = file_size_hint, |
| 317 | .offset = @intCast(u32, off), |
| 318 | .@"align" = p_align, |
| 319 | .reloff = 0, |
| 320 | .nreloc = 0, |
| 321 | .flags = macho.S_REGULAR, |
| 322 | .reserved1 = 0, |
| 323 | .reserved2 = 0, |
| 324 | .reserved3 = 0, |
| 325 | }); |
| 326 | self.header_dirty = true; |
| 327 | self.load_commands_dirty = true; |
| 328 | self.debug_aranges_section_dirty = true; |
| 329 | } |
| 330 | if (self.debug_line_section_index == null) { |
| 331 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 332 | self.debug_line_section_index = @intCast(u16, dwarf_segment.sections.items.len); |
| 333 | |
| 334 | const file_size_hint = 250; |
| 335 | const p_align = 1; |
| 336 | const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null); |
| 337 | |
| 338 | log.debug("found dSym __debug_line free space 0x{x} to 0x{x}", .{ off, off + file_size_hint }); |
| 339 | |
| 340 | try dwarf_segment.addSection(allocator, .{ |
| 341 | .sectname = makeStaticString("__debug_line"), |
| 342 | .segname = makeStaticString("__DWARF"), |
| 343 | .addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff, |
| 344 | .size = file_size_hint, |
| 345 | .offset = @intCast(u32, off), |
| 346 | .@"align" = p_align, |
| 347 | .reloff = 0, |
| 348 | .nreloc = 0, |
| 349 | .flags = macho.S_REGULAR, |
| 350 | .reserved1 = 0, |
| 351 | .reserved2 = 0, |
| 352 | .reserved3 = 0, |
| 353 | }); |
| 354 | self.header_dirty = true; |
| 355 | self.load_commands_dirty = true; |
| 356 | self.debug_line_header_dirty = true; |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | pub fn flushModule(self: *DebugSymbols, allocator: *Allocator, options: link.Options) !void { |
| 361 | // TODO This linker code currently assumes there is only 1 compilation unit and it corresponds to the |
| 362 | // Zig source code. |
| 363 | const module = options.module orelse return error.LinkingWithoutZigSourceUnimplemented; |
| 364 | const init_len_size: usize = 4; |
| 365 | |
| 366 | if (self.debug_abbrev_section_dirty) { |
| 367 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 368 | const debug_abbrev_sect = &dwarf_segment.sections.items[self.debug_abbrev_section_index.?]; |
| 369 | |
| 370 | // These are LEB encoded but since the values are all less than 127 |
| 371 | // we can simply append these bytes. |
| 372 | const abbrev_buf = [_]u8{ |
| 373 | abbrev_compile_unit, DW.TAG_compile_unit, DW.CHILDREN_yes, // header |
| 374 | DW.AT_stmt_list, DW.FORM_sec_offset, // offset |
| 375 | DW.AT_low_pc, DW.FORM_addr, |
| 376 | DW.AT_high_pc, DW.FORM_addr, |
| 377 | DW.AT_name, DW.FORM_strp, |
| 378 | DW.AT_comp_dir, DW.FORM_strp, |
| 379 | DW.AT_producer, DW.FORM_strp, |
| 380 | DW.AT_language, DW.FORM_data2, |
| 381 | 0, 0, // table sentinel |
| 382 | abbrev_subprogram, DW.TAG_subprogram, DW.CHILDREN_yes, // header |
| 383 | DW.AT_low_pc, DW.FORM_addr, // start VM address |
| 384 | DW.AT_high_pc, DW.FORM_data4, |
| 385 | DW.AT_type, DW.FORM_ref4, |
| 386 | DW.AT_name, DW.FORM_string, |
| 387 | DW.AT_decl_line, DW.FORM_data4, |
| 388 | DW.AT_decl_file, DW.FORM_data1, |
| 389 | 0, 0, // table sentinel |
| 390 | abbrev_subprogram_retvoid, |
| 391 | DW.TAG_subprogram, DW.CHILDREN_yes, // header |
| 392 | DW.AT_low_pc, DW.FORM_addr, |
| 393 | DW.AT_high_pc, DW.FORM_data4, |
| 394 | DW.AT_name, DW.FORM_string, |
| 395 | DW.AT_decl_line, DW.FORM_data4, |
| 396 | DW.AT_decl_file, DW.FORM_data1, |
| 397 | 0, 0, // table sentinel |
| 398 | abbrev_base_type, DW.TAG_base_type, DW.CHILDREN_no, // header |
| 399 | DW.AT_encoding, DW.FORM_data1, DW.AT_byte_size, |
| 400 | DW.FORM_data1, DW.AT_name, DW.FORM_string, |
| 401 | 0, 0, // table sentinel |
| 402 | abbrev_pad1, DW.TAG_unspecified_type, DW.CHILDREN_no, // header |
| 403 | 0, 0, // table sentinel |
| 404 | abbrev_parameter, DW.TAG_formal_parameter, DW.CHILDREN_no, // header |
| 405 | DW.AT_location, DW.FORM_exprloc, DW.AT_type, |
| 406 | DW.FORM_ref4, DW.AT_name, DW.FORM_string, |
| 407 | 0, 0, // table sentinel |
| 408 | 0, 0, 0, // section sentinel |
| 409 | }; |
| 410 | |
| 411 | const needed_size = abbrev_buf.len; |
| 412 | const allocated_size = dwarf_segment.allocatedSize(debug_abbrev_sect.offset); |
| 413 | if (needed_size > allocated_size) { |
| 414 | debug_abbrev_sect.size = 0; // free the space |
| 415 | const offset = dwarf_segment.findFreeSpace(needed_size, 1, null); |
| 416 | debug_abbrev_sect.offset = @intCast(u32, offset); |
| 417 | debug_abbrev_sect.addr = dwarf_segment.inner.vmaddr + offset - dwarf_segment.inner.fileoff; |
| 418 | } |
| 419 | debug_abbrev_sect.size = needed_size; |
| 420 | log.debug("__debug_abbrev start=0x{x} end=0x{x}", .{ |
| 421 | debug_abbrev_sect.offset, |
| 422 | debug_abbrev_sect.offset + needed_size, |
| 423 | }); |
| 424 | |
| 425 | const abbrev_offset = 0; |
| 426 | self.debug_abbrev_table_offset = abbrev_offset; |
| 427 | try self.file.pwriteAll(&abbrev_buf, debug_abbrev_sect.offset + abbrev_offset); |
| 428 | self.load_commands_dirty = true; |
| 429 | self.debug_abbrev_section_dirty = false; |
| 430 | } |
| 431 | |
| 432 | if (self.debug_info_header_dirty) debug_info: { |
| 433 | // If this value is null it means there is an error in the module; |
| 434 | // leave debug_info_header_dirty=true. |
| 435 | const first_dbg_info_decl = self.dbg_info_decl_first orelse break :debug_info; |
| 436 | const last_dbg_info_decl = self.dbg_info_decl_last.?; |
| 437 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 438 | const debug_info_sect = &dwarf_segment.sections.items[self.debug_info_section_index.?]; |
| 439 | |
| 440 | var di_buf = std.ArrayList(u8).init(allocator); |
| 441 | defer di_buf.deinit(); |
| 442 | |
| 443 | // We have a function to compute the upper bound size, because it's needed |
| 444 | // for determining where to put the offset of the first `LinkBlock`. |
| 445 | try di_buf.ensureCapacity(self.dbgInfoNeededHeaderBytes()); |
| 446 | |
| 447 | // initial length - length of the .debug_info contribution for this compilation unit, |
| 448 | // not including the initial length itself. |
| 449 | // We have to come back and write it later after we know the size. |
| 450 | const after_init_len = di_buf.items.len + init_len_size; |
| 451 | // +1 for the final 0 that ends the compilation unit children. |
| 452 | const dbg_info_end = last_dbg_info_decl.dbg_info_off + last_dbg_info_decl.dbg_info_len + 1; |
| 453 | const init_len = dbg_info_end - after_init_len; |
| 454 | mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len)); |
| 455 | mem.writeIntLittle(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4); // DWARF version |
| 456 | const abbrev_offset = self.debug_abbrev_table_offset.?; |
| 457 | mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, abbrev_offset)); |
| 458 | di_buf.appendAssumeCapacity(8); // address size |
| 459 | // Write the form for the compile unit, which must match the abbrev table above. |
| 460 | const name_strp = try self.makeDebugString(allocator, module.root_pkg.root_src_path); |
| 461 | const comp_dir_strp = try self.makeDebugString(allocator, module.root_pkg.root_src_directory.path orelse "."); |
| 462 | const producer_strp = try self.makeDebugString(allocator, link.producer_string); |
| 463 | // Currently only one compilation unit is supported, so the address range is simply |
| 464 | // identical to the main program header virtual address and memory size. |
| 465 | const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; |
| 466 | const text_section = text_segment.sections.items[self.text_section_index.?]; |
| 467 | const low_pc = text_section.addr; |
| 468 | const high_pc = text_section.addr + text_section.size; |
| 469 | |
| 470 | di_buf.appendAssumeCapacity(abbrev_compile_unit); |
| 471 | mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), 0); // DW.AT_stmt_list, DW.FORM_sec_offset |
| 472 | mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), low_pc); |
| 473 | mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), high_pc); |
| 474 | mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, name_strp)); |
| 475 | mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, comp_dir_strp)); |
| 476 | mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, producer_strp)); |
| 477 | // We are still waiting on dwarf-std.org to assign DW_LANG_Zig a number: |
| 478 | // http://dwarfstd.org/ShowIssue.php?issue=171115.1 |
| 479 | // Until then we say it is C99. |
| 480 | mem.writeIntLittle(u16, di_buf.addManyAsArrayAssumeCapacity(2), DW.LANG_C99); |
| 481 | |
| 482 | if (di_buf.items.len > first_dbg_info_decl.dbg_info_off) { |
| 483 | // Move the first N decls to the end to make more padding for the header. |
| 484 | @panic("TODO: handle __debug_info header exceeding its padding"); |
| 485 | } |
| 486 | const jmp_amt = first_dbg_info_decl.dbg_info_off - di_buf.items.len; |
| 487 | try self.pwriteDbgInfoNops(0, di_buf.items, jmp_amt, false, debug_info_sect.offset); |
| 488 | self.debug_info_header_dirty = false; |
| 489 | } |
| 490 | |
| 491 | if (self.debug_aranges_section_dirty) { |
| 492 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 493 | const debug_aranges_sect = &dwarf_segment.sections.items[self.debug_aranges_section_index.?]; |
| 494 | const debug_info_sect = dwarf_segment.sections.items[self.debug_info_section_index.?]; |
| 495 | |
| 496 | var di_buf = std.ArrayList(u8).init(allocator); |
| 497 | defer di_buf.deinit(); |
| 498 | |
| 499 | // Enough for all the data without resizing. When support for more compilation units |
| 500 | // is added, the size of this section will become more variable. |
| 501 | try di_buf.ensureCapacity(100); |
| 502 | |
| 503 | // initial length - length of the .debug_aranges contribution for this compilation unit, |
| 504 | // not including the initial length itself. |
| 505 | // We have to come back and write it later after we know the size. |
| 506 | const init_len_index = di_buf.items.len; |
| 507 | di_buf.items.len += init_len_size; |
| 508 | const after_init_len = di_buf.items.len; |
| 509 | mem.writeIntLittle(u16, di_buf.addManyAsArrayAssumeCapacity(2), 2); // version |
| 510 | // When more than one compilation unit is supported, this will be the offset to it. |
| 511 | // For now it is always at offset 0 in .debug_info. |
| 512 | mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), 0); // __debug_info offset |
| 513 | di_buf.appendAssumeCapacity(@sizeOf(u64)); // address_size |
| 514 | di_buf.appendAssumeCapacity(0); // segment_selector_size |
| 515 | |
| 516 | const end_header_offset = di_buf.items.len; |
| 517 | const begin_entries_offset = mem.alignForward(end_header_offset, @sizeOf(u64) * 2); |
| 518 | di_buf.appendNTimesAssumeCapacity(0, begin_entries_offset - end_header_offset); |
| 519 | |
| 520 | // Currently only one compilation unit is supported, so the address range is simply |
| 521 | // identical to the main program header virtual address and memory size. |
| 522 | const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; |
| 523 | const text_section = text_segment.sections.items[self.text_section_index.?]; |
| 524 | mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), text_section.addr); |
| 525 | mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), text_section.size); |
| 526 | |
| 527 | // Sentinel. |
| 528 | mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), 0); |
| 529 | mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), 0); |
| 530 | |
| 531 | // Go back and populate the initial length. |
| 532 | const init_len = di_buf.items.len - after_init_len; |
| 533 | // initial length - length of the .debug_aranges contribution for this compilation unit, |
| 534 | // not including the initial length itself. |
| 535 | mem.writeIntLittle(u32, di_buf.items[init_len_index..][0..4], @intCast(u32, init_len)); |
| 536 | |
| 537 | const needed_size = di_buf.items.len; |
| 538 | const allocated_size = dwarf_segment.allocatedSize(debug_aranges_sect.offset); |
| 539 | if (needed_size > allocated_size) { |
| 540 | debug_aranges_sect.size = 0; // free the space |
| 541 | const new_offset = dwarf_segment.findFreeSpace(needed_size, 16, null); |
| 542 | debug_aranges_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff; |
| 543 | debug_aranges_sect.offset = @intCast(u32, new_offset); |
| 544 | } |
| 545 | debug_aranges_sect.size = needed_size; |
| 546 | log.debug("__debug_aranges start=0x{x} end=0x{x}", .{ |
| 547 | debug_aranges_sect.offset, |
| 548 | debug_aranges_sect.offset + needed_size, |
| 549 | }); |
| 550 | |
| 551 | try self.file.pwriteAll(di_buf.items, debug_aranges_sect.offset); |
| 552 | self.load_commands_dirty = true; |
| 553 | self.debug_aranges_section_dirty = false; |
| 554 | } |
| 555 | if (self.debug_line_header_dirty) debug_line: { |
| 556 | if (self.dbg_line_fn_first == null) { |
| 557 | break :debug_line; // Error in module; leave debug_line_header_dirty=true. |
| 558 | } |
| 559 | const dbg_line_prg_off = self.getDebugLineProgramOff(); |
| 560 | const dbg_line_prg_end = self.getDebugLineProgramEnd(); |
| 561 | assert(dbg_line_prg_end != 0); |
| 562 | |
| 563 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 564 | const debug_line_sect = &dwarf_segment.sections.items[self.debug_line_section_index.?]; |
| 565 | |
| 566 | var di_buf = std.ArrayList(u8).init(allocator); |
| 567 | defer di_buf.deinit(); |
| 568 | |
| 569 | // The size of this header is variable, depending on the number of directories, |
| 570 | // files, and padding. We have a function to compute the upper bound size, however, |
| 571 | // because it's needed for determining where to put the offset of the first `SrcFn`. |
| 572 | try di_buf.ensureCapacity(self.dbgLineNeededHeaderBytes(module)); |
| 573 | |
| 574 | // initial length - length of the .debug_line contribution for this compilation unit, |
| 575 | // not including the initial length itself. |
| 576 | const after_init_len = di_buf.items.len + init_len_size; |
| 577 | const init_len = dbg_line_prg_end - after_init_len; |
| 578 | mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len)); |
| 579 | mem.writeIntLittle(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4); // version |
| 580 | |
| 581 | // Empirically, debug info consumers do not respect this field, or otherwise |
| 582 | // consider it to be an error when it does not point exactly to the end of the header. |
| 583 | // Therefore we rely on the NOP jump at the beginning of the Line Number Program for |
| 584 | // padding rather than this field. |
| 585 | const before_header_len = di_buf.items.len; |
| 586 | di_buf.items.len += @sizeOf(u32); // We will come back and write this. |
| 587 | const after_header_len = di_buf.items.len; |
| 588 | |
| 589 | const opcode_base = DW.LNS_set_isa + 1; |
| 590 | di_buf.appendSliceAssumeCapacity(&[_]u8{ |
| 591 | 1, // minimum_instruction_length |
| 592 | 1, // maximum_operations_per_instruction |
| 593 | 1, // default_is_stmt |
| 594 | 1, // line_base (signed) |
| 595 | 1, // line_range |
| 596 | opcode_base, |
| 597 | |
| 598 | // Standard opcode lengths. The number of items here is based on `opcode_base`. |
| 599 | // The value is the number of LEB128 operands the instruction takes. |
| 600 | 0, // `DW.LNS_copy` |
| 601 | 1, // `DW.LNS_advance_pc` |
| 602 | 1, // `DW.LNS_advance_line` |
| 603 | 1, // `DW.LNS_set_file` |
| 604 | 1, // `DW.LNS_set_column` |
| 605 | 0, // `DW.LNS_negate_stmt` |
| 606 | 0, // `DW.LNS_set_basic_block` |
| 607 | 0, // `DW.LNS_const_add_pc` |
| 608 | 1, // `DW.LNS_fixed_advance_pc` |
| 609 | 0, // `DW.LNS_set_prologue_end` |
| 610 | 0, // `DW.LNS_set_epilogue_begin` |
| 611 | 1, // `DW.LNS_set_isa` |
| 612 | 0, // include_directories (none except the compilation unit cwd) |
| 613 | }); |
| 614 | // file_names[0] |
| 615 | di_buf.appendSliceAssumeCapacity(module.root_pkg.root_src_path); // relative path name |
| 616 | di_buf.appendSliceAssumeCapacity(&[_]u8{ |
| 617 | 0, // null byte for the relative path name |
| 618 | 0, // directory_index |
| 619 | 0, // mtime (TODO supply this) |
| 620 | 0, // file size bytes (TODO supply this) |
| 621 | 0, // file_names sentinel |
| 622 | }); |
| 623 | |
| 624 | const header_len = di_buf.items.len - after_header_len; |
| 625 | mem.writeIntLittle(u32, di_buf.items[before_header_len..][0..4], @intCast(u32, header_len)); |
| 626 | |
| 627 | // We use NOPs because consumers empirically do not respect the header length field. |
| 628 | if (di_buf.items.len > dbg_line_prg_off) { |
| 629 | // Move the first N files to the end to make more padding for the header. |
| 630 | @panic("TODO: handle __debug_line header exceeding its padding"); |
| 631 | } |
| 632 | const jmp_amt = dbg_line_prg_off - di_buf.items.len; |
| 633 | try self.pwriteDbgLineNops(0, di_buf.items, jmp_amt, debug_line_sect.offset); |
| 634 | self.debug_line_header_dirty = false; |
| 635 | } |
| 636 | { |
| 637 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 638 | const debug_strtab_sect = &dwarf_segment.sections.items[self.debug_str_section_index.?]; |
| 639 | if (self.debug_string_table_dirty or self.debug_string_table.items.len != debug_strtab_sect.size) { |
| 640 | const allocated_size = dwarf_segment.allocatedSize(debug_strtab_sect.offset); |
| 641 | const needed_size = self.debug_string_table.items.len; |
| 642 | |
| 643 | if (needed_size > allocated_size) { |
| 644 | debug_strtab_sect.size = 0; // free the space |
| 645 | const new_offset = dwarf_segment.findFreeSpace(needed_size, 1, null); |
| 646 | debug_strtab_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff; |
| 647 | debug_strtab_sect.offset = @intCast(u32, new_offset); |
| 648 | } |
| 649 | debug_strtab_sect.size = @intCast(u32, needed_size); |
| 650 | |
| 651 | log.debug("__debug_strtab start=0x{x} end=0x{x}", .{ |
| 652 | debug_strtab_sect.offset, |
| 653 | debug_strtab_sect.offset + needed_size, |
| 654 | }); |
| 655 | |
| 656 | try self.file.pwriteAll(self.debug_string_table.items, debug_strtab_sect.offset); |
| 657 | self.load_commands_dirty = true; |
| 658 | self.debug_string_table_dirty = false; |
| 659 | } |
| 660 | } |
| 661 | |
| 662 | try self.writeStringTable(); |
| 663 | self.updateDwarfSegment(); |
| 664 | try self.writeLoadCommands(allocator); |
| 665 | try self.writeHeader(); |
| 666 | |
| 667 | assert(!self.header_dirty); |
| 668 | assert(!self.load_commands_dirty); |
| 669 | assert(!self.string_table_dirty); |
| 670 | assert(!self.debug_abbrev_section_dirty); |
| 671 | assert(!self.debug_aranges_section_dirty); |
| 672 | assert(!self.debug_string_table_dirty); |
| 673 | } |
| 674 | |
| 675 | pub fn deinit(self: *DebugSymbols, allocator: *Allocator) void { |
| 676 | self.dbg_info_decl_free_list.deinit(allocator); |
| 677 | self.dbg_line_fn_free_list.deinit(allocator); |
| 678 | self.debug_string_table.deinit(allocator); |
| 679 | for (self.load_commands.items) |*lc| { |
| 680 | lc.deinit(allocator); |
| 681 | } |
| 682 | self.load_commands.deinit(allocator); |
| 683 | self.file.close(); |
| 684 | } |
| 685 | |
| 686 | fn copySegmentCommand(self: *DebugSymbols, allocator: *Allocator, base_cmd: SegmentCommand) !SegmentCommand { |
| 687 | var cmd = SegmentCommand.empty(.{ |
| 688 | .cmd = macho.LC_SEGMENT_64, |
| 689 | .cmdsize = base_cmd.inner.cmdsize, |
| 690 | .segname = undefined, |
| 691 | .vmaddr = base_cmd.inner.vmaddr, |
| 692 | .vmsize = base_cmd.inner.vmsize, |
| 693 | .fileoff = 0, |
| 694 | .filesize = 0, |
| 695 | .maxprot = base_cmd.inner.maxprot, |
| 696 | .initprot = base_cmd.inner.initprot, |
| 697 | .nsects = base_cmd.inner.nsects, |
| 698 | .flags = base_cmd.inner.flags, |
| 699 | }); |
| 700 | mem.copy(u8, &cmd.inner.segname, &base_cmd.inner.segname); |
| 701 | |
| 702 | try cmd.sections.ensureCapacity(allocator, cmd.inner.nsects); |
| 703 | for (base_cmd.sections.items) |base_sect, i| { |
| 704 | var sect = macho.section_64{ |
| 705 | .sectname = undefined, |
| 706 | .segname = undefined, |
| 707 | .addr = base_sect.addr, |
| 708 | .size = base_sect.size, |
| 709 | .offset = 0, |
| 710 | .@"align" = base_sect.@"align", |
| 711 | .reloff = 0, |
| 712 | .nreloc = 0, |
| 713 | .flags = base_sect.flags, |
| 714 | .reserved1 = base_sect.reserved1, |
| 715 | .reserved2 = base_sect.reserved2, |
| 716 | .reserved3 = base_sect.reserved3, |
| 717 | }; |
| 718 | mem.copy(u8, &sect.sectname, &base_sect.sectname); |
| 719 | mem.copy(u8, &sect.segname, &base_sect.segname); |
| 720 | |
| 721 | if (self.base.text_section_index.? == i) { |
| 722 | self.text_section_index = @intCast(u16, i); |
| 723 | } |
| 724 | |
| 725 | cmd.sections.appendAssumeCapacity(sect); |
| 726 | } |
| 727 | |
| 728 | return cmd; |
| 729 | } |
| 730 | |
| 731 | fn updateDwarfSegment(self: *DebugSymbols) void { |
| 732 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 733 | var file_size: u64 = 0; |
| 734 | for (dwarf_segment.sections.items) |sect| { |
| 735 | file_size += sect.size; |
| 736 | } |
| 737 | if (file_size != dwarf_segment.inner.filesize) { |
| 738 | dwarf_segment.inner.filesize = file_size; |
| 739 | if (dwarf_segment.inner.vmsize < dwarf_segment.inner.filesize) { |
| 740 | dwarf_segment.inner.vmsize = mem.alignForwardGeneric(u64, dwarf_segment.inner.filesize, page_size); |
| 741 | } |
| 742 | self.load_commands_dirty = true; |
| 743 | } |
| 744 | } |
| 745 | |
| 746 | /// Writes all load commands and section headers. |
| 747 | fn writeLoadCommands(self: *DebugSymbols, allocator: *Allocator) !void { |
| 748 | if (!self.load_commands_dirty) return; |
| 749 | |
| 750 | var sizeofcmds: usize = 0; |
| 751 | for (self.load_commands.items) |lc| { |
| 752 | sizeofcmds += lc.cmdsize(); |
| 753 | } |
| 754 | |
| 755 | var buffer = try allocator.alloc(u8, sizeofcmds); |
| 756 | defer allocator.free(buffer); |
| 757 | var writer = std.io.fixedBufferStream(buffer).writer(); |
| 758 | for (self.load_commands.items) |lc| { |
| 759 | try lc.write(writer); |
| 760 | } |
| 761 | |
| 762 | const off = @sizeOf(macho.mach_header_64); |
| 763 | log.debug("writing {} dSym load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); |
| 764 | try self.file.pwriteAll(buffer, off); |
| 765 | self.load_commands_dirty = false; |
| 766 | } |
| 767 | |
| 768 | fn writeHeader(self: *DebugSymbols) !void { |
| 769 | if (!self.header_dirty) return; |
| 770 | |
| 771 | self.header.?.ncmds = @intCast(u32, self.load_commands.items.len); |
| 772 | var sizeofcmds: u32 = 0; |
| 773 | for (self.load_commands.items) |cmd| { |
| 774 | sizeofcmds += cmd.cmdsize(); |
| 775 | } |
| 776 | self.header.?.sizeofcmds = sizeofcmds; |
| 777 | log.debug("writing Mach-O dSym header {}", .{self.header.?}); |
| 778 | try self.file.pwriteAll(mem.asBytes(&self.header.?), 0); |
| 779 | self.header_dirty = false; |
| 780 | } |
| 781 | |
| 782 | fn allocatedSizeLinkedit(self: *DebugSymbols, start: u64) u64 { |
| 783 | assert(start > 0); |
| 784 | var min_pos: u64 = std.math.maxInt(u64); |
| 785 | |
| 786 | if (self.symtab_cmd_index) |idx| { |
| 787 | const symtab = self.load_commands.items[idx].Symtab; |
| 788 | if (symtab.symoff >= start and symtab.symoff < min_pos) min_pos = symtab.symoff; |
| 789 | if (symtab.stroff >= start and symtab.stroff < min_pos) min_pos = symtab.stroff; |
| 790 | } |
| 791 | |
| 792 | return min_pos - start; |
| 793 | } |
| 794 | |
| 795 | fn detectAllocCollisionLinkedit(self: *DebugSymbols, start: u64, size: u64) ?u64 { |
| 796 | const end = start + satMul(size, alloc_num) / alloc_den; |
| 797 | |
| 798 | if (self.symtab_cmd_index) |idx| outer: { |
| 799 | if (self.load_commands.items.len == idx) break :outer; |
| 800 | const symtab = self.load_commands.items[idx].Symtab; |
| 801 | { |
| 802 | // Symbol table |
| 803 | const symsize = symtab.nsyms * @sizeOf(macho.nlist_64); |
| 804 | const increased_size = satMul(symsize, alloc_num) / alloc_den; |
| 805 | const test_end = symtab.symoff + increased_size; |
| 806 | if (end > symtab.symoff and start < test_end) { |
| 807 | return test_end; |
| 808 | } |
| 809 | } |
| 810 | { |
| 811 | // String table |
| 812 | const increased_size = satMul(symtab.strsize, alloc_num) / alloc_den; |
| 813 | const test_end = symtab.stroff + increased_size; |
| 814 | if (end > symtab.stroff and start < test_end) { |
| 815 | return test_end; |
| 816 | } |
| 817 | } |
| 818 | } |
| 819 | |
| 820 | return null; |
| 821 | } |
| 822 | |
| 823 | fn findFreeSpaceLinkedit(self: *DebugSymbols, object_size: u64, min_alignment: u16) u64 { |
| 824 | var start: u64 = self.linkedit_off; |
| 825 | while (self.detectAllocCollisionLinkedit(start, object_size)) |item_end| { |
| 826 | start = mem.alignForwardGeneric(u64, item_end, min_alignment); |
| 827 | } |
| 828 | return start; |
| 829 | } |
| 830 | |
| 831 | fn relocateSymbolTable(self: *DebugSymbols) !void { |
| 832 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; |
| 833 | const nlocals = self.base.local_symbols.items.len; |
| 834 | const nglobals = self.base.global_symbols.items.len; |
| 835 | const nsyms = nlocals + nglobals; |
| 836 | |
| 837 | if (symtab.nsyms < nsyms) { |
| 838 | const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; |
| 839 | const needed_size = nsyms * @sizeOf(macho.nlist_64); |
| 840 | if (needed_size > self.allocatedSizeLinkedit(symtab.symoff)) { |
| 841 | // Move the entire symbol table to a new location |
| 842 | const new_symoff = self.findFreeSpaceLinkedit(needed_size, @alignOf(macho.nlist_64)); |
| 843 | const existing_size = symtab.nsyms * @sizeOf(macho.nlist_64); |
| 844 | |
| 845 | assert(new_symoff + existing_size <= self.linkedit_off + self.linkedit_size); // TODO expand LINKEDIT segment. |
| 846 | log.debug("relocating dSym symbol table from 0x{x}-0x{x} to 0x{x}-0x{x}", .{ |
| 847 | symtab.symoff, |
| 848 | symtab.symoff + existing_size, |
| 849 | new_symoff, |
| 850 | new_symoff + existing_size, |
| 851 | }); |
| 852 | |
| 853 | const amt = try self.file.copyRangeAll(symtab.symoff, self.file, new_symoff, existing_size); |
| 854 | if (amt != existing_size) return error.InputOutput; |
| 855 | symtab.symoff = @intCast(u32, new_symoff); |
| 856 | } |
| 857 | symtab.nsyms = @intCast(u32, nsyms); |
| 858 | self.load_commands_dirty = true; |
| 859 | } |
| 860 | } |
| 861 | |
| 862 | pub fn writeLocalSymbol(self: *DebugSymbols, index: usize) !void { |
| 863 | const tracy = trace(@src()); |
| 864 | defer tracy.end(); |
| 865 | try self.relocateSymbolTable(); |
| 866 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; |
| 867 | const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; |
| 868 | log.debug("writing dSym local symbol {} at 0x{x}", .{ index, off }); |
| 869 | try self.file.pwriteAll(mem.asBytes(&self.base.local_symbols.items[index]), off); |
| 870 | } |
| 871 | |
| 872 | fn writeStringTable(self: *DebugSymbols) !void { |
| 873 | if (!self.string_table_dirty) return; |
| 874 | |
| 875 | const tracy = trace(@src()); |
| 876 | defer tracy.end(); |
| 877 | |
| 878 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; |
| 879 | const allocated_size = self.allocatedSizeLinkedit(symtab.stroff); |
| 880 | const needed_size = mem.alignForwardGeneric(u64, self.base.string_table.items.len, @alignOf(u64)); |
| 881 | |
| 882 | if (needed_size > allocated_size) { |
| 883 | symtab.strsize = 0; |
| 884 | symtab.stroff = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1)); |
| 885 | } |
| 886 | symtab.strsize = @intCast(u32, needed_size); |
| 887 | log.debug("writing dSym string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); |
| 888 | |
| 889 | try self.file.pwriteAll(self.base.string_table.items, symtab.stroff); |
| 890 | self.load_commands_dirty = true; |
| 891 | self.string_table_dirty = false; |
| 892 | } |
| 893 | |
| 894 | pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const Module.Decl) !void { |
| 895 | const tracy = trace(@src()); |
| 896 | defer tracy.end(); |
| 897 | |
| 898 | const container_scope = decl.scope.cast(Module.Scope.Container).?; |
| 899 | const tree = container_scope.file_scope.contents.tree; |
| 900 | const file_ast_decls = tree.root_node.decls(); |
| 901 | // TODO Look into improving the performance here by adding a token-index-to-line |
| 902 | // lookup table. Currently this involves scanning over the source code for newlines. |
| 903 | const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; |
| 904 | const block = fn_proto.getBodyNode().?.castTag(.Block).?; |
| 905 | const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); |
| 906 | const casted_line_off = @intCast(u28, line_delta); |
| 907 | |
| 908 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 909 | const shdr = &dwarf_segment.sections.items[self.debug_line_section_index.?]; |
| 910 | const file_pos = shdr.offset + decl.fn_link.macho.off + getRelocDbgLineOff(); |
| 911 | var data: [4]u8 = undefined; |
| 912 | leb.writeUnsignedFixed(4, &data, casted_line_off); |
| 913 | try self.file.pwriteAll(&data, file_pos); |
| 914 | } |
| 915 | |
| 916 | pub const DeclDebugBuffers = struct { |
| 917 | dbg_line_buffer: std.ArrayList(u8), |
| 918 | dbg_info_buffer: std.ArrayList(u8), |
| 919 | dbg_info_type_relocs: link.File.DbgInfoTypeRelocsTable, |
| 920 | }; |
| 921 | |
| 922 | /// Caller owns the returned memory. |
| 923 | pub fn initDeclDebugBuffers( |
| 924 | self: *DebugSymbols, |
| 925 | allocator: *Allocator, |
| 926 | module: *Module, |
| 927 | decl: *Module.Decl, |
| 928 | ) !DeclDebugBuffers { |
| 929 | const tracy = trace(@src()); |
| 930 | defer tracy.end(); |
| 931 | |
| 932 | var dbg_line_buffer = std.ArrayList(u8).init(allocator); |
| 933 | var dbg_info_buffer = std.ArrayList(u8).init(allocator); |
| 934 | var dbg_info_type_relocs: link.File.DbgInfoTypeRelocsTable = .{}; |
| 935 | |
| 936 | const typed_value = decl.typed_value.most_recent.typed_value; |
| 937 | switch (typed_value.ty.zigTypeTag()) { |
| 938 | .Fn => { |
| 939 | const zir_dumps = if (std.builtin.is_test) &[0][]const u8{} else build_options.zir_dumps; |
| 940 | if (zir_dumps.len != 0) { |
| 941 | for (zir_dumps) |fn_name| { |
| 942 | if (mem.eql(u8, mem.spanZ(decl.name), fn_name)) { |
| 943 | std.debug.print("\n{}\n", .{decl.name}); |
| 944 | typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*); |
| 945 | } |
| 946 | } |
| 947 | } |
| 948 | |
| 949 | // For functions we need to add a prologue to the debug line program. |
| 950 | try dbg_line_buffer.ensureCapacity(26); |
| 951 | |
| 952 | const line_off: u28 = blk: { |
| 953 | if (decl.scope.cast(Module.Scope.Container)) |container_scope| { |
| 954 | const tree = container_scope.file_scope.contents.tree; |
| 955 | const file_ast_decls = tree.root_node.decls(); |
| 956 | // TODO Look into improving the performance here by adding a token-index-to-line |
| 957 | // lookup table. Currently this involves scanning over the source code for newlines. |
| 958 | const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; |
| 959 | const block = fn_proto.getBodyNode().?.castTag(.Block).?; |
| 960 | const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); |
| 961 | break :blk @intCast(u28, line_delta); |
| 962 | } else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| { |
| 963 | const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src; |
| 964 | const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off); |
| 965 | break :blk @intCast(u28, line_delta); |
| 966 | } else { |
| 967 | unreachable; |
| 968 | } |
| 969 | }; |
| 970 | |
| 971 | dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{ |
| 972 | DW.LNS_extended_op, |
| 973 | @sizeOf(u64) + 1, |
| 974 | DW.LNE_set_address, |
| 975 | }); |
| 976 | // This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`. |
| 977 | assert(dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len); |
| 978 | dbg_line_buffer.items.len += @sizeOf(u64); |
| 979 | |
| 980 | dbg_line_buffer.appendAssumeCapacity(DW.LNS_advance_line); |
| 981 | // This is the "relocatable" relative line offset from the previous function's end curly |
| 982 | // to this function's begin curly. |
| 983 | assert(getRelocDbgLineOff() == dbg_line_buffer.items.len); |
| 984 | // Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later. |
| 985 | leb.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line_off); |
| 986 | |
| 987 | dbg_line_buffer.appendAssumeCapacity(DW.LNS_set_file); |
| 988 | assert(getRelocDbgFileIndex() == dbg_line_buffer.items.len); |
| 989 | // Once we support more than one source file, this will have the ability to be more |
| 990 | // than one possible value. |
| 991 | const file_index = 1; |
| 992 | leb.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index); |
| 993 | |
| 994 | // Emit a line for the begin curly with prologue_end=false. The codegen will |
| 995 | // do the work of setting prologue_end=true and epilogue_begin=true. |
| 996 | dbg_line_buffer.appendAssumeCapacity(DW.LNS_copy); |
| 997 | |
| 998 | // .debug_info subprogram |
| 999 | const decl_name_with_null = decl.name[0 .. mem.lenZ(decl.name) + 1]; |
| 1000 | try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 27 + decl_name_with_null.len); |
| 1001 | |
| 1002 | const fn_ret_type = typed_value.ty.fnReturnType(); |
| 1003 | const fn_ret_has_bits = fn_ret_type.hasCodeGenBits(); |
| 1004 | if (fn_ret_has_bits) { |
| 1005 | dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram); |
| 1006 | } else { |
| 1007 | dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram_retvoid); |
| 1008 | } |
| 1009 | // These get overwritten after generating the machine code. These values are |
| 1010 | // "relocations" and have to be in this fixed place so that functions can be |
| 1011 | // moved in virtual address space. |
| 1012 | assert(dbg_info_low_pc_reloc_index == dbg_info_buffer.items.len); |
| 1013 | dbg_info_buffer.items.len += @sizeOf(u64); // DW.AT_low_pc, DW.FORM_addr |
| 1014 | assert(getRelocDbgInfoSubprogramHighPC() == dbg_info_buffer.items.len); |
| 1015 | dbg_info_buffer.items.len += 4; // DW.AT_high_pc, DW.FORM_data4 |
| 1016 | if (fn_ret_has_bits) { |
| 1017 | const gop = try dbg_info_type_relocs.getOrPut(allocator, fn_ret_type); |
| 1018 | if (!gop.found_existing) { |
| 1019 | gop.entry.value = .{ |
| 1020 | .off = undefined, |
| 1021 | .relocs = .{}, |
| 1022 | }; |
| 1023 | } |
| 1024 | try gop.entry.value.relocs.append(allocator, @intCast(u32, dbg_info_buffer.items.len)); |
| 1025 | dbg_info_buffer.items.len += 4; // DW.AT_type, DW.FORM_ref4 |
| 1026 | } |
| 1027 | dbg_info_buffer.appendSliceAssumeCapacity(decl_name_with_null); // DW.AT_name, DW.FORM_string |
| 1028 | mem.writeIntLittle(u32, dbg_info_buffer.addManyAsArrayAssumeCapacity(4), line_off + 1); // DW.AT_decl_line, DW.FORM_data4 |
| 1029 | dbg_info_buffer.appendAssumeCapacity(file_index); // DW.AT_decl_file, DW.FORM_data1 |
| 1030 | }, |
| 1031 | else => { |
| 1032 | // TODO implement .debug_info for global variables |
| 1033 | }, |
| 1034 | } |
| 1035 | |
| 1036 | return DeclDebugBuffers{ |
| 1037 | .dbg_info_buffer = dbg_info_buffer, |
| 1038 | .dbg_line_buffer = dbg_line_buffer, |
| 1039 | .dbg_info_type_relocs = dbg_info_type_relocs, |
| 1040 | }; |
| 1041 | } |
| 1042 | |
| 1043 | pub fn commitDeclDebugInfo( |
| 1044 | self: *DebugSymbols, |
| 1045 | allocator: *Allocator, |
| 1046 | module: *Module, |
| 1047 | decl: *Module.Decl, |
| 1048 | debug_buffers: *DeclDebugBuffers, |
| 1049 | target: std.Target, |
| 1050 | ) !void { |
| 1051 | const tracy = trace(@src()); |
| 1052 | defer tracy.end(); |
| 1053 | |
| 1054 | var dbg_line_buffer = &debug_buffers.dbg_line_buffer; |
| 1055 | var dbg_info_buffer = &debug_buffers.dbg_info_buffer; |
| 1056 | var dbg_info_type_relocs = &debug_buffers.dbg_info_type_relocs; |
| 1057 | |
| 1058 | const symbol = self.base.local_symbols.items[decl.link.macho.local_sym_index]; |
| 1059 | const text_block = &decl.link.macho; |
| 1060 | // If the Decl is a function, we need to update the __debug_line program. |
| 1061 | const typed_value = decl.typed_value.most_recent.typed_value; |
| 1062 | switch (typed_value.ty.zigTypeTag()) { |
| 1063 | .Fn => { |
| 1064 | // Perform the relocations based on vaddr. |
| 1065 | { |
| 1066 | const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..8]; |
| 1067 | mem.writeIntLittle(u64, ptr, symbol.n_value); |
| 1068 | } |
| 1069 | { |
| 1070 | const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..8]; |
| 1071 | mem.writeIntLittle(u64, ptr, symbol.n_value); |
| 1072 | } |
| 1073 | { |
| 1074 | const ptr = dbg_info_buffer.items[getRelocDbgInfoSubprogramHighPC()..][0..4]; |
| 1075 | mem.writeIntLittle(u32, ptr, @intCast(u32, text_block.size)); |
| 1076 | } |
| 1077 | |
| 1078 | try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS_extended_op, 1, DW.LNE_end_sequence }); |
| 1079 | |
| 1080 | // Now we have the full contents and may allocate a region to store it. |
| 1081 | |
| 1082 | // This logic is nearly identical to the logic below in `updateDeclDebugInfo` for |
| 1083 | // `TextBlock` and the .debug_info. If you are editing this logic, you |
| 1084 | // probably need to edit that logic too. |
| 1085 | |
| 1086 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 1087 | const debug_line_sect = &dwarf_segment.sections.items[self.debug_line_section_index.?]; |
| 1088 | const src_fn = &decl.fn_link.macho; |
| 1089 | src_fn.len = @intCast(u32, dbg_line_buffer.items.len); |
| 1090 | if (self.dbg_line_fn_last) |last| { |
| 1091 | if (src_fn.next) |next| { |
| 1092 | // Update existing function - non-last item. |
| 1093 | if (src_fn.off + src_fn.len + min_nop_size > next.off) { |
| 1094 | // It grew too big, so we move it to a new location. |
| 1095 | if (src_fn.prev) |prev| { |
| 1096 | _ = self.dbg_line_fn_free_list.put(allocator, prev, {}) catch {}; |
| 1097 | prev.next = src_fn.next; |
| 1098 | } |
| 1099 | next.prev = src_fn.prev; |
| 1100 | src_fn.next = null; |
| 1101 | // Populate where it used to be with NOPs. |
| 1102 | const file_pos = debug_line_sect.offset + src_fn.off; |
| 1103 | try self.pwriteDbgLineNops(0, &[0]u8{}, src_fn.len, file_pos); |
| 1104 | // TODO Look at the free list before appending at the end. |
| 1105 | src_fn.prev = last; |
| 1106 | last.next = src_fn; |
| 1107 | self.dbg_line_fn_last = src_fn; |
| 1108 | |
| 1109 | src_fn.off = last.off + (last.len * alloc_num / alloc_den); |
| 1110 | } |
| 1111 | } else if (src_fn.prev == null) { |
| 1112 | // Append new function. |
| 1113 | // TODO Look at the free list before appending at the end. |
| 1114 | src_fn.prev = last; |
| 1115 | last.next = src_fn; |
| 1116 | self.dbg_line_fn_last = src_fn; |
| 1117 | |
| 1118 | src_fn.off = last.off + (last.len * alloc_num / alloc_den); |
| 1119 | } |
| 1120 | } else { |
| 1121 | // This is the first function of the Line Number Program. |
| 1122 | self.dbg_line_fn_first = src_fn; |
| 1123 | self.dbg_line_fn_last = src_fn; |
| 1124 | |
| 1125 | src_fn.off = self.dbgLineNeededHeaderBytes(module) * alloc_num / alloc_den; |
| 1126 | } |
| 1127 | |
| 1128 | const last_src_fn = self.dbg_line_fn_last.?; |
| 1129 | const needed_size = last_src_fn.off + last_src_fn.len; |
| 1130 | if (needed_size != debug_line_sect.size) { |
| 1131 | if (needed_size > dwarf_segment.allocatedSize(debug_line_sect.offset)) { |
| 1132 | const new_offset = dwarf_segment.findFreeSpace(needed_size, 1, null); |
| 1133 | const existing_size = last_src_fn.off; |
| 1134 | |
| 1135 | log.debug("moving __debug_line section: {} bytes from 0x{x} to 0x{x}", .{ |
| 1136 | existing_size, |
| 1137 | debug_line_sect.offset, |
| 1138 | new_offset, |
| 1139 | }); |
| 1140 | |
| 1141 | const amt = try self.file.copyRangeAll(debug_line_sect.offset, self.file, new_offset, existing_size); |
| 1142 | if (amt != existing_size) return error.InputOutput; |
| 1143 | debug_line_sect.offset = @intCast(u32, new_offset); |
| 1144 | debug_line_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff; |
| 1145 | } |
| 1146 | debug_line_sect.size = needed_size; |
| 1147 | self.load_commands_dirty = true; // TODO look into making only the one section dirty |
| 1148 | self.debug_line_header_dirty = true; |
| 1149 | } |
| 1150 | const prev_padding_size: u32 = if (src_fn.prev) |prev| src_fn.off - (prev.off + prev.len) else 0; |
| 1151 | const next_padding_size: u32 = if (src_fn.next) |next| next.off - (src_fn.off + src_fn.len) else 0; |
| 1152 | |
| 1153 | // We only have support for one compilation unit so far, so the offsets are directly |
| 1154 | // from the .debug_line section. |
| 1155 | const file_pos = debug_line_sect.offset + src_fn.off; |
| 1156 | try self.pwriteDbgLineNops(prev_padding_size, dbg_line_buffer.items, next_padding_size, file_pos); |
| 1157 | |
| 1158 | // .debug_info - End the TAG_subprogram children. |
| 1159 | try dbg_info_buffer.append(0); |
| 1160 | }, |
| 1161 | else => {}, |
| 1162 | } |
| 1163 | |
| 1164 | // Now we emit the .debug_info types of the Decl. These will count towards the size of |
| 1165 | // the buffer, so we have to do it before computing the offset, and we can't perform the actual |
| 1166 | // relocations yet. |
| 1167 | var it = dbg_info_type_relocs.iterator(); |
| 1168 | while (it.next()) |entry| { |
| 1169 | entry.value.off = @intCast(u32, dbg_info_buffer.items.len); |
| 1170 | try self.addDbgInfoType(entry.key, dbg_info_buffer, target); |
| 1171 | } |
| 1172 | |
| 1173 | try self.updateDeclDebugInfoAllocation(allocator, text_block, @intCast(u32, dbg_info_buffer.items.len)); |
| 1174 | |
| 1175 | // Now that we have the offset assigned we can finally perform type relocations. |
| 1176 | it = dbg_info_type_relocs.iterator(); |
| 1177 | while (it.next()) |entry| { |
| 1178 | for (entry.value.relocs.items) |off| { |
| 1179 | mem.writeIntLittle( |
| 1180 | u32, |
| 1181 | dbg_info_buffer.items[off..][0..4], |
| 1182 | text_block.dbg_info_off + entry.value.off, |
| 1183 | ); |
| 1184 | } |
| 1185 | } |
| 1186 | |
| 1187 | try self.writeDeclDebugInfo(text_block, dbg_info_buffer.items); |
| 1188 | } |
| 1189 | |
| 1190 | /// Asserts the type has codegen bits. |
| 1191 | fn addDbgInfoType( |
| 1192 | self: *DebugSymbols, |
| 1193 | ty: Type, |
| 1194 | dbg_info_buffer: *std.ArrayList(u8), |
| 1195 | target: std.Target, |
| 1196 | ) !void { |
| 1197 | switch (ty.zigTypeTag()) { |
| 1198 | .Void => unreachable, |
| 1199 | .NoReturn => unreachable, |
| 1200 | .Bool => { |
| 1201 | try dbg_info_buffer.appendSlice(&[_]u8{ |
| 1202 | abbrev_base_type, |
| 1203 | DW.ATE_boolean, // DW.AT_encoding , DW.FORM_data1 |
| 1204 | 1, // DW.AT_byte_size, DW.FORM_data1 |
| 1205 | 'b', |
| 1206 | 'o', |
| 1207 | 'o', |
| 1208 | 'l', |
| 1209 | 0, // DW.AT_name, DW.FORM_string |
| 1210 | }); |
| 1211 | }, |
| 1212 | .Int => { |
| 1213 | const info = ty.intInfo(target); |
| 1214 | try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 12); |
| 1215 | dbg_info_buffer.appendAssumeCapacity(abbrev_base_type); |
| 1216 | // DW.AT_encoding, DW.FORM_data1 |
| 1217 | dbg_info_buffer.appendAssumeCapacity(switch (info.signedness) { |
| 1218 | .signed => DW.ATE_signed, |
| 1219 | .unsigned => DW.ATE_unsigned, |
| 1220 | }); |
| 1221 | // DW.AT_byte_size, DW.FORM_data1 |
| 1222 | dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(target))); |
| 1223 | // DW.AT_name, DW.FORM_string |
| 1224 | try dbg_info_buffer.writer().print("{}\x00", .{ty}); |
| 1225 | }, |
| 1226 | else => { |
| 1227 | std.log.scoped(.compiler).err("TODO implement .debug_info for type '{}'", .{ty}); |
| 1228 | try dbg_info_buffer.append(abbrev_pad1); |
| 1229 | }, |
| 1230 | } |
| 1231 | } |
| 1232 | |
| 1233 | fn updateDeclDebugInfoAllocation( |
| 1234 | self: *DebugSymbols, |
| 1235 | allocator: *Allocator, |
| 1236 | text_block: *TextBlock, |
| 1237 | len: u32, |
| 1238 | ) !void { |
| 1239 | const tracy = trace(@src()); |
| 1240 | defer tracy.end(); |
| 1241 | |
| 1242 | // This logic is nearly identical to the logic above in `updateDecl` for |
| 1243 | // `SrcFn` and the line number programs. If you are editing this logic, you |
| 1244 | // probably need to edit that logic too. |
| 1245 | |
| 1246 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 1247 | const debug_info_sect = &dwarf_segment.sections.items[self.debug_info_section_index.?]; |
| 1248 | text_block.dbg_info_len = len; |
| 1249 | if (self.dbg_info_decl_last) |last| { |
| 1250 | if (text_block.dbg_info_next) |next| { |
| 1251 | // Update existing Decl - non-last item. |
| 1252 | if (text_block.dbg_info_off + text_block.dbg_info_len + min_nop_size > next.dbg_info_off) { |
| 1253 | // It grew too big, so we move it to a new location. |
| 1254 | if (text_block.dbg_info_prev) |prev| { |
| 1255 | _ = self.dbg_info_decl_free_list.put(allocator, prev, {}) catch {}; |
| 1256 | prev.dbg_info_next = text_block.dbg_info_next; |
| 1257 | } |
| 1258 | next.dbg_info_prev = text_block.dbg_info_prev; |
| 1259 | text_block.dbg_info_next = null; |
| 1260 | // Populate where it used to be with NOPs. |
| 1261 | const file_pos = debug_info_sect.offset + text_block.dbg_info_off; |
| 1262 | try self.pwriteDbgInfoNops(0, &[0]u8{}, text_block.dbg_info_len, false, file_pos); |
| 1263 | // TODO Look at the free list before appending at the end. |
| 1264 | text_block.dbg_info_prev = last; |
| 1265 | last.dbg_info_next = text_block; |
| 1266 | self.dbg_info_decl_last = text_block; |
| 1267 | |
| 1268 | text_block.dbg_info_off = last.dbg_info_off + (last.dbg_info_len * alloc_num / alloc_den); |
| 1269 | } |
| 1270 | } else if (text_block.dbg_info_prev == null) { |
| 1271 | // Append new Decl. |
| 1272 | // TODO Look at the free list before appending at the end. |
| 1273 | text_block.dbg_info_prev = last; |
| 1274 | last.dbg_info_next = text_block; |
| 1275 | self.dbg_info_decl_last = text_block; |
| 1276 | |
| 1277 | text_block.dbg_info_off = last.dbg_info_off + (last.dbg_info_len * alloc_num / alloc_den); |
| 1278 | } |
| 1279 | } else { |
| 1280 | // This is the first Decl of the .debug_info |
| 1281 | self.dbg_info_decl_first = text_block; |
| 1282 | self.dbg_info_decl_last = text_block; |
| 1283 | |
| 1284 | text_block.dbg_info_off = self.dbgInfoNeededHeaderBytes() * alloc_num / alloc_den; |
| 1285 | } |
| 1286 | } |
| 1287 | |
| 1288 | fn writeDeclDebugInfo(self: *DebugSymbols, text_block: *TextBlock, dbg_info_buf: []const u8) !void { |
| 1289 | const tracy = trace(@src()); |
| 1290 | defer tracy.end(); |
| 1291 | |
| 1292 | // This logic is nearly identical to the logic above in `updateDecl` for |
| 1293 | // `SrcFn` and the line number programs. If you are editing this logic, you |
| 1294 | // probably need to edit that logic too. |
| 1295 | |
| 1296 | const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; |
| 1297 | const debug_info_sect = &dwarf_segment.sections.items[self.debug_info_section_index.?]; |
| 1298 | |
| 1299 | const last_decl = self.dbg_info_decl_last.?; |
| 1300 | // +1 for a trailing zero to end the children of the decl tag. |
| 1301 | const needed_size = last_decl.dbg_info_off + last_decl.dbg_info_len + 1; |
| 1302 | if (needed_size != debug_info_sect.size) { |
| 1303 | if (needed_size > dwarf_segment.allocatedSize(debug_info_sect.offset)) { |
| 1304 | const new_offset = dwarf_segment.findFreeSpace(needed_size, 1, null); |
| 1305 | const existing_size = last_decl.dbg_info_off; |
| 1306 | |
| 1307 | log.debug("moving __debug_info section: {} bytes from 0x{x} to 0x{x}", .{ |
| 1308 | existing_size, |
| 1309 | debug_info_sect.offset, |
| 1310 | new_offset, |
| 1311 | }); |
| 1312 | |
| 1313 | const amt = try self.file.copyRangeAll(debug_info_sect.offset, self.file, new_offset, existing_size); |
| 1314 | if (amt != existing_size) return error.InputOutput; |
| 1315 | debug_info_sect.offset = @intCast(u32, new_offset); |
| 1316 | debug_info_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff; |
| 1317 | } |
| 1318 | debug_info_sect.size = needed_size; |
| 1319 | self.load_commands_dirty = true; // TODO look into making only the one section dirty |
| 1320 | self.debug_info_header_dirty = true; |
| 1321 | } |
| 1322 | const prev_padding_size: u32 = if (text_block.dbg_info_prev) |prev| |
| 1323 | text_block.dbg_info_off - (prev.dbg_info_off + prev.dbg_info_len) |
| 1324 | else |
| 1325 | 0; |
| 1326 | const next_padding_size: u32 = if (text_block.dbg_info_next) |next| |
| 1327 | next.dbg_info_off - (text_block.dbg_info_off + text_block.dbg_info_len) |
| 1328 | else |
| 1329 | 0; |
| 1330 | |
| 1331 | // To end the children of the decl tag. |
| 1332 | const trailing_zero = text_block.dbg_info_next == null; |
| 1333 | |
| 1334 | // We only have support for one compilation unit so far, so the offsets are directly |
| 1335 | // from the .debug_info section. |
| 1336 | const file_pos = debug_info_sect.offset + text_block.dbg_info_off; |
| 1337 | try self.pwriteDbgInfoNops(prev_padding_size, dbg_info_buf, next_padding_size, trailing_zero, file_pos); |
| 1338 | } |
| 1339 | |
| 1340 | fn getDebugLineProgramOff(self: DebugSymbols) u32 { |
| 1341 | return self.dbg_line_fn_first.?.off; |
| 1342 | } |
| 1343 | |
| 1344 | fn getDebugLineProgramEnd(self: DebugSymbols) u32 { |
| 1345 | return self.dbg_line_fn_last.?.off + self.dbg_line_fn_last.?.len; |
| 1346 | } |
| 1347 | |
| 1348 | /// TODO Improve this to use a table. |
| 1349 | fn makeDebugString(self: *DebugSymbols, allocator: *Allocator, bytes: []const u8) !u32 { |
| 1350 | try self.debug_string_table.ensureCapacity(allocator, self.debug_string_table.items.len + bytes.len + 1); |
| 1351 | const result = self.debug_string_table.items.len; |
| 1352 | self.debug_string_table.appendSliceAssumeCapacity(bytes); |
| 1353 | self.debug_string_table.appendAssumeCapacity(0); |
| 1354 | return @intCast(u32, result); |
| 1355 | } |
| 1356 | |
| 1357 | /// The reloc offset for the line offset of a function from the previous function's line. |
| 1358 | /// It's a fixed-size 4-byte ULEB128. |
| 1359 | fn getRelocDbgLineOff() usize { |
| 1360 | return dbg_line_vaddr_reloc_index + @sizeOf(u64) + 1; |
| 1361 | } |
| 1362 | |
| 1363 | fn getRelocDbgFileIndex() usize { |
| 1364 | return getRelocDbgLineOff() + 5; |
| 1365 | } |
| 1366 | |
| 1367 | fn getRelocDbgInfoSubprogramHighPC() u32 { |
| 1368 | return dbg_info_low_pc_reloc_index + @sizeOf(u64); |
| 1369 | } |
| 1370 | |
| 1371 | fn dbgLineNeededHeaderBytes(self: DebugSymbols, module: *Module) u32 { |
| 1372 | const directory_entry_format_count = 1; |
| 1373 | const file_name_entry_format_count = 1; |
| 1374 | const directory_count = 1; |
| 1375 | const file_name_count = 1; |
| 1376 | const root_src_dir_path_len = if (module.root_pkg.root_src_directory.path) |p| p.len else 1; // "." |
| 1377 | return @intCast(u32, 53 + directory_entry_format_count * 2 + file_name_entry_format_count * 2 + |
| 1378 | directory_count * 8 + file_name_count * 8 + |
| 1379 | // These are encoded as DW.FORM_string rather than DW.FORM_strp as we would like |
| 1380 | // because of a workaround for readelf and gdb failing to understand DWARFv5 correctly. |
| 1381 | root_src_dir_path_len + |
| 1382 | module.root_pkg.root_src_path.len); |
| 1383 | } |
| 1384 | |
| 1385 | fn dbgInfoNeededHeaderBytes(self: DebugSymbols) u32 { |
| 1386 | return 120; |
| 1387 | } |
| 1388 | |
| 1389 | /// Writes to the file a buffer, prefixed and suffixed by the specified number of |
| 1390 | /// bytes of NOPs. Asserts each padding size is at least `min_nop_size` and total padding bytes |
| 1391 | /// are less than 126,976 bytes (if this limit is ever reached, this function can be |
| 1392 | /// improved to make more than one pwritev call, or the limit can be raised by a fixed |
| 1393 | /// amount by increasing the length of `vecs`). |
| 1394 | fn pwriteDbgLineNops( |
| 1395 | self: *DebugSymbols, |
| 1396 | prev_padding_size: usize, |
| 1397 | buf: []const u8, |
| 1398 | next_padding_size: usize, |
| 1399 | offset: u64, |
| 1400 | ) !void { |
| 1401 | const tracy = trace(@src()); |
| 1402 | defer tracy.end(); |
| 1403 | |
| 1404 | const page_of_nops = [1]u8{DW.LNS_negate_stmt} ** 4096; |
| 1405 | const three_byte_nop = [3]u8{ DW.LNS_advance_pc, 0b1000_0000, 0 }; |
| 1406 | var vecs: [32]std.os.iovec_const = undefined; |
| 1407 | var vec_index: usize = 0; |
| 1408 | { |
| 1409 | var padding_left = prev_padding_size; |
| 1410 | if (padding_left % 2 != 0) { |
| 1411 | vecs[vec_index] = .{ |
| 1412 | .iov_base = &three_byte_nop, |
| 1413 | .iov_len = three_byte_nop.len, |
| 1414 | }; |
| 1415 | vec_index += 1; |
| 1416 | padding_left -= three_byte_nop.len; |
| 1417 | } |
| 1418 | while (padding_left > page_of_nops.len) { |
| 1419 | vecs[vec_index] = .{ |
| 1420 | .iov_base = &page_of_nops, |
| 1421 | .iov_len = page_of_nops.len, |
| 1422 | }; |
| 1423 | vec_index += 1; |
| 1424 | padding_left -= page_of_nops.len; |
| 1425 | } |
| 1426 | if (padding_left > 0) { |
| 1427 | vecs[vec_index] = .{ |
| 1428 | .iov_base = &page_of_nops, |
| 1429 | .iov_len = padding_left, |
| 1430 | }; |
| 1431 | vec_index += 1; |
| 1432 | } |
| 1433 | } |
| 1434 | |
| 1435 | vecs[vec_index] = .{ |
| 1436 | .iov_base = buf.ptr, |
| 1437 | .iov_len = buf.len, |
| 1438 | }; |
| 1439 | vec_index += 1; |
| 1440 | |
| 1441 | { |
| 1442 | var padding_left = next_padding_size; |
| 1443 | if (padding_left % 2 != 0) { |
| 1444 | vecs[vec_index] = .{ |
| 1445 | .iov_base = &three_byte_nop, |
| 1446 | .iov_len = three_byte_nop.len, |
| 1447 | }; |
| 1448 | vec_index += 1; |
| 1449 | padding_left -= three_byte_nop.len; |
| 1450 | } |
| 1451 | while (padding_left > page_of_nops.len) { |
| 1452 | vecs[vec_index] = .{ |
| 1453 | .iov_base = &page_of_nops, |
| 1454 | .iov_len = page_of_nops.len, |
| 1455 | }; |
| 1456 | vec_index += 1; |
| 1457 | padding_left -= page_of_nops.len; |
| 1458 | } |
| 1459 | if (padding_left > 0) { |
| 1460 | vecs[vec_index] = .{ |
| 1461 | .iov_base = &page_of_nops, |
| 1462 | .iov_len = padding_left, |
| 1463 | }; |
| 1464 | vec_index += 1; |
| 1465 | } |
| 1466 | } |
| 1467 | try self.file.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); |
| 1468 | } |
| 1469 | |
| 1470 | /// Writes to the file a buffer, prefixed and suffixed by the specified number of |
| 1471 | /// bytes of padding. |
| 1472 | fn pwriteDbgInfoNops( |
| 1473 | self: *DebugSymbols, |
| 1474 | prev_padding_size: usize, |
| 1475 | buf: []const u8, |
| 1476 | next_padding_size: usize, |
| 1477 | trailing_zero: bool, |
| 1478 | offset: u64, |
| 1479 | ) !void { |
| 1480 | const tracy = trace(@src()); |
| 1481 | defer tracy.end(); |
| 1482 | |
| 1483 | const page_of_nops = [1]u8{abbrev_pad1} ** 4096; |
| 1484 | var vecs: [32]std.os.iovec_const = undefined; |
| 1485 | var vec_index: usize = 0; |
| 1486 | { |
| 1487 | var padding_left = prev_padding_size; |
| 1488 | while (padding_left > page_of_nops.len) { |
| 1489 | vecs[vec_index] = .{ |
| 1490 | .iov_base = &page_of_nops, |
| 1491 | .iov_len = page_of_nops.len, |
| 1492 | }; |
| 1493 | vec_index += 1; |
| 1494 | padding_left -= page_of_nops.len; |
| 1495 | } |
| 1496 | if (padding_left > 0) { |
| 1497 | vecs[vec_index] = .{ |
| 1498 | .iov_base = &page_of_nops, |
| 1499 | .iov_len = padding_left, |
| 1500 | }; |
| 1501 | vec_index += 1; |
| 1502 | } |
| 1503 | } |
| 1504 | |
| 1505 | vecs[vec_index] = .{ |
| 1506 | .iov_base = buf.ptr, |
| 1507 | .iov_len = buf.len, |
| 1508 | }; |
| 1509 | vec_index += 1; |
| 1510 | |
| 1511 | { |
| 1512 | var padding_left = next_padding_size; |
| 1513 | while (padding_left > page_of_nops.len) { |
| 1514 | vecs[vec_index] = .{ |
| 1515 | .iov_base = &page_of_nops, |
| 1516 | .iov_len = page_of_nops.len, |
| 1517 | }; |
| 1518 | vec_index += 1; |
| 1519 | padding_left -= page_of_nops.len; |
| 1520 | } |
| 1521 | if (padding_left > 0) { |
| 1522 | vecs[vec_index] = .{ |
| 1523 | .iov_base = &page_of_nops, |
| 1524 | .iov_len = padding_left, |
| 1525 | }; |
| 1526 | vec_index += 1; |
| 1527 | } |
| 1528 | } |
| 1529 | |
| 1530 | if (trailing_zero) { |
| 1531 | var zbuf = [1]u8{0}; |
| 1532 | vecs[vec_index] = .{ |
| 1533 | .iov_base = &zbuf, |
| 1534 | .iov_len = zbuf.len, |
| 1535 | }; |
| 1536 | vec_index += 1; |
| 1537 | } |
| 1538 | |
| 1539 | try self.file.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); |
| 1540 | } |