# This gdb script provides several useful routines for debugging ngx_lua or
# standalone Lua/LuaJIT.
#
# You need gdb >= v7.3 to make this script working correctly.
#
# Installation: place it at $HOME/.gdbinit
#
# -- chaoslawful <at> gmail <dot> com

#### Lua type defines ####

set $__LUA_TNONE = -1
set $__LUA_TNIL = 0
set $__LUA_TBOOLEAN = 1
set $__LUA_TLIGHTUSERDATA = 2
set $__LUA_TNUMBER = 3
set $__LUA_TSTRING = 4
set $__LUA_TTABLE = 5
set $__LUA_TFUNCTION = 6
set $__LUA_TUSERDATA = 7
set $__LUA_TTHREAD = 8

#### Lua constants ####

set $__LUA_GLOBALSINDEX = -10002
set $__LUA_ENVIRONINDEX = -10001
set $__LUA_REGISTRYINDEX = -10000

#### Auxiliary methods ####

define __lua_debug_instance
	if !$__lua_debug_instance
		set $__lua_debug_instance = (lua_Debug*)malloc(sizeof(lua_Debug))
	end
end

define __free_lua_debug_instance
	if $__lua_debug_instance
		set $rc = free($__lua_debug_instance)
		set $__lua_debug_instance = 0
	end
end

set $__BUCKET_SIZE = 16
define __set_instance
	if !$__set_instance
		set $__set_instance = (void*(*(*))[2])malloc($__BUCKET_SIZE*sizeof(void*(*)[2]))
		set $rc = memset($__set_instance, 0, $__BUCKET_SIZE*sizeof(void*(*)[2]))
	end
end

define __free_set_instance
	if $__set_instance
		__set_clean
		set $rc = free($__set_instance)
		set $__set_instance = 0
	end
end

define __set_add
	set $p = (void*)$arg0
	set $__bkt_idx = (int)$p%$__BUCKET_SIZE

	__set_instance
	set $__elem = (void*(*)[2])$__set_instance[$__bkt_idx]
	set $__found = 0
	while $__elem
		if (*$__elem)[0] == $p
			set $__found = 1
			loop_break
		end
		set $__elem = (void*(*)[2])(*$__elem)[1]
	end
	if $__found
		set $existed_in_set = 1
	else
		set $existed_in_set = 0

		set $rc = (void*(*)[2])calloc(1, sizeof(void*)*2)
		set (*$rc)[0] = $p
		set (*$rc)[1] = $__set_instance[$__bkt_idx]
		set $__set_instance[$__bkt_idx] = $rc
	end
end

define __set_is_exist
	set $p = (void*)$arg0
	set $__bkt_idx = (int)$p%$__BUCKET_SIZE

	__set_instance
	set $__elem = (void*(*)[2])$__set_instance[$__bkt_idx]
	set $__found = 0
	while $__elem
		if (*$__elem)[0] == $p
			set $__found = 1
			loop_break
		end
		set $__elem = (void*(*)[2])(*$__elem)[1]
	end
	if $__found
		set $existed_in_set = 1
	else
		set $existed_in_set = 0
	end
end

define __set_clean
	__set_instance

	set $__bkt_idx = 0
	while $__bkt_idx < $__BUCKET_SIZE
		set $__elem = (void*(*)[2])$__set_instance[$__bkt_idx]
		while $__elem
			set $__next = (void*(*)[2])(*$__elem)[1]
			set $rc = free($__elem)
			set $__elem = $__next
		end
		set $__set_instance[$__bkt_idx] = 0
		set $__bkt_idx = $__bkt_idx+1
	end
end

define hook-quit
	__free_lua_debug_instance
	__free_set_instance
end

define hook-detach
	__free_lua_debug_instance
	__free_set_instance
end

define hook-disconnect
	__free_lua_debug_instance
	__free_set_instance
end

define _lua_pop
	set $l = (lua_State*)$arg0
	set $_n = (int)$arg1
	set $_rc = lua_settop($l, -$_n-1)
end

define _lua_dump_locals
	set $l = (lua_State*)$arg0
	set $dbg = (lua_Debug*)$arg1
	set $idx = 1

	set $rc = lua_getlocal($l, $dbg, $idx)
	if $rc
		printf "\t----[[ Locals ]]----\n"
		while $rc
			printf "\t%d:\t'%s' = ", $idx, $rc
			__lua_dump_stack $l -1
			printf "\n"

			_lua_pop $l 1
			set $idx = $idx + 1
			set $rc = lua_getlocal($l, $dbg, $idx)
		end
	else
		printf "\tNo locals!\n"
	end
	printf "\n"
end

define _lua_dump_upvalues
	set $l = (lua_State*)$arg0
	set $dbg = (lua_Debug*)$arg1
	set $idx = 1

	set $rc = lua_getinfo($l, "f", $dbg)
	if $rc
		set $rc = lua_getupvalue($l, -1, $idx)
		if $rc
			printf "\t----[[ Upvalues ]]----\n"
			while $rc
				printf "\t%d:\t'%s' = ", $idx, $rc
				__lua_dump_stack $l -1
				printf "\n"

				_lua_pop $l 1
				set $idx = $idx + 1
				set $rc = lua_getupvalue($l, -1, $idx)
			end
		else
			printf "\tNo upvalues!\n"
		end
		_lua_pop $l 1
	else
		printf "\tFailed to get function closure!\n"
	end
	printf "\n"
end

define __lua_dump_stack
	__set_clean
	__lua_dump_stack_aux $arg0 $arg1 0
end

define __lua_dump_stack_aux
	set $l = (lua_State*)$arg0
	set $nidx_$arg2 = (int)$arg1
	set $cidx_$arg2 = (int)$arg2+1

	# relative stack index to absolute index
	if $nidx_$arg2 < 0 && $nidx_$arg2 > $__LUA_REGISTRYINDEX
		set $nidx_$arg2 = $nidx_$arg2 + (int)lua_gettop($l) + 1
	end

	set $vt_$arg2 = (int)lua_type($l, $nidx_$arg2)

	if $vt_$arg2 == $__LUA_TNONE
		echo <invalid index>
	end
	if $vt_$arg2 == $__LUA_TNIL
		echo (nil)
	end
	if $vt_$arg2 == $__LUA_TBOOLEAN
		printf "(bool) %d", lua_toboolean($l, $nidx_$arg2)
	end
	if $vt_$arg2 == $__LUA_TLIGHTUSERDATA
		printf "(ludata) %p", lua_touserdata($l, $nidx_$arg2)
	end
	if $vt_$arg2 == $__LUA_TNUMBER
		printf "%g", lua_tonumber($l, $nidx_$arg2)
	end
	if $vt_$arg2 == $__LUA_TSTRING
		set $tmplen = (size_t*)malloc(sizeof(size_t))
		set $tmp = lua_pushvalue($l, $nidx_$arg2)
		set $tmp = lua_tolstring($l, -1, $tmplen)
#printf "(string:%d) ", *$tmplen
		eval "output/r *(const char (*)[%d])$tmp", *$tmplen
		_lua_pop $l 1
		set $tmp = free($tmplen)
	end
	if $vt_$arg2 == $__LUA_TTABLE
		set $rc = lua_topointer($l, $nidx_$arg2)
#printf "(table) %p { ", $rc
		printf "{ "
		__set_add $rc
		if $existed_in_set
			printf "... "
		else
			set $rc = lua_pushnil($l)
			set $rc = lua_next($l, $nidx_$arg2)
			while $rc != 0
				printf "["
				__lua_dump_stack_aux $l -2 $cidx_$arg2
				printf "]"
				printf " = "
				__lua_dump_stack_aux $l -1 $cidx_$arg2
				printf ", "
				_lua_pop $l 1
				set $rc = lua_next($l, $nidx_$arg2)
			end
		end
		printf "}"
	end
	if $vt_$arg2 == $__LUA_TFUNCTION
		printf "(func) %p", lua_topointer($l, $nidx_$arg2)
	end
	if $vt_$arg2 == $__LUA_TUSERDATA
		printf "(udata) %p", lua_topointer($l, $nidx_$arg2)
	end
	if $vt_$arg2 == $__LUA_TTHREAD
		printf "(thread) %p", lua_topointer($l, $nidx_$arg2)
	else
		if $vt_$arg2 > $__LUA_TTHREAD || $vt_$arg2 < 0
			echo <unknown type>
		end
	end
end

#### Command methods ####

define lbt 
	if $argc < 1
		echo Please specify Lua state and/or dump flag!\n
	else
		set $l = (lua_State*)$arg0
		if $argc > 1
			set $dump_local = ($arg1&1)==1
			set $dump_upvalue = ($arg1&2)==2
		else
			set $dump_local = 0
			set $dump_upvalue = 0
		end

		__lua_debug_instance
		set $dbg = $__lua_debug_instance

		set $level = 0
		set $rc = lua_getstack($l, $level, $dbg)
		while $rc > 0
			set $rc = lua_getinfo($l, "Sln", $dbg)
			set $name = $dbg->name
			if !$name
				set $name = "???"
			end

			printf "#%d\t%s\t[%s]\tat %s:%d\n", $level, $name, $dbg->what, $dbg->source, $dbg->currentline

			if $dump_local
				_lua_dump_locals $l $dbg
			end
			if $dump_upvalue
				_lua_dump_upvalues $l $dbg
			end

			set $level = $level+1
			set $rc = lua_getstack($l, $level, $dbg)
		end
	end
end

document lbt
lbt <lua state> [<dump>]: Dump the backtrace of the specified Lua state. <dump> is a mask value, whose bit 1/2 controls the dump of locals/upvalues at each stack frame correspondingly. So set <dump> to 1 dumps only locals; set to 2 dumps only upvalues; and set to 3 dumps both locals and upvalues.
end

define ll
	if $argc != 2
		echo Please specify Lua state and stack frame number (0-based)!\n
	else
		set $l = (lua_State*)$arg0
		set $level = (int)$arg1

		__lua_debug_instance
		set $dbg = $__lua_debug_instance

		set $rc = lua_getstack($l, $level, $dbg)
		if $rc > 0
			_lua_dump_locals $l $dbg
		else
			echo Failed to get Lua stack frame!\n
		end
	end
end

document ll
ll <lua state> <frameno>: Dump all local vars in the specified Lua stack frame (0-based).
end

define lu
	if $argc != 2
		echo Please specify Lua state and stack frame number (0-based)!\n
	else
		set $l = (lua_State*)$arg0
		set $level = (int)$arg1

		__lua_debug_instance
		set $dbg = $__lua_debug_instance

		set $rc = lua_getstack($l, $level, $dbg)
		if $rc > 0
			_lua_dump_upvalues $l $dbg
		else
			echo Failed to get Lua stack frame!\n
		end
	end
end

document lu
lu <lua state> <frameno>: Dump all upvalues in the specified Lua stack frame (0-based).
end

define lg
	if $argc != 1
		echo Please specify Lua state!\n
	else
		set $l = (lua_State*)$arg0
		__lua_dump_stack $l $__LUA_GLOBALSINDEX
		printf "\n"
	end
end

document lg
lg <lua state>: Dump all entries in Lua global table.
end

define lr
	if $argc != 1
		echo Please specify Lua state!\n
	else
		set $l = (lua_State*)$arg0
		__lua_dump_stack $l $__LUA_REGISTRYINDEX
		printf "\n"
	end
end

document lr
lr <lua state>: Dump all entries in Lua registry table.
end

define ls
	if $argc != 1
		echo Please specify Lua state!\n
	else
		set $l = (lua_State*)$arg0
		set $idx = lua_gettop($l)
		while $idx >= 1
			printf "#%d ", $idx
			__lua_dump_stack $l $idx
			printf "\n"
			set $idx = $idx - 1
		end
	end
end

document ls
ls <lua state>: Dump all entries in call stack.
end

# vi:ft=gdb ts=4 sw=4

