记录一个 NixOS 下 WeChat crash

2025-3-11

记录一次查 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
#1  0x0000560ab3e80c66 in ?? ()
(gdb) disass 0x0000560ab3e80c66-0x30,0x0000560ab3e80c66+0x20
Dump of assembler code from 0x560ab3e80c36 to 0x560ab3e80c86:
   0x0000560ab3e80c36:	movzbl 0x1c338bb(%rip),%eax        # 0x560ab5ab44f8
   0x0000560ab3e80c3d:	test   %al,%al
   0x0000560ab3e80c3f:	je     0x560ab3e80d0d
   0x0000560ab3e80c45:	mov    0x1c338a4(%rip),%rax        # 0x560ab5ab44f0
   0x0000560ab3e80c4c:	mov    0x10(%rbx),%rdi
   0x0000560ab3e80c50:	lea    0x2ac(%rbx),%rdx
   0x0000560ab3e80c57:	lea    -0x8063590(%rip),%rsi        # 0x560aabe1d6ce
   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        # 0x560ab5ab44f8

因为栈帧 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
      ]
    }
''; }
https://pen.guru/posts/feed.xml