Files
swift-mirror/stdlib/public/RuntimeModule/PDB/PDBFile.swift
Carl Peto 0d542c57e3 [Windows] [Runtime] Fix import-ability of Runtime module (#90021)
Fixes #90019

Also added in more debug into the backtracer when DEBUG_BACKTRACING is
enabled.
2026-06-24 09:37:16 +01:00

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)
}
}
}