(这是19年遇到的一个问题,现在回来看看,有了不一样的收获~)
问题:Android下执行reboot recovery后, 黑屏无输出,重新上电也还是黑屏
具体现象:执行reboot recovery后,进入recovery下黑屏能够输入命令,但是recovery服务已经挂了,busybox reboot 重启后仍然进入recovery;断电后再次上电还是进入recovery。
背景:
(1)3月26号发现该问题
(2)3月23号reboot recovery正常
这个问题要怎么解决呢?下面开始我们的探索之旅吧~
Day one -- 正面干!
reboot recovery进去抓个log看看啥情况
[ 28.645975@2] fb: osd_open, 1491, fb_index=0,fb_rmem_size=26738688
[ 28.646537@2] fb: osd_release now.index=0,open_count=1
[ 28.752360@2] init: Service 'recovery' (pid 2986) received signal 6
[ 28.753016@2] init: Sending signal 9 to service 'recovery' (pid 2986) process group...
[ 28.761056@2] libprocessgroup: Successfully killed process cgroup uid 0 pid 2986 in 0ms
[ 28.769150@2] init: Untracked pid 2987 exited with status 1
从上面的log可以看到recovery这个Service挂了,received signal 6?signal 6是啥呢,
百度一下看看,程序运行产生SIGABRT信号的原因_Season_hangzhou的博客-CSDN博客_sigabrt信号
上文提到SIGABRT信号即signal 6的信号,可能的原因有:
1、多次free导致的SIGABRT
2、执行abort函数
3、执行到assert函数
嗯,很有道理,opengrok搜一波"received signal"试试,这个搜索要充分利用opengrok的功能
这里可以指明File Path可以帮助我们过滤问题,这个操作就如cd 到File Path里面grep一下
换一个"process group..."试试
这个在/android/system/core/init/service.cpp KillProcessGroup函数里面,看了一下代码,功能就是把pid同组的进程全部杀死
从log中我们也可以看到recovery服务一直在被杀,杀完又起,如此循环往复
从上面的分析也得不出什么原因,这些都是公共的东西,只能得出有问题导致了这些打印。
直接分析log有很多不熟悉的地方,也不确定是不是这个原因导致的,这个时候对比法就很重要了,我们升级一下23号的软件进recovery抓个log
从log的对比,我大概了解到以下几点信息:
正常的是:
[ 12.896990@0] fb: osd_open, 1491, fb_index=0,fb_rmem_size=26738688
[ 12.897613@0] fb: osd_release now.index=0,open_count=1
[ 12.906375@0] fb: osd_open, 1491, fb_index=0,fb_rmem_size=26738688
[ 12.909027@0] fb: malloc_osd_memory, cma:c18d3a8c
[ 12.913535@0] fb: malloc_osd_memory, 1188, base:0x3f800000, size:8388608
[ 12.920218@0] fb: use ion buffer for fb memory, fb_index=0
[ 12.934219@0] meson-fb fb: create ion_client c9ee5980, handle=e59cc640
[ 12.935111@0] meson-fb fb: ion memory(0): created fb at 0x2b400000, size 25 MiB
[ 12.942564@0] fb: Frame buffer memory assigned at[ 12.946927@0] fb: 0, phy: 0x2b400000, vir:0xeb400000, size=26112K
[ 12.946927@0]
[ 12.954723@0] fb: logo_index=0,fb_index=0
[ 12.958755@0] fb: ---------------clear fb0 memory eb400000
[ 12.974833@0] fb: osd[0] canvas.idx =0x40
[ 12.974857@0] fb: osd[0] canvas.addr=0x2b400000
[ 12.977737@0] fb: osd[0] canvas.width=7680
[ 12.981812@0] fb: osd[0] canvas.height=2160
[ 12.985965@0] fb: osd[0] frame.width=1920
[ 12.989995@0] fb: osd[0] frame.height=1080
[ 12.994032@0] fb: osd[0] out_addr_id =0x1
[ 13.023258@0] fb: osd[0] enable: 0 (recovery)
[ 13.039920@0] fb: osd[0] enable: 1 (recovery)
[ 13.496240@1] fb: osd[0] enable: 0 (recovery)
异常的是:
[ 28.645975@2] fb: osd_open, 1491, fb_index=0,fb_rmem_size=26738688
[ 28.646537@2] fb: osd_release now.index=0,open_count=1
[ 28.752360@2] init: Service 'recovery' (pid 2986) received signal 6
[ 28.753016@2] init: Sending signal 9 to service 'recovery' (pid 2986) process group...
[ 28.761056@2] libprocessgroup: Successfully killed process cgroup uid 0 pid 2986 in 0ms
[ 28.769150@2] init: Untracked pid 2987 exited with status 1
为啥osd_open,osd_release之后挂了呢?osd这部分代码有人修改?
/android/common/drivers/amlogic/media/osd/osd_fb.c
查看一下这个仓库的修改,发现修改不在问题发生的区间,pass
追下代码看看osd_release之后发生了什么,半小时后,无果
问了自己一个问题,是因为osd这部分有问题导致的recovery挂掉?还是recovery挂掉导致的这部分代码没跑下去?好像有点鸡和蛋的关系
和同事讨论分析了一下目前的情况,我这边没有进展,他分析可能是remotecfg这个服务的原因,因为在正常的里面:
Line 1674: [ 7.869074@3] init: starting service 'remotecfg3'...
Line 1675: [ 7.870146@3] init: starting service 'remotecfg1'...
Line 1676: [ 7.871185@3] init: starting service 'remotecfg2'...
Line 1678: [ 7.873270@3] init: Service 'remotecfg3' (pid 2916) exited with status 253
Line 1679: [ 7.873398@3] init: Service 'remotecfg1' (pid 2918) exited with status 253
Line 1681: [ 7.874162@3] init: Service 'remotecfg2' (pid 2922) exited with status 253
异常的是:
Line 1462: [ 7.436788@2] init: starting service 'remotecfg3'...
Line 1464: [ 7.437824@2] init: starting service 'remotecfg1'...
Line 1465: [ 7.438317@0] init: cannot execve('/sbin/remotecfg'): No such file or directory
Line 1466: [ 7.439033@2] init: starting service 'remotecfg2'...
Line 1467: [ 7.439078@3] init: cannot execve('/sbin/remotecfg'): No such file or directory
Line 1469: [ 7.440309@3] init: cannot execve('/sbin/remotecfg'): No such file or directory
尝试直接将remotecfg从好的里面拷贝到异常的里面去,还是不能执行。
每日小结:
1、搜索代码有技巧
2、对比分析
Day two -- 反面迂回
目前看来正面是暂时无法找到原因了,既然这个问题是改出来的,那么必然会有一个修改导致这个问题。
接着开始愉快的夹版本了,一开始是要通过代码回退版本然后编译升级,后面想了一下,直接用dailybuild就行了
最后发现这两版软件
dailybuild软件:
20190325_083028 OK
20190325_120226 NG
多亏这每天几个版本的dailybuild,很快的,用二分法的方法定位到了出问题的前后版本
用dailybuild还有一个好处就是Jenkins上编译信息最后面有这次编译修改的内容,很方便就可以查看到本次编译版本的所有修改,但是这个因为时间久了没了
不过也可以通过命令来导出修改:
repo forall -p -c git log -n 30 --format="%Cgreen%h %Cred[%ci] %Creset <%cn>%C(yellow)%d%Creset %Creset %Cgreen%s %Creset " --after="2019-03-25 08:30:28" --before="2019-03-25 12:02:26"
修改包含了:
project android/common/
6fc8254 [2019-03-25 11:38:04 +0800] <xxxxx> log messagexxxxproject customers/public_alltv/
3338689 [2019-03-25 11:22:59 +0800] <xxxxx> log messagexxxx
还原6fc8254 修改reboot recovery正常
至此,已经找到了罪魁祸首了吗?
我们看一下修改了什么吧~
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index cd2be1c..4ebab8b 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -199,6 +199,7 @@ header-y += in.hheader-y += inotify.hheader-y += input.hheader-y += input-event-codes.h
+header-y += key_define.hheader-y += in_route.hheader-y += ioctl.hheader-y += ip6_tunnel.h
diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
index 3af60ee..4884bad 100644
--- a/include/uapi/linux/input-event-codes.h
+++ b/include/uapi/linux/input-event-codes.h
@@ -507,7 +507,7 @@#define KEY_FRAMEBACK 0x1b4 /* Consumer - transport controls */#define KEY_FRAMEFORWARD 0x1b5#define KEY_CONTEXT_MENU 0x1b6 /* GenDesc - system context menu */
-#define KEY_MEDIA_REPEAT 0x1b7 /* Consumer - transport control */
+//#define KEY_MEDIA_REPEAT 0x1b7 /* Consumer - transport control */#define KEY_10CHANNELSUP 0x1b8 /* 10 channels up (10+) */#define KEY_10CHANNELSDOWN 0x1b9 /* 10 channels down (10-) */#define KEY_IMAGES 0x1ba /* AL Image Browser */
@@ -685,9 +685,15 @@#define BTN_TRIGGER_HAPPY40 0x2e7/* We avoid low common keys in module aliases so they don't get huge. */
-#define KEY_MIN_INTERESTING KEY_MUTE
-#define KEY_MAX 0x2ff
-#define KEY_CNT (KEY_MAX+1)
+//#define KEY_MIN_INTERESTING KEY_MUTE
+//#define KEY_MAX 0x2ff
+//#define KEY_CNT (KEY_MAX+1)
+
+#include "key_define.h"
+/** Relative axes
diff --git a/include/uapi/linux/key_define.h b/include/uapi/linux/key_define.h
new file mode 100755
index 0000000..c7a9fee
--- /dev/null
+++ b/include/uapi/linux/key_define.h
@@ -0,0 +1,168 @@
+#ifndef __KEY_DEFINE_H__
+#define __KEY_DEFINE_H__
+
+#define KEY_MEDIA_PLAY_PAUSE 637
+#define KEY_MEDIA_STOP 638
+#define KEY_MEDIA_REWIND 639
+#define KEY_MEDIA_FAST_FORWARD 640
+#define KEY_MEDIA_PREVIOUS 641
+#define KEY_MEDIA_NEXT 642
+#define KEY_MEDIA_REPEAT 643
+
+/* We avoid low common keys in module aliases so they don't get huge. */
+#define KEY_MIN_INTERESTING KEY_MUTE
+
+/*****************************************************************************************************
+ * Notice for amlogic:
+ * 1. When KEY_MAX is modified, it is necessary to check whether "recovery code" needs to be changed !!!!
+ * 2. KEY_MAX must be same as INPUT_DEVICE_ID_KEY_MAX in android/common/include/linux/mod_devicetable.h
+ *******************************************************************************************************/
+#define KEY_MAX 0x6FF
+#define KEY_CNT (KEY_MAX+1)
+
+#endif /* __KEY_DEFINE_H__ */
+
这个修改一看应该也没有问题才对,做的事情是将添加按键部分都挪到customers仓库下,方便后面添加新按键
这里面唯一有可能出现问题应该就是KEY_MAX了
仔细一看,下面竟然有两行注释?!
+/*****************************************************************************************************
+ * Notice for amlogic:
+ * 1. When KEY_MAX is modified, it is necessary to check whether "recovery code" needs to be changed !!!!
+ * 2. KEY_MAX must be same as INPUT_DEVICE_ID_KEY_MAX in android/common/include/linux/mod_devicetable.h
+ *******************************************************************************************************/
改KEY_MAX的时候,需要注意recovery code?这么一说这个问题在A方案上有改过咯
再去问问,发现B方案上面也改过这个问题,找到这两个方案的修改,对比一下是一样的
diff --git a/bootable/recovery/minui/events.cpp b/bootable/recovery/minui/events.cpp
index 0e1fd44a0..9a3fb817a 100644
--- a/bootable/recovery/minui/events.cpp
+++ b/bootable/recovery/minui/events.cpp
@@ -31,6 +31,8 @@#define MAX_DEVICES 16#define MAX_MISC_FDS 16+#define KEY_MAX_ORG 0x2FF
+#define BITS_PER_LONG (sizeof(unsigned long) * 8)#define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG)@@ -176,7 +178,7 @@ int ev_get_input(int fd, uint32_t epevents, input_event* ev) {int ev_sync_key_state(const ev_set_key_callback& set_key_cb) {// Use unsigned long to match ioctl's parameter type.unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
- unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT
+ unsigned long key_bits[BITS_TO_LONGS(KEY_MAX_ORG)]; // NOLINTfor (size_t i = 0; i < ev_dev_count; ++i) {memset(ev_bits, 0, sizeof(ev_bits));
@@ -192,7 +194,7 @@ int ev_sync_key_state(const ev_set_key_callback& set_key_cb) {continue;}- for (int code = 0; code <= KEY_MAX; code++) {
+ for (int code = 0; code <= KEY_MAX_ORG; code++) {if (test_bit(code, key_bits)) {set_key_cb(code, 1);}
@@ -205,7 +207,7 @@ int ev_sync_key_state(const ev_set_key_callback& set_key_cb) {void ev_iterate_available_keys(const std::function<void(int)>& f) {// Use unsigned long to match ioctl's parameter type.unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
- unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT
+ unsigned long key_bits[BITS_TO_LONGS(KEY_MAX_ORG)]; // NOLINTfor (size_t i = 0; i < ev_dev_count; ++i) {memset(ev_bits, 0, sizeof(ev_bits));
@@ -219,12 +221,12 @@ void ev_iterate_available_keys(const std::function<void(int)>& f) {continue;}- int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits);
+ int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX_ORG), key_bits);if (rc == -1) {continue;}- for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
+ for (int key_code = 0; key_code <= KEY_MAX_ORG; ++key_code) {if (test_bit(key_code, key_bits)) {f(key_code);}
diff --git a/bootable/recovery/ui.cpp b/bootable/recovery/ui.cpp
index cad744930..b7c77971e 100644
--- a/bootable/recovery/ui.cpp
+++ b/bootable/recovery/ui.cpp
@@ -130,7 +130,7 @@ bool RecoveryUI::Init(const std::string& locale) {ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2));- ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
+ //ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));if (!InitScreensaver()) {LOG(INFO) << "Screensaver disabled";
@@ -169,7 +169,7 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {rel_sum = 0;}- if (ev.type == EV_KEY && ev.code <= KEY_MAX) {
+ if (ev.type == EV_KEY && ev.code <= KEY_MAX_ORG) {ProcessKey(ev.code, ev.value);}diff --git a/bootable/recovery/ui.h b/bootable/recovery/ui.h
index 823eb6574..a2d4d8c6d 100644
--- a/bootable/recovery/ui.h
+++ b/bootable/recovery/ui.h
@@ -23,6 +23,8 @@#include <string>+#define KEY_MAX_ORG 0x2FF
+// Abstract class for controlling the user interface during recovery.class RecoveryUI {public:
@@ -142,7 +144,7 @@ class RecoveryUI {pthread_mutex_t key_queue_mutex;pthread_cond_t key_queue_cond;int key_queue[256], key_queue_len;
- char key_pressed[KEY_MAX + 1]; // under key_queue_mutex
+ char key_pressed[KEY_MAX_ORG + 1]; // under key_queue_mutexint key_last_down; // under key_queue_mutexbool key_long_press; // under key_queue_mutexint key_down_count; // under key_queue_mutex
导入修改,reboot recovery没有问题啦,ok万岁!
大概看了一下修改的内容,我们可以看到上面把recovery下KEY_MAX都固定成了0x2ff
但是其中有一点我非常疑惑,我们把注意力集中在ev_iterate_available_keys这个函数上面,上面的修改注释了调用这个函数的地方,但是函数内又对应做了修改。
为什么呢?我搜索了整个代码,这个是唯一调用的地方,这不是多此一举吗?
然后我开始质疑这个修改了。
很赞成公司编码规范那句话:
于是乎,这才开始了我们真正的探索之旅
try 1:
去掉这个函数调用的注释,仍然调用函数,升级后发现,还是死掉了
接着追一下recovery的流程吧
这个是recovery的log,recovery下log在/tmp/recovery.log
正常的是:
[ 4.028655] locale is [en-US]
[ 4.028665] stage is []
[ 4.028674] reason is [(null)]
[ 5.034970] loaded /etc/recovery.kl
[ 5.034999] skipping malformed recovery.lk line: back_door
[ 5.035012] recovery key map table:
[ 5.035025] 0 type:select value:28 key:97 28 15 158 -1 -1
[ 5.035038] 1 type:down value:108 key:108 114 109 -1 -1 -1
[ 5.035049] 2 type:up value:103 key:103 104 115 -1 -1 -1
[ 5.035059]
[ 5.035064] loaded /etc/recovery.kl
[ 5.035073] skipping malformed recovery.lk line: back_door
[ 5.035078] recovery key map table:
[ 5.035088] 0 type:select value:28 key:97 28 15 158 -1 -1
[ 5.035097] 1 type:down value:108 key:108 114 109 -1 -1 -1
[ 5.035106] 2 type:up value:103 key:103 104 115 -1 -1 -1
[ 5.035115]
[ 5.035618] W:Failed to read max brightness: No such file or directory
[ 5.035705] I:Screensaver disabled
[ 5.038276] cannot find/open a drm device: No such file or directory
[ 5.040919] fb0 reports (possibly inaccurate):
[ 5.040937] vi.bits_per_pixel = 32
[ 5.040950] vi.red.offset = 16 .length = 8
[ 5.040960] vi.green.offset = 8 .length = 8
[ 5.040970] vi.blue.offset = 0 .length = 8
异常的是:
[ 4.021206] locale is [en-US]
[ 4.021246] stage is []
[ 4.021291] reason is [(null)]
[ 5.027493] loaded /etc/recovery.kl
[ 5.027523] skipping malformed recovery.lk line: back_door
[ 5.027531] recovery key map table:
[ 5.027541] 0 type:select value:28 key:97 28 15 158 -1 -1
[ 5.027550] 1 type:down value:108 key:108 114 109 -1 -1 -1
[ 5.027564] 2 type:up value:103 key:103 104 115 -1 -1 -1
[ 5.027574]
[ 5.027710] loaded /etc/recovery.kl
[ 5.027806] skipping malformed recovery.lk line: back_door
[ 5.027901] recovery key map table:
[ 5.027944] 0 type:select value:28 key:97 28 15 158 -1 -1
[ 5.027987] 1 type:down value:108 key:108 114 109 -1 -1 -1
[ 5.028024] 2 type:up value:103 key:103 104 115 -1 -1 -1
[ 5.028061]
[ 0.002358] Starting recovery (pid 2955) on Mon Jan 1 00:01:15 2018
[ 0.002398] recovery filesystem table
[ 0.002420] =========================
对比log看代码,开始追recovery部分的代码:android/bootable/recovery
bool RecoveryUI::Init(const std::string& /* locale */) {ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2),touch_screen_allowed_);ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));if (touch_screen_allowed_) {ev_iterate_touch_inputs(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));// Parse /proc/cmdline to determine if it's booting into recovery with a bootreason of// "recovery_ui". This specific reason is set by some (wear) bootloaders, to allow an easier way// to turn on text mode. It will only be set if the recovery boot is triggered from fastboot, or// with 'adb reboot recovery'. Note that this applies to all build variants. Otherwise the text// mode will be turned on automatically on debuggable builds, even without a swipe.std::string cmdline;if (android::base::ReadFileToString("/proc/cmdline", &cmdline)) {is_bootreason_recovery_ui_ = cmdline.find("bootreason=recovery_ui") != std::string::npos;} else {// Non-fatal, and won't affect Init() result.PLOG(WARNING) << "Failed to read /proc/cmdline";}}if (!InitScreensaver()) {LOG(INFO) << "Screensaver disabled";}pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);return true;
}
结合代码和log,我们可以看出:
正常的RecoveryUI执行Init初始化之后有个打印:“ W:Failed to read max brightness: No such file or directory”,这个打印在函数InitScreensaver()里
而异常的没有。添加打印,很快的定位在执行ev_iterate_available_keys函数后就没有打印了。
try 2:
去掉所有的其他修改,只注释这个函数调用的地方,升级后,reboot recovery正常
也就是说我推翻了之前的理论?KEY_MAX没有半毛钱关系?
看一下这个函数都干了些什么
void ev_iterate_available_keys(const std::function<void(int)>& f) {// Use unsigned long to match ioctl's parameter type.unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINTunsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINTfor (size_t i = 0; i < ev_dev_count; ++i) {memset(ev_bits, 0, sizeof(ev_bits));memset(key_bits, 0, sizeof(key_bits));// Does this device even have keys?if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {continue;}if (!test_bit(EV_KEY, ev_bits)) {continue;}int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits);if (rc == -1) {continue;}for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {if (test_bit(key_code, key_bits)) {f(key_code);}}}
}
大概理解了一下,功能就是获取设备的能力,比如有没有key或者button等。
时间紧急,OTA的问题被这个卡住了,暂时先注释这个函数先吧。
每日小结:
1、只要是改出来的问题,那么必然能找到导致问题的修改。
2、倒版本首选dailybuild,用二分法定位
3、提高调试手段,磨刀不费砍柴功
Day three -- 峰回路转
既然知道问题出在ev_iterate_available_keys这个函数上,那么究竟什么引起的呢?
目前可以确定的是:有KEY_MAX的那个修改,recovery就会挂,去掉就好了。
这个时候我们横向对比,找其他方案看看,
其中B、C方案将KEY_MAX改成0x4ff,然而C方案没有对recovery修改,B方案有修改,这个是为啥?
A、D方案都将KEY_MAX改成0x6ff,然而D方案没有对recovery修改,A方案有修改,这个是为啥?
难道是KEY_MAX太大了导致的?我试一下现在的方案上改成0x4ff如何?
这个时候我添加打印,顺便在ev_iterate_available_keys函数调用之前添加打印出KEY_MAX的值
升级recovery之后打印是0x2ff,what?
清除一下所有仓库的修改,只留下这个打印,再删除out目录,整编一次。
升级后进recovery打印还是0x2ff,这下彻底明白了,这里用KEY_MAX的值压根不是修改的那个
从目前的结论可以推翻A、B方案的修改都是多此一举的,找到的根因也不对,只是改好了问题而已
ctags整一个,追到了代码:
recovery中KEY_MAX中的值用的是:
android/bionic/libc/kernel/uapi/linux/input-event-codes.h里面的,
#define KEY_MAX 0x2ff
#define KEY_CNT (KEY_MAX + 1)
而不是android/common/include/uapi/linux/input-event-codes.h里面的
这个时候我在函数里面加满了打印,因为我不知道这里都干了什么
void ev_iterate_available_keys(const std::function<void(int)>& f) {// Use unsigned long to match ioctl's parameter type.unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINTunsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINTprintf("key_bits addr : %p\n", key_bits);printf("ev_iterate_available_keys\n");printf("ev_dev_count : %d\n", ev_dev_count);printf("key_bits : %d; KEY_MAX : %d; EV_MAX : %d; ev_bits : %d \n",sizeof(key_bits),KEY_MAX,EV_MAX,sizeof(ev_bits));printf("EV_KEY : %d\n",EV_KEY);for (size_t i = 0; i < ev_dev_count; ++i) {printf("for ev_dev_count value : %d\n",i);memset(ev_bits, 0, sizeof(ev_bits));memset(key_bits, 0, sizeof(key_bits));printf("ioctl ev_bits %d\n",i);// Does this device even have keys?if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {printf("ev_bits error");continue;}printf("test_bit ev_bits : %d\n",i);if (!test_bit(EV_KEY, ev_bits)) {printf("EV_KEY test_bit");continue;}printf("ioctl key_bits: %d\n",i);int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits);if (rc == -1) {printf("continue\n");continue;}printf("test_bit key_bits : %d\n",i);for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {printf("for key_code value : %d\n",key_code);if (test_bit(key_code, key_bits)) {printf("key_code : %d\n", key_code);printf("key_bits \n");f(key_code);}}printf("end : %d\n",i);}
}
看起来有点可怕,但是对于分析还是起了很关键的作用
从多次试错的过程和打印中,我发现,它在key_code为116的时候会进去if (test_bit(key_code, key_bits))这个判断
然后程序就死了,首先116这个key_code有什么特别的,其次,f(key_code);干了什么
ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
可以看到是OnKeyDetected这个函数
void RecoveryUI::OnKeyDetected(int key_code) {printf("OnKeyDetected key_code : %d\n", key_code);if (key_code == KEY_POWER) {has_power_key = true;} else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) {has_down_key = true;} else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) {has_up_key = true;} else if (key_code == ABS_MT_POSITION_X || key_code == ABS_MT_POSITION_Y) {has_touch_screen = true;}
}
这个函数也没有什么特别的,在第一行添加打印,发现根本没有打印,也就是说是这个函数回调的时候死掉了?
偶然间,发现E方案也改过这个问题:
// fix Service 'recovery' killed by signal 11 ++
long length = EVIOCGBIT(EV_KEY, KEY_MAX);
if(length < 0){length = 0;
}
int rc = ioctl(ev_fdinfo[i].fd, length, key_bits);
// fix Service 'recovery' killed by signal 11 --
这个有点厉害,因为从我目前的分析来说,问题应该是在这里了,这个时候我只注释
//int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits);
//if (rc == -1) {
// printf("continue\n");
// continue;
//}
发现也是ok的。
这下子没辙了吗?不,还有一个办法:简化代码,编写测试用的最小程序
一来方便调试,不需要每次进recovery死掉再线刷
二来可以排除很多无用项,还有方便了解代码debug
代码路径:android/bootable/recovery/minui
修改Android.mk为:
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_SRC_FILES := \test.cpp \events.cpp \LOCAL_CFLAGS := -Wall -Werror
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
LOCAL_MODULE := testinclude $(BUILD_EXECUTABLE)
添加test.cpp
#include <linux/input.h>
#include <stdio.h>
#include <stdlib.h>
#include <minui/minui.h>void OnKeyDetected(int key_code) {printf("OnKeyDetected key_code : %d\n", key_code);
}int OnInputEvent(int fd, uint32_t epevents){printf("OnInputEvent: %d ,%d\n",fd,epevents);return 0;
}int main(){printf("hello\n");ev_init(std::bind(&OnInputEvent, std::placeholders::_1, std::placeholders::_2),false);ev_iterate_available_keys(std::bind(&OnKeyDetected, std::placeholders::_1));printf("end\n");return 0;
}
单独编译:
cd android
source build/envsetup.sh
lunch xxxx-userdebug
cd bootable/recovery/minui/
mm -j
这下修改一次的调试时间一下子从4min(编译2min+线刷2min+其他)缩减到30s(编译20s+其他),极大提高了效率
生成一个test可执行文件,拷贝到板卡添加权限,在recovery下操作行不通,干脆直接进Android里面尝试,一运行,打印:
console:/data # ./test
hello
ev_iterate_available_keys
ev_dev_count : 4
key_bits : 96; KEY_MAX : 767; EV_MAX : 31; ev_bits : 4
EV_KEY : 1
key_code : 116
key_bits
Aborted
同时logcat信息有:
04-13 07:00:07.527 15100 15100 I crash_dump32: type=1400 audit(0.0:747): avc: denied { open } for path="/data/test" dev="mmcblk0p21" ino=1494 scontext=u:r:crash_dump:s0 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=1
04-13 07:00:07.524 3299 3299 I /system/bin/tombstoned: received crash request for pid 15097
04-13 07:00:07.525 15100 15100 I crash_dump32: performing dump of process 15097 (target tid = 15097)
04-13 07:00:07.529 15100 15100 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
04-13 07:00:07.530 15100 15100 F DEBUG : Build fingerprint: 'xxxxx/PPR1.180610.011/20190413:eng/test-keys'
04-13 07:00:07.530 15100 15100 F DEBUG : Revision: '0'
04-13 07:00:07.530 15100 15100 F DEBUG : ABI: 'arm'
04-13 07:00:07.530 15100 15100 F DEBUG : pid: 15097, tid: 15097, name: test >>> ./test <<<
04-13 07:00:07.530 15100 15100 F DEBUG : signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
04-13 07:00:07.530 15100 15100 F DEBUG : r0 00000000 r1 00003af9 r2 00000006 r3 a4d1e918
04-13 07:00:07.530 15100 15100 F DEBUG : r4 00003af9 r5 00003af9 r6 bb9dc98c r7 0000010c
04-13 07:00:07.530 15100 15100 F DEBUG : r8 00000000 r9 bb9dc9b0 r10 00000001 r11 879ca008
04-13 07:00:07.530 15100 15100 F DEBUG : ip 00000074 sp bb9dc978 lr a4cad0e5 pc a4ca4e72
04-13 07:00:07.533 15100 15100 F DEBUG :
04-13 07:00:07.533 15100 15100 F DEBUG : backtrace:
04-13 07:00:07.533 15100 15100 F DEBUG : #00 pc 0001ce72 /system/lib/libc.so (abort+62)
04-13 07:00:07.533 15100 15100 F DEBUG : #01 pc 00000ef1 /data/test (ev_iterate_available_keys(std::__1::function<void (int)> const&)+356)
04-13 07:00:07.564 3586 3745 W NativeCrashListener: Couldn't find ProcessRecord for pid 15097
04-13 07:00:07.565 3299 3299 E /system/bin/tombstoned: Tombstone written to: /data/tombstones/tombstone_01
查看tombstone_01
arm-linux-androideabi-addr2line -C -f -e out/target/product/xxxx/symbols/system/lib/libc.so 0001ce72
abort
bionic/libc/bionic/abort.cpp:72arm-linux-androideabi-addr2line -C -f -e out/target/product/xxxx/symbols/system/bin/test 00000ef1
std::__1::__throw_bad_function_call()
external/libcxx/include/functional:1408
第一行说明了啥,我们回顾一下第一天的内容,abort==》SIGABRT信号,即signal 6的信号
第二行说明了啥,__throw_bad_function_call?
网上搜索一下,std::function - 碎语心弦 - 博客园
当std::function对象未包裹任何实际的可调用元素,调用该std::function对象将抛出std::bad_function_call异常。
也就是说ev_iterate_available_keys的参数f是空的?
添加打印:
console:/data # ./test
hello
std:bind not null
f start is not null
ev_iterate_available_keys
ev_dev_count : 4
key_bits : 96; KEY_MAX : 767; EV_MAX : 31; ev_bits : 4
EV_KEY : 1
ioctl start, f is not null
ioctl end, f is null
key_code : 116
key_bits
f is null
Aborted
自此,可以说明的是int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits);之后后会导致问题的发生
讲来讲去好像又回到一开始的问题了,这个ioctl导致程序挂了,怎么挂的?
但是现在已经理清楚了问题发现的后半段了:
ioctl执行之前,f还是非空的,执行之后,f成了空指针,接着循环的时候到116条件成立,回调函数,发现是空的,于是抛出了bad_function_call
导致了abort的发现,也就有了后面的SIGABRT信号
每日小结:
1、抽离问题的核心,简化,去除无用项和干扰项
2、提高效率是debug的第一步
Day four -- 豁然开朗
问题到这里看似没有办法了,不,只要你追下去,就会有答案!
在对比其他方案和不同版本的Android代码的时候,我发现一点,EVIOCGBIT第二个参数,大部分写的都是sizeof(xxx),xxx是ioctl的第三个参数
在网上的介绍这个用途是:
使用 EVIOCGBIT ioctl可以获取设备的能力和特性。它告知你设备是否有 key或者 button。
EVIOCGBIT ioctl处理 4个参数 ( ioctl(fd, EVIOCGBIT(ev_type, max_bytes), bitfield)):
ev_type是返回的 type feature( 0是个特殊 case,表示返回设备支持的所有的type features);
max_bytes表示返回的最大字节数;
bitfield域是指向保存结果的内存指针;
return value表示保存结果的实际字节数,如果调用失败,则返回负值。
这个时候就有了一个尝试:
int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits);
修改为:
int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), key_bits);
结果一试,好了,recovery正常。
看着这部分的代码很有意思,key_bits多大?
==》看下定义:
unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)];#define BITS_PER_LONG (sizeof(unsigned long) * 8)
#define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG)
KEY_MAX现在是0x2ff,也就是767,BITS_TO_LONGS呢计算了有多少个long,一个long 4个字节,也就是32位
那么key_bits那个数组里面大小就是767/32 = 23.96 ==》24 ,BITS_TO_LONGS计算出来是24,因为24个long才能装得下23.96
那么sizeof(key_bits)就是24*4=96
sizeof(key_bits)=96;KEY_MAX=767,为什么将这个数据改小了就行了呢?
接下来就到驱动里面看为什么了,涉及代码:
/android/common/drivers/input/evdev.c
/android/bionic/libc/kernel/uapi/asm-generic/ioctl.h
我们把注意力集中在evdev_do_ioctl函数中
size = _IOC_SIZE(cmd);
.../* Multi-number variable-length handlers */if (_IOC_TYPE(cmd) != 'E')return -EINVAL;if (_IOC_DIR(cmd) == _IOC_READ) {if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0, 0)))return handle_eviocgbit(dev,_IOC_NR(cmd) & EV_MAX, size,p, compat_mode);if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {if (!dev->absinfo)return -EINVAL;t = _IOC_NR(cmd) & ABS_MAX;abs = dev->absinfo[t];if (copy_to_user(p, &abs, min_t(size_t,size, sizeof(struct input_absinfo))))return -EFAULT;return 0;}}
这里有很多位运算了,首先EVIOCGBIT(android/bionic/libc/kernel/uapi/linux/input.h)就是一个宏,对于两个参数已经确定,这个宏的值都已经确定了,即cmd
#define EVIOCGBIT(ev,len) _IOC(_IOC_READ, 'E', 0x20 + (ev), len)
计算了一下:
767:
1000 0010 1111 1111 0100 0101 0010 0001
32位signed int,即-2097199839
在代码中添加打印,和计算结果一致
这个时候E方案的修改就有点意思了,如果说这个EVIOCGBIT返回的都是负值,那么判断小于0则等于0的原因是什么?
这样修改和直接注释这段代码有区别吗,都失去了本身的意义。这么说来,又推翻了E方案的修改,也是有问题的。
计算96的看看,即上面的sizeof(key_bits)
96:
1000 0000 0110 0000 0100 0101 0010 0001
32位signed int,即-2141174495
没有啥用。
这个时候回到代码,跟到最后面是copy_to_user(p, bits, len)
这个是从内核中拷贝数据到用户空间,用户空间的数据是p,内核中的是bits,len是读的长度
以ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits);分析为例:
从上面的evdev_do_ioctl已经得出接下来的是handle_eviocgbit
其中参数计算后,dev是对应的设备,_IOC_NR(cmd) & EV_MAX 的值是1,即EV_KEY,size=767,p就是key_bits,compat_mode=0;
static int handle_eviocgbit(struct input_dev *dev,unsigned int type, unsigned int size,void __user *p, int compat_mode)
{unsigned long *bits;int len;switch (type) {case 0: bits = dev->evbit; len = EV_MAX; break;case EV_KEY: bits = dev->keybit; len = KEY_MAX; break;case EV_REL: bits = dev->relbit; len = REL_MAX; break;case EV_ABS: bits = dev->absbit; len = ABS_MAX; break;case EV_MSC: bits = dev->mscbit; len = MSC_MAX; break;case EV_LED: bits = dev->ledbit; len = LED_MAX; break;case EV_SND: bits = dev->sndbit; len = SND_MAX; break;case EV_FF: bits = dev->ffbit; len = FF_MAX; break;case EV_SW: bits = dev->swbit; len = SW_MAX; break;default: return -EINVAL;}pr_err("handle_eviocgbit KEY_CNT : %d , KEY_MAX : %d\n",KEY_CNT,KEY_MAX);pr_err("handle_eviocgbit bits : %d , len : %d , size : %d , compat_mode : %d \n",sizeof(bits),len,size,compat_mode);return bits_to_user(bits, len, size, p, compat_mode);
}
下一个是bits_to_user,参数bits=dev->keybit,len = KEY_MAX,size=767,p就是key_bits,compat_mode=0
这里我们想当然的以为KEY_MAX就是767,实际上添加打印才发现这个是1791,即0x6ff
static int bits_to_user(unsigned long *bits, unsigned int maxbit,unsigned int maxlen, void __user *p, int compat)
{int len, i;if (compat) {len = BITS_TO_LONGS_COMPAT(maxbit) * sizeof(compat_long_t);if (len > maxlen)len = maxlen;for (i = 0; i < len / sizeof(compat_long_t); i++)if (copy_to_user((compat_long_t __user *) p + i,(compat_long_t *) bits +i + 1 - ((i % 2) << 1),sizeof(compat_long_t)))return -EFAULT;} else {len = BITS_TO_LONGS(maxbit) * sizeof(long);if (len > maxlen)len = maxlen;if (copy_to_user(p, bits, len))return -EFAULT;}return len;
}
len计算之后是224,前面已经计算过了sizeof(key_bits)=96
这意味着什么,我们把224个字节塞在key_bits里面了,这明显数组越界了啊
再来分析ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), key_bits);这个为什么不会,追代码和前面一致,
但是size=96,并不是767,然后在if (len > maxlen)判断下,len=96,因此没有越界
问题到这里,已经明了了,解决问题的办法有两个:
1、修改ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), key_bits);
这个改法影响是0x2ff之后的按键值都不能在recovery下被检测到了,不过这个影响也不大
2、修改android/bionic/libc/kernel/uapi/linux/input-event-codes.h里面的KEY_MAX为0x6ff
这个修改为什么可以呢?我们回顾一下6fc8254 这个修改,我们之前一直把关注点放在KEY_MAX上,忽略了还有一处很重要的修改:
+ for (i = KEY_FAC_ATV; i < KEY_MAX; i++)
+ __set_bit(i, dev->input_device->keybit);
这里的KEY_MAX是多少?没错,就是0x6ff,这个也就是后面的dev->keybit
修改android/bionic/libc/kernel/uapi/linux/input-event-codes.h里面的KEY_MAX后,
unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)];
==》
unsigned long key_bits[BITS_TO_LONGS(1791)];
==》224个字节
数组同样也不会越界
参考文档
Linux下对input设备调用ioctl时指定EVIOCGBIT选项时的缓冲区该多大_imred的博客-CSDN博客_eviocgbit
https://www.cnblogs.com/yeyinfu/p/7317665.html
Android Framework------之Input子系统 - Balancor - 博客园
Linux Input 子系统Event Interface详解 <一> 得到Input Device信息_BigSam78_新浪博客
input子系统事件处理层evdev分析_流浪的孩子的博客-CSDN博客
自动扫描键盘对应的输入事件设备结点文件-bluedrum-ChinaUnix博客
linux内核input子系统解析 - 华清远见嵌入式学院
Android Tombstone/Crash的log分析和定位 - hrhguanli - 博客园
C++11新特性之八——函数对象function - 小天_y - 博客园