【gcc编译优化系列】各操作系统平台下的可执行文件格式介绍

目 录

一 重要知识点

二 本文重点内容

三 gcc相关知识补充

四 Windows 系列(Win7、Win8.1、Win10…)

五 Linux 系列(Ubuntu、RedHat、Kali Linux…)

六 Mac 系列(MacOS & iOS)

七 更多分享


一 重要知识点

- 可执行文件指的是可以由操作系统进行加载执行的文件;

- 可执行文件的文件格式常见有:
   exe 和 dll(Windows 系列)、elf(Linux 系列)和 Mach-O(Mac系列);

- 可执行文件的 “格式”,和 “后缀” 二者没有直接关系,
   事实上 Linux 下如 elf 类型是没有后缀名的;

- 可执行文件的 “执行” 二字,从直接执运行和间接调用两方面理解;

- 通用对象文件格(COFF,Common Object File Format)是这些 Unix-Like 系统可执行文件(如可执行的 elf、PE)的共同祖先,如今已被继承取代。

## 可执行文件结构学得好,对以后搞加密破解都是很有帮助的 ##


二 本文重点内容

主要包括:

Windows的PE文件
Linux的elf文件
Mac的macho文件
参考:维基百科 - 不同类型的可执行文件


三 gcc相关知识补充

/**********************************************************************
 只编译不链接,生成二进制对象文件,后缀名在windows下为obj,在Unix-like下为o
 如果想要生成可执行文件,还需要将这些文件进行链接操作。
**********************************************************************/
gcc -c <file_name>
/**********************************************************************
直接生成可执行文件,包含了前面提到的生成object file的过程,
缺点是看不到编译的细节,想要看编译细节还请使用别的指令参数。
**********************************************************************/
gcc <file_name>

区别和联系:不同系统文件后缀名(只有 Windows 才有后缀名,Unix-Like 那叫假后缀名,这里写是为了方便理解)

Windows的obj文件,相当于Unix-Like的o文件
Windows的dll文件,相当于Unix-like的so文件
Windows的lib文件,相当于Unix-Like的a文件
Windows的PE文件,基本相当于Unix-Like的elf或mach-O文件
注意上面写的是 Unix-Like,也就是包括了 Linux 和 Mac 两个系列。我们说 Windows 的 PE、Linux 的 elf、Mac 的 mach-O,因为它们三者是对应的,是同一层次的。PE、elf、mach-O,不单单指的是某一个类型,它们指的是一类类型。比如 Windows 的 PE 文件,又可以分为 exe 文件和 dll 文件等… Linux 和 Mac 也可以进行类似的划分。而之所以把 Windows PE 单独拿出来讲,讲 exe,讲 dll,是因为 Windows 讲究文件后缀名,而 Unix-Like 的 OS 并没有这方面习惯。其实要划分也是可以划分的。


四 Windows 系列(Win7、Win8.1、Win10…)

PE 格式,可移植可执行格式(Portable Executable),
是 Windows 下的主要可执行文件格式。别被名字迷惑了,PE 文件必须是 Windows 下的文件。

在 Windows 下,exe 格式文件的后缀名就是 exe,
同理,dll格式文件的后缀名就是dll…(Windows比较注重文件后缀名)。

不过 obj 文件虽然叫obj文件,其实格式是COFF… 这点提请注意下…(唤做 COFF object)

PE(Portable Executable)格式,是微软 Win32 环境可移植可执行文件(如exe、dll、vxd、sys 和 vdm 等)的标准文件格式。PE 格式衍生于早期建立在VAX(R)VMS(R)上的COFF文件格式(前面有讲)。其中 portable 是指对于不同的 Windows 版本和不同的 CPU 类型上 PE 文件的格式是一样的,当然 CPU 不一样了,CPU 指令的二进制编码是不一样的。只是文件中各种东西的布局是一样的。

PE 格式… 这有个方便理解的简化版本

在这里插入图片描述

如同 ELF 存在的 ELF 头(本文后面讲),PE 格式文件也有 PE 头(PE header),而且加以区分 PE 格式还在文件的最上面加上了 MS-DOS 头部(DOS MZ header)… PE文件的第一个字节起始于MS-DOS头部,被称作IMAGE_DOS_HEADER。

紧随 DOS 头的,是 PE 头。PE Header 是 PE 相关结构 NT 映像头(IMAGE_NT_HEADERS)的简称,其中包含许多PE装载器用到的重要字段

包括:

  • 程序入口点(OEP,Original Entry Point)
  • 文件偏移地址(File Offset)
  • 虚拟地址(VA,Visual Address)
  • 基地址(ImageBase,这个单词记住)
  • 相对虚拟地址(RVA,Relative Virual Address)

文件偏移地址是指数据在PE文件中的地址,是文件在磁盘上存放时相对于文件开头的偏移。文件偏移地址从 PE 文件的第一个字节开始计数,起始值为 0。用十六进制工具(如 WINHEX)打开文件所显示的地址就是文件偏移地址。对于一个确定的文件,基地址可能变化这是由编译器决定的,而偏移是固定的。通常认为 EXE 文件的基地址都是 0x00400000,而 DLL 文件的基地址都是 0x0010000(这两个量要记住)。

相对虚拟地址 - 虚拟地址 = 基地址

具体 PE 格式的结构体定义可以在编译器的 include 文件夹里的 WinNT.h 找到。在 Window 下的MinGW,相当于 Linux 的 gcc。你可以在这里找到它的源码,看看结构体是如何定义的。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述 

复杂版 PE 结构图,呵呵呵

在这里插入图片描述

在这里插入图片描述 

 在这里插入图片描述

PE 文件执行时,和进程的映射关系

在这里插入图片描述

最后放两张总结性的图

在这里插入图片描述在这里插入图片描述

 

当可执行文件执行起来,跑起来,就和系统内存挂钩。不管是 Windows 还是 Linux,每个程序分配 4G 的虚拟内存。不是真的分配这么多,这是允许访问的最大内存,况且 4G 中大概 2G 留给操作系统,剩下的还不到 2G。物理地址的内存空间属于底层,由操作系统负责,而进程属于用户;进程的虚拟内存空间和物理内存空间之间存在看不见的地址映射关系。

 而从虚拟地址通过页表映射到物理地址的过程,就是页表映射。

在这里插入图片描述

在这里插入图片描述

好好理解上面的这个过程,
不仅是 Windows,对学习 ELF 等 Linux 文件执行也是有帮助的。

Windows 下审核PE文件内容的方法
常用工具:PEView

PEView 本身是一个 exe 文件。

在这里插入图片描述在这里插入图片描述


五 Linux 系列(Ubuntu、RedHat、Kali Linux…)

elf 格式,可执行可链接格式(Executable and Linkable Format),
是 Linux 下的主要可执行文件格式。

elf格式文件包括
二进制文件、可执行文件、目标代码、共享库和核心转储格式文件;

因为Unix-Like不关心文件后缀名,所以实际上你可以给它们分别取任何你想要取的后缀名字,比如将Linux下可执行文件后缀写成execute,目标代码文件后缀写成object等… 这不重要。

elf 文件四个部分。其中:

包括三个索引表

  • ELF 文件头:ELF file header,位于文件开头,描述了文件组织情况。
  • 程序头表:program header table,告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表
  • 节头表:section header table:包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。

以及作为主体部分的节们
节:section、section、section、section…

使用下面的命令生成可重定位目标文件。“可重定位” 的意思是,“文件里面的代码段和数据的地址还没有最终确定”。默认生成的文件名为 cc.o。cc.c 是你事先写好的 C 语言源代码。

gcc -c cc.c

在这里插入图片描述使用下面的命令生成a.out,它本质上是一个可执行共享对象文件。在有的机子上可能有点不一样,显示的不是 shared object,或者显示为 executable,你要知道它们本质上没有什么不同:

在这里插入图片描述

对于 elf 格式文件,其中共享对象文件(shared object)和可执行类型文件(executable)没什么不同,唯一的区别就是,在编译时,前者可以被后者链接,而后者不能被前者链接(“共享” 的意思)。

在这里插入图片描述
上面生成的两个文件cc.o和a.out有什么不同呢?首先.o文件通常是没有program header table 的(或者说不是必须的),而由编译器将一个或多个.o文件链接编译后生成 program header table,前面说过 program header table 的作用是,文件载入内存执行时,是以 segment 组织的,每个 segment 对应 ELF 文件中 program header table 中的一个条目,用来建立可执行文件的进程映像。

在这里插入图片描述 

这里引入一个新的概念:segment。赛格门特毕竟也不是什么魔鬼?,
本质上 segment 和 section 不过是对同一部分 ELF 文件的不同描述罢了。

在这里插入图片描述
对于目标代码文件(.o),用 section 进行概括它的各个 “部分”,比如我们熟悉的 “data section”、“text section”、“bss section” 等(最基本的三个 section);至于可执行文件(用什么作后缀好呢,就是前面的那个 a.out),编译器将它的各个 section 又进一步整合,这些更大区间的 “部分” 叫 segment。目标代码文件视图(左边),program header table 是可选的,而可执行文件(右边),section header table 是可选的。

sections存储了程序链接时需要的信息,而 segment 存储了程序运行时需要的信息。

1 个 segment 可以包含 0 个或多个section。如下图所示,
PHDR 段没有 section,而 LOAD 段含有多个 section。

在这里插入图片描述在这里插入图片描述
>>> section 和 segment 的中文翻译,以后不作说明,将 section 称为节,将 segment 称为段。比如下面的这个小例子,分别表示了文本段和数据段。<<<

在这里插入图片描述 

在这里插入图片描述 
标题是 “可执行文件的内部结构”。生成的 “目标文件” 并不是可执行的,链接后生成的才是可执行文件。虽然它们都属于 ELF 文件,但是却有区别。这一点请注意。

既然说 “可执行的 ELF 文件”,那就要带上 segment 玩(编译器已将 section 整合成 segment),如果对 segment 和section 的关系不理解或者不清楚的,再回头看几遍就懂了。?️

Linux 下审核ELF文件内容的方法
常用工具:readelf

readelf 作为二进制工具集预装在绝大部分 Linux 发行版中,
看名字就知道该命令专门审核 elf 文件,它本身是一个 elf 类型的可执行文件。

z@ubuntu:~$ readelf 
Usage: readelf <option(s)> elf-file(s)
 Display information about the contents of ELF format files
 Options are:
  -a --all               Equivalent to: -h -l -S -s -r -d -V -A -I
  -h --file-header       Display the ELF file header
  -l --program-headers   Display the program headers
     --segments          An alias for --program-headers
  -S --section-headers   Display the sections' header
     --sections          An alias for --section-headers
  -g --section-groups    Display the section groups
  -t --section-details   Display the section details
  -e --headers           Equivalent to: -h -l -S
  -s --syms              Display the symbol table
     --symbols           An alias for --syms
  --dyn-syms             Display the dynamic symbol table
  -n --notes             Display the core notes (if present)
  -r --relocs            Display the relocations (if present)
  -u --unwind            Display the unwind info (if present)
  -d --dynamic           Display the dynamic section (if present)
  -V --version-info      Display the version sections (if present)
  -A --arch-specific     Display architecture specific information (if any)
  -c --archive-index     Display the symbol/file index in an archive
  -D --use-dynamic       Use the dynamic section info when displaying symbols
  -x --hex-dump=<number|name>
                         Dump the contents of section <number|name> as bytes
  -p --string-dump=<number|name>
                         Dump the contents of section <number|name> as strings
  -R --relocated-dump=<number|name>
                         Dump the contents of section <number|name> as relocated bytes
  -z --decompress        Decompress section before dumping it
  -w[lLiaprmfFsoRtUuTgAckK] or
  --debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
               =frames-interp,=str,=loc,=Ranges,=pubtypes,
               =gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
               =addr,=cu_index,=links,=follow-links]
                         Display the contents of DWARF debug sections
  --dwarf-depth=N        Do not display DIEs at depth N or greater
  --dwarf-start=N        Display DIEs starting with N, at the same depth
                         or deeper
  -I --histogram         Display histogram of bucket list lengths
  -W --wide              Allow output width to exceed 80 characters
  @<file>                Read options from <file>
  -H --help              Display this information
  -v --version           Display the version number of readelf

除了readelf 命令,还附赠你一个方法:使用objdump 命令。。区别就是,objdump 命令相较之下更具通用型。

经过测试,objdump 反正读取 exe 和 elf 文件都没有问题。该方法更具有一般性。

z@ubuntu:~$ objdump -a evil.macho 
objdump: evil.macho: File format not recognized
z@ubuntu:~$ objdu
objdu: command not found
z@ubuntu:~$ objdump 
Usage: objdump <option(s)> <file(s)>
 Display information from object <file(s)>.
 At least one of the following switches must be given:
  -a, --archive-headers    Display archive header information
  -f, --file-headers       Display the contents of the overall file header
  -p, --private-headers    Display object format specific file header contents
  -P, --private=OPT,OPT... Display object format specific contents
  -h, --[section-]headers  Display the contents of the section headers
  -x, --all-headers        Display the contents of all headers
  -d, --disassemble        Display assembler contents of executable sections
  -D, --disassemble-all    Display assembler contents of all sections
  -S, --source             Intermix source code with disassembly
  -s, --full-contents      Display the full contents of all sections requested
  -g, --debugging          Display debug information in object file
  -e, --debugging-tags     Display debug information using ctags style
  -G, --stabs              Display (in raw form) any STABS info in the file
  -W[lLiaprmfFsoRtUuTgAckK] or
  --dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
          =frames-interp,=str,=loc,=Ranges,=pubtypes,
          =gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
          =addr,=cu_index,=links,=follow-links]
                           Display DWARF info in the file
  -t, --syms               Display the contents of the symbol table(s)
  -T, --dynamic-syms       Display the contents of the dynamic symbol table
  -r, --reloc              Display the relocation entries in the file
  -R, --dynamic-reloc      Display the dynamic relocation entries in the file
  @<file>                  Read options from <file>
  -v, --version            Display this program's version number
  -i, --info               List object formats and architectures supported
  -H, --help               Display this information

/usr/include/elf.h,部分截图如下两张图所示:

在这里插入图片描述

在这里插入图片描述 

除了数据的存储大小,不管 32 位还是 64 位,ELF 文件的基本格式是一样的。
对应下面这张图,第一个 e_ident[EI_NIDENT] 变量就是 ELF 的文件幻数。

ELF file header,不管是目标代码文件,还是生成可执行文件,都有。
但是内容有所不同。两张对比图仅供参考。

在这里插入图片描述在这里插入图片描述
根据下面划黑线部分, a.out 的程序头大小为 56 字节。

在这里插入图片描述

第一列是地址,别搞错了… 然后注意下你的通常都是小端存储,反汇编十六进制从右往左读…

在这里插入图片描述
很好理解,比如这里文件幻数 7f45 4c46 0201...,采取两个两个从右往左读的方法。源文件中写的代码注释,不影响输出结果。

查看发现,.text 节的编号是 14(左边框框里的数字)

readelf -S a.out 

在这里插入图片描述

使用下面的命令,打印 .text 节的内容

readelf -x 14 a.out

在这里插入图片描述

 Linux 的 ELF,和 Windows 的 PE,它们的一个主要区别就是,PE 的结构是稳定的,而 ELF,你看 .o 文件(相当于 Windows的 obj 文件)和 .elf(后缀名随便起,我是说最后链接生成的真正可执行文件),虽然都是 ELF 文件然而在 SHT(节头表) 和 PHT(程序表头)上却有所不同… 至于 Windows,根本就没把obj文件(COFF格式,COFF格式,COFF格式!)当成是 PE(确实也不是)。这便是 Windows 和 Unix-Like 的差别了(Unix-Like,将 Mac 也考虑进去)。

六 Mac 系列(MacOS & iOS)

苹果基于 FreeBSD,属于 Unix-Like 操作系统。

苹果操作系统,虽然 GUI 闭源,然而内核是开源的。?

首先生成一个 Mach-O 格式文件… 如图所示。

在这里插入图片描述

不妨自己动手,看看 Mac 上的可执行文件都是什么格式

在这里插入图片描述 mach-O 格式,全称为 “Mach Object”,意思是麦金塔个人CP 和 爱疯上的专用文件对象格式。

和前面一样,mach-O 也没有什么特别显眼的 “格式,不像 XML、YAML、JSON 等具有肉眼可以辨别的特征。就是二进制,不过是以数据块为单位组织有序的二进制集合。

下面是 mach-O 格式的简化图示。典型的mach-O文件由三个部分构成。

  • Header(Mach-O 文件头):包含文件二进制的基本信息,如比特字节的排列顺序、CPU 的种类、以及后面要讲的加载命令。
  • Load Command(加载命令):描述了段的位置,符号表,动态符号表等。每个加载命令包括一个元信息,如命令类型,名称,二进制位置等。
  • Data(存储数据):文件的主体部分。包含数据段和代码段它包含代码和数据,例如符号表,动态符号表等。

在这里插入图片描述 在这里插入图片描述

 MacOS下审核mcaho-O文件内容的方法
常用工具:MachOView、otool 命令

前面提到的 objdump,对 mach-O 格式的文件没有效果。…… 事实上苹果原生也是不带这个命令的,可以通过 brew install 进行安装。

在这里插入图片描述

MachOView 项目开源地址:https://github.com/gdbinit/MachOView
MachOView 软件下载地址:https://sourceforge.net/projects/machoview/

在这里插入图片描述

 苹果提供了一个十分好用的命令,叫 otool(o 代表 object,object file displaying tool)。记住,它很好用。甚至在某些时候你大可不必大费周章的打开上面提到的 GUI 分析程序进行分析。


更多详情请直接查看帮助文档

otool -help

Usage: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjC] [-mcpu=arg] [--version] <object file> ...
    -f print the fat headers
    -a print the archive header
    -h print the mach header
    -l print the load commands
    -L print shared libraries used
    -D print shared library id name
    -t print the text section (disassemble with -v)
    -p <routine name>  start dissassemble from routine name
    -s <segname> <sectname> print contents of section
    -d print the data section
    -o print the Objective-C segment
    -r print the relocation entries
    -S print the table of contents of a library
    -T print the table of contents of a dynamic shared library
    -M print the module table of a dynamic shared library
    -R print the reference table of a dynamic shared library
    -I print the indirect symbol table
    -H print the two-level hints table
    -G print the data in code table
    -v print verbosely (symbolically) when possible
    -V print disassembled operands symbolically
    -c print argument strings of a core file
    -X print no leading addresses or headers
    -m don't use archive(member) syntax
  -B force Thumb disassembly (ARM objects only)
  -q use llvm's disassembler (the default)
    -Q use otool(1)'s disassembler
  -mcpu=arg use `arg' as the cpu for disassembly
    -j print opcode bytes
    -C print linker optimization hints
    --version print the version of /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool

事实上,有关 mach-O 结构体的信息,在本地文件 /usr/include/mach-o/loader.h 中找到,如果没有请尝试重新安装 XCode。XCode 由苹果公司开发,这样做也就不奇怪了。

总结:通过本文,我们基本掌握了 Windows、Linux 以及 Mac,不同操作系统下可执行文件的内容细节。本文作为一个大概,望读者能从宏观上把握不同类型可执行文件的区别和联系。形成你自己的 “文件” 和 “数据” 的概念。文件的格式太多了,可不是只有 “可执行文件”,还有 txt 格式的文本文件、mp3格式的音频文件、mp4 格式的视频文件,Linux 下的软连接和硬链接(这个比较特殊)以及 zip 后缀名的特定压缩类型文件… 文件类型,你可以把它理解成现实世界的一个个人儿,不同的人,不同分工,执行特定的工作。本文之所以选择可执行文件,是因为它 “简单”,我们写 GUI 程序,简化成控制台程序,形形色色的游戏文件,“点一下,玩一年”,你可知道它背后是什么编程语言写成,经历了怎样的编译过程… 最后到终端用户手上,已经是封装了好多层后的产物。

你不但要对宏观上的 “文件” 了解,还要理解微观上的 “二进制”。二进制的尺度小,不像 exe、elf 等格式文件已经完成了分类,要研究一个文件,你不妨从它的二进制级别进行研究。之前说过,Linux 下比较好的十六进制编辑器 Bless,Windows 下比较好的十六进制编辑器 WinHex… 至于本文提到的 readelf、PEView,都是针对特定文件格式进行分析,有所专门性,不具有一般性。

工具是为了简化操作,怎么方便怎么搞。我在提供给你一个命令,hexdump 命令,用来从二进制的角度读取文件字节并以终端形式输出。如果你觉得这样更方便,就不必打开 Bless 来搞了,那样太麻烦。不要刻意去记忆。

在这里插入图片描述

 怎么简单怎么搞,怎么爽就怎么搞。

对于硬盘机或任何电脑存储来说,有效的信息只有 0 和 1 两种。所以电脑必须设计有相应的方式进行信息-位元的转换。对于不同的信息有不同的存储格式。理解二进制,理解二进制背后的运行机理,这就是你接下来要做的事情。


七 更多分享

欢迎关注我的[github仓库01workstation](https://github.com/recan-li/01workstation/tree/master/workspace/gcc/gc_section),日常分享一些开发笔记和项目实战,欢迎指正问题。

同时也非常欢迎关注我的CSDN主页和专栏:

[【CSDN主页:架构师李肯】](http://yyds.recan-li.cn)

[【RT-Thread主页:架构师李肯】](https://club.rt-thread.org/u/18001)

[【C/C++语言编程专栏】](https://blog.csdn.net/szullc/category_8450784.html)

[【GCC专栏】](https://blog.csdn.net/szullc/category_8626555.html)

[【信息安全专栏】](https://blog.csdn.net/szullc/category_8452787.html)

[【RT-Thread开发笔记】](https://blog.csdn.net/szullc/category_11461616.html)

[【freeRTOS开发笔记】](https://blog.csdn.net/szullc/category_11467856.html)

有问题的话,可以跟我讨论,知无不答,谢谢大家。


————————————————
版权声明:本文为CSDN博主「凤兮凤兮非无凰」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/abc_12366/article/details/88205670

如有侵权,请联系我删除,谢谢。

————————————————


  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值