mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
9c861ee7cd
Update all disassembly code to use new cgo-based decoder API with stack-allocated instruction structs instead of heap-allocated pointers. Add instruction filtering to skip decoding operations that register tracking doesn't care about, avoiding expensive CGo calls. Key changes: - Replace Decompose with DecomposeInto using stack allocation - Add mayBeTrackedInstruction filter for common tracked ops - Introduce helper functions for safe operand/register access - Fix metaclass pointer index to use caller index for efficiency - Remove root file special-casing in pointer index builder - Add comprehensive unit tests for tracking options and helpers
224 lines
7.7 KiB
Go
224 lines
7.7 KiB
Go
package cpp
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/blacktop/arm64-cgo/disassemble"
|
|
)
|
|
|
|
func testOp(op disassemble.Operand) disassemble.Op {
|
|
out := disassemble.Op{
|
|
Class: op.Class,
|
|
ArrSpec: op.ArrSpec,
|
|
Condition: op.Condition,
|
|
SysReg: op.SysReg,
|
|
LaneUsed: op.LaneUsed,
|
|
Lane: op.Lane,
|
|
Immediate: op.Immediate,
|
|
ShiftType: op.ShiftType,
|
|
ShiftValueUsed: op.ShiftValueUsed,
|
|
ShiftValue: op.ShiftValue,
|
|
Extend: op.Extend,
|
|
SignedImm: op.SignedImm,
|
|
PredQual: op.PredQual,
|
|
MulVl: op.MulVl,
|
|
Tile: op.Tile,
|
|
Slice: op.Slice,
|
|
}
|
|
out.NumRegisters = uint8(copy(out.Registers[:], op.Registers))
|
|
return out
|
|
}
|
|
|
|
func testInst(addr uint64, op disassemble.Operation, operands ...disassemble.Operand) *disassemble.Inst {
|
|
inst := &disassemble.Inst{
|
|
Address: addr,
|
|
Operation: op,
|
|
NumOps: uint8(len(operands)),
|
|
}
|
|
for idx := range operands {
|
|
inst.Operands[idx] = testOp(operands[idx])
|
|
}
|
|
return inst
|
|
}
|
|
|
|
func TestBuildMicroPlanMarksAnchorStoreAndPAC(t *testing.T) {
|
|
start := uint64(0x2000)
|
|
anchor := uint64(0x2040)
|
|
data := wordsToBytes(
|
|
encodeBL(start, anchor),
|
|
0xdac10230, // pacia x16, x17
|
|
0xf9000010, // str x16, [x0]
|
|
0xd65f03c0, // ret
|
|
)
|
|
|
|
plan := buildMicroPlan(start, data, func(target uint64) bool { return target == anchor }, len(data)-4)
|
|
|
|
if len(plan.anchorBLOffsets) != 1 || plan.anchorBLOffsets[0] != 0 {
|
|
t.Fatalf("anchorBLOffsets = %#v, want [0]", plan.anchorBLOffsets)
|
|
}
|
|
if len(plan.branchEventOffsets) != 1 || plan.branchEventOffsets[0] != 0 {
|
|
t.Fatalf("branchEventOffsets = %#v, want [0]", plan.branchEventOffsets)
|
|
}
|
|
if len(plan.x16CandidateOffsets) != 1 || plan.x16CandidateOffsets[0] != 4 {
|
|
t.Fatalf("x16CandidateOffsets = %#v, want [4]", plan.x16CandidateOffsets)
|
|
}
|
|
if len(plan.storeToX0Offsets) != 1 || plan.storeToX0Offsets[0] != 8 {
|
|
t.Fatalf("storeToX0Offsets = %#v, want [8]", plan.storeToX0Offsets)
|
|
}
|
|
if len(plan.retOffsets) != 1 || plan.retOffsets[0] != 12 {
|
|
t.Fatalf("retOffsets = %#v, want [12]", plan.retOffsets)
|
|
}
|
|
if plan.tags[0]µTagBL == 0 {
|
|
t.Fatalf("expected BL tag at offset 0")
|
|
}
|
|
if plan.tags[1]µTagX16Candidate == 0 {
|
|
t.Fatalf("expected x16 candidate tag at offset 4")
|
|
}
|
|
if plan.tags[2]µTagStoreToX0 == 0 {
|
|
t.Fatalf("expected store-to-x0 tag at offset 8")
|
|
}
|
|
}
|
|
|
|
func TestApplyMicroInstructionTracksAddressMaterializationAndMoves(t *testing.T) {
|
|
scanner := &Scanner{}
|
|
state := newMicroState(nil, 0x1000)
|
|
state.setKnownBase(1, 0x1000)
|
|
|
|
scanner.applyMicroInstruction(state, testInst(0x1000, disassemble.ARM64_ADD,
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_X0}},
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_X1}},
|
|
disassemble.Operand{Class: disassemble.IMM64, Immediate: 0x20},
|
|
))
|
|
if got, want := state.GetX(0), uint64(0x1020); got != want {
|
|
t.Fatalf("x0 = %#x, want %#x", got, want)
|
|
}
|
|
if got, want := state.regBase[0], uint64(0x1020); got != want {
|
|
t.Fatalf("regBase[x0] = %#x, want %#x", got, want)
|
|
}
|
|
|
|
scanner.applyMicroInstruction(state, testInst(0x1004, disassemble.ARM64_MOV,
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_X2}},
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_X0}},
|
|
))
|
|
if got, want := state.GetX(2), uint64(0x1020); got != want {
|
|
t.Fatalf("x2 = %#x, want %#x", got, want)
|
|
}
|
|
|
|
scanner.applyMicroInstruction(state, testInst(0x1008, disassemble.ARM64_ORR,
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_X3}},
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_XZR}},
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_X2}},
|
|
))
|
|
if got, want := state.GetX(3), uint64(0x1020); got != want {
|
|
t.Fatalf("x3 = %#x, want %#x", got, want)
|
|
}
|
|
if got, want := state.regBase[3], uint64(0x1020); got != want {
|
|
t.Fatalf("regBase[x3] = %#x, want %#x", got, want)
|
|
}
|
|
}
|
|
|
|
func TestApplyMicroInstructionLoadsFromTrackedStackSlot(t *testing.T) {
|
|
scanner := &Scanner{}
|
|
state := newMicroState(nil, 0x3000)
|
|
state.sp = 0x4000
|
|
state.writeStack(0x4000, 0xfeedface)
|
|
|
|
scanner.applyMicroInstruction(state, testInst(0x3000, disassemble.ARM64_LDR,
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_X2}},
|
|
disassemble.Operand{Class: disassemble.MEM_OFFSET, Registers: []disassemble.Register{disassemble.REG_SP}, Immediate: 0},
|
|
))
|
|
|
|
if got, want := state.GetX(2), uint64(0xfeedface); got != want {
|
|
t.Fatalf("x2 = %#x, want %#x", got, want)
|
|
}
|
|
if got, want := state.regLoadAddr[2], uint64(0x4000); got != want {
|
|
t.Fatalf("regLoadAddr[x2] = %#x, want %#x", got, want)
|
|
}
|
|
}
|
|
|
|
func TestApplyMicroInstructionDoesNotMaterializeUnknownDynamicLoad(t *testing.T) {
|
|
scanner := &Scanner{}
|
|
state := newMicroState(nil, 0x5000)
|
|
state.setKnownValue(1, 0xfffffe0001234000)
|
|
|
|
scanner.applyMicroInstruction(state, testInst(0x5000, disassemble.ARM64_LDR,
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_X0}},
|
|
disassemble.Operand{Class: disassemble.MEM_OFFSET, Registers: []disassemble.Register{disassemble.REG_X1}, Immediate: 0},
|
|
))
|
|
|
|
if got := state.GetX(0); got != 0 {
|
|
t.Fatalf("x0 = %#x, want 0 for unsupported dynamic load", got)
|
|
}
|
|
if got := state.regLoadAddr[0]; got != 0 {
|
|
t.Fatalf("regLoadAddr[x0] = %#x, want 0 for unsupported dynamic load", got)
|
|
}
|
|
}
|
|
|
|
func TestApplyMicroInstructionZeroExtendsWRegisterWrites(t *testing.T) {
|
|
scanner := &Scanner{}
|
|
state := newMicroState(nil, 0x6000)
|
|
state.setKnownValue(1, 0x1_0000_0001)
|
|
|
|
scanner.applyMicroInstruction(state, testInst(0x6000, disassemble.ARM64_MOV,
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_W0}},
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_X1}},
|
|
))
|
|
|
|
if got, want := state.GetX(0), uint64(1); got != want {
|
|
t.Fatalf("x0 = %#x, want %#x after W write zero-extension", got, want)
|
|
}
|
|
if got := state.regBase[0]; got != 0 {
|
|
t.Fatalf("regBase[x0] = %#x, want 0 after W write", got)
|
|
}
|
|
}
|
|
|
|
func TestApplyMicroInstructionClearsUnsupportedDestination(t *testing.T) {
|
|
scanner := &Scanner{}
|
|
state := newMicroState(nil, 0x7000)
|
|
state.setKnownValue(0, 0xfeedface)
|
|
|
|
scanner.applyMicroInstruction(state, testInst(0x7000, disassemble.ARM64_CSEL,
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_X0}},
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_X1}},
|
|
disassemble.Operand{Registers: []disassemble.Register{disassemble.REG_X2}},
|
|
))
|
|
|
|
if got := state.GetX(0); got != 0 {
|
|
t.Fatalf("x0 = %#x, want 0 after unsupported destination write", got)
|
|
}
|
|
}
|
|
|
|
func TestLooksLikeRecoveredClassName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
want bool
|
|
}{
|
|
{name: "IOMemoryMap", want: true},
|
|
{name: "OSValueObject<IOAccessoryIDBusTransport::TransferData>", want: true},
|
|
{name: "AGX·PI_300·P·A0·Accelerator", want: true},
|
|
{name: "vm_map_init", want: false},
|
|
{name: "com.apple.xnu.accounting_health", want: false},
|
|
{name: "/arm-io/sgx", want: false},
|
|
{name: "\"AppleSEPKeyStore\"", want: false},
|
|
{name: "0 == (status)", want: false},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
if got := looksLikeRecoveredClassName(tc.name); got != tc.want {
|
|
t.Fatalf("looksLikeRecoveredClassName(%q) = %v, want %v", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRecoveredClassNameScorePrefersClassLikeNames(t *testing.T) {
|
|
if got, want := recoveredClassNameScore("UnknownClass_0xfffffe0007004000"), 1; got != want {
|
|
t.Fatalf("unknown score = %d, want %d", got, want)
|
|
}
|
|
if got, want := recoveredClassNameScore("vm_map_init"), 1; got != want {
|
|
t.Fatalf("function-like score = %d, want %d", got, want)
|
|
}
|
|
if got, want := recoveredClassNameScore("IOMemoryMap"), 3; got != want {
|
|
t.Fatalf("class-like score = %d, want %d", got, want)
|
|
}
|
|
}
|