205 lines
7.2 KiB
Lua
205 lines
7.2 KiB
Lua
|
--[[
|
||
|
hwpatcher arm decoding/encoding library
|
||
|
|
||
|
]]--
|
||
|
arm = {}
|
||
|
|
||
|
-- determines whether an address is in Thumb code or not
|
||
|
function arm.is_thumb(addr)
|
||
|
return bit32.extract(addr.addr, 0) == 1
|
||
|
end
|
||
|
|
||
|
-- translate address to real address (ie without Thumb bit)
|
||
|
-- produces an error if address is not properly aligned in ARM mode
|
||
|
function arm.xlate_addr(addr)
|
||
|
local res = hwp.deepcopy(addr)
|
||
|
if arm.is_thumb(addr) then
|
||
|
res.addr = bit32.replace(addr.addr, 0, 0)
|
||
|
elseif bit32.extract(addr.addr, 0, 2) ~= 0 then
|
||
|
error("ARM address is not word-aligned")
|
||
|
end
|
||
|
return res
|
||
|
end
|
||
|
|
||
|
-- switch between arm and thumb
|
||
|
function arm.to_thumb(addr)
|
||
|
local res = hwp.deepcopy(addr)
|
||
|
res.addr = bit32.bor(addr.addr, 1)
|
||
|
return res
|
||
|
end
|
||
|
|
||
|
function arm.to_arm(addr)
|
||
|
return arm.xlate_addr(addr)
|
||
|
end
|
||
|
|
||
|
-- sign extend a value to 32-bits
|
||
|
-- only the lower 'bits' bits are considered, everything else is trashed
|
||
|
-- watch out arithmetic vs logical shift !
|
||
|
function arm.sign32(v)
|
||
|
if bit32.extract(v, 31) == 1 then
|
||
|
return -1 - bit32.bnot(v)
|
||
|
else
|
||
|
return v
|
||
|
end
|
||
|
end
|
||
|
function arm.sign_extend(val, bits)
|
||
|
return arm.sign32(bit32.arshift(bit32.lshift(val, 32 - bits), 32 - bits))
|
||
|
end
|
||
|
|
||
|
-- check that a signed value fits in some field
|
||
|
function arm.check_sign_truncation(val, bits)
|
||
|
return val == arm.sign_extend(val, bits)
|
||
|
end
|
||
|
|
||
|
-- create a branch description
|
||
|
function arm.make_branch(addr, link)
|
||
|
local t = {type = "branch", addr = addr, link = link}
|
||
|
local branch_to_string = function(self)
|
||
|
return string.format("branch(%s,%s)", self.addr, self.link)
|
||
|
end
|
||
|
setmetatable(t, {__tostring = branch_to_string})
|
||
|
return t
|
||
|
end
|
||
|
|
||
|
-- parse a jump and returns its description
|
||
|
function arm.parse_branch(fw, addr)
|
||
|
local opcode = hwp.read32(fw, arm.xlate_addr(addr))
|
||
|
if arm.is_thumb(addr) then
|
||
|
if bit32.band(opcode, 0xf800) ~= 0xf000 then
|
||
|
error("first instruction is not a bl(x) prefix")
|
||
|
end
|
||
|
local to_thumb = false
|
||
|
if bit32.band(opcode, 0xf8000000) == 0xf8000000 then
|
||
|
to_thumb = true
|
||
|
elseif bit32.band(opcode, 0xf8000000) ~= 0xe8000000 then
|
||
|
error("second instruction is not a bl(x) suffix")
|
||
|
end
|
||
|
local dest = hwp.make_addr(bit32.lshift(arm.sign_extend(opcode, 11), 12) +
|
||
|
arm.xlate_addr(addr).addr + 4 +
|
||
|
bit32.rshift(bit32.band(opcode, 0x7ff0000), 16) * 2, addr.section)
|
||
|
if to_thumb then
|
||
|
dest = arm.to_thumb(dest)
|
||
|
else
|
||
|
dest.addr = bit32.replace(dest.addr, 0, 0, 2)
|
||
|
end
|
||
|
return arm.make_branch(dest, true)
|
||
|
else
|
||
|
if bit32.band(opcode, 0xfe000000) == 0xfa000000 then -- BLX
|
||
|
local dest = hwp.make_addr(arm.sign_extend(opcode, 24) * 4 + 8 +
|
||
|
bit32.extract(opcode, 24) * 2 + arm.xlate_addr(addr).addr, addr.section)
|
||
|
return arm.make_branch(arm.to_thumb(dest), true)
|
||
|
elseif bit32.band(opcode, 0xfe000000) == 0xea000000 then -- B(L)
|
||
|
local dest = hwp.make_addr(arm.sign_extend(opcode, 24) * 4 + 8 +
|
||
|
arm.xlate_addr(addr).addr, addr.section)
|
||
|
return arm.make_branch(arm.to_arm(dest), bit32.extract(opcode, 24))
|
||
|
else
|
||
|
error("instruction is not a valid branch")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- generate the encoding of a branch
|
||
|
-- if the branch cannot be encoded using an immediate branch, it is generated
|
||
|
-- with an indirect form like a ldr, using a pool
|
||
|
function arm.write_branch(fw, addr, dest, pool)
|
||
|
local offset = arm.xlate_addr(dest.addr).addr - arm.xlate_addr(addr).addr
|
||
|
local exchange = arm.is_thumb(addr) ~= arm.is_thumb(dest.addr)
|
||
|
local opcode = 0
|
||
|
if arm.is_thumb(addr) then
|
||
|
if not dest.link then
|
||
|
return arm.write_load_pc(fw, addr, dest, pool)
|
||
|
end
|
||
|
offset = offset - 4 -- in Thumb, PC+4 relative
|
||
|
-- NOTE: BLX is undefined if the resulting offset has bit 0 set, follow
|
||
|
-- the procedure from the manual to ensure correct operation
|
||
|
if bit32.extract(offset, 1) ~= 0 then
|
||
|
offset = offset + 2
|
||
|
end
|
||
|
offset = offset / 2
|
||
|
if not arm.check_sign_truncation(offset, 22) then
|
||
|
error("destination is too far for immediate branch from thumb")
|
||
|
end
|
||
|
opcode = 0xf000 + -- BL/BLX prefix
|
||
|
bit32.band(bit32.rshift(offset, 11), 0x7ff) + -- offset (high part)
|
||
|
bit32.lshift(exchange and 0xe800 or 0xf800,16) + -- BLX suffix
|
||
|
bit32.lshift(bit32.band(offset, 0x7ff), 16) -- offset (low part)
|
||
|
else
|
||
|
offset = offset - 8 -- in ARM, PC+8 relative
|
||
|
if exchange and not dest.link then
|
||
|
return arm.write_load_pc(fw, addr, dest, pool)
|
||
|
end
|
||
|
offset = offset / 4
|
||
|
if not arm.check_sign_truncation(offset, 24) then
|
||
|
if pool == nil then
|
||
|
error("destination is too far for immediate branch from arm (no pool available)")
|
||
|
else
|
||
|
return arm.write_load_pc(fw, addr, dest, pool)
|
||
|
end
|
||
|
end
|
||
|
opcode = bit32.lshift(exchange and 0xf or 0xe, 28) + -- BLX / AL cond
|
||
|
bit32.lshift(0xa, 24) + -- branch
|
||
|
bit32.lshift(exchange and bit32.extract(offset, 1) or dest.link and 1 or 0, 24) + -- link / bit1
|
||
|
bit32.band(offset, 0xffffff)
|
||
|
end
|
||
|
return hwp.write32(fw, arm.xlate_addr(addr), opcode)
|
||
|
end
|
||
|
|
||
|
function arm.write_load_pc(fw, addr, dest, pool)
|
||
|
-- write pool
|
||
|
hwp.write32(fw, pool, dest.addr.addr)
|
||
|
-- write "ldr pc, [pool]"
|
||
|
local opcode
|
||
|
if arm.is_thumb(addr) then
|
||
|
error("unsupported pc load in thumb mode")
|
||
|
else
|
||
|
local offset = pool.addr - arm.xlate_addr(addr).addr - 8 -- ARM is PC+8 relative
|
||
|
local add = offset >= 0 and 1 or 0
|
||
|
offset = math.abs(offset)
|
||
|
opcode = bit32.lshift(0xe, 28) + -- AL cond
|
||
|
bit32.lshift(1, 26) + -- ldr/str
|
||
|
bit32.lshift(1, 24) + -- P
|
||
|
bit32.lshift(add, 23) + -- U
|
||
|
bit32.lshift(1, 20) + -- ldr
|
||
|
bit32.lshift(15, 16) + -- Rn=PC
|
||
|
bit32.lshift(15, 12) + -- Rd=PC
|
||
|
bit32.band(offset, 0xfff)
|
||
|
end
|
||
|
return hwp.write32(fw, arm.xlate_addr(addr), opcode)
|
||
|
end
|
||
|
|
||
|
-- generate the encoding of a "bx lr"
|
||
|
function arm.write_return(fw, addr)
|
||
|
if arm.is_thumb(addr) then
|
||
|
error("unsupported return from thumb code")
|
||
|
end
|
||
|
local opcode = bit32.lshift(0xe, 28) + -- AL cond
|
||
|
bit32.lshift(0x12, 20) + -- BX
|
||
|
bit32.lshift(1, 4) + -- BX
|
||
|
bit32.lshift(0xfff, 8) + -- SBO
|
||
|
14 -- LR
|
||
|
hwp.write32(fw, arm.xlate_addr(addr), opcode)
|
||
|
end
|
||
|
|
||
|
function arm.write_xxx_regs(fw, addr, load)
|
||
|
if arm.is_thumb(addr) then
|
||
|
error("unsupported save/restore regs from thumb code")
|
||
|
end
|
||
|
-- STMFD sp!,{r0-r12, lr}
|
||
|
local opcode = bit32.lshift(0xe, 28) + -- AL cond
|
||
|
bit32.lshift(0x4, 25) + -- STM/LDM
|
||
|
bit32.lshift(load and 0 or 1,24) + -- P
|
||
|
bit32.lshift(load and 1 or 0, 23) + -- U
|
||
|
bit32.lshift(1, 21) +-- W
|
||
|
bit32.lshift(load and 1 or 0, 20) + -- L
|
||
|
bit32.lshift(13, 16) + -- base = SP
|
||
|
0x5fff -- R0-R12,LR
|
||
|
return hwp.write32(fw, addr, opcode)
|
||
|
end
|
||
|
|
||
|
function arm.write_save_regs(fw, addr)
|
||
|
return arm.write_xxx_regs(fw, addr, false)
|
||
|
end
|
||
|
|
||
|
function arm.write_restore_regs(fw, addr)
|
||
|
return arm.write_xxx_regs(fw, addr, true)
|
||
|
end
|