authorgravatar for sahnvour@pm.meSahnvour <sahnvour@pm.me> 2019-07-28 19:03:36+02:00
committergravatar for sahnvour@pm.meSahnvour <sahnvour@pm.me> 2019-07-29 23:40:18+02:00
log05032c869378e7c7e3da3a2770161266058aa320
treec6488503510c6730c85a1980fed73cf2e6d6a0a9
parentd08425a0a5b15fa903d379ea5547fbb5dfecda62

coff & pdb: improved correctness of our implementation, it is now able to handle stage1's pdb and print its stack traces


3 files changed, 113 insertions(+), 45 deletions(-)

std/coff.zig+37-5
...@@ -19,6 +19,7 @@ const IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b;...@@ -19,6 +19,7 @@ const IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b;
19const IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b;19const IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b;
2020
21const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;21const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;
22const IMAGE_DEBUG_TYPE_CODEVIEW = 2;
22const DEBUG_DIRECTORY = 6;23const DEBUG_DIRECTORY = 6;
2324
24pub const CoffError = error{25pub const CoffError = error{
...@@ -28,6 +29,7 @@ pub const CoffError = error{...@@ -28,6 +29,7 @@ pub const CoffError = error{
28 MissingCoffSection,29 MissingCoffSection,
29};30};
3031
32// Official documentation of the format: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
31pub const Coff = struct {33pub const Coff = struct {
32 in_file: File,34 in_file: File,
33 allocator: *mem.Allocator,35 allocator: *mem.Allocator,
...@@ -120,6 +122,7 @@ pub const Coff = struct {...@@ -120,6 +122,7 @@ pub const Coff = struct {
120122
121 pub fn getPdbPath(self: *Coff, buffer: []u8) !usize {123 pub fn getPdbPath(self: *Coff, buffer: []u8) !usize {
122 try self.loadSections();124 try self.loadSections();
125
123 const header = blk: {126 const header = blk: {
124 if (self.getSection(".buildid")) |section| {127 if (self.getSection(".buildid")) |section| {
125 break :blk section.header;128 break :blk section.header;
...@@ -130,14 +133,32 @@ pub const Coff = struct {...@@ -130,14 +133,32 @@ pub const Coff = struct {
130 }133 }
131 };134 };
132135
133 // The linker puts a chunk that contains the .pdb path right after the
134 // debug_directory.
135 const debug_dir = &self.pe_header.data_directory[DEBUG_DIRECTORY];136 const debug_dir = &self.pe_header.data_directory[DEBUG_DIRECTORY];
136 const file_offset = debug_dir.virtual_address - header.virtual_address + header.pointer_to_raw_data;137 const file_offset = debug_dir.virtual_address - header.virtual_address + header.pointer_to_raw_data;
137 try self.in_file.seekTo(file_offset + debug_dir.size);
138138
139 var file_stream = self.in_file.inStream();139 var file_stream = self.in_file.inStream();
140 const in = &file_stream.stream;140 const in = &file_stream.stream;
141 try self.in_file.seekTo(file_offset);
142
143 // Find the correct DebugDirectoryEntry, and where its data is stored.
144 // It can be in any section.
145 const debug_dir_entry_count = debug_dir.size / @sizeOf(DebugDirectoryEntry);
146 var i: u32 = 0;
147 blk: while (i < debug_dir_entry_count) : (i += 1) {
148 const debug_dir_entry = try in.readStruct(DebugDirectoryEntry);
149 if (debug_dir_entry.type == IMAGE_DEBUG_TYPE_CODEVIEW) {
150 for (self.sections.toSlice()) |*section| {
151 const section_start = section.header.virtual_address;
152 const section_size = section.header.misc.virtual_size;
153 const rva = debug_dir_entry.address_of_raw_data;
154 const offset = rva - section_start;
155 if (section_start <= rva and offset < section_size and debug_dir_entry.size_of_data <= section_size - offset) {
156 try self.in_file.seekTo(section.header.pointer_to_raw_data + offset);
157 break :blk;
158 }
159 }
160 }
161 }
141162
142 var cv_signature: [4]u8 = undefined; // CodeView signature163 var cv_signature: [4]u8 = undefined; // CodeView signature
143 try in.readNoEof(cv_signature[0..]);164 try in.readNoEof(cv_signature[0..]);
...@@ -149,7 +170,7 @@ pub const Coff = struct {...@@ -149,7 +170,7 @@ pub const Coff = struct {
149170
150 // Finally read the null-terminated string.171 // Finally read the null-terminated string.
151 var byte = try in.readByte();172 var byte = try in.readByte();
152 var i: usize = 0;173 i = 0;
153 while (byte != 0 and i < buffer.len) : (i += 1) {174 while (byte != 0 and i < buffer.len) : (i += 1) {
154 buffer[i] = byte;175 buffer[i] = byte;
155 byte = try in.readByte();176 byte = try in.readByte();
...@@ -178,7 +199,7 @@ pub const Coff = struct {...@@ -178,7 +199,7 @@ pub const Coff = struct {
178 try self.sections.append(Section{199 try self.sections.append(Section{
179 .header = SectionHeader{200 .header = SectionHeader{
180 .name = name,201 .name = name,
181 .misc = SectionHeader.Misc{ .physical_address = try in.readIntLittle(u32) },202 .misc = SectionHeader.Misc{ .virtual_size = try in.readIntLittle(u32) },
182 .virtual_address = try in.readIntLittle(u32),203 .virtual_address = try in.readIntLittle(u32),
183 .size_of_raw_data = try in.readIntLittle(u32),204 .size_of_raw_data = try in.readIntLittle(u32),
184 .pointer_to_raw_data = try in.readIntLittle(u32),205 .pointer_to_raw_data = try in.readIntLittle(u32),
...@@ -222,6 +243,17 @@ const OptionalHeader = struct {...@@ -222,6 +243,17 @@ const OptionalHeader = struct {
222 data_directory: [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]DataDirectory,243 data_directory: [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]DataDirectory,
223};244};
224245
246const DebugDirectoryEntry = packed struct {
247 characteristiccs: u32,
248 time_date_stamp: u32,
249 major_version: u16,
250 minor_version: u16,
251 @"type": u32,
252 size_of_data: u32,
253 address_of_raw_data: u32,
254 pointer_to_raw_data: u32,
255};
256
225pub const Section = struct {257pub const Section = struct {
226 header: SectionHeader,258 header: SectionHeader,
227};259};
std/debug.zig+10-3
...@@ -375,7 +375,7 @@ fn printSourceAtAddressWindows(di: *DebugInfo, out_stream: var, relocated_addres...@@ -375,7 +375,7 @@ fn printSourceAtAddressWindows(di: *DebugInfo, out_stream: var, relocated_addres
375 const obj_basename = fs.path.basename(mod.obj_file_name);375 const obj_basename = fs.path.basename(mod.obj_file_name);
376376
377 var symbol_i: usize = 0;377 var symbol_i: usize = 0;
378 const symbol_name = while (symbol_i != mod.symbols.len) {378 const symbol_name = if (!mod.populated) "???" else while (symbol_i != mod.symbols.len) {
379 const prefix = @ptrCast(*pdb.RecordPrefix, &mod.symbols[symbol_i]);379 const prefix = @ptrCast(*pdb.RecordPrefix, &mod.symbols[symbol_i]);
380 if (prefix.RecordLen < 2)380 if (prefix.RecordLen < 2)
381 return error.InvalidDebugInfo;381 return error.InvalidDebugInfo;
...@@ -858,8 +858,10 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo {...@@ -858,8 +858,10 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo {
858 const age = try pdb_stream.stream.readIntLittle(u32);858 const age = try pdb_stream.stream.readIntLittle(u32);
859 var guid: [16]u8 = undefined;859 var guid: [16]u8 = undefined;
860 try pdb_stream.stream.readNoEof(guid[0..]);860 try pdb_stream.stream.readNoEof(guid[0..]);
861 if (version != 20000404) // VC70, only value observed by LLVM team
862 return error.UnknownPDBVersion;
861 if (!mem.eql(u8, di.coff.guid, guid) or di.coff.age != age)863 if (!mem.eql(u8, di.coff.guid, guid) or di.coff.age != age)
862 return error.InvalidDebugInfo;864 return error.PDBMismatch;
863 // We validated the executable and pdb match.865 // We validated the executable and pdb match.
864866
865 const string_table_index = str_tab_index: {867 const string_table_index = str_tab_index: {
...@@ -903,13 +905,18 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo {...@@ -903,13 +905,18 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo {
903 return error.MissingDebugInfo;905 return error.MissingDebugInfo;
904 };906 };
905907
906 di.pdb.string_table = di.pdb.getStreamById(string_table_index) orelse return error.InvalidDebugInfo;908 di.pdb.string_table = di.pdb.getStreamById(string_table_index) orelse return error.MissingDebugInfo;
907 di.pdb.dbi = di.pdb.getStream(pdb.StreamType.Dbi) orelse return error.MissingDebugInfo;909 di.pdb.dbi = di.pdb.getStream(pdb.StreamType.Dbi) orelse return error.MissingDebugInfo;
908910
909 const dbi = di.pdb.dbi;911 const dbi = di.pdb.dbi;
910912
911 // Dbi Header913 // Dbi Header
912 const dbi_stream_header = try dbi.stream.readStruct(pdb.DbiStreamHeader);914 const dbi_stream_header = try dbi.stream.readStruct(pdb.DbiStreamHeader);
915 if (dbi_stream_header.VersionHeader != 19990903) // V70, only value observed by LLVM team
916 return error.UnknownPDBVersion;
917 if (dbi_stream_header.Age != age)
918 return error.UnmatchingPDB;
919
913 const mod_info_size = dbi_stream_header.ModInfoSize;920 const mod_info_size = dbi_stream_header.ModInfoSize;
914 const section_contrib_size = dbi_stream_header.SectionContributionSize;921 const section_contrib_size = dbi_stream_header.SectionContributionSize;
915922
std/pdb.zig+66-37
...@@ -499,45 +499,78 @@ const Msf = struct {...@@ -499,45 +499,78 @@ const Msf = struct {
499499
500 const superblock = try in.readStruct(SuperBlock);500 const superblock = try in.readStruct(SuperBlock);
501501
502 // Sanity checks
502 if (!mem.eql(u8, superblock.FileMagic, SuperBlock.file_magic))503 if (!mem.eql(u8, superblock.FileMagic, SuperBlock.file_magic))
503 return error.InvalidDebugInfo;504 return error.InvalidDebugInfo;
504505 if (superblock.FreeBlockMapBlock != 1 and superblock.FreeBlockMapBlock != 2)
506 return error.InvalidDebugInfo;
507 if (superblock.NumBlocks * superblock.BlockSize != try file.getEndPos())
508 return error.InvalidDebugInfo;
505 switch (superblock.BlockSize) {509 switch (superblock.BlockSize) {
506 // llvm only supports 4096 but we can handle any of these values510 // llvm only supports 4096 but we can handle any of these values
507 512, 1024, 2048, 4096 => {},511 512, 1024, 2048, 4096 => {},
508 else => return error.InvalidDebugInfo,512 else => return error.InvalidDebugInfo,
509 }513 }
510514
511 if (superblock.NumBlocks * superblock.BlockSize != try file.getEndPos())515 const dir_block_count = blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize);
512 return error.InvalidDebugInfo;516 if (dir_block_count > superblock.BlockSize / @sizeOf(u32))
517 return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment.
513518
514 self.directory = try MsfStream.init(519 try file.seekTo(superblock.BlockSize * superblock.BlockMapAddr);
520 var dir_blocks = try allocator.alloc(u32, dir_block_count);
521 for (dir_blocks) |*b| {
522 b.* = try in.readIntLittle(u32);
523 }
524 self.directory = MsfStream.init(
515 superblock.BlockSize,525 superblock.BlockSize,
516 blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize),
517 superblock.BlockSize * superblock.BlockMapAddr,
518 file,526 file,
519 allocator,527 dir_blocks,
520 );528 );
521529
530 const begin = self.directory.pos;
522 const stream_count = try self.directory.stream.readIntLittle(u32);531 const stream_count = try self.directory.stream.readIntLittle(u32);
523
524 const stream_sizes = try allocator.alloc(u32, stream_count);532 const stream_sizes = try allocator.alloc(u32, stream_count);
525 for (stream_sizes) |*s| {533 defer allocator.free(stream_sizes);
534
535 // Microsoft's implementation uses u32(-1) for inexistant streams.
536 // These streams are not used, but still participate in the file
537 // and must be taken into account when resolving stream indices.
538 const Nil = 0xFFFFFFFF;
539 for (stream_sizes) |*s, i| {
526 const size = try self.directory.stream.readIntLittle(u32);540 const size = try self.directory.stream.readIntLittle(u32);
527 s.* = blockCountFromSize(size, superblock.BlockSize);541 s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.BlockSize);
528 }542 }
529543
530 self.streams = try allocator.alloc(MsfStream, stream_count);544 self.streams = try allocator.alloc(MsfStream, stream_count);
531 for (self.streams) |*stream, i| {545 for (self.streams) |*stream, i| {
532 stream.* = try MsfStream.init(546 const size = stream_sizes[i];
533 superblock.BlockSize,547 if (size == 0) {
534 stream_sizes[i],548 stream.* = MsfStream{
535 // MsfStream.init expects the file to be at the part where it reads [N]u32549 .blocks = [_]u32{},
536 try file.getPos(),550 };
537 file,551 } else {
538 allocator,552 var blocks = try allocator.alloc(u32, size);
539 );553 var j: u32 = 0;
554 while (j < size) : (j += 1) {
555 const block_id = try self.directory.stream.readIntLittle(u32);
556 const n = (block_id % superblock.BlockSize);
557 // 0 is for SuperBlock, 1 and 2 for FPMs.
558 if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > try file.getEndPos())
559 return error.InvalidBlockIndex;
560 blocks[j] = block_id;
561 }
562
563 stream.* = MsfStream.init(
564 superblock.BlockSize,
565 file,
566 blocks,
567 );
568 }
540 }569 }
570
571 const end = self.directory.pos;
572 if (end - begin != superblock.NumDirectoryBytes)
573 return error.InvalidStreamDirectory;
541 }574 }
542};575};
543576
...@@ -574,7 +607,6 @@ const SuperBlock = packed struct {...@@ -574,7 +607,6 @@ const SuperBlock = packed struct {
574 NumDirectoryBytes: u32,607 NumDirectoryBytes: u32,
575608
576 Unknown: u32,609 Unknown: u32,
577
578 /// The index of a block within the MSF file. At this block is an array of610 /// The index of a block within the MSF file. At this block is an array of
579 /// ulittle32_t’s listing the blocks that the stream directory resides on.611 /// ulittle32_t’s listing the blocks that the stream directory resides on.
580 /// For large MSF files, the stream directory (which describes the block612 /// For large MSF files, the stream directory (which describes the block
...@@ -584,45 +616,41 @@ const SuperBlock = packed struct {...@@ -584,45 +616,41 @@ const SuperBlock = packed struct {
584 /// and the stream directory itself can be stitched together accordingly.616 /// and the stream directory itself can be stitched together accordingly.
585 /// The number of ulittle32_t’s in this array is given by617 /// The number of ulittle32_t’s in this array is given by
586 /// ceil(NumDirectoryBytes / BlockSize).618 /// ceil(NumDirectoryBytes / BlockSize).
619 // Note: microsoft-pdb code actually suggests this is a variable-length
620 // array. If the indices of blocks occupied by the Stream Directory didn't
621 // fit in one page, there would be other u32 following it.
622 // This would mean the Stream Directory is bigger than BlockSize / sizeof(u32)
623 // blocks. We're not even close to this with a 1GB pdb file, and LLVM didn't
624 // implement it so we're kind of safe making this assumption for now.
587 BlockMapAddr: u32,625 BlockMapAddr: u32,
588};626};
589627
590const MsfStream = struct {628const MsfStream = struct {
591 in_file: File,629 in_file: File = undefined,
592 pos: u64,630 pos: u64 = undefined,
593 blocks: []u32,631 blocks: []u32 = undefined,
594 block_size: u32,632 block_size: u32 = undefined,
595633
596 /// Implementation of InStream trait for Pdb.MsfStream634 /// Implementation of InStream trait for Pdb.MsfStream
597 stream: Stream,635 stream: Stream = undefined,
598636
599 pub const Error = @typeOf(read).ReturnType.ErrorSet;637 pub const Error = @typeOf(read).ReturnType.ErrorSet;
600 pub const Stream = io.InStream(Error);638 pub const Stream = io.InStream(Error);
601639
602 fn init(block_size: u32, block_count: u32, pos: u64, file: File, allocator: *mem.Allocator) !MsfStream {640 fn init(block_size: u32, file: File, blocks: []u32) MsfStream {
603 var stream = MsfStream{641 const stream = MsfStream{
604 .in_file = file,642 .in_file = file,
605 .pos = 0,643 .pos = 0,
606 .blocks = try allocator.alloc(u32, block_count),644 .blocks = blocks,
607 .block_size = block_size,645 .block_size = block_size,
608 .stream = Stream{ .readFn = readFn },646 .stream = Stream{ .readFn = readFn },
609 };647 };
610648
611 var file_stream = file.inStream();
612 const in = &file_stream.stream;
613 try file.seekTo(pos);
614
615 var i: u32 = 0;
616 while (i < block_count) : (i += 1) {
617 stream.blocks[i] = try in.readIntLittle(u32);
618 }
619
620 return stream;649 return stream;
621 }650 }
622651
623 fn readNullTermString(self: *MsfStream, allocator: *mem.Allocator) ![]u8 {652 fn readNullTermString(self: *MsfStream, allocator: *mem.Allocator) ![]u8 {
624 var list = ArrayList(u8).init(allocator);653 var list = ArrayList(u8).init(allocator);
625 defer list.deinit();
626 while (true) {654 while (true) {
627 const byte = try self.stream.readByte();655 const byte = try self.stream.readByte();
628 if (byte == 0) {656 if (byte == 0) {
...@@ -633,6 +661,7 @@ const MsfStream = struct {...@@ -633,6 +661,7 @@ const MsfStream = struct {
633 }661 }
634662
635 fn read(self: *MsfStream, buffer: []u8) !usize {663 fn read(self: *MsfStream, buffer: []u8) !usize {
664
636 var block_id = @intCast(usize, self.pos / self.block_size);665 var block_id = @intCast(usize, self.pos / self.block_size);
637 var block = self.blocks[block_id];666 var block = self.blocks[block_id];
638 var offset = self.pos % self.block_size;667 var offset = self.pos % self.block_size;