Files
luma/Sources/LumaCore/Patterns/PatternDisassembly.swift
Ole André Vadla Ravnås ad61503b47 core: Decode pattern visualizers
Interpret each of ImHex's visualizers from its arguments, and
disassemble through r2.
2026-10-01 20:49:11 +02:00

97 lines
3.3 KiB
Swift

import Foundation
import SwiftyR2
extension PatternVisualization.Disassembly {
public struct Instruction: Identifiable, Sendable {
public var id: UInt64 { address }
public let address: UInt64
public let bytes: String
public let text: String
}
public func instructions() async throws -> [Instruction] {
guard let target = R2Target(architecture: architecture) else {
throw PatternVisualizationError("Unknown architecture \"\(architecture)\".")
}
let r2 = await R2Core.create()
await r2.config.set("log.quiet", bool: true)
await r2.config.set("asm.arch", string: target.arch)
await r2.config.set("asm.bits", int: target.bits)
await r2.config.set("cfg.bigendian", bool: target.isBigEndian)
let origin = "0x" + String(address, radix: 16)
await r2.cmd("o malloc://\(data.count) \(origin)")
await r2.cmd("wx \(data.map { String(format: "%02x", $0) }.joined()) @ \(origin)")
let listing = await r2.cmd("pDj \(data.count) @ \(origin)").output ?? "[]"
return try JSONDecoder().decode([R2Instruction].self, from: Data(listing.utf8)).map {
Instruction(address: $0.addr, bytes: $0.bytes.hexPairs, text: $0.disasm)
}
}
}
private struct R2Instruction: Decodable {
let addr: UInt64
let bytes: String
let disasm: String
}
private struct R2Target {
let arch: String
let bits: Int
let isBigEndian: Bool
init?(architecture: String) {
let parts = architecture.split(separator: ";", maxSplits: 1).map { $0.trimmingCharacters(in: .whitespaces).lowercased() }
var name = parts.first ?? ""
let options = Set((parts.count > 1 ? parts[1] : "").split(whereSeparator: { $0 == " " || $0 == "," }).map(String.init))
isBigEndian = name.hasSuffix("be") || name.hasSuffix("eb")
if isBigEndian || name.hasSuffix("le") || name.hasSuffix("el") {
name.removeLast(2)
}
guard let (arch, defaultBits) = Self.known[name] else { return nil }
self.arch = arch
if options.contains("thumb") {
bits = 16
} else if let explicit = [16, 32, 64].first(where: { options.contains("\($0)bit") }) {
bits = explicit
} else {
bits = defaultBits
}
}
private static let known: [String: (String, Int)] = [
"arm": ("arm", 32),
"thumb": ("arm", 16),
"aarch64": ("arm", 64),
"arm64": ("arm", 64),
"mips": ("mips", 32),
"x86": ("x86", 32),
"x86_64": ("x86", 64),
"x64": ("x86", 64),
"ppc": ("ppc", 32),
"powerpc": ("ppc", 32),
"sparc": ("sparc", 32),
"sysz": ("sysz", 64),
"xcore": ("xcore", 32),
"m68k": ("m68k", 32),
"m680x": ("m680x", 8),
"tms320c64x": ("tms320", 32),
"evm": ("evm", 8),
"wasm": ("wasm", 32),
"riscv": ("riscv", 32),
"mos65xx": ("6502", 8),
"bpf": ("bpf", 64),
"sh": ("sh", 32),
"superh": ("sh", 32),
"tricore": ("tricore", 32),
]
}
extension String {
fileprivate var hexPairs: String {
let digits = Array(uppercased())
return stride(from: 0, to: digits.count, by: 2).map { String(digits[$0..<min($0 + 2, digits.count)]) }.joined(separator: " ")
}
}