lua debbuger
Closed this issue · 5 comments
Hello, you know how we can debug lua code step by setp?
For the C# side of things you can attach a debugger to the program. However, its not going to tell you about the lua code past what you call in C#.
@tilkinsc On the C# side I can debug normally, I wanted to debug the Lua side, I found some related things using an IDE called Zerobrane, I would have to copy a script from Zerobrane, and put it to be loaded by the Lua VM in C#, and do some other things configurations so that Zerobrane could listen to the scripts of Lua VM in C#.
Have you ever done something like that?
I'm using your wrapper to develop some games here using LuaJit in C#, I'll be sending some Pull Requests with improvements that I make along the way.
When I have some time I'll study this debugging part better, if I get something that works I'll create a doc explaining how to debug and upload a PR.
Related materials:
https://defold.com/manuals/zerobrane/
https://www.geeks3d.com/hacklab/20200219/how-to-debug-your-lua-scripts-with-zerobrane-studio/
https://stackoverflow.com/questions/48661026/remote-debugging-hosted-lua
OpenRA/OpenRA#15852
https://discuss.cocos2d-x.org/t/tutorial-how-to-remote-debug-lua-via-zerobrane/11072/2
Zerobrane is its own thing really. I personally have no need for a debugger lua side. I think you don't either. I do however encourage you to make use of the error param of pcall so that you have a way to cleanly print out stack traces, which are already a thing in lua. Be sure to include the full path and line number thats conformant with vscode (assuming your IDE), so that you can easily ctrl click to jump to the lua file. Debugging is a nice to have, but nothing prints can't fix of course. Remember, you can also print out whats on your stack at any time you feel like. I think I have a utility for that in this code base, if not look at https://github.com/tilkinsc/LuaConsole . You can print out whats on the stack as well as print out a stack trace. This is my default behavior in LuaConsole; I also believe it will be invaluable for you going forth because I employ really good practices.
For introspection purposes it may be best to base your Lua around globals like in WoW. We both know that isn't very performant. You are using LuaJIT at least. Having a clear stack trace is better than figuring out where the root of the call came from. Roblox has a good example of what you can do with design when it comes to introspection. Although their Lua isnt Lua anymore. I pride languages like C# for having the null coalescing operator ?. which Lua so desperately needs due to the nature of nil being a very common thing and probably the biggest source of errors.
Side note, you may also want to seek options with the vm regarding optimizations. Iirc not all LuaJITs optimizations are enabled?
Lets say you want to go further and really debug Lua. I have concerns about the generation of JIT'd code being indeterministic from a human perspective leading to problems. Here is a vscode extension which uses some panda software which integrates with vscode all open source. https://github.com/Tencent/LuaHelper/tree/master/luahelper-vscode/debugger (which will probably have to be reimplemented due to the nature of it being chinese)
It just so happened that I previously translated the Stack Dump from C to CS for an upcoming project. I think its just missing a table walk.
public static int StackDump(lua_State L)
{
int top = lua_gettop(L);
Console.WriteLine("--------------- Stack Dump ----------------");
while (top > 0)
{
int type = lua_type(L, top);
Console.WriteLine(type switch
{
LUA_TNIL => $"{top}:(Nil):`{lua_tostring(L, top)}`",
LUA_TBOOLEAN => $"{top}:(Boolean):`{(lua_toboolean(L, top) == 1 ? "true" : "false")}`",
LUA_TLIGHTUSERDATA => $"{top}:(LUserdata):`0x{lua_topointer(L, top):X}`",
LUA_TNUMBER => $"{top}:(Number):`{lua_tonumber(L, top)}`",
LUA_TSTRING => $"{top}:(String):`{lua_tostring(L, top)}`",
LUA_TTABLE => $"{top}:(Table):`0x{lua_topointer(L, top):X}`",
LUA_TFUNCTION => $"{top}:(Function):`0x{lua_topointer(L, top):X}`",
LUA_TUSERDATA => $"{top}:(Userdata):`0x{lua_topointer(L, top):X}`",
LUA_TTHREAD => $"{top}:(Thread):`0x{lua_topointer(L, top):X}`",
_ => $"{top}:(Object):{lua_typename(L, type)}:`0x{lua_topointer(L, top):X}",
});
top--;
}
Console.WriteLine("----------- Stack Dump Finished -----------");
return 0;
}Here is it with a rudimentary table walk
public static int print_table(lua_State L)
{
long tabOffset = lua_tointeger(L, -1);
string tabString = new('\t', (int) tabOffset);
Console.WriteLine(tabString + "{");
lua_pushnil(L);
while (lua_next(L, -3) != 0)
{
int keyType = lua_type(L, -2);
int valueType = lua_type(L, -1);
string keyString = keyType switch
{
LUA_TNIL => "\tnil: ",
LUA_TBOOLEAN => $"\t{(lua_toboolean(L, -2) == 1 ? "true" : "false")}: ",
LUA_TLIGHTUSERDATA => $"\tLU_{lua_topointer(L, -2):X6}: ",
LUA_TNUMBER => $"\t{lua_tonumber(L, -2)}: ",
LUA_TTABLE => $"\tT_{lua_topointer(L, -2):X6}",
LUA_TSTRING => $"\t\"{lua_tostring(L, -2)}\": ",
LUA_TFUNCTION => $"\tF_{lua_topointer(L, -2):X6}: ",
LUA_TUSERDATA => $"\tU_{lua_topointer(L, -2):X6}: ",
LUA_TTHREAD => $"\tTH_{lua_topointer(L, -2):X6}: ",
_ => $"\t{lua_typename(L, keyType)} 0x{lua_topointer(L, -2):X6}: "
};
if (valueType == LUA_TTABLE)
{
Console.WriteLine(tabString + keyString);
lua_pushcfunction(L, print_table);
lua_pushvalue(L, -2);
lua_pushinteger(L, tabOffset + 1);
int status = lua_pcall(L, 2, 0, 0);
if (status != 0)
{
string? str = lua_tostring(L, -1);
Console.WriteLine("Lua error : " + str);
}
}
else
{
string valueString = valueType switch
{
LUA_TNIL => "nil",
LUA_TBOOLEAN => $"{(lua_toboolean(L, -1) == 1 ? "true" : "false")},",
LUA_TLIGHTUSERDATA => $"LU_{lua_topointer(L, -1):X6},",
LUA_TNUMBER => $"{lua_tonumber(L, -1)},",
LUA_TSTRING => $"\"{lua_tostring(L, -1)}\",",
LUA_TFUNCTION => $"F_{lua_topointer(L, -1):X6},",
LUA_TUSERDATA => $"U_{lua_topointer(L, -1):X6},",
LUA_TTHREAD => $"TH_{lua_topointer(L, -1):X6},",
_ => $"{lua_typename(L, keyType)} O_{lua_topointer(L, -1):X6},"
};
Console.WriteLine(tabString + keyString + valueString);
}
lua_pop(L, 1);
}
Console.WriteLine(tabString + "}");
return 0;
}
public static int stack_dump(lua_State L)
{
int top = lua_gettop(L);
Console.WriteLine("--------------- Stack Dump ----------------");
while (top > 0)
{
int type = lua_type(L, top);
if (type == LUA_TTABLE)
{
Console.WriteLine($"{top}:(Table):`0x{lua_topointer(L, top):X}`");
lua_pushcfunction(L, print_table);
lua_pushvalue(L, top);
lua_pushinteger(L, 0);
int status = lua_pcall(L, 2, 0, 0);
if (status != 0)
{
string? str = lua_tostring(L, -1);
Console.WriteLine("Lua error : " + str);
}
top--;
continue;
}
Console.WriteLine(type switch
{
LUA_TNIL => $"{top}:(Nil):`{lua_tostring(L, top)}`",
LUA_TBOOLEAN => $"{top}:(Boolean):`{(lua_toboolean(L, top) == 1 ? "true" : "false")}`",
LUA_TLIGHTUSERDATA => $"{top}:(LUserdata):`0x{lua_topointer(L, top):X}`",
LUA_TNUMBER => $"{top}:(Number):`{lua_tonumber(L, top)}`",
LUA_TSTRING => $"{top}:(String):`{lua_tostring(L, top)}`",
LUA_TFUNCTION => $"{top}:(Function):`0x{lua_topointer(L, top):X}`",
LUA_TUSERDATA => $"{top}:(Userdata):`0x{lua_topointer(L, top):X}`",
LUA_TTHREAD => $"{top}:(Thread):`0x{lua_topointer(L, top):X}`",
_ => $"{top}:(Object):{lua_typename(L, type)}:`0x{lua_topointer(L, top):X}",
});
top--;
}
Console.WriteLine("----------- Stack Dump Finished -----------");
return 0;
}
public static void Main(string[] args)
{
lua_State L = luaL_newstate();
if (L.Handle == 0)
{
Console.Error.WriteLine("Out of memory!");
return ExitCode.OutOfMemory;
}
lua_pushboolean(L, 1);
lua_pushcfunction(L, stack_dump);
lua_pushstring(L, "abc");
lua_pushlightuserdata(L, 0x123);
lua_newtable(L);
lua_pushboolean(L, 1);
lua_pushnumber(L, 0.0D);
lua_settable(L, -3);
lua_pushboolean(L, 0);
lua_pushnumber(L, 1.0D);
lua_settable(L, -3);
lua_pushcfunction(L, stack_dump);
lua_pushliteral(L, "abc");
lua_settable(L, -3);
lua_pushliteral(L, "def");
lua_pushliteral(L, "ghk");
lua_settable(L, -3);
lua_pushlightuserdata(L, 0x456);
lua_pushnumber(L, 4.0D);
lua_settable(L, -3);
lua_pushinteger(L, 1);
lua_newtable(L);
lua_pushinteger(L, 123);
lua_pushstring(L, "lmn");
lua_settable(L, -3);
lua_pushstring(L, "opq");
lua_pushinteger(L, 777);
lua_settable(L, -3);
lua_settable(L, -3);
stack_dump(L);
lua_close(L);
}--------------- Stack Dump ----------------
5:(Table):`0x243B6C03438`
{
false: 1,
true: 0,
F_243B6C034B8: "abc",
1:
{
123: "lmn",
"opq": 777,
}
"def": "ghk",
LU_000456: 4,
}
4:(LUserdata):`0x123`
3:(String):`abc`
2:(Function):`0x243B6C033B8`
1:(Boolean):`true`
----------- Stack Dump Finished -----------