记录一个 NixOS 下 WeChat crash
记录一次查 bug 指南,
Core was generated by `/nix/store/rywj8pppkbi2adnxxjvwqkb609j9zcvh-wechat-4.0.1/wechat/wechat'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000000000000000 in ?? ()
[Current thread is 1 (LWP 50442)]
(gdb) bt
#0 0x0000000000000000 in ?? ()
#1 0x0000560ab3e80c66 in ?? ()
#2 0x0000560ab5598fd8 in ?? ()
#3 0x00007f0b8c01d6f0 in ?? ()
#4 0x0000560ad9073450 in ?? ()
#5 0x0000560ab3e81cb9 in ?? ()
#6 0x00007f0b8c00a2a0 in ?? ()
#7 0x0000560ab273cba8 in ?? ()
#8 0x0000000200000000 in ?? ()
运行微信后执行 gdb -p (pgrep wechat)
attach 上去,现象是只要收到对面发来的消息就一定会崩溃。
因为没有符号,只能看原始的反汇编了,
(gdb) frame 1
(gdb) disass 0x0000560ab3e80c66-0x30,0x0000560ab3e80c66+0x20
Dump of assembler code from 0x560ab3e80c36 to 0x560ab3e80c86:
0x0000560ab3e80c36: movzbl 0x1c338bb(%rip),%eax 0x0000560ab3e80c3d: test %al,%al
0x0000560ab3e80c3f: je 0x560ab3e80d0d
0x0000560ab3e80c45: mov 0x1c338a4(%rip),%rax 0x0000560ab3e80c4c: mov 0x10(%rbx),%rdi
0x0000560ab3e80c50: lea 0x2ac(%rbx),%rdx
0x0000560ab3e80c57: lea -0x8063590(%rip),%rsi 0x0000560ab3e80c5e: xor %ecx,%ecx
0x0000560ab3e80c60: call *0x140(%rax)
=> 0x0000560ab3e80c66: mov %rax,0x290(%rbx)
0x0000560ab3e80c6d: test %rax,%rax
0x0000560ab3e80c70: je 0x560ab3e80cd6
0x0000560ab3e80c72: movzbl 0x1c3387f(%rip),%eax
因为栈帧 0 对应的是 NULL,没有什么意义,就直接看栈帧 1 的,crash 的点应该就是 call *0x140(%rax)
这里,也就是说这个是个函数指针,而且空了。
摸索了好久没有什么头绪,后来突然想到可以看看 call 传递的参数,
(gdb) info reg
rax 0x7f0b8c009e60 139687570218592
rbx 0x7f0b8c01d6f0 139687570298608
rcx 0x0 0
rdx 0x7f0b8c01d99c 139687570299292
rsi 0x560aabe1d6ce 94603833366222
rdi 0x0 0
rbp 0xaaaaaaaaaaaaaaaa 0xaaaaaaaaaaaaaaaa
rsp 0x7f0b9effbd10 0x7f0b9effbd10
r8 0x0 0
r9 0x0 0
r10 0x0 0
这里就可以依次去解析一下 $rdi,$rdx,$rsi
这些寄存器了。
果然,在看到 $rsi
寄存器值的时候,就有点豁然开朗了,
(gdb) x/150bc $rsi-0x50
0x560aabe1d67e: 77 'M' 69 'E' 83 'S' 0 '\000' 91 '[' 37 '%' 115 's' 93 ']'
0x560aabe1d686: 91 '[' 37 '%' 115 's' 58 ':' 37 '%' 100 'd' 93 ']' 0 '\000'
0x560aabe1d68e: 80 'P' 117 'u' 108 'l' 115 's' 101 'e' 65 'A' 117 'u' 100 'd'
0x560aabe1d696: 105 'i' 111 'o' 83 'S' 121 'y' 115 's' 116 't' 101 'e' 109 'm'
0x560aabe1d69e: 58 ':' 58 ':' 111 'o' 112 'p' 101 'e' 110 'n' 95 '_' 114 'r'
0x560aabe1d6a6: 101 'e' 99 'c' 111 'o' 114 'r' 100 'd' 95 '_' 115 's' 116 't'
0x560aabe1d6ae: 114 'r' 101 'e' 97 'a' 109 'm' 32 ' ' 102 'f' 97 'a' 105 'i'
0x560aabe1d6b6: 108 'l' 101 'e' 100 'd' 44 ',' 32 ' ' 115 's' 116 't' 114 'r'
0x560aabe1d6be: 101 'e' 97 'a' 109 'm' 32 ' ' 105 'i' 115 's' 32 ' ' 110 'n'
0x560aabe1d6c6: 117 'u' 108 'l' 108 'l' 112 'p' 116 't' 114 'r' 46 '.' 0 '\000'
0x560aabe1d6ce: 119 'w' 119 'w' 107 'k' 32 ' ' 112 'p' 108 'l' 97 'a' 121 'y'
0x560aabe1d6d6: 32 ' ' 115 's' 116 't' 114 'r' 101 'e' 97 'a' 109 'm' 0 '\000'
0x560aabe1d6de: 112 'p' 97 'a' 95 '_' 99 'c' 111 'o' 110 'n' 116 't' 101 'e'
0x560aabe1d6e6: 120 'x' 116 't' 95 '_' 110 'n' 101 'e' 119 'w' 95 '_' 119 'w'
0x560aabe1d6ee: 105 'i' 116 't' 104 'h' 95 '_' 112 'p' 114 'r' 111 'o' 112 'p'
0x560aabe1d6f6: 108 'l' 105 'i' 115 's' 116 't' 0 '\000' 112 'p' 97 'a' 95 '_'
0x560aabe1d6fe: 112 'p' 114 'r' 111 'o' 112 'p' 108 'l' 105 'i' 115 's' 116 't'
0x560aabe1d706: 95 '_' 103 'g' 101 'e' 116 't' 115 's' 0 '\000' 112 'p' 97 'a'
0x560aabe1d70e: 95 '_' 115 's' 97 'a' 109 'm' 112 'p' 108 'l'
出错地址对应的字符串是 wwk play stream
,看不出个啥端倪出来,但是按照局部性”原理“(其实就是同一个源文件的字符串一般编译的时候都会放到比较接近的地方),可以发现有比较多的 %s
这种 printf 输出格式,也就间接的印证了这段内存映射为只读的区域(可以通过 info files
或者 info proc mappings
找到段属性)就是存放的日志打印字符串。
同样是局部性原理,粗略翻阅可以发现基本都是音频相关的,尤其是后面出现了如 pa_context_new_with_proplist
以及 pa_proplist_gets
等百度一搜就是 PulseAudio 相关的接口函数,基本判断是 PulseAudio 的符号没有被 resolve。
再次查证 info proc mappings
,发现确实是没有 libpulseaudio.so
等相关的符号的,说明微信应该没有对 dlopen
的返回值进行判断,导致最终发生空指针异常。
NixOS 仓库 https://github.com/NixOS/nixpkgs/pull/354332 也有人提出相同的问题,回答一波看看其他人是不是也是因为 libpulseaudio.so
不在 ld.so 搜索路径中的,我自己的解决方法就是 wrap 中增加 LD_LIBRARY_PATH
来保证搜索正常,
{ installPhase = ''
wrapProgram $out/wechat/wechat \
--prefix LD_LIBRARY_PATH : ${
lib.makeLibraryPath [
libGL
udev
libpulseaudio
]
}
''; }