mirror of
https://github.com/apple/swift.git
synced 2026-02-27 18:26:24 +01:00
Without scanning the main symbol stream, we get weird names for the Swift functions in the backtrace, because the compiler generates C++-style names for them. We actually want the mangled names, if possible, which only seem to be in the symbol stream, so scan that as well and use it to find mangled names. rdar://168454023
901 lines
27 KiB
Swift
901 lines
27 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(macOS) || os(iOS) || os(tvOS) || os(watchOS)
|
|
internal import Darwin
|
|
#elseif os(Windows)
|
|
internal import WinSDK
|
|
#elseif canImport(Glibc)
|
|
internal import Glibc
|
|
#elseif canImport(Musl)
|
|
internal import Musl
|
|
#endif
|
|
|
|
internal 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
|
|
}
|
|
}
|
|
|
|
_ = 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 }
|
|
}
|
|
|
|
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]
|
|
let module = modules[function.moduleIndex]
|
|
|
|
let sourceLocation = lookup(address: address, in: module)
|
|
|
|
return SymbolLookup(rawName: function.rawName,
|
|
name: function.name,
|
|
offset: address - function.address,
|
|
sourceLocation: sourceLocation)
|
|
}
|
|
}
|