C 语言类型强转引发的隐蔽内存破坏问题分析

1、问题背景

最近在开发过程中遇到一个非常诡异的问题:

  • 某些变量被篡改成异常值
  • 加一行 printf 后,问题消失或表现改变
  • Debug 版本容易复现
  • Release 版本基本正常

这类问题非常典型,但也非常容易误导人,因为它看起来像:

随机问题 / 编译器问题 / 栈问题 / 多线程问题

但最终定位下来,其实是一个经典错误:

👉 非法类型转换 + 栈内存越界写(Undefined Behavior)


2、最小复现代码

测试平台:Aarch64

c 复制代码
#include <stdio.h>
#include <stdint.h>

void overflow(uint64_t *p) {
    uint64_t a = 0XFF;
    *p = a;
}

void test() {
    uint8_t a;
    uint32_t x = 0x11111111;
    uint32_t y = 0x22222222;

    printf("Before:\n");
    printf("x = 0x%x, y = 0x%x\n", x, y);

    overflow((uint64_t *)&a);

    printf("\nAfter:\n");
    printf("x = 0x%x, y = 0x%x\n", x, y);
}

int main() {
    test();
    return 0;
}

3、运行现象

Debug 版本

bash 复制代码
[root@sylixos:/apps/app_test]# ./app_test
Before:
x = 0x11111111, y = 0x22222222

After:
x = 0x11000000, y = 0x0

👉 x 和 y 都被破坏了


Release 版本

bash 复制代码
[root@sylixos:/apps/app_test]# ./app_test
Before:
x = 0x11111111, y = 0x22222222

After:
x = 0x11111111, y = 0x22222222

👉 看起来完全正常

4、问题本质

核心问题在这一行:

c 复制代码
overflow((uint64_t *)&a);

把 1 字节变量当成 8 字节变量,然后越界写。

c 复制代码
*p = a;   // 写 8 字节

我们观察反汇编:

Debug 版本:

c 复制代码
void test() {
 744:        a9be7bfd         stp        x29, x30, [sp, #-32]!
 748:        910003fd         mov        x29, sp
 74c:        39005fff         strb       wzr, [sp, #23]        // 将 0 存储到 [sp + 23] 这个栈内存地址中
 
 750:        3200e3e0         mov        w0, #0x11111111       // 将立即数 0x11111111 加载到 w0 寄存器       
 754:        b9001fe0         str        w0, [sp, #28]		   // 将 w0 寄存器的值存储到栈上
 
 758:        3203e3e0         mov        w0, #0x22222222   	   // 将立即数 0x22222222 加载到 w0 寄存器   
 75c:        b9001be0         str        w0, [sp, #24]		   // 将 w0 寄存器的值存储到栈上
 ......
 ......
 76c:        b9401be2         ldr        w2, [sp, #24]         // 从栈中读取 y
 770:        b9401fe1         ldr        w1, [sp, #28]         // 从栈中读取 x
 774:        90000000         adrp        x0, 0 <.plt-0x480>
 778:        911fe000         add        x0, x0, #0x7f8
 77c:        97ffff49         bl        4a0 <printf@plt>
 780:        91005fe0         add        x0, sp, #0x17			// x0 保存的是变量 a 的地址,0x17 换算成十进制就是 23
 784:        97ffff4f         bl        4c0 <overflow@plt>
 788:        90000000         adrp        x0, 0 <.plt-0x480>
 78c:        91204000         add        x0, x0, #0x810
 790:        97ffff48         bl        4b0 <puts@plt>
 794:        b9401be2         ldr        w2, [sp, #24]			// 从栈中读取 y
 798:        b9401fe1         ldr        w1, [sp, #28]			// 从栈中读取 x
 79c:        90000000         adrp        x0, 0 <.plt-0x480>
 7a0:        911fe000         add        x0, x0, #0x7f8
 7a4:        97ffff3f         bl        4a0 <printf@plt>
}

👉 栈布局大致是:

bash 复制代码
sp+23 : a (1 byte)
sp+24 : y (4 bytes)
sp+28 : x (4 bytes)

写入发生什么?

c 复制代码
*p = 0xFF;

👉 实际写:

sp+23 ~ sp+30 (连续 8 字节)

覆盖范围:

a + y + x 的一部分

4.1 为什么 Debug 会显现问题,Release 却不会?

查看 Release 的反汇编:

c 复制代码
void test() {
 750:        a9bd7bfd         stp        x29, x30, [sp, #-48]!
 754:        90000000         adrp        x0, 0 <.plt-0x480>
 758:        911f8000         add        x0, x0, #0x7e0
 75c:        910003fd         mov        x29, sp
 760:        f9000bf3         str        x19, [sp, #16]
 764:        90000013         adrp        x19, 0 <.plt-0x480>
 768:        3900bfff         strb        wzr, [sp, #47]
 76c:        97ffff51         bl        4b0 <puts@plt>
 770:        3203e3e2         mov        w2, #0x22222222                    // #572662306
 774:        3200e3e1         mov        w1, #0x11111111                    // #286331153
 778:        911fa273         add        x19, x19, #0x7e8
 77c:        aa1303e0         mov        x0, x19
 780:        97ffff48         bl        4a0 <printf@plt>
 784:        9100bfe0         add        x0, sp, #0x2f
 788:        97ffff4e         bl        4c0 <overflow@plt>
 78c:        90000000         adrp        x0, 0 <.plt-0x480>
 790:        91200000         add        x0, x0, #0x800
 794:        97ffff47         bl        4b0 <puts@plt>
 798:        aa1303e0         mov        x0, x19
 79c:        3203e3e2         mov        w2, #0x22222222                    // #572662306
 7a0:        3200e3e1         mov        w1, #0x11111111                    // #286331153
 7a4:        97ffff3f         bl        4a0 <printf@plt>
}
 7a8:        f9400bf3         ldr        x19, [sp, #16]
 7ac:        a8c37bfd         ldp        x29, x30, [sp], #48
 7b0:        d65f03c0         ret
 7b4:        d503201f         nop
 7b8:        d503201f         nop
 7bc:        d503201f         nop

👉 x 和 y:

  • 根本没从内存读
  • 直接用立即数
c 复制代码
mov w2, #0x22222222
mov w1, #0x11111111

Release 版本改变了栈布局、重排变量、使用寄存器缓存数据

4.2 为什么加 printf 会改变程序执行结果?

原因: printf 会影响栈布局

  • 压栈更多数据
  • 改变变量偏移
相关推荐
子木HAPPY阳VIP7 小时前
Tomcat 9 + JSP 中文乱码终极解决方案(完整版可复制)
java·开发语言·docker·tomcat·jsp
知识分享小能手7 小时前
R语言入门学习教程,从入门到精通,R语言分布式数据可视化(6)
学习·信息可视化·r语言
郝学胜-神的一滴7 小时前
epoll 反应堆模型深度拆解:从红黑树到回调闭环,手写高性能回射服务器
linux·运维·服务器·开发语言·c++·unix
csbysj20207 小时前
Bootstrap4 模态框
开发语言
AI玫瑰助手7 小时前
Python基础:输入input与输出print函数详解
开发语言·windows·python
Gary Studio7 小时前
安卓HAL AIDL经验笔记
笔记
rit84324997 小时前
电容层析成像(ECT)的ART算法MATLAB演示实例
开发语言·算法·matlab
故事和你917 小时前
洛谷-算法2-4-字符串2
开发语言·数据结构·c++·算法·深度优先·动态规划·图论
郝学胜-神的一滴7 小时前
干货版《算法导论》 02 :算法效率核心解密
java·开发语言·数据结构·c++·python·算法