最近在写一个 Lua 库,用于打印报错堆栈上的函数参数和 upvalue,最开始用 Lua 实现了一个版本,运行效果还可以。想着用 C 来实现一遍,经过一段时间的奋战,终于能跑了。
运行效果展示
测试代码:
local tb = require "traceback.c"
debug.traceback = tb.traceback
local a = "test upvalue a"
local function f(a, b, c, d, e)
print("in f")
local x = a
error("my test error")
end
local function f1(a, b, c, d, e)
print("in f1")
f(a,b,c,d,e)
end
local c = {2,2.22}
local a = {
b = {"x","y"},
c = c,
}
a.b.xx = c
a.c.yy = a.b
a.c.zz = a.b.xx
local ok, msg = xpcall(f1, debug.traceback, a, "xs", 1.22, 100)
print(msg)
运行效果大概是这样的:
test.lua:9: my test error
stack traceback:
[C]: in function 'error'
test.lua:9: in upvalue 'f'
<arg 1> a = {['c']={[1]=2,[2]=2.22,,['yy']={...}},}
a['c']['zz'] = a['c']
a['b'] = a['c']['yy']
<arg 2> b = "xs"
<arg 3> c = 1.22
<arg 4> d = 100
<arg 5> e = nil
<upv 1> _ENV = {['os']={['difftime']=function: 0x560853d17e20,['date']=function: 0x560853d18250,['setlocale']=function: 0x560853d17bf0,['clock']=function: 0x560853d17e70...},['rawlen']=function: 0x560853d11520,['tostring']=function: 0x560853d112e0,['warn']=function: 0x560853d115d0,['select']=function: 0x560853d11310...}
test.lua:14: in function <test.lua:12>
<arg 1> a = {['c']={[1]=2,[2]=2.22,,['yy']={...}},}
a['c']['zz'] = a['c']
a['b'] = a['c']['yy']
<arg 2> b = "xs"
<arg 3> c = 1.22
<arg 4> d = 100
<arg 5> e = nil
<upv 1> _ENV = {['os']={['difftime']=function: 0x560853d17e20,['date']=function: 0x560853d18250,['setlocale']=function: 0x560853d17bf0,['clock']=function: 0x560853d17e70...},['rawlen']=function: 0x560853d11520,['tostring']=function: 0x560853d112e0,['warn']=function: 0x560853d115d0,['select']=function: 0x560853d11310...}
<upv 2> f = function: 0x560853f186f0
[C]: in function 'xpcall'
test.lua:26: in main chunk
<upv 1> _ENV = {['os']={['difftime']=function: 0x560853d17e20,['date']=function: 0x560853d18250,['setlocale']=function: 0x560853d17bf0,['clock']=function: 0x560853d17e70...},['rawlen']=function: 0x560853d11520,['tostring']=function: 0x560853d112e0,['warn']=function: 0x560853d115d0,['select']=function: 0x560853d11310...}
[C]: in ?
During its normal operation, a string buffer uses a variable number of stack slots. So, while using a buffer, you cannot assume that you know where the top of the stack is.
一般的操作过程中,字符串缓存会使用不定量的栈槽。 因此,在使用缓存中,你不能假定目前栈顶在哪。 在对缓存操作的函数调用间,你都可以使用栈,只需要保证栈平衡即可; 即,在你做一次缓存操作调用时,当时的栈位置和上次调用缓存操作后的位置相同。 (对于 luaL_addvalue 是个唯一的例外。) 在调用完 luaL_pushresult 后, 栈会恢复到缓存初始化时的位置上,并在顶部压入最终的字符串。
最近在写一个 Lua 库,用于打印报错堆栈上的函数参数和 upvalue,最开始用 Lua 实现了一个版本,运行效果还可以。想着用 C 来实现一遍,经过一段时间的奋战,终于能跑了。
运行效果展示
测试代码:
运行效果大概是这样的:
遇到问题
但是会偶然的出现 core dumped 。由于是偶现的, gdb 好像也不好调试,就只能加打印,批量重试了。 写个脚本批量运行,把 core 文件和日志文件对应起来。
遇到报错就停下来。 然后也借助 gdb 来查看 core 文件,发现 gdb 竟然有个 TUI 界面。使用
tui enable
开启 TUI 界面,界面是会自动显示当前代码文件的,按方向键上下滚动代码。 ![[Pasted image 20230211143947.png]] 使用up/down
切换堆栈层级,代码会跟着切换。排除法定位问题
怀疑是 lua_Buffer 的问题,注释部分代码试试看。怀疑 luaL_tolstring 函数会影响,先修改不调用这个函数。
怀疑 lua_Buffer 的问题,可以把 lua_Buffer 注释掉,只打印序列化出的数据,或者用其他方法来存储序列化后的数据。怀疑 lua_Buffer 的原因是调试的时候发现 lua_pushvalue 经常会把 buffer 的数据弄成乱码,可能是
_ENV
里有特殊字符。通过注释 lua_Buffer 相关代码,目前跑了一天还没遇到过报错。
或许是不是不能同时使用 2 个 buffer 呢?可以研究一下 lua_Buffer 的代码。
通过排除法,已经定位到是 Buffer 的问题,具体是加了什么特殊字符问题引起的还没定位到。特殊字符可能是
_ENV
里的。再使用排除法,只保留
已经重现到。
再试试只序列化 ENV 的情况,不处理堆栈的情况。终于知道原因了,原来几年前就遇到过。 https://github.com/hanxi/lua-seri/issues/1
终于理解这句话的意思了,就是每次操作 buffer 前后,需要保持栈不变。所以解决方法也就是 issues 里提出的 2 个,自己实现一个 buffer 或者把数据缓存到一个表里,最后才使用 buffer 合并。甚至包括 luaL_bufferinit ,这也就说明,必须保证 buffer 在栈顶了。因为无法使用此办法来序列化了。
还特意去 Lua 邮件列表里问了这个 buffer 的用法: http://lua-users.org/lists/lua-l/2023-02/msg00082.html
最终的解决办法是使用
luaL_buffinitsize
申请一段固定长度的 buffer, 采用memcpy
写入数据。最终成果: https://github.com/hanxi/ltraceback
顺便把之前写的一个序列化 lua 数据的库的 bug 修复好了。 https://github.com/hanxi/lua-seri