第2部分 静态链接---(3)目标文件里有什么

###################
# 3、目标文件里有什么
###################
# 目标文件的格式
可执行文件:Linux下的ELF(Executable Linkable Format)可执行文件
动态链接库:DLL(Dynamic Linking Library) , linux的.so
静态链接库:.a
ELF格式的文件可以归为以下4类:
(1)可重定位文件(Relocatable File)
这类文件包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态链接库也可以归为这一类。linux中的.o
[xiaoloaw@AONT03 partTwo]$ file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
(2)可执行文件
这类文件包含了可以直接执行的程序,它的代表就是ELF可执行文件,一般没有扩展名
[xiaoloaw@AONT03 partTwo]$ file /bin/bash
/bin/bash: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
(3)共享目标文件
这种文件包含了代码和数据,可以在两种情况下使用。linux中的.so
一种是链接器可以使用这种文件跟其他的可重定位文件和共享目标文件链接,产生新的目标文件;
第二种是动态链接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像的一部分来运行;
[xiaoloaw@AONT03 partTwo]$ file /lib/libgcc_s-4.4.4-20100726.so.1
/lib/libgcc_s-4.4.4-20100726.so.1: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, stripped
(4)核心转储文件
当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其他信息转储到核心转储文件。linux下的core dump
[xiaoloaw@AONT03 HD_R5601_FDT1356]$ file omciMgr-1970-01-01-00-01-13_pid1461_sig11.core
omciMgr-1970-01-01-00-01-13_pid1461_sig11.core: ELF 32-bit MSB core file, MIPS, MIPS-I version 1 (SYSV), SVR4-style

# 目标文件是什么样的
    目标文件中的内容至少有编译后的机器指令代码、数据,还包含了链接时所需要的一些信息(符号表、调试信息、字符串等)。
一般目标文件将这些信息按不同的属性,以节(Section)的形式存储,也叫段(Segment)。
(1).text段:一般C语言的编译后执行语句都编译成机器代码
(2).data段:已初始化的全局变量和已初始化的局部静态变量
(3).bss段:未初始化的全局变量和局部静态变量预留位置而已,默认值为0,并没有内容,所以它在文件中不占据空间。
(4).rodata段:只读数据段
(5).comment段:注释信息段
(6).note.GNU-stack段:堆栈提示段
// SimpleSection.c
int printf( const char *format, ... );

int global_init_var = 84;            // 数据段 .data section
int global_uninit_var;               // .bss section

void func1(int i)
{
    printf("%d\n", i);
}

int main(void)                                  // .text section
{
    static int static_var = 85;     // 数据段 .data section
    static int static_var2;         // .bss section
    int a = 1;
    int b;

    func1( static_var + static_var2 + a + b);    // .text section
    return a;
}
$ gcc -c SimpleSection.c          // -c 只编译不链接
// printf语句中用到了字符串常量"%d\n",它是一个只读数据,所有它被放到了".rodata段",这个段的4个字节刚好是这个字符串常量的ASCII字节序,最后以\0结尾。
$ objdump -h SimpleSection.o      // 查看目标文件的结构和内容
SimpleSection.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000050  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  00000000  00000000  00000084  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  00000000  00000000  0000008c  2**2
                  ALLOC
  3 .rodata       00000004  00000000  00000000  0000008c  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      0000002e  00000000  00000000  00000090  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  00000000  00000000  000000be  2**0
                  CONTENTS, READONLY
// global_init_var 和 static_var 保存在数据段,共8个字节。
$ size SimpleSection.o          // size命令用来查看ELF文件的代码段、数据段和BSS段的长度
   text    data     bss     dec     hex filename
     84       8       4      96      60 SimpleSection.o

# ELF文件结构描述
ELF Header
.text
.data
.bss
...
other sections
Section header table
String Tables
Symbol Tables
...
        ELF 结构 

(1)文件头
$ readelf -h SimpleSection.o      // 查看ELF文件头
ELF Header:
// e_ident
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
// e_type, ELF文件类型
  Type:                              REL (Relocatable file)
// e_machine,ELF文件的CPU平台属性
  Machine:                           Intel 80386
// e_version,ELF版本号,一般为常数1
  Version:                           0x1
// e_entry,入口虚拟地址
  Entry point address:               0x0
// e_phoff
  Start of program headers:          0 (bytes into file)
// e_shoff,段表在文件中的偏移,也就是段表从文件的第273个字节开始
  Start of section headers:          272 (bytes into file)
// e_flags,ELF标志位
  Flags:                             0x0
// e_ehsize,ELF文件头本身的大小
  Size of this header:               52 (bytes)
// e_phentsize
  Size of program headers:           0 (bytes)
// e_phnum
  Number of program headers:         0
// e_shentsize,段表描述符的大小
  Size of section headers:           40 (bytes)
// e_shnum,段表描述符数量
  Number of section headers:         11
// e_shstrndx,段表字符串表所在的段 在段表中的下标
  Section header string table index: 8
ELF文件头结构及相关常数被定义在/usr/include/elf.h
typedef struct
{
  unsigned char    e_ident[EI_NIDENT];    /* Magic number and other info */
  Elf32_Half    e_type;            /* Object file type */
  Elf32_Half    e_machine;        /* Architecture */
  Elf32_Word    e_version;        /* Object file version */
  Elf32_Addr    e_entry;        /* Entry point virtual address */
  Elf32_Off    e_phoff;        /* Program header table file offset */
  Elf32_Off    e_shoff;        /* Section header table file offset */
  Elf32_Word    e_flags;        /* Processor-specific flags */
  Elf32_Half    e_ehsize;        /* ELF header size in bytes */
  Elf32_Half    e_phentsize;        /* Program header table entry size */
  Elf32_Half    e_phnum;        /* Program header table entry count */
  Elf32_Half    e_shentsize;        /* Section header table entry size */
  Elf32_Half    e_shnum;        /* Section header table entry count */
  Elf32_Half    e_shstrndx;        /* Section header string table index */
} Elf32_Ehdr;
(2)段表(Section Header Table )
ELF文件中有各种各样的段,段表就是保存这些段的基本属性的结构。
$ readelf -S SimpleSection.o        // 查看完整ELF文件段表的内容
There are 11 section headers, starting at offset 0x110:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 000050 00  AX  0   0  4
  [ 2] .rel.text         REL             00000000 000420 000028 08      9   1  4
  [ 3] .data             PROGBITS        00000000 000084 000008 00  WA  0   0  4
  [ 4] .bss              NOBITS          00000000 00008c 000004 00  WA  0   0  4
  [ 5] .rodata           PROGBITS        00000000 00008c 000004 00   A  0   0  1
  [ 6] .comment          PROGBITS        00000000 000090 00002e 01  MS  0   0  1
  [ 7] .note.GNU-stack   PROGBITS        00000000 0000be 000000 00      0   0  1
  [ 8] .shstrtab         STRTAB          00000000 0000be 000051 00      0   0  1
  [ 9] .symtab           SYMTAB          00000000 0002c8 0000f0 10     10  10  4
  [10] .strtab           STRTAB          00000000 0003b8 000066 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
段表的结构比较简单,它是一个以 "Elf32_Shdr" 结构体为元素的数组。数组元素的个数等于段的个数,每个Elf32_Shdr结构体对应一个段。
"Elf32_Shdr"又被称为段描述符
typedef struct
{
  Elf32_Word    sh_name;        /* Section name (string tbl index) */ 段名是一个字符串,它位于一个叫做".shstrtab"的字符串表。sh_name是段名字符串在".shstrtab"中的偏移。
  Elf32_Word    sh_type;        /* Section type */
  Elf32_Word    sh_flags;        /* Section flags */
  Elf32_Addr    sh_addr;        /* Section virtual addr at execution */ 段虚拟地址,如果该段可以被加载,则sh_addr为该段被加载后在进程地址空间中的虚拟地址;否则sh_addr为0。
  Elf32_Off    sh_offset;        /* Section file offset */ 段偏移,如果该段存在于文件中,则表示该段在文件中的偏移;否则无意义,比如sh_offset对于BSS段来说就没有意义。
  Elf32_Word    sh_size;        /* Section size in bytes */
  Elf32_Word    sh_link;        /* Link to another section */ 段链接信息
  Elf32_Word    sh_info;        /* Additional section information */ 段链接信息
  Elf32_Word    sh_addralign;        /* Section alignment */ 段地址对齐
  Elf32_Word    sh_entsize;        /* Entry size if section holds table */ 项的长度,有些段包含了一些固定大小的项,比如符号表,它包含的每个符号所占的大小都一样的,对于这种段,sh_entsize表示每个项的大小。如果为0,则表示该段不包含固定大小的项。
} Elf32_Shdr;
(3)重定位表(Relocation Table)
链接器在处理目标文件时,需要对目标文件中某些部位进行重定向,即代码段和数据段中那些绝对地址的引用的位置。
这些重定位的信息都记录在ELF文件的重定位表里面,对于每个需要重定位的代码段或数据段,都会有一个相应的重定位表。
SimpleSection.o中的.rel.text就是针对.text段的重定位表,因为.text段中至少有一个绝对地址的引用,那就是 printf函数的调用
.data段则没有对绝对地址的引用,它只包含了几个常量,所以没有重定位表。

(4)字符串表
.strtab:字符串表(String Table),用来保存普通的字符串,比如符号的名字
.shstrtab:段表字符串表(Section Header String Table),用来保存段表中用到的字符串,最常见的就是段名(sh_name)。

# 链接的接口---符号
    在链接中,目标文件之间相互拼接实际上是 目标文件之间对地址的引用,即对函数和变量的地址的引用。
每个函数或变量都有自己独特的名字,才能避免链接过程中不同变量和函数之间的混淆。
在链接中,将函数和变量统称为符号,函数名或变量名就是符号名。
每一个目标文件都会有一个相应的符号表,这个表里面记录了目标文件中所用到的所有符号。
可以使用很多工具来查看ELF文件的符号表,比如readelf、objdump、nm等
$ nm SimpleSection.o
00000000 T func1
00000000 D global_init_var
00000004 C global_uninit_var
0000001b T main
         U printf
00000004 d static_var.1243
00000000 b static_var2.1244
typedef struct
{
  Elf32_Word    st_name;        /* Symbol name (string tbl index) */
  Elf32_Addr    st_value;        /* Symbol value */
  Elf32_Word    st_size;        /* Symbol size */
  unsigned char    st_info;        /* Symbol type and binding */
  unsigned char    st_other;        /* Symbol visibility */
  Elf32_Section    st_shndx;        /* Section index */
} Elf32_Sym;
$ readelf -s SimpleSection.o

Symbol table '.symtab' contains 15 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS SimpleSection.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1
     3: 00000000     0 SECTION LOCAL  DEFAULT    3
     4: 00000000     0 SECTION LOCAL  DEFAULT    4
     5: 00000000     0 SECTION LOCAL  DEFAULT    5
     6: 00000004     4 OBJECT  LOCAL  DEFAULT    3 static_var.1243
     7: 00000000     4 OBJECT  LOCAL  DEFAULT    4 static_var2.1244
     8: 00000000     0 SECTION LOCAL  DEFAULT    7
     9: 00000000     0 SECTION LOCAL  DEFAULT    6
    10: 00000000     4 OBJECT  GLOBAL DEFAULT    3 global_init_var
    11: 00000004     4 OBJECT  GLOBAL DEFAULT  COM global_uninit_var
    12: 00000000    27 FUNC    GLOBAL DEFAULT    1 func1
    13: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
    14: 0000001b    53 FUNC    GLOBAL DEFAULT    1 main
符号修饰与函数签名
    GCC的基本C++名称修饰方法如下:所有的符号都以"_Z"开头,对于嵌套的名称(在名称空间或在类里面的),后面紧跟"N"。
然后是各个名称空间和类的名字,每个名字前是名字字符串长度,再以"E"结尾。
对于一个函数来说,它的参数列表紧跟在"E"后面,对于int类型来说,就是字母"i"。
N::C::func(int)函数签名经过修饰为_ZN1N1C4funcEi。
工具c++filt可以用来解析被修饰过的名称
$ c++filt _ZN1N1C4funcEi
N::C::func(int)
$ c++filt _ZN3foo3barE
foo::bar

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值