| 1 | const std = @import("../std.zig"); |
| 2 | const Io = std.Io; |
| 3 | const File = Io.File; |
| 4 | const Allocator = std.mem.Allocator; |
| 5 | const pdb = std.pdb; |
| 6 | const assert = std.debug.assert; |
| 7 | |
| 8 | const Pdb = @This(); |
| 9 | |
| 10 | file_reader: *File.Reader, |
| 11 | msf: Msf, |
| 12 | allocator: Allocator, |
| 13 | string_table: ?*MsfStream, |
| 14 | ipi: ?[]u8, |
| 15 | modules: []Module, |
| 16 | sect_contribs: []pdb.SectionContribEntry, |
| 17 | guid: [16]u8, |
| 18 | age: u32, |
| 19 | |
| 20 | pub const Module = struct { |
| 21 | mod_info: pdb.ModInfo, |
| 22 | module_name: []u8, |
| 23 | obj_file_name: []u8, |
| 24 | // The fields below are filled on demand. |
| 25 | populated: bool, |
| 26 | symbols: []u8, |
| 27 | subsect_info: []u8, |
| 28 | checksum_offset: ?usize, |
| 29 | /// The inlinee source lines, sorted by inlinee, then file, then line number. |
| 30 | /// This saves us from repeatedly doing linear searches over all inlinees. |
| 31 | /// We prefer binary search over a hashmap as LLVM somtimes outputs multiple entries |
| 32 | /// for a single inlinee ID, see `getInlineeSourceLines` for more info. |
| 33 | inlinee_source_lines: []*align(1) const pdb.InlineeSourceLine, |
| 34 | |
| 35 | pub fn deinit(self: *Module, allocator: Allocator) void { |
| 36 | allocator.free(self.module_name); |
| 37 | allocator.free(self.obj_file_name); |
| 38 | if (self.populated) { |
| 39 | allocator.free(self.symbols); |
| 40 | allocator.free(self.subsect_info); |
| 41 | allocator.free(self.inlinee_source_lines); |
| 42 | } |
| 43 | } |
| 44 | }; |
| 45 | |
| 46 | pub fn init(gpa: Allocator, file_reader: *File.Reader) !Pdb { |
| 47 | return .{ |
| 48 | .file_reader = file_reader, |
| 49 | .allocator = gpa, |
| 50 | .string_table = null, |
| 51 | .ipi = null, |
| 52 | .msf = try Msf.init(gpa, file_reader), |
| 53 | .modules = &.{}, |
| 54 | .sect_contribs = &.{}, |
| 55 | .guid = undefined, |
| 56 | .age = undefined, |
| 57 | }; |
| 58 | } |
| 59 | |
| 60 | pub fn deinit(self: *Pdb) void { |
| 61 | const gpa = self.allocator; |
| 62 | self.msf.deinit(gpa); |
| 63 | if (self.ipi) |ipi| gpa.free(ipi); |
| 64 | for (self.modules) |*module| { |
| 65 | module.deinit(gpa); |
| 66 | } |
| 67 | gpa.free(self.modules); |
| 68 | gpa.free(self.sect_contribs); |
| 69 | } |
| 70 | |
| 71 | pub fn parseDbiStream(self: *Pdb) !void { |
| 72 | var stream = self.getStream(pdb.StreamType.dbi) orelse |
| 73 | return error.InvalidDebugInfo; |
| 74 | |
| 75 | const gpa = self.allocator; |
| 76 | const reader = &stream.interface; |
| 77 | |
| 78 | const header = try reader.takeStruct(pdb.DbiStreamHeader, .little); |
| 79 | if (header.version_header != 19990903) // V70, only value observed by LLVM team |
| 80 | return error.UnknownPDBVersion; |
| 81 | // if (header.Age != age) |
| 82 | // return error.UnmatchingPDB; |
| 83 | |
| 84 | const mod_info_size = header.mod_info_size; |
| 85 | const section_contrib_size = header.section_contribution_size; |
| 86 | |
| 87 | var modules: std.ArrayList(Module) = .empty; |
| 88 | defer modules.deinit(gpa); |
| 89 | |
| 90 | // Module Info Substream |
| 91 | var mod_info_offset: usize = 0; |
| 92 | while (mod_info_offset != mod_info_size) { |
| 93 | const mod_info = try reader.takeStruct(pdb.ModInfo, .little); |
| 94 | var this_record_len: usize = @sizeOf(pdb.ModInfo); |
| 95 | |
| 96 | var module_name: Io.Writer.Allocating = .init(gpa); |
| 97 | defer module_name.deinit(); |
| 98 | this_record_len += try reader.streamDelimiterLimit(&module_name.writer, 0, .limited(1024)); |
| 99 | assert(reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API |
| 100 | reader.toss(1); |
| 101 | this_record_len += 1; |
| 102 | |
| 103 | var obj_file_name: Io.Writer.Allocating = .init(gpa); |
| 104 | defer obj_file_name.deinit(); |
| 105 | this_record_len += try reader.streamDelimiterLimit(&obj_file_name.writer, 0, .limited(1024)); |
| 106 | assert(reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API |
| 107 | reader.toss(1); |
| 108 | this_record_len += 1; |
| 109 | |
| 110 | if (this_record_len % 4 != 0) { |
| 111 | const round_to_next_4 = (this_record_len | 0x3) + 1; |
| 112 | const march_forward_bytes = round_to_next_4 - this_record_len; |
| 113 | try stream.seekBy(@as(isize, @intCast(march_forward_bytes))); |
| 114 | this_record_len += march_forward_bytes; |
| 115 | } |
| 116 | |
| 117 | try modules.ensureUnusedCapacity(gpa, 1); |
| 118 | const module_name_slice = try module_name.toOwnedSlice(); |
| 119 | errdefer gpa.free(module_name_slice); |
| 120 | const obj_file_name_slice = try obj_file_name.toOwnedSlice(); |
| 121 | errdefer gpa.free(obj_file_name_slice); |
| 122 | |
| 123 | modules.appendAssumeCapacity(.{ |
| 124 | .mod_info = mod_info, |
| 125 | .module_name = module_name_slice, |
| 126 | .obj_file_name = obj_file_name_slice, |
| 127 | .populated = false, |
| 128 | .symbols = undefined, |
| 129 | .subsect_info = undefined, |
| 130 | .checksum_offset = null, |
| 131 | .inlinee_source_lines = undefined, |
| 132 | }); |
| 133 | |
| 134 | mod_info_offset += this_record_len; |
| 135 | if (mod_info_offset > mod_info_size) |
| 136 | return error.InvalidDebugInfo; |
| 137 | } |
| 138 | |
| 139 | // Section Contribution Substream |
| 140 | var sect_contribs: std.ArrayList(pdb.SectionContribEntry) = .empty; |
| 141 | defer sect_contribs.deinit(gpa); |
| 142 | |
| 143 | var sect_cont_offset: usize = 0; |
| 144 | if (section_contrib_size != 0) { |
| 145 | const version = reader.takeEnum(pdb.SectionContrSubstreamVersion, .little) catch |err| switch (err) { |
| 146 | error.InvalidEnumTag, error.EndOfStream => return error.InvalidDebugInfo, |
| 147 | error.ReadFailed => |e| return e, |
| 148 | }; |
| 149 | _ = version; |
| 150 | sect_cont_offset += @sizeOf(u32); |
| 151 | } |
| 152 | while (sect_cont_offset != section_contrib_size) { |
| 153 | const entry = try sect_contribs.addOne(gpa); |
| 154 | entry.* = try reader.takeStruct(pdb.SectionContribEntry, .little); |
| 155 | sect_cont_offset += @sizeOf(pdb.SectionContribEntry); |
| 156 | |
| 157 | if (sect_cont_offset > section_contrib_size) |
| 158 | return error.InvalidDebugInfo; |
| 159 | } |
| 160 | |
| 161 | try sect_contribs.shrinkToLen(gpa); |
| 162 | try modules.shrinkToLen(gpa); |
| 163 | |
| 164 | self.sect_contribs = sect_contribs.toOwnedSliceAssert(); |
| 165 | self.modules = modules.toOwnedSliceAssert(); |
| 166 | } |
| 167 | |
| 168 | pub fn parseIpiStream(self: *Pdb) !void { |
| 169 | const gpa = self.allocator; |
| 170 | const stream = self.getStream(.ipi) orelse return; |
| 171 | const header = try stream.interface.peekStruct(pdb.IpiStreamHeader, .little); |
| 172 | if (header.version != .v80) // only value observed by LLVM team |
| 173 | return error.UnknownPDBVersion; |
| 174 | self.ipi = try stream.interface.readAlloc(gpa, @sizeOf(pdb.IpiStreamHeader) + header.type_record_bytes); |
| 175 | } |
| 176 | |
| 177 | pub fn parseInfoStream(self: *Pdb) !void { |
| 178 | var stream = self.getStream(pdb.StreamType.pdb) orelse return error.InvalidDebugInfo; |
| 179 | const reader = &stream.interface; |
| 180 | |
| 181 | // Parse the InfoStreamHeader. |
| 182 | const version = try reader.takeInt(u32, .little); |
| 183 | const signature = try reader.takeInt(u32, .little); |
| 184 | _ = signature; |
| 185 | const age = try reader.takeInt(u32, .little); |
| 186 | const guid = try reader.takeArray(16); |
| 187 | |
| 188 | if (version != 20000404) // VC70, only value observed by LLVM team |
| 189 | return error.UnknownPDBVersion; |
| 190 | |
| 191 | self.guid = guid.*; |
| 192 | self.age = age; |
| 193 | |
| 194 | const gpa = self.allocator; |
| 195 | |
| 196 | // Find the string table. |
| 197 | const string_table_index = str_tab_index: { |
| 198 | const name_bytes_len = try reader.takeInt(u32, .little); |
| 199 | const name_bytes = try reader.readAlloc(gpa, name_bytes_len); |
| 200 | defer gpa.free(name_bytes); |
| 201 | |
| 202 | const HashTableHeader = extern struct { |
| 203 | size: u32, |
| 204 | capacity: u32, |
| 205 | |
| 206 | fn maxLoad(cap: u32) u32 { |
| 207 | return cap * 2 / 3 + 1; |
| 208 | } |
| 209 | }; |
| 210 | const hash_tbl_hdr = try reader.takeStruct(HashTableHeader, .little); |
| 211 | if (hash_tbl_hdr.capacity == 0) |
| 212 | return error.InvalidDebugInfo; |
| 213 | |
| 214 | if (hash_tbl_hdr.size > HashTableHeader.maxLoad(hash_tbl_hdr.capacity)) |
| 215 | return error.InvalidDebugInfo; |
| 216 | |
| 217 | const present = try readSparseBitVector(reader, gpa); |
| 218 | defer gpa.free(present); |
| 219 | if (present.len != hash_tbl_hdr.size) |
| 220 | return error.InvalidDebugInfo; |
| 221 | const deleted = try readSparseBitVector(reader, gpa); |
| 222 | defer gpa.free(deleted); |
| 223 | |
| 224 | for (present) |_| { |
| 225 | const name_offset = try reader.takeInt(u32, .little); |
| 226 | const name_index = try reader.takeInt(u32, .little); |
| 227 | if (name_offset > name_bytes.len) |
| 228 | return error.InvalidDebugInfo; |
| 229 | const name = std.mem.sliceTo(name_bytes[name_offset..], 0); |
| 230 | if (std.mem.eql(u8, name, "/names")) { |
| 231 | break :str_tab_index name_index; |
| 232 | } |
| 233 | } |
| 234 | return error.MissingDebugInfo; |
| 235 | }; |
| 236 | |
| 237 | self.string_table = self.getStreamById(string_table_index) orelse |
| 238 | return error.MissingDebugInfo; |
| 239 | } |
| 240 | |
| 241 | pub fn getProcSym(self: *Pdb, module: *Module, address: u64) ?*align(1) pdb.ProcSym { |
| 242 | _ = self; |
| 243 | std.debug.assert(module.populated); |
| 244 | var reader: Io.Reader = .fixed(module.symbols); |
| 245 | while (true) { |
| 246 | const prefix = reader.takeStructPointer(pdb.RecordPrefix) catch return null; |
| 247 | if (prefix.record_len < 2) |
| 248 | return null; |
| 249 | reader.discardAll(prefix.record_len - @sizeOf(u16)) catch return null; |
| 250 | switch (prefix.record_kind) { |
| 251 | .lproc32, .gproc32 => { |
| 252 | const proc_sym: *align(1) pdb.ProcSym = @ptrCast(prefix); |
| 253 | if (address >= proc_sym.code_offset and address < proc_sym.code_offset + proc_sym.code_size) { |
| 254 | return proc_sym; |
| 255 | } |
| 256 | }, |
| 257 | else => {}, |
| 258 | } |
| 259 | } |
| 260 | return null; |
| 261 | } |
| 262 | |
| 263 | pub const InlineSiteSymIterator = struct { |
| 264 | module_index: usize, |
| 265 | offset: usize, |
| 266 | end: usize, |
| 267 | |
| 268 | const empty: InlineSiteSymIterator = .{ |
| 269 | .module_index = 0, |
| 270 | .offset = 0, |
| 271 | .end = 0, |
| 272 | }; |
| 273 | |
| 274 | pub fn next(iter: *InlineSiteSymIterator, module: *Module) ?*align(1) pdb.InlineSiteSym { |
| 275 | while (iter.offset < iter.end) { |
| 276 | const inline_prefix: *align(1) pdb.RecordPrefix = @ptrCast(&module.symbols[iter.offset]); |
| 277 | const end = iter.offset + inline_prefix.record_len + @sizeOf(u16); |
| 278 | if (end > iter.end) return null; |
| 279 | defer iter.offset = end; |
| 280 | switch (inline_prefix.record_kind) { |
| 281 | // Skip nested procedures |
| 282 | .lproc32, |
| 283 | .lproc32_st, |
| 284 | .gproc32, |
| 285 | .gproc32_st, |
| 286 | .lproc32_id, |
| 287 | .gproc32_id, |
| 288 | .lproc32_dpc, |
| 289 | .lproc32_dpc_id, |
| 290 | => { |
| 291 | const skip: *align(1) pdb.ProcSym = @ptrCast(inline_prefix); |
| 292 | iter.offset = skip.end; |
| 293 | }, |
| 294 | .inlinesite, |
| 295 | .inlinesite2, |
| 296 | => return @ptrCast(inline_prefix), |
| 297 | else => {}, |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | return null; |
| 302 | } |
| 303 | }; |
| 304 | |
| 305 | pub const BinaryAnnotation = union(enum) { |
| 306 | code_offset: u32, |
| 307 | change_code_offset_base: u32, |
| 308 | change_code_offset: u32, |
| 309 | change_code_length: u32, |
| 310 | change_file: u32, |
| 311 | change_line_offset: i32, |
| 312 | change_line_end_delta: u32, |
| 313 | change_range_kind: RangeKind, |
| 314 | change_column_start: u32, |
| 315 | change_column_end_delta: i32, |
| 316 | change_code_offset_and_line_offset: struct { code_delta: u32, line_delta: i32 }, |
| 317 | change_code_length_and_code_offset: struct { length: u32, delta: u32 }, |
| 318 | change_column_end: u32, |
| 319 | |
| 320 | pub const RangeKind = enum(u32) { expression = 0, statement = 1 }; |
| 321 | |
| 322 | /// A virtual machine that processed binary annotations. |
| 323 | pub const RangeIterator = struct { |
| 324 | annotations: Iterator, |
| 325 | curr: PartialRange, |
| 326 | /// The previous range is tracked as the code length is sometimes implied by the subsequent |
| 327 | /// range. |
| 328 | prev: ?PartialRange, |
| 329 | |
| 330 | const PartialRange = struct { |
| 331 | line_offset: i32, |
| 332 | file_id: ?u32, |
| 333 | code_offset: u32, |
| 334 | code_length: ?u32, |
| 335 | |
| 336 | /// Resolves a partial range to a range with a definite length, or returns null if this |
| 337 | /// is not possible. |
| 338 | fn resolve(self: PartialRange, next_code_offset: ?u32) ?Range { |
| 339 | return .{ |
| 340 | .line_offset = self.line_offset, |
| 341 | .file_id = self.file_id, |
| 342 | .code_offset = self.code_offset, |
| 343 | .code_length = b: { |
| 344 | if (self.code_length) |l| break :b l; |
| 345 | const end = next_code_offset orelse return null; |
| 346 | break :b end - self.code_offset; |
| 347 | }, |
| 348 | }; |
| 349 | } |
| 350 | }; |
| 351 | |
| 352 | pub fn init(annotations: Iterator) RangeIterator { |
| 353 | return .{ |
| 354 | .annotations = annotations, |
| 355 | .curr = .{ |
| 356 | .line_offset = 0, |
| 357 | .file_id = null, |
| 358 | .code_offset = 0, |
| 359 | .code_length = null, |
| 360 | }, |
| 361 | .prev = null, |
| 362 | }; |
| 363 | } |
| 364 | |
| 365 | pub const Range = struct { |
| 366 | line_offset: i32, |
| 367 | file_id: ?u32, |
| 368 | code_offset: u32, |
| 369 | code_length: u32, |
| 370 | |
| 371 | pub fn contains(self: Range, offset_in_func: usize) bool { |
| 372 | return self.code_offset <= offset_in_func and |
| 373 | offset_in_func < self.code_offset + self.code_length; |
| 374 | } |
| 375 | }; |
| 376 | |
| 377 | pub fn next(self: *RangeIterator) error{InvalidDebugInfo}!?Range { |
| 378 | while (try self.annotations.next()) |annotation| { |
| 379 | switch (annotation) { |
| 380 | .change_code_offset => |delta| { |
| 381 | self.curr.code_offset += delta; |
| 382 | }, |
| 383 | .change_code_length => |length| { |
| 384 | if (self.prev) |*prev| prev.code_length = prev.code_length orelse length; |
| 385 | self.curr.code_offset += length; |
| 386 | }, |
| 387 | // LLVM has code to emit these, but I wasn't able to figure out how trigger it |
| 388 | // so this logic is untested. |
| 389 | .change_file => |file_id| { |
| 390 | self.curr.file_id = file_id; |
| 391 | }, |
| 392 | // LLVM never emits this opcode, but it's clear enough how to interpret it so we |
| 393 | // may as well handle it in case they emit it in the future |
| 394 | .change_code_length_and_code_offset => |info| { |
| 395 | self.curr.code_length = info.length; |
| 396 | self.curr.code_offset += info.delta; |
| 397 | }, |
| 398 | .change_line_offset => |delta| { |
| 399 | self.curr.line_offset += delta; |
| 400 | }, |
| 401 | .change_code_offset_and_line_offset => |info| { |
| 402 | self.curr.code_offset += info.code_delta; |
| 403 | self.curr.line_offset += info.line_delta; |
| 404 | }, |
| 405 | |
| 406 | // Not emitted by LLVM at the time of writing, and we don't want to add support |
| 407 | // without a test case. Safe to ignore since we don't use this info right now. |
| 408 | .change_line_end_delta, |
| 409 | .change_column_start, |
| 410 | .change_column_end_delta, |
| 411 | .change_column_end, |
| 412 | => {}, |
| 413 | |
| 414 | // Not emitted by LLVM at the time of writing. Various sources conflict on how |
| 415 | // these opcodes should be interpreted, so we make no attempt to handle them. |
| 416 | .code_offset, |
| 417 | .change_code_offset_base, |
| 418 | .change_range_kind, |
| 419 | => { |
| 420 | self.annotations = .empty; |
| 421 | self.prev = null; |
| 422 | return null; |
| 423 | }, |
| 424 | } |
| 425 | |
| 426 | // If we have a new code offset, return the previous range if it exists, resolving |
| 427 | // its length if necessary. |
| 428 | switch (annotation) { |
| 429 | .change_code_offset, |
| 430 | .change_code_offset_and_line_offset, |
| 431 | .change_code_length_and_code_offset, |
| 432 | => {}, |
| 433 | else => continue, |
| 434 | } |
| 435 | defer self.prev = self.curr; |
| 436 | const prev = self.prev orelse continue; |
| 437 | return prev.resolve(self.curr.code_offset); |
| 438 | } |
| 439 | |
| 440 | // If we've processed all the binary operations but still have a previous range leftover |
| 441 | // with a known length, return it. |
| 442 | const prev = self.prev orelse return null; |
| 443 | defer self.prev = null; |
| 444 | return prev.resolve(null); |
| 445 | } |
| 446 | }; |
| 447 | |
| 448 | pub const Iterator = struct { |
| 449 | reader: Io.Reader, |
| 450 | |
| 451 | pub const empty: Iterator = .{ .reader = .ending_instance }; |
| 452 | |
| 453 | pub fn next(self: *Iterator) error{InvalidDebugInfo}!?BinaryAnnotation { |
| 454 | return take(&self.reader) catch |err| switch (err) { |
| 455 | error.ReadFailed => return error.InvalidDebugInfo, |
| 456 | error.EndOfStream => return null, |
| 457 | }; |
| 458 | } |
| 459 | }; |
| 460 | |
| 461 | pub fn take(reader: *Io.Reader) Io.Reader.Error!BinaryAnnotation { |
| 462 | const op = std.enums.fromInt( |
| 463 | pdb.BinaryAnnotationOpcode, |
| 464 | try takePackedU32(reader), |
| 465 | ) orelse return error.ReadFailed; |
| 466 | switch (op) { |
| 467 | // Microsoft's docs say that invalid is used as padding, though it is left ambiguous |
| 468 | // whether padding is allowed internally or only after all instructions are complete. |
| 469 | // Empirically, the latter appears to be the case, at least with the output from LLVM |
| 470 | // that I've tested. |
| 471 | .invalid => return error.EndOfStream, |
| 472 | .code_offset => return .{ |
| 473 | .code_offset = try expect(takePackedU32(reader)), |
| 474 | }, |
| 475 | .change_code_offset_base => return .{ |
| 476 | .change_code_offset_base = try expect(takePackedU32(reader)), |
| 477 | }, |
| 478 | .change_code_offset => return .{ |
| 479 | .change_code_offset = try expect(takePackedU32(reader)), |
| 480 | }, |
| 481 | .change_code_length => return .{ |
| 482 | .change_code_length = try expect(takePackedU32(reader)), |
| 483 | }, |
| 484 | .change_file => return .{ |
| 485 | .change_file = try expect(takePackedU32(reader)), |
| 486 | }, |
| 487 | .change_line_offset => return .{ |
| 488 | .change_line_offset = try expect(takePackedI32(reader)), |
| 489 | }, |
| 490 | .change_line_end_delta => return .{ |
| 491 | .change_line_end_delta = try expect(takePackedU32(reader)), |
| 492 | }, |
| 493 | .change_range_kind => return .{ |
| 494 | .change_range_kind = std.enums.fromInt( |
| 495 | RangeKind, |
| 496 | try expect(takePackedU32(reader)), |
| 497 | ) orelse return error.ReadFailed, |
| 498 | }, |
| 499 | .change_column_start => return .{ |
| 500 | .change_column_start = try expect(takePackedU32(reader)), |
| 501 | }, |
| 502 | .change_column_end_delta => return .{ |
| 503 | .change_column_end_delta = try expect(takePackedI32(reader)), |
| 504 | }, |
| 505 | .change_code_offset_and_line_offset => { |
| 506 | const EncodedArgs = packed struct(u32) { |
| 507 | code_delta: u4, |
| 508 | encoded_line_delta: u28, |
| 509 | }; |
| 510 | const args: EncodedArgs = @bitCast(try expect(takePackedU32(reader))); |
| 511 | return .{ |
| 512 | .change_code_offset_and_line_offset = .{ |
| 513 | .code_delta = args.code_delta, |
| 514 | .line_delta = decodeI32(args.encoded_line_delta), |
| 515 | }, |
| 516 | }; |
| 517 | }, |
| 518 | .change_code_length_and_code_offset => return .{ |
| 519 | .change_code_length_and_code_offset = .{ |
| 520 | .length = try expect(takePackedU32(reader)), |
| 521 | .delta = try expect(takePackedU32(reader)), |
| 522 | }, |
| 523 | }, |
| 524 | .change_column_end => return .{ |
| 525 | .change_column_end = try expect(takePackedU32(reader)), |
| 526 | }, |
| 527 | } |
| 528 | } |
| 529 | |
| 530 | // Adapted from: |
| 531 | // https://github.com/microsoft/microsoft-pdb/blob/805655a28bd8198004be2ac27e6e0290121a5e89/include/cvinfo.h#L4942 |
| 532 | pub fn takePackedU32(reader: *Io.Reader) Io.Reader.Error!u32 { |
| 533 | const b0: u32 = try reader.takeByte(); |
| 534 | if (b0 & 0x80 == 0x00) return b0; |
| 535 | |
| 536 | const b1: u32 = try reader.takeByte(); |
| 537 | if (b0 & 0xC0 == 0x80) return ((b0 & 0x3F) << 8) | b1; |
| 538 | |
| 539 | const b2: u32 = try reader.takeByte(); |
| 540 | const b3: u32 = try reader.takeByte(); |
| 541 | if (b0 & 0xE0 == 0xC0) return ((b0 & 0x1f) << 24) | (b1 << 16) | (b2 << 8) | b3; |
| 542 | |
| 543 | return error.ReadFailed; |
| 544 | } |
| 545 | |
| 546 | pub fn takePackedI32(reader: *Io.Reader) Io.Reader.Error!i32 { |
| 547 | return decodeI32(try takePackedU32(reader)); |
| 548 | } |
| 549 | |
| 550 | pub fn decodeI32(u: u32) i32 { |
| 551 | const i: i32 = @bitCast(u); |
| 552 | if (i & 1 != 0) { |
| 553 | return -(i >> 1); |
| 554 | } else { |
| 555 | return i >> 1; |
| 556 | } |
| 557 | } |
| 558 | |
| 559 | fn expect(value: anytype) error{ReadFailed}!@typeInfo(@TypeOf(value)).error_union.payload { |
| 560 | comptime assert(@typeInfo(@TypeOf(value)).error_union.error_set == Io.Reader.Error); |
| 561 | return value catch error.ReadFailed; |
| 562 | } |
| 563 | }; |
| 564 | |
| 565 | pub fn findInlineeName(self: *const Pdb, inlinee: u32) ?[]const u8 { |
| 566 | // According to LLVM, the high bit *can* be used to indicate that a type index comes from the |
| 567 | // ipi stream in which case that bit needs to be cleared. LLVM doesn't generate data in this |
| 568 | // manner, but we may as well handle it since it just involves a single bitwise and. |
| 569 | // https://llvm.org/docs/PDB/TpiStream.html#type-indices |
| 570 | const type_index = inlinee & 0x7FFFFFFF; |
| 571 | |
| 572 | var reader: Io.Reader = .fixed(self.ipi orelse return null); |
| 573 | const header = reader.takeStructPointer(pdb.IpiStreamHeader) catch return null; |
| 574 | for (header.type_index_begin..header.type_index_end) |curr_type_index| { |
| 575 | const prefix = reader.takeStructPointer(pdb.LfRecordPrefix) catch return null; |
| 576 | if (prefix.len < 2) return null; |
| 577 | reader.discardAll(prefix.len - @sizeOf(u16)) catch return null; |
| 578 | |
| 579 | if (curr_type_index == type_index) { |
| 580 | switch (prefix.kind) { |
| 581 | .func_id => { |
| 582 | const func: *align(1) pdb.LfFuncId = @ptrCast(prefix); |
| 583 | return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&func.name[0])), 0); |
| 584 | }, |
| 585 | .mfunc_id => { |
| 586 | const func: *align(1) pdb.LfMFuncId = @ptrCast(prefix); |
| 587 | return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&func.name[0])), 0); |
| 588 | }, |
| 589 | else => return null, |
| 590 | } |
| 591 | } |
| 592 | } |
| 593 | return null; |
| 594 | } |
| 595 | |
| 596 | pub fn getInlinees(self: *Pdb, module: *Module, proc_sym: *align(1) const pdb.ProcSym) InlineSiteSymIterator { |
| 597 | const module_index = module - self.modules.ptr; |
| 598 | const offset = @intFromPtr(proc_sym) - |
| 599 | @intFromPtr(module.symbols.ptr) + |
| 600 | proc_sym.record_len + |
| 601 | @sizeOf(u16); |
| 602 | const symbols_end = @intFromPtr(module.symbols.ptr) + module.symbols.len; |
| 603 | if (offset > symbols_end or proc_sym.end > symbols_end) return .empty; |
| 604 | return .{ |
| 605 | .module_index = module_index, |
| 606 | .offset = offset, |
| 607 | .end = proc_sym.end, |
| 608 | }; |
| 609 | } |
| 610 | |
| 611 | pub fn getBinaryAnnotations(self: *Pdb, module: *Module, site: *align(1) const pdb.InlineSiteSym) BinaryAnnotation.Iterator { |
| 612 | _ = self; |
| 613 | var start: usize = @intFromPtr(site) + @sizeOf(pdb.InlineSiteSym); |
| 614 | var end = start + site.record_len + @sizeOf(u16) - @sizeOf(pdb.InlineSiteSym); |
| 615 | switch (site.record_kind) { |
| 616 | .inlinesite => {}, |
| 617 | .inlinesite2 => start += @sizeOf(pdb.InlineSiteSym2) - @sizeOf(pdb.InlineSiteSym), |
| 618 | else => end = start, |
| 619 | } |
| 620 | if (start < @intFromPtr(module.symbols.ptr) or end > @intFromPtr(module.symbols.ptr) + module.symbols.len) return .empty; |
| 621 | const len = end - start; |
| 622 | const ptr: [*]const u8 = @ptrFromInt(start); |
| 623 | const slice = ptr[0..len]; |
| 624 | return .{ .reader = Io.Reader.fixed(slice) }; |
| 625 | } |
| 626 | |
| 627 | pub fn getInlineSiteSourceLocation( |
| 628 | self: *Pdb, |
| 629 | gpa: Allocator, |
| 630 | mod: *Module, |
| 631 | site: *align(1) const pdb.InlineSiteSym, |
| 632 | inlinee_src_line: *align(1) const pdb.InlineeSourceLine, |
| 633 | offset_in_func: usize, |
| 634 | ) !?std.debug.SourceLocation { |
| 635 | var ranges: BinaryAnnotation.RangeIterator = .init(self.getBinaryAnnotations(mod, site)); |
| 636 | while (try ranges.next()) |range| { |
| 637 | if (!range.contains(offset_in_func)) continue; |
| 638 | |
| 639 | const file_id = range.file_id orelse inlinee_src_line.file_id; |
| 640 | const file_name = try self.getFileName(gpa, mod, file_id); |
| 641 | errdefer self.allocator.free(file_name); |
| 642 | |
| 643 | return .{ |
| 644 | .line = inlinee_src_line.source_line_num +% @as(u32, @bitCast(range.line_offset)), |
| 645 | // LLVM doesn't currently emit column information for inlined calls in PDBs. |
| 646 | .column = 0, |
| 647 | .file_name = file_name, |
| 648 | }; |
| 649 | } |
| 650 | return null; |
| 651 | } |
| 652 | |
| 653 | pub fn getFileName(self: *Pdb, gpa: Allocator, mod: *Module, file_id: u32) ![]const u8 { |
| 654 | const checksum_offset = mod.checksum_offset orelse return error.MissingDebugInfo; |
| 655 | const subsect_index = checksum_offset + file_id; |
| 656 | const chksum_hdr: *align(1) pdb.FileChecksumEntryHeader = @ptrCast(&mod.subsect_info[subsect_index]); |
| 657 | const strtab_offset = @sizeOf(pdb.StringTableHeader) + chksum_hdr.file_name_offset; |
| 658 | self.string_table.?.seekTo(strtab_offset) catch return error.InvalidDebugInfo; |
| 659 | const string_reader = &self.string_table.?.interface; |
| 660 | var source_file_name: Io.Writer.Allocating = .init(gpa); |
| 661 | defer source_file_name.deinit(); |
| 662 | _ = try string_reader.streamDelimiterLimit(&source_file_name.writer, 0, .limited(1024)); |
| 663 | assert(string_reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API |
| 664 | string_reader.toss(1); |
| 665 | return try source_file_name.toOwnedSlice(); |
| 666 | } |
| 667 | |
| 668 | pub fn getSymbolName(self: *Pdb, proc_sym: *align(1) const pdb.ProcSym) []const u8 { |
| 669 | _ = self; |
| 670 | return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&proc_sym.name[0])), 0); |
| 671 | } |
| 672 | |
| 673 | fn inlineeSourceLineLessThan( |
| 674 | _: void, |
| 675 | lhs: *align(1) const pdb.InlineeSourceLine, |
| 676 | rhs: *align(1) const pdb.InlineeSourceLine, |
| 677 | ) bool { |
| 678 | if (lhs.inlinee < rhs.inlinee) return true; |
| 679 | if (lhs.inlinee > rhs.inlinee) return false; |
| 680 | if (lhs.file_id < rhs.file_id) return true; |
| 681 | if (lhs.file_id > rhs.file_id) return false; |
| 682 | return lhs.source_line_num < rhs.source_line_num; |
| 683 | } |
| 684 | |
| 685 | fn compareInlineeSourceLineInlinee( |
| 686 | inlinee: u32, |
| 687 | inlinee_src_line: *align(1) const pdb.InlineeSourceLine, |
| 688 | ) std.math.Order { |
| 689 | return std.math.order(inlinee, inlinee_src_line.inlinee); |
| 690 | } |
| 691 | |
| 692 | pub const InlineeSourceLocationIterator = struct { |
| 693 | /// The iterator assumes that all source lines in the slice are associated |
| 694 | /// with the same inlinee, and that it is sorted by file, then line number. |
| 695 | lines: []*align(1) const pdb.InlineeSourceLine, |
| 696 | |
| 697 | pub const empty: InlineeSourceLocationIterator = .{ .lines = &.{} }; |
| 698 | |
| 699 | pub fn next(iter: *InlineeSourceLocationIterator) ?*align(1) const pdb.InlineeSourceLine { |
| 700 | if (iter.lines.len == 0) return null; |
| 701 | const line = iter.lines[0]; |
| 702 | iter.lines = iter.lines[1..]; |
| 703 | // Filter out duplicate entries |
| 704 | while (iter.lines.len != 0 and |
| 705 | iter.lines[0].file_id == line.file_id and |
| 706 | iter.lines[0].source_line_num == line.source_line_num) |
| 707 | { |
| 708 | iter.lines = iter.lines[1..]; |
| 709 | } |
| 710 | return line; |
| 711 | } |
| 712 | }; |
| 713 | |
| 714 | /// Returns all `pdb.InlineeSourceLine`s for a given module with the given inlinee. Ideally |
| 715 | /// there would only be one entry per inlinee, but LLVM appears to assign all functions that share |
| 716 | /// a name the same inlinee ID. This is a bug: https://github.com/llvm/llvm-project/issues/191787 |
| 717 | /// The best the caller can do right now is print all the results. |
| 718 | pub fn getInlineeSourceLines(self: *Pdb, mod: *Module, inlinee: u32) InlineeSourceLocationIterator { |
| 719 | _ = self; |
| 720 | |
| 721 | // Binary search to an arbitrary match, if there are other matches they will be adjacent |
| 722 | const any = std.sort.binarySearch( |
| 723 | *align(1) const pdb.InlineeSourceLine, |
| 724 | mod.inlinee_source_lines, |
| 725 | inlinee, |
| 726 | compareInlineeSourceLineInlinee, |
| 727 | ) orelse return .empty; |
| 728 | |
| 729 | // Linearly scan to the first match |
| 730 | const begin = b: { |
| 731 | var begin = any; |
| 732 | while (begin > 0) { |
| 733 | const prev = begin - 1; |
| 734 | if (mod.inlinee_source_lines[prev].inlinee != inlinee) break; |
| 735 | begin = prev; |
| 736 | } |
| 737 | break :b begin; |
| 738 | }; |
| 739 | |
| 740 | // Linearly scan to the last match |
| 741 | const end = b: { |
| 742 | var end = any + 1; |
| 743 | while (end < mod.inlinee_source_lines.len and |
| 744 | mod.inlinee_source_lines[end].inlinee == inlinee) : (end += 1) |
| 745 | {} |
| 746 | break :b end; |
| 747 | }; |
| 748 | |
| 749 | // Return an iterator over all matches (the iterator filters out duplicate entries) |
| 750 | return .{ .lines = mod.inlinee_source_lines[begin..end] }; |
| 751 | } |
| 752 | |
| 753 | pub fn getLineNumberInfo(self: *Pdb, gpa: Allocator, module: *Module, address: u64) !std.debug.SourceLocation { |
| 754 | std.debug.assert(module.populated); |
| 755 | const subsect_info = module.subsect_info; |
| 756 | |
| 757 | var sect_offset: usize = 0; |
| 758 | var skip_len: usize = undefined; |
| 759 | while (sect_offset != subsect_info.len) : (sect_offset += skip_len) { |
| 760 | const subsect_hdr: *align(1) pdb.DebugSubsectionHeader = @ptrCast(&subsect_info[sect_offset]); |
| 761 | skip_len = subsect_hdr.length; |
| 762 | sect_offset += @sizeOf(pdb.DebugSubsectionHeader); |
| 763 | |
| 764 | switch (subsect_hdr.kind) { |
| 765 | .lines => { |
| 766 | var line_index = sect_offset; |
| 767 | |
| 768 | const line_hdr: *align(1) pdb.LineFragmentHeader = @ptrCast(&subsect_info[line_index]); |
| 769 | if (line_hdr.reloc_segment == 0) |
| 770 | return error.MissingDebugInfo; |
| 771 | line_index += @sizeOf(pdb.LineFragmentHeader); |
| 772 | const frag_vaddr_start = line_hdr.reloc_offset; |
| 773 | const frag_vaddr_end = frag_vaddr_start + line_hdr.code_size; |
| 774 | |
| 775 | if (address >= frag_vaddr_start and address < frag_vaddr_end) { |
| 776 | // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records) |
| 777 | // from now on. We will iterate through them, and eventually find a SourceLocation that we're interested in, |
| 778 | // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection. |
| 779 | const subsection_end_index = sect_offset + subsect_hdr.length; |
| 780 | |
| 781 | while (line_index < subsection_end_index) { |
| 782 | const block_hdr: *align(1) pdb.LineBlockFragmentHeader = @ptrCast(&subsect_info[line_index]); |
| 783 | line_index += @sizeOf(pdb.LineBlockFragmentHeader); |
| 784 | const start_line_index = line_index; |
| 785 | |
| 786 | const has_column = line_hdr.flags.have_columns; |
| 787 | |
| 788 | // All line entries are stored inside their line block by ascending start address. |
| 789 | // Heuristic: we want to find the last line entry |
| 790 | // that has a vaddr_start <= address. |
| 791 | // This is done with a simple linear search. |
| 792 | var line_i: u32 = 0; |
| 793 | while (line_i < block_hdr.num_lines) : (line_i += 1) { |
| 794 | const line_num_entry: *align(1) pdb.LineNumberEntry = @ptrCast(&subsect_info[line_index]); |
| 795 | line_index += @sizeOf(pdb.LineNumberEntry); |
| 796 | |
| 797 | const vaddr_start = frag_vaddr_start + line_num_entry.offset; |
| 798 | if (address < vaddr_start) { |
| 799 | break; |
| 800 | } |
| 801 | } |
| 802 | |
| 803 | // line_i == 0 would mean that no matching pdb.LineNumberEntry was found. |
| 804 | if (line_i > 0) { |
| 805 | const file_name = try self.getFileName(gpa, module, block_hdr.name_index); |
| 806 | errdefer gpa.free(file_name); |
| 807 | |
| 808 | const line_entry_idx = line_i - 1; |
| 809 | |
| 810 | const column = if (has_column) blk: { |
| 811 | const start_col_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.num_lines; |
| 812 | const col_index = start_col_index + @sizeOf(pdb.ColumnNumberEntry) * line_entry_idx; |
| 813 | const col_num_entry: *align(1) pdb.ColumnNumberEntry = @ptrCast(&subsect_info[col_index]); |
| 814 | break :blk col_num_entry.start_column; |
| 815 | } else 0; |
| 816 | |
| 817 | const found_line_index = start_line_index + line_entry_idx * @sizeOf(pdb.LineNumberEntry); |
| 818 | const line_num_entry: *align(1) pdb.LineNumberEntry = @ptrCast(&subsect_info[found_line_index]); |
| 819 | |
| 820 | return .{ |
| 821 | .file_name = file_name, |
| 822 | .line = line_num_entry.flags.start, |
| 823 | .column = column, |
| 824 | }; |
| 825 | } |
| 826 | } |
| 827 | |
| 828 | // Checking that we are not reading garbage after the (possibly) multiple block fragments. |
| 829 | if (line_index != subsection_end_index) { |
| 830 | return error.InvalidDebugInfo; |
| 831 | } |
| 832 | } |
| 833 | }, |
| 834 | else => {}, |
| 835 | } |
| 836 | |
| 837 | if (sect_offset > subsect_info.len) |
| 838 | return error.InvalidDebugInfo; |
| 839 | } |
| 840 | |
| 841 | return error.MissingDebugInfo; |
| 842 | } |
| 843 | |
| 844 | pub fn getModule(self: *Pdb, index: usize) !?*Module { |
| 845 | if (index >= self.modules.len) |
| 846 | return null; |
| 847 | |
| 848 | const mod = &self.modules[index]; |
| 849 | if (mod.populated) |
| 850 | return mod; |
| 851 | |
| 852 | // At most one can be non-zero. |
| 853 | if (mod.mod_info.c11_byte_size != 0 and mod.mod_info.c13_byte_size != 0) |
| 854 | return error.InvalidDebugInfo; |
| 855 | if (mod.mod_info.c13_byte_size == 0) |
| 856 | return error.InvalidDebugInfo; |
| 857 | |
| 858 | const stream = self.getStreamById(mod.mod_info.module_sym_stream) orelse |
| 859 | return error.MissingDebugInfo; |
| 860 | const reader = &stream.interface; |
| 861 | |
| 862 | const signature = try reader.takeInt(u32, .little); |
| 863 | if (signature != 4) |
| 864 | return error.InvalidDebugInfo; |
| 865 | |
| 866 | const gpa = self.allocator; |
| 867 | |
| 868 | mod.symbols = try reader.readAlloc(gpa, mod.mod_info.sym_byte_size - 4); |
| 869 | errdefer gpa.free(mod.symbols); |
| 870 | mod.subsect_info = try reader.readAlloc(gpa, mod.mod_info.c13_byte_size); |
| 871 | errdefer gpa.free(mod.subsect_info); |
| 872 | mod.inlinee_source_lines = b: { |
| 873 | var inlinee_source_lines: std.ArrayList(*align(1) const pdb.InlineeSourceLine) = .empty; |
| 874 | defer inlinee_source_lines.deinit(gpa); |
| 875 | var subsects: Io.Reader = .fixed(mod.subsect_info); |
| 876 | while (subsects.takeStructPointer(pdb.DebugSubsectionHeader) catch null) |subsect_hdr| { |
| 877 | var subsect: Io.Reader = .fixed(subsects.take(subsect_hdr.length) catch return null); |
| 878 | if (subsect_hdr.kind == .inlinee_lines) { |
| 879 | const inlinee_source_line_signature = subsect.takeEnum(pdb.InlineeSourceLineSignature, .little) catch return error.InvalidDebugInfo; |
| 880 | const has_extra_files = switch (inlinee_source_line_signature) { |
| 881 | .normal => false, |
| 882 | .ex => true, |
| 883 | else => continue, |
| 884 | }; |
| 885 | while (subsect.takeStructPointer(pdb.InlineeSourceLine) catch null) |info| { |
| 886 | if (has_extra_files) { |
| 887 | const file_count = subsect.takeInt(u32, .little) catch |
| 888 | return error.InvalidDebugInfo; |
| 889 | const file_bytes = std.math.mul(usize, file_count, @sizeOf(u32)) catch return error.InvalidDebugInfo; |
| 890 | subsect.discardAll(file_bytes) catch |
| 891 | return error.InvalidDebugInfo; |
| 892 | } |
| 893 | |
| 894 | try inlinee_source_lines.append(gpa, info); |
| 895 | } |
| 896 | } |
| 897 | } |
| 898 | |
| 899 | std.mem.sortUnstable( |
| 900 | *align(1) const pdb.InlineeSourceLine, |
| 901 | inlinee_source_lines.items, |
| 902 | {}, |
| 903 | inlineeSourceLineLessThan, |
| 904 | ); |
| 905 | break :b try inlinee_source_lines.toOwnedSlice(gpa); |
| 906 | }; |
| 907 | errdefer gpa.free(mod.inlinee_source_lines); |
| 908 | |
| 909 | var sect_offset: usize = 0; |
| 910 | var skip_len: usize = undefined; |
| 911 | while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) { |
| 912 | const subsect_hdr: *align(1) pdb.DebugSubsectionHeader = @ptrCast(&mod.subsect_info[sect_offset]); |
| 913 | skip_len = subsect_hdr.length; |
| 914 | sect_offset += @sizeOf(pdb.DebugSubsectionHeader); |
| 915 | |
| 916 | switch (subsect_hdr.kind) { |
| 917 | .file_checksums => { |
| 918 | mod.checksum_offset = sect_offset; |
| 919 | break; |
| 920 | }, |
| 921 | else => {}, |
| 922 | } |
| 923 | |
| 924 | if (sect_offset > mod.subsect_info.len) |
| 925 | return error.InvalidDebugInfo; |
| 926 | } |
| 927 | |
| 928 | mod.populated = true; |
| 929 | return mod; |
| 930 | } |
| 931 | |
| 932 | pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream { |
| 933 | if (id >= self.msf.streams.len) return null; |
| 934 | return &self.msf.streams[id]; |
| 935 | } |
| 936 | |
| 937 | pub fn getStream(self: *Pdb, stream: pdb.StreamType) ?*MsfStream { |
| 938 | const id = @backingInt(stream); |
| 939 | return self.getStreamById(id); |
| 940 | } |
| 941 | |
| 942 | /// https://llvm.org/docs/PDB/MsfFile.html |
| 943 | const Msf = struct { |
| 944 | directory: MsfStream, |
| 945 | streams: []MsfStream, |
| 946 | |
| 947 | fn init(gpa: Allocator, file_reader: *File.Reader) !Msf { |
| 948 | const superblock = try file_reader.interface.takeStruct(pdb.SuperBlock, .little); |
| 949 | |
| 950 | if (!std.mem.eql(u8, &superblock.file_magic, pdb.SuperBlock.expect_magic)) |
| 951 | return error.InvalidDebugInfo; |
| 952 | if (superblock.free_block_map_block != 1 and superblock.free_block_map_block != 2) |
| 953 | return error.InvalidDebugInfo; |
| 954 | if (superblock.num_blocks * superblock.block_size != try file_reader.getSize()) |
| 955 | return error.InvalidDebugInfo; |
| 956 | switch (superblock.block_size) { |
| 957 | // llvm only supports 4096 but we can handle any of these values |
| 958 | 512, 1024, 2048, 4096 => {}, |
| 959 | else => return error.InvalidDebugInfo, |
| 960 | } |
| 961 | |
| 962 | const dir_block_count = blockCountFromSize(superblock.num_directory_bytes, superblock.block_size); |
| 963 | if (dir_block_count > superblock.block_size / @sizeOf(u32)) |
| 964 | return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment. |
| 965 | |
| 966 | try file_reader.seekTo(superblock.block_size * superblock.block_map_addr); |
| 967 | const dir_blocks = try gpa.alloc(u32, dir_block_count); |
| 968 | errdefer gpa.free(dir_blocks); |
| 969 | for (dir_blocks) |*b| { |
| 970 | b.* = try file_reader.interface.takeInt(u32, .little); |
| 971 | } |
| 972 | var directory_buffer: [64]u8 = undefined; |
| 973 | var directory = MsfStream.init(superblock.block_size, file_reader, dir_blocks, &directory_buffer); |
| 974 | |
| 975 | const begin = directory.logicalPos(); |
| 976 | const stream_count = try directory.interface.takeInt(u32, .little); |
| 977 | const stream_sizes = try gpa.alloc(u32, stream_count); |
| 978 | defer gpa.free(stream_sizes); |
| 979 | |
| 980 | // Microsoft's implementation uses @as(u32, -1) for inexistent streams. |
| 981 | // These streams are not used, but still participate in the file |
| 982 | // and must be taken into account when resolving stream indices. |
| 983 | const nil_size = 0xFFFFFFFF; |
| 984 | for (stream_sizes) |*s| { |
| 985 | const size = try directory.interface.takeInt(u32, .little); |
| 986 | s.* = if (size == nil_size) 0 else blockCountFromSize(size, superblock.block_size); |
| 987 | } |
| 988 | |
| 989 | const streams = try gpa.alloc(MsfStream, stream_count); |
| 990 | errdefer gpa.free(streams); |
| 991 | |
| 992 | for (streams, stream_sizes) |*stream, size| { |
| 993 | if (size == 0) { |
| 994 | stream.* = .empty; |
| 995 | continue; |
| 996 | } |
| 997 | const blocks = try gpa.alloc(u32, size); |
| 998 | errdefer gpa.free(blocks); |
| 999 | for (blocks) |*block| { |
| 1000 | const block_id = try directory.interface.takeInt(u32, .little); |
| 1001 | // Index 0 is reserved for the superblock. |
| 1002 | // In theory, every page which is `n * block_size + 1` or `n * block_size + 2` |
| 1003 | // is also reserved, for one of the FPMs. However, LLVM has been observed to map |
| 1004 | // these into actual streams, so allow it for compatibility. |
| 1005 | if (block_id == 0 or block_id >= superblock.num_blocks) return error.InvalidBlockIndex; |
| 1006 | block.* = block_id; |
| 1007 | } |
| 1008 | const buffer = try gpa.alloc(u8, 64); |
| 1009 | errdefer gpa.free(buffer); |
| 1010 | stream.* = .init(superblock.block_size, file_reader, blocks, buffer); |
| 1011 | } |
| 1012 | |
| 1013 | const end = directory.logicalPos(); |
| 1014 | if (end - begin != superblock.num_directory_bytes) |
| 1015 | return error.InvalidStreamDirectory; |
| 1016 | |
| 1017 | return .{ |
| 1018 | .directory = directory, |
| 1019 | .streams = streams, |
| 1020 | }; |
| 1021 | } |
| 1022 | |
| 1023 | fn deinit(self: *Msf, gpa: Allocator) void { |
| 1024 | gpa.free(self.directory.blocks); |
| 1025 | for (self.streams) |*stream| { |
| 1026 | gpa.free(stream.interface.buffer); |
| 1027 | gpa.free(stream.blocks); |
| 1028 | } |
| 1029 | gpa.free(self.streams); |
| 1030 | } |
| 1031 | }; |
| 1032 | |
| 1033 | const MsfStream = struct { |
| 1034 | file_reader: *File.Reader, |
| 1035 | next_read_pos: u64, |
| 1036 | blocks: []u32, |
| 1037 | block_size: u32, |
| 1038 | interface: Io.Reader, |
| 1039 | err: ?Error, |
| 1040 | |
| 1041 | const Error = File.Reader.SeekError; |
| 1042 | |
| 1043 | const empty: MsfStream = .{ |
| 1044 | .file_reader = undefined, |
| 1045 | .next_read_pos = 0, |
| 1046 | .blocks = &.{}, |
| 1047 | .block_size = undefined, |
| 1048 | .interface = .ending_instance, |
| 1049 | .err = null, |
| 1050 | }; |
| 1051 | |
| 1052 | fn init(block_size: u32, file_reader: *File.Reader, blocks: []u32, buffer: []u8) MsfStream { |
| 1053 | return .{ |
| 1054 | .file_reader = file_reader, |
| 1055 | .next_read_pos = 0, |
| 1056 | .blocks = blocks, |
| 1057 | .block_size = block_size, |
| 1058 | .interface = .{ |
| 1059 | .vtable = &.{ .stream = stream }, |
| 1060 | .buffer = buffer, |
| 1061 | .seek = 0, |
| 1062 | .end = 0, |
| 1063 | }, |
| 1064 | .err = null, |
| 1065 | }; |
| 1066 | } |
| 1067 | |
| 1068 | fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { |
| 1069 | const ms: *MsfStream = @alignCast(@fieldParentPtr("interface", r)); |
| 1070 | |
| 1071 | var block_id: usize = @intCast(ms.next_read_pos / ms.block_size); |
| 1072 | if (block_id >= ms.blocks.len) return error.EndOfStream; |
| 1073 | var block = ms.blocks[block_id]; |
| 1074 | var offset = ms.next_read_pos % ms.block_size; |
| 1075 | |
| 1076 | ms.file_reader.seekTo(block * ms.block_size + offset) catch |err| { |
| 1077 | ms.err = err; |
| 1078 | return error.ReadFailed; |
| 1079 | }; |
| 1080 | |
| 1081 | var remaining = @backingInt(limit); |
| 1082 | while (remaining != 0) { |
| 1083 | const stream_len: usize = @min(remaining, ms.block_size - offset); |
| 1084 | const n = try ms.file_reader.interface.stream(w, .limited(stream_len)); |
| 1085 | remaining -= n; |
| 1086 | offset += n; |
| 1087 | |
| 1088 | // If we're at the end of a block, go to the next one. |
| 1089 | if (offset == ms.block_size) { |
| 1090 | offset = 0; |
| 1091 | block_id += 1; |
| 1092 | if (block_id >= ms.blocks.len) break; // End of Stream |
| 1093 | block = ms.blocks[block_id]; |
| 1094 | ms.file_reader.seekTo(block * ms.block_size) catch |err| { |
| 1095 | ms.err = err; |
| 1096 | return error.ReadFailed; |
| 1097 | }; |
| 1098 | } |
| 1099 | } |
| 1100 | |
| 1101 | const total = @backingInt(limit) - remaining; |
| 1102 | ms.next_read_pos += total; |
| 1103 | return total; |
| 1104 | } |
| 1105 | |
| 1106 | pub fn logicalPos(ms: *const MsfStream) u64 { |
| 1107 | return ms.next_read_pos - ms.interface.bufferedLen(); |
| 1108 | } |
| 1109 | |
| 1110 | pub fn seekBy(ms: *MsfStream, len: i64) !void { |
| 1111 | ms.next_read_pos = @as(u64, @intCast(@as(i64, @intCast(ms.logicalPos())) + len)); |
| 1112 | if (ms.next_read_pos >= ms.blocks.len * ms.block_size) return error.EOF; |
| 1113 | ms.interface.tossBuffered(); |
| 1114 | } |
| 1115 | |
| 1116 | pub fn seekTo(ms: *MsfStream, len: u64) !void { |
| 1117 | ms.next_read_pos = len; |
| 1118 | if (ms.next_read_pos >= ms.blocks.len * ms.block_size) return error.EOF; |
| 1119 | ms.interface.tossBuffered(); |
| 1120 | } |
| 1121 | |
| 1122 | fn getSize(ms: *const MsfStream) u64 { |
| 1123 | return ms.blocks.len * ms.block_size; |
| 1124 | } |
| 1125 | |
| 1126 | fn getFilePos(ms: *const MsfStream) u64 { |
| 1127 | const pos = ms.logicalPos(); |
| 1128 | const block_id = pos / ms.block_size; |
| 1129 | const block = ms.blocks[block_id]; |
| 1130 | const offset = pos % ms.block_size; |
| 1131 | |
| 1132 | return block * ms.block_size + offset; |
| 1133 | } |
| 1134 | }; |
| 1135 | |
| 1136 | fn readSparseBitVector(reader: *Io.Reader, gpa: Allocator) ![]u32 { |
| 1137 | const num_words = try reader.takeInt(u32, .little); |
| 1138 | var list: std.ArrayList(u32) = .empty; |
| 1139 | defer list.deinit(gpa); |
| 1140 | var word_i: u32 = 0; |
| 1141 | while (word_i != num_words) : (word_i += 1) { |
| 1142 | const word = try reader.takeInt(u32, .little); |
| 1143 | var bit_i: u5 = 0; |
| 1144 | while (true) : (bit_i += 1) { |
| 1145 | if (word & (@as(u32, 1) << bit_i) != 0) { |
| 1146 | try list.append(gpa, word_i * 32 + bit_i); |
| 1147 | } |
| 1148 | if (bit_i == std.math.maxInt(u5)) break; |
| 1149 | } |
| 1150 | } |
| 1151 | return try list.toOwnedSlice(gpa); |
| 1152 | } |
| 1153 | |
| 1154 | fn blockCountFromSize(size: u32, block_size: u32) u32 { |
| 1155 | return (size + block_size - 1) / block_size; |
| 1156 | } |