在今天这样干什么都离不开手机的时代里,手机的待机时间太重要了。特别是对于我这个不喜欢带充电宝出门的人来说,一旦看到手机电量低于20%,立刻就精神紧张了,因为一切信息都在手机里,如果手机没电,那么就失联了。
我一直保存着一个小的诺基亚手机,我使用它多年,如今想来,它的最大好处是“一周只需充一次电”。
对于今天我用的手机,必须每个晚上给它充电。即使这样,如果第二天用的比较多,还可能陷入缺电危机。
是什么让手机变得如此耗电呢?我和计算机打交道20多年了,当然很清楚这个问题的答案。
硬件角度看,屏幕和处理器(CPU、GPU等)最费电。而处理器到底费电多少,就要看软件了。
手机厂商当然也知道这些道理,所以他们也一直在想办法。比如,打开我手机里的设置程序,切换到电池功能,它就会显示出一个很不错的耗电排行榜。
在上面这个耗电排行榜上,硬件消耗25%的电量,展开后看到都是屏幕消耗的。
硬件的列表里没有处理器,因为处理器耗的电就是软件消耗的,这样分类很合理。
上图中的“软件(75%)”, 代表软件消耗了75%的电量,是硬件的3倍。
而对于软件消耗的75%电量,百分百都是一个程序消耗的,它就是“微信”。这里的百分百应该是做了取整处理的。
对于“电池”程序给出的这个排行榜,我是不惊讶的,因为我的手机此前多次提示我“微信是高耗电应用”。
那么,微信为何如此耗电呢?
有一天深夜,我在一大桂花树下,与一群人讨论这个话题。一白衣人说是因为微信的功能很多。
我说:功能很多就应该如此费电么?Office的功能也很多啊,但是Office并不很费电。内核的功能更多呢,但内核大多时候默默无闻,静静等待为应用程序服务。
白衣人又说:是因为微信总要检查是否有新的消息来。
我说:很多软件都能自动检查新消息啊,比如邮件客户端,比如微博,比如知乎。
这时,我突然想到千牛, 于是补充说:千牛还24小时帮着看店呢,也没有像微信这样上耗电排行榜的第一名啊?
听我说到千牛,白衣人有些急了,加快语速:因为你总用微信,微信总要开着啊,全天运行。
我说:全天运行的软件多了,千牛不是全天运行么?还有大多数后台服务也都是全天运行的,也并不费电啊。关键是我不用微信时,它也很费电。
白衣人更急了,脸色通红地说:你没有做过微信,你不知道微信有多复杂,微信要支持的平台太多,各种版本的Windows,各种版本的iOS。
我说:这样就应该如此费电么?如果这样,那么gdb应该最费电,因为gdb不只支持无数种操作系统(OS),还支持数不清的古怪处理器(CPU)。
这时白衣人面色难看,一时无话可说了,他身边一穿格子衫者说:因为微信的很多代码是用Java写的,Java虚拟机的很多东西, 上层软件控制不了的。
我听后,觉得有些道理,但很快又反驳说:那你们为啥不把Java代码改成C或者C++呢?苹果不一直坚持用Object C么?
[编者按: 事实上,根据一位使用苹果手机的格友所提供的截图(下图),微信在苹果手机上的耗电情况似乎要好一些,至少在耗电排行榜上不是头名.]
这时白衣人怒了,从桌子上拿起一只茶杯向我投了过来。我赶紧闪身,杯子落在身后的石凳上,击得粉碎。
正在这形势危急之时,树下一端坐者举起手,示意他要说话,但他似乎闭着双眼,双唇只开了窄窄的一道缝,从那道缝里,似乎有气流涌出。但我根本听不见他在说什么,好在他一说话,其他人立刻都闭嘴,恭恭敬敬的看着他。这时端坐者仍双目低垂,面无表情,仅仅从双唇的那条缝里向外吐气,“f,f,f,f, f, f, ......”仿佛像小孩子学拼音那样,一个音一个音的向外发。
渐渐地,我似乎听出了他在发的音,我抬头看天,月朗星稀。环顾四周,山峦起伏, 万籁俱寂。眼前的桂花树也安安静静,每一片树叶都纹丝不动,空气中只有桂花的香气在缓慢地流动。根本没有一丝风啊?
但此时,端坐者身边的两个穿西装者站了起来,我也转过了神,感觉不妙,赶紧站起身,大喊一声:“甚矣哉!老夫...”
此时有人拉我的手,轻轻拍我的胸膛,并且对我说:“calm,calm,calm,在哪里当老夫呢?”,妻把我从梦中叫醒。
俗话说,日有所思,夜有所梦。在很多个白天里,特别是手机电量不足时,我都会产生一个想法:“为什么微信如此费电?”
与其它app不想用就卸载不同,微信太重要了,即使费电,也还必须得开着它。
很多次想起这个问题时,我心里都有一个愿望。那就是上调试器看一下,看它为何如此费电。
这个愿望不是那么好实现,因为微信运行在手机上,手机是个封闭的系统,进入这个系统很难,在里面安装调试工具更难。
有人说,不是可以使用adb(Android Debug Bridge)连接么? 某些手机是可以,但是连上了之后,还有工具链的问题, 手机里一般不会预装gdb, 产品化阶段一般也不会有gdbserver, strace, kernelshark这样的工具就更不会有了。如果从普通Linux系统复制,那么多半无法运行, 因为安卓系统里使用的是bionic, 不是glibc。 于是乎, 就需要使用ndk来构建手机所需的工具,而且还需要构建版本和手机版本一致。
今年8月,格蠹在幽兰代码本上打通了Waydroid环境,让微信可以在幽兰上运行了。看着微信的图标出现在幽兰的任务栏上,我非常开心,忍不住对它说:“Welcome onboard!”
很多兰友也都喜欢这个功能, 因为有了这个功能后, 就可以运行三个微信实例: 在普通电脑上运行Windows版本, 在手机上运行手机版本, 在幽兰代码本上运行平板(tablet)版本。
这样登录后, 手机的微信上会显示"2个设备已登录微信"。点一下,便列出已登录设备。
幽兰上的微信是平板版本,所以屏幕更大,更合适看照片和视频。但对于我来说, 在幽兰上运行微信的最大的好处是提供了调试的便利。
Waydroid是基于容器技术的,使用的是LXC,不是docker,但能让安卓app在幽兰代码本上以容器方式运行就已经足够了。
与虚拟机这样的深度隔离环境不同,容器技术的好处是弱隔离。举例来说,如果在Linux上装个安卓虚拟机,在虚拟机里安装微信,那么在Linux系统中是看不到微信进程的。另一方面,当在Linux系统中运行容器,在容器里运行微信。那么在Linux系统中也是可以看到容器中的所有进程,而且可以用gdb附加上去的(需要sudo)。
比如在幽兰上启动微信后,再执行ps aux | grep tencent就可以看到微信的各个进程。
geduer@ulan:/$ ps aux | grep tencent
geduer 2608 0.0 0.1 353972 28972 ? Sl 10:52 0:00 /usr/bin/python3 /usr/bin/waydroid app launch com.tencent.mm
10140 139020 8.9 4.8 128958040 794620 ? t
是的,com.tencent.mm就是微信程序,它有名字类似的进程:
com.tencent.mm
com.tencent.mm:appbrand0
com.tencent.mm:appbrand1
com.tencent.mm:xweb_privileged_process_0
om.tencent.mm:xweb_sandboxed_process_0:com.tencent.xweb.pinus.sdk.process.SandboxedProcessService
com.tencent.mm:push
执行top,也可以看到微信的各个进程。下图中排在最上面的和最下面两个都是。
从上图看,微信进程在内存中使用了122.9G的虚拟内存,764M物理内存,产生了1600万次页错误。
从微信能在幽兰上运行的第一天起,我就想上gdb调试它,看看它耗电多的原因。但是一直没有抽出时间。昨天有兰友提出想用幽兰来深入分析安卓系统,他的想法又激发了我调试微信的念头。
于是,今天终于下手做这件事了。
微信有多个进程,选哪个呢?当然就选占用CPU净时间最长的,也就是上图中排在最上面的,名叫com.tencent.mm,进程ID为139020。
打开一个终端窗口,轻敲键盘,发出如下命令,唤出gdb:
gdb --pid 139020
命令发出后,微信的界面不动了,说明这个进程就是微信的主程序。大约1秒钟后,安装弹出程序不响应提示,选择等待,令其消失。
接下来,集中精力与gdb对话,在gdb下看微信正式开始。
首先,gdb很顺利地附加到了微信进程,开始枚举进程里的所有线程。gdb有按页显示信息的习惯。显示了一屏后,便暂停等待,按回车就可以继续显示。
出乎我预料的是,按了很多个回车,还没有显示完,直到我把回车键按住不放,才终于显示完。
使用info threads命令再列一遍,仍有很多屏,但是这次看到个数了,189个。
我推测微信会有很多线程,但实际超过了150个,是有点出乎预料的。
附加成功后,我想做的第一个试验是看微信的安静度,也就是用户不使用微信时,微信进程是否安静,静则省电,动则耗电,少动少耗,多动多耗。
于是输入c命令,恢复微信执行。
不想它立刻又断入到调试器,再c,再断。
反复中断到调试器的原因是微信总收到信号,有SIG35,SIG33,还有SIGQUIT。
信号是用于在不同软件组织间通信使用的,这么多信号,说明彼此间协作很多。
在上图中,除了“某某线程收到xx信号”这样的信息外,还有创建新线程的消息,即[New LWP xxx]。
线程是软件时间里的生命,创建线程是比较重的操作,频繁创建线程当然是要耗电的。但问题应该不仅于此。
我发出handle命令,让GDB收到信号后,不必中断,打印信息后便自动继续。
(gdb) handle SIG35 nostop
Signal Stop Print Pass to program Description
SIG35 No Yes Yes Real-time event 35
(gdb) handle SIG33 nostop
Signal Stop Print Pass to program Description
SIG33 No Yes Yes Real-time event 33
(gdb) handle SIGQUIT nostop
Signal Stop Print Pass to program Description
SIGQUIT No Yes Yes Quit
这一下,屏幕哗哗地打印...
看来即使用户没有主动使用微信,微信进程内的活动也非常频繁。接下来,我启动一个clock小程序,让它在前台运行,把微信切换到后台(不可见)。看它在后台时的安静度。
没想到,在做这个切换时,微信进程发生了段错误。
Thread 1 "com.tencent.mm" received signal SIGSEGV, Segmentation fault.
[Switching to LWP 139020]
0x000000009cdc7ce8 in com.tencent.mm.plugin.newtips.model.i[j] ()
反汇编看错误指令:
0x000000009cdc7ce4 <+596>: b.ne 0x9cdc7f2c // b.any
=> 0x000000009cdc7ce8 <+600>: ldrsb w0, [x0, #8]
0x000000009cdc7cec <+604>: b 0x9cdc7d74
0x000000009cdc7cf0 <+608>: ldr w21, [x19, #168]
0x000000009cdc7cf4 <+612>: str wzr, [x19, #168]
0x000000009cdc7cf8 <+616>: ldr w0, 0x9cdc80d4
0x000000009cdc7cfc <+620>: mov w1, #0x1 // #1
0x000000009cdc7d00 <+624>: ldr x30, [x19, #488]
0x000000009cdc7d04 <+628>: blr x30
此处果然有不合理的内存访问.触发异常的是一条内存访问指令,这条指令访问的是x0对象,观察x0,值是0。
(gdb) p $x0
$4 = 0
看来这里有比较明显的空指针问题. 模块标识为com.tencent.mm.plugin.newtips.model(希望可以帮助快速修正)。
这个突如其来的段错误让我有点担心微信进程会消失,影响我继续分析。
看了一下调用栈,栈很深,估计某一级父函数会有异常保护,于是输入c继续。
有惊无险,GDB又收到了几次段错误后,进程还在。
大约几秒钟后,GDB收到一些线程退出事件,这可能是到后台后,某些线程故意退出,减少耗电。
为了检测此时的安静度,我在gdb里输入detach分离调试会话. 改用strace -c -p来监视微信的系统调用情况。
大约5分钟后,我Ctrl + C停止监视,strace给出了如下报告。
从上图可以看出,微信进程在后台时,仍有比较频繁的系统调用。系统调用是软件世界里的一种跨空间调用,微观意义上讲,具有较大的开销。
使用strace快速建立一些印象后,再次将gdb附加到微信进程。info threads观察,仍有100多个线程。
切换到1号线程,果然在调用epoll_pwait。
(gdb) thread 1
[Switching to thread 1 (LWP 230737)]
#0 0x0000007f8aec2ad8 in __epoll_pwait () from target:/apex/com.android.runtime/lib64/bionic/libc.so
(gdb) bt
#0 0x0000007f8aec2ad8 in __epoll_pwait () from target:/apex/com.android.runtime/lib64/bionic/libc.so
#1 0x0000007f8b33aad4 in android::Looper::pollInner(int) () from target:/system/lib64/libutils.so
#2 0x0000007f8b33a9b4 in android::Looper::pollOnce(int, int*, int*, void**) () from target:/system/lib64/libutils.so
#3 0x0000007f8d3d9108 in android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int) ()
from target:/system/lib64/libandroid_runtime.so
#4 0x00000000720f7c10 in art_jni_trampoline () from target:/system/framework/arm64/boot-framework.oat
#5 0x0000000072541c80 in android.os.MessageQueue.next () from target:/system/framework/arm64/boot-framework.oat
#6 0x000000007253e5bc in android.os.Looper.loop () from target:/system/framework/arm64/boot-framework.oat
#7 0x00000000722f8da0 in android.app.ActivityThread.main () from target:/system/framework/arm64/boot-framework.oat
#8 0x0000007f08e737ec in art_quick_invoke_static_stub () from target:/apex/com.android.art/lib64/libart.so
#9 0x0000007f08ee8a98 in art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*) ()
from target:/apex/com.android.art/lib64/libart.so
#10 0x0000007f0929574c in art::InvokeMethod(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jobject*, _jobject*, unsigned long) () from target:/apex/com.android.art/lib64/libart.so
#11 0x0000007f09214ef4 in art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*) ()
from target:/apex/com.android.art/lib64/libart.so
#12 0x0000000070b836f8 in art_jni_trampoline () from target:/apex/com.android.art/javalib/arm64/boot.oat
#13 0x000000007278f69c in com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run ()
from target:/system/framework/arm64/boot-framework.oat
#14 0x0000000072798228 in com.android.internal.os.ZygoteInit.main () from target:/system/framework/arm64/boot-framework.oat
#15 0x0000007f08e737ec in art_quick_invoke_static_stub () from target:/apex/com.android.art/lib64/libart.so
#16 0x0000007f08ee8a98 in art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*) ()
from target:/apex/com.android.art/lib64/libart.so
#17 0x0000007f09294188 in art::JValue art::InvokeWithVarArgs(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, art::ArtMethod*, std::__va_list) () from target:/apex/com.android.art/lib64/libart.so
#18 0x0000007f0929463c in art::JValue art::InvokeWithVarArgs<_jmethodID*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, std::__va_list) () from target:/apex/com.android.art/lib64/libart.so
#19 0x0000007f09178b0c in art::JNI::CallStaticVoidMethodV(_JNIEnv*, _jclass*, _jmethodID*, std::__va_list) ()
from target:/apex/com.android.art/lib64/libart.so
#20 0x0000007f8d361428 in _JNIEnv::CallStaticVoidMethod(_jclass*, _jmethodID*, ...) ()
--Type for more, q to quit, c to continue without paging--
from target:/system/lib64/libandroid_runtime.so
#21 0x0000007f8d3688b4 in android::AndroidRuntime::start(char const*, android::Vector const&, bool) ()
from target:/system/lib64/libandroid_runtime.so
#22 0x0000005579dc4584 in main ()
最后,我想看一下微信进程的内存分配行为,在gdb中埋下两个断点:
b malloc
b free
一个是分配内存,一个是释放内存。
恢复进程后,断点立刻命中,bt观察调用者,来自libwechatmm.so,从它名字里的mm来看,它可能是很重要的一个模块。
(gdb) bt
#0 0x0000007f8ae2fbbc in malloc () from target:/apex/com.android.runtime/lib64/bionic/libc.so
#1 0x0000007e8b1996b8 in operator new(unsigned long) ()
from target:/data/app/~~Au1aWPhBArFeVNeRmXfc1Q==/com.tencent.mm-EG5OCux-RKBnXU6UuLnpjQ==/lib/arm64/libc++_shared.so
#2 0x0000007e5b062444 in ?? ()
from target:/data/app/~~Au1aWPhBArFeVNeRmXfc1Q==/com.tencent.mm-EG5OCux-RKBnXU6UuLnpjQ==/lib/arm64/libwechatmm.so
#3 0x0000007e5b4f0820 in ?? ()
from target:/data/app/~~Au1aWPhBArFeVNeRmXfc1Q==/com.tencent.mm-EG5OCux-RKBnXU6UuLnpjQ==/lib/arm64/libwechatmm.so
#4 0x0000007e5b4f0588 in ?? ()
from target:/data/app/~~Au1aWPhBArFeVNeRmXfc1Q==/com.tencent.mm-EG5OCux-RKBnXU6UuLnpjQ==/lib/arm64/libwechatmm.so
#5 0x0000007e5b4f0320 in ?? ()
from target:/data/app/~~Au1aWPhBArFeVNeRmXfc1Q==/com.tencent.mm-EG5OCux-RKBnXU6UuLnpjQ==/lib/arm64/libwechatmm.so
#6 0x0000007e5b4f02c8 in ?? ()
from target:/data/app/~~Au1aWPhBArFeVNeRmXfc1Q==/com.tencent.mm-EG5OCux-RKBnXU6UuLnpjQ==/lib/arm64/libwechatmm.so
#7 0x0000007f8aed68f0 in __pthread_start(void*) () from target:/apex/com.android.runtime/lib64/bionic/libc.so
#8 0x0000007f8ae75a88 in __start_thread () from target:/apex/com.android.runtime/lib64/bionic/libc.so
执行p $x0,看起来,这一次分配的内存块大小为272个字节。
(gdb) p $x0
$1 = 272
执行c继续,又立刻命中,发生在不同的线程,但是仍是mm模块,这一次是528字节。
Thread 40 "wc_srvinit_2" hit Breakpoint 1, 0x0000007f8ae2fbbc in malloc ()
from target:/apex/com.android.runtime/lib64/bionic/libc.so
(gdb) bt
#0 0x0000007f8ae2fbbc in malloc () from target:/apex/com.android.runtime/lib64/bionic/libc.so
#1 0x0000007e8b1996b8 in operator new(unsigned long) ()
from target:/data/app/~~Au1aWPhBArFeVNeRmXfc1Q==/com.tencent.mm-EG5OCux-RKBnXU6UuLnpjQ==/lib/arm64/libc++_shared.so
#2 0x0000007e5b062444 in ?? ()
from target:/data/app/~~Au1aWPhBArFeVNeRmXfc1Q==/com.tencent.mm-EG5OCux-RKBnXU6UuLnpjQ==/lib/arm64/libwechatmm.so
#3 0x0000007e5b32fbac in ?? ()
from target:/data/app/~~Au1aWPhBArFeVNeRmXfc1Q==/com.tencent.mm-EG5OCux-RKBnXU6UuLnpjQ==/lib/arm64/libwechatmm.so
#4 0x0000007e5b1565b8 in ?? ()
from target:/data/app/~~Au1aWPhBArFeVNeRmXfc1Q==/com.tencent.mm-EG5OCux-RKBnXU6UuLnpjQ==/lib/arm64/libwechatmm.so
#5 0x0000007f8aed68f0 in __pthread_start(void*) () from target:/apex/com.android.runtime/lib64/bionic/libc.so
#6 0x0000007f8ae75a88 in __start_thread () from target:/apex/com.android.runtime/lib64/bionic/libc.so
(gdb) p $x0
$2 = 528
为了把上述过程自动化,我给断点附加了一串命令:
(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>printf "****mallocing memory: %d bytes\n", $x0
>bt
>c
>end
(gdb) commands 2
Type commands for breakpoint(s) 2, one per line.
End with a line saying just "end".
>printf "freeing memory %p\n", $x0
>bt
>c
>end
这次再继续,打印不断,大量信息快速喷涌出来,难以辨认。
截屏出来浏览一下,发现频繁的小块内存分配很多。
为什么对malloc函数设置断点呢?因为它是从堆(heap)上分配内存。有经验的软件工程师都知道,从堆上分配内存具有很大的开销.频繁从堆上分配内存会消耗较多的系统资源,包括CPU,内存,内存总线等。
在我分析时,还有一次遇到疑似死锁的问题,微信界面无法输入文字,看主线程的调用栈,android.text.TextLine.handleText在等待一个互斥量,难以自拔.这个调用栈非常的深,有115个栈帧。
0x0000007f8ae70b50 in syscall () from target:/apex/com.android.runtime/lib64/bionic/libc.so
(gdb) bt
#0 0x0000007f8ae70b50 in syscall () from target:/apex/com.android.runtime/lib64/bionic/libc.so
#1 0x0000007f08eed930 in art::Mutex::ExclusiveLock(art::Thread*) () from target:/apex/com.android.art/lib64/libart.so
#2 0x0000007f093a9af0 in art::GoToRunnable(art::Thread*) () from target:/apex/com.android.art/lib64/libart.so
#3 0x0000007f093a9900 in art::JniMethodEnd(unsigned int, art::Thread*) () from target:/apex/com.android.art/lib64/libart.so
#4 0x00000000720e8d44 in art_jni_trampoline () from target:/system/framework/arm64/boot-framework.oat
#5 0x00000000722acf1c in android.graphics.Paint.getRunAdvance () from target:/system/framework/arm64/boot-framework.oat
#6 0x00000000722acd9c in android.graphics.Paint.getRunAdvance () from target:/system/framework/arm64/boot-framework.oat
#7 0x00000000726b1114 in android.text.TextLine.getRunAdvance () from target:/system/framework/arm64/boot-framework.oat
#8 0x00000000726b2280 in android.text.TextLine.handleText () from target:/system/framework/arm64/boot-framework.oat
#9 0x00000000726b1edc in android.text.TextLine.handleRun () from target:/system/framework/arm64/boot-framework.oat
#10 0x00000000726b3618 in android.text.TextLine.measure () from target:/system/framework/arm64/boot-framework.oat
#11 0x00000000726a0ce8 in android.text.Layout.getHorizontal () from target:/system/framework/arm64/boot-framework.oat
#12 0x00000000726a604c in android.text.Layout.getPrimaryHorizontal () from target:/system/framework/arm64/boot-framework.oat
#13 0x00000000726a5fb0 in android.text.Layout.getPrimaryHorizontal () from target:/system/framework/arm64/boot-framework.oat
#14 0x00000000726a582c in android.text.Layout.getOffsetForHorizontal () from target:/system/framework/arm64/boot-framework.oat
#15 0x00000000726a53f0 in android.text.Layout.getOffsetForHorizontal () from target:/system/framework/arm64/boot-framework.oat
#16 0x0000000072920058 in android.widget.TextView.getOffsetAtCoordinate () from target:/system/framework/arm64/boot-framework.oat
#17 0x0000000072920100 in android.widget.TextView.getOffsetForPosition () from target:/system/framework/arm64/boot-framework.oat
#18 0x0000007f08e73568 in art_quick_invoke_stub () from target:/apex/com.android.art/lib64/libart.so
#19 0x0000007f08ee8a7c in art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*) ()
from target:/apex/com.android.art/lib64/libart.so
#20 0x0000007f09058320 in art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*) () from target:/apex/com.android.art/lib64/libart.so
#21 0x0000007f0904e64c in bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) () from target:/apex/com.android.art/lib64/libart.so
#22 0x0000007f093bc548 in MterpInvokeVirtual () from target:/apex/com.android.art/lib64/libart.so
#23 0x0000007f08e6d818 in mterp_op_invoke_virtual () from target:/apex/com.android.art/lib64/libart.so
#24 0x0000007f09045c48 in art::interpreter::Execute(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame&, art::JValue, bool, bool) [clone .llvm.15486036983929349625] () from target:/apex/com.android.art/lib64/libart.so
#25 0x0000007f093ab250 in artQuickToInterpreterBridge () from target:/apex/com.android.art/lib64/libart.so
--Type for more, q to quit, c to continue without paging--
#26 0x0000007f08e7cffc in art_quick_to_interpreter_bridge () from target:/apex/com.android.art/lib64/libart.so
#27 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#28 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#29 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#30 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#31 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#32 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#33 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#34 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#35 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#36 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#37 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#38 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#39 0x0000007f08e73568 in art_quick_invoke_stub () from target:/apex/com.android.art/lib64/libart.so
#40 0x0000007f08ee8a7c in art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*) ()
from target:/apex/com.android.art/lib64/libart.so
#41 0x0000007f09058320 in art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*) () from target:/apex/com.android.art/lib64/libart.so
#42 0x0000007f0904e64c in bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) () from target:/apex/com.android.art/lib64/libart.so
#43 0x0000007f093bd2fc in MterpInvokeSuper () from target:/apex/com.android.art/lib64/libart.so
#44 0x0000007f08e6d898 in mterp_op_invoke_super () from target:/apex/com.android.art/lib64/libart.so
#45 0x0000007f09045c48 in art::interpreter::Execute(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame&, art::JValue, bool, bool) [clone .llvm.15486036983929349625] () from target:/apex/com.android.art/lib64/libart.so
#46 0x0000007f093ab250 in artQuickToInterpreterBridge () from target:/apex/com.android.art/lib64/libart.so
#47 0x0000007f08e7cffc in art_quick_to_interpreter_bridge () from target:/apex/com.android.art/lib64/libart.so
#48 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#49 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#50 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#51 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
--Type for more, q to quit, c to continue without paging--
#52 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#53 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#54 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#55 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#56 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#57 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#58 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#59 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#60 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#61 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#62 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#63 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#64 0x000000009ca4559c in android.view.ViewGroup.dispatchResolvePointerIcon ()
#65 0x000000009cdedf60 in android.view.ViewGroup.onResolvePointerIcon ()
#66 0x000000009cdf12fc in android.view.ViewRootImpl.updatePointerIcon ()
#67 0x0000000072845160 in android.view.ViewRootImpl$ViewPostImeInputStage.maybeUpdatePointerIcon ()
from target:/system/framework/arm64/boot-framework.oat
#68 0x0000000072845b5c in android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent () from target:/system/framework/arm64/boot-framework.oat
#69 0x0000000072845f78 in android.view.ViewRootImpl$ViewPostImeInputStage.onProcess () from target:/system/framework/arm64/boot-framework.oat
#70 0x000000007271c81c in android.view.ViewRootImpl$InputStage.deliver () from target:/system/framework/arm64/boot-framework.oat
#71 0x000000007271c9d8 in android.view.ViewRootImpl$InputStage.onDeliverToNext () from target:/system/framework/arm64/boot-framework.oat
#72 0x0000000072843848 in android.view.ViewRootImpl$AsyncInputStage.forward () from target:/system/framework/arm64/boot-framework.oat
#73 0x000000007271c63c in android.view.ViewRootImpl$InputStage.apply () from target:/system/framework/arm64/boot-framework.oat
#74 0x00000000728436f0 in android.view.ViewRootImpl$AsyncInputStage.apply () from target:/system/framework/arm64/boot-framework.oat
#75 0x000000007271c87c in android.view.ViewRootImpl$InputStage.deliver () from target:/system/framework/arm64/boot-framework.oat
#76 0x000000007271c9d8 in android.view.ViewRootImpl$InputStage.onDeliverToNext () from target:/system/framework/arm64/boot-framework.oat
#77 0x000000007271c964 in android.view.ViewRootImpl$InputStage.forward () from target:/system/framework/arm64/boot-framework.oat
#78 0x000000007271c63c in android.view.ViewRootImpl$InputStage.apply () from target:/system/framework/arm64/boot-framework.oat
#79 0x000000007271c87c in android.view.ViewRootImpl$InputStage.deliver () from target:/system/framework/arm64/boot-framework.oat
#80 0x000000007284b4a0 in android.view.ViewRootImpl.deliverInputEvent () from target:/system/framework/arm64/boot-framework.oat
--Type for more, q to quit, c to continue without paging--
#81 0x0000000072859638 in android.view.ViewRootImpl.doProcessInputEvents () from target:/system/framework/arm64/boot-framework.oat
#82 0x000000007285999c in android.view.ViewRootImpl.enqueueInputEvent () from target:/system/framework/arm64/boot-framework.oat
#83 0x0000000072848354 in android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent () from target:/system/framework/arm64/boot-framework.oat
#84 0x000000007270b530 in android.view.InputEventReceiver.dispatchInputEvent () from target:/system/framework/arm64/boot-framework.oat
#85 0x0000007f08e73568 in art_quick_invoke_stub () from target:/apex/com.android.art/lib64/libart.so
#86 0x0000007f08ee8a7c in art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*) ()
from target:/apex/com.android.art/lib64/libart.so
#87 0x0000007f0929502c in art::JValue art::InvokeVirtualOrInterfaceWithVarArgs(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, art::ArtMethod*, std::__va_list) () from target:/apex/com.android.art/lib64/libart.so
#88 0x0000007f092951cc in art::JValue art::InvokeVirtualOrInterfaceWithVarArgs<_jmethodID*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, std::__va_list) () from target:/apex/com.android.art/lib64/libart.so
#89 0x0000007f090eb864 in art::JNI::CallVoidMethodV(_JNIEnv*, _jobject*, _jmethodID*, std::__va_list) ()
from target:/apex/com.android.art/lib64/libart.so
#90 0x0000007f8d3605c4 in _JNIEnv::CallVoidMethod(_jobject*, _jmethodID*, ...) () from target:/system/lib64/libandroid_runtime.so
#91 0x0000007f8d3b17bc in android::NativeInputEventReceiver::consumeEvents(_JNIEnv*, bool, long, bool*) ()
from target:/system/lib64/libandroid_runtime.so
#92 0x0000007f8d3b14e8 in android::NativeInputEventReceiver::handleEvent(int, int, void*) () from target:/system/lib64/libandroid_runtime.so
#93 0x0000007f8b33adb0 in android::Looper::pollInner(int) () from target:/system/lib64/libutils.so
#94 0x0000007f8b33a9b4 in android::Looper::pollOnce(int, int*, int*, void**) () from target:/system/lib64/libutils.so
#95 0x0000007f8d3d9108 in android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int) ()
from target:/system/lib64/libandroid_runtime.so
#96 0x00000000720f7c10 in art_jni_trampoline () from target:/system/framework/arm64/boot-framework.oat
#97 0x0000000072541c80 in android.os.MessageQueue.next () from target:/system/framework/arm64/boot-framework.oat
#98 0x000000007253e5bc in android.os.Looper.loop () from target:/system/framework/arm64/boot-framework.oat
#99 0x00000000722f8da0 in android.app.ActivityThread.main () from target:/system/framework/arm64/boot-framework.oat
#100 0x0000007f08e737ec in art_quick_invoke_static_stub () from target:/apex/com.android.art/lib64/libart.so
#101 0x0000007f08ee8a98 in art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*) ()
from target:/apex/com.android.art/lib64/libart.so
#102 0x0000007f0929574c in art::InvokeMethod(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jobject*, _jobject*, unsigned long) ()
from target:/apex/com.android.art/lib64/libart.so
--Type for more, q to quit, c to continue without paging--
#103 0x0000007f09214ef4 in art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*) () from target:/apex/com.android.art/lib64/libart.so
#104 0x0000000070b836f8 in art_jni_trampoline () from target:/apex/com.android.art/javalib/arm64/boot.oat
#105 0x000000007278f69c in com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run () from target:/system/framework/arm64/boot-framework.oat
#106 0x0000000072798228 in com.android.internal.os.ZygoteInit.main () from target:/system/framework/arm64/boot-framework.oat
#107 0x0000007f08e737ec in art_quick_invoke_static_stub () from target:/apex/com.android.art/lib64/libart.so
#108 0x0000007f08ee8a98 in art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*) ()
from target:/apex/com.android.art/lib64/libart.so
#109 0x0000007f09294188 in art::JValue art::InvokeWithVarArgs(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, art::ArtMethod*, std::__va_list) () from target:/apex/com.android.art/lib64/libart.so
#110 0x0000007f0929463c in art::JValue art::InvokeWithVarArgs<_jmethodID*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, std::__va_list) () from target:/apex/com.android.art/lib64/libart.so
#111 0x0000007f09178b0c in art::JNI::CallStaticVoidMethodV(_JNIEnv*, _jclass*, _jmethodID*, std::__va_list) ()
from target:/apex/com.android.art/lib64/libart.so
#112 0x0000007f8d361428 in _JNIEnv::CallStaticVoidMethod(_jclass*, _jmethodID*, ...) () from target:/system/lib64/libandroid_runtime.so
#113 0x0000007f8d3688b4 in android::AndroidRuntime::start(char const*, android::Vector const&, bool) ()
from target:/system/lib64/libandroid_runtime.so
#114 0x0000005579dc4584 in main ()
归纳一下, 微信软件早已不"微".相反,它已经非常庞大,可谓"煌煌巨篇".它的体量不仅体现在它的安装程序上,也体现在模块众多,错综复杂.用古人的话来说,可谓"五步一楼,十步一阁。廊腰缦回,檐牙高啄"。
当然,微信的体量更体现在它运行时,使用122G的虚拟空间,动辄消耗数百兆的物理空间。当然还有它也使用大量的闪存空间,还有明显的耗电。
本文使用gdb调试工具,走进微信进程内部,看其模块成堆,线程成群,信号繁复,段错误时有发生,调用栈深不见底,系统调用和动态内存分配频繁.这些观察证实了微信之宏大,也从侧面验证了此前的一些猜想。
孔子晚年整理<诗经>,对诗经有一句经典的归纳:"诗三百,一言以蔽之,曰:思无邪。" 对微信分析一番之后,我心头也浮出三个字:"软件难"。 微信算得上当今最成功之软件(之一),用户遍及海内,可谓盛矣,开发团队,人才济济,可谓优矣. 但为何还有如此之多的问题呢? 我又有一个大胆的猜测,没有证实,就此打住。
软件调试研习班庐山站正在招募中,欢迎有志于深入探究软件复杂问题的同行报名,一起在庐山思考软件,切磋调试技艺.
(写文章很辛苦,恳请各位读者点击“在看”,也欢迎转发)
*************************************************
正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生
扫描下方二维码或者在微信中搜索“盛格塾”小程序,可以阅读更多文章和有声读物
也欢迎关注格友公众号