debug/minichunkspy.lua

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
}