Skip to content

Commit

Permalink
patchlib: Rewrote B.W and BLX assembler funcs (#32)
Browse files Browse the repository at this point in the history
This also fixes a mistake in 60a4318.
  • Loading branch information
pgaskin committed Mar 16, 2020
1 parent 9933b59 commit 2f4342d
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 245 deletions.
131 changes: 131 additions & 0 deletions patchlib/asm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package patchlib

import "encoding/binary"

// note: this is the 32-bit thumb-2 instruction encoding, not thumb-1
// see the thumb-2 reference manual, section 4.6.18

// AsmBW assembles a B.W instruction and returns a byte slice which can be patched
// directly into a binary.
func AsmBW(pc, target uint32) []byte {
return mustBytes(toBEBin(bw(pc, target)))
}

// AsmBLX assembles a BLX instruction and returns a byte slice which can be patched
// directly into a binary.
func AsmBLX(pc, target uint32) []byte {
return mustBytes(toBEBin(blx(pc, target)))
}

// Thumb-2 reference manual, 4.6.12
// B.W (encoding T4) (no cond) (thumb to thumb)
// 1 1 1 1 0 s imm10 1 0 J1 1 J2 imm11
//
// I1 = NOT(J1 EOR S) thus... J1 = NOT(I1) EOR S
// I2 = NOT(J2 EOR S) thus... J2 = NOT(I2) EOR S
// imm32 = SignExtend(S:I1:I2:imm10:imm11:'0', 32) thus... S = SIGN(imm32)
// I1 = imm32[24]
// I2 = imm32[23]
// imm10 = imm32[12:22]
// imm11 = imm32[1:12]
//
// BranchWritePC(PC + imm32) thus... imm32 = target - PC - 4
//
// imm32 must be between -16777216 and 16777214
func bw(PC, target uint32) uint32 {
var imm32 int32
var imm11, imm10, S, I2, I1, J2, J1 uint32
imm32 = int32(target) - int32(PC) - 4
imm11 = uint32(imm32>>1) & 0b11111111111 // imm32[1:12]
imm10 = uint32(imm32>>12) & 0b1111111111 // imm32[12:22]
I2 = bi((imm32>>22)&1 != 0) // imm32[22]
I1 = bi((imm32>>23)&1 != 0) // imm32[23]
S = bi(imm32 < 0) // SIGN(imm32)
J2 = (^I2 ^ S) & 1
J1 = (^I1 ^ S) & 1

var inst uint32
inst |= uint32(1) << 31
inst |= uint32(1) << 30
inst |= uint32(1) << 29
inst |= uint32(1) << 28
inst |= uint32(0) << 27
inst |= uint32(S) << 26
inst |= uint32(imm10) << 16 // 17 18 19 20 21 22 23 24 25
inst |= uint32(1) << 15
inst |= uint32(0) << 14
inst |= uint32(J1) << 13
inst |= uint32(1) << 12
inst |= uint32(J2) << 11
inst |= uint32(imm11) << 0 // 1 2 3 4 5 6 7 8 9 10

lebuf := make([]byte, 4)
lebuf[0] = uint8(inst >> 8 & 0xFF)
lebuf[1] = uint8(inst >> 0 & 0xFF)
lebuf[2] = uint8(inst >> 24 & 0xFF)
lebuf[3] = uint8(inst >> 16 & 0xFF)

return binary.LittleEndian.Uint32(lebuf) // le to sys endianness
}

// Thumb-2 reference manual, 4.6.18
// BLX (encoding T2) (no cond) (thumb to arm)
// 1 1 1 1 0 S imm10H 1 1 J1 0 J2 imm10L 0
//
// I1 = NOT(J1 EOR S) thus... J1 = NOT(I1) EOR S
// I2 = NOT(J2 EOR S) thus... J2 = NOT(I2) EOR S
// imm32 = SignExtend(S:I1:I2:imm10H:imm10L:'00', 32) thus... S = SIGN(imm32)
// I1 = imm32[24]
// I2 = imm32[23]
// imm10H = imm32[12:22]
// imm10L = imm32[2:12]
//
// next_instr_addr = PC n/a (for the return address)
// LR = next_instr_addr<31:1>:'1' n/a (for the return address)
// SelectInstrSet(InstrSet_ARM) n/a (for the target)
// BranchWritePC(Align(PC, 4) + imm32) thus... imm32 = target - (pc & ~3) - 4
//
// imm32 must be multiples of 4 between -16777216 and 16777212
func blx(PC, target uint32) uint32 {
var imm32 int32
var imm10L, imm10H, S, I2, I1, J2, J1 uint32
imm32 = int32(target) - int32(PC&^3) - 4
imm10L = uint32(imm32>>2) & 0b1111111111 // imm32[2:12]
imm10H = uint32(imm32>>12) & 0b1111111111 // imm32[12:22]
I2 = bi((imm32>>22)&1 != 0) // imm32[22]
I1 = bi((imm32>>23)&1 != 0) // imm32[23]
S = bi(imm32 < 0) // SIGN(imm32)
J2 = (^I2 ^ S) & 1
J1 = (^I1 ^ S) & 1

var inst uint32
inst |= uint32(1) << 31
inst |= uint32(1) << 30
inst |= uint32(1) << 29
inst |= uint32(1) << 28
inst |= uint32(0) << 27
inst |= uint32(S) << 26
inst |= uint32(imm10H) << 16 // 17 18 19 20 21 22 23 24 25
inst |= uint32(1) << 15
inst |= uint32(1) << 14
inst |= uint32(J1) << 13
inst |= uint32(0) << 12
inst |= uint32(J2) << 11
inst |= uint32(imm10L) << 1 // 2 3 4 5 6 7 8 9 10
inst |= uint32(0) << 0

lebuf := make([]byte, 4)
lebuf[0] = uint8(inst >> 8 & 0xFF)
lebuf[1] = uint8(inst >> 0 & 0xFF)
lebuf[2] = uint8(inst >> 24 & 0xFF)
lebuf[3] = uint8(inst >> 16 & 0xFF)

return binary.LittleEndian.Uint32(lebuf) // le to sys endianness
}

func bi(b bool) uint32 {
if b {
return 1
}
return 0
}
43 changes: 43 additions & 0 deletions patchlib/asm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package patchlib

import (
"fmt"
"testing"
)

// Note: kstool thumb "B..." 0xOFFSET

func TestAsmBW(t *testing.T) {
for _, tc := range []struct{ pc, target, inst uint32 }{
{0x83EDE8, 0x40EF40, 0xD0F7AAB0},
{0x83EDE8, 0x41A4A0, 0xDBF75AB3},
{0x83D426, 0x40EF40, 0xD1F78BB5},
{0x83D426, 0x41A4A0, 0xDDF73BB0},
{0x02EFE6, 0x0189C8, 0xE9F7EFBC},
} {
t.Run(fmt.Sprintf("%X_%X", tc.pc, tc.target), func(t *testing.T) {
if inst := bw(tc.pc, tc.target); inst != tc.inst {
t.Errorf("%X: B.W #0x%X - expected %X, got %X", tc.pc, tc.target, tc.inst, inst)
} else if fmt.Sprintf("%X", inst) != fmt.Sprintf("%X", AsmBW(tc.pc, tc.target)) {
t.Errorf("mismatch between []byte and uint32 versions")
}
})
}
}

func TestAsmBLX(t *testing.T) {
for _, tc := range []struct{ pc, target, inst uint32 }{
{0x83EDE8, 0x40EF40, 0xD0F7AAE0},
{0x83EDE8, 0x41A4A0, 0xDBF75AE3},
{0x83D426, 0x40EF40, 0xD1F78CE5},
{0x83D426, 0x41A4A0, 0xDDF73CE0},
} {
t.Run(fmt.Sprintf("%X_%X", tc.pc, tc.target), func(t *testing.T) {
if inst := blx(tc.pc, tc.target); inst != tc.inst {
t.Errorf("%X: BLX #0x%X - expected %X, got %X", tc.pc, tc.target, tc.inst, inst)
} else if fmt.Sprintf("%X", inst) != fmt.Sprintf("%X", AsmBLX(tc.pc, tc.target)) {
t.Errorf("mismatch between []byte and uint32 versions")
}
})
}
}
88 changes: 0 additions & 88 deletions patchlib/blx.go

This file was deleted.

Loading

0 comments on commit 2f4342d

Please sign in to comment.