Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

调试 Lua 的 C 库报错记录 #88

Open
hanxi opened this issue Feb 18, 2023 · 0 comments
Open

调试 Lua 的 C 库报错记录 #88

hanxi opened this issue Feb 18, 2023 · 0 comments

Comments

@hanxi
Copy link
Owner

hanxi commented Feb 18, 2023

最近在写一个 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 ?

遇到问题

但是会偶然的出现 core dumped 。由于是偶现的, gdb 好像也不好调试,就只能加打印,批量重试了。
写个脚本批量运行,把 core 文件和日志文件对应起来。

while true; do
    ../lua-5.4.4/src/lua test.lua > tmp.log 2>&1 &
    pid=$!
    wait $!
    err=$?
    if [ $err -ne 0 ]; then
        mv tmp.log log.$pid
        exit 1
    fi
    sleep 2
done

遇到报错就停下来。
然后也借助 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 里的。

再使用排除法,只保留

static void
seri_other(lua_State *L, int index, luaL_Buffer *b) {
    size_t len;
    const char *str = luaL_tolstring(L, index, &len);
    fprintf(stderr, "seri_other:%s\n", str);
    luaL_addvalue(b);
}

// 另外保留一些插入明显是字符或者字符串的
luaL_addstring(b, "...");
luaL_addchar(b, '"');

已经重现到。

再试试只序列化 ENV 的情况,不处理堆栈的情况。终于知道原因了,原来几年前就遇到过。
hanxi/lua-seri#1

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 后, 栈会恢复到缓存初始化时的位置上,并在顶部压入最终的字符串。

终于理解这句话的意思了,就是每次操作 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant