Skip to content

Commit

Permalink
Merge pull request #14 from p-x9/feature/closest-symbol-info
Browse files Browse the repository at this point in the history
  • Loading branch information
p-x9 authored Jan 3, 2024
2 parents 4b31a57 + ff1b497 commit aa3901f
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Sources/MachOKit/MachOImage+Symbols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ extension MachOImage.Symbols64 {
nextIndex += 1
}

var symbol = symbols.advanced(by: nextIndex).pointee
let symbol = symbols.advanced(by: nextIndex).pointee
let str = stringBase
.advanced(by: numericCast(symbol.n_un.n_strx))
let address = addressStart + numericCast(symbol.n_value)
Expand Down
21 changes: 21 additions & 0 deletions Sources/MachOKit/MachOImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,24 @@ extension MachOImage {
return nil
}
}

extension MachOImage {
/// Find the symbol closest to the address.
///
/// Behaves almost identically to the `dladdr` function
///
/// - Parameters:
/// - address: Address to find closest symbol.
/// - sectionNumber: Section number to be searched.
/// - Returns: Closest symbol.
public func closestSymbol(
at address: UnsafeRawPointer,
inSection sectionNumber: Int = 0
) -> Symbol? {
let offset = Int(bitPattern: address) - Int(bitPattern: ptr)
return closestSymbol(
at: offset,
inSection: sectionNumber
)
}
}
9 changes: 9 additions & 0 deletions Sources/MachOKit/Model/Symbol/Nlist.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Foundation
public protocol NlistProtocol: LayoutWrapper {
var flags: SymbolFlags? { get }
var symbolDescription: SymbolDescription? { get }
var sectionNumber: Int? { get }
}

public struct Nlist: NlistProtocol {
Expand All @@ -25,6 +26,10 @@ public struct Nlist: NlistProtocol {
public var symbolDescription: SymbolDescription? {
.init(rawValue: numericCast(layout.n_desc))
}

public var sectionNumber: Int? {
layout.n_sect == NO_SECT ? nil : numericCast(layout.n_sect)
}
}

public struct Nlist64: NlistProtocol {
Expand All @@ -39,4 +44,8 @@ public struct Nlist64: NlistProtocol {
public var symbolDescription: SymbolDescription? {
.init(rawValue: numericCast(layout.n_desc))
}

public var sectionNumber: Int? {
layout.n_sect == NO_SECT ? nil : numericCast(layout.n_sect)
}
}
73 changes: 73 additions & 0 deletions Sources/MachOKit/Protocol/MachORepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ public protocol MachORepresentable {
var weakBindingSymbols: [BindingSymbol] { get }
var lazyBindingSymbols: [BindingSymbol] { get }
var rebases: [Rebase] { get }

/// Find the symbol closest to the address at the specified offset.
///
/// Behaves almost identically to the `dladdr` function
///
/// - Parameters:
/// - offset: Offset from start of mach header. (``SymbolProtocol.offset``)
/// - sectionNumber: Section number to be searched.
/// - Returns: Closest symbol.
func closestSymbol(at offset: Int, inSection sectionNumber: Int) -> Symbol?
}

extension MachORepresentable {
Expand Down Expand Up @@ -166,3 +176,66 @@ extension MachORepresentable {
return rebaseOperations.rebases(is64Bit: is64Bit)
}
}

extension MachORepresentable {
public func closestSymbol(
at offset: Int,
inSection sectionNumber: Int = 0
) -> Symbol? {
let symbols = Array(self.symbols)
var bestSymbol: Symbol?

if let dysym = loadCommands.dysymtab {
// find closest match in globals
let globalStart: Int = numericCast(dysym.iextdefsym)
let globalCount: Int = numericCast(dysym.nextdefsym)
for i in globalStart ..< globalStart + globalCount {
let symbol = symbols[i]
let nlist = symbol.nlist
let symbolSectionNumber = symbol.nlist.sectionNumber

guard nlist.flags?.type == .sect,
symbol.offset <= offset,
sectionNumber == 0 || symbolSectionNumber == sectionNumber,
bestSymbol == nil || bestSymbol!.offset < symbol.offset else {
continue
}
bestSymbol = symbol
}

// find closest match in locals
let localStart: Int = numericCast(dysym.ilocalsym)
let localCount: Int = numericCast(dysym.nlocalsym)
for i in localStart ..< localStart + localCount {
let symbol = symbols[i]
let nlist = symbol.nlist
let symbolSectionNumber = symbol.nlist.sectionNumber

guard nlist.flags?.type == .sect,
nlist.flags?.stab == nil,
symbol.offset <= offset,
sectionNumber == 0 || symbolSectionNumber == sectionNumber,
bestSymbol == nil || bestSymbol!.offset < symbol.offset else {
continue
}
bestSymbol = symbol
}
} else {
// find closest match in locals
for symbol in symbols {
let nlist = symbol.nlist
let symbolSectionNumber = symbol.nlist.sectionNumber
guard nlist.flags?.type == .sect,
nlist.flags?.stab == nil,
symbol.offset <= offset,
sectionNumber == 0 || symbolSectionNumber == sectionNumber,
bestSymbol == nil || bestSymbol!.offset < symbol.offset else {
continue
}
bestSymbol = symbol
}
}

return bestSymbol
}
}
24 changes: 24 additions & 0 deletions Tests/MachOKitTests/MachOFilePrintTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,27 @@ extension MachOFilePrintTests {
}
}
}

extension MachOFilePrintTests {
func testClosestSymbol() {
for symbol in machO.symbols {
let offset = symbol.offset + Int.random(in: 0..<100)
guard let best = machO.closestSymbol(
at: offset
) else {
continue
}

let diff = best.offset - offset

print(
offset == best.offset,
symbol.offset == best.offset,
symbol.name == best.name,
symbol.name,
best.name,
diff
)
}
}
}
39 changes: 39 additions & 0 deletions Tests/MachOKitTests/MachOPrintTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,42 @@ extension MachOPrintTests {
}
}
}

extension MachOPrintTests {
func testClosestSymbol() {
for symbol in machO.symbols {
let offset = symbol.offset + Int.random(in: 0..<100)
guard let best = machO.closestSymbol(
at: offset
) else {
continue
}

let diff = best.offset - offset

var info = Dl_info()
let isSucceeded = dladdr(machO.ptr.advanced(by: offset), &info) != 0
if !isSucceeded { print("failed") }

guard let dli_saddr = info.dli_saddr,
let dli_sname = info.dli_sname else {
print("[dladdr is nil]", best.name, diff)
continue
}

XCTAssertEqual(
dli_saddr,
machO.ptr.advanced(by: best.offset)
)

print(
offset == best.offset,
dli_saddr == machO.ptr.advanced(by: offset),
dli_saddr == machO.ptr.advanced(by: best.offset),
strcmp(best.nameC + 1, dli_sname),
best.name,
diff
)
}
}
}

0 comments on commit aa3901f

Please sign in to comment.