Add PowerPC instruction encoding

With this, we can write assembly directly within patches. This assists for easier tweaks of code, and no need to manually assemble for rapid prototyping.
This commit is contained in:
Spotlight 2021-12-30 21:39:28 -06:00
parent 88142b2269
commit 957d69f118
No known key found for this signature in database
GPG Key ID: 874AA355B3209BDC
3 changed files with 217 additions and 43 deletions

View File

@ -30,9 +30,11 @@ var OverwriteIOSPatch = PatchSet{
padding,
}.toBytes(),
// We wish to clear these so that our custom overwriteIOSMemory
// function does not somehow conflict.
After: emptyBytes(64),
// We wish to clear extraneous blrs so that our custom overwriteIOSMemory
// function does not somehow conflict. We only preserve onSE.
After: append(Instructions{
BLR(),
}.toBytes(), emptyBytes(60)...),
},
Patch{
Name: "Repair textinput::EventObserver vtable",
@ -73,70 +75,62 @@ var OverwriteIOSPatch = PatchSet{
// This area should be cleared.
Before: emptyBytes(48),
After: []byte{
After: Instructions{
// We want r9 to store the location of MEM_PROT at 0x0d8b420a.
// For us, this is mapped to 0xcd8b420a.
// lis r9, 0xcd8b
0x3D, 0x20, 0xCD, 0x8B,
// ori r9, r9, 0x420a
0x61, 0x29, 0x42, 0x0A,
LIS(R9, 0xcd8b),
ORI(R9, R9, 0x420a),
// We want to write 0x2 and unlock everything.
// li r10, 0x02
0x39, 0x40, 0x00, 0x02,
LI(R10, 0x02),
// Write!
// sth r10, 0x0(r9)
0xB1, 0x49, 0x00, 0x00,
STH(R10, 0x0, R9),
// Flush memory
// eieio
0x7C, 0x00, 0x06, 0xAC,
EIEIO(),
// Location of IOSC_VerifyPublicKeySign
// lis r9, 0xd3a7
0x3D, 0x20, 0xD3, 0xA7,
// ori r9, r9, 0x3ad4
0x61, 0x29, 0x3A, 0xD4,
LIS(R9, 0xd3a7),
ORI(R9, R9, 0x3ad4),
// Write our custom THUMB.
// 0x20004770 is equivalent to:
// mov r0, #0x0
// bx lr
// lis r10, 0x2000
0x3D, 0x40, 0x20, 0x00,
// ori r10, r10, 0x4770
0x61, 0x4A, 0x47, 0x70,
LIS(R10, 0x2000),
ORI(R10, R10, 0x4770),
// Write!
// stw r10, 0x0(r9)
0x91, 0x49, 0x00, 0x00,
STW(R10, 0x0, R9),
// Possibly clear cache
// TODO(spotlightishere): Is this needed?
// dcbi 0, r10
0x7C, 0x00, 0x53, 0xAC,
Instruction{0x7C, 0x00, 0x53, 0xAC},
// And finish.
// blr
0x4E, 0x80, 0x00, 0x20,
},
BLR(),
}.toBytes(),
},
Patch{
Name: "Modify ES_InitLib",
AtOffset: 2399844,
// We inject in the epilog of the function.
Before: []byte{
0x80, 0x01, 0x00, 0x14, // lwz r0, local_res4(r1)
0x7C, 0x08, 0x03, 0xA6, // mtspr LR, r0
0x38, 0x21, 0x00, 0x10, // addi r1, r1, 0x10
0x4E, 0x80, 0x00, 0x20, // blr
0x00, 0x00, 0x00, 0x00, // ; empty space following function
},
After: []byte{
0x80, 0x01, 0x00, 0x14, // lwz r0, local_res4(r1)
0x4B, 0xDB, 0xB1, 0x01, // bl overwriteIOSMemory @ 0x80014428
0x7C, 0x08, 0x03, 0xA6, // mtspr LR, r0
0x38, 0x21, 0x00, 0x10, // addi r1, r1, 0x10
0x4E, 0x80, 0x00, 0x20, // blr
},
Before: Instructions{
LWZ(R0, 0x14, R1),
// mtspr LR, r0
Instruction{0x7C, 0x08, 0x03, 0xA6},
ADDI(R1, R1, 0x10),
BLR(),
padding,
}.toBytes(),
After: Instructions{
LWZ(R0, 0x14, R1),
// bl overwriteIOSMemory @ 0x80014428
Instruction{0x4B, 0xDB, 0xB1, 0x01},
// mtspr LR, r0
Instruction{0x7C, 0x08, 0x03, 0xA6},
ADDI(R1, R1, 0x10),
BLR(),
}.toBytes(),
},
}

View File

@ -20,7 +20,57 @@ func (i Instructions) toBytes() []byte {
// padding is not an actual instruction - it represents 4 zeros.
var padding Instruction = [4]byte{0x00, 0x00, 0x00, 0x00}
// BLR represents the blr PowerPC instruction.
// BLR represents the blr mnemonic on PowerPC.
func BLR() Instruction {
return [4]byte{0x4E, 0x80, 0x00, 0x20}
}
// ADDI represents the addi PowerPC instruction.
func ADDI(rT Register, rA Register, value uint16) Instruction {
return EncodeInstrDForm(14, rT, rA, value)
}
// LI represents the li mnemonic on PowerPC.
func LI(rT Register, value uint16) Instruction {
return ADDI(rT, 0, value)
}
// SUBI represents the subi mnemonic on PowerPC.
func SUBI(rT Register, rA Register, value uint16) Instruction {
return ADDI(rT, 0, -value)
}
// ADDIS represents the addis PowerPC instruction.
func ADDIS(rT Register, rA Register, value uint16) Instruction {
return EncodeInstrDForm(15, rT, rA, value)
}
// LIS represents the lis mnemonic on PowerPC.
func LIS(rT Register, value uint16) Instruction {
return ADDIS(rT, 0, value)
}
// ORI represents the ori PowerPC instruction.
func ORI(rS Register, rA Register, value uint16) Instruction {
return EncodeInstrDForm(24, rS, rA, value)
}
// STH represents the sth PowerPC instruction.
func STH(rS Register, offset uint16, rA Register) Instruction {
return EncodeInstrDForm(44, rS, rA, offset)
}
// EIEIO represents the eieio PowerPC instruction.
func EIEIO() Instruction {
return [4]byte{0x7C, 0x00, 0x06, 0xAC}
}
// STW represents the stw PowerPC instruction.
func STW(rS Register, offset uint16, rA Register) Instruction {
return EncodeInstrDForm(36, rS, rA, offset)
}
// LWZ represents the lwz PowerPC instruction.
func LWZ(rT Register, offset uint16, rA Register) Instruction {
return EncodeInstrDForm(32, rT, rA, offset)
}

130
powerpc_encoding.go Normal file
View File

@ -0,0 +1,130 @@
package main
import (
"encoding/binary"
"encoding/hex"
"log"
)
// Register represents a value for a PowerPC register.
type Register byte
const (
R0 = iota
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
R11
R12
R13
R14
R15
R16
R17
R18
R19
R20
R21
R22
R23
R24
R25
R26
R27
R28
R29
R30
R31
)
// Bits represents bits for a byte.
// If set, considered as 1. If not, 0.
type Bits [8]bool
// getBits returns a usable array of bits for the given byte.
func getBits(in byte) Bits {
return [8]bool{
(in>>7)&1 == 1,
(in>>6)&1 == 1,
(in>>5)&1 == 1,
(in>>4)&1 == 1,
(in>>3)&1 == 1,
(in>>2)&1 == 1,
(in>>1)&1 == 1,
in&1 == 1,
}
}
// getByte returns the byte represented by these bits.
func (b Bits) getByte() byte {
var result byte
for idx, truthy := range b {
if truthy {
result |= 1 << (7 - idx)
}
}
return result
}
// EncodeInstrDForm handles encoding a given opcode, RT, RA and SI.
// D-form assumes:
// - 6 bits for the opcode
// - 5 for rT
// - 5 for rA
// - 16 for SI
func EncodeInstrDForm(opcode byte, rT Register, rA Register, value uint16) Instruction {
var instr [2]Bits
opBits := getBits(opcode)
rTBits := getBits(byte(rT))
rABits := getBits(byte(rA))
instr[0] = Bits{
// We need the upper six bits for our opcode.
opBits[2],
opBits[3],
opBits[4],
opBits[5],
opBits[6],
opBits[7],
// Next, the lower two bits for rT.
rTBits[3],
rTBits[4],
}
instr[1] = Bits{
// Third, the lower three bits for rT.
rTBits[5],
rTBits[6],
rTBits[7],
// Finally, all five lowest bits for rA.
rABits[3],
rABits[4],
rABits[5],
rABits[6],
rABits[7],
}
firstInstr := instr[0].getByte()
secondInstr := instr[1].getByte()
valByte := twoByte(value)
log.Println(hex.EncodeToString([]byte{
firstInstr, secondInstr, valByte[0], valByte[1],
}))
return Instruction{firstInstr, secondInstr, valByte[0], valByte[1]}
}
// twoByte converts a uint16 to two big-endian bytes.
func twoByte(passed uint16) [2]byte {
result := make([]byte, 2)
binary.BigEndian.PutUint16(result, passed)
return [2]byte{result[0], result[1]}
}