由C源文件到可执行文件——链接、GNU工具(CS:APP)

本文详细介绍了C程序从源文件到可执行文件的编译过程,包括预处理、汇编、链接(静态与动态链接)等步骤。重点讨论了静态链接中的符号解析和重定位,动态链接的共享库(位置无关代码)、全局偏移量表(GOT)和过程连接表(PLT)。此外,还涵盖了GNU工具的使用,如ar、strings、strip、nm、size、readelf、objdump、ldd等在目标文件处理和链接过程中的作用。
摘要由CSDN通过智能技术生成

文章目录

一、编译的基本过程

主要使用的工具是gcc,下面以一个简单的程序来演示基本的编译过程
1、源文件
  • 本源文件意在展示编译过程,总共包含四个文件:主函数main.c、头文件vector.h、头文件实现addvec.c及multvec.c 。
  • main.c
#include"vector.h"

int x[2] = {
   1, 2};
int y[2] = {
   3, 3};
int z[2];
int main()
{
   
    addvec(x, y, z, 2);
    return 0;
}

  • vector.h
extern int addcnt;
extern int multcnt;
void addvec (int*,int*,int*,int);
void multvec (int*,int*,int*,int);
  • addvec.c
int addcnt = 0;
void addvec(int *x, int *y, int *z, int n)
{
   
    int i;
    addcnt++;
    for (i = 0; i < n; ++i)
        z[i] = x[i] + y[i];
}
  • multvec.c
int multcnt = 0;

void multvec(int *x, int *y, int *z, int n)
{
   
    int i;
    multcnt++;
    for (i = 0; i < n; ++i)
        z[i] = x[i] * y[i];
}
2、预处理文件
shell命令: gcc -E main.c -o main.i
  • 预处理器cpp来完成预处理的工作,主要处理宏和include文件,常用选项如下:
    • -D name[=definition] 相当于#define NAME 或 #define NAME 100等c程序中的宏定义
    • -U name 相当于#undef
    • -include file 顾名思义#include"file"
    • -undef Do not predefine any system-specific or GCC-specific macros. The standard predefined macros remain defined
      使用上面命令行会生成如下文件:
  • main.i
# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "main.c"
# 1 "vector.h" 1

extern int addcnt;
extern int multcnt;

void addvec (int*,int*,int*,int);
void multvec (int*,int*,int*,int);
# 2 "main.c" 2

int x[2] = {
   1, 2};
int y[2] = {
   3, 3};
int z[2];

int main()
{
   
    addvec(x, y, z, 2);
    return 0;
}
3、汇编文件
shell命令 gcc -S main.c  -o main.s
  • cc1程序将c语言程序转化为汇编代码程序,注意此处的数字使用的是十进制,全局变量x、y、z的地址还没有确定,调用的函数采用助记符标示。
    上面shell命令行生成的文件如下,可以看出函数声明在其中好像没有体现,其实不然:函数声明会影响程序汇编代码的内容,函数调用之前的参数该如何传递,如果没有函数声明,传递参数的代码将无法实现汇编。
 22 main:
 23 .LFB0:
 25     pushq   %rbp
 28     movq    %rsp, %rbp
 30     movl    $2, %ecx
 31     leaq    z(%rip), %rdx
 32     leaq    y(%rip), %rsi
 33     leaq    x(%rip), %rdi
 34     call    addvec@PLT
 35     movl    $0, %eax
 36     popq    %rbp
 38     ret
4、可重定位目标文件
shell命令: gcc -c mian.c && objdump -dx main.o
  • as程序,将汇编代码转变成可重定位的目标文件;内容与汇编代码相同,但数字时十六进制、标明了需要重定位符号的重定位方式;另外指令的长度也确定了。
0000000000000000 <main>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	b9 02 00 00 00       	mov    $0x2,%ecx
   9:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # 10 <main+0x10>
			c: R_X86_64_PC32	z-0x4
  10:	48 8d 35 00 00 00 00 	lea    0x0(%rip),%rsi        # 17 <main+0x17>
			13: R_X86_64_PC32	y-0x4
  17:	48 8d 3d 00 00 00 00 	lea    0x0(%rip),%rdi        # 1e <main+0x1e>
			1a: R_X86_64_PC32	x-0x4
  1e:	e8 00 00 00 00       	callq  23 <main+0x23>
			1f: R_X86_64_PLT32	addvec-0x4
  23:	b8 00 00 00 00       	mov    $0x0,%eax
  28:	5d                   	pop    %rbp
  29:	c3                   	retq   
5、可执行文件
shell命令: gcc main.o addvec.o -o main && objdump -dx main
  • ld程序负责链接工作,此处main.o跟addvec.o的链接方式是静态链接。
  • 对比上面反汇编的可重定位目标文件及反汇编的可执行文件,可以看出:
    1. main函数的代码在链接前后代码长度没有发生变化,操作码都没有变;
    2. 只改变了标示需要重定位的那四个32位位置发生了变化。
0000000000001125 <main>:
    1125:	55                   	push   %rbp
    1126:	48 89 e5             	mov    %rsp,%rbp
    1129:	b9 02 00 00 00       	mov    $0x2,%ecx
    112e:	48 8d 15 fb 2e 00 00 	lea    0x2efb(%rip),%rdx        # 4030 <z>
    1135:	48 8d 35 dc 2e 00 00 	lea    0x2edc(%rip),%rsi        # 4018 <y>
    113c:	48 8d 3d cd 2e 00 00 	lea    0x2ecd(%rip),%rdi        # 4010 <x>
    1143:	e8 07 00 00 00       	callq  114f <addvec>
    1148:	b8 00 00 00 00       	mov    $0x0,%eax
    114d:	5d                   	pop    %rbp
    114e:	c3                   	retq   

000000000000114f <addvec>:
   

二、链接

《深入了解计算机系统》第三版 第七章
1、静态链接
  • 下图展示了一个简单程序从C源文件到可执行目标文件的执行过程,此处执行的是静态链接:
main.o
addvec.o
printf.o和其他printf.o调用的模块
源文件main.c
翻译器(cpp、cc1、as)
头文件vector.h
链接器(ld)
静态库libvcetor.a
静态库libc.a
prog完全可链接的可执行目标文件
1.1、目标文件
  • 可重定位目标文件
  • 可执行目标文件(完全链接的)
  • 共享目标文件:静态库,可重定位目标文件的归档文件。
1.2、可重定位目标文件
1.2.1、 可执行目标文件的格式如下:
Section Description
ELF头 文件类型说明,机器相关内容说明、节头部表偏移和大小说明
.text 代码段
.rodata 只读数据段
.data 已初始化的全局变量和静态变量
.bss 未初始化的静态变量;被初始化为0的全局变量和静态变量
.symtab 符号表,存放程序中定义和引用的函数和全局变量的信息
.rel.text .text节中位置的列表,链接时需要修改这些位置
.rel.data 被模块引用或定义的所有全局变量的重定位信息
.debug 一个调试符号表,包含程序中定义的局部变量和类型定义,程序中引用和定义的全局变量以及原始的C源文件
.line 原始C源程序中的行号和.text节中机器指令间的映射。
.strtab 一个字符串表,包含.symtab和.debug中所有符号及节头部中的节名字
节头部表 描述目标文件的节
1.2.2、可重定位目标文件各部分之间的关系
  • 各部分说明:
    • ELF头:是可重定位目标文件入口,定义可重定位目标文件的读取方式,以及节头部表的位置和大小。通过ELF头可以读到节头部表。
    • 节头部表:包含各节的位置和大小,通过它可以定位到各节;
    • symtab节:符号在strtab中的位置,可以读取各符号的名字;符号类型、符号的位置(符号所在节及节内偏移)、符号所占内存大小。这样就可以访问各个符号了。
    • 重定位信息(.rel.text节和.rel.data节):需要重定位的位置(所在text节或data节的位置),知道哪里需要重定位;其所引用的符号在symtab中的位置,通过symtab就可以找到引用符号了。
    • 代码段、只读数据段、初始化数据段及bbs段:这些是程序执行真正需要的东西,跟C源文件内容是对应的;在重定位过程中会被修改。ELF文件中其他节的内容其实都是为了访问和维护这些内容。
    • debug段和line段:是调试需要的信息,要通过-g选项才能得到;此处咱不说明
    • strtab段:符号的字符串表,通过symtab和debug段来访问。
  • 下图是简单的访问关系图
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值