zoukankan      html  css  js  c++  java
  • Chap-4 Section 4.6 链接控制过程

    4.6 链接过程控制
    前面我们在使用ld链接器的时候,没有指定链接控制脚本,其实ld在用户没有指定链接脚本的
    时候会使用默认链接脚本,可以使用ld -verbose来查看ld默认的链接脚本。

    当然,为了更加精确的控制链接过程,可以自己写一个链接控制脚本,然后指定该脚本为链接
    控制脚本,可以使用-T参数:ld -T link.script

    4.6.2 最小的程序
    为了演示链接的控制过程,我们要写一个程序,该程序的功能是在终端输出"hello world"。但是
    我们这里的程序与C语言教科书中的例子有所不同。
    首先,经典的hello world使用了printf函数,该函数是C语言库的一部分,要使用该函数,必须将
    C语言库与程序的目标文件链接产生最终的可执行文件,而这里的程序没用C语言运行库,使得它成
    为一个独立于任何库的纯的程序。
    其次,经典的hello world使用了库,所以必须有main函数,我们知道一般程序的入口点是_start,
    由库负责初始化后调用main函数来执行程序的主题部分,我们这里使用另外一个nomain函数做为
    程序的入口。
    最后,经典的hello world会产生多个段,main程序的指令部分会产生.text段、字符串常量
    "hello world! "会被放在数据段或者只读数据段,还有C库所包含的各种段。为了演示ld链接脚本
    的控制过程,我们将这个程序的所有段都合并到一个叫"tinytext"的段。注意:这个段是我们任意
    命名的。
    该程序的源代码如下:
    //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();
    }

    这里的printf函数使用了Linux的Write系统调用,exit函数使用了Exit系统调用。系统调用使用
    0x80中断来实现,其中eax为调用号,ebx,ecx,edx等通过寄存器来传递参数。比如Write系统调用
    往一个文件句柄中写入数据,原型为:
    int write(int filedesc, char* buffer, int size);
    Write系统调用的调用号为4,因此eax寄存器的值为4
    filedesc表示被写入的文件句柄,使用ebx寄存器传递,我们这里是要往默认终端(stdout)输出,它
    的文件句柄为0,即ebx=0
    buffer表示要写入的缓冲区首址,使用ecx寄存器传递。
    size表示要写入的字节数,使用edx传递,字符串"hello world! "长度为13字节,因此edx=13

    同理,Exit系统调用中,ebx表示进程的退出码。比如我们平时的main程序中的return的数值会
    返回给系统库,由系统库将该数值传递给Exit系统调用,这样父进程就可以接收到子进程的退出码,
    Exit系统调用的调用号为1,即eax=1。

    我们先用普通命令行的方式来编译和链接TinyHelloWorld.c,如图4.2.18所示:


    ***图4.2.18***
    其中-fno-builtin GCC编译器提供了很多内置函数(Built-in Function),它会把一些常用的C库函数
    替换成编译器的内置函数,以到达优化的功能。比如GCC会将只有字符串参数的printf函数替换成
    puts,以节省格式解析时间,exit()函数也是GCC的内置参数之一,所以我们要使用-fno-builtin
    参数来关闭GCC内置函数的功能。
    -static 这个参数表示ld将以静态链接的方式来链接程序,而不是使用默认的动态链接方式。
    -e nomain 表示该程序的入口函数为nomain。
    我们用readelf -h test打印出了可执行文件的文件头,从图4.2.19可以看出,入口点地址为0x80480c4,
    用readeld -s test打印出了可执行文件的符号表,从中可以看出nomain符号的地址为0x80480c4。


    ***图4.2.19***

    用readelf -S test打印出了可执行文件的段表,如图4.2.20所示:


    ***图4.2.20***
    其中可读数据段.rodata大小为0x00000e,正好为"Hello World! "13个字节的长度。而数据段.data的
    大小为0x000004,正好是char* str指针的大小。

    4.6.3 使用链接控制脚本
    如果把链接过程比作一台计算机,那么ld链接器就是计算机的CPU,所有的目标文件、库文件就是输入,
    链接结果输出的可执行文件就是输出,而链接控制脚本就是这台计算机的程序,它控制CPU的执行,以
    “程序”要求的方式将输入加工成所需要的输出结果。链接控制脚本“程序”使用一种特殊的语言写成,即
    ld的链接脚本语言。
    简单来讲,控制链接过程无非是控制输入段如何变成输出段,比如哪些输入段要合并一个输出段,哪些
    输入段要丢弃;指定输出段的名字、装载地址、属性的等等。下面是一个链接控制脚本:
    ENTRY(nomain)

    SECTIONS
    {
    . = 0x08048000 + SIZEOF_HEADERS;
    tinytext : {*(.text) *(.data) *(.rodata)}
    /DISCARD/ : {*(.comment)}
    }
    第一行的ENTRY(nomain)指定了程序的入口为nomain()函数,后面的SECTIONS命令一般是链接脚本的主体,
    这个命令指定了各种输入段到输出段的变换,SECTIONS后面紧跟着一对大括号里面包含了SECTIONS变换规则,
    其中三条语句,每条语句一行。其中第一条是赋值语句,后两条是段转换规则。
    . = 0x08048000 + SIZEOF_HEADERS 是将当前虚拟地址设置成0x08048000 + SIZEOF_HEADERS, SIZEOF_HEADERS
    为输出文件的文件头大小,“.”表示当前虚拟地址,因为这条语句后面紧跟着输出段"tinytext",所以"tinytext"
    段的起始虚拟地址为0x08048000 + SIZEOF_HEADERS。它将当前虚拟地址设置成一个比较巧妙的值,以便于装载
    时页映射方便。
    tinytext : {*(.text) *(.data) *(.rodata)} 将输入文件中名字为.text .data .rodata的段以此合并到输出
    文件的tinytext。
    /DISCARD/:{*(.comment)} 将所有输出文件中名字为.comment的段丢弃,不保存到输出文件中。

    通过在链接的时候指定上述脚本为链接控制脚本,我们可以实现将TinyHelloWorld.c输出的目标文件中的.text
    .data .rodata合并为一个段:.tinytext,如图4.2.21所示:


    ***图4.2.21***

  • 相关阅读:
    Django学习 之 Django安装与一个简单的实例认识
    Django学习 之 HTTP与WEB为Django做准备
    Ansible ssh-key密钥认证配置
    Python 之并发编程之进程下(事件(Event())、队列(Queue)、生产者与消费者模型、JoinableQueue)
    Python 之并发编程之进程中(守护进程(daemon)、锁(Lock)、Semaphore(信号量))
    Python 之并发编程之进程上(基本概念、并行并发、cpu调度、阻塞 )
    Python 之网络编程之socket(3)hashlib模块
    Python 之网络编程之进程总体概要
    Python 之网络编程之socket(2)黏包现象和socketserver并发
    Python 之网络编程之socket(1)TCP 方式与UDP方式
  • 原文地址:https://www.cnblogs.com/miaoyong/p/3508089.html
Copyright © 2011-2022 走看看