SBCL x86 win32版本‘alien callback’的bug及解决方案
SBCL win32版的官方发布版本,最近几个版本(从1.2.8到最新的1.2.13),一直存在一个很烦人的bug,在控制台界面输入Ctrl+C组合键时,SBCL就会异常退出。在SBCL里面,使用Ctrl+C本来可以中断/停止正在运行的程序。
我从1.2.11版本开始注意到有这个问题,在1.2.11之前我使用的1.2.7版本是正常的。现在发布了1.2.13问题仍然没有解决。Google了一下,原来也有人发现了这个问题,并且和SBCL的开发人员有过交流,不过没有找到问题原因。他们的讨论可以参考这个帖子。
这位老兄也是厉害,他根据代码提交记录,一个版本一个版本的尝试,发现了其中一个commit之后就出了问题。但是开发人员站出来说,这次提交基本没有功能变化,只是整理了一下代码,把sb-saftepoint和sb-thread的feature判断换了一部分(类似于C里面的条件编译),但是在现在的版本中这两个feature是同时打开的,所以也不应有问题。我也去查看了这次提交的代码情况,的确找不到有什么疑点。
这一段时间我自己尝试分析这个bug,断断续续折腾了好几天,终于发现了问题所在。最后的原因比较简单,但是过程曲折,由于一开始的方法不对,花了很多时间在一些SBCL实现方面的原理的学习上。如果是对于普通的一个应用程序,应该分析起来没那么困难,但是SBCL用gdb、visual studio等常用的调试器分析都不方便。为了阅读方便,我先直接描述问题原因和解决办法,再将相关的背景知识和调试经验简单介绍一下。
问题原因简单的说就是:GCC优化不当。
问题现象打开sbcl,按Ctrl+C,即出现经典的windows程序异常通知窗口。如果电脑上安装有调试器比如VS20XX,那么会提示是否立即进行调试。
其实SBCL没有就此死去,如果你把异常报告框拖到一边,SBCL仍然能继续使用,但是不能在对Ctrl-C进行响应了。
这说明SBCL主线程还运行正常!
问题代码在SBCL的runtime模块,代码位置src/runtime/thread.c
简化起见,引用源代码时我会删除不想关的部分,感兴趣的读者可以直接查看sbcl的源码。
请看detach_os_thread和 callback_wrapper_trampoline,这两个函数的开头第一句是相同的,都是获取thread结构指针。而这两个函数在同一个文件(thread.c),这给gcc提供了一个优化的机会,他认为在detach_os_thread的时候,th还没有变化,所以把detach_os_threadinline到callback_wrapper_trampoline中了,并且把detach_os_thread的第一条语句去掉了。
void detach_os_thread(init_thread_data *scribble) { struct thread *th = arch_os_get_current_thread();</span> undo_init_new_thread(th, scribble); odxprint(misc, "deattach_os_thread: detached"); pthread_setspecific(lisp_thread, (void *)0); thread_sigmask(SIG_SETMASK, &scribble->oldset, 0); }其实,th在if判断之前是NULL,但是在attach_os_thread里面会赋有效值。在detach_os_thread中根据新的th做一些清理的工作. 现在既然获取新的th的语句被优化掉了,那么到了undo_init_new_thread的时候,就变成了访问非法指针了, 异常就这么出现了.
static void undo_init_new_thread(struct thread *th, init_thread_data *scribble) { int lock_ret; //... gc_alloc_update_page_tables(BOXED_PAGE_FLAG, &th->alloc_region);当然我们还要看看到底th是怎么获取的(thread.h):
static inline struct thread* arch_os_get_current_thread() __attribute__((__const__)); static inline struct thread *arch_os_get_current_thread(void) { register struct thread *me=0; __asm__ ("movl %%fs:0xE10+(4*63), %0" : "=r"(me) :); return me; }这个函数定义还是略有可疑的,他不仅声明为inline,还声明为__attribute__((__const__)),这表示这个函数结果只依赖参数或者全局变量。如此是不是就可以大胆优化了?是不是这个定义有问题?
但是这个还不够,最终确认还是得依靠汇编代码,只有汇编代码才不骗人。callback_wrapper_trampoline的反汇编如下,可以看出在执行funcll3和undo_init_new_thread之间并没有重新获取th.
Dump of assembler code for function callback_wrapper_trampoline: 0x00416e20 <+0>: push %ebp 0x00416e21 <+1>: mov %esp,%ebp 0x00416e23 <+3>: push %edi 0x00416e24 <+4>: push %esi 0x00416e25 <+5>: push %ebx 0x00416e26 <+6>: sub $0x1c,%esp 0x00416e29 <+9>: mov 0x10(%ebp),%ebx 0x00416e2c <+12>: call 0x41cb40 <pthread_np_notice_thread> 0x00416e31 <+17>: mov %fs:0xf0c,%eax ;; 读取th 0x00416e37 <+23>: test %eax,%eax ;; 判断th是不是0,如果是0跳转 0x00416e39 <+25>: je 0x416ed1 <callback_wrapper_trampoline+177> ;; 0x00416ed1 <+177>: lea -0x18(%ebp),%esi ;;跳转到这里 0x00416ed4 <+180>: mov %esi,(%esp) 0x00416ed7 <+183>: call 0x416b50 <attach_os_thread> ;;执行attach_os_thread 0x00416edc <+188>: mov 0xc(%ebp),%eax 0x00416edf <+191>: mov %ebx,0xc(%esp) 0x00416ee3 <+195>: mov %eax,0x8(%esp) 0x00416ee7 <+199>: mov 0x8(%ebp),%eax 0x00416eea <+202>: mov %eax,0x4(%esp) 0x00416eee <+206>: mov 0x22100664,%eax 0x00416ef3 <+211>: and $0xfffffff8,%eax 0x00416ef6 <+214>: mov 0x8(%eax),%eax 0x00416ef9 <+217>: mov %eax,(%esp) 0x00416efc <+220>: call 0x403440 <funcall3> ;;执行funcall3 0x00416f01 <+225>: mov 0x4420d4,%ebx 0x00416f07 <+231>: test %ebx,%ebx 0x00416f09 <+233>: jne 0x416f50 <callback_wrapper_trampoline+304> 0x00416f0b <+235>: xor %eax,%eax 0x00416f0d <+237>: mov %esi,%edx 0x00416f0f <+239>: call 0x4166d0 <undo_init_new_thread> ;;执行undo_init_new_thread 0x00416f14 <+244>: mov 0x4420d4,%ecx 0x00416f1a <+250>: test %ecx,%ecx 0x00416f1c <+252>: je 0x416f2a <callback_wrapper_trampoline+266> 0x00416f1e <+254>: movl $0x43ec7e,(%esp) 0x00416f25 <+261>: call 0x40bfe0 <odxprint_fun> 0x00416f2a <+266>: call 0x41c7a0 <pthread_self> 0x00416f2f <+271>: mov 0x442104,%ebx 0x00416f35 <+277>: mov -0x18(%ebp),%edx 0x00416f38 <+280>: movl $0x0,0x33c(%eax,%ebx,4) 0x00416f43 <+291>: mov %edx,0x14(%eax) 0x00416f46 <+294>: jmp 0x416e73 <callback_wrapper_trampoline+83> 0x00416f4b <+299>: nop 0x00416f4c <+300>: lea 0x0(%esi,%eiz,1),%esi 0x00416f50 <+304>: movl $0x43ec61,(%esp) 0x00416f57 <+311>: call 0x40bfe0 <odxprint_fun> 0x00416f5c <+316>: jmp 0x416f0b <callback_wrapper_trampoline+235> 如何解决温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/69491.html