Debugging Lua: how to get a stack trace / dump on any point.

Debugging Lua: how to get a stack trace / dump on any point.

Discuss Lua based Spring scripts (LuaUI widgets, mission scripts, gaia scripts, mod-rules scripts, scripted keybindings, etc...)

Moderator: Moderators

Post Reply
User avatar
Beherith
Posts: 5145
Joined: 26 Oct 2007, 16:21

Debugging Lua: how to get a stack trace / dump on any point.

Post by Beherith »

So, after much thought and suffering, I have thrown together a function which does some extremely useful things:

Call it at any point, and it will give you the name of each function on the stack (up to maxdepth), and arguments and first maxwidth local variables of that function, and if any of the values of the locals are tables, then it will try to shallow print + count them up to maxtablelements numbers. It will also just print any args after the first 3.

Known bug: I cant get the names or locals of functions like widget:UnitCreated(), only 1 call into that. EDIT: FIXED!

All together its almost as good as having a breakpoint!

So the output of it from one specific Chili call (in Chotify:Post()) for those interested, looks like this:

Code: Select all

1: Post  @libs/chotify/chotify/chotify.lua:51 Locals:(self=??; obj={title:User is now online, body:<userdata>, }[#2])
2: OnAddUser  @LuaMenu/widgets/chobby/components/friend_list_window.lua:75 Locals:(self=??; userName=vbs; userInfo={userName:vbs, accountID:76, lobbyID:skylobby, country:DE, isIgnored:false, }[#7]; userControl=<userdata>)
3: listener  @LuaMenu/widgets/chobby/components/friend_list_window.lua:65 Locals:(listener=<function>)
4: ??
5: xpcall  @=[C]:-1 Locals:((*temporary)=<function>)
6: _CallListeners  @libs/liblobby/lobby/observable.lua:33 Locals:(self=??; event=OnAddUser; eventListeners={1:<function>, 2:<function>, 3:<function>, 4:<function>, 5:<function>, }[#5]; args={1:vbs, 2:<table>, }[#2]; n=2; (for index)=5; (for limit)=5; (for step)=1; i=5; listener=<function>)
7: method  @libs/liblobby/lobby/lobby.lua:542 Locals:(self=??; userName=vbs; status={accountID:76, lobbyID:skylobby, country:DE, }[#3]; userInfo={userName:vbs, accountID:76, lobbyID:skylobby, country:DE, isIgnored:false, }[#7])
8: super  @sourcetoolong:152 Locals:(self=??; f=_OnAddUser; superClass={RecoverAccount:<function>, Channels:<function>, _OnRegistrationAccepted:<function>, _OnMOTD:<function>, __tostring:<function>, }[#228]; super={GetUserPartyID:<function>, _OnRegistrationAccepted:<function>, __tostring:<function>, _OnFriend:<function>, _OnSuggestedEngineVersion:<function>, }[#230]; s={RecoverAccount:<function>, ForceSpectatorMode:<function>, _OnMOTD:<function>, EnableAllUnits:<function>, _OnLeftTeam:<function>, }[#291]; method=<function>; (*temporary)={}[#0])
9: commandFunction  @libs/liblobby/lobby/interface.lua:528 Locals:(self=??; userName=vbs; country=DE; accountID=76; lobbyID=skylobby; userTable={accountID:76, lobbyID:skylobby, country:DE, }[#3])
10: _OnCommandReceived  @libs/liblobby/lobby/interface_shared.lua:169 Locals:(self=??; cmdName=ADDUSER; arguments=vbs DE 76 skylobby; cmdId=nil; commandFunction=<function>; pattern=(%S+)%s+(%S%S)%s+(%S+)%s*(.*); fullCmd=ADDUSER vbs DE 76 skylobby; pattern=(%S+)%s+(%S%S)%s+(%S+)%s*(.*); funArgs={1:vbs, 2:DE, 3:76, 4:skylobby, }[#4])
11: CommandReceived  @libs/liblobby/lobby/interface_shared.lua:112 Locals:(self=??; command=ADDUSER vbs DE 76 skylobby; cmdId=nil; cmdName=ADDUSER; arguments=vbs DE 76 skylobby; argumentsPos=8)
12: _SocketUpdate  @libs/liblobby/lobby/interface_shared.lua:208 Locals:(self=??; readable={1:tcp{client}: 000002c6fd9e6048, tcp{client}: 000002c6fd9e6048:1, }[#2]; writeable={1:tcp{client}: 000002c6fd9e6048, tcp{client}: 000002c6fd9e6048:1, }[#2]; err=nil; host=176.56.236.106; port=8200; brec=9543; bsent=204; age=0; (for generator)=<function>)
13: SafeUpdate  @libs/liblobby/lobby/interface_shared.lua:261 Locals:(self=??)
14: ??
15: xpcall  @=[C]:-1 Locals:((*temporary)=<function>)
The code is here and GPL:

Code: Select all

local function traceFullEcho(maxdepth, maxwidth, maxtableelements, ...)
    -- Some docs are in order:
    -- 
	local tracedebug = false
	functionsource= true
	maxdepth = maxdepth or 16
	maxwidth = maxwidth or 10
    maxtableelements = maxtableelements or 6 -- max amount of elements to expand from table type values

    local function dbgt(t, maxtableelements)
        local count = 0
        local res = ''
        for k,v in pairs(t) do
            count = count + 1
            if count < maxtableelements then
				if tracedebug then Spring.Echo(count, k) end 
				if type(k) == "number" and type(v) == "function" then -- try to get function lists?
					if tracedebug then Spring.Echo(k,v, debug.getinfo(v), debug.getinfo(v).name) end  --debug.getinfo(v).short_src)?
                	res = res .. tostring(k) .. ':' .. ((debug.getinfo(v) and debug.getinfo(v).name) or "<function>") ..', '
				else
                	res = res .. tostring(k) .. ':' .. tostring(v) ..', '
				end
            end
        end
        res = '{'..res .. '}[#'..count..']'
        return res
    end

	local myargs = {...}
	infostr = ""
	for i,v in ipairs(myargs) do
		infostr = infostr .. tostring(v) .. "\t"
	end
	if infostr ~= "" then infostr = "Trace:[" .. infostr .. "]\n" end 
	local functionstr = "" -- "Trace:["
	for i = 2, maxdepth do
		if debug.getinfo(i) then
			local funcName = (debug and debug.getinfo(i) and debug.getinfo(i).name)
			if funcName then
				functionstr = functionstr .. tostring(i-1) .. ": " .. tostring(funcName) .. " "
				local arguments = ""
				local funcName = (debug and debug.getinfo(i) and debug.getinfo(i).name) or "??"
				if funcName ~= "??" then
					if functionsource and debug.getinfo(i).source then 
						local source = debug.getinfo(i).source 
						if string.len(source) > 128 then source = "sourcetoolong" end
						functionstr = functionstr .. " @" .. source
					end 
					if functionsource and debug.getinfo(i).linedefined then 
						functionstr = functionstr .. ":" .. tostring(debug.getinfo(i).linedefined) 
					end 
					for j = 1, maxwidth do
						local name, value = debug.getlocal(i, j)
						if not name then break end
						if tracedebug then Spring.Echo(i,j, funcName,name) end 
						local sep = ((arguments == "") and "") or  "; "
                        if tostring(name) == 'self'  then
    						arguments = arguments .. sep .. ((name and tostring(name)) or "name?") .. "=" .. tostring("??")
                        else
                            local newvalue
                            if maxtableelements > 0 and type({}) == type(value) then newvalue = dbgt(value, maxtableelements) else newvalue = value end 
    						arguments = arguments .. sep .. ((name and tostring(name)) or "name?") .. "=" .. tostring(newvalue)
                        end
					end
				end
				functionstr  = functionstr .. " Locals:(" .. arguments .. ")" .. "\n"
			else 
				functionstr = functionstr .. tostring(i-1) .. ": ??\n"
			end
		else break end
	end
	Spring.Echo(infostr .. functionstr)
end
Edit: now it also prints out the source line each function is defined on
User avatar
PicassoCT
Journeywar Developer & Mapper
Posts: 10450
Joined: 24 Jan 2006, 21:12

Re: Debugging Lua: how to get a stack trace / dump on any point.

Post by PicassoCT »

Not be rude, but are you spying on me. Im pretty sure, i never posted about needing that, but i need that. Very much.

*Paranoia sets in*
User avatar
Beherith
Posts: 5145
Joined: 26 Oct 2007, 16:21

Re: Debugging Lua: how to get a stack trace / dump on any point.

Post by Beherith »

Weve all needed this at some point... :)
User avatar
prandipadaro
Posts: 98
Joined: 19 Oct 2011, 22:38

Re: Debugging Lua: how to get a stack trace / dump on any point.

Post by prandipadaro »

Is implementable as API in th engine?
User avatar
Beherith
Posts: 5145
Joined: 26 Oct 2007, 16:21

Re: Debugging Lua: how to get a stack trace / dump on any point.

Post by Beherith »

I have added it to Chobby's Spring.Utilities.TraceFullEcho()

BAR has it as Spring.Debug.TraceFullEcho()

You can freely add it anywhere :)
User avatar
Beherith
Posts: 5145
Joined: 26 Oct 2007, 16:21

Re: Debugging Lua: how to get a stack trace / dump on any point.

Post by Beherith »

It seems that one cant use the 'debug' library from synced or unsynced gadget code, because it doesnt even get loaded into those lua environments.
The engine has some references saying that the debug library is not sync safe. Besides being able to set upvalues and locals from other scopes, is there anything that should prevent me from enabling the debug library in gadgets?

Code: Select all

void CLuaHandle::PushTracebackFuncToRegistry(lua_State* L)
{
	SPRING_LUA_OPEN_LIB(L, luaopen_debug);
		HSTR_PUSH(L, "traceback");
		LuaUtils::PushDebugTraceback(L);
		lua_rawset(L, LUA_REGISTRYINDEX);
	// We only need the debug.traceback function, the others are unsafe for syncing.
	// Later CLuaHandle implementations decide themselves if they want to reload the lib or not (LuaUI does).
	LUA_UNLOAD_LIB(L, LUA_DBLIBNAME);
}
Post Reply

Return to “Lua Scripts”