一个链接 lua 引起的 bug , 事不过三
今天花了将近 3 个小时帮同事看一个崩在 lua VM 中的 bug 结果打乱了进度,没有在年前把预想的东西做完。其实说起来这不是个大问题,以前也碰到过。我检讨自己没有在看到出错时的调用栈时去看一眼 lua 相关的代码。如果是那样,因为以前遇到过同样的问题,所以就可以条件反射出问题原因,而不用荒废宝贵了数小时时间了。
唉,这下整合的进度没接上,过年不能自己一个人接着做下面的活了。
下面记录一下这个 bug ,提醒自己第三次遇到时不用再花时间找问题:
问题的缘由是因为的不同的 lua 扩展库链接的时候多链接了一份 lua 库,导致进程内有超过一份 lua 库。这是在写 Makefile 时不小心造成的。知道原因后自然很容易解决。要么把 lua 编译成 .so ;要么只有嵌入的主框架使用 -E 参数静态链接 lua 的静态库。这里就不展开说了。
用了 lua 十年,绝对不是第一次帮其他用 lua 的同学指出这个问题。下面要讨论的是错误产生后,程序是怎么崩溃的。
lua 的代码中几乎没有使用全局变量。就是说,lua 的 api 的相关状态仅存在于参数 L 中,而和库无关。这点 lua 实现的是很漂亮的。对于一个无外部状态的 api ,理论上无论代码段在进程内被链接再多次,都不会有副作用的。但副作用的确又发生了,为何?
问题出在 ltable.c 中引用了一个静态全局变量 dummynode_
。这个东西是做什么用的呢?
在 lua table 实现中,为了不让 hash part 为空的时候不引用 NULL 指针(一种优化),而引用了这个 dummynode ,这样就可以减少操作表时的判断操作。
由于这个 dummynode 是静态分配出来的特殊节点,所以是不能调用内存管理函数去释放它的。lua 另外用一个宏来判断一个节点是否为 dummynode 。
#define isdummy(n) ((n) == dummynode)
当进程中链接了多份 lua 库时,就出现了多份 dummynode 对象,自然 isdummy 的行为就可能不正确了。那么接下来的释放工作则会崩掉,看起来段错误会发生在 lua_Alloc
中。
问题多出现在运行过程中,向空 table 插入第一个 hash 值时,也就是 luaH_resize
函数的最后一行:
if (!isdummy(nold)) luaM_freearray(L, nold, cast(size_t, twoto(oldhsize))); /* free old array */
或是关闭 lua state 时,释放 table 时,的 luaH_free
函数最前面:
if (!isdummy(t->node)) luaM_freearray(L, t->node, cast(size_t, sizenode(t)));
之所以我把代码和出错位置列出来,是因为我今天不是第一次专门分析这个点了。如果我下午在刚出问题时,能够在看到 stack traceback 信息的函数名)luaH_resize
打开 lua 的源代码看一眼的话,我相信我能立刻回想起上次分析这个问题的情景,从而节省掉后来浪费在查别的地方上的无尽时间。
希望经过这次,下次有人向我反应类似问题时,我能直接对应到问题。
4 月 19 日补充:
如何在出现这个问题时立刻检查出问题呢?
可以在每个为 lua 写的 C 扩展库的第一行调用一下 luaL_checkversion
, 它可以检查有没有多次链接 lua 库, 以及其它一些版本问题.
Comments
Posted by: Anonymous | (10) March 4, 2014 08:41 PM
Posted by: Cloud | (9) July 11, 2013 04:40 PM
Posted by: damon | (8) July 10, 2013 09:31 AM
Posted by: wellbye | (7) February 5, 2012 08:36 PM
Posted by: 虔诚的学者 | (6) February 1, 2012 10:14 PM
Posted by: Raindy Long | (5) January 20, 2012 01:41 AM
Posted by: okzai | (4) January 19, 2012 09:50 PM
Posted by: Davy.xu | (3) January 19, 2012 08:38 PM
Posted by: suerey | (2) January 19, 2012 08:30 PM
Posted by: resty | (1) January 19, 2012 07:42 PM