mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
feat(tests): add comprehensive tests for micro instruction handling and class name recovery
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
package cpp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/blacktop/arm64-cgo/disassemble"
|
||||
)
|
||||
|
||||
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, &disassemble.Instruction{
|
||||
Address: 0x1000,
|
||||
Operation: disassemble.ARM64_ADD,
|
||||
Operands: []disassemble.Operand{
|
||||
{Registers: []disassemble.Register{disassemble.REG_X0}},
|
||||
{Registers: []disassemble.Register{disassemble.REG_X1}},
|
||||
{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, &disassemble.Instruction{
|
||||
Address: 0x1004,
|
||||
Operation: disassemble.ARM64_MOV,
|
||||
Operands: []disassemble.Operand{
|
||||
{Registers: []disassemble.Register{disassemble.REG_X2}},
|
||||
{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, &disassemble.Instruction{
|
||||
Address: 0x1008,
|
||||
Operation: disassemble.ARM64_ORR,
|
||||
Operands: []disassemble.Operand{
|
||||
{Registers: []disassemble.Register{disassemble.REG_X3}},
|
||||
{Registers: []disassemble.Register{disassemble.REG_XZR}},
|
||||
{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, &disassemble.Instruction{
|
||||
Address: 0x3000,
|
||||
Operation: disassemble.ARM64_LDR,
|
||||
Operands: []disassemble.Operand{
|
||||
{Registers: []disassemble.Register{disassemble.REG_X2}},
|
||||
{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, &disassemble.Instruction{
|
||||
Address: 0x5000,
|
||||
Operation: disassemble.ARM64_LDR,
|
||||
Operands: []disassemble.Operand{
|
||||
{Registers: []disassemble.Register{disassemble.REG_X0}},
|
||||
{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, &disassemble.Instruction{
|
||||
Address: 0x6000,
|
||||
Operation: disassemble.ARM64_MOV,
|
||||
Operands: []disassemble.Operand{
|
||||
{Registers: []disassemble.Register{disassemble.REG_W0}},
|
||||
{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, &disassemble.Instruction{
|
||||
Address: 0x7000,
|
||||
Operation: disassemble.ARM64_CSEL,
|
||||
Operands: []disassemble.Operand{
|
||||
{Registers: []disassemble.Register{disassemble.REG_X0}},
|
||||
{Registers: []disassemble.Register{disassemble.REG_X1}},
|
||||
{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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package cpp
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRecoveredClassNameFromSymbol(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
symbol string
|
||||
want string
|
||||
}{
|
||||
{symbol: "__ZTV11IOMemoryMap", want: "IOMemoryMap"},
|
||||
{symbol: "__ZTVN12IOUserClient9MetaClassE", want: "IOUserClient"},
|
||||
{symbol: "__ZN11IOMemoryMapC2Ev", want: "IOMemoryMap"},
|
||||
{symbol: "_vm_map_init", want: ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if got := recoveredClassNameFromSymbol(tt.symbol); got != tt.want {
|
||||
t.Fatalf("recoveredClassNameFromSymbol(%q) = %q, want %q", tt.symbol, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLooksLikeRecoveredClassNameRejectsNonsense(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
invalid := []string{
|
||||
"",
|
||||
"/arm-io/sgx",
|
||||
"%2hhx",
|
||||
"\"",
|
||||
"bad.name",
|
||||
"vm_map_init",
|
||||
"atm_init",
|
||||
}
|
||||
for _, name := range invalid {
|
||||
if looksLikeRecoveredClassName(name) {
|
||||
t.Fatalf("expected %q to be rejected as a class name", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLooksLikeRecoveredClassNameAllowsKnownLowercaseIdentifiers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
valid := []string{
|
||||
"cache",
|
||||
"client_log_buffer_t",
|
||||
"com_apple_filesystems_apfs",
|
||||
"com_apple_filesystems_hfs",
|
||||
"com_apple_filesystems_lifs",
|
||||
}
|
||||
for _, name := range valid {
|
||||
if !looksLikeRecoveredClassName(name) {
|
||||
t.Fatalf("expected %q to be accepted as a class name", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDedupePrefersResolvedNamedClass(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := &Scanner{}
|
||||
in := []discoveredClass{
|
||||
{
|
||||
Class: Class{
|
||||
Name: "AppleSmartBattery",
|
||||
Bundle: "com.apple.driver.AppleSmartBatteryManagerEmbedded",
|
||||
MetaPtr: 0xfffffe000761db30,
|
||||
SuperMeta: 0xfffffe000b312ec0,
|
||||
Size: 0x250,
|
||||
Ctor: 0xfffffe0009bf7d48,
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: Class{
|
||||
Name: "AppleSmartBattery",
|
||||
Bundle: "com.apple.driver.AppleSmartBatteryManagerEmbedded",
|
||||
MetaPtr: 0xfffffe000b4a7da0,
|
||||
SuperMeta: 0xfffffe000b312ec0,
|
||||
Size: 0x250,
|
||||
Ctor: 0xfffffe0009bf76f8,
|
||||
VtableAddr: 0xfffffe0007fb4ed0,
|
||||
MetaVtableAddr: 0xfffffe0007fb5430,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out := s.dedupe(in)
|
||||
if len(out) != 1 {
|
||||
t.Fatalf("dedupe returned %d classes, want 1", len(out))
|
||||
}
|
||||
if got := out[0].MetaPtr; got != 0xfffffe000b4a7da0 {
|
||||
t.Fatalf("dedupe kept meta %#x, want %#x", got, uint64(0xfffffe000b4a7da0))
|
||||
}
|
||||
if out[0].VtableAddr == 0 || out[0].MetaVtableAddr == 0 {
|
||||
t.Fatalf("dedupe dropped resolved vtable data: %+v", out[0].Class)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package cpp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func encodeB(from, to uint64) uint32 {
|
||||
imm := int64(to-from) >> 2
|
||||
return 0x14000000 | uint32(imm)&0x03ffffff
|
||||
}
|
||||
|
||||
func encodeBL(from, to uint64) uint32 {
|
||||
imm := int64(to-from) >> 2
|
||||
return 0x94000000 | uint32(imm)&0x03ffffff
|
||||
}
|
||||
|
||||
func wordsToBytes(words ...uint32) []byte {
|
||||
out := make([]byte, 4*len(words))
|
||||
for i, word := range words {
|
||||
binary.LittleEndian.PutUint32(out[i*4:], word)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestFindFunctionStartInSection(t *testing.T) {
|
||||
base := uint64(0x1000)
|
||||
data := wordsToBytes(
|
||||
0xd503237f,
|
||||
0xd503201f,
|
||||
0xd503201f,
|
||||
0xd503237f,
|
||||
0xd503201f,
|
||||
)
|
||||
|
||||
start, err := findFunctionStartInSection(base, data, base+0x10)
|
||||
if err != nil {
|
||||
t.Fatalf("findFunctionStartInSection failed: %v", err)
|
||||
}
|
||||
if want := base + 0x0c; start != want {
|
||||
t.Fatalf("findFunctionStartInSection = %#x, want %#x", start, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectFunctionDataDirectCall(t *testing.T) {
|
||||
start := uint64(0x2000)
|
||||
anchor := uint64(0x2100)
|
||||
data := wordsToBytes(
|
||||
encodeBL(start, anchor),
|
||||
0xd503201f,
|
||||
)
|
||||
|
||||
inspection := inspectFunctionData(start, data, func(addr uint64) bool {
|
||||
return addr == anchor
|
||||
})
|
||||
if !inspection.direct {
|
||||
t.Fatalf("expected direct OSMetaClass caller")
|
||||
}
|
||||
if len(inspection.nextTargets) != 0 {
|
||||
t.Fatalf("unexpected wrapper targets: %v", inspection.nextTargets)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectFunctionDataWrapperTargets(t *testing.T) {
|
||||
start := uint64(0x3000)
|
||||
targetA := uint64(0x3040)
|
||||
targetB := uint64(0x3080)
|
||||
data := wordsToBytes(
|
||||
encodeB(start, targetA),
|
||||
encodeBL(start+4, targetB),
|
||||
0xd503201f,
|
||||
)
|
||||
|
||||
inspection := inspectFunctionData(start, data, func(uint64) bool { return false })
|
||||
if inspection.direct {
|
||||
t.Fatalf("expected wrapper inspection, not direct")
|
||||
}
|
||||
if got, want := len(inspection.nextTargets), 2; got != want {
|
||||
t.Fatalf("wrapper targets = %d, want %d (%v)", got, want, inspection.nextTargets)
|
||||
}
|
||||
if inspection.nextTargets[0] != targetA || inspection.nextTargets[1] != targetB {
|
||||
t.Fatalf("wrapper targets = %#v, want [%#x %#x]", inspection.nextTargets, targetA, targetB)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user