C语言编译与链接过程详解

news2024/11/27 9:53:18

C语言编译与链接过程详解

源文件

main.c
#include <stdio.h>

extern int data;
extern int add(int a,int b);

int a1;
int a2 = 0;
int a3 = 10;

static int b1;
static int b2 = 0;
static int b3 = 20;

int main()
{
	int c1;
	int c2 = 0;
	int c3 = 30;

	static int d1;
	static int d2 = 0;
	static int d3 = 40;

	c1 = data;
	c2 = add(a1,a2);

	while(1);

	return 0;
}
add.c
int data = 3;
int add(int a,int b)
{
	return a+b;
}

两大过程:编译、链接

一、编译过程:


  1. 预处理 (.i)

    • 处理#开头的预处理指令:#include #define #ifndef #if #else 等等

    • 去注释、加行号、生成文件索引等等

    命令:gcc -E main.c -o main.i,生成 .i 文件

  2. 编译 (.s)

    将 .i 文件编译生成 .s 汇编文件

    命令:gcc -S main.i 生成 .s 文件

  3. 汇编 (.o)

    将汇编文件翻译成二进程可重定位文件,即 .o 文件

    命令:gcc -c main.s 生成 .o 文件

PS:gcc命令只是一些后台程序的包装,它会根据不同的参数调用其他程序:

  • 预编译和编译合并成了一个步骤,使用的是程序cc1,也可以通过如下命令生成.s文件

    cc1 hello.c

    等同于 gcc -S hello.c -o hello.s

  • 汇编器 as

  • 链接器 ld

分析二进制可重定位文件

main.c文件

#include <stdio.h>

int a1;
int a2 = 0;
int a3 = 10;

static int b1;
static int b2 = 0;
static int b3 = 20;

int main(void)
{
	int c1;
	int c2 = 0;
	int c3 = 30;

	static int d1;
	static int d2 = 0;
	static int d3 = 40;

	return 0;
}

编译命令:在64位的机器上编译32位的.o文件

*gcc -m32 -fno-PIC -c .c

-m32指定编译生成32位文件;-fno-PIC去除和位置无关的段(只留下.text .data .bss .comment 等)

在这里插入图片描述

1. 读取 elf 文件头
$ readelf -h main.o                                                           
ELF 头:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF32
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              REL (可重定位文件)
  系统架构:                          ARM
  版本:                              0x1
  入口点地址:               0x0
  程序头起点:          0 (bytes into file)
  Start of section headers:          268 (bytes into file)
  标志:             0x5000000, Version5 EABI
  本头的大小:       52 (字节)
  程序头大小:       0 (字节)
  Number of program headers:         0
  节头大小:         40 (字节)
  节头数量:         10
  字符串表索引节头: 7

(1) 魔数

Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

在这里插入图片描述

(2) REL (可重定位文件)

(3) 入口点地址: 0x0

(4) Start of section headers: 268 (bytes into file)

(5) 本头的大小: 52 (字节)

2. 获取 elf 文件的 section headers(段头) 信息 (供链接使用)
$ readelf -S main.o
There are 12 section headers, starting at offset 0x2ec:

节头:
  [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 000044 00  AX  0   0  1
  [ 2] .rel.text         REL             00000000 00026c 000020 08   I  9   1  4
  [ 3] .data             PROGBITS        00000000 000078 00000c 00  WA  0   0  4
  [ 4] .bss              NOBITS          00000000 000084 000014 00  WA  0   0  4
  [ 5] .comment          PROGBITS        00000000 000084 00002a 01  MS  0   0  1
  [ 6] .note.GNU-stack   PROGBITS        00000000 0000ae 000000 00      0   0  1
  [ 7] .eh_frame         PROGBITS        00000000 0000b0 00003c 00   A  0   0  4
  [ 8] .rel.eh_frame     REL             00000000 00028c 000008 08   I  9   7  4
  [ 9] .symtab           SYMTAB          00000000 0000ec 000140 10     10  14  4
  [10] .strtab           STRTAB          00000000 00022c 000040 00      0   0  1
  [11] .shstrtab         STRTAB          00000000 000294 000057 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

有12个段头,起始段头偏移为 0x2ec

可以看到每个段的偏移与大小

3. 打印出段的内容
~ $ objdump -s main.o

main.o:     文件格式 elf32-i386

Contents of section .text:
 0000 8d4c2404 83e4f0ff 71fc5589 e55183ec  .L$.....q.U..Q..
 0010 14c745ec 00000000 c745f01e 000000a1  ..E......E......
 0020 00000000 8945f48b 15000000 00a10000  .....E..........
 0030 000083ec 085250e8 fcffffff 83c41089  .....RP.........
 0040 45ecebfe                             E...            
Contents of section .data:
 0000 0a000000 14000000 28000000           ........(...    
Contents of section .comment:
 0000 00474343 3a202855 62756e74 7520372e  .GCC: (Ubuntu 7.
 0010 352e302d 33756275 6e747531 7e31382e  5.0-3ubuntu1~18.
 0020 30342920 372e352e 3000               04) 7.5.0.      
Contents of section .eh_frame:
 0000 14000000 00000000 017a5200 017c0801  .........zR..|..
 0010 1b0c0404 88010000 20000000 1c000000  ........ .......
 0020 00000000 44000000 00440c01 00471005  ....D....D...G..
 0030 02750043 0f03757c 06000000           .u.C..u|....
4. 读取 .o 文件符号表
~ $ objdump -t main.o                                                           
main.o:     文件格式 elf32-little

SYMBOL TABLE:
00000000 l    df *ABS*	00000000 main.c
00000000 l    d  .text	00000000 .text
00000000 l    d  .data	00000000 .data
00000000 l    d  .bss	00000000 .bss
00000004 l     O .bss	00000004 b1
00000008 l     O .bss	00000004 b2
00000004 l     O .data	00000004 b3
00000008 l     O .data	00000004 d3.1881
0000000c l     O .bss	00000004 d2.1880
00000010 l     O .bss	00000004 d1.1879
00000000 l    d  .note.GNU-stack	00000000 .note.GNU-stack
00000000 l    d  .eh_frame	00000000 .eh_frame
00000000 l    d  .comment	00000000 .comment
00000004       O *COM*	00000004 a1
00000000 g     O .bss	00000004 a2
00000000 g     O .data	00000004 a3
00000000 g     F .text	00000044 main
00000000         *UND*	00000000 data
00000000         *UND*	00000000 add

标出了每个符号处于那个段,占多大内存,其中 a1 标记为 *COM* 表示它是弱符号(未初始化的非静态全局变量,可能其他文件里也定义了同名的)

data 和 add 这两个符号被标记为 *UND* ,表示未定义的符号,在本文件中找不到定义,链接时会从其他文件中寻找

5. 根据 section headers(段头) 信息,画出二进制可重定位文件的组成(.o文件)

在这里插入图片描述

可以发现bss段和comment段的起始卫视相同,但实际计算得出bss段在.o文件中并没有存储,但是符号表中对bss段有记录。

得出结论:bss段保存的都是未初始化 / 初始化为0的全局变量,和未初始化 / 初始化为0静态局部变量,所以他们的默认值都为0 ,故为了节省.o文件的空间,无需存储,但是需要在符号表中记录,在最后执行可执行文件后,将bss段的符号存到虚拟地址空间中。
在这里插入图片描述
在这里插入图片描述

二、链接过程:


在64位x86机器上编译-链接生成32位目标文件和可执行文件的命令

编译:
	gcc -m32 -fno-PIC -c *.c
手动链接:
    ld -e main -melf_i386 *.o -o run
    
生成如下文件:
    $ ls
	add.c  add.o  main.c  main.o  run

PS:

-m32指定编译生成32位文件;

-fno-PIC去除和位置无关的段(只留下.text .data .bss .comment 等)

-e 指定程序入口,-e后跟着符号即可,也可以把add函数作为程序入口,即 -e add

-melf_i386指定链接生成32位的,x86架构的可执行文件


链接过程的本质主要是将多个目标文件“粘”在一起,实质上拼合的是目标文件之间对地址的引用,即函数名和全局变量

符号表就是.o文件的一个段,symtab,查看符号表命令

readelf -s main.o

objdump -t main.o

nm main.o

符号表中包含什么,主要关注1和2

    1. 定义在本目标文件中的全局符号,例如变量名、函数名等
    1. 引用的其他目标文件中的符号,没有在本文件中定义,一般叫做外部符号
    1. 段名,如 “.text”, “.data” 等
    1. 局部符号,只在编译单元内部可见,调试器可以使用这些符号来分析程序或崩溃时的核心转储文件,链接过程中链接器往往忽略它们
$ objdump -t main.o

main.o:     文件格式 elf32-i386

SYMBOL TABLE:
00000000 l    df *ABS*	00000000 main.c
00000000 l    d  .text	00000000 .text
00000000 l    d  .data	00000000 .data
00000000 l    d  .bss	00000000 .bss
00000004 l     O .bss	00000004 b1
00000008 l     O .bss	00000004 b2
00000004 l     O .data	00000004 b3
00000008 l     O .data	00000004 d3.1877
0000000c l     O .bss	00000004 d2.1876
00000010 l     O .bss	00000004 d1.1875
00000000 l    d  .note.GNU-stack	00000000 .note.GNU-stack
00000000 l    d  .eh_frame	00000000 .eh_frame
00000000 l    d  .comment	00000000 .comment
00000004       O *COM*	00000004 a1
00000000 g     O .bss	00000004 a2
00000000 g     O .data	00000004 a3
00000000 g     F .text	00000016 main
1. 合并所有 .o 文件的段

在这里插入图片描述

如上图所示,text段合并,data段合并,bss段合并的同时,需要将弱符号转化为强符号(或者弱符号被强符号替换),bss段大小增加

并且发现链接后,生成的可执行文件的每个段都分配了内存地址(虚拟内存)

2. 合并符号表、符号解析、重定位

在这里插入图片描述

  • 合并符号表

​ 可以看出,可执行文件的符号表就是将多个.o文件的符号表简单的合并起来

  • 符号解析

​ 将弱符号(*COM*)转化为强符号

​ 在其他文件中找到本文件中未定义的符号(*UND*)

  • 重定位

​ 为符号分配虚拟内存地址,符号的地址是根据段的地址加上自身的偏移计算的

可执行文件分析

1. 查看文件头
$ readelf -h run
ELF 头:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF32
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              EXEC (可执行文件)
  系统架构:                          Intel 80386
  版本:                              0x1
  入口点地址:               0x80480a1
  程序头起点:          52 (bytes into file)
  Start of section headers:          4676 (bytes into file)
  标志:             0x0
  本头的大小:       52 (字节)
  程序头大小:       32 (字节)
  Number of program headers:         3
  节头大小:         40 (字节)
  节头数量:         9
  字符串表索引节头: 8

入口点地址:0x80480a1。

2. 查看段信息
$ readelf -S run
There are 9 section headers, starting at offset 0x1244:

节头:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        08048094 000094 000051 00  AX  0   0  1
  [ 2] .eh_frame         PROGBITS        080480e8 0000e8 00005c 00   A  0   0  4
  [ 3] .data             PROGBITS        0804a000 001000 000010 00  WA  0   0  4
  [ 4] .bss              NOBITS          0804a010 001010 000018 00  WA  0   0  4
  [ 5] .comment          PROGBITS        00000000 001010 000029 01  MS  0   0  1
  [ 6] .symtab           SYMTAB          00000000 00103c 000170 10      7  14  4
  [ 7] .strtab           STRTAB          00000000 0011ac 000059 00      0   0  1
  [ 8] .shstrtab         STRTAB          00000000 001205 00003f 00      0   0  1

每个段都分配了虚拟地址。

3. 查看 program headers
$ readelf -l run

Elf 文件类型为 EXEC (可执行文件)
Entry point 0x80480a1
There are 3 program headers, starting at offset 52

程序头:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0x00144 0x00144 R E 0x1000
  LOAD           0x001000 0x0804a000 0x0804a000 0x00010 0x00028 RW  0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10

 Section to Segment mapping:
  段节...
   00     .text .eh_frame 
   01     .data .bss 
   02

二进制可重定位文件只有 “section headers”,只有可执行文件里有 “program headers”“program headers” 中显示了各个段的虚拟地址、对齐字节(一页4K)

按段的属性合并,只读(text+rodata)、可读可写(data+bss)等等

使用 readelf -l main 查看ELF的 “Segment” (供装载使用)

PS:因为我们是自己链接的,没有链接C库,所以段里的内容比较少

​ * 如果直接运行 gcc main.c -o main,则会默认链接C库,查看可执行文件的每个段时就有很多内容了

​ * 可执行文件是被 execve 加载到进程中的

​ * 可执行文件之所以可以运行,因为其指定了入口地址(main)、program headers(指定加载的虚拟地址)

​ * 描述 “Segment” 的结构叫 ”程序头” ,它描述了ELF文件该如何被操作系统映射到进程的虚拟空间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1056060.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QQ通信协议

不管UDP还是TCP&#xff0c;最终登陆成功之后&#xff0c;QQ都会有一个TCP连接来保持在线状态。这个TCP连接的远程端口一般是80&#xff0c;采用UDP方式登陆的时候&#xff0c;端口是8000。因此&#xff0c;假如你所在的网络开放了80端口&#xff08;80端口是最常用端口。。就是…

轻松掌握二叉树和堆(保姆级详解,小白必看系列)

目录 一、前言 二、二叉树的概念和结构 &#x1f34e;二叉树的概念 &#x1f350;特殊的二叉树&#xff08;重点&#xff09; &#x1f349;二叉树的性质 &#xff08;超重点------面试做题会用&#xff09; &#x1f353;二叉树的概念选择题 &#x1f34c;二叉树的存储…

三、逻辑代数基础

1.简介 在数字电路中&#xff0c;我们用两种数码1和0来表示一个信号的两种不同的逻辑状态。 例如&#xff1a;用1表示开关闭合&#xff0c;电路导通&#xff1b;用0表示开关打开&#xff0c;电路断开。 这两种对立的逻辑关系就称之为二值逻辑。 当两种数码表示不同的逻辑状态…

Acwing 845. 八数码

Acwing 845. 八数码 知识点题目描述思路讲解代码展示 知识点 BFS 题目描述 思路讲解 分析一下y总的思路&#xff0c;也相当于做个课堂笔记吧&#xff08;这也太巧妙了吧&#xff0c;讲解视频不到20分钟&#xff0c;我愣是半天没想出来 1、题目的目标 2、移动情况 移动方式…

基于Java的城市天然气费管理系统的设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

S5PV210裸机(一):裸机基础,arm指令,210启动刷机

本文主要探讨s5pv210裸机基础知识&#xff0c;arm指令&#xff0c;以及210启动刷机相关知识。 Soc与cpu Soc是cpu与其他外设的集合即SoC<>cpuDDRflashutral...... 地址总线与数据总线 cpu通过地址总线寻址即传输DDR或flash等地址,通过数据总线与外设进…

【算法优选】双指针专题——贰

文章目录 &#x1f60e;前言&#x1f332;[快乐数](https://leetcode.cn/problems/happy-number/)&#x1f6a9;题目描述&#x1f6a9;题⽬分析&#xff1a;&#x1f6a9;算法思路&#xff1a;&#x1f6a9;代码实现&#xff1a; &#x1f38b;[盛水最多的容器](https://leetco…

第四十章 持久对象和SQL - Object IDs

文章目录 第四十章 持久对象和SQL - Object IDsObject IDsID是如何确定的访问 ID 第四十章 持久对象和SQL - Object IDs Object IDs 每个对象在其所属的每个范围内都有一个唯一的 ID。在大多数情况下&#xff0c;使用此 ID 来处理对象。此 ID 是类中 %Persistent 的以下常用方…

如何制作在线流程图?6款在线工具帮你轻松搞定

流程图&#xff0c;顾名思义 —— 用视觉化的方式来描述一种过程或流程。它可以应用于各种领域&#xff0c;从业务流程&#xff0c;算法&#xff0c;到计算机程序等。然而&#xff0c;在创建流程图时&#xff0c;可能会遇到许多问题或者困惑&#xff0c;如缺乏专业的设计技能&a…

锚框_的标定

一、查漏补缺、熟能生巧&#xff1a; 1.关于fix.axis.add_patch在原来图像的坐标系同添加 边框的函数的使用&#xff1a; 2.torch.arange( h , device)生成tensor的等差数组: 3.torch.T&#xff08;&#xff09;就是transpose转置操作的函数咯: 4.torch.repeat操作&#xff0c…

静态数码管显示+动态数码管显示——“51单片机”

各位CSDN的uu们好呀&#xff0c;今天小雅兰的内容还是51单片机的知识&#xff0c;是为静态数码管显示和动态数码管显示&#xff0c;下面&#xff0c;让我们进入51单片机的世界吧&#xff01;&#xff01;&#xff01; 静态数码管显示 动态数码管显示 源代码 静态数码管显示 …

网络运营推广过程中客户说需要资质

大家好&#xff0c;我是网络工程师成长日记实验室的郑老师&#xff0c;您现在正在查看的是网络工程师成长日记专栏&#xff0c;记录网络工程师日常生活的点点滴滴 一个同学跟我学网络运营&#xff0c;他说他现在也学我做弱电。然后他说他接到电话&#xff0c;对方都是问资质&am…

首发Orin N芯片,腾势追赶「智驾第一梯队」

张祥威 编辑 | 德新 英伟达最新一代芯片—— Orin N&#xff0c;腾势拿下 首发。 9月26日&#xff0c;腾势N7推出「高快智驾包」。官方描述中&#xff0c;这一选装将“基于新一代NIVIDIA DRIVE ORIN的 高性能平台”&#xff0c;可以实现高速NOA。 此前&#xff0c;腾势的…

Acwing 843. n-皇后问题

Acwing 843. n-皇后问题 知识点题目描述思路讲解代码展示 知识点 DFS剪枝 题目描述 思路讲解 代码展示 第一种搜索方式&#xff1a; #include <iostream>using namespace std;const int N 20;int n; char g[N][N]; bool col[N], dg[N * 2], udg[N * 2];void dfs(in…

常见开发、测试模型

开发模型瀑布模型螺旋模型增量、迭代敏捷开发模型 测试模型V模型W模型 开发模型 瀑布模型 瀑布模型的每一个阶段都只执行一次&#xff0c;是线性顺序进行的软件开发模式。 优点&#xff1a;每个阶段做什么&#xff1b;产生什么非常清晰&#xff1b; 缺点&#xff1a;风险往…

Python3数据科学包系列(三):数据分析实战

Python3中类的高级语法及实战 Python3(基础|高级)语法实战(|多线程|多进程|线程池|进程池技术)|多线程安全问题解决方案 Python3数据科学包系列(一):数据分析实战 Python3数据科学包系列(二):数据分析实战 一: 数据分析与挖掘认知升维 我们知道在数据分析与数据挖掘中,数据…

15: 8种GPIO模式和其他资源

目录 一:GPIO 1:简历 2:模式 3:位结构 4:八种模式 A: 浮空/上拉/下拉输入 B:模拟输入 ----GPIO_Mode_AIN C: 开漏/推挽输出 D:复用开漏/推挽输出 E: 模式总结 二:其他资源 1:片上资源/外设 2:引脚定义表 一:GPIO 1:简历 GPIO&#xff08;General Purpose Input …

排序---P1012 [NOIP1998 提高组] 拼数

思路&#xff1a; 这道题的思路就是进行排序&#xff0c;但不同于以往是根据数的大小排序&#xff0c;这道题是根据最高位最大就放在越前面。那么要怎么解决最高位越大排得越前这个问题呢&#xff0c;我们就会想到用字符串比较大小&#xff0c;就可以解决&#xff0c;所以我们…

RESTFul风格接口如何设计

RESTFul风格设计规范 HTTP协议请求方式要求 REST 风格主张在项目设计、开发过程中&#xff0c;具体的操作符合HTTP协议定义的请求方式的语义。 操作请求方式查询操作GET保存操作POST删除操作DELETE更新操作PUT 需求分析 数据结构&#xff1a; User {id 唯一标识,name 用户名&a…

车牌超分辨率:License Plate Super-Resolution Using Diffusion Models

论文作者&#xff1a;Sawsan AlHalawani,Bilel Benjdira,Adel Ammar,Anis Koubaa,Anas M. Ali 作者单位&#xff1a;Prince Sultan University 论文链接&#xff1a;http://arxiv.org/abs/2309.12506v1 内容简介&#xff1a; 1&#xff09;方向&#xff1a;图像超分辨率技术…