debug/minichunkspy.lua

changeset 34
0e34461ab2a6
equal deleted inserted replaced
33:575e8a530f30 34:0e34461ab2a6
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 }

mercurial