|
1 -- Minichunkspy: Disassemble and reassemble chunks. |
|
2 -- Copyright M Joonas Pihlaja 2009 |
|
3 -- MIT license |
|
4 -- |
|
5 -- minichunkspy = require"minichunkspy" |
|
6 -- |
|
7 -- chunk = string.dump(loadfile"blabla.lua") |
|
8 -- disassembled_chunk = minichunkspy.disassemble(chunk) |
|
9 -- chunk = minichunkspy.assemble(disassembled_chunk) |
|
10 -- assert(minichunkspy.validate(<function or chunk>)) |
|
11 -- |
|
12 -- Tested on little-endian 32 bit platforms. Modify |
|
13 -- the Size_t type to be a 64 bit integer to make it work |
|
14 -- for 64 bit systems, and set BIG_ENDIAN = true for |
|
15 -- big-endian systems. |
|
16 local string, table, math = string, table, math |
|
17 local ipairs, setmetatable, type, assert = ipairs, setmetatable, type, assert |
|
18 local _ = __END_OF_GLOBALS__ |
|
19 local string_char, string_byte, string_sub = string.char, string.byte, string.sub |
|
20 local table_concat = table.concat |
|
21 local math_abs, math_ldexp, math_frexp = math.abs, math.ldexp, math.frexp |
|
22 local Inf = math.huge |
|
23 local Nan = Inf - Inf |
|
24 |
|
25 local BIG_ENDIAN = false --twiddle this for your platform. |
|
26 |
|
27 local function construct (class, ...) |
|
28 return class.new(class, ...) |
|
29 end |
|
30 |
|
31 local mt_memo = {} |
|
32 |
|
33 local Field = construct{ |
|
34 new = |
|
35 function (class, self) |
|
36 local self = self or {} |
|
37 local mt = mt_memo[class] or { |
|
38 __index = class, |
|
39 __call = construct |
|
40 } |
|
41 mt_memo[class] = mt |
|
42 return setmetatable(self, mt) |
|
43 end, |
|
44 } |
|
45 |
|
46 local None = Field{ |
|
47 unpack = function (self, bytes, ix) return nil, ix end, |
|
48 pack = function (self, val) return "" end |
|
49 } |
|
50 |
|
51 local char_memo = {} |
|
52 |
|
53 local function char(n) |
|
54 local field = char_memo[n] or Field{ |
|
55 unpack = function (self, bytes, ix) |
|
56 return string_sub(bytes, ix, ix+n-1), ix+n |
|
57 end, |
|
58 pack = function (self, val) return string_sub(val, 1, n) end |
|
59 } |
|
60 char_memo[n] = field |
|
61 return field |
|
62 end |
|
63 |
|
64 local uint8 = Field{ |
|
65 unpack = function (self, bytes, ix) |
|
66 return string_byte(bytes, ix, ix), ix+1 |
|
67 end, |
|
68 pack = function (self, val) return string_char(val) end |
|
69 } |
|
70 |
|
71 local uint32 = Field{ |
|
72 unpack = |
|
73 function (self, bytes, ix) |
|
74 local a,b,c,d = string_byte(bytes, ix, ix+3) |
|
75 if BIG_ENDIAN then a,b,c,d = d,c,b,a end |
|
76 return a + b*256 + c*256^2 + d*256^3, ix+4 |
|
77 end, |
|
78 pack = |
|
79 function (self, val) |
|
80 assert(type(val) == "number", |
|
81 "unexpected value type to pack as an uint32") |
|
82 local a,b,c,d |
|
83 d = val % 2^32 |
|
84 a = d % 256; d = (d - a) / 256 |
|
85 b = d % 256; d = (d - b) / 256 |
|
86 c = d % 256; d = (d - c) / 256 |
|
87 if BIG_ENDIAN then a,b,c,d = d,c,b,a end |
|
88 return string_char(a,b,c,d) |
|
89 end |
|
90 } |
|
91 |
|
92 local int32 = uint32{ |
|
93 unpack = function (self, bytes, ix) |
|
94 local val, ix = uint32:unpack(bytes, ix) |
|
95 return val < 2^32 and val or (val - 2^31), ix |
|
96 end |
|
97 } |
|
98 |
|
99 local Byte = uint8 |
|
100 local Size_t = uint32 |
|
101 local Integer = int32 |
|
102 |
|
103 -- Opaque types: |
|
104 local Number = char(8) |
|
105 local Insn = char(4) |
|
106 |
|
107 local Struct = Field{ |
|
108 unpack = |
|
109 function (self, bytes, ix) |
|
110 local val = {} |
|
111 local i,j = 1,1 |
|
112 while self[i] do |
|
113 local field = self[i] |
|
114 local key = field.name |
|
115 if not key then key, j = j, j+1 end |
|
116 --print("unpacking struct field", key, " at index ", ix) |
|
117 val[key], ix = field:unpack(bytes, ix) |
|
118 i = i+1 |
|
119 end |
|
120 return val, ix |
|
121 end, |
|
122 pack = |
|
123 function (self, val) |
|
124 local data = {} |
|
125 local i,j = 1,1 |
|
126 while self[i] do |
|
127 local field = self[i] |
|
128 local key = field.name |
|
129 if not key then key, j = j, j+1 end |
|
130 data[i] = field:pack(val[key]) |
|
131 i = i+1 |
|
132 end |
|
133 return table_concat(data) |
|
134 end |
|
135 } |
|
136 |
|
137 local List = Field{ |
|
138 unpack = |
|
139 function (self, bytes, ix) |
|
140 local len, ix = Integer:unpack(bytes, ix) |
|
141 local vals = {} |
|
142 local field = self.type |
|
143 for i=1,len do |
|
144 --print("unpacking list field", i, " at index ", ix) |
|
145 vals[i], ix = field:unpack(bytes, ix) |
|
146 end |
|
147 return vals, ix |
|
148 end, |
|
149 pack = |
|
150 function (self, vals) |
|
151 local len = #vals |
|
152 local data = { Integer:pack(len) } |
|
153 local field = self.type |
|
154 for i=1,len do |
|
155 data[#data+1] = field:pack(vals[i]) |
|
156 end |
|
157 return table_concat(data) |
|
158 end |
|
159 } |
|
160 |
|
161 local Boolean = Field{ |
|
162 unpack = |
|
163 function (self, bytes, ix) |
|
164 local val, ix = Integer:unpack(bytes, ix) |
|
165 assert(val == 0 or val == 1, |
|
166 "unpacked an unexpected value "..val.." for a Boolean") |
|
167 return val == 1, ix |
|
168 end, |
|
169 pack = |
|
170 function (self, val) |
|
171 assert(type(val) == "boolean", |
|
172 "unexpected value type to pack as a Boolean") |
|
173 return Integer:pack(val and 1 or 0) |
|
174 end |
|
175 } |
|
176 |
|
177 local String = Field{ |
|
178 unpack = |
|
179 function (self, bytes, ix) |
|
180 local len, ix = Integer:unpack(bytes, ix) |
|
181 local val = nil |
|
182 if len > 0 then |
|
183 -- len includes trailing nul byte; ignore it |
|
184 local string_len = len - 1 |
|
185 val = bytes:sub(ix, ix+string_len-1) |
|
186 end |
|
187 return val, ix + len |
|
188 end, |
|
189 pack = |
|
190 function (self, val) |
|
191 assert(type(val) == "nil" or type(val) == "string", |
|
192 "unexpected value type to pack as a String") |
|
193 if val == nil then |
|
194 return Integer:pack(0) |
|
195 end |
|
196 return Integer:pack(#val+1) .. val .. "\000" |
|
197 end |
|
198 } |
|
199 |
|
200 local ChunkHeader = Struct{ |
|
201 char(4){name = "signature"}, |
|
202 Byte{name = "version"}, |
|
203 Byte{name = "format"}, |
|
204 Byte{name = "endianness"}, |
|
205 Byte{name = "sizeof_int"}, |
|
206 Byte{name = "sizeof_size_t"}, |
|
207 Byte{name = "sizeof_insn"}, |
|
208 Byte{name = "sizeof_Number"}, |
|
209 Byte{name = "integral_flag"}, |
|
210 } |
|
211 |
|
212 local ConstantTypes = { |
|
213 [0] = None, |
|
214 [1] = Boolean, |
|
215 [3] = Number, |
|
216 [4] = String, |
|
217 } |
|
218 local Constant = Field{ |
|
219 unpack = |
|
220 function (self, bytes, ix) |
|
221 local t, ix = Byte:unpack(bytes, ix) |
|
222 local field = ConstantTypes[t] |
|
223 assert(field, "unknown constant type "..t.." to unpack") |
|
224 local v, ix = field:unpack(bytes, ix) |
|
225 return { |
|
226 type = t, |
|
227 value = v |
|
228 }, ix |
|
229 end, |
|
230 pack = |
|
231 function (self, val) |
|
232 local t, v = val.type, val.value |
|
233 return Byte:pack(t) .. ConstantTypes[t]:pack(v) |
|
234 end |
|
235 } |
|
236 |
|
237 local Local = Struct{ |
|
238 String{name = "name"}, |
|
239 Integer{name = "startpc"}, |
|
240 Integer{name = "endpc"} |
|
241 } |
|
242 |
|
243 local Function = Struct{ |
|
244 String{name = "name"}, |
|
245 Integer{name = "line"}, |
|
246 Integer{name = "last_line"}, |
|
247 Byte{name = "num_upvalues"}, |
|
248 Byte{name = "num_parameters"}, |
|
249 Byte{name = "is_vararg"}, |
|
250 Byte{name = "max_stack_size"}, |
|
251 List{name = "insns", type = Insn}, |
|
252 List{name = "constants", type = Constant}, |
|
253 List{name = "prototypes", type = nil}, --patch type below |
|
254 List{name = "source_lines", type = Integer}, |
|
255 List{name = "locals", type = Local}, |
|
256 List{name = "upvalues", type = String}, |
|
257 } |
|
258 assert(Function[10].name == "prototypes", |
|
259 "missed the function prototype list") |
|
260 Function[10].type = Function |
|
261 |
|
262 local Chunk = Struct{ |
|
263 ChunkHeader{name = "header"}, |
|
264 Function{name = "body"} |
|
265 } |
|
266 |
|
267 local function validate(chunk) |
|
268 if type(chunk) == "function" then |
|
269 return validate(string.dump(chunk)) |
|
270 end |
|
271 local f = Chunk:unpack(chunk, 1) |
|
272 local chunk2 = Chunk:pack(f) |
|
273 |
|
274 if chunk == chunk2 then return true end |
|
275 |
|
276 local i |
|
277 local len = math.min(#chunk, #chunk2) |
|
278 for i=1,len do |
|
279 local a = chunk:sub(i,i) |
|
280 local b = chunk:sub(i,i) |
|
281 if a ~= b then |
|
282 return false, ("chunk roundtripping failed: ".. |
|
283 "first byte difference at index %d"):format(i) |
|
284 end |
|
285 end |
|
286 return false, ("chunk round tripping failed: ".. |
|
287 "original length %d vs. %d"):format(#chunk, #chunk2) |
|
288 end |
|
289 |
|
290 return { |
|
291 disassemble = function (chunk) return Chunk:unpack(chunk, 1) end, |
|
292 assemble = function (disassembled) return Chunk:pack(disassembled) end, |
|
293 validate = validate |
|
294 } |