记一次数组越界的BUG

本文详细记录了一位工程师在解决Android设备在执行reboot recovery后出现黑屏问题的过程。通过日志分析、版本对比、代码调试,最终发现问题出在ioctl调用的参数错误,导致了内存越界。修复方法包括修改ioctl调用参数或更新系统中的KEY_MAX值。此外,文章还强调了问题排查中的关键步骤和技巧,如使用dailybuild快速定位问题、简化代码进行调试等。

(这是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的博客-优快云博客_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 messagexxxx
 
project 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.h
 header-y += inotify.h
 header-y += input.h
 header-y += input-event-codes.h
+header-y += key_define.h
 header-y += in_route.h
 header-y += ioctl.h
 header-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_IM
void property_Proc(TywEvent_t *pEvent) { uint32_t u32FrameLen = 0; uint32_t u32Current_Length = 0; while(u32FrameLen < pEvent->u32FrameLen) { u32Current_Length = 0; switch (pEvent->Data[u32FrameLen + 2]) { case GlobalStatus_ID01: // 1. 计算当前数据块长度(保持原逻辑,假设是小端序) u32Current_Length = ((pEvent->Data[u32FrameLen]) | (pEvent->Data[u32FrameLen + 1] << 8)); // 2. 拷贝数据:索引 += u32FrameLen,避免越界 for(int i = 0; i < u32Current_Length - 1; i++) { Receive_MCU_Data.GlobalStatus_ID01H.u8Msg[i] = pEvent->Data[u32FrameLen + i + 3]; } break; case SpeedArea_ID02: // 计算当前数据块长度(小端序) u32Current_Length = ((pEvent->Data[u32FrameLen]) | (pEvent->Data[u32FrameLen + 1] << 8)); // 拷贝数据:索引 += u32FrameLen,避免越界 for(int i = 0; i < u32Current_Length - 1; i++) { Receive_MCU_Data.SpeedArea_ID02H.u8Msg[i] = pEvent->Data[u32FrameLen + i + 3]; } break; case Mileage_ID03: // 计算当前数据块长度(小端序) u32Current_Length = ((pEvent->Data[u32FrameLen]) | (pEvent->Data[u32FrameLen + 1] << 8)); // 拷贝数据:索引 += u32FrameLen,避免越界 for(int i = 0; i < u32Current_Length - 1; i++) { Receive_MCU_Data.Mileage_ID03H.u8Msg[i] = pEvent->Data[u32FrameLen + i + 3]; } break; case BatteryPower_ID04: // 计算当前数据块长度(小端序) u32Current_Length = ((pEvent->Data[u32FrameLen]) | (pEvent->Data[u32FrameLen + 1] << 8)); // 拷贝数据:索引 += u32FrameLen,避免越界 for(int i = 0; i < u32Current_Length - 1; i++) { Receive_MCU_Data.BatteryPower_ID04H.u8Msg[i] = pEvent->Data[u32FrameLen + i + 3]; } break; 代码注释
10-15
<think>好的,我需要分析用户提供的C语言函数,并给出详细的中文解释。首先,这个函数名为property_Proc,接收一个TywEvent_t类型的指针pEvent。函数内部有两个uint32_t变量u32FrameLen和u32Current_Length,初始值都为0。 接下来是一个while循环,条件是u32FrameLen小于pEvent->u32FrameLen。这说明循环会处理整个数据帧,每次处理一个数据块后,u32FrameLen会递增,直到处理完所有数据。循环内部首先将u32Current_Length重置为0,然后根据当前数据块的位置(u32FrameLen + 2)处的值进行switch判断,确定不同的数据块类型。 每个case分支(如GlobalStatus_ID01、SpeedArea_ID02等)都会计算当前数据块的长度。这里的数据块长度是通过读取当前u32FrameLen位置的两个字节,采用小端序组合成16位无符号整数。例如,u32Current_Length = pEvent->Data[u32FrameLen] | (pEvent->Data[u32FrameLen + 1] << 8)。这一步需要注意,因为C语言中移位操作符的优先级较低,所以括号是必须的,否则结果会出错。 计算完长度后,函数会进入一个for循环,从当前位置+3开始拷贝数据到相应的结构体成员中。例如,Receive_MCU_Data.GlobalStatus_ID01H.u8Msg[i] = pEvent->Data[u32FrameLen + i + 3]。这里的索引计算需要确保不会越界,因此循环次数是u32Current_Length - 1次,因为前面两个字节已经用于长度字段,所以实际数据长度是减去1后的值。 需要注意的是,每次处理完一个数据块后,u32FrameLen应该增加当前数据块的总长度(包括长度字段的两个字节和数据本身),否则循环可能无法正确处理下一个数据块,导致无限循环或处理错误。但当前代码中并没有看到u32FrameLen的更新,这可能是一个潜在的bug,需要检查是否在循环结束后有其他地方更新了这个变量,或者是否遗漏了这一步。 此外,每个case的处理逻辑几乎相同,可以考虑使用函数指针或宏来减少重复代码,提高可维护性。同时,数据拷贝部分可能存在缓冲区溢出的风险,需要确保目标数组u8Msg的大小足够容纳u32Current_Length -1个字节的数据,否则会导致内存越界访问,引发未定义行为。 最后,代码中的注释提到“避免越界”,但实际处理中是否正确处理了边界情况,比如当u32Current_Length为0或1时,循环次数是否合理,这也是需要验证的点。例如,当u32Current_Length为1时,循环次数为0次,这可能意味着该数据块没有实际数据,但这种情况是否符合协议设计也需要确认。</think>该函数`property_Proc`用于解析事件数据帧中的多个子数据块。其核心逻辑如下: 1. **循环处理**:通过`while`循环遍历整个数据帧,每次处理一个子数据块。`u32FrameLen`录当前处理的起始位置,直到处理完所有数据(`pEvent->u32FrameLen`为总长度)。 2. **数据块类型判断**: - 使用`switch`语句根据`Data[u32FrameLen + 2]`的值(即数据块ID)决定如何解析后续数据。 - 支持的ID包括`GlobalStatus_ID01`、`SpeedArea_ID02`、`Mileage_ID03`、`BatteryPower_ID04`。 3. **小端序解析长度字段**: - 从`Data[u32FrameLen]`和`Data[u32FrameLen + 1]`读取两个字节,按小端序组合为16位无符号整数(`u32Current_Length`)。 - 示例:`0x01 0x02`会被解析为`0x0201`(十进制513)。 4. **数据拷贝**: - 根据`u32Current_Length`的值,从`Data[u32FrameLen + 3]`开始拷贝`u32Current_Length - 1`字节到对应结构体的`u8Msg`缓冲区中。 - 例如,若`u32Current_Length=5`,则拷贝`Data[...+3]`到`Data[...+7]`共4字节数据。 5. **潜在问题**: - **缺少`u32FrameLen`更新**:处理完一个数据块后,未更新`u32FrameLen`,可能导致无限循环或处理错误。应添加`u32FrameLen += u32Current_Length + 2`。 - **缓冲区溢出风险**:需确保`u8Msg`的大小足够大,否则拷贝时可能越界。 - **未处理未知ID**:`switch`中未包含`default`分支,可能导致未处理的数据块被忽略。 ```c // 示例:修复后的u32FrameLen更新逻辑 u32FrameLen += u32Current_Length + 2; // 跳过当前数据块(2字节长度 + u32Current_Length -1 数据) ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值