Skip to content

Commit ceec239

Browse files
authored
Merge pull request #2965 from Sahnvour/zig-stack-traces
Stage1 stack traces on windows
2 parents bc982e6 + c087525 commit ceec239

File tree

4 files changed

+136
-52
lines changed

4 files changed

+136
-52
lines changed

std/coff.zig

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b;
1919
const IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b;
2020

2121
const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;
22+
const IMAGE_DEBUG_TYPE_CODEVIEW = 2;
2223
const DEBUG_DIRECTORY = 6;
2324

2425
pub const CoffError = error{
@@ -28,6 +29,7 @@ pub const CoffError = error{
2829
MissingCoffSection,
2930
};
3031

32+
// Official documentation of the format: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
3133
pub const Coff = struct {
3234
in_file: File,
3335
allocator: *mem.Allocator,
@@ -120,6 +122,7 @@ pub const Coff = struct {
120122

121123
pub fn getPdbPath(self: *Coff, buffer: []u8) !usize {
122124
try self.loadSections();
125+
123126
const header = blk: {
124127
if (self.getSection(".buildid")) |section| {
125128
break :blk section.header;
@@ -130,14 +133,32 @@ pub const Coff = struct {
130133
}
131134
};
132135

133-
// The linker puts a chunk that contains the .pdb path right after the
134-
// debug_directory.
135136
const debug_dir = &self.pe_header.data_directory[DEBUG_DIRECTORY];
136137
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

139139
var file_stream = self.in_file.inStream();
140140
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

142163
var cv_signature: [4]u8 = undefined; // CodeView signature
143164
try in.readNoEof(cv_signature[0..]);
@@ -149,7 +170,7 @@ pub const Coff = struct {
149170

150171
// Finally read the null-terminated string.
151172
var byte = try in.readByte();
152-
var i: usize = 0;
173+
i = 0;
153174
while (byte != 0 and i < buffer.len) : (i += 1) {
154175
buffer[i] = byte;
155176
byte = try in.readByte();
@@ -178,7 +199,7 @@ pub const Coff = struct {
178199
try self.sections.append(Section{
179200
.header = SectionHeader{
180201
.name = name,
181-
.misc = SectionHeader.Misc{ .physical_address = try in.readIntLittle(u32) },
202+
.misc = SectionHeader.Misc{ .virtual_size = try in.readIntLittle(u32) },
182203
.virtual_address = try in.readIntLittle(u32),
183204
.size_of_raw_data = try in.readIntLittle(u32),
184205
.pointer_to_raw_data = try in.readIntLittle(u32),
@@ -222,6 +243,17 @@ const OptionalHeader = struct {
222243
data_directory: [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]DataDirectory,
223244
};
224245

246+
const 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+
225257
pub const Section = struct {
226258
header: SectionHeader,
227259
};

std/debug.zig

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ fn printSourceAtAddressWindows(di: *DebugInfo, out_stream: var, relocated_addres
375375
const obj_basename = fs.path.basename(mod.obj_file_name);
376376

377377
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) {
379379
const prefix = @ptrCast(*pdb.RecordPrefix, &mod.symbols[symbol_i]);
380380
if (prefix.RecordLen < 2)
381381
return error.InvalidDebugInfo;
@@ -858,8 +858,10 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo {
858858
const age = try pdb_stream.stream.readIntLittle(u32);
859859
var guid: [16]u8 = undefined;
860860
try pdb_stream.stream.readNoEof(guid[0..]);
861+
if (version != 20000404) // VC70, only value observed by LLVM team
862+
return error.UnknownPDBVersion;
861863
if (!mem.eql(u8, di.coff.guid, guid) or di.coff.age != age)
862-
return error.InvalidDebugInfo;
864+
return error.PDBMismatch;
863865
// We validated the executable and pdb match.
864866

865867
const string_table_index = str_tab_index: {
@@ -903,13 +905,18 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo {
903905
return error.MissingDebugInfo;
904906
};
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;
907909
di.pdb.dbi = di.pdb.getStream(pdb.StreamType.Dbi) orelse return error.MissingDebugInfo;
908910

909911
const dbi = di.pdb.dbi;
910912

911913
// Dbi Header
912914
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+
913920
const mod_info_size = dbi_stream_header.ModInfoSize;
914921
const section_contrib_size = dbi_stream_header.SectionContributionSize;
915922

std/os.zig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2053,6 +2053,22 @@ pub fn accessC(path: [*]const u8, mode: u32) AccessError!void {
20532053
}
20542054
}
20552055

2056+
/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
2057+
/// Otherwise use `access` or `accessC`.
2058+
/// TODO currently this ignores `mode`.
2059+
pub fn accessW(path: [*]const u16, mode: u32) windows.GetFileAttributesError!void {
2060+
const ret = try windows.GetFileAttributesW(path);
2061+
if (ret != windows.INVALID_FILE_ATTRIBUTES) {
2062+
return;
2063+
}
2064+
switch (windows.kernel32.GetLastError()) {
2065+
windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
2066+
windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
2067+
windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
2068+
else => |err| return windows.unexpectedError(err),
2069+
}
2070+
}
2071+
20562072
pub const PipeError = error{
20572073
SystemFdQuotaExceeded,
20582074
ProcessFdQuotaExceeded,

std/pdb.zig

Lines changed: 73 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -499,45 +499,78 @@ const Msf = struct {
499499

500500
const superblock = try in.readStruct(SuperBlock);
501501

502+
// Sanity checks
502503
if (!mem.eql(u8, superblock.FileMagic, SuperBlock.file_magic))
503504
return error.InvalidDebugInfo;
504-
505+
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;
505509
switch (superblock.BlockSize) {
506510
// llvm only supports 4096 but we can handle any of these values
507511
512, 1024, 2048, 4096 => {},
508512
else => return error.InvalidDebugInfo,
509513
}
510514

511-
if (superblock.NumBlocks * superblock.BlockSize != try file.getEndPos())
512-
return error.InvalidDebugInfo;
515+
const dir_block_count = blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize);
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(
515525
superblock.BlockSize,
516-
blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize),
517-
superblock.BlockSize * superblock.BlockMapAddr,
518526
file,
519-
allocator,
527+
dir_blocks,
520528
);
521529

530+
const begin = self.directory.pos;
522531
const stream_count = try self.directory.stream.readIntLittle(u32);
523-
524532
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| {
526540
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);
528542
}
529543

530544
self.streams = try allocator.alloc(MsfStream, stream_count);
531545
for (self.streams) |*stream, i| {
532-
stream.* = try MsfStream.init(
533-
superblock.BlockSize,
534-
stream_sizes[i],
535-
// MsfStream.init expects the file to be at the part where it reads [N]u32
536-
try file.getPos(),
537-
file,
538-
allocator,
539-
);
546+
const size = stream_sizes[i];
547+
if (size == 0) {
548+
stream.* = MsfStream{
549+
.blocks = [_]u32{},
550+
};
551+
} else {
552+
var blocks = try allocator.alloc(u32, size);
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+
}
540569
}
570+
571+
const end = self.directory.pos;
572+
if (end - begin != superblock.NumDirectoryBytes)
573+
return error.InvalidStreamDirectory;
541574
}
542575
};
543576

@@ -574,7 +607,6 @@ const SuperBlock = packed struct {
574607
NumDirectoryBytes: u32,
575608

576609
Unknown: u32,
577-
578610
/// The index of a block within the MSF file. At this block is an array of
579611
/// ulittle32_t’s listing the blocks that the stream directory resides on.
580612
/// For large MSF files, the stream directory (which describes the block
@@ -584,45 +616,41 @@ const SuperBlock = packed struct {
584616
/// and the stream directory itself can be stitched together accordingly.
585617
/// The number of ulittle32_t’s in this array is given by
586618
/// 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.
587625
BlockMapAddr: u32,
588626
};
589627

590628
const MsfStream = struct {
591-
in_file: File,
592-
pos: u64,
593-
blocks: []u32,
594-
block_size: u32,
629+
in_file: File = undefined,
630+
pos: u64 = undefined,
631+
blocks: []u32 = undefined,
632+
block_size: u32 = undefined,
595633

596634
/// Implementation of InStream trait for Pdb.MsfStream
597-
stream: Stream,
635+
stream: Stream = undefined,
598636

599637
pub const Error = @typeOf(read).ReturnType.ErrorSet;
600638
pub const Stream = io.InStream(Error);
601639

602-
fn init(block_size: u32, block_count: u32, pos: u64, file: File, allocator: *mem.Allocator) !MsfStream {
603-
var stream = MsfStream{
640+
fn init(block_size: u32, file: File, blocks: []u32) MsfStream {
641+
const stream = MsfStream{
604642
.in_file = file,
605643
.pos = 0,
606-
.blocks = try allocator.alloc(u32, block_count),
644+
.blocks = blocks,
607645
.block_size = block_size,
608646
.stream = Stream{ .readFn = readFn },
609647
};
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-
620649
return stream;
621650
}
622651

623652
fn readNullTermString(self: *MsfStream, allocator: *mem.Allocator) ![]u8 {
624653
var list = ArrayList(u8).init(allocator);
625-
defer list.deinit();
626654
while (true) {
627655
const byte = try self.stream.readByte();
628656
if (byte == 0) {
@@ -642,11 +670,12 @@ const MsfStream = struct {
642670
const in = &file_stream.stream;
643671

644672
var size: usize = 0;
645-
for (buffer) |*byte| {
646-
byte.* = try in.readByte();
647-
648-
offset += 1;
649-
size += 1;
673+
var rem_buffer = buffer;
674+
while (size < buffer.len) {
675+
const size_to_read = math.min(self.block_size - offset, rem_buffer.len);
676+
size += try in.read(rem_buffer[0..size_to_read]);
677+
rem_buffer = buffer[size..];
678+
offset += size_to_read;
650679

651680
// If we're at the end of a block, go to the next one.
652681
if (offset == self.block_size) {
@@ -657,8 +686,8 @@ const MsfStream = struct {
657686
}
658687
}
659688

660-
self.pos += size;
661-
return size;
689+
self.pos += buffer.len;
690+
return buffer.len;
662691
}
663692

664693
fn seekBy(self: *MsfStream, len: i64) !void {

0 commit comments

Comments
 (0)