• 正文
  • 相关推荐
申请入驻 产业图谱

嵌入式Linux诡异bug:局部变量莫名被改?指针类型不匹配内存越界详解

07/14 08:50
160
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

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

大家好,我是仲一。最近在排查一个嵌入式 Linux 项目,有个模块跑着跑着,局部变量会莫名其妙被改掉。反复定位下来,根因很小——一个 unsigned int 的地址,被当成 unsigned long long * 传进了函数。

用几段代码还原一下现场。

第一段代码

#include <stdio.h>

#define CONFIG_SIMPLE_SYS_BOOTARGS "mem=%luM console=ttyS0,115200 root=/dev/mtdblock%d rootfstype=squashfs"
void test(unsigned long long *size)
{
    unsignedlonglong memsize = 456789;
    *size = memsize;
}

int main()
{
    unsignedint  var = 272;
    int var2 = 14;
    
    char var3[256]={0};
    printf(" var2:%dn",var2);

    test((&var));
    
    printf(" var2:%dn",var2);
    sprintf(var3, CONFIG_SIMPLE_SYS_BOOTARGS, var, var2);

    printf(" var2:%dn",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 console=ttyS0,115200 root=/dev/mtdblock%d rootfstype=squashfs"
void boot_getMemSize(unsigned long long *size)
{
    unsignedlonglong memsize = 419430400;
    *size = memsize;
}

int main()
{
    unsignedint  memSize = 272;
    int partIndex = 14;
    
    char tempBuf[256]={0};
    printf(" partIndex:%dn",partIndex);

    boot_getMemSize((&memSize));
    
    printf(" partIndex:%dn",partIndex);
    sprintf(tempBuf, CONFIG_SIMPLE_SYS_BOOTARGS, memSize, partIndex);
    printf(" tempBuf:%sn",tempBuf);
    printf(" partIndex:%dn",partIndex);

    return0;
}

这种问题常见的原因就两个:要么一开始就把 unsigned int 当 unsigned long long 传了,要么函数接口升级了类型,调用方没跟上。

那能不能不动变量类型,只改函数签名来绕过?有人试过这么写:

#include <stdio.h>
#define CONFIG_SIMPLE_SYS_BOOTARGS "mem=%luM console=ttyS0,115200 root=/dev/mtdblock%d rootfstype=squashfs"
void boot_getMemSize(unsigned long long **size)
{
    staticunsignedlonglong memsize = 419430400;
    *size = &memsize;
}

int main()
{
    unsignedint  *memSize;
    int partIndex = 14;
    
    char tempBuf[256]={0};
    printf("[] partIndex1:%dn",partIndex);

    boot_getMemSize(&memSize);
    
    printf("[] partIndex2:%d,memSize=%lun",partIndex, *memSize);
    sprintf(tempBuf, CONFIG_SIMPLE_SYS_BOOTARGS, *memSize, partIndex);
    printf("[] tempBuf:%sn",tempBuf);
    printf("[] partIndex3:%dn",partIndex);

    return0;
}

绕了个弯,但引入了 static 变量,多线程或函数重入就炸了,不推荐。

反汇编看一下

回到第一种情况,看反汇编就一清二楚了。

000000000040063c <main>:

int main()
{
  40063c:   a9ae7bfd    stp x29, x30, [sp,#-288]!
  400640:   910003fd    mov x29, sp
    unsigned int  var = 10;
  400644:   52800140    mov w0, #0x0a
  400648:   b9011ba0    str w0, [x29,#280]

    int      var2     = 5;
  40064c:   528000a0    mov w0, #0x5
  400650:   b9011fa0    str w0, [x29,#284]

    ...
    test((&var));
  400678:   910463a0    add x0, x29, #0x118
  40067c:   97ffffe5    bl  400610 <test>
    ...

var 在 x29+280var2 在 x29+284,紧挨着。test 的 x0 指向了 var

再看 test 的函数体:

void test(unsigned long long *var)
{
  ...
             *var = tempVar;
  400624:   f94007e0    ldr x0, [sp,#8]
  400628:   f9400fe1    ldr x1, [sp,#24]
  40062c:   f9000001    str x1, [x0]
}

str x1, [x0],一条指令写 8 字节。x0 指向的是 var(4 字节),后 4 字节直接盖到 var2 上去了。

总结

根因一句话:函数参数类型和实参类型不匹配。8 字节写入 4 字节的目标,多出来的字节肯定踩到旁边的变量。

平时写代码,编译器的 -Wpointer-sign 和 -Wformat 开关建议打开。如果有强制类型转换,特别留心两边的内存大小。

常见踩坑场景:接口升级改了参数类型,调用方漏了没改用了 void* 做通用参数,传参时类型搞错头文件更新了,其他模块没重新编译

遇到局部变量鬼畜改值的,先看看是不是指针类型大小不匹配。


相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

作者就职于某500强公司,担任BSP工程师。具有丰富的嵌入式开发经验。专栏主要分享计算机基础,操作系统,Linux驱动开发,Arm体系与架构,C/C++,数据结构与算法等相关文章。欢迎关注我的公众号【嵌入式与Linux那些事】,一起学习交流。