C# · 12月 20, 2021

C语言二进制文件跨平台问题

      最近,在一个技术交流群内,有朋友问:“linux下的bin文件是在大多数linux平台下都能运行的码?比如Ubuntu下的bin能放在redhat上运行吗?”当时简单回答了一下,主要考虑三方面,cpu架构(指令集)、动态库、系统调用。最近在学习逆向技术时候,听烟台大学的贺利坚老师一句,“编程要细细品味”,觉得特别对味,于是,把这老问题翻回来再品一番。

      linux下bin目录下存放的是编译好的二进制文件,这些二进制文件多由C语言开发编译得到,涉及到C的编译,我们就需要从cpu架构、动态库和系统api三方面来考虑。

      首先从底层考虑。对于指令集系统,可以分为精简指令集系统CISC和复杂指令集系统RISC,每个指令集系统又可以分为多种cpu架构,如精简指令集中有OWER/PowerPC架构、MIPS架构、Alpha架构、 ARM架构,而复杂指令集有x86、x86-64架构。在编译过程中,编译器根据cpu架构,将源代码编译成当前架构下机器可识别的机器指令。因此,当我们从A架构的cpu机器上编译出二进制文件,在B架构的cpu上运行时,很可能因为B架构的cpu并不认识A架构下编译出的指令,导致我们的程序无法正常运行。

     当cpu架构相同时,我们就要从OS层面考虑这个问题。比如都是x86架构cpu的两台机器,我们分别安装了linux和windows操作系统,linux可执行文件有其特定的封装格式elf格式,而windows操作系统则封装为pe格式。这些二进制文件很多时候并不是一个完整的可执行文件,在运行时候,通常会调用系统中已经编译好的动态库文件,在linux中为.so文件,windows中为dll,显然两者的动态库文件有着很大不同。因此,在只有二进制文件的情况下,由于需要调用的动态库文件不同,程序常常很难夸平台运行。

      当命令运行不依赖动态库时(如将静态库编译到整个命令中),又要考虑什么呢?这就需要我们从C语言编译原理的角度来认识这个问题。C编译的过程分为编译和链接两步,编译将源文件编译成目标文件,链接则是将目标文件、启动代码和库文件三者结合在一起。启动代码就是最终运行的程序和操作系统之间的一个接口,如dos和linux运行在相同cpu硬件的机器上,因为硬件相同,所以目标文件是相同的,由于我们使用静态库编译的,因此库文件也是相同的,但dos和linux要使用不同的启动代码,因此,从dos机器上编译的二进制文件,在linux机器上也很难运行。

      最后,回到我们原来的问题,redhat和Ubuntu上的bin能通用吗?答:如果架构不同的cpu编译出的操作系统中的bin,很多时候会不能跨平台通用的,如64位cpu上编译的redhat系统中的bin,放在32位的Ubuntu中就不能运行。在硬件相同,os也相同的情况下,就需要比较.so动态库文件、启动文件是否相同,如果三者都一致,我们可以判断这类文件通常是可以跨平台运行的。