diff options
Diffstat (limited to 'utils/hwpatcher/arm.lua')
-rw-r--r-- | utils/hwpatcher/arm.lua | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/utils/hwpatcher/arm.lua b/utils/hwpatcher/arm.lua new file mode 100644 index 0000000000..0211244d11 --- /dev/null +++ b/utils/hwpatcher/arm.lua | |||
@@ -0,0 +1,205 @@ | |||
1 | --[[ | ||
2 | hwpatcher arm decoding/encoding library | ||
3 | |||
4 | ]]-- | ||
5 | arm = {} | ||
6 | |||
7 | -- determines whether an address is in Thumb code or not | ||
8 | function arm.is_thumb(addr) | ||
9 | return bit32.extract(addr.addr, 0) == 1 | ||
10 | end | ||
11 | |||
12 | -- translate address to real address (ie without Thumb bit) | ||
13 | -- produces an error if address is not properly aligned in ARM mode | ||
14 | function arm.xlate_addr(addr) | ||
15 | local res = hwp.deepcopy(addr) | ||
16 | if arm.is_thumb(addr) then | ||
17 | res.addr = bit32.replace(addr.addr, 0, 0) | ||
18 | elseif bit32.extract(addr.addr, 0, 2) ~= 0 then | ||
19 | error("ARM address is not word-aligned") | ||
20 | end | ||
21 | return res | ||
22 | end | ||
23 | |||
24 | -- switch between arm and thumb | ||
25 | function arm.to_thumb(addr) | ||
26 | local res = hwp.deepcopy(addr) | ||
27 | res.addr = bit32.bor(addr.addr, 1) | ||
28 | return res | ||
29 | end | ||
30 | |||
31 | function arm.to_arm(addr) | ||
32 | return arm.xlate_addr(addr) | ||
33 | end | ||
34 | |||
35 | -- sign extend a value to 32-bits | ||
36 | -- only the lower 'bits' bits are considered, everything else is trashed | ||
37 | -- watch out arithmetic vs logical shift ! | ||
38 | function arm.sign32(v) | ||
39 | if bit32.extract(v, 31) == 1 then | ||
40 | return -1 - bit32.bnot(v) | ||
41 | else | ||
42 | return v | ||
43 | end | ||
44 | end | ||
45 | function arm.sign_extend(val, bits) | ||
46 | return arm.sign32(bit32.arshift(bit32.lshift(val, 32 - bits), 32 - bits)) | ||
47 | end | ||
48 | |||
49 | -- check that a signed value fits in some field | ||
50 | function arm.check_sign_truncation(val, bits) | ||
51 | return val == arm.sign_extend(val, bits) | ||
52 | end | ||
53 | |||
54 | -- create a branch description | ||
55 | function arm.make_branch(addr, link) | ||
56 | local t = {type = "branch", addr = addr, link = link} | ||
57 | local branch_to_string = function(self) | ||
58 | return string.format("branch(%s,%s)", self.addr, self.link) | ||
59 | end | ||
60 | setmetatable(t, {__tostring = branch_to_string}) | ||
61 | return t | ||
62 | end | ||
63 | |||
64 | -- parse a jump and returns its description | ||
65 | function arm.parse_branch(fw, addr) | ||
66 | local opcode = hwp.read32(fw, arm.xlate_addr(addr)) | ||
67 | if arm.is_thumb(addr) then | ||
68 | if bit32.band(opcode, 0xf800) ~= 0xf000 then | ||
69 | error("first instruction is not a bl(x) prefix") | ||
70 | end | ||
71 | local to_thumb = false | ||
72 | if bit32.band(opcode, 0xf8000000) == 0xf8000000 then | ||
73 | to_thumb = true | ||
74 | elseif bit32.band(opcode, 0xf8000000) ~= 0xe8000000 then | ||
75 | error("second instruction is not a bl(x) suffix") | ||
76 | end | ||
77 | local dest = hwp.make_addr(bit32.lshift(arm.sign_extend(opcode, 11), 12) + | ||
78 | arm.xlate_addr(addr).addr + 4 + | ||
79 | bit32.rshift(bit32.band(opcode, 0x7ff0000), 16) * 2, addr.section) | ||
80 | if to_thumb then | ||
81 | dest = arm.to_thumb(dest) | ||
82 | else | ||
83 | dest.addr = bit32.replace(dest.addr, 0, 0, 2) | ||
84 | end | ||
85 | return arm.make_branch(dest, true) | ||
86 | else | ||
87 | if bit32.band(opcode, 0xfe000000) == 0xfa000000 then -- BLX | ||
88 | local dest = hwp.make_addr(arm.sign_extend(opcode, 24) * 4 + 8 + | ||
89 | bit32.extract(opcode, 24) * 2 + arm.xlate_addr(addr).addr, addr.section) | ||
90 | return arm.make_branch(arm.to_thumb(dest), true) | ||
91 | elseif bit32.band(opcode, 0xfe000000) == 0xea000000 then -- B(L) | ||
92 | local dest = hwp.make_addr(arm.sign_extend(opcode, 24) * 4 + 8 + | ||
93 | arm.xlate_addr(addr).addr, addr.section) | ||
94 | return arm.make_branch(arm.to_arm(dest), bit32.extract(opcode, 24)) | ||
95 | else | ||
96 | error("instruction is not a valid branch") | ||
97 | end | ||
98 | end | ||
99 | end | ||
100 | |||
101 | -- generate the encoding of a branch | ||
102 | -- if the branch cannot be encoded using an immediate branch, it is generated | ||
103 | -- with an indirect form like a ldr, using a pool | ||
104 | function arm.write_branch(fw, addr, dest, pool) | ||
105 | local offset = arm.xlate_addr(dest.addr).addr - arm.xlate_addr(addr).addr | ||
106 | local exchange = arm.is_thumb(addr) ~= arm.is_thumb(dest.addr) | ||
107 | local opcode = 0 | ||
108 | if arm.is_thumb(addr) then | ||
109 | if not dest.link then | ||
110 | return arm.write_load_pc(fw, addr, dest, pool) | ||
111 | end | ||
112 | offset = offset - 4 -- in Thumb, PC+4 relative | ||
113 | -- NOTE: BLX is undefined if the resulting offset has bit 0 set, follow | ||
114 | -- the procedure from the manual to ensure correct operation | ||
115 | if bit32.extract(offset, 1) ~= 0 then | ||
116 | offset = offset + 2 | ||
117 | end | ||
118 | offset = offset / 2 | ||
119 | if not arm.check_sign_truncation(offset, 22) then | ||
120 | error("destination is too far for immediate branch from thumb") | ||
121 | end | ||
122 | opcode = 0xf000 + -- BL/BLX prefix | ||
123 | bit32.band(bit32.rshift(offset, 11), 0x7ff) + -- offset (high part) | ||
124 | bit32.lshift(exchange and 0xe800 or 0xf800,16) + -- BLX suffix | ||
125 | bit32.lshift(bit32.band(offset, 0x7ff), 16) -- offset (low part) | ||
126 | else | ||
127 | offset = offset - 8 -- in ARM, PC+8 relative | ||
128 | if exchange and not dest.link then | ||
129 | return arm.write_load_pc(fw, addr, dest, pool) | ||
130 | end | ||
131 | offset = offset / 4 | ||
132 | if not arm.check_sign_truncation(offset, 24) then | ||
133 | if pool == nil then | ||
134 | error("destination is too far for immediate branch from arm (no pool available)") | ||
135 | else | ||
136 | return arm.write_load_pc(fw, addr, dest, pool) | ||
137 | end | ||
138 | end | ||
139 | opcode = bit32.lshift(exchange and 0xf or 0xe, 28) + -- BLX / AL cond | ||
140 | bit32.lshift(0xa, 24) + -- branch | ||
141 | bit32.lshift(exchange and bit32.extract(offset, 1) or dest.link and 1 or 0, 24) + -- link / bit1 | ||
142 | bit32.band(offset, 0xffffff) | ||
143 | end | ||
144 | return hwp.write32(fw, arm.xlate_addr(addr), opcode) | ||
145 | end | ||
146 | |||
147 | function arm.write_load_pc(fw, addr, dest, pool) | ||
148 | -- write pool | ||
149 | hwp.write32(fw, pool, dest.addr.addr) | ||
150 | -- write "ldr pc, [pool]" | ||
151 | local opcode | ||
152 | if arm.is_thumb(addr) then | ||
153 | error("unsupported pc load in thumb mode") | ||
154 | else | ||
155 | local offset = pool.addr - arm.xlate_addr(addr).addr - 8 -- ARM is PC+8 relative | ||
156 | local add = offset >= 0 and 1 or 0 | ||
157 | offset = math.abs(offset) | ||
158 | opcode = bit32.lshift(0xe, 28) + -- AL cond | ||
159 | bit32.lshift(1, 26) + -- ldr/str | ||
160 | bit32.lshift(1, 24) + -- P | ||
161 | bit32.lshift(add, 23) + -- U | ||
162 | bit32.lshift(1, 20) + -- ldr | ||
163 | bit32.lshift(15, 16) + -- Rn=PC | ||
164 | bit32.lshift(15, 12) + -- Rd=PC | ||
165 | bit32.band(offset, 0xfff) | ||
166 | end | ||
167 | return hwp.write32(fw, arm.xlate_addr(addr), opcode) | ||
168 | end | ||
169 | |||
170 | -- generate the encoding of a "bx lr" | ||
171 | function arm.write_return(fw, addr) | ||
172 | if arm.is_thumb(addr) then | ||
173 | error("unsupported return from thumb code") | ||
174 | end | ||
175 | local opcode = bit32.lshift(0xe, 28) + -- AL cond | ||
176 | bit32.lshift(0x12, 20) + -- BX | ||
177 | bit32.lshift(1, 4) + -- BX | ||
178 | bit32.lshift(0xfff, 8) + -- SBO | ||
179 | 14 -- LR | ||
180 | hwp.write32(fw, arm.xlate_addr(addr), opcode) | ||
181 | end | ||
182 | |||
183 | function arm.write_xxx_regs(fw, addr, load) | ||
184 | if arm.is_thumb(addr) then | ||
185 | error("unsupported save/restore regs from thumb code") | ||
186 | end | ||
187 | -- STMFD sp!,{r0-r12, lr} | ||
188 | local opcode = bit32.lshift(0xe, 28) + -- AL cond | ||
189 | bit32.lshift(0x4, 25) + -- STM/LDM | ||
190 | bit32.lshift(load and 0 or 1,24) + -- P | ||
191 | bit32.lshift(load and 1 or 0, 23) + -- U | ||
192 | bit32.lshift(1, 21) +-- W | ||
193 | bit32.lshift(load and 1 or 0, 20) + -- L | ||
194 | bit32.lshift(13, 16) + -- base = SP | ||
195 | 0x5fff -- R0-R12,LR | ||
196 | return hwp.write32(fw, addr, opcode) | ||
197 | end | ||
198 | |||
199 | function arm.write_save_regs(fw, addr) | ||
200 | return arm.write_xxx_regs(fw, addr, false) | ||
201 | end | ||
202 | |||
203 | function arm.write_restore_regs(fw, addr) | ||
204 | return arm.write_xxx_regs(fw, addr, true) | ||
205 | end \ No newline at end of file | ||