查看原文
其他

腾讯会议为何不闪即退

格蠹老雷 格友 2023-06-10








腾讯会议为何不闪即退

2023/05/02









最近一段时间里,我的背包里总是装着一新一旧两台笔记本,它们都是灰色的,屏幕都是14英寸,外壳的长度是一样的。论差异,新的比旧的明显薄很多,也轻很多。




YOURLAND


除了外表的差别外,它们更大的差别是“内心”:旧的是Wintel(Windows + X86),新的是ARM + Linux。旧的叫ThinkPad,新的叫幽兰。


用了几十年Wintel之后,我决意要迁移到新的ARM + Linux了。要实现这个迁移,数量众多的应用软件是最大的困难。比如,视频会议软件的问题就不大好解决。以Zoom和腾讯会议这两个著名的会议系统软件为例,Zoom的网站上对LINUX系统只有X86版本供下载,而腾讯会议虽然有ARM64的版本,但是装了之后不闪即退。




具体来说,从腾讯会议官网下载腾讯会议的ARM64版本客户端后,可以在Downloads目录中找打一个大约146MB的安装包(deb文件):

TencentMeeting_0300000000_3.14.0.402_arm64_default.publish.deb


打开终端窗口,执行如下命令安装:

sudo dpkg -i ~/Downaloads/TencentMeeting_0300000000_3.14.0.402_arm64_default.publish.deb

安装过程是比较顺利的,安装后,应用程序列表中会新增一个腾讯会议的图标,名叫WemeetApp。


但是点击这个图标试图启动它时,根本没有看到任何腾讯会议的界面,而是回到空的桌面,根据多年的经验,看起来是启动时失败了,失败的时间点可能比较早,所以根本没有机会显示出任何窗口,哪怕是小小的一个对话框。


几周前就听小伙伴说起过这个问题,但是我一直没有空深究。这几天五一小长假,我特意分出一段时间来调查这个问题。


图形方式启动看到的信息太有限了,为了便于分析,我打开一个终端窗口,切换到存放第三方程序的/opt目录,ls一下,果然看到wemeet目录,进去后看到几个子目录,还有一个脚本文件:wemeetapp.sh

bin  lib  plugins  resources  translations  wemeetapp.sh  wemeet.png  wemeet.svg

读一下这个这个脚本,看起来就是用来启动应用程序的。


手工执行这个脚本,可以看到一些错误信息了:

开始的四行错误信息是关于图形子系统(DRI)的,我最初以为是这几个错误导致无法启动,但是网上搜索一番后得知,这些错误信息可能并无大碍,在多年前就有很多讨论了。接下来的LoadCustomFont(加载定制字体)信息,也证明了这一点,说明在DRI错误后程序的逻辑仍在前进。


两行YaHei(雅黑)信息后,有一行是:

Failed to open file ‘/opt/wemeet/bin/xcast.conf’

无法打开配置文件,我核对了一下,的确没有这个配置文件。


接下来的一系列信息都有XNN字样,感觉是在初始化腾讯会议的内部设施了,比如XNNContext和XNNRTResource。


或许再跑几步,界面就出现了,但这时致命的事件发生了:

Segmentation fault(段错误)

段错误是Linux下应用程序崩溃的最常见原因,这个错误的名字本身也是错误,它的真正含义其实大多时候都是页错误,相当于Windows下的访问违例(access violation),因为历史原因叫了这个名字,不好改了。


另外值得注意的是,虽然段错误信息后,有core dumped字样,但是其实默认并不会产生core文件。




执行如下步骤,

可以启用core文件:

ulimit -c unlimitedecho /var/tmp/%p.core

启用后,再重现错误,果然在/var/tmp下有core文件了:

geduer@ulan:/var/tmp$ ls6770.core


接下来启动gdb

分析core文件:

gdb --core /var/tmp/6770.core /opt/wemeet/bin/wemeetapp

GDB启动后,列出很多线程后,显示出如下崩溃现场:

执行thread apply all bt观察各个线程的执行情况,可以看到,进程里一共有45个线程,很多线程已经是就绪状态,进入等待函数。

28号线程看起来是腾讯会议的业务线程,代码所属的模块名为libwemeet_base.so,它在调用select系统调用等待数据。

Thread 28 (Thread 0x7f44ff8fd0 (LWP 6800)):#0  0x0000007fb1844620 in __GI___select (nfds=<optimized out>, readfds=0x7f44ff85d8, writefds=0x0, exceptfds=0x0, timeout=0x7f44ff85c8) at ../sysdeps/unix/sysv/linux/select.c:53#1  0x0000007faf863c38 in ?? () from /opt/wemeet/lib/libwemeet_base.so#2  0x0000007faf867f4c in ?? () from /opt/wemeet/lib/libwemeet_base.so#3  0x0000007faf867e50 in ?? () from /opt/wemeet/lib/libwemeet_base.so#4  0x0000007faf867df4 in ?? () from /opt/wemeet/lib/libwemeet_base.so#5  0x0000007faf867d94 in ?? () from /opt/wemeet/lib/libwemeet_base.so#6  0x0000007faf867b30 in ?? () from /opt/wemeet/lib/libwemeet_base.so#7  0x0000007fb1a88fac in ?? () from /lib/aarch64-linux-gnu/libstdc++.so.6#8  0x0000007fa36a4624 in start_thread (arg=0x7fb1a88f90) at pthread_create.c:477#9  0x0000007fb184b49c in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone.S:78



再回到崩溃现场

执行bt命令观察调用栈,可以看到是在调用eglGetDisplay时触发了钩子逻辑:


(gdb) bt#0  0x0000007f9efc508c in fixup_x11_display (display=0x1) at ../hook/hook.c:462#1  0x0000007f9efc5104 in eglGetDisplay (display_id=<optimized out>) at ../hook/hook.c:485#2  0x0000007fa1beccd8 in ?? () from /opt/wemeet/lib/libxcast.so

查阅EGL的官方文档,可以看到eglGetDisplay函数的详细说明。某种程度来说,这个函数与Windows的获取屏幕DC(GetDC(NULL))差不多,就是获取整个屏幕的显示上下文。


https://registry.khronos.org/EGL/sdk/docs/man/html/eglGetDisplay.xhtml


崩溃发生在fixup_x11_display函数中,它的源文件名叫hook.c,行号为462行。腾讯没有剥离这个模块的符号,让我们可以看到源代码信息,这可能有两个原因:

01/

这个模块经常出问题

02/

这个模块包含的不是腾讯会议的核心逻辑

因为有符号,所以我们可以看当前函数的参数信息:

(gdb) info argsdisplay = 0x1

观察这个参数的类型,可以看到它是指向一个局大结构体的指针:

(gdb) pt displaytype = struct _XDisplay {    XExtData *ext_data;    struct _XFreeFuncs *free_funcs;    int fd;    int conn_checker;    int proto_major_version;    int proto_minor_version;    char *vendor;    XID resource_base;    XID resource_mask;    XID resource_id;    int resource_shift;    XID (*resource_alloc)(struct _XDisplay *);    int byte_order;    int bitmap_unit;    int bitmap_pad;    int bitmap_bit_order;    int nformats;    ScreenFormat *pixmap_format;    int vnumber;    int release;    struct _XSQEvent *head;    struct _XSQEvent *tail;    int qlen;    unsigned long last_request_read;    unsigned long request;    char *last_req;    char *buffer;    char *bufptr;    char *bufmax;    unsigned int max_request_size;    struct _XrmHashBucketRec *db;--Type <RET> for more, q to quit, c to continue without paging--    int (*synchandler)(struct _XDisplay *);    char *display_name;    int default_screen;    int nscreens;    Screen *screens;    unsigned long motion_buffer;    volatile unsigned long flags;    int min_keycode;    int max_keycode;    KeySym *keysyms;    XModifierKeymap *modifiermap;    int keysyms_per_keycode;    char *xdefaults;    char *scratch_buffer;    unsigned long scratch_length;    int ext_number;    struct _XExten *ext_procs;    int (*event_vec[128])(Display *, XEvent *, xEvent *);    int (*wire_vec[128])(Display *, XEvent *, xEvent *);    KeySym lock_meaning;    struct _XLockInfo *lock;    struct _XInternalAsync *async_handlers;    unsigned long bigreq_size;    struct _XLockPtrs *lock_fns;    void (*idlist_alloc)(Display *, XID *, int);    struct _XKeytrans *key_bindings;    Font cursor_font;    struct _XDisplayAtoms *atoms;    unsigned int mode_switch;    unsigned int num_lock;    struct _XContextDB *context_db;    int (**error_vec)(Display *, XErrorEvent *, xError *);--Type <RET> for more, q to quit, c to continue without paging--    struct {        XPointer defaultCCCs;        XPointer clientCmaps;        XPointer perVisualIntensityMaps;    } cms;    struct _XIMFilter *im_filters;    struct _XSQEvent *qfree;    unsigned long next_event_serial_num;    struct _XExten *flushes;    struct _XConnectionInfo *im_fd_info;    int im_fd_length;    struct _XConnWatchInfo *conn_watchers;    int watcher_count;    XPointer filedes;    int (*savedsynchandler)(Display *);    XID resource_max;    int xcmisc_opcode;    struct _XkbInfoRec *xkb_info;    struct _XtransConnInfo *trans_conn;    struct _X11XCBPrivate *xcb;    unsigned int next_cookie;    int (*generic_event_vec[128])(Display *, XGenericEventCookie *, xEvent *);    int (*generic_event_copy_vec[128])(Display *, XGenericEventCookie *, XGenericEventCookie *);    void *cookiejar;} *

既然是指针,那么这个指针的值就很古怪,是1。在Linux这样的软件世界里,1不是有效地址,属于空指针。


那么这个1是GDB的显示错误,

还是的确如此呢?


我们可以尝试通过寄存器来核对。

(gdb) info registersx0             0x0                 0x1             0x7f9efc54e0        548128183520x2             0x53                83x3             0x5655              22101x4             0x8                 8x5             0x20                32x6             0x68437364466b6664  7512975477899486820x7             0x7f7f7f7f7f7f7f7f  9187201950435737471x8             0x101010101010101   72340172838076673x9             0x7eec000bb0        545125305264x10            0x0                 0x11            0x30                48x12            0x7f9efc4260        548128178784x13            0x0                 0x14            0x0                 0x15            0x20                32x16            0x7f9efd6060        548128252000x17            0x7fb17afad0        548438473424x18            0x0                 0x19            0x1                 1x20            0x0                 0x21            0x0                 0x22            0x7fa2625f59        548185202521x23            0x7fa2625f83        548185202563x24            0x7eec000b60        545125305184x25            0x7fa2896000        548187758592x26            0x7efa7fbfd0        545368555472x27            0x7fa36c9000        548202647552x28            0x7efa7fbfd0        545368555472x29            0x7efa7fb640        545368553024x30            0x7f9efc5074        548128182388sp             0x7efa7fb640        0x7efa7fb640pc             0x7f9efc508c        0x7f9efc508c <fixup_x11_display+52>cpsr           0x20001000          [ EL=0 SSBS C ]fpsr           0x1f                31fpcr           0x0                 0


寄存器x19的值是1,

它是参数么?


执行info frame观察帧信息:

(gdb) info frameStack level 0, frame at 0x7efa7fb670: pc = 0x7f9efc508c in fixup_x11_display (../hook/hook.c:462); saved pc = 0x7f9efc5104 called by frame at 0x7efa7fb680 source language c. Arglist at 0x7efa7fb640, args: display=0x1 Locals at 0x7efa7fb640, Previous frame's sp is 0x7efa7fb670 Saved registers:  x19 at 0x7efa7fb650, x20 at 0x7efa7fb658, x29 at 0x7efa7fb640, x30 at 0x7efa7fb648

x19的本来值保存到内存了,本函数里可能使用它来记录display参数。


是否真的如此,可以看汇编指令:

0x0000007f9efc5064 <+12>:    mov     x19, x0

(gdb) disassembleDump of assembler code for function fixup_x11_display:   0x0000007f9efc5058 <+0>:     stp     x29, x30, [sp, #-48]!   0x0000007f9efc505c <+4>:     mov     x29, sp   0x0000007f9efc5060 <+8>:     stp     x19, x20, [sp, #16]   0x0000007f9efc5064 <+12>:    mov     x19, x0   0x0000007f9efc5068 <+16>:    adrp    x0, 0x7f9efc5000 <register_tm_clones+48>   0x0000007f9efc506c <+20>:    add     x0, x0, #0x4c8   0x0000007f9efc5070 <+24>:    bl      0x7f9efc4db0 <getenv@plt>   0x0000007f9efc5074 <+28>:    cbz     x0, 0x7f9efc5088 <fixup_x11_display+48>   0x0000007f9efc5078 <+32>:    mov     x0, x19   0x0000007f9efc507c <+36>:    ldp     x19, x20, [sp, #16]   0x0000007f9efc5080 <+40>:    ldp     x29, x30, [sp], #48   0x0000007f9efc5084 <+44>:    ret   0x0000007f9efc5088 <+48>:    cbz     x19, 0x7f9efc5078 <fixup_x11_display+32>=> 0x0000007f9efc508c <+52>:    ldr     x0, [x19, #2408]   0x0000007f9efc5090 <+56>:    cbnz    x0, 0x7f9efc5078 <fixup_x11_display+32>   0x0000007f9efc5094 <+60>:    str     x21, [sp, #32]   0x0000007f9efc5098 <+64>:    adrp    x21, 0x7f9efd6000 <exit@got.plt>   0x0000007f9efc509c <+68>:    add     x20, x21, #0x90   0x0000007f9efc50a0 <+72>:    mov     x0, x20   0x0000007f9efc50a4 <+76>:    bl      0x7f9efc4dd0 <pthread_mutex_lock@plt>   0x0000007f9efc50a8 <+80>:    ldr     x0, [x19, #216]   0x0000007f9efc50ac <+84>:    bl      0x7f9efc4d60 <XOpenDisplay@plt>   0x0000007f9efc50b0 <+88>:    mov     x19, x0   0x0000007f9efc50b4 <+92>:    add     x2, x20, #0x30   0x0000007f9efc50b8 <+96>:    mov     x0, #0x0                        // #0   0x0000007f9efc50bc <+100>:   b       0x7f9efc50c4 <fixup_x11_display+108>   0x0000007f9efc50c0 <+104>:   b.eq    0x7f9efc50dc <fixup_x11_display+132>  // b.none   0x0000007f9efc50c4 <+108>:   ldr     x1, [x2, x0, lsl #3]   0x0000007f9efc50c8 <+112>:   mov     w3, w0   0x0000007f9efc50cc <+116>:   add     x0, x0, #0x1   0x0000007f9efc50d0 <+120>:   cmp     x0, #0x20--Type <RET> for more, q to quit, c to continue without paging--   0x0000007f9efc50d4 <+124>:   cbnz    x1, 0x7f9efc50c0 <fixup_x11_display+104>   0x0000007f9efc50d8 <+128>:   str     x19, [x2, w3, sxtw #3]   0x0000007f9efc50dc <+132>:   add     x0, x21, #0x90   0x0000007f9efc50e0 <+136>:   bl      0x7f9efc4de0 <pthread_mutex_unlock@plt>   0x0000007f9efc50e4 <+140>:   mov     x0, x19   0x0000007f9efc50e8 <+144>:   ldp     x19, x20, [sp, #16]   0x0000007f9efc50ec <+148>:   ldr     x21, [sp, #32]   0x0000007f9efc50f0 <+152>:   ldp     x29, x30, [sp], #48   0x0000007f9efc50f4 <+156>:   retEnd of assembler dump.

看上面的反汇编结果,果然在第4行处把本来放在x0里的参数转存到了x19。


再往下看,带有=>标志的那一行是引发崩溃的指令:

=> 0x0000007f9efc508c <+52>:    ldr     x0, [x19, #2408]

这条指令果然使用了x19,在访问x19所指向结构体的偏移2408的字段。


如此看来,腾讯会议闪退是因为获取屏幕句柄时,触发了内部的钩子函数fixup_x11_display。而fixup_x11_display内部没有很好的检查传进来的XDisplay指针是否有效(并不是不为0的指针都有效),在访问XDisplay大结构体的+2408字段时引发段错误而导致整个进程终止。

相对于了老牌的WinTel,新的ARM + Linux还很年轻,有些设施还不成熟,但对于程序员来说,ARM + Linux代表着未来,是大势所趋,是机遇所在。也正因为它们年轻,所以充满着各种有趣的问题。仿佛像英姿勃发的少年,虽然有时表现单纯,甚至鲁莽,但是性情天真,充满魅力。




ARM + Linux




腾讯会议为何不闪即退

2023/05/02


盛格塾是格蠹科技旗下的知识分享平台,是以“格物致知”为教育理念的现代私塾。


本着为先圣继绝学的思想,盛格塾努力将传统文化中的精华与现代科技密切结合,以传统文化和人文情怀阐释现代科技,用现代科技传播传统文化。


访问方式

手机端:微信小程序搜索“盛格塾”

电脑端:下载Nano Code社区版客户端

https://nanocode.cn/#/download

格友公众号

盛格塾小程序

往期 · 精彩推荐

两只老鼠的罪与罚

何处觅得活水来?

在调试器下理解RK3588和LINUX5.10

道法自然:我受益最多的老师


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存