大家好,我是轩辕。
今天给大家看个有趣的编程知识。
看看下面几行代码:
大家觉得运行后会输出什么?
一进main函数后就进入一个while死循环,理论上来说,这程序就死在这里了。
但有人居然运行打出了“hello world”,你敢信吗?
这是著名的安全论坛——看雪论坛的一位网友(LeadroyaL)的研究过程,强烈推荐大家看看,gcc编译器到底干了什么事!
以下是该网友的原贴:
某日在群里看到这张很怪的表情包,想着这不是扯淡么,一看到运行结果顿时傻眼了。。。
#include <stdio.h>
int main(){
while(1) ;
}
void unreachable(){
printf("HelloWorld\n");
}
先放一个在线编译C代码的网站:https://godbolt.org/
初步结果:只有在比较新的C++上才会触发,稳定复现,因此选定 clang++ test.cpp -O1 作为目标进行研究。
通过 objdump -d a.out 可以看到,_start 函数直接调用了 unreachable 函数。(一口盐汽水就喷了在屏幕上。。。)
gdb 调试,很诡异,也直接就断在了 unreachable 里,说明二进制确实执行到了这里。
什么?还有程序没有main、从别的函数开始执行?这连接器是抽什么风?
说实话,我还是第一次见到 “没有main函数、但是能运行” 的程序,于是查看符号表,发现main函数还在,和unreachable指向同一个偏移,但函数大小为0。
也就是说,main函数的函数体被清空了,恰好指向了unreachable的位置,“越界”执行了下一个函数的代码。
clang++ -c -S test.cpp -O1 得到 test.s 汇编文件。
我去掉了汇编大部分不必要的注释,但故意保留了一部分,才能让读者知道,他读的是汇编 。可以看到,main函数里一条指令都没有,全部都是点开头的或者井号开头的注释。
去除全部注释后就是,main 和 _Z11unreachablev 指向同一段汇编代码:
clang++ -emit-llvm -S -c test.cpp -O1
得到 test.ll IR文件。可以看出,确实在IR阶段main的内容就发生了变换,由原先的死循环被替换成了 unreachable。注意,这个unreachable是一条名叫UnreachableInstruction的指令,并非函数名。
对比 -O0 的输出结果:
最终,可以得到结论,这个反常的现象是在IR层面的编译器优化引起的。
原文来自「编程技术宇宙」|侵删