吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2307|回复: 1
收起左侧

[调试逆向] 由使用未定义变量引出的gcc编译器优化初探

[复制链接]
XaraMysteria 发表于 2025-10-10 14:13

由使用未定义变量引出的gcc编译器优化初探

起因

有人问我一道题为什么在本地运行时对的,但提交到评测机却异常了。我的第一反应就是变量初始化可能有问题,而编译选项的不同导致了不同行为。

经过仔细研究,发现一个有趣的现象,遂在此分享一下。

代码类似于这样的逻辑(各种变量的调用关系跟题目里是一样的,这里做了个最简抽象):

#include<stdio.h>
int test(){
        int a,b;
        scanf("%d",&b);
        if(b>0){
                a=-1;
        }else{
                // do sth
                // 但是这里忘记对a变量进行任何赋值操作了,即使用了未定义的变量
        }
        return a;
}

int main(){
        int a = test();
        printf("%d\n",a);
        return 0;
}

我用 printf debug大法

#include<stdio.h>

int test(){
        int a,b;
        scanf("%d",&b);
        if(b>0){
                a=-1;
        printf("%d\n",a);
        }else{
                // do sth
                printf("%d\n",a);
        }
        printf("%d\n",a);
        return a;
}

int main(){
        int a = test();
        printf("%d\n",a);
        return 0;
}

随后本地编译 gcc test.c -o test.exe
b 输入-1,输出结果是

32758
32758
32758

很明显的未定义嘛,但是恰好只要 a!=-1 程序就不会有任何问题,这就是为什么在本地运行是“正确”的

但是线上编译的命令有加优化: gcc -O2 test.c -o test.exe
b 同样输入-1,输出结果是

0
-1
-1

哦豁,不妙,未定义的情况下居然恰好返回了 a=-1, 这下程序在外面就 Error 了,这就是为什么提交之后报错。

略懂逆向的我,自然不愿意停留于此。经过测试,只要开启编译优化选项 -O 就会出现这个问题,这是为什么呢?看看汇编。(我选择了用一个在线汇编编译器来看,很方便,搜索 gobolt compiler explorer 就可以找到 ——并非广告,仅作分享),而且会用颜色标明源代码和汇编的联系。

前置汇编知识

需要了解寄存器,栈空间,函数调用时参数和返回值的传递规约,基本的汇编指令。

假如没有汇编知识,那就看看这个通俗版本(受限于本人水平可能不严谨,见谅):

原本局部变量应该给它一个内存放置的,对它的赋值和读取操作都通过那个内存。所以 a=-1 的操作会把内存的值写为-1,而没有赋值操作的情况下,那块内存原本是啥就是啥(比如上面的输出结果和返回值是32758)

但是,开启优化之后,为了节约时间,编译器选择有些局部变量的简单操作不分配栈空间了,而是直接通过代码做一些预测判断,把-1 的传参数、赋值、返回操作直接在 cpu 内实现,而不再经过内存。然而变量未定义(未赋值)的情况会导致这种预测考虑不周全,在这里就发生了这种情况。优化时编译器以为 返回值的 a 只可能等于-1(毕竟所有给 a赋值的操作都是-1),所以直接就写死了返回-1.

至于为什么在 else 里面打印出 0,大概也是编译器在这个分支内部预测之后发现 a 没有赋值过,就默认给了 0。

探究原理

没开优化之前,只看 test 函数,可以发现对变量 a 的操作都会真正地使用栈空间。所以未初始化的情况下,返回值的确是随缘的(就看那块地址本来是什么数据了)
image-49.png image-50.png
开启优化之后,可以发现对 a 的操作不再经过栈空间,而是直接操作对应寄存器了(如下图标注)

[!note]
当 GCC(或其他现代编译器)开启优化(例如使用 -O1, -O2, -O3 等选项)后,许多局部变量确实可能不会在栈上分配实际空间,甚至可能完全“消失”。

image-51.png image-52.png

而我们发现,return 操作对应的 .L3 那一段是被两个分支共用的。然而,编译器优化 .L3 的时候万万没有考虑到未定义的情况,直接在 return 的时候写上了 mov eax,-1,所以无论走哪个分支,都会返回-1 了,这就是问题出现的原因。

免费评分

参与人数 3吾爱币 +9 热心值 +3 收起 理由
willJ + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
杨辣子 + 1 + 1 谢谢@Thanks!
wzvideni + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

cbxg1992 发表于 2025-10-12 09:06
厉害支持下!!!!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-14 11:35

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表