ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

排查了一周的灵异 Bug,真相是一个不起眼的类型转换

排查了一周的灵异 Bug,真相是一个不起眼的类型转换 大家好我是仲一。最近在排查一个嵌入式 Linux 项目有个模块跑着跑着局部变量会莫名其妙被改掉。反复定位下来根因很小——一个unsigned int的地址被当成unsigned long long *传进了函数。用几段代码还原一下现场。第一段代码#include stdio.h#define CONFIG_SIMPLE_SYS_BOOTARGS mem%luM consolettyS0,115200 root/dev/mtdblock%d rootfstypesquashfsvoid test(unsigned long long *size){unsigned long long memsize456789;*sizememsize;}intmain(){unsigned int var272;int var214;char var3[256]{0};printf( var2:%d\n,var2);test((var));printf( var2:%d\n,var2);sprintf(var3, CONFIG_SIMPLE_SYS_BOOTARGS, var, var2);printf( var2:%d\n,var2);return0;}问题出在test((var))。var只占了 4 字节test期望的是一个unsigned long long *——它要写 8 字节。编译器会报个 warning但程序照常运行test往var的地址一口气写 8 字节多出来的 4 字节正好踩到相邻的var2。实际项目里的写法更隐蔽一点#include stdio.h#define CONFIG_SIMPLE_SYS_BOOTARGS mem%luM consolettyS0,115200 root/dev/mtdblock%d rootfstypesquashfsvoid boot_getMemSize(unsigned long long *size){unsigned long long memsize419430400;*sizememsize;}intmain(){unsigned int memSize272;int partIndex14;char tempBuf[256]{0};printf( partIndex:%d\n,partIndex);boot_getMemSize((memSize));printf( partIndex:%d\n,partIndex);sprintf(tempBuf, CONFIG_SIMPLE_SYS_BOOTARGS, memSize, partIndex);printf( tempBuf:%s\n,tempBuf);printf( partIndex:%d\n,partIndex);return0;}这种问题常见的原因就两个要么一开始就把unsigned int当unsigned long long传了要么函数接口升级了类型调用方没跟上。那能不能不动变量类型只改函数签名来绕过有人试过这么写#include stdio.h#define CONFIG_SIMPLE_SYS_BOOTARGS mem%luM consolettyS0,115200 root/dev/mtdblock%d rootfstypesquashfsvoid boot_getMemSize(unsigned long long **size){static unsigned long long memsize419430400;*sizememsize;}intmain(){unsigned int *memSize;int partIndex14;char tempBuf[256]{0};printf([] partIndex1:%d\n,partIndex);boot_getMemSize(memSize);printf([] partIndex2:%d,memSize%lu\n,partIndex, *memSize);sprintf(tempBuf, CONFIG_SIMPLE_SYS_BOOTARGS, *memSize, partIndex);printf([] tempBuf:%s\n,tempBuf);printf([] partIndex3:%d\n,partIndex);return0;}绕了个弯但引入了static变量多线程或函数重入就炸了不推荐。反汇编看一下回到第一种情况看反汇编就一清二楚了。000000000040063cmain: intmain(){40063c: a9ae7bfd stp x29, x30,[sp,#-288]!400640: 910003fd mov x29, sp unsigned int var10;400644:52800140mov w0,#0x0a400648: b9011ba0 str w0,[x29,#280]int var25;40064c: 528000a0 mov w0,#0x5400650: b9011fa0 str w0,[x29,#284]...test((var));400678: 910463a0addx0, x29,#0x11840067c: 97ffffe5 bl400610test...var在x29280var2在x29284紧挨着。test的x0指向了var。再看test的函数体void test(unsigned long long *var){... *vartempVar;400624: f94007e0 ldr x0,[sp,#8]400628: f9400fe1 ldr x1,[sp,#24]40062c: f9000001 str x1,[x0]}str x1, [x0]一条指令写 8 字节。x0指向的是var4 字节后 4 字节直接盖到var2上去了。总结根因一句话函数参数类型和实参类型不匹配。8 字节写入 4 字节的目标多出来的字节肯定踩到旁边的变量。平时写代码编译器的-Wpointer-sign和-Wformat开关建议打开。如果有强制类型转换特别留心两边的内存大小。常见踩坑场景接口升级改了参数类型调用方漏了没改用了void*做通用参数传参时类型搞错头文件更新了其他模块没重新编译遇到局部变量鬼畜改值的先看看是不是指针类型大小不匹配。end往期推荐嵌入式Linux必读经典书籍嵌入式学习路线推荐一位读者逻辑清晰的提问机械转行嵌入式成功上岸一位音视频方向读者秋招上岸的经历
返回列表