Modules-view actions built names via anchor.displayString, yielding module!symbol for hooks and module+offset for disassembly/memory insights. Route both through the symbol the tracer search already uses. Add AddressAnchor.symbolName; default tracer hook names to it. Thread the context anchor hint into getOrCreateInsight so insights opened on exports and imports inherit the symbol name. Drop the unused title parameter from ModuleDetailView.
189 lines
5.8 KiB
Swift
189 lines
5.8 KiB
Swift
import Foundation
|
|
|
|
public typealias JSONObject = [String: Any]
|
|
|
|
public enum AddressAnchor: Codable, Hashable, Sendable {
|
|
case absolute(UInt64)
|
|
case moduleOffset(name: String, offset: UInt64)
|
|
case moduleExport(name: String, export: String)
|
|
case objcMethod(selector: String)
|
|
case swiftFunc(module: String, function: String)
|
|
case debugSymbol(name: String)
|
|
case javaMethod(className: String, methodName: String)
|
|
|
|
public var moduleGroupName: String {
|
|
switch self {
|
|
case .moduleOffset(let name, _),
|
|
.moduleExport(let name, _):
|
|
return name
|
|
case .swiftFunc(let module, _):
|
|
return module
|
|
case .objcMethod:
|
|
return "Objective-C"
|
|
case .javaMethod:
|
|
return "Java"
|
|
case .debugSymbol:
|
|
return "Debug Symbols"
|
|
case .absolute:
|
|
return "Absolute Addresses"
|
|
}
|
|
}
|
|
|
|
public var displayString: String {
|
|
switch self {
|
|
case .absolute(let a):
|
|
return String(format: "0x%llx", a)
|
|
|
|
case .moduleOffset(let name, let offset):
|
|
return "\(moduleBasename(name))+\(String(format: "0x%llx", offset))"
|
|
|
|
case .moduleExport(let name, let export):
|
|
return "\(moduleBasename(name))!\(export)"
|
|
|
|
case .objcMethod(let selector):
|
|
return selector
|
|
|
|
case .swiftFunc(let module, let function):
|
|
return "\(moduleBasename(module))!\(function)"
|
|
|
|
case .debugSymbol(let name):
|
|
return name
|
|
|
|
case .javaMethod(let className, let methodName):
|
|
return "\(className).\(methodName)"
|
|
}
|
|
}
|
|
|
|
public var symbolName: String {
|
|
switch self {
|
|
case .moduleExport(_, let export):
|
|
return export
|
|
case .swiftFunc(_, let function):
|
|
return function
|
|
default:
|
|
return displayString
|
|
}
|
|
}
|
|
|
|
public static func fromJSON(_ object: JSONObject) throws -> AddressAnchor {
|
|
guard let type = object["type"] as? String else {
|
|
throw LumaCoreError.invalidArgument("Anchor missing 'type'")
|
|
}
|
|
switch type {
|
|
case "absolute":
|
|
return .absolute(try parseAnchorAddress(object["address"]))
|
|
case "moduleOffset":
|
|
return .moduleOffset(
|
|
name: try parseAnchorString(object["name"], field: "name"),
|
|
offset: try parseAnchorOffset(object["offset"])
|
|
)
|
|
case "moduleExport":
|
|
return .moduleExport(
|
|
name: try parseAnchorString(object["name"], field: "name"),
|
|
export: try parseAnchorString(object["export"], field: "export")
|
|
)
|
|
case "objcMethod":
|
|
return .objcMethod(selector: try parseAnchorString(object["selector"], field: "selector"))
|
|
case "swiftFunc":
|
|
return .swiftFunc(
|
|
module: try parseAnchorString(object["module"], field: "module"),
|
|
function: try parseAnchorString(object["function"], field: "function")
|
|
)
|
|
case "debugSymbol":
|
|
return .debugSymbol(name: try parseAnchorString(object["name"], field: "name"))
|
|
case "javaMethod":
|
|
return .javaMethod(
|
|
className: try parseAnchorString(object["className"], field: "className"),
|
|
methodName: try parseAnchorString(object["methodName"], field: "methodName")
|
|
)
|
|
default:
|
|
throw LumaCoreError.invalidArgument("Unknown anchor type '\(type)'")
|
|
}
|
|
}
|
|
|
|
public func toJSON() -> JSONObject {
|
|
switch self {
|
|
case .absolute(let a):
|
|
return [
|
|
"type": "absolute",
|
|
"address": a,
|
|
]
|
|
|
|
case .moduleOffset(let name, let offset):
|
|
return [
|
|
"type": "moduleOffset",
|
|
"name": name,
|
|
"offset": offset,
|
|
]
|
|
|
|
case .moduleExport(let name, let export):
|
|
return [
|
|
"type": "moduleExport",
|
|
"name": name,
|
|
"export": export,
|
|
]
|
|
|
|
case .objcMethod(let selector):
|
|
return [
|
|
"type": "objcMethod",
|
|
"selector": selector,
|
|
]
|
|
|
|
case .swiftFunc(let module, let function):
|
|
return [
|
|
"type": "swiftFunc",
|
|
"module": module,
|
|
"function": function,
|
|
]
|
|
|
|
case .debugSymbol(let name):
|
|
return [
|
|
"type": "debugSymbol",
|
|
"name": name,
|
|
]
|
|
|
|
case .javaMethod(let className, let methodName):
|
|
return [
|
|
"type": "javaMethod",
|
|
"className": className,
|
|
"methodName": methodName,
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
private func moduleBasename(_ name: String) -> String {
|
|
guard let slash = name.lastIndex(of: "/") else { return name }
|
|
return String(name[name.index(after: slash)...])
|
|
}
|
|
|
|
private func parseAnchorString(_ value: Any?, field: String) throws -> String {
|
|
guard let s = value as? String else {
|
|
throw LumaCoreError.invalidArgument("Anchor field '\(field)' is not a String")
|
|
}
|
|
return s
|
|
}
|
|
|
|
private func parseAnchorAddress(_ value: Any?) throws -> UInt64 {
|
|
if let u = value as? UInt64 {
|
|
return u
|
|
}
|
|
if let s = value as? String {
|
|
return try parseAgentHexAddress(s)
|
|
}
|
|
throw LumaCoreError.invalidArgument("Anchor 'address' is not a UInt64 or hex String")
|
|
}
|
|
|
|
private func parseAnchorOffset(_ value: Any?) throws -> UInt64 {
|
|
if let u = value as? UInt64 {
|
|
return u
|
|
}
|
|
if let n = value as? Double {
|
|
return UInt64(n)
|
|
}
|
|
if let i = value as? Int {
|
|
return UInt64(i)
|
|
}
|
|
throw LumaCoreError.invalidArgument("Anchor 'offset' is not numeric")
|
|
}
|