Add new debug extension

Mon, 27 Jul 2009 03:32:13 +0100

author
Matthew Wild <mwild1@gmail.com>
date
Mon, 27 Jul 2009 03:32:13 +0100
changeset 34
0e34461ab2a6
parent 33
575e8a530f30
child 35
8843ea9f9e27

Add new debug extension

Makefile file | annotate | diff | comparison | revisions
debug/minichunkspy.lua file | annotate | diff | comparison | revisions
debug/squish.debug.lua file | annotate | diff | comparison | revisions
debug/squishy file | annotate | diff | comparison | revisions
squish.lua file | annotate | diff | comparison | revisions
squishy file | annotate | diff | comparison | revisions
--- a/Makefile	Mon Jul 27 03:30:29 2009 +0100
+++ b/Makefile	Mon Jul 27 03:32:13 2009 +0100
@@ -1,11 +1,14 @@
 
-OPTIONS=--with-minify --with-uglify --with-compile --with-virtual-io
+OPTIONS=-q --with-minify --with-uglify --with-compile --with-virtual-io
 
 squish: squish.lua squishy
-	./squish.lua $(OPTIONS)
+	./squish.lua $(OPTIONS) # Bootstrap squish
+	chmod +x squish
+	./squish -q debug # Minify debug code
+	./squish $(OPTIONS) --with-debug # Build squish with minified debug
 	
 install: squish
 	install squish /usr/local/bin/squish
 
 clean:
-	rm squish
+	rm squish squish.debug
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debug/minichunkspy.lua	Mon Jul 27 03:32:13 2009 +0100
@@ -0,0 +1,294 @@
+-- Minichunkspy: Disassemble and reassemble chunks.
+-- Copyright M Joonas Pihlaja 2009
+-- MIT license
+--
+-- minichunkspy = require"minichunkspy"
+--
+-- chunk = string.dump(loadfile"blabla.lua")
+-- disassembled_chunk = minichunkspy.disassemble(chunk)
+-- chunk = minichunkspy.assemble(disassembled_chunk)
+-- assert(minichunkspy.validate(<function or chunk>))
+--
+-- Tested on little-endian 32 bit platforms.  Modify
+-- the Size_t type to be a 64 bit integer to make it work
+-- for 64 bit systems, and set BIG_ENDIAN = true for
+-- big-endian systems.
+local string, table, math = string, table, math
+local ipairs, setmetatable, type, assert = ipairs, setmetatable, type, assert
+local _ = __END_OF_GLOBALS__
+local string_char, string_byte, string_sub = string.char, string.byte, string.sub
+local table_concat = table.concat
+local math_abs, math_ldexp, math_frexp = math.abs, math.ldexp, math.frexp
+local Inf = math.huge
+local Nan = Inf - Inf
+
+local BIG_ENDIAN = false	--twiddle this for your platform.
+
+local function construct (class, ...)
+    return class.new(class, ...)
+end
+
+local mt_memo = {}
+
+local Field = construct{
+    new =
+	function (class, self)
+	    local self = self or {}
+	    local mt = mt_memo[class] or {
+		__index = class,
+		__call = construct
+	    }
+	    mt_memo[class] = mt
+	    return setmetatable(self, mt)
+	end,
+}
+
+local None = Field{
+    unpack = function (self, bytes, ix) return nil, ix end,
+    pack = function (self, val) return "" end
+}
+
+local char_memo = {}
+
+local function char(n)
+    local field = char_memo[n] or Field{
+	unpack = function (self, bytes, ix)
+		     return string_sub(bytes, ix, ix+n-1), ix+n
+		 end,
+	pack = function (self, val) return string_sub(val, 1, n) end
+    }
+    char_memo[n] = field
+    return field
+end
+
+local uint8 = Field{
+    unpack = function (self, bytes, ix)
+		 return string_byte(bytes, ix, ix), ix+1
+	     end,
+    pack = function (self, val) return string_char(val) end
+}
+
+local uint32 = Field{
+    unpack =
+	function (self, bytes, ix)
+	    local a,b,c,d = string_byte(bytes, ix, ix+3)
+	    if BIG_ENDIAN then a,b,c,d = d,c,b,a end
+	    return a + b*256 + c*256^2 + d*256^3, ix+4
+	end,
+    pack =
+	function (self, val)
+	    assert(type(val) == "number",
+		   "unexpected value type to pack as an uint32")
+	    local a,b,c,d
+	    d = val % 2^32
+	    a = d % 256; d = (d - a) / 256
+	    b = d % 256; d = (d - b) / 256
+	    c = d % 256; d = (d - c) / 256
+	    if BIG_ENDIAN then a,b,c,d = d,c,b,a end
+	    return string_char(a,b,c,d)
+	end
+}
+
+local int32 = uint32{
+    unpack = function (self, bytes, ix)
+		 local val, ix = uint32:unpack(bytes, ix)
+		 return val < 2^32 and val or (val - 2^31), ix
+	     end
+}
+
+local Byte = uint8
+local Size_t = uint32
+local Integer = int32
+
+-- Opaque types:
+local Number = char(8)
+local Insn = char(4)
+
+local Struct = Field{
+    unpack =
+	function (self, bytes, ix)
+	    local val = {}
+	    local i,j = 1,1
+	    while self[i] do
+		local field = self[i]
+		local key = field.name
+		if not key then key, j = j, j+1 end
+		--print("unpacking struct field", key, " at index ", ix)
+		val[key], ix = field:unpack(bytes, ix)
+		i = i+1
+	    end
+	    return val, ix
+	end,
+    pack =
+	function (self, val)
+	    local data = {}
+	    local i,j = 1,1
+	    while self[i] do
+		local field = self[i]
+		local key = field.name
+		if not key then key, j = j, j+1 end
+		data[i] = field:pack(val[key])
+		i = i+1
+	    end
+	    return table_concat(data)
+	end
+}
+
+local List = Field{
+    unpack =
+	function (self, bytes, ix)
+	    local len, ix = Integer:unpack(bytes, ix)
+	    local vals = {}
+	    local field = self.type
+	    for i=1,len do
+		--print("unpacking list field", i, " at index ", ix)
+		vals[i], ix = field:unpack(bytes, ix)
+	    end
+	    return vals, ix
+	end,
+    pack =
+	function (self, vals)
+	    local len = #vals
+	    local data = { Integer:pack(len) }
+	    local field = self.type
+	    for i=1,len do
+		data[#data+1] = field:pack(vals[i])
+	    end
+	    return table_concat(data)
+	end
+}
+
+local Boolean = Field{
+    unpack =
+	function (self, bytes, ix)
+	    local val, ix = Integer:unpack(bytes, ix)
+	    assert(val == 0 or val == 1,
+		   "unpacked an unexpected value "..val.." for a Boolean")
+	    return val == 1, ix
+	end,
+    pack =
+	function (self, val)
+	    assert(type(val) == "boolean",
+		   "unexpected value type to pack as a Boolean")
+	    return Integer:pack(val and 1 or 0)
+	end
+}
+
+local String = Field{
+    unpack =
+	function (self, bytes, ix)
+	    local len, ix = Integer:unpack(bytes, ix)
+	    local val = nil
+	    if len > 0 then
+		-- len includes trailing nul byte; ignore it
+		local string_len = len - 1
+		val = bytes:sub(ix, ix+string_len-1)
+	    end
+	    return val, ix + len
+	end,
+    pack =
+	function (self, val)
+	    assert(type(val) == "nil" or type(val) == "string",
+		   "unexpected value type to pack as a String")
+	    if val == nil then
+		return Integer:pack(0)
+	    end
+	    return Integer:pack(#val+1) .. val .. "\000"
+	end
+}
+
+local ChunkHeader = Struct{
+    char(4){name = "signature"},
+    Byte{name = "version"},
+    Byte{name = "format"},
+    Byte{name = "endianness"},
+    Byte{name = "sizeof_int"},
+    Byte{name = "sizeof_size_t"},
+    Byte{name = "sizeof_insn"},
+    Byte{name = "sizeof_Number"},
+    Byte{name = "integral_flag"},
+}
+
+local ConstantTypes = {
+    [0] = None,
+    [1] = Boolean,
+    [3] = Number,
+    [4] = String,
+}
+local Constant = Field{
+    unpack =
+	function (self, bytes, ix)
+	    local t, ix = Byte:unpack(bytes, ix)
+	    local field = ConstantTypes[t]
+	    assert(field, "unknown constant type "..t.." to unpack")
+	    local v, ix = field:unpack(bytes, ix)
+	    return {
+		type = t,
+		value = v
+	    }, ix
+	end,
+    pack =
+	function (self, val)
+	    local t, v = val.type, val.value
+	    return Byte:pack(t) .. ConstantTypes[t]:pack(v)
+	end
+}
+
+local Local = Struct{
+    String{name = "name"},
+    Integer{name = "startpc"},
+    Integer{name = "endpc"}
+}
+
+local Function = Struct{
+    String{name = "name"},
+    Integer{name = "line"},
+    Integer{name = "last_line"},
+    Byte{name = "num_upvalues"},
+    Byte{name = "num_parameters"},
+    Byte{name = "is_vararg"},
+    Byte{name = "max_stack_size"},
+    List{name = "insns", type = Insn},
+    List{name = "constants", type = Constant},
+    List{name = "prototypes", type = nil}, --patch type below
+    List{name = "source_lines", type = Integer},
+    List{name = "locals", type = Local},
+    List{name = "upvalues", type = String},
+}
+assert(Function[10].name == "prototypes",
+       "missed the function prototype list")
+Function[10].type = Function
+
+local Chunk = Struct{
+    ChunkHeader{name = "header"},
+    Function{name = "body"}
+}
+
+local function validate(chunk)
+    if type(chunk) == "function" then
+	return validate(string.dump(chunk))
+    end
+    local f = Chunk:unpack(chunk, 1)
+    local chunk2 = Chunk:pack(f)
+
+    if chunk == chunk2 then return true end
+
+    local i
+    local len = math.min(#chunk, #chunk2)
+    for i=1,len do
+	local a = chunk:sub(i,i)
+	local b = chunk:sub(i,i)
+	if a ~= b then
+	    return false, ("chunk roundtripping failed: "..
+			   "first byte difference at index %d"):format(i)
+	end
+    end
+    return false, ("chunk round tripping failed: "..
+		   "original length %d vs. %d"):format(#chunk, #chunk2)
+end
+
+return {
+    disassemble = function (chunk) return Chunk:unpack(chunk, 1) end,
+    assemble = function (disassembled) return Chunk:pack(disassembled) end,
+    validate = validate
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debug/squish.debug.lua	Mon Jul 27 03:32:13 2009 +0100
@@ -0,0 +1,22 @@
+
+local cs = require "minichunkspy"
+
+local function ___adjust_chunk(chunk, newname, lineshift)
+	local c = cs.disassemble(string.dump(chunk));
+	c.body.name = newname;
+
+	lineshift = -c.body.line;
+	local function shiftlines(c)
+		c.line = c.line + lineshift;
+		c.last_line = c.last_line + lineshift;
+		for i, line in ipairs(c.source_lines) do
+			c.source_lines[i] = line+lineshift;
+		end
+		for i, f in ipairs(c.prototypes) do
+			shiftlines(f);
+		end
+	end
+	shiftlines(c.body);
+
+	return assert(loadstring(cs.assemble(c), newname))();
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debug/squishy	Mon Jul 27 03:32:13 2009 +0100
@@ -0,0 +1,9 @@
+Option "minify-locals" (false)
+Option "minify-level" "full"
+
+Module "minichunkspy" "minichunkspy.lua"
+
+Main "squish.debug.lua"
+
+Output "squish.debug"
+
--- a/squish.lua	Mon Jul 27 03:30:29 2009 +0100
+++ b/squish.lua	Mon Jul 27 03:32:13 2009 +0100
@@ -160,30 +160,7 @@
 end
 
 if enable_debug then
-	f:write [[
-	local function ___rename_chunk(chunk, name)
-		if type(chunk) == "function" then
-			chunk = string.dump(chunk);
-		end
-		local intsize = chunk:sub(8,8):byte();
-		local b = { chunk:sub(13, 13+intsize-1):byte(1, intsize) };
-		local oldlen = 0;
-		for i = 1, #b do 
-			oldlen = oldlen + b[i] * 2^((i-1)*8);
-		end
-		
-		local newname = name.."\0";
-		local newlen = #newname;
-		
-		local b = { };
-		for i=1,intsize do
-			b[i] = string.char(math.floor(newlen / 2^((i-1)*8)) % (2^(i*8)));
-		end
-		
-		return loadstring(chunk:sub(1, 12)..table.concat(b)..newname
-			..chunk:sub(13+intsize+oldlen, -1));
-	end
-	]];
+	f:write(require_resource("squish.debug"));
 end
 
 print_verbose("Packing modules...");
@@ -200,7 +177,7 @@
 		f:write(data);
 		f:write("end)\n");
 		if enable_debug then
-			f:write(string.format("package.preload[%q] = ___rename_chunk(package.preload[%q], %q);\n\n", 
+			f:write(string.format("package.preload[%q] = ___adjust_chunk(package.preload[%q], %q);\n\n", 
 				modulename, modulename, "@"..path));
 		end
 	else
--- a/squishy	Mon Jul 27 03:30:29 2009 +0100
+++ b/squishy	Mon Jul 27 03:32:13 2009 +0100
@@ -35,3 +35,7 @@
 if GetOption "with-virtual-io" then
 	Resource "vio" "vio/vio.lua"
 end
+
+if GetOption "with-debug" then
+	Resource "squish.debug" "squish.debug"
+end

mercurial