mirror of
https://github.com/apple/swift.git
synced 2026-11-06 00:14:32 +01:00
Fixes #90019 Also added in more debug into the backtracer when DEBUG_BACKTRACING is enabled.
927 lines
28 KiB
Swift
927 lines
28 KiB
Swift
//===--- PDBFile.swift - PDB support for Swift ----------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2025 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Provides a PDBFile struct that understands how to read information from a
|
|
// Microsoft Portable Debug Format file.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import Swift
|
|
|
|
#if os(anyAppleOS)
|
|
internal import Darwin
|
|
#elseif os(Windows)
|
|
internal import WinSDK
|
|
#elseif canImport(Glibc)
|
|
internal import Glibc
|
|
#elseif canImport(Musl)
|
|
internal import Musl
|
|
#endif
|
|
|
|
@_implementationOnly import BacktracingImpl.ImageFormats.PDB
|
|
|
|
extension SC2 {
|
|
static func fromSc(_ sc: SC) -> SC2 {
|
|
return SC2(isect: sc.isect,
|
|
off: sc.off,
|
|
cb: sc.cb,
|
|
dwCharacteristics: sc.dwCharacteristics,
|
|
imod: sc.imod,
|
|
dwDataCrc: sc.dwDataCrc,
|
|
dwRelocCrc: sc.dwRelocCrc,
|
|
isectCoff: 0)
|
|
}
|
|
}
|
|
|
|
@_spi(PDB)
|
|
public class PDBFile {
|
|
var msf: MultiStreamFile
|
|
|
|
public struct Version: RawRepresentable, Comparable, Sendable {
|
|
public let rawValue: UInt32
|
|
|
|
public init(rawValue: UInt32) {
|
|
self.rawValue = rawValue
|
|
}
|
|
|
|
public static let vc2 = Version(rawValue: 19941610)
|
|
public static let vc4 = Version(rawValue: 19950623)
|
|
public static let vc41 = Version(rawValue: 19950814)
|
|
public static let vc50 = Version(rawValue: 19960307)
|
|
public static let vc98 = Version(rawValue: 19970604)
|
|
public static let vc70 = Version(rawValue: 20000404)
|
|
public static let vc70dep = Version(rawValue: 19990604)
|
|
public static let vc80 = Version(rawValue: 20030901)
|
|
public static let vc110 = Version(rawValue: 20091201)
|
|
public static let vc140 = Version(rawValue: 20140508)
|
|
|
|
public static func == (lhs: Self, rhs: Self) -> Bool {
|
|
return lhs.rawValue == rhs.rawValue
|
|
}
|
|
|
|
public static func < (lhs: Self, rhs: Self) -> Bool {
|
|
return lhs.rawValue < rhs.rawValue
|
|
}
|
|
}
|
|
|
|
public struct DebugInfoVersion: RawRepresentable, Comparable, Sendable {
|
|
public let rawValue: UInt32
|
|
|
|
public init(rawValue: UInt32) {
|
|
self.rawValue = rawValue
|
|
}
|
|
|
|
public static let v0 = DebugInfoVersion(rawValue: 0)
|
|
public static let v41 = DebugInfoVersion(rawValue: 930803)
|
|
public static let v50 = DebugInfoVersion(rawValue: 19960307)
|
|
public static let v60 = DebugInfoVersion(rawValue: 19970606)
|
|
public static let v70 = DebugInfoVersion(rawValue: 19990903)
|
|
public static let v110 = DebugInfoVersion(rawValue: 20091201)
|
|
|
|
public static func == (lhs: Self, rhs: Self) -> Bool {
|
|
return lhs.rawValue == rhs.rawValue
|
|
}
|
|
|
|
public static func < (lhs: Self, rhs: Self) -> Bool {
|
|
return lhs.rawValue < rhs.rawValue
|
|
}
|
|
}
|
|
|
|
var streamCount: Int {
|
|
return msf.streams.count
|
|
}
|
|
|
|
public private(set) var version: Version
|
|
public private(set) var signature: [UInt8]
|
|
public private(set) var age: UInt32
|
|
|
|
public private(set) var debugInfoVersion: DebugInfoVersion
|
|
|
|
var globalSymbolIndexStream: Int
|
|
var publicSymbolIndexStream: Int
|
|
var globalSymbolStream: Int
|
|
|
|
var extraStreams: [Int]
|
|
|
|
struct Features: OptionSet, Sendable {
|
|
let rawValue: Int
|
|
|
|
init(rawValue: Int) {
|
|
self.rawValue = rawValue
|
|
}
|
|
|
|
static let idStream = Features(rawValue: 1 << 0)
|
|
static let noTypeMerge = Features(rawValue: 1 << 1)
|
|
static let minimalDbgInfo = Features(rawValue: 1 << 2)
|
|
}
|
|
|
|
private(set) var features: Features
|
|
|
|
public convenience init?(path: String) {
|
|
guard let msf = MultiStreamFile(path: path) else {
|
|
return nil
|
|
}
|
|
self.init(multiStreamFile: msf)
|
|
}
|
|
|
|
var streamNameTable: NameTableNI
|
|
var modules: [Module] = []
|
|
var segments: [PDB_OMF_SEGMAP_DESC] = []
|
|
var sectionContributions: [SC2] = []
|
|
|
|
var sections: [SectionInfo] = []
|
|
var functions: [FunctionInfo] = []
|
|
|
|
init?(multiStreamFile msf: MultiStreamFile) {
|
|
self.msf = msf
|
|
|
|
guard var headerStream = msf.open(stream: 1) else {
|
|
return nil
|
|
}
|
|
|
|
do {
|
|
let pdb = try headerStream.read(as: PDB_STREAM.self)
|
|
|
|
version = Version(rawValue: pdb.impv)
|
|
|
|
if version < .vc4 || version > .vc140 {
|
|
// Unsupported version
|
|
return nil
|
|
}
|
|
|
|
// Version 7 and above have a GUID; we represent that here as a byte array
|
|
if version >= .vc70 {
|
|
signature = try headerStream.read(as: UInt8.self, count: 16)
|
|
} else {
|
|
signature = [ UInt8(truncatingIfNeeded: pdb.sig),
|
|
UInt8(truncatingIfNeeded: pdb.sig >> 8),
|
|
UInt8(truncatingIfNeeded: pdb.sig >> 16),
|
|
UInt8(truncatingIfNeeded: pdb.sig >> 24) ]
|
|
}
|
|
|
|
// Read the name table
|
|
guard let streamNameTable = try NameTableNI.load(from: &headerStream) else {
|
|
return nil
|
|
}
|
|
self.streamNameTable = streamNameTable
|
|
|
|
// Read feature flags
|
|
features = []
|
|
while !headerStream.atEnd {
|
|
let sig = try headerStream.read(as: UInt32.self)
|
|
|
|
if sig == Version.vc110.rawValue {
|
|
features.insert(.idStream)
|
|
break
|
|
}
|
|
|
|
if sig == Version.vc140.rawValue {
|
|
features.insert(.idStream)
|
|
} else if sig == 0x4d544f4e { // NOTM
|
|
features.insert(.noTypeMerge)
|
|
} else if sig == 0x494e494d { // MINI
|
|
features.insert(.minimalDbgInfo)
|
|
}
|
|
}
|
|
} catch {
|
|
return nil
|
|
}
|
|
|
|
// Now read the debug info stream
|
|
guard var debugInfoStream = msf.open(stream: 3) else {
|
|
return nil
|
|
}
|
|
|
|
do {
|
|
let sig = try debugInfoStream.pread(from: 0, as: UInt32.self)
|
|
|
|
let dbiHdr: PDB_NEW_DBI_HDR
|
|
if sig != 0xffffffff {
|
|
// Convert the old header to the new format
|
|
let oldDbiHdr = try debugInfoStream.read(as: PDB_DBI_HDR.self)
|
|
|
|
dbiHdr = PDB_NEW_DBI_HDR(
|
|
verSignature: 0xffffffff,
|
|
verHdr: DebugInfoVersion.v0.rawValue,
|
|
age: 1,
|
|
snGSSyms: oldDbiHdr.snGSSyms,
|
|
usVerAll: 0,
|
|
snPSSyms: oldDbiHdr.snPSSyms,
|
|
usVerPdbDllBuild: 0,
|
|
snSymRecs: oldDbiHdr.snSymRecs,
|
|
usVerPdbDllRBld: 0,
|
|
cbGpModi: oldDbiHdr.cbGpModi,
|
|
cbSC: oldDbiHdr.cbSC,
|
|
cbSecMap: oldDbiHdr.cbSecMap,
|
|
cbFileInfo: oldDbiHdr.cbFileInfo,
|
|
cbTSMap: 0,
|
|
iMFC: 0,
|
|
cbDbgHdr: 0,
|
|
cbECInfo: 0,
|
|
flags: 0,
|
|
wMachine: 0,
|
|
rgulReserved: (0,)
|
|
)
|
|
} else {
|
|
dbiHdr = try debugInfoStream.read(as: PDB_NEW_DBI_HDR.self)
|
|
}
|
|
|
|
age = dbiHdr.age
|
|
|
|
if dbiHdr.cbDbgHdr > 0 {
|
|
let numStreams = Int(dbiHdr.cbDbgHdr) / MemoryLayout<SN>.stride
|
|
let offset = debugInfoStream.size - Int(dbiHdr.cbDbgHdr)
|
|
|
|
extraStreams = (try debugInfoStream.pread(from: offset,
|
|
as: Int16.self,
|
|
count: numStreams)).map {
|
|
Int($0)
|
|
}
|
|
} else {
|
|
extraStreams = []
|
|
}
|
|
|
|
debugInfoVersion = DebugInfoVersion(rawValue: dbiHdr.verHdr)
|
|
globalSymbolIndexStream = Int(dbiHdr.snGSSyms)
|
|
publicSymbolIndexStream = Int(dbiHdr.snPSSyms)
|
|
globalSymbolStream = Int(dbiHdr.snSymRecs)
|
|
|
|
// Now read the module info substream
|
|
if dbiHdr.cbGpModi > 0 {
|
|
let end = debugInfoStream.offset + Int(dbiHdr.cbGpModi)
|
|
if debugInfoVersion < .v60 {
|
|
while debugInfoStream.offset < end
|
|
&& end - debugInfoStream.offset >= MemoryLayout<MODI50>.size {
|
|
let modi = try debugInfoStream.read(as: MODI50.self)
|
|
let szModule = try debugInfoStream.readString()
|
|
let szObjFile = try debugInfoStream.readString()
|
|
|
|
modules.append(
|
|
Module(
|
|
name: szModule,
|
|
object: szObjFile,
|
|
streamNumber: modi.sn == SN_NIL ? nil : Int(modi.sn),
|
|
cbSyms: Int(modi.cbSyms),
|
|
cbLines: Int(modi.cbLines),
|
|
cbFpo: Int(modi.cbFpo),
|
|
cbC13Lines: 0
|
|
)
|
|
)
|
|
|
|
_ = debugInfoStream.align(toMultiple: 4)
|
|
}
|
|
} else {
|
|
while debugInfoStream.offset < end
|
|
&& end - debugInfoStream.offset >= MemoryLayout<MODI60>.size {
|
|
let modi = try debugInfoStream.read(as: MODI60.self)
|
|
let szModule = try debugInfoStream.readString()
|
|
let szObjFile = try debugInfoStream.readString()
|
|
|
|
modules.append(
|
|
Module(
|
|
name: szModule,
|
|
object: szObjFile,
|
|
streamNumber: modi.sn == SN_NIL ? nil : Int(modi.sn),
|
|
cbSyms: Int(modi.cbSyms),
|
|
cbLines: Int(modi.cbLines),
|
|
cbFpo: 0,
|
|
cbC13Lines: Int(modi.cbC13Lines)
|
|
)
|
|
)
|
|
|
|
_ = debugInfoStream.align(toMultiple: 4)
|
|
}
|
|
}
|
|
_ = debugInfoStream.seek(offset: end)
|
|
}
|
|
|
|
// Read the section contribution substream
|
|
if dbiHdr.cbSC > 0 {
|
|
let version = try debugInfoStream.read(as: UInt32.self)
|
|
|
|
let DBISCImpv = UInt32(0xeffe0000) &+ 19970605
|
|
let DBISCImpv2 = UInt32(0xeffe0000) &+ 20140516
|
|
|
|
if version == DBISCImpv {
|
|
let count = (Int(dbiHdr.cbSC) - 4) / MemoryLayout<SC>.stride
|
|
sectionContributions = (try debugInfoStream.read(as: SC.self,
|
|
count: count))
|
|
.map { SC2.fromSc($0) }
|
|
} else if version == DBISCImpv2 {
|
|
let count = (Int(dbiHdr.cbSC) - 4) / MemoryLayout<SC2>.stride
|
|
sectionContributions = try debugInfoStream.read(as: SC2.self,
|
|
count: count)
|
|
} else {
|
|
_ = debugInfoStream.seek(offset: Int(dbiHdr.cbSC) - 4,
|
|
relativeTo: .current)
|
|
}
|
|
}
|
|
|
|
// Read the segment map substream
|
|
segments = []
|
|
if dbiHdr.cbSecMap > 0 {
|
|
let header = try debugInfoStream.read(as: PDB_OMF_SEGMAP.self)
|
|
|
|
segments = try debugInfoStream.read(as: PDB_OMF_SEGMAP_DESC.self,
|
|
count: Int(header.cSeg))
|
|
}
|
|
|
|
// Read the file info substream
|
|
if dbiHdr.cbFileInfo > 0 {
|
|
let moduleCount = try debugInfoStream.read(as: IMOD.self)
|
|
if moduleCount != modules.count {
|
|
return nil
|
|
}
|
|
|
|
// Skip 16-bit count
|
|
_ = debugInfoStream.seek(offset: 2, relativeTo: .current)
|
|
|
|
// Skip the first 16-bit array
|
|
_ = debugInfoStream.seek(offset: 2 * modules.count,
|
|
relativeTo: .current)
|
|
|
|
// Read the cref array
|
|
let crefs = try debugInfoStream.read(as: UInt16.self,
|
|
count: modules.count)
|
|
|
|
// Compute the total of the crefs array
|
|
let crefSum = crefs.map { Int($0) }.reduce(0, +)
|
|
|
|
// Read the character offset array
|
|
let ichs = try debugInfoStream.read(as: ICH.self, count: crefSum)
|
|
|
|
let chbase = debugInfoStream.offset
|
|
|
|
// Now extract all the strings
|
|
var crefsDone = 0
|
|
for ndx in 0..<modules.count {
|
|
for nfile in 0..<crefs[ndx] {
|
|
let iref = crefsDone + Int(nfile)
|
|
let ich = Int(ichs[iref])
|
|
let filename = try debugInfoStream.preadString(from: chbase + ich)
|
|
|
|
modules[ndx].files.append(filename)
|
|
}
|
|
crefsDone += Int(crefs[ndx])
|
|
}
|
|
}
|
|
} catch {
|
|
print("Failed to load debug info: \(error)")
|
|
return nil
|
|
}
|
|
|
|
// Read the section header stream
|
|
guard var sectionStream = open(stream: .sectionHeader) else {
|
|
return nil
|
|
}
|
|
|
|
do {
|
|
let sectionCount = sectionStream.size
|
|
/ MemoryLayout<PDB_IMAGE_SECTION_HEADER>.stride
|
|
|
|
let sectionArray = try sectionStream.read(as: PDB_IMAGE_SECTION_HEADER.self,
|
|
count: sectionCount)
|
|
for section in sectionArray {
|
|
let name = withUnsafeBytes(of: section.Name) { bytes in
|
|
if let firstNul = bytes.firstIndex(of: 0) {
|
|
return String(decoding: bytes[0..<firstNul], as: UTF8.self)
|
|
} else {
|
|
return String(decoding: bytes, as: UTF8.self)
|
|
}
|
|
}
|
|
sections.append(
|
|
SectionInfo(
|
|
name: name,
|
|
virtualAddress: section.VirtualAddress,
|
|
size: section.VirtualSize,
|
|
characteristics:
|
|
SectionCharacteristics(rawValue: section.Characteristics))
|
|
)
|
|
}
|
|
} catch {
|
|
return nil
|
|
}
|
|
|
|
// We scan the main symbol stream, because that contains the raw names
|
|
// of the symbols; the module streams only contain "demangled" versions
|
|
// which in the case of Swift don't actually match what we'd normally do.
|
|
var rawNames : [UInt32:String] = [:]
|
|
if var symbols = open(stream: .symbolRecords) {
|
|
do {
|
|
while !symbols.atEnd {
|
|
let pos = symbols.offset
|
|
|
|
let length = try symbols.read(as: UInt16.self)
|
|
if length == 0 {
|
|
break
|
|
}
|
|
|
|
let type = try symbols.read(as: UInt16.self)
|
|
|
|
if type == S_PUB32 {
|
|
let info = try symbols.read(as: PDB_CV_PUBSYM32.self)
|
|
let section = Int(info.seg) - 1
|
|
|
|
if section >= 0 && section < sections.count {
|
|
let rawName = try symbols.readString()
|
|
let address = sections[Int(info.seg) - 1].virtualAddress + info.off
|
|
|
|
rawNames[address] = rawName
|
|
|
|
functions.append(FunctionInfo(rawName: rawName,
|
|
name: rawName,
|
|
address: address))
|
|
}
|
|
}
|
|
|
|
_ = symbols.seek(offset: pos + Int(length) + 2)
|
|
}
|
|
} catch {
|
|
print(error)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
do {
|
|
// Read the individual module streams
|
|
for (ndx, module) in modules.enumerated() {
|
|
guard let streamNumber = module.streamNumber else {
|
|
continue
|
|
}
|
|
|
|
guard var moduleStream = open(stream: streamNumber) else {
|
|
return nil
|
|
}
|
|
|
|
// Read the signature
|
|
let signature = try moduleStream.read(as: UInt32.self)
|
|
|
|
// Ignore anything that isn't C13 format
|
|
if signature != CV_SIGNATURE_C13 {
|
|
continue
|
|
}
|
|
|
|
// Scan the symbol records
|
|
while moduleStream.offset < module.cbSyms {
|
|
let pos = moduleStream.offset
|
|
|
|
let length = try moduleStream.read(as: UInt16.self)
|
|
if length == 0 {
|
|
break
|
|
}
|
|
|
|
let type = try moduleStream.read(as: UInt16.self)
|
|
|
|
if type == S_LPROC32 || type == S_GPROC32
|
|
|| type == S_LPROC32_ID || type == S_GPROC32_ID {
|
|
let info = try moduleStream.read(as: PDB_CV_PROCSYM32.self)
|
|
|
|
let name = try moduleStream.readString()
|
|
let address = sections[Int(info.seg) - 1].virtualAddress + info.off
|
|
let scope: FunctionInfo.Scope
|
|
= (type == S_LPROC32 || type == S_LPROC32_ID) ? .local : .global
|
|
|
|
functions.append(FunctionInfo(rawName: rawNames[address],
|
|
name: name,
|
|
address: address,
|
|
length: info.len,
|
|
scope: scope,
|
|
moduleIndex: ndx))
|
|
}
|
|
|
|
_ = moduleStream.seek(offset: pos + Int(length) + 2)
|
|
}
|
|
}
|
|
} catch {
|
|
print(error)
|
|
return nil
|
|
}
|
|
|
|
functions.sort { $0.address < $1.address
|
|
|| ($0.address == $1.address
|
|
&& (($0.scope != nil && $1.scope == nil)
|
|
|| $0.name < $1.name)) }
|
|
|
|
// Remove any duplicates
|
|
var read = 0, write = 0
|
|
while read < functions.count {
|
|
let address = functions[read].address
|
|
functions[write] = functions[read]
|
|
read += 1
|
|
write += 1
|
|
while read < functions.count && functions[read].address == address {
|
|
read += 1
|
|
}
|
|
}
|
|
functions.removeLast(functions.count - write)
|
|
}
|
|
|
|
enum StreamId {
|
|
case pdbHeader
|
|
case typeInfo
|
|
case debugInfo
|
|
case ipiHash
|
|
case module(Int)
|
|
case globalSymbolHash
|
|
case publicSymbolHash
|
|
case symbolRecords
|
|
// case typeHash
|
|
case fpo
|
|
case exception
|
|
case fixup
|
|
case omapToSource
|
|
case omapFromSource
|
|
case sectionHeader
|
|
case tokenRidMap
|
|
case xdata
|
|
case pdata
|
|
case newFpo
|
|
case sectionHeaderOrig
|
|
}
|
|
|
|
func streamNumber(from id: StreamId) -> Int? {
|
|
switch id {
|
|
case .pdbHeader: return 1
|
|
case .typeInfo: return 2
|
|
case .debugInfo: return 3
|
|
case .ipiHash: return 4
|
|
case .module(let n): return modules[n].streamNumber
|
|
case .globalSymbolHash: return globalSymbolIndexStream
|
|
case .publicSymbolHash: return publicSymbolIndexStream
|
|
case .symbolRecords: return globalSymbolStream
|
|
// case .typeHash: return ??
|
|
case .fpo:
|
|
return has(stream: id) ? extraStreams[0] : nil
|
|
case .exception:
|
|
return has(stream: id) ? extraStreams[1] : nil
|
|
case .fixup:
|
|
return has(stream: id) ? extraStreams[2] : nil
|
|
case .omapToSource:
|
|
return has(stream: id) ? extraStreams[3] : nil
|
|
case .omapFromSource:
|
|
return has(stream: id) ? extraStreams[4] : nil
|
|
case .sectionHeader:
|
|
return has(stream: id) ? extraStreams[5] : nil
|
|
case .tokenRidMap:
|
|
return has(stream: id) ? extraStreams[6] : nil
|
|
case .xdata:
|
|
return has(stream: id) ? extraStreams[7] : nil
|
|
case .pdata:
|
|
return has(stream: id) ? extraStreams[8] : nil
|
|
case .newFpo:
|
|
return has(stream: id) ? extraStreams[9] : nil
|
|
case .sectionHeaderOrig:
|
|
return has(stream: id) ? extraStreams[10] : nil
|
|
}
|
|
}
|
|
|
|
func has(stream: StreamId) -> Bool {
|
|
switch stream {
|
|
case .pdbHeader, .typeInfo, .debugInfo, .ipiHash: return true
|
|
case .module(let n): return n >= 0 && n < modules.count
|
|
case .globalSymbolHash: return globalSymbolIndexStream > 0
|
|
case .publicSymbolHash: return publicSymbolIndexStream > 0
|
|
case .symbolRecords: return globalSymbolStream > 0
|
|
case .fpo: return extraStreams.count > 1 && extraStreams[0] > 0
|
|
case .exception: return extraStreams.count > 2 && extraStreams[1] > 0
|
|
case .fixup: return extraStreams.count > 3 && extraStreams[2] > 0
|
|
case .omapToSource: return extraStreams.count > 4 && extraStreams[3] > 0
|
|
case .omapFromSource: return extraStreams.count > 5 && extraStreams[4] > 0
|
|
case .sectionHeader: return extraStreams.count > 6 && extraStreams[5] > 0
|
|
case .tokenRidMap: return extraStreams.count > 7 && extraStreams[6] > 0
|
|
case .xdata: return extraStreams.count > 8 && extraStreams[7] > 0
|
|
case .pdata: return extraStreams.count > 9 && extraStreams[8] > 0
|
|
case .newFpo: return extraStreams.count > 10 && extraStreams[9] > 0
|
|
case .sectionHeaderOrig:
|
|
return extraStreams.count > 11 && extraStreams[10] > 0
|
|
}
|
|
}
|
|
|
|
func open(stream: Int) -> MultiStreamFile.Stream? {
|
|
return msf.open(stream: stream)
|
|
}
|
|
|
|
func open(stream: StreamId) -> MultiStreamFile.Stream? {
|
|
guard let stream = streamNumber(from: stream) else {
|
|
return nil
|
|
}
|
|
return msf.open(stream: stream)
|
|
}
|
|
|
|
func open(stream: String) -> MultiStreamFile.Stream? {
|
|
guard let streamNumber = streamNameTable[stream] else {
|
|
return nil
|
|
}
|
|
return msf.open(stream: streamNumber)
|
|
}
|
|
|
|
func dump(stream: Int) throws {
|
|
try msf.dump(stream: stream)
|
|
}
|
|
|
|
func dump(stream: String) throws {
|
|
guard let streamNumber = streamNameTable[stream] else {
|
|
return
|
|
}
|
|
try msf.dump(stream: streamNumber)
|
|
}
|
|
|
|
func dump(stream: StreamId) throws {
|
|
try msf.dump(stream: streamNumber(from: stream)!)
|
|
}
|
|
|
|
func forEachDebugSection<R>(
|
|
ofType wantedType: UInt32,
|
|
in module: Module,
|
|
body: (_ stream: inout MultiStreamFile.Stream, _ length: UInt32) -> R?
|
|
) -> R? {
|
|
guard let streamNumber = module.streamNumber,
|
|
var moduleStream = open(stream: streamNumber) else {
|
|
return nil
|
|
}
|
|
|
|
// Find the sections
|
|
_ = moduleStream.seek(offset: module.cbSyms + module.cbLines)
|
|
|
|
do {
|
|
while !moduleStream.atEnd {
|
|
let type = try moduleStream.read(as: UInt32.self)
|
|
|
|
if type == 0 {
|
|
break
|
|
}
|
|
|
|
let length = try moduleStream.read(as: UInt32.self)
|
|
let next = moduleStream.offset + Int(length)
|
|
|
|
if type == wantedType {
|
|
if let result = body(&moduleStream, length) {
|
|
return result
|
|
}
|
|
}
|
|
|
|
_ = moduleStream.seek(offset: next)
|
|
}
|
|
} catch {
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func lookup(file offset: UInt32, in module: Module) -> String? {
|
|
var fileId: Int? = nil
|
|
|
|
let ok = (forEachDebugSection(ofType: UInt32(DEBUG_S_FILECHKSMS),
|
|
in: module) {
|
|
(stream: inout MultiStreamFile.Stream, length: UInt32) -> Bool? in
|
|
|
|
if offset + 5 >= length {
|
|
return false
|
|
}
|
|
|
|
_ = stream.seek(offset: Int(offset), relativeTo: .current)
|
|
|
|
do {
|
|
fileId = Int(try stream.read(as: UInt32.self))
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}) ?? false
|
|
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
guard let fileId else {
|
|
return nil
|
|
}
|
|
|
|
// Read directly from the name table (no need to load the name table
|
|
// into memory this way).
|
|
do {
|
|
guard let nameStreamIndex = streamNameTable["/names"] else {
|
|
return nil
|
|
}
|
|
guard var stream = msf.open(stream: nameStreamIndex) else {
|
|
return nil
|
|
}
|
|
let vhdr = try stream.read(as: PDB_NMT_VHDR.self)
|
|
if vhdr.ulHdr != 0xeffeeffe {
|
|
// No version header
|
|
_ = stream.seek(offset: 0)
|
|
}
|
|
|
|
let size = try stream.read(as: CB.self)
|
|
|
|
if fileId >= size {
|
|
return nil
|
|
}
|
|
|
|
_ = stream.seek(offset: fileId, relativeTo: .current)
|
|
|
|
return try stream.readString()
|
|
} catch {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public struct SourceLocation: CustomStringConvertible, Sendable {
|
|
public struct Flags: OptionSet, Sendable {
|
|
public var rawValue: Int
|
|
|
|
public init(rawValue: Int) { self.rawValue = rawValue; }
|
|
|
|
public static let noStepOnto = Flags(rawValue: 1 << 0)
|
|
public static let noStepInto = Flags(rawValue: 1 << 1)
|
|
}
|
|
|
|
public var filename: String
|
|
public var lineRange: Range<Int>?
|
|
public var columnRange: Range<Int>?
|
|
public var flags: Flags
|
|
|
|
public var description: String {
|
|
var formatted = filename
|
|
|
|
if let lineRange {
|
|
let firstLine = lineRange.lowerBound
|
|
let lastLine = lineRange.upperBound
|
|
if firstLine == lastLine {
|
|
formatted += ":\(firstLine)"
|
|
} else {
|
|
formatted += ":\(firstLine)-\(lastLine)"
|
|
}
|
|
|
|
if let columnRange {
|
|
let firstCol = columnRange.lowerBound
|
|
let lastCol = columnRange.upperBound
|
|
if firstCol == lastCol {
|
|
formatted += ":\(firstCol)"
|
|
} else {
|
|
formatted += ":\(firstCol)-\(lastCol)"
|
|
}
|
|
}
|
|
}
|
|
|
|
if flags.contains(.noStepOnto) {
|
|
formatted += " [no-step-onto]"
|
|
}
|
|
if flags.contains(.noStepInto) {
|
|
formatted += " [no-step-into]"
|
|
}
|
|
|
|
return formatted
|
|
}
|
|
}
|
|
|
|
func lookup(
|
|
address: UInt32, in module: Module
|
|
) -> SourceLocation? {
|
|
if module.cbC13Lines == 0 {
|
|
return nil
|
|
}
|
|
|
|
return forEachDebugSection(ofType: UInt32(DEBUG_S_LINES),
|
|
in: module) {
|
|
(stream: inout MultiStreamFile.Stream, length: UInt32) -> SourceLocation? in
|
|
|
|
do {
|
|
let end = stream.offset + Int(length)
|
|
let header = try stream.read(as: PDB_CV_LINE_HEADER.self)
|
|
let sectionBase = sections[Int(header.segCon) - 1].virtualAddress
|
|
let baseAddress = header.offCon + sectionBase
|
|
if address < baseAddress {
|
|
return nil
|
|
}
|
|
let offset = address - baseAddress
|
|
|
|
while stream.offset < end {
|
|
let block = try stream.read(as: PDB_CV_LINE_BLOCK_HEADER.self)
|
|
let count = Int(block.nLines)
|
|
let lines = try stream.read(as: PDB_CV_LINE.self, count: count)
|
|
var filename: String? = nil
|
|
var lineRange: Range<Int>? = nil
|
|
var columnRange: Range<Int>? = nil
|
|
var lineIndex: Int? = nil
|
|
var flags: SourceLocation.Flags = []
|
|
|
|
for (ndx, line) in lines.enumerated() {
|
|
if offset == line.offset
|
|
|| (offset > line.offset
|
|
&& ndx + 1 < lines.count
|
|
&& offset < lines[ndx + 1].offset) {
|
|
lineIndex = ndx
|
|
filename = lookup(file: block.offFile, in: module)!
|
|
let start = Int(pdb_line_start(line.lineInfo))
|
|
let end = start + Int(pdb_line_delta(line.lineInfo))
|
|
if start == 0xfeefee {
|
|
flags = [.noStepOnto]
|
|
} else if start == 0xf00f00 {
|
|
flags = [.noStepInto]
|
|
} else {
|
|
lineRange = start..<end
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
guard let lineIndex else {
|
|
continue
|
|
}
|
|
|
|
if (header.flags & UInt16(CV_LINES_HAVE_COLUMNS)) != 0 {
|
|
let columns = try stream.read(as: PDB_CV_COLUMN.self, count: count)
|
|
let start = Int(columns[lineIndex].offColumnStart)
|
|
let end = Int(columns[lineIndex].offColumnEnd)
|
|
columnRange = start..<end
|
|
}
|
|
|
|
return SourceLocation(filename: filename!,
|
|
lineRange: lineRange,
|
|
columnRange: columnRange,
|
|
flags: flags)
|
|
}
|
|
|
|
return nil
|
|
} catch {
|
|
print("Error \(error) during lookup")
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct SymbolLookup: CustomStringConvertible {
|
|
public var rawName: String?
|
|
public var name: String
|
|
public var offset: UInt32
|
|
public var sourceLocation: SourceLocation?
|
|
|
|
public var description: String {
|
|
var formatted: String
|
|
|
|
if offset == 0 {
|
|
formatted = name
|
|
} else {
|
|
formatted = "\(name) + \(offset)"
|
|
}
|
|
|
|
if let sourceLocation {
|
|
formatted += " (\(sourceLocation))"
|
|
}
|
|
|
|
return formatted
|
|
}
|
|
}
|
|
|
|
public func lookupFunction(for address: UInt32) -> Int? {
|
|
var min = 0, max = functions.count
|
|
while min < max {
|
|
let mid = min + (max - min) / 2
|
|
|
|
if address >= functions[mid].address
|
|
&& (mid + 1 == functions.count
|
|
|| address < functions[mid + 1].address) {
|
|
return mid
|
|
} else if address < functions[mid].address {
|
|
max = mid
|
|
} else {
|
|
min = mid + 1
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public func lookup(address: UInt32) -> SymbolLookup? {
|
|
guard let funcIndex = lookupFunction(for: address) else {
|
|
return nil
|
|
}
|
|
|
|
let function = functions[funcIndex]
|
|
|
|
if let moduleIndex = function.moduleIndex {
|
|
let module = modules[moduleIndex]
|
|
let sourceLocation = lookup(address: address, in: module)
|
|
|
|
return SymbolLookup(rawName: function.rawName,
|
|
name: function.name,
|
|
offset: address - function.address,
|
|
sourceLocation: sourceLocation)
|
|
} else {
|
|
return SymbolLookup(rawName: function.rawName,
|
|
name: function.name,
|
|
offset: address - function.address)
|
|
}
|
|
}
|
|
}
|