编译器、连接器学习笔记--(二)--编译

本文只记录学习过程中整理后的知识结构,不涉及具体细节,具体细节参考man手册和相关书籍,我再怎么说肯定都不如它们说的正确。

内容很浅,如果需要详细细节的可以点叉叉了,对此表示抱歉。


而且本来想写的详细一些,但是写着写着就跑题了,而且因为修改了多次,结果感觉中间错误很多


一、简单介绍

编译过程很复杂,而且不同的编程语言的编译过程也有所不同。

但是万变不离其踪,该有的过程还是得有,这个过程就是:

(Source Code)-->Scanner-->(Tokens)-->Parser-->(Syntax Tree)-->Semantic Analyzer-->(Commented Syntax Tree)-->Source Code Optimizer-->(Intermediate Representation)-->Code Generator-->(Target Code)-->Code Optimizer-->(Final Target Code)
这个过程很长,大致的过程就是:

扫描(词法分析)--》语法分析--》语义分析--》源代码优化--》源代码优化--》代码生成--》目标代码优化

1、1 词法分析

源代码被输入到扫描器(Scanner)将代码分割成一系列的记号(Token)。

例如:lex就是一个词法扫描器。

1、2 语法分析

在1、1中产生的标记,被送入语法分析器,产生语法树(Syntax Tree)

例如:yacc就是一个语法分析器


1、3 语义分析

这部分工作由语义分析器完成,但是它能分析的仅仅是静态语义。


1、4 源码优化

这部分由源码优化器完成,生成相应的中间代码。

常见中间代码有:三地址码,P -码等


1、1~1、4中的内容由被归纳成编译器前端,主要负责产生机器无关的中间代码。


1、5目标代码

这部分由代码生成器和目标代码优化器完成


 1、5中的内容又被划分到编译器后端。主要将中间码变成机器码。


上面的只是一些概念性的东西,其实也没什么大用。

编译过程其实很复杂,不仅和源代码使用的语言类型有关,还和具体的CPU体系相关,所以就以C语言为例,使用GCC编译,CPU是X86架构,下面分两个部分介绍:

(1)从C源码到目标文件

(2)分析目标文件


使用工具:

gcc、objdump,readelf。



2、从C源码到目标文件

2.1 预编译

功能:

  • 宏展开
  • 处理与编译指令,如“#if”,“#ifdef”等
  • 删除注释
  • 添加行号--便于编译时候产生调试用的行号信息和产生错误时候能够显示行号
  • 其他


使用指令1编译代码1,得到test.i,内容如下:

# 1 "test.c"
# 1 "<command-line>"
# 1 "test.c"

int main()
{

    int a = 100;
    returun;
}

与test.c比较,容易发现

  • 宏定义被展开
  • 注释被删除
  • 预编译指令也被处理
  • 注:这里没有使用头文件

注意:预编译不检查任何错误,测试代码中,return语句拼写错误,并且不带返回值,但是可以执行成功。


2.2 编译

功能

生成汇编源代码


使用指令2编译代码1,得到test.s,其内容如下:

        .file   "test.c"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $16, %esp
        movl    $100, -4(%ebp)
        movl    $0, %eax
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (GNU) 4.7.1 20120721 (prerelease)"
        .section        .note.GNU-stack,"",@progbits

这个是X86下的汇编原代码。以后如果开始看链接过程,就需要对这个语法有一定了解。

汇编我也学的不怎么样。感觉比较重要的就是:

(1)弄明白ebp和esp寄存器作用,因为后面涉及到栈的操作,尤其是函数调用时候的栈操作。

(2)弄明白eax,ebx……几个寄存器的作用,尤其是eax寄存器,系统调用以及函数返回值都需要借助它保存一些临时变量。

(3)当然还有几个在代码中没有体现的,段寄存器和控制寄存器,内存寻址都依靠这些寄存器。

其他的基本看名字都能猜明白是什么意思。应该压力不大了。


2.3 汇编

功能:

将汇编源代码,编译成可执行指令


使用指令3编译代码1,得到test.o文件

使用指令4,查看得到的test.o文件,显示如下:

00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  01 00 03 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  fc 00 00 00 00 00 00 00  34 00 00 00 00 00 28 00  |........4.....(.|
00000030  0b 00 08 00 55 89 e5 83  ec 10 c7 45 fc 64 00 00  |....U......E.d..|
00000040  00 b8 00 00 00 00 c9 c3  00 47 43 43 3a 20 28 47  |.........GCC: (G|
00000050  4e 55 29 20 34 2e 37 2e  31 20 32 30 31 32 30 37  |NU) 4.7.1 201207|
00000060  32 31 20 28 70 72 65 72  65 6c 65 61 73 65 29 00  |21 (prerelease).|
00000070  14 00 00 00 00 00 00 00  01 7a 52 00 01 7c 08 01  |.........zR..|..|
00000080  1b 0c 04 04 88 01 00 00  1c 00 00 00 1c 00 00 00  |................|
00000090  00 00 00 00 14 00 00 00  00 41 0e 08 85 02 42 0d  |.........A....B.|
000000a0  05 50 c5 0c 04 04 00 00  00 2e 73 79 6d 74 61 62  |.P........symtab|
000000b0  00 2e 73 74 72 74 61 62  00 2e 73 68 73 74 72 74  |..strtab..shstrt|
000000c0  61 62 00 2e 74 65 78 74  00 2e 64 61 74 61 00 2e  |ab..text..data..|
000000d0  62 73 73 00 2e 63 6f 6d  6d 65 6e 74 00 2e 6e 6f  |bss..comment..no|
000000e0  74 65 2e 47 4e 55 2d 73  74 61 63 6b 00 2e 72 65  |te.GNU-stack..re|
000000f0  6c 2e 65 68 5f 66 72 61  6d 65 00 00 00 00 00 00  |l.eh_frame......|
00000100  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
下文忽略
………………………………
内容很乱,后面介绍使用objdump和readelf来分析这个文件。这里仅仅是感受一下。


2.4 链接

这部分就要涉及链接器了,而且需要的基础知识还特别多。暂时先不介绍。


2.1~2.4中的内容其实没什么好分析的,首先源码文件都是语法的堆积,分析起来顶多分析下语法规则。但是生成的目标文件我们毕竟没办法用编辑器打开,就好比2.3中打开的就是一堆乱码。

所以好好学习如何分析.o文件才是重点,而且弄明白了.o文件,后面讲述链接的时候才能看明白说的是什么。


3、分析目标文件(test.o)

3.1ELF文件结构

先从一下3点说明:

(1)ELF文件有那些类型?

(2)每种类型的结构是什么样的?

(3)结构中每个模块大概有什么内容,我要怎么获取它的信息?


使用file命令查看文件类型:

使用指令5,查看之前编译得到的一些文件,得到如下信息:

test:   ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0x00245abc5b4fa8dba465e6f58365fb10e1eaadc2, not stripped
test.c: C source, ASCII text
test.i: C source, ASCII text
test.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
test.s: ASCII text

ELF文件类型有4种,这里见到其中的2中,就是可执行文件test和可重定位文件test.o。另外两种是共享目标文件和核心存储文件。


ELF文件不止一种类型,但是它们的基本结构是一致的:

ELF Header
Sections
Sections Header Table
Symbol Tables
ELF Header:存储了这个ELF文件最基本的信息,主要是标识了ELF文件类型,版本,段表偏移等基本信息。

Sections:中分为很多段,其中最基本的段就是代码段(.text),数据段(.data),只读数据段(.rodata),bss段。不同的ELF类型文件还有各种不同的段,有的段也很重要,遇到再说。

Sections Header Table:Sections内容很多,需要一个表来管理,这个部分内容就是用于管理这些段的。

Symbol Table:符号表,这部分内容也很多,在链接的时候要将不同的目标文件链接起来就是依靠符号表。


分析目标文件的时候,经常得到的都是一堆乱七八糟的的东西,上网找太麻烦了,其实身边就有最好的说明文档,那就是man。使用命令:

$man elf
手册中很详细的介绍了ELF文件的内容。看不懂的时候参考它是最好的办法。


从个人的感觉,ELF中最开始需要掌握的就是其中的3张地图,就好像我们来到了一个陌生的城市,最开始需要的其实就是这个城市的地图,而后我们才会有心思去到处观光。



3.2 第一张地图--- ELF Header

header部分记录的这个ELF文件的基本信息,以及下文的规划。

这张地图嘛,感觉就像说明了这个城市有多大,是一座什么定位的城市,最后还给出了1个很重要的提示“如果想知道这座城市的交通分布,就往XX方向走多少就可以看到Sections Header Table的内容,那里有这座城市的交通分布”。

使用指令7,可以查看test.o文件的header中的内容,得到内容如下:

ELF Header:
  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
  Type:                              REL (Relocatable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          252 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
  Number of section headers:         11
  Section header string table index: 8
看着头皮很麻,那就使用man手册来看看吧,


首先,header的信息是用ElfN_Ehdr进行描述的,结构体定义如下:

The ELF header is described by the type Elf32_Ehdr or Elf64_Ehdr:

           #define EI_NIDENT 16

           typedef struct {
               unsigned char e_ident[EI_NIDENT];
               uint16_t      e_type;
               uint16_t      e_machine;
               uint32_t      e_version;
               ElfN_Addr     e_entry;
               ElfN_Off      e_phoff;
               ElfN_Off      e_shoff;
               uint32_t      e_flags;
               uint16_t      e_ehsize;
               uint16_t      e_phentsize;
               uint16_t      e_phnum;
               uint16_t      e_shentsize;
               uint16_t      e_shnum;
               uint16_t      e_shstrndx;
           } ElfN_Ehdr;

两者之间的信息是一一对应的:

header中的魔术信息就是对应结构体中的e_ident。之后依次类推。

之后的内容嘛。。。对着man手册慢慢看吧。

下面是一些觉得比较重要的内容:

(1)注意下魔数中各个字段的概念,这样我们就懂得为什么经常会在shell文件的开头写 “#!/bin/sh”了。

(2)其它字段扫描看一下就好了。


注意Start of section headers字段,这个标识了Sections Header Table的在elf文件中的文件偏移量。



3.3 第二张地图--Sections Header Table

使用指令3编译代码3,得到目标文件test.o

这张就是这个城市的交通地图了,它告诉了我们这座城市的规划布局,以及每个段它在文件中的位置信息。甚至我们可以看出每个段它大概是做什么用的。

使用指令11分析代码3,得到如下信息

There are 13 section headers, starting at offset 0x130:

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 00002d 00  AX  0   0  4
  [ 2] .rel.text         REL             00000000 000430 000018 08     11   1  4
  [ 3] .data             PROGBITS        00000000 000064 000008 00  WA  0   0  4
  [ 4] .bss              NOBITS          00000000 00006c 000000 00  WA  0   0  4
  [ 5] .rodata           PROGBITS        00000000 00006c 000004 00   A  0   0  1
  [ 6] .comment          PROGBITS        00000000 000070 000028 01  MS  0   0  1
  [ 7] .note.GNU-stack   PROGBITS        00000000 000098 000000 00      0   0  1
  [ 8] .eh_frame         PROGBITS        00000000 000098 000038 00   A  0   0  4
  [ 9] .rel.eh_frame     REL             00000000 000448 000008 08     11   8  4
  [10] .shstrtab         STRTAB          00000000 0000d0 00005f 00      0   0  1
  [11] .symtab           SYMTAB          00000000 000338 0000d0 10     12  10  4
  [12] .strtab           STRTAB          00000000 000408 000026 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
这个部分显示了,ELF中段表部分的内容


描述这部分内容的结构体如下,和上述header中的一样,也是一一对应的关系。

The section header has the following structure:

           typedef struct {
               uint32_t   sh_name;
               uint32_t   sh_type;
               uint32_t   sh_flags;
               Elf32_Addr sh_addr;
               Elf32_Off  sh_offset;
               uint32_t   sh_size;
               uint32_t   sh_link;
               uint32_t   sh_info;
               uint32_t   sh_addralign;
               uint32_t   sh_entsize;
           } Elf32_Shdr;

当然这部分内容还是参考man手册来看了。就不介绍了

下面是一些觉得比较重要的内容:

(1)注意其中类型字段和标志字段,做到心中有数

当然除了上述方法还可以使用下面方法查看这个程序的各个段信息。

使用指令3,得到目标文件test.o文件

使用指令6,查看已经编译得到的test.o文件中各个段的基本信息,输出信息如下:

test.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000014  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00000000  00000000  00000048  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000048  2**2
                  ALLOC
  3 .comment      00000028  00000000  00000000  00000048  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  00000000  00000000  00000070  2**0
                  CONTENTS, READONLY
  5 .eh_frame     00000038  00000000  00000000  00000070  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

这张表描述的内容比上一张表更加简单,但是它很好的描述了这些字段在以后的可执行文件中所在的虚拟文件的位置(需要进行链接后才能看出来)。在链接过程中反而这张表更重要些。

它就是以后程序执行过程中的地图了,当然现在用不上了。

3.4 第三张地图--symbol table

这张地图也很强大,它描述了这座城市中住着那些人。但是这张表仅仅只是表示这个城市有那些人,不描述这些人住在什么地方,而且更重要的是,在没有链接之前,目标文件是不知道这些符号应该在哪个位置的,所以其实链接过程就是安排这些人入住的一个过程。

使用指令3编译代码3,得到目标文件test.o

使用指令12查看test.o

Symbol table '.symtab' contains 13 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS test.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 b.1359
     7: 00000000     0 SECTION LOCAL  DEFAULT    7 
     8: 00000000     0 SECTION LOCAL  DEFAULT    8 
     9: 00000000     0 SECTION LOCAL  DEFAULT    6 
    10: 00000000     4 OBJECT  GLOBAL DEFAULT    3 gloabl_var
    11: 00000000    45 FUNC    GLOBAL DEFAULT    1 main
    12: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

如之前介绍的,这个内容使用结构体表示的:

       typedef struct {
               uint32_t      st_name;
               Elf32_Addr    st_value;
               uint32_t      st_size;
               unsigned char st_info;
               unsigned char st_other;
               uint16_t      st_shndx;
           } Elf32_Sym;
内容也是一对一,参考man手册就可以看到详细信息了。
符号在编译链接过程中很重要,现在经常出现的兼容问题中有一些就是因为它引起的。弄明白符号,在链接过程中才能看的更加透彻。


当然也可以使用objdump命令查看,

使用指令13查看test.o就可以查看文件中的符号表,内容如下:

test.o:     file format elf32-i386

SYMBOL TABLE:
00000000 l    df *ABS*	00000000 test.c
00000000 l    d  .text	00000000 .text
00000000 l    d  .data	00000000 .data
00000000 l    d  .bss	00000000 .bss
00000000 l    d  .rodata	00000000 .rodata
00000004 l     O .data	00000004 b.1359
00000000 l    d  .note.GNU-stack	00000000 .note.GNU-stack
00000000 l    d  .eh_frame	00000000 .eh_frame
00000000 l    d  .comment	00000000 .comment
00000000 g     O .data	00000004 gloabl_var
00000000 g     F .text	0000002d main
00000000         *UND*	00000000 printf
这个可以通过查看objdump的man手册查看相关信息。

来尝试分析下几个比较基本的段

3.5 .text段

使用指令8查看test.o文件的.text段反汇编的信息,得到内容如下:

Disassembly of section .text:

00000000 <main>:
   0:	55                   	push   %ebp
   1:	89 e5                	mov    %esp,%ebp
   3:	83 ec 10             	sub    $0x10,%esp
   6:	c7 45 fc 64 00 00 00 	movl   $0x64,-0x4(%ebp)
   d:	b8 00 00 00 00       	mov    $0x0,%eax
  12:	c9                   	leave  
  13:	c3                   	ret      

可以计算,text段长度为0x14,和4.1中的长度大小一致。

以下内容为之前得到的test.s文件的内容

        .file   "test.c"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $16, %esp
        movl    $100, -4(%ebp)
        movl    $0, %eax
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (GNU) 4.7.1 20120721 (prerelease)"
        .section        .note.GNU-stack,"",@progbits
对比后发现,其实就是把汇编指令翻译成了二进制码。


3.6 .data字段

为了测试,我们在代码1的基础上,添加一行全局变量的申明,并且进行初始化,得到代码2

使用指令3编译代码2,得到新的test.o文件

使用指令6,查看新的test.o各个段的基本信息。得到信息如下:

test.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000014  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000004  00000000  00000000  00000048  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  0000004c  2**2
                  ALLOC
  3 .comment      00000028  00000000  00000000  0000004c  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  00000000  00000000  00000074  2**0
                  CONTENTS, READONLY
  5 .eh_frame     00000038  00000000  00000000  00000074  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA


与代码1的test.o中的 ,data段比较,发现新编译得到的test.o中.data段不再为0了。
另外,还可以添加局部的静态变量,如代码3所示。

重复指令3和指令6,就可以发现.data字段增大为0x8了

然后使用指令9(这里用的测试代码是代码2),查看相关数据段的信息。找到以下两行信息:

Contents of section .data:
 0000 64000000                             d...  
我们在代码2中声明了一个全局变量,并初始化为100,100表示成2进制为 1100100,正好是0x64(1100100)的大小

注:这里涉及了计算机中大端存储和小端存储的问题。所以看的时候不要看错了。


3.7 .bss字段

在代码3的基础上,加入未初始化的全局变量和局部静态变量申明。然后使用指令3和指令6。得到如下信息:

test.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000014  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000008  00000000  00000000  00000048  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  00000000  00000000  00000050  2**2
                  ALLOC
  3 .comment      00000028  00000000  00000000  00000050  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  00000000  00000000  00000078  2**0
                  CONTENTS, READONLY
  5 .eh_frame     00000038  00000000  00000000  00000078  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
这里会有一个很奇怪的现象:就是明明申明了2个变量,大小应该是8字节才对,但是为什么.bss段大小只有4字节。


使用指令10,查看test.o文件,得到如下信息(截取部分):

………………………………
SYMBOL TABLE:
00000000 l    df *ABS*	00000000 test.c
00000000 l    d  .text	00000000 .text
00000000 l    d  .data	00000000 .data
00000000 l    d  .bss	00000000 .bss
00000000 l     O .bss	00000004 c.1361
00000004 l     O .data	00000004 b.1360
00000000 l    d  .note.GNU-stack	00000000 .note.GNU-stack
00000000 l    d  .eh_frame	00000000 .eh_frame
00000000 l    d  .comment	00000000 .comment
00000000 g     O .data	00000004 gloabl_var
00000004       O *COM*	00000004 global_var2
00000000 g     F .text	00000014 main
………………………………


3.8 .rodata字段

使用代码5,添加一句打印语句。

然后使用指令3编译新的test.o文件,使用指令6,得到如下信息:

test.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000002d  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  00000000  00000000  00000064  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  0000006c  2**2
                  ALLOC
  3 .rodata       00000004  00000000  00000000  0000006c  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      00000028  00000000  00000000  00000070  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  00000000  00000000  00000098  2**0
                  CONTENTS, READONLY
  6 .eh_frame     00000038  00000000  00000000  00000098  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
与之前的内容相比,发现多出一个字段.rodata。这个字段占用4个字节。

使用指令9,查看.rodata段中的信息,得到如下:

test.o:     file format elf32-i386

Contents of section .text:
 0000 5589e583 e4f083ec 20c74424 1c640000  U....... .D$.d..
 0010 00a10400 00008944 2404c704 24000000  .......D$...$...
 0020 00e8fcff ffffb800 000000c9 c3        .............   
Contents of section .data:
 0000 64000000 e8030000                    d.......        
Contents of section .rodata:
 0000 25640a00                             %d..            
Contents of section .comment:
 0000 00474343 3a202847 4e552920 342e372e  .GCC: (GNU) 4.7.
 0010 31203230 31323037 32312028 70726572  1 20120721 (prer
 0020 656c6561 73652900                    elease).        
Contents of section .eh_frame:
 0000 14000000 00000000 017a5200 017c0801  .........zR..|..
 0010 1b0c0404 88010000 1c000000 1c000000  ................
 0020 00000000 2d000000 00410e08 8502420d  ....-....A....B.
 0030 0569c50c 04040000                    .i......        
可以使用man查看ascii,可以查到如下ASCII码关系:

Oct   Dec   Hex   Char
045   37    25    % 
144   100   64    d
012   10    0A    LF  '\n' (new line) 
对应.rodata字段中的信息,明显存的就是“%d\n”这三个符号了。

注:第四个字节没有使用是因为字节对齐的原因。


3.9 其他字段

ELF中字段很多,甚至还可以自定义,如果掌握了如上的分析方法,那分析其他段就相对容易了。




命令集:

指令1、-E:只进行预处理,并把处理结果输出

$ gcc -E test.c -o test.i
或者:
$ cpp test.c  > test.i


指令2:-S:只进行汇编

从预处理文件到汇编文件

$ gcc -S test.i -o test.s
或者:从C文件到汇编文件

$ gcc -S test.c -o test.s

指令3:

从汇编源码编译

$ as test.s -o test.o
或者:-c:只编译,不链接 ,

$ gcc -c test.c -o test.o

指令4:

$ hexdump -C test.o

指令5、查看文件类型

$ file *

指令6:-h : 查看各个段的基本信息

$ objdump -h test.o

指令7:-h:目标文件首部的详细信息

$ readelf -h test.o

指令8:-d : 反汇编

$ objdump -d test.o

指令9:-s:显示各个段的详细内容

$ objdump -s test.o

指令10:-x : 显示文件的所有头文件

$ objdump -x test.o 


指令11:

readelf -S test.o

指令12:

$ readelf -s test.o


指令13:

$objdump -t test.o



测试代码集合

代码1、

/*test.c*/

#ifndef NUM
    #define NUM 100
#endif

int main()
{
    /*this is test.cc*/
    int a = NUM;
    return 0;
}


代码2、

/*test.c*/

#ifndef NUM
    #define NUM 100
#endif

int gloabl_var = 100; /*全局变量*/

int main()
{
    /*this is test.cc*/
    int a = NUM;
    return 0;
}

代码3、

/*test.c*/

#ifndef NUM
    #define NUM 100
#endif

int gloabl_var = 100; /*全局变量*/

int main()
{
    /*this is test.cc*/
    int a = NUM;
    static int b = 1000;/* 局部静态变量*/
    return 0;
}

代码4:

/*test.c*/

#ifndef NUM
    #define NUM 100
#endif

int gloabl_var = 100; /*全局变量*/
int global_var2; /* 未初始化的全局变量*/
int main()
{
    /*this is test.cc*/
    int a = NUM;
    static int b = 1000;/* 局部静态变量*/
    static int c; /*未初始化的静态变量*/
    return 0;
}

代码5、

/*test.c*/

#ifndef NUM
    #define NUM 100
#endif

int gloabl_var = 100; /*全局变量*/
/*int global_var2; [> 未初始化的全局变量<]*/
int main()
{
    /*this is test.cc*/
    int a = NUM;
    static int b = 1000;/* 局部静态变量*/
    /*static int c; [>未初始化的静态变量<]*/

    printf("%d\n", b); /*.rodata字段*/
    return 0;
}

你可能感兴趣的:(编译器、连接器学习笔记--(二)--编译)