FD镜像文件生成过程分析

二进制FD生成过程推演


1.obj二级制目标文件的生成

每个带有inf文件的模块,Build时都会先生成一个对应的GNUMakefile文件,里面定义了该模块的Build规则。我们以ArmPkg/Drivers/CpuDxe模块为例。通过其GNUMakefile分析二进制文件生成规则。

1.1.1.obj的生成规则

模块的每个源文件都会编译生成一个xx.obj目标文件。
在执行完build后,每个模块对应的输出目录下会有GnuMakefile文件,从中我们可以看到模块的xx.obj目标文件构建规则。
在这里插入图片描述

主要是基于gcc编译器。

1.1.2. 编译工具和Flag

我们看下GnuMakefile中的定义:

在这里插入图片描述

生成了各个未经链接的二级制目标文件。Flag中使用了“-fno-pic”,看来是要生成位置无关码。

1.1.3 编译规则的定义

定义在:BaseTools/Conf/build_rule.template#149文件。

125     <OutputFile>
126         $(OUTPUT_DIR)(+)${s_dir}(+)${s_base}.obj
127 
128     <Command.MSFT, Command.INTEL>
129         "$(CC)" /Fo${dst} $(DEPS_FLAGS) $(CC_FLAGS) $(INC) ${src}
130 
131     <Command.GCC, Command.RVCT>
132         # For RVCTCYGWIN CC_FLAGS must be first to work around pathing issues
133         "$(CC)" $(DEPS_FLAGS) $(CC_FLAGS) -c -o ${dst} $(INC) ${src}
134 
135     <Command.XCODE>
136         "$(CC)" $(DEPS_FLAGS) $(CC_FLAGS) -o ${dst} $(INC) ${src}
137 
138 [C-Code-File.BASE.AARCH64,C-Code-File.SEC.AARCH64,C-Code-File.PEI_CORE.AARCH64,C-Code-File.PEIM.AARCH64,C-Code-File.BASE.ARM,C-Code-File.SEC.ARM,C-Code-File.PEI_CORE.ARM,C-Code-File.PEIM.ARM]
139     <InputFile>
140         ?.c
141 
142     <ExtraDependency>
143         $(MAKE_FILE)
144 
145     <OutputFile>
146         $(OUTPUT_DIR)(+)${s_dir}(+)${s_base}.obj
147 
148     <Command.GCC, Command.RVCT>
149         "$(CC)" $(CC_FLAGS) $(CC_XIPFLAGS) -c -o ${dst} $(INC) ${src}

这里就定义了obj文件的生成规则,使用命令“ “$(CC)” $(CC_FLAGS) $(CC_XIPFLAGS) -c -o ${dst} $(INC) ${src}”

我们这里$(CC)使用的是gcc-4.8,看下flag的定义。

在文件:BaseTools/Conf/tools_def.template

1903 DEFINE GCC48_ALL_CC_FLAGS            = DEF(GCC_ALL_CC_FLAGS) -ffunction-sections -fdata-sections -DSTRING_ARRAY_NAME=$(BASE_NAME)Strings
...
1912 DEFINE GCC48_ASM_FLAGS               = DEF(GCC_ASM_FLAGS)
1913 DEFINE GCC48_ARM_ASM_FLAGS           = $(ARCHASM_FLAGS) $(PLATFORM_FLAGS) DEF(GCC_ASM_FLAGS) -mlittle-endian
1914 DEFINE GCC48_AARCH64_ASM_FLAGS       = $(ARCHASM_FLAGS) $(PLATFORM_FLAGS) DEF(GCC_ASM_FLAGS) -mlittle-endian
1915 DEFINE GCC48_ARM_CC_FLAGS            = $(ARCHCC_FLAGS) $(PLATFORM_FLAGS) DEF(GCC_ARM_CC_FLAGS) -fstack-protector -mword-relocations
1916 DEFINE GCC48_ARM_CC_XIPFLAGS         = DEF(GCC_ARM_CC_XIPFLAGS)
1917 DEFINE GCC48_AARCH64_CC_FLAGS        = $(ARCHCC_FLAGS) $(PLATFORM_FLAGS) -mcmodel=large DEF(GCC_AARCH64_CC_FLAGS)
1918 DEFINE GCC48_AARCH64_CC_XIPFLAGS     = DEF(GCC_AARCH64_CC_XIPFLAGS)

这些编译和链接选项也可以在INF文件或dsc文件的[BuildOptions]中设置。会对此默认模板的配置覆盖。

28 [BuildOptions]
29   RELEASE_*_*_CC_FLAGS  = -D MDEPKG_NDEBUG
30   *_*_*_CC_FLAGS  = -D DISABLE_NEW_DEPRECATED_INTERFACES
31 

2 dll静态库文件的生成

xx.dll 文件是所有obj文件链接成的静态库文件,也是elf可执行文件的一种。注意:xx.dll文件实际上是ELF格式的文件,它带有调试信息,它是静态库文件。

我们经常使用readelf和objdump 工具去解析它,获取一些信息。当程序运行panic后,dump出栈帧之后,我们也利用addr2line工具从dll文件获取执行失败的offset对应的函数名称。

还是看模块的GnuMakefile。

2.1 xx.dll 可执行文件的生成规则

在这里插入图片描述

从多个*.obj到xx.dll文件的过程,经过了三步:

  • 先将*.obj 处理成静态库文件*.lib
  • 采用$(DLINK)表示的链接器链接静态库文件*.lib,输出xx.dll。
  • 采用$(OBJCOPY) 工具对xx.dll文件进行处理,去除了一些非必须的段。输出到xx.dll。
2.2 DLINK链接处理

看下链接选项:

在这里插入图片描述

这里指定了链接脚本:

-W1,--script=BaseTools/Scripts/GccBase.lds

我们看下链接脚本的定义:

/*BaseTools/Scripts/GccBase.lds*/
13 SECTIONS {
14 
15   /*
16    * The PE/COFF binary consists of DOS and PE/COFF headers, and a sequence of
17    * section headers adding up to PECOFF_HEADER_SIZE bytes (which differs
18    * between 32-bit and 64-bit builds). The actual start of the .text section
19    * will be rounded up based on its actual alignment.
20    */
21   . = PECOFF_HEADER_SIZE;
22 
23   .text : ALIGN(CONSTANT(COMMONPAGESIZE)) {
24     *(.text .text.* .stub .gnu.linkonce.t.*)
25     *(.rodata .rodata.* .gnu.linkonce.r.*)
26     *(.got .got.*)
27 
28     /*
29      * The contents of AutoGen.c files are mostly constant from the POV of the
30      * program, but most of it ends up in .data or .bss by default since few of
31      * the variable definitions that get emitted are declared as CONST.
32      * Unfortunately, we cannot pull it into the .text section entirely, since
33      * patchable PCDs are also emitted here, but we can at least move all of the
34      * emitted GUIDs here.
35      */
36     *:AutoGen.obj(.data.g*Guid)
37   }
38 
39   /*
40    * The alignment of the .data section should be less than or equal to the
41    * alignment of the .text section. This ensures that the relative offset
42    * between these sections is the same in the ELF and the PE/COFF versions of
43    * this binary.
44    */
45   .data ALIGN(ALIGNOF(.text)) : ALIGN(CONSTANT(COMMONPAGESIZE)) {
46     *(.data .data.* .gnu.linkonce.d.*)
47     *(.bss .bss.*)
48   }
49 
50   .eh_frame ALIGN(CONSTANT(COMMONPAGESIZE)) : {
51     KEEP (*(.eh_frame))
52   }
53 
54   .rela (INFO) : {
55     *(.rela .rela.*)
56   }
57 
58   .hii : ALIGN(CONSTANT(COMMONPAGESIZE)) {
59     KEEP (*(.hii))
60   }
61 
62   /*
63    * Retain the GNU build id but in a non-allocatable section so GenFw
64    * does not copy it into the PE/COFF image.
65    */
66   .build-id (INFO) : { *(.note.gnu.build-id) }
67 
68   /DISCARD/ : {
69     *(.note.GNU-stack)
70     *(.gnu_debuglink)
71     *(.interp)
72     *(.dynsym)
73     *(.dynstr)
74     *(.dynamic)
75     *(.hash .gnu.hash)
76     *(.comment)
77   }
78 }
79 

看来,这里在链接时使用 /DISCARD/ strip掉了*(.comment)*(.dynstr)*(.dynsym)等不需要的section。但是.strtab、.symtab、*.debug等段还在dll文件中。这点我们可以通过readelf 分析xx.dll来证实。

注意:依然这里已经strip掉了不需要的调试信息,那么$(OBJCOPY) 就不需要再去strip了吧。

2.3 DLINK链接规则的定义

还是在BaseTools/Conf/build_rule.template文件。

静态库文件的xx.dll的生成。

289 [Static-Library-File]
290     <InputFile>
291         *.lib
292 
293     <ExtraDependency>
294         $(MAKE_FILE)
295 
296     <OutputFile>
297         $(DEBUG_DIR)(+)$(MODULE_NAME).dll
298 
299     <Command.MSFT, Command.INTEL>
300         "$(DLINK)" /OUT:${dst} $(DLINK_FLAGS) $(DLINK2_FLAGS) $(DLINK_SPATH) @$(STATIC_LIBRARY_FILES_LIST)
301         "$(DLINK)" /OUT:${dst} $(DLINK_FLAGS) $(DLINK_SPATH) @$(STATIC_LIBRARY_FILES_LIST)
302 
303     <Command.CLANGPDB>
304         "$(DLINK)" /OUT:${dst} $(DLINK_FLAGS) $(DLINK_SPATH) @$(STATIC_LIBRARY_FILES_LIST) $(DLINK2_FLAGS)
305 
306     <Command.GCC>
307         "$(DLINK)" -o ${dst} $(DLINK_FLAGS) -Wl,--start-group,@$(STATIC_LIBRARY_FILES_LIST),--end-group $(CC_FLAGS) $(DLINK2_FLAGS)
308         "$(OBJCOPY)" $(OBJCOPY_FLAGS) ${dst}
309 
310     <Command.RVCT>
311         "$(DLINK)" $(DLINK_FLAGS) -o ${dst} $(DLINK_SPATH) --via $(STATIC_LIBRARY_FILES_LIST) $(DLINK2_FLAGS)
312 
313     <Command.RVCTCYGWIN>
314         #$(STATIC_LIBRARY_FILES_LIST) has wrong paths for cygwin
315         "$(DLINK)" $(DLINK_FLAGS) -o ${dst} $(DLINK_SPATH) $(STATIC_LIBRARY_FILES) $(DLINK2_FLAGS)
316 
317     <Command.XCODE>
318         "$(DLINK)" $(DLINK_FLAGS) -o ${dst} $(DLINK_SPATH) -filelist $(STATIC_LIBRARY_FILES_LIST)  $(DLINK2_FLAGS)

PEI, DXE,和Application, 有不同的生成规则。

我们看下DLINK_FLAGS和DLINK2_FLAGS的定义,在BaseTools/Conf/tools_def.template

#BaseTools/Conf/tools_def.template
1919 DEFINE GCC48_ARM_DLINK_FLAGS         = DEF(GCC_ARM_DLINK_FLAGS) -Wl,--oformat=elf32-littlearm
1920 DEFINE GCC48_ARM_DLINK2_FLAGS        = DEF(GCC_DLINK2_FLAGS_COMMON) -Wl,--defsym=PECOFF_HEADER_SIZE=0x220
1921 DEFINE GCC48_AARCH64_DLINK_FLAGS     = DEF(GCC_AARCH64_DLINK_FLAGS)
1922 DEFINE GCC48_AARCH64_DLINK2_FLAGS    = DEF(GCC_DLINK2_FLAGS_COMMON) -Wl,--defsym=PECOFF_HEADER_SIZE=0x228
1923 DEFINE GCC48_ARM_ASLDLINK_FLAGS      = DEF(GCC_ARM_ASLDLINK_FLAGS) -Wl,--oformat=elf32-littlearm
1924 DEFINE GCC48_AARCH64_ASLDLINK_FLAGS  = DEF(GCC_AARCH64_ASLDLINK_FLAGS)
1925 DEFINE GCC48_ASLCC_FLAGS             = DEF(GCC_ASLCC_FLAGS)

同样,支持在模块的INF文件和pkg的dsc文文件通过 [BuildOptions] 块修改。

137 # Force PE/COFF sections to be aligned at 4KB boundaries to support NX protection
138 [BuildOptions.common.EDKII.DXE_DRIVER, BuildOptions.common.EDKII.DXE_CORE, BuildOptions.common.EDKII.UEFI_DRIVER, BuildOptions.common.EDKII.UEFI_APPLICATION]
139   #MSFT:*_*_*_DLINK_FLAGS = /ALIGN:4096
140   #GCC:*_*_*_DLINK_FLAGS = -z common-page-size=0x1000
141 
2.4 objcopy对xx.dll文件处理

前面我们介绍xx.dll文件生成规则时,xx.dll可执行文件带有很多调试信息,虽然DISCARD了一些,但是还有.debug、.symtab、.strtab等段,占据很大Size。objcopy工具对生成的xx.dll可执行文件再处理一次,再strip一些段。

我们从GNUMakefile看下$(OBJCOPY) 的定义。
在这里插入图片描述

看来只是用“echo”打印了下,并没有使用aarch64-linux-gnu-objcopy工具来进行处理。如果是在ELF目标文件中,会采用如下命令来去除掉调试信息,只保留必要的段。

aarch64-linux-gnu-objcopy --strip-unneeded -R .eh_frame xx.dll

实际上,通过readelf 解析xx.dll证明,它的符号信息(debug、.symtab、.strtab等)还在xx.dll中。没有使用objcopy工具strip调试信息。

我们看下BaseTools/Conf/tools_def.template中的定义:

1854 ####################################################################################
1855 # GCC Common
1856 ####################################################################################
1857 
1858 *_*_*_OBJCOPY_PATH              = echo
1859 *_*_*_OBJCOPY_FLAGS             = objcopy not needed for
1860 *_*_*_SYMRENAME_PATH            = echo
1861 *_*_*_SYMRENAME_FLAGS           = Symbol renaming not needed for
1862 DEBUG_*_*_OBJCOPY_ADDDEBUGFLAG     = --add-gnu-debuglink=$(DEBUG_DIR)/$(MODULE_NAME).debug
1863 RELEASE_*_*_OBJCOPY_ADDDEBUGFLAG   =
1864 NOOPT_*_*_OBJCOPY_ADDDEBUGFLAG     = --add-gnu-debuglink=$(DEBUG_DIR)/$(MODULE_NAME).debug

可见,这里并没有真正使用objcopy工具。

3 动态库 xx.efi 文件的生成

xx.efi被认为是一个动态库,查看它的头,是一个“MD-DOS”可执行文件。它的内容实际上是PE32, TE(精简版PE), PE32+中的一种。

3.1 efi文件的生成规则

继续看GnuMakefile中的生成规则:

在这里插入图片描述

无非就是经过如下几个处理步骤:

  • 使用objcopy --strip-unneeded 处理xx.dll文件,strip掉不必须的段。实际上并没有做。
  • 使用objcopy工具,添加回一些$(OBJCOPY_ADDDEBUGFLAG)指定的debug相关的段。实际上没有做。
  • 调用GenFw工具将xx.dll 静态库处理成动态库xx.efi。

这里重点是:GenFw转换工具对dll文件做了什么。

3.2 GenFw 转换工具

GnuMakefile中的定义:

GENFW_FLAGS =
GENFW = GenFw

MODULE_TYPE =DXE_DRIVER

命令展开就是:

GenFw -e DXE_DRIVER  -o xx.efi xx.dll

最后,生成了两个文件:xx.efi和xx.txt。 xx.txt中记录了文件的Size和时间戳。

我们可以通过readpe命令来分析该:xx.efi文件。

$readpe -S xx.efi

通过readpe -S 查看该文件中保留了哪些段。

在这里插入图片描述

看到只剩下:.text(代码段)、.data(数据段)、.reloc(重定位表) 这几个必需的段,实际还有.bss段只是没数据,其余段没有了。

看来GenFw工具将静态库文件xx.dll ELF格式的文件变成MD_DOS格式的了。而且xx.efi中的段和xx.dll的段很不一样,它只有.text(代码段)、.data(数据段)、.reloc(重定位表) 这几个段。

那么生成的xx.efi到底是PE32、TE(精简版PE)还是PE32+格式的呢?

我们通过readpe -H命令读取所有的header看下。

readpe -H xx.efi

在这里插入图片描述

原来是PE32+格式的。

3.3 GenFw工具的定义

GenFw工具编译后生成在:BaseTools/Source/C/GenFw/bin下。

我们分析时将它导出到环境变量,就可以在任意位置使用该命令了。

export PATH=/.../BaseTools/Source/C/GenFw/bin:$PATH

GenFw工具的源码在:BaseTools/Source/C/GenFw

TODO:

GenFw工具对xx.dll文件究竟做了哪些处理和转换,有兴趣可以从BaseTools/Source/C/GenFw/GenFw.c文件main函数开始阅读。

3.3 efi文件中符号表去哪了

我们之前分析时,生成的静态库xx.dll中包含了.symtab、.strtab、.debug等调试段,但是经过GenFw工具处理后变成PE32+文件,看不到这些段了。难道是GenFw工具将这些段剔除了吗?

经过运行实践,并不是!字符串表.strtab就没有剔除,要不然就无法打印字符串常量。

  1. 通过“readpe -A”命令查看xx.efi文件的内容。

    “Data directiories”,是个资源目录。表里面放着两类资源:

    • IMAGE_DIRECTORY_ENTRY_BASERELOC, 重定位表, 存放它的虚拟地址Offset和Size
    • IMAGE_DIRECTORY_ENTRY_DEDBUG, 调试信息。这应该也是一张表,这里放的是它的虚拟地址Offset和Size。

    由此可见,efi文件种是带着调试信息的。但是怎么只有28字节呢?

在这里插入图片描述

  1. 通过“pestr -s” 命令,查看程序中各个段种的字符串。实际上就是dump 文件的字符串表示.strtab

    该命令会显示xx.efi中所有字符串。

    pestr -s xx.efi
    

    会显示.text和.data段种都有很多字符串信息。使用“-o” 参数不仅显示字符串,还显示每个字符串在efi文件中的offset。

    pestr -o xx.efi
    

发现只有字符串表.strtab中部分内容,没有符号表.symtab。

我们还是再看下原来的xx.dll文件。使用“objdump -h xx.dll”查看ELF文件的头信息(GenFw就是根据这里的头信息遍历ELF 段的,这里没有就不会处理)。竟然没有符串表.strtab和符号表.symtab的头,而使用“readelf -t ”能看到.strtab和.symtab的信息。

怀疑Gcc工具不支持。(实际上验证,确实如此。)无论怎么弄,ELF的header中都不会有.strtab和.symtab段。

objdump -h xx.dll结果:

在这里插入图片描述

readelf -t xx.dll结果:

在这里插入图片描述

如果Gcc工具链没有将.strtab和.symtab放在文件header表的话,GenFw应该不会把它们放在相应的debug表(IMAGE_DIRECTORY_ENTRY_DEDBUG)。怀疑使用GenFw工具将ELF格式转换成PE32格式的过程中会导致ELF中一些段的内容丢失。

查看GenFw源码:

///BaseTools/Source/C/GenFw/Elf64Convert.c
ScanSections64 (
861    //
862    // Make room for .debug data in .data (or .text if .data is empty) instead of
863    // putting it in a section of its own. This is explicitly allowed by the
864    // PE/COFF spec, and prevents bloat in the binary when using large values for
865    // section alignment.
866    //
867    if (SectionCount > 0) {
868      mDebugOffset = DebugRvaAlign(mCoffOffset);
869    }
870    mCoffOffset = mDebugOffset + sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY) +
871                  sizeof(EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY) +
872                  strlen(mInImageName) + 1;
873  
874    mCoffOffset = CoffAlign(mCoffOffset);
875    if (SectionCount == 0) {
876      mDataOffset = mCoffOffset;
877    }
878  

通过注释可以看到:只是将.debug 段放在了.data或.text段,再PE32中不会开辟单独的段放这些特殊段的信息。

但是我们查看xx.dll文件,并没有名叫“.debug”的段。

3.4GenFw命令

GenFw工具的使用,自行查看使用方法。

GenFw --help
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老衲不依

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值