GNU链接脚本(11) - 构建可运行程序

原文:https://github.com/iDalink/ld-linker-script/tree/master/11%20%E6%9E%84%E5%BB%BA%E5%8F%AF%E8%BF%90%E8%A1%8C%E7%A8%8B%E5%BA%8F

1、目标

        你可能已经注意到,我们前面自定义linker script编译的可执行均无法正常运行。其原因是现代系统有无数的细节需要小心处理。一方面是,printf是系统libc提供的函数,为了保证编译通过我们只是简单得把printf函数屏蔽,这导致app运行过程中无法输出日志。而且我们为了保证编译简单性,直接将可执行入口指定为main函数,而不是常见的_start。另外一部分是,程序没有执行exit系统调用,这会导致程序无法正常退出,也就是你能在控制台看到程序执行异常。这个异常通常为segmentation fault.

        本小节我们即解决运行问题。我们的目就是自定义链接脚本,并构建可执行应用程序。接下来的数个小节,即12和13小节,我们会继续探索更多实现方案,这将会更有趣。

2、系统库依赖

        我们先打开标准C库和部分必要的链接。默认gcc命令编译产出可执行程序时,会默认指定一组二进制文件。使用gcc .... -v命令可以看到完整的编译指令。笔者的计算机上,该指令输出如下。

/usr/lib/gcc/x86_64-linux-gnu/6/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/6/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/6/lto-wrapper -plugin-opt=-fresolution=/tmp/ccFeIZgJ.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --sysroot=/ --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -o app /usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/6/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/6 -L/usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/6/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/6/../../.. /tmp/ccy30oUZ.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-linux-gnu/6/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/crtn.o

        我们先对该命令中的二进制文件做个整理。gcc指定部分参数时,会屏蔽其中部分文件。表格中的x标记表示该参数屏蔽该文件。如果你直接使用ld命令构建应用程序,那么任何默认的二进制文件均不会连接。

文件|包|功能|-nostartfiles|-nostdlib|-nodefaultlibs -|-|-|-|-|-|- /usr/lib/x86_64-linux-gnu/Scrt1.o|libc6-dev|_start符号|x|x| /usr/lib/x86_64-linux-gnu/crti.o|libc6-dev||x|x| /usr/lib/gcc/x86_64-linux-gnu/6/crtbeginS.o|libgcc-6-dev||x|x| loads| /usr/lib/gcc/x86_64-linux-gnu/6/libgcc.a|libgcc-6-dev|||x|x /usr/lib/gcc/x86_64-linux-gnu/6/libgcc_s.so|libgcc-6-dev|||x|x /usr/lib/x86_64-linux-gnu/libc.so|libc6-dev|||x|x /usr/lib/gcc/x86_64-linux-gnu/6/crtendS.o |libgcc-6-dev||x|x| /usr/lib/x86_64-linux-gnu/crtn.o|libc6-dev||x|x|

3、构建可运行程序

        前面已经分析,由于一个完整的程序依赖这些文件。那么我们只需要按照对应的方式在ld中指定这些文件即可。链接命令如下。

ld -nostdlib -T linker_script.lds -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o app -L/usr/lib/gcc/x86_64-linux-gnu/6/ -L /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu/Scrt1.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/6/crtbeginS.o main.o -lgcc -lgcc_s -lc /usr/lib/gcc/x86_64-linux-gnu/6/crtendS.o /usr/lib/x86_64-linux-gnu/crtn.o

        由于我们的可执行程序包含了动态库依赖,动态库的rela.plt是ld临时生成的段,需要明确保留该段。链接脚本如下。该部分测试代码位于01目录。

ENTRY(_start)
SECTIONS
{
    
  . = 0x2000;
  .rela.plt       :
    {
      *(.rela.plt)
    }
  . = 0x1000;
  .text . : { *(.text) }
  
  .data . : { *(.data)}

  .bss : { *(.bss) }
  

  _bss_end = (. + 0x20000 - 1) / 0x20000 * 0x20000;
  __init_array_start = .;
  __init_array_end = .;
  /DISCARD/ : {*(.note.*)}
}
上一篇:JS如何合并两个数组呢?


下一篇:JS实现前置和后置AOP