记一次数组越界的BUG

news/2024/11/28 6:34:49/

(这是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 - 博客园

 


http://www.ppmy.cn/news/715592.html

相关文章

android porting usb audio,android - 在android中启动时找不到audio-hal-2-0 - 堆栈内存溢出...

我正在为Resenas Rcar-H3开发板构建android 9。 构建过程成功。 但是,在将映像刷新到电路板上之后,启动过程将无法正常进行。 audioserver由初始化进程重新启动,并通知一些错误,如下所示: [ 737.947862] init: Service audioserver (pid 2734) exited with status 1 [ 737…

Nginx——Nginx反向代理

目录 1、Nginx反向代理概述 1.1、Nginx正向代理 1.2、Nginx反向代理 1.2.1、Nginx反向代理的配置语法 1.2.2、Nginx反向代理实战 2、Nginx的安全控制 2.1、如何使用SSL对流量进行加密 2.1.1、nginx添加SSL的支持 2.1.2、Nginx的SSL相关指令 2.1.3、生成证书 2.1.4、…

BGP概述及基础配置(一)

BGP概述及基础配置&#xff08;一&#xff09; 一、BGP——边界网关协议 BGP是一种实现自治系统AS之间的路由可达&#xff0c;并选择最佳路由的距离矢量路由协议。 AS概述&#xff1a;指的是在同一个组织管理下&#xff0c;使用相同选路策略的设备的集合。 不同AS通过AS号区…

Nmap-06:Nmap的NSE脚本使用

目录 1.NSE介绍 2.NSE的使用 3.NSE分类使用 4.NSE调试功能使用 5.NSE参数的使用 6.NSE更新 7.NSE脚本分类 1.NSE介绍 NSE&#xff08;Nmap Script Engine&#xff09;是Nmap脚本引擎&#xff0c;内置了很多可以用来扫描的、针对特定任务的脚本。通过NSE可以不断拓展Nmap…

【9929】潜水员

Time Limit: 1 second Memory Limit: 128 MB 【问题描述】 潜水员为了潜水要使用特殊的装备。他有一个带2种气体的气缸&#xff1a;一个为氧气&#xff0c;一个为氮气。让潜水员下潜的深度需要各种的数量 的氧和氮。潜水员有一定数量的气缸。每个气缸都有重量和气体容量。潜水员…

2023全云在线联合微软AIGC专场沙龙:人工智能与企业创新,促进创造力的数字化转型

6月29日&#xff0c;由全云在线平台和微软联合主办的人工智能与企业创新&#xff1a;促进创造力的数字化转型——2023AIGC微软专场沙龙在广州天河区正佳万豪酒店举行。 关于2023AIGC微软专场沙龙 GPT翻开了AGI新的一页&#xff0c;也翻开了各行各业的新篇章。 2022年11月30日…

台式中端计算机配置单,2017年台式电脑主流配置

对于网络上推荐的各种组装电脑配置清单,看的眼花缭乱的,不知道如何选择,下面就让学习啦小编给大家推荐一些2017年的主流台式电脑配置单给大家吧。 2017年主流台式电脑配置单一 CPU类型: Intel英特尔酷睿i5-4590 CPU主频: 3.0GHz及以上 主板品牌: Gigabyte/技嘉(该主板推荐品…

电脑内存常见问题处理方法

相信众多朋友在使用电脑时,总会遇到这样或那样的各种问题。如启动电脑却无法正常启动、无法进入操作系统或是运行应用软件,无故经常死机等故障时,这些问题的产生常会因为内存出现异常故障而导致操作失败。这是因为内存做为电脑中三大件配件之一,主要担负着数据的临时存取任…