zoukankan      html  css  js  c++  java
  • binary hacks读数笔记(ld 链接讲解 二)

    这块将介绍一下ld链接命令的具体使用。ld的作用:ld是GNU binutils工具集中的一个,是众多Linkers(链接器)的一种。完成的功能自然也就是链接器的基本功能:把各种目标文件和库文件链接起来,并重定向它们的数据,完成符号解析。链接其实主要就是完成四个方面的工作:storage allocation、symbol management、libraries、relocation。
    ld可以识别一种Linker command Language表示的linker scriopt文件来显式的控制链接的过程。通过BFD(Binary Format Description)库,ld可以读取和操作COFF(common object file format)、ELF(executable and linking format)、a.out等各种格式的目标文件。
    查看ld的版本以及简单介绍:ld -version

    一、首先:看一个程序TinyHelloWorld.c

    /*
     *TinyHelloWorld.c
     */
    
    char* str = "Hello World!
    ";
    
    void print(){
        asm( "movl $13,%%edx 
    	"
            "movl  %0,%%ecx 
    	"
            "movl $0,%%ebx 
    	"
            "movl $4,%%eax 
    	"
            "int $0x80     
    	"
            ::"r"(str):"edx","ecx","ebx");
    }
    
    void exit() {
        asm( "movl $42,%ebx 
    	"
            "movl $1,%eax 
    	"
            "int $0x80    
    	");
    }
    
    void nomain() {
        print();
        exit();
    }

    1、进行 编译

    gcc -c -fno-builtin TinyHelloWorld.c
    其中,”-fno-builtin” 用来关闭GCC内置函数(built-in function)优化功能。
    但编译出错
    Error如下:
    TinyHelloWorld.c: Assembler messages:
    TinyHelloWorld.c:5: Error: unsupported instruction `mov'

    问题原因:
    在64位系统下去编译32位的目标文件,这样是非法的。


    解决方案:
    用”-m32”强制用32位ABI去编译,即可编译通过。

    gcc -c -fno-builtin -m32 TinyHelloWorld.c


    2、编译完进行链接, 链接报错

    ld -static -T TinyHelloWorld.lds -e nomain -o TinyHelloWorld TinyHelloWorld.o

    其中:

        “-T TinyHelloWorld.lds”是TinyHelloWorld的链接控制脚本;
        -e 是指定程序入口函数为nomain();
        -static 表示ld是静态链接的方式链接程序,而不是用默认的动态链接方式;
        -o 表示指定输出文件名为”TinyHelloWorld”

    Error如下:

    ld: i386 architecture of input file `TinyHelloWorld.o' is incompatible with i386:x86-64 output

    问题原因:
    输入目标文件`TinyHelloWorld.o’是32位系统的,然而我们的平台是64位的(默认链接脚本位于/usr/lib/ldscripts下,x86_64平台默认链接64位可执行文件用的是elf_x86_64.x,链接32位可执行文件用的是elf32_x86_64.x),如果直接ld肯定不匹配,所以需要指定链接脚本与输入目标文件对应的。

    解决方案:
    链接的时候加上“-m elf_i386”,因为输入目标文件为i386平台。

    ld -static -m elf_i386 -T TinyHelloWorld.lds -e nomain -o TinyHelloWorld TinyHelloWorld.o

    利用objdump查看TinyHelloWorld 目标文件:

    [root@tlinux /]# objdump -h TinyHelloWorld
    
    TinyHelloWorld:     file format elf32-i386
    
    Sections:
    Idx Name          Size      VMA       LMA       File off  Algn
      0 .text         0000003f  08048094  08048094  00000094  2**0
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
      1 .rodata       0000000e  080480d3  080480d3  000000d3  2**0
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
      2 .eh_frame     0000007c  080480e4  080480e4  000000e4  2**2
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
      3 .data         00000004  0804a000  0804a000  00001000  2**2
                      CONTENTS, ALLOC, LOAD, DATA
      4 .comment      0000002d  00000000  00000000  00001004  2**0
                      CONTENTS, READONLY

    二、上述链接过程是用的系统默认链接脚本,可以由ld -verbose查看默认链接脚本,接下来对脚本进行简单lds连接脚本进行介绍说明:

    开头:

    OUTPUT_FORMAT("elf32-i386", "elf32-i386",
    	      "elf32-i386")
    OUTPUT_ARCH(i386)

    OUTPUT_FORMAT 和 OUTPUT_ARCH 都是 ld 脚本的保留字命令。OUTPUT_FORMAT 说明输出二进制文件的格式。OUTPUT_ARCH 说明输出文件系统平台。

    ENTRY(_start)
                ENTRY 命令的作用是,将后面括号中的符号值设置成入口地址。入口地址(entry point)的定义是这样的──进程执行的第一条用户空间的指令在进程地址空间中的地址。ld 有多种方法设置进程入口地址,通常它按以下顺序:(编号越前, 优先级越高)
                       1, ld 命令行的-e选项
                       2, 连接脚本的 ENTRY(SYMBOL) 命令
                       3, 如果定义了 start 符号, 使用 start 符号值
                       4, 如果存在 .text section, 使用 .text section 的第一字节的位置值
                       5, 使用值 0
    SEARCH_DIR("/usr/i486-linux-gnu/lib32"):    设置链接时搜寻库文件目录.
              

     

    接下来是一大段的 SECTIONS,对应的右大括号直到脚本的末尾。
    SECTIONS 命令告诉 ld 如何把输入文件的 sections 映射到输出文件的各个 section:即是如何将输入 section 合为输出 section;如何把输出 section 放入程序地址空间 (VMA) 和进程地址空间 (LMA) 。

    该命令格式如下:
    SECTIONS
    {
    ….
    }


    PROVIDE (__executable_start = SEGMENT_START(“text-segment”, 0x08048000)):PROVIDE 定义的变量 如果源文件中已经定义值 那么用源文件中的,如果没有定义则用脚本中定义的。并设定该变量的值为0x08048000
               

    . = SEGMENT_START(“text-segment”, 0x08048000) + SIZEOF_HEADERS:这句把定位器符号置为 0x08048000 + SIZE_HEADERS(若不指定,则该符号的初始值为 0)。SIZE_HEADERS为输出文件的文件头
                .

    . 是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置(或某section内的偏移,如果它在SECTIONS命令内的某section描述内),该符号只能在SECTIONS命令内使用。

    .rel.dyn        :
        {
          *(.rel.init)
          *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
          *(.rel.fini)
          *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
          *(.rel.data.rel.ro* .rel.gnu.linkonce.d.rel.ro.*)
          *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
          *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
          *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
          *(.rel.ctors)
          *(.rel.dtors)
          *(.rel.got)
          *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
          *(.rel.ifunc)
        }
      .rel.plt        :
        {
          *(.rel.plt)
          PROVIDE_HIDDEN (__rel_iplt_start = .);
          *(.rel.iplt)
          PROVIDE_HIDDEN (__rel_iplt_end = .);
        }

    以上这些段主要用于重定位.

     .init           :
      {
        KEEP (*(.init))
      } =0x90909090
      .plt            : { *(.plt) *(.iplt) }
      .text           :
      {
        *(.text.unlikely .text.*_unlikely)
        *(.text .stub .text.* .gnu.linkonce.t.*)
        /* .gnu.warning sections are handled specially by elf32.em.  */
        *(.gnu.warning)
      } =0x90909090

    .init将在下文与.fini一起介绍.


    .text : 表示text段开始.
    *(.text) 将所有(*符号代表任意输入文件)输入文件的.text section合并成一个.text section, 该section的地址由定位器符号的值指定, 即0x08048000.

    *(.text.unlikely .text.*_unlikely)
    *(.text .stub .text.* .gnu.linkonce.t.*)
    /* .gnu.warning sections are handled specially by elf32.em.  */
    *(.gnu.warning)
      .fini           :
      {
        KEEP (*(.fini)) #
    

    KEEP()强制连接器保留一些特定的section

    ELF文件中定义了 .init 和 .fini 两个特殊的段,其中 .init 段中的代码会在main之前被执行,.fini 段中的代码会在main退出之后被执行.默认用NOP(0x90)字段进行填充.


    CONSTRUCTORS 是一个保留字命令。与 c++ 内的(全局对象的)构造函数和(全局对像的)析构函数相关。

     .ctors          :
      {
        /* gcc uses crtbegin.o to find the start of
           the constructors, so we make sure it is
           first.  Because this is a wildcard, it
           doesn't matter if the user does not
           actually link against crtbegin.o; the
           linker won't look for a file to match a
           wildcard.  The wildcard also means that it
           doesn't matter which directory crtbegin.o
           is in.  */
        KEEP (*crtbegin.o(.ctors))
        KEEP (*crtbegin?.o(.ctors))
        /* We don't want to include the .ctor section from
           the crtend.o file until after the sorted ctors.
           The .ctor section from the crtend file contains the
           end of ctors marker and it must be last */
        KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
        KEEP (*(SORT(.ctors.*)))
        KEEP (*(.ctors))
      }
      .dtors          :
      {
        KEEP (*crtbegin.o(.dtors))
        KEEP (*crtbegin?.o(.dtors))
        KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
        KEEP (*(SORT(.dtors.*)))
        KEEP (*(.dtors))
      }
    

    对于支持任意section名的目标文件格式,比如COFF、ELF格式,GNU C++将全局构造和全局析构信息分别放入 .ctors section 和 .dtors section 内

    当连接器生成的目标文件格式不支持任意section名字时,比如说ECOFF、XCOFF格式,连接器将通过名字来识别全局构造和全局析构,对于这些文件格式,连接器把与全局构造和全局析构的相关信息放入出现 CONSTRUCTORS 关键字的输出section内。

    /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) (.gnu.lto_) }
    DISCARD关键字用于将指定段舍弃,不出现在输出文件中.

    三、接下来,我们利用自定义链接脚本进行链接

    TinyHelloWorld.lds脚本

     ENTRY(nomain)
     
     SECTIONS
     {
    . = 0x08048000 + SIZEOF_HEADERS;
     tinytext : { *(.text) *(.data) *(.rodata) }
     /DISCARD/ : { *(.comment) }
    
    }

     利用上述链接脚本进行链接,将代码段、数据段合成tinytext段输出:

     ld -static -T TinyHelloWorld.lds -m elf_i386 -o TinyHelloWorld TinyHelloWorld.o

     利用objdump命令查看是否符合我们预期:

    [root@tlinux /]# objdump -h TinyHelloWorld
    
    TinyHelloWorld:     file format elf32-i386
    
    Sections:
    Idx Name          Size      VMA       LMA       File off  Algn
      0 .eh_frame     0000007c  08048074  08048074  00000074  2**2
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
      1 tinytext      00000052  080480f0  080480f0  000000f0  2**2
                      CONTENTS, ALLOC, LOAD, CODE
  • 相关阅读:
    检查 Linux 服务器性能
    openssl、x509、crt、cer、key、csr、ssl、tls 这些都是什么鬼?
    Jenkins+ Xcode+ 蒲公英 实现IOS自动化打包和分发
    如何在 Linux 上录制你的终端操作
    Swift 开源 Linux Ubuntu Install
    SCP 命令
    ubuntu openfire Server install
    git 和 svn 的比较
    implicitly declaring function 'malloc' with type void *(unsigned long ) 错误 解决
    python 使用缓存加快运算
  • 原文地址:https://www.cnblogs.com/wsw-seu/p/10627845.html
Copyright © 2011-2022 走看看