Files
ipsw/pkg/disass/macho.go
blacktop 27e8ec23ad chore: refactor disassembly code to use new instruction representation
- Updated disassembly functions to utilize the new `disassemble.Inst` type instead of `disassemble.Instruction`.
- Modified operand retrieval functions to accommodate the new instruction structure.
- Enhanced error handling and logging for instruction decoding failures.
- Improved JSON output for disassembly to ensure disassembly strings are preserved.
- Refactored various components across the disassembly package, including Mach-O and dyld handling, to streamline instruction processing.
- Added tests to validate the new disassembly behavior and ensure backward compatibility.
2026-03-09 21:13:33 -06:00

907 lines
25 KiB
Go

package disass
import (
"bytes"
"encoding/binary"
"encoding/gob"
"fmt"
"io"
"maps"
"os"
"path/filepath"
"slices"
"strings"
"github.com/apex/log"
"github.com/blacktop/arm64-cgo/disassemble"
"github.com/blacktop/go-macho"
"github.com/blacktop/go-macho/pkg/fixupchains"
"github.com/blacktop/go-macho/pkg/swift"
"github.com/blacktop/go-macho/types/objc"
"github.com/blacktop/ipsw/internal/demangle"
"github.com/blacktop/ipsw/internal/utils"
"github.com/blacktop/ipsw/pkg/symbols"
"github.com/pkg/errors"
)
type MachoDisass struct {
f *macho.File
cfg *Config
tr *Triage
a2s map[uint64]string
sinfo map[uint64]uint64
swiftstrs map[uint64]string
decoder disassemble.Decoder
}
func NewMachoDisass(f *macho.File, cfg *Config) *MachoDisass {
return &MachoDisass{f: f, cfg: cfg, a2s: make(map[uint64]string, 0), swiftstrs: make(map[uint64]string, 0)}
}
func (d MachoDisass) Demangle() bool {
return d.cfg.Demangle
}
func (d MachoDisass) Quite() bool {
return d.cfg.Quiet
}
func (d MachoDisass) Color() bool {
return d.cfg.Color
}
func (d MachoDisass) AsJSON() bool {
return d.cfg.AsJSON
}
func (d MachoDisass) Data() []byte {
return d.cfg.Data
}
func (d MachoDisass) StartAddr() uint64 {
return d.cfg.StartAddress
}
func (d MachoDisass) Middle() uint64 {
return d.cfg.Middle
}
func (d MachoDisass) ReadAddr(addr uint64) (uint64, error) {
ptr, err := d.f.GetPointerAtAddress(addr)
if err != nil {
return 0, err
}
return d.f.SlidePointer(ptr), nil
}
// Triage walks a function and analyzes all immediates
func (d *MachoDisass) Triage() error {
var instrValue uint32
var prevInstr disassemble.Inst
var hasPrev bool
d.tr = &Triage{
Addresses: make(map[uint64]uint64),
Locations: make(map[uint64][]uint64),
}
startAddr := d.StartAddr()
r := bytes.NewReader(d.Data())
// extract all immediates
for {
err := binary.Read(r, binary.LittleEndian, &instrValue)
if err == io.EOF {
break
}
var instruction disassemble.Inst
if err := d.decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
startAddr += uint64(binary.Size(uint32(0)))
continue
}
if IsBranchOp(instruction.Operation) {
for idx := 0; idx < int(instruction.NumOps); idx++ {
op := instruction.Operands[idx]
if op.Class == disassemble.LABEL {
d.tr.Addresses[instruction.Address] = uint64(op.Immediate)
}
}
} else if IsLoadLiteral(&instruction) {
d.tr.Addresses[instruction.Address] = uint64(instruction.Operands[1].Immediate)
} else if hasPrev && prevInstr.Operation == disassemble.ARM64_ADRP &&
(instruction.Operation == disassemble.ARM64_ADD ||
instruction.Operation == disassemble.ARM64_LDR ||
instruction.Operation == disassemble.ARM64_LDRB ||
instruction.Operation == disassemble.ARM64_LDRSW) {
adrpRegister, ok := operandRegister(&prevInstr, 0)
if !ok {
prevInstr = instruction
hasPrev = true
startAddr += uint64(binary.Size(uint32(0)))
continue
}
adrpImm, ok := operandImmediate(&prevInstr, 1)
if !ok {
prevInstr = instruction
hasPrev = true
startAddr += uint64(binary.Size(uint32(0)))
continue
}
if srcRegister, ok := operandRegister(&instruction, 1); ok && adrpRegister == srcRegister {
switch instruction.Operation {
case disassemble.ARM64_LDR, disassemble.ARM64_LDRB, disassemble.ARM64_LDRSW:
if imm, ok := operandImmediate(&instruction, 1); ok {
adrpImm += imm
}
case disassemble.ARM64_ADD:
if imm, ok := operandImmediate(&instruction, 2); ok {
adrpImm += imm
}
}
}
d.tr.Addresses[instruction.Address] = adrpImm
}
prevInstr = instruction
hasPrev = true
startAddr += uint64(binary.Size(uint32(0)))
}
if !d.Quite() {
d.tr.Details = make(map[uint64]AddrDetails)
for addr, imm := range d.tr.Addresses {
if fn, err := d.f.GetFunctionForVMAddr(addr); err == nil {
d.tr.Function = &fn
if d.tr.Function.StartAddr <= imm && imm < d.tr.Function.EndAddr {
d.tr.Locations[imm] = append(d.tr.Locations[imm], addr)
continue
}
}
if c := d.f.FindSectionForVMAddr(imm); c != nil {
d.tr.Details[imm] = AddrDetails{
Segment: c.Seg,
Section: c.Name,
Flags: c.Flags,
}
} else {
ptr, _ := d.f.GetPointerAtAddress(imm)
ptr = d.f.SlidePointer(ptr)
if c := d.f.FindSectionForVMAddr(ptr); c != nil {
d.tr.Details[imm] = AddrDetails{
Segment: c.Seg,
Section: c.Name,
Pointer: ptr,
Flags: c.Flags,
}
}
}
}
}
return nil
}
// FindSwiftStrings walks a function extracts Swift StringObjects/String Structs/Compiler optimized strings
// ref - test/SILOptimizer/character_literals.swift
// ref - stdlib/public/core/StringObject.swift
func (d *MachoDisass) FindSwiftStrings() (out map[uint64]string, err error) {
var instrValue uint32
var prevInstr disassemble.Inst
var hasPrev bool
d.tr = &Triage{
Addresses: make(map[uint64]uint64),
Locations: make(map[uint64][]uint64),
}
startAddr := d.StartAddr()
r := bytes.NewReader(d.Data())
out = make(map[uint64]string)
ss := make([]byte, 16)
buf := bytes.NewBuffer(ss)
strAddr := uint64(0)
reg := disassemble.REG_NONE
regVal := uint64(0)
next := disassemble.REG_NONE
nextVal := uint64(0)
// extract all Swift strings
for {
err := binary.Read(r, binary.LittleEndian, &instrValue)
if err == io.EOF {
break
}
var instruction disassemble.Inst
if err := d.decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
startAddr += uint64(binary.Size(uint32(0)))
continue
}
if instruction.Operation == disassemble.ARM64_MOV {
if dstRegister, ok := operandRegister(&instruction, 0); ok {
if imm, ok := operandImmediate(&instruction, 1); ok {
if reg == disassemble.REG_NONE {
strAddr = instruction.Address
reg = dstRegister
regVal = imm
} else {
if regVal > 0 {
next = dstRegister
nextVal = imm
} else {
strAddr = instruction.Address
reg = dstRegister
regVal = imm
}
}
}
}
} else if hasPrev &&
((prevInstr.Operation == disassemble.ARM64_MOV && instruction.Operation == disassemble.ARM64_MOVK) ||
(prevInstr.Operation == disassemble.ARM64_MOVK && instruction.Operation == disassemble.ARM64_MOVK)) {
if dstRegister, ok := operandRegister(&instruction, 0); ok {
if imm, ok := operandImmediate(&instruction, 1); ok {
if shift, ok := operandShiftValue(&instruction, 1); ok {
if reg == dstRegister {
regVal += imm << shift
} else if next == dstRegister {
nextVal += imm << shift
}
}
}
}
} else {
if regVal > 0 && nextVal > 0 {
discriminator := (nextVal & 0xFF00_0000_0000_0000) >> 56
count := discriminator & 0xF
if count > 0 {
nextVal = nextVal & 0x00FF_FFFF_FFFF_FFFF
buf.Reset()
binary.Write(buf, binary.LittleEndian, regVal)
binary.Write(buf, binary.LittleEndian, nextVal)
if (discriminator & 0xF0) == 0xE0 { // small ascii string
if utils.IsASCII(string(ss[:count])) {
out[strAddr] = string(ss[:count])
ss = ss[:0]
}
} else if (discriminator & 0xF0) == 0xA0 { // small non-ascii string
out[strAddr] = utils.UnicodeSanitize(string(ss[:count]))
ss = ss[:0]
} // TODO: add support for (discriminator & 0xF0) == 0x80 { // large string
}
}
// RESET
strAddr = uint64(0)
reg = disassemble.REG_NONE
regVal = uint64(0)
next = disassemble.REG_NONE
nextVal = uint64(0)
}
prevInstr = instruction
hasPrev = true
startAddr += uint64(binary.Size(uint32(0)))
}
return out, nil
}
// IsFunctionStart checks if address is at a function start and returns symbol name
func (d MachoDisass) IsFunctionStart(addr uint64) (bool, string) {
for _, fn := range d.f.GetFunctions() {
if addr == fn.StartAddr {
if symName, ok := d.a2s[addr]; ok {
display := symbols.FormatSymbol(symName, d.Demangle())
return ok, display
}
return true, fmt.Sprintf("sub_%x", addr)
}
}
return false, ""
}
// IsLocation returns if given address is a local branch location within the disassembled function
func (d MachoDisass) IsLocation(imm uint64) bool {
if _, ok := d.tr.Locations[imm]; ok {
return true
}
return false
}
// IsBranchLocation returns if given address is branch to a location instruction
func (d MachoDisass) IsBranchLocation(addr uint64) (bool, uint64) {
for loc, addrs := range d.tr.Locations {
if slices.Contains(addrs, addr) {
return true, loc
}
}
return false, 0
}
// IsData returns if given address is a data variable address referenced in the disassembled function
func (d MachoDisass) IsData(addr uint64) (bool, *AddrDetails) {
if detail, ok := d.tr.Details[addr]; ok {
if detail.Flags.IsCstringLiterals() {
return true, &detail
}
segment := strings.ToLower(detail.Segment)
if strings.Contains(segment, "data") {
return true, &detail
}
}
return false, nil
}
// IsPointer returns if given address is a pointer to another address
func (d MachoDisass) IsPointer(imm uint64) (bool, *AddrDetails) {
if deet, ok := d.tr.Details[imm]; ok {
if deet.Pointer > 0 {
return true, &deet
}
}
return false, nil
}
// FindSymbol returns symbol from the addr2symbol map for a given virtual address
func (d MachoDisass) FindSymbol(addr uint64) (string, bool) {
if symName, ok := d.a2s[addr]; ok {
return symName, true
}
return "", false
}
func (d MachoDisass) FindSwiftString(addr uint64) (string, bool) {
if str, ok := d.swiftstrs[addr]; ok {
return str, true
}
return "", false
}
// Contains returns true if Triage immediates contains a given address and will return the instruction address
func (d MachoDisass) Contains(address uint64) (bool, uint64) {
for loc, addr := range d.tr.Addresses {
if addr == address {
return true, loc
}
}
return false, 0
}
func (d MachoDisass) GetCString(addr uint64) (string, error) {
return d.f.GetCString(addr)
}
func (d MachoDisass) Analyze() error {
if err := d.parseSymbols(); err != nil {
return fmt.Errorf("failed to parse symbols: %v", err)
}
if err := d.parseImports(); err != nil {
return fmt.Errorf("failed to parse imports: %v", err)
}
if err := d.parseObjC(); err != nil {
return fmt.Errorf("failed to parse objc runtime: %v", err)
}
if err := d.parseSwift(); err != nil {
return fmt.Errorf("failed to parse swift: %v", err)
}
if err := d.parseRebaseInfo(); err != nil {
if !errors.Is(err, macho.ErrMachODyldInfoNotFound) {
return fmt.Errorf("failed to parse rebase info: %v", err)
}
}
if err := d.parseHelpers(); err != nil {
if !errors.Is(err, macho.ErrMachOSectionNotFound) {
return fmt.Errorf("failed to parse stubs helpers: %v", err)
}
}
if err := d.parseGOT(); err != nil {
return fmt.Errorf("failed to parse GOT: %v", err)
}
if err := d.parseStubs(); err != nil {
return fmt.Errorf("failed to parse symbol stubs: %v", err)
}
if a2s, err := d.FindSwiftStrings(); err == nil {
maps.Copy(d.swiftstrs, a2s)
} else {
return fmt.Errorf("failed to find swift strings: %v", err)
}
return nil
}
func (d *MachoDisass) parseSymbols() error {
for _, sym := range d.f.Symtab.Syms {
if sym.Value > 0 && len(sym.Name) > 0 {
d.a2s[sym.Value] = sym.Name
}
}
exports, err := d.f.GetExports()
if err != nil {
if err != macho.ErrMachODyldInfoNotFound {
return fmt.Errorf("failed to get exports: %v", err)
}
}
for _, sym := range exports {
if sym.Address > 0 {
d.a2s[sym.Address] = sym.Name
}
}
return nil
}
func (d *MachoDisass) parseRebaseInfo() error {
d.sinfo = make(map[uint64]uint64)
if d.f.HasFixups() {
dcf, err := d.f.DyldChainedFixups()
if err != nil {
return fmt.Errorf("failed to parse fixups: %v", err)
}
for _, start := range dcf.Starts {
if start.PageStarts != nil {
// var sec *macho.Section
// var lastSec *macho.Section
for _, fixup := range start.Fixups {
addr, err := d.f.GetVMAddress(fixup.Offset())
if err != nil {
continue
}
switch fx := fixup.(type) {
case fixupchains.Bind:
// var addend string
// addr := uint64(fx.Offset()) + d.f.GetBaseAddress()
// if fullAddend := dcf.Imports[fx.Ordinal()].Addend() + fx.Addend(); fullAddend > 0 {
// addend = fmt.Sprintf(" + %#x", fullAddend)
// addr += fullAddend
// }
// sec = d.f.FindSectionForVMAddr(addr)
// lib := d.f.LibraryOrdinalName(dcf.Imports[fx.Ordinal()].LibOrdinal())
// if sec != nil && sec != lastSec {
// fmt.Printf("%s.%s\n", sec.Seg, sec.Name)
// }
// fmt.Printf("%s\t%s/%s%s\n", fixupchains.Bind(fx).String(d.f.GetBaseAddress()), lib, fx.Name(), addend)
case fixupchains.Rebase:
d.sinfo[addr] = uint64(fx.Target()) + d.f.GetBaseAddress()
}
// lastSec = sec
}
}
}
} else {
rbs, err := d.f.GetRebaseInfo()
if err != nil {
return err
}
for _, r := range rbs {
d.sinfo[r.Start+r.Offset] = r.Value
}
}
return nil
}
func (d *MachoDisass) parseObjC() error {
if d.f.HasObjC() {
if cfstrs, err := d.f.GetCFStrings(); err == nil {
for _, cfstr := range cfstrs {
d.a2s[cfstr.Address] = fmt.Sprintf("%#v", cfstr.Name)
}
}
if selRefs, err := d.f.GetObjCSelectorReferences(); err == nil {
for off, sel := range selRefs {
d.a2s[off] = fmt.Sprintf("sel_%s", sel.Name)
d.a2s[d.f.GetBaseAddress()+sel.VMAddr] = sel.Name
}
}
if classes, err := d.f.GetObjCClasses(); err == nil {
for _, class := range classes {
d.a2s[class.ClassPtr] = fmt.Sprintf("class_%s", class.Name)
d.a2s[class.IsaVMAddr] = fmt.Sprintf("objc_isa_%s", class.Isa)
for _, meth := range class.ClassMethods {
if len(meth.Name) > 0 {
d.a2s[meth.ImpVMAddr] = fmt.Sprintf("+[%s %s]", class.Name, meth.Name)
}
}
for _, imeth := range class.InstanceMethods {
if len(imeth.Name) > 0 {
d.a2s[imeth.ImpVMAddr] = fmt.Sprintf("-[%s %s]", class.Name, imeth.Name)
}
}
}
}
if classRefs, err := d.f.GetObjCClassReferences(); err == nil {
for off, class := range classRefs {
d.a2s[off] = fmt.Sprintf("class_%s", class.Name)
d.a2s[d.f.GetBaseAddress()+class.ClassPtr] = class.Name
}
}
if superRefs, err := d.f.GetObjCSuperReferences(); err == nil {
for off, class := range superRefs {
d.a2s[off] = fmt.Sprintf("class_%s", class.Name)
d.a2s[class.ClassPtr] = class.Name
d.a2s[class.IsaVMAddr] = class.Isa
}
}
if protoRefs, err := d.f.GetObjCProtoReferences(); err == nil {
for off, proto := range protoRefs {
d.a2s[off] = fmt.Sprintf("proto_%s", proto.Name)
d.a2s[d.f.GetBaseAddress()+proto.Ptr] = proto.Name
}
}
if objcStubs, err := d.f.GetObjCStubs(func(addr uint64, data []byte) (map[uint64]*objc.Stub, error) {
stubs := make(map[uint64]*objc.Stub)
addr2sel, err := ParseStubsASM(data, addr, func(u uint64) (uint64, error) {
ptr, err := d.f.GetPointerAtAddress(u)
if err != nil {
return 0, err
}
if name, err := d.f.GetBindName(ptr); err == nil && name == "_objc_msgSend" {
return 0, nil
}
ptr = d.f.SlidePointer(ptr)
if ptr < d.f.GetBaseAddress() {
return ptr + d.f.GetBaseAddress(), nil
}
return ptr, nil
})
if err != nil {
return nil, err
}
for addr, sel := range addr2sel {
if d.a2s[sel] != "_objc_msgSend" {
stubs[addr] = &objc.Stub{
Name: d.a2s[sel],
SelectorRef: sel,
}
}
}
return stubs, nil
}); err == nil {
for addr, stub := range objcStubs {
if len(stub.Name) > 0 {
d.a2s[addr] = fmt.Sprintf("j__objc_msgSend(x0, \"%s\")", stub.Name)
}
}
}
}
return nil
}
func (d *MachoDisass) parseSwift() error {
if d.f.HasSwift() {
if types, err := d.f.GetSwiftTypes(); err == nil {
for _, typ := range types {
if typ.Name != "" {
if d.cfg.Demangle {
typ.Name, _ = swift.Demangle(typ.Name)
}
d.a2s[typ.Address] = fmt.Sprintf("type descriptor for %s", typ.Name)
}
}
}
if fields, err := d.f.GetSwiftFields(); err == nil {
for _, field := range fields {
if field.Type != "" {
if d.cfg.Demangle {
field.Type, _ = swift.Demangle(field.Type)
}
d.a2s[field.Address] = fmt.Sprintf("field descriptor for %s", field.Type)
}
}
}
if dtds, err := d.f.GetSwiftProtocolConformances(); err == nil {
for _, dtd := range dtds {
if dtd.TypeRef.Name != "" {
if dtd.TypeRef.Parent != nil && dtd.TypeRef.Parent.Name != "" &&
dtd.TypeRef.Parent.Parent != nil && dtd.TypeRef.Parent.Parent.Name != "" {
dtd.TypeRef.Name = fmt.Sprintf("%s.%s", dtd.TypeRef.Parent, dtd.TypeRef.Name)
}
if d.cfg.Demangle {
dtd.Protocol, _ = swift.DemangleSimple(dtd.Protocol)
}
// log.Debugf("nominal type descriptor for %s : %s", dtd.TypeRef.Name, dtd.Protocol)
d.a2s[dtd.TypeRef.Address] = fmt.Sprintf("nominal type descriptor for %s : %s", dtd.TypeRef.Name, dtd.Protocol)
}
}
}
}
return nil
}
func (d *MachoDisass) parseImports() error {
if d.f.HasFixups() {
var addr uint64
dcf, err := d.f.DyldChainedFixups()
if err != nil {
return err
}
if dcf.Imports != nil {
for _, start := range dcf.Starts {
if start.PageStarts != nil {
binds := start.Binds()
if len(binds) > 0 {
for _, bind := range binds {
fullAddend := dcf.Imports[bind.Ordinal()].Addend() + bind.Addend()
addr = d.f.GetBaseAddress() + bind.Offset() + fullAddend
d.a2s[bind.Raw()] = bind.Name()
d.a2s[addr] = bind.Name()
}
}
}
}
}
}
return nil
}
func (d *MachoDisass) parseGOT() error {
gots, err := ParseGotPtrs(d.f)
if err != nil {
return err
}
for entry, target := range gots {
if slide, ok := d.sinfo[entry]; ok {
target = slide
}
if symName, ok := d.a2s[target]; ok {
d.a2s[entry] = fmt.Sprintf("%s%s", symbols.PrefixGot, symName)
continue
}
if laptr, ok := gots[target]; ok {
if symName, ok := d.a2s[laptr]; ok {
d.a2s[entry] = fmt.Sprintf("%s%s", symbols.PrefixGot, symName)
continue
}
}
// Try to decode as a chained fixup bind pointer
if d.f.HasFixups() {
if name, err := d.f.GetBindName(target); err == nil {
d.a2s[entry] = fmt.Sprintf("%s%s", symbols.PrefixGot, name)
continue
}
}
utils.Indent(log.Debug, 2)(fmt.Sprintf("no sym found for GOT entry %#x => %#x", entry, target))
d.a2s[entry] = fmt.Sprintf("%s%x", symbols.PrefixGotFallback, target)
}
return nil
}
func (d *MachoDisass) parseStubs() error {
stubs, err := ParseStubsForMachO(d.f)
if err != nil {
return err
}
for stub, target := range stubs {
if slide, ok := d.sinfo[stub]; ok {
target = slide
}
// Check if target is in GOT - if so, use the GOT entry's symbol
if gotSymName, ok := d.a2s[target]; ok {
if !strings.HasPrefix(gotSymName, symbols.PrefixJump) {
d.a2s[stub] = symbols.PrefixJump + strings.TrimPrefix(strings.TrimPrefix(gotSymName, symbols.PrefixGot), symbols.PrefixStubHelper)
} else {
d.a2s[stub] = gotSymName
}
continue
}
// Try to decode as a chained fixup bind pointer
if d.f.HasFixups() {
if name, err := d.f.GetBindName(target); err == nil {
d.a2s[stub] = fmt.Sprintf("%s%s", symbols.PrefixJump, name)
continue
}
}
utils.Indent(log.Debug, 2)(fmt.Sprintf("no sym found for stub %#x => %#x", stub, target))
d.a2s[stub] = fmt.Sprintf("%s%x", symbols.PrefixStubFallback, target)
}
return nil
}
func (d *MachoDisass) parseHelpers() error {
helpers, err := ParseHelpersASM(d.f)
if err != nil {
return err
}
for start, target := range helpers {
if slide, ok := d.sinfo[start]; ok {
target = slide
}
if symName, ok := d.a2s[target]; ok {
d.a2s[start] = fmt.Sprintf("%s%s", symbols.PrefixStubHelper, symName)
} else {
d.a2s[start] = fmt.Sprintf("%s%x", symbols.PrefixStubHelper, target)
}
}
return nil
}
func (d *MachoDisass) EmptySymMap() bool {
return len(d.a2s) == 0
}
func (d *MachoDisass) SetStartSym(addr uint64) {
d.a2s[addr] = "start"
}
func (d *MachoDisass) loadDwarf(machoPath string) error {
dsymPath := filepath.Join(machoPath+".dSYM", "Contents/Resources/DWARF", filepath.Base(machoPath))
if _, statErr := os.Stat(dsymPath); statErr == nil {
dm, err := macho.Open(dsymPath)
if err != nil {
return fmt.Errorf("failed to open dSYM file: %v", err)
} else {
foundDSYMSymbols := false
for _, sym := range dm.Symtab.Syms {
if sym.Name != "" {
name := sym.Name
if d.cfg.Demangle {
if strings.HasPrefix(name, "_$s") {
name, _ = swift.Demangle(name)
} else {
name = demangle.Do(name, false, false)
}
}
d.a2s[sym.Value] = name
foundDSYMSymbols = true
}
}
dm.Close()
if foundDSYMSymbols {
utils.Indent(log.Info, 2)(fmt.Sprintf("Loaded %d symbols from .dSYM file", len(d.a2s)))
} else {
utils.Indent(log.Warn, 2)("No symbols found in dSYM file")
}
}
}
return nil
}
func (d *MachoDisass) getTempCachePath(cacheFile *string) string {
var tmpfile string
if d.f != nil {
uuid := d.f.UUID()
if uuid.UUID.IsNull() {
tmpfile = filepath.Base(*cacheFile)
} else {
tmpfile = uuid.String() + ".a2s"
}
}
return filepath.Join(os.TempDir(), tmpfile)
}
var ErrCorruptCache = errors.New("corrupt cache file")
var (
cachedSymbols map[uint64]string // In-memory cache of loaded symbols
cachedSymbolsFile string // Track which file was loaded
cacheLoadedOnce bool // Track if we've printed the cache load message
)
func (d *MachoDisass) loadCache(cacheFile *string) error {
// If we've already loaded this exact file, copy from memory cache
if cachedSymbols != nil && cachedSymbolsFile == *cacheFile {
maps.Copy(d.a2s, cachedSymbols)
return nil
}
// Load from disk
f, err := os.Open(*cacheFile)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return os.ErrNotExist
}
return fmt.Errorf("failed to open cache file %s: %v", *cacheFile, err)
}
defer f.Close()
a2s := make(map[uint64]string)
if gobErr := gob.NewDecoder(f).Decode(&a2s); gobErr != nil {
return ErrCorruptCache
}
// Copy to instance
maps.Copy(d.a2s, a2s)
// Cache in memory for future instances
cachedSymbols = a2s
cachedSymbolsFile = *cacheFile
// Only print the load message once per program execution
if !cacheLoadedOnce {
log.Infof("Loaded %d symbols from cache file: %s", len(a2s), *cacheFile)
cacheLoadedOnce = true
}
return nil
}
func (d *MachoDisass) OpenOrCreateSymMap(cacheFile *string, machoPath string) error {
if err := d.loadCache(cacheFile); err == nil {
return nil // cache file loaded successfully
} else if errors.Is(err, os.ErrNotExist) {
tmpcache := d.getTempCachePath(cacheFile)
if err := d.loadCache(&tmpcache); err == nil {
*cacheFile = tmpcache
return nil // temp cache file loaded successfully
} else if errors.Is(err, os.ErrNotExist) {
// cache AND temp cache files do not exist (create new one)
} else if errors.Is(err, ErrCorruptCache) {
log.Warn("temp cache file is corrupt, recreating it")
if err := os.Remove(tmpcache); err != nil {
return fmt.Errorf("failed to remove temp cache file: %v", err)
}
} else {
return fmt.Errorf("failed to load temp cache file: %v", err)
}
} else if errors.Is(err, ErrCorruptCache) {
log.Warn("cache file is corrupt, recreating it")
if err := os.Remove(*cacheFile); err != nil {
return fmt.Errorf("failed to remove cache file: %v", err)
}
} else {
return fmt.Errorf("failed to load cache file: %v", err)
}
// load the .dSYM file if it exists
if err := d.loadDwarf(machoPath); err != nil {
return fmt.Errorf("failed to load dSYM file: %v", err)
}
if _, err := os.Create(*cacheFile); err != nil {
if errors.Is(err, os.ErrPermission) {
var e *os.PathError
if errors.As(err, &e) {
log.Errorf("failed to create symbol cache file %s (most likely a read-only location): %v", filepath.Base(e.Path), e.Err)
}
tmpcache := d.getTempCachePath(cacheFile)
if _, err := os.Create(tmpcache); err != nil {
return fmt.Errorf("failed to create temp cache file: %v", err)
}
utils.Indent(log.Warn, 2)("creating in the temp folder")
utils.Indent(log.Warn, 3)(fmt.Sprintf("to use in the future supply the flag: --cache %s ", tmpcache))
*cacheFile = tmpcache
return nil // Successfully created temp cache file
}
return fmt.Errorf("failed to create cache file: %v", err)
}
return nil
}
func (d *MachoDisass) SaveAddrToSymMap(dest string) (err error) {
f, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return fmt.Errorf("failed to open symbol cache file %s: %v", dest, err)
}
defer f.Close()
buff := new(bytes.Buffer)
// Encoding the map
if err := gob.NewEncoder(buff).Encode(d.a2s); err != nil {
return fmt.Errorf("failed to encode addr2sym map to binary: %v", err)
}
if _, err := buff.WriteTo(f); err != nil {
return fmt.Errorf("failed to write addr2sym map to file: %v", err)
}
return nil
}