xym-ee
  • 计算机与嵌入式开发学习
  • 1-1.编程基础
    • C 语言
      • C 中的数据
      • C 语言基础
      • 字符输入输出
      • 函数
      • 数组和指针
      • 字符串处理
      • 存储类别
      • 文件 I/O
      • 复杂数据类型
      • 位操作
      • 预处理和 C 库
    • 数据结构和算法入门
    • leetcode 刷算法题
      • 递归与栈
      • 二叉树与递归
      • 回溯问题
      • 动态规划 1
    • 基本工具和使用
      • shell
      • shell 脚本
      • vim 编辑器
      • 命令行数据整理
      • 命令行环境和配置
  • 1-2.计算机系统基础(CSAPP)
    • 1.计算机基础
    • 2.数据的表示
    • 3.加减运算
    • 4.乘除运算
    • 5.程序的表示转换和链接
    • 6.IA32指令
    • 7.过程调用
    • 10.程序的链接
  • 1-3.数字电路、计算机组成
    • 1.数字电路、virtual circuit board
    • 2.计算机组成/steam:Turing Complete
    • 3.微机原理与接口技术(8086)
  • 1-4.计算机网络
    • 1.从浏览器开始
    • 2.协议栈和网卡
    • 3.网络设备
    • 4.运营商、接入网
    • 5.服务器
    • 6.数据返回浏览器
    • socket编程
  • 1-5.操作系统
    • 0.绪论
      • 1.应用视角的操作系统
      • 2.硬件视角的操作系统
      • 3.数学视角的操作系统
      • 4.状态机模型的应用
    • 1.并发
      • 1.并发 bug 的解决思路
      • 2.互斥
      • 3.同步
      • 4.信号量
      • 5.真实并发
      • 6.调试技巧
      • 7.os kernel 实现
    • 2.虚拟化
      • 1.操作系统上的进程
      • 2.进程的地址空间
      • 3.系统调用和unix shell
      • 4.C 标准库的实现
      • 5.linux 操作系统
      • 6.可执行文件和加载
      • 7.动态链接和加载
      • 8.内核的实现
      • 9.fork 的应用
    • 3.持久化
      • 1.存储设备的原理
      • 2.输入输出设备模型
      • 3.设备驱动程序
      • 4.文件系统 API
      • 5.fat 和 unix 文件系统
      • 6.持久数据的可靠性
    • 总结
  • 2-1.嵌入式裸机开发
    • 嵌入式系统通信接口与协议
    • cortex-m 内核芯片裸机开发
    • MPU
  • 2-2.中等规模系统开发
    • LVGL 图形库
    • 裸机开发的软件框架
    • 基于 rtos 开发
  • 2-3.armv7-m架构与 rtos 原理
    • armv7-m 架构
    • rt-thread 内核实现
    • rt-thread 驱动开发
  • 3-1.linux 应用开发基础
  • 3-2.linux 镜像构建
    • uboot 使用
    • uboot 适配
    • uboot 启动分析
    • uboot 自定义命令
    • linux 内核适配
    • linux 内核启动分析
    • busybox 根文件系统构建
  • 3-3.linux 驱动开发
    • 驱动开发基础
    • sysfs
    • led 驱动
    • 设备树
    • pinctrl 和 gpio 子系统
    • 并发控制
由 GitBook 提供支持
在本页
  • 可执行文件生成过程
  • 链接器
  • 链接的本质
  • 可执行文件存储
  • 目标文件
  • 可重定位文件

这有帮助吗?

  1. 1-2.计算机系统基础(CSAPP)

10.程序的链接

可执行文件生成过程

#include <stdio.h>

int main()
{
    printf("hello world\n");
}

程序的功能是在屏幕上输出 hello world。

  • 文本编辑器输入程序,hello.c ,用 ASCII 表示的文本文件

  • 预处理,(cpp) hello.c 处理为 hello.i 用 ASCII 表示的文本文件

  • 编译,(cc1) hello.i 处理为 hello.s 汇编语言程序文件,这还是用 ascii 表示的文本

  • 汇编,(as) hello.c 处理为 hello.o 可重定位目标程序,二进制文件,一条一条机器指令,计算机直接是被执行

  • 源程序调用了 printf ,需要用 ld 链接,把不同模块拼接起来

Linux 下使用的工具

  • 预处理过程,处理预编译指令,

    • gcc -E hello.c -o hello.i

    • cpp hello.c > hello.i

    • 删除 #define 并展开对应的宏

    • 处理条件预编译指令

    • 插入头文件到 #include ,递归方式处理

    • 删除注释

    • 添加行号和文件摸标识,供调试用

    • 保留编译指令,#pragma 后面编译器用

  • 编译过程,词法分析,语法分析,语义分析,生成汇编代码文件。此程序称为编译器,compiler

    • gcc -S hello.i -o hello.s

    • gcc -S hello.c -o hello.s 两步合一步

    • /usr/lib/gcc/***/cc1 hello.c

    • 编译后,得到汇编代码文件(汇编语言源程序),人可读的 ascii 文件,CPU 不可读

  • 汇编过程,汇编器把汇编语言源程序转换为机器指令序列(机器语言程序)。汇编代码源程序和机器语言程序都是机器级代码,一一对应,

    • gcc -c hello.s -o hello.o

    • gcc -c hello.c -o hello.o 三步合一

    • as hello.s -o hello.o

    • 得到的为可重定位目标文件,

  • 链接,前面步骤针对的是一个模块(一个 .c 文件),得到一个目标文件,链接将多个 .o 合并,生成可执行文件

    • gcc -static -o myproc main.o test.o

    • ld -static -o myproc main.o test.o

    • 不指定输出文件名,默认为 a.out

gcc 是上面的 cpp cc1 as 工具的集合。

链接器

最早的时候,穿孔纸袋编程。存在跳转地址,但是只有修改了就比较麻烦。后来出现了汇编,用符号表示跳转位置,而非绝对地址,即汇编程序中做的事,程序确定后,手工汇编。链接时,先确定标号的地址,然后把地址填入最终的二进制文件中。

高级语言出现后,多人开发,子程序的地址,实际上函数名就是符号。函数名、变量名就是符号的定义引用。本质上和汇编中的符号没区别。最终编译,在链接之前,调用时跳转的地址是无法确定的。链接时,才能确定最终的地址

链接操作的步骤

  • 符号解析:确定符号的引用,互相之间的调用关系

  • 合并模块,把代码和数据都合到一起,放到同一个地址空间,虚拟地址空间,

  • 确定符号地址,合并不同模块到一个地址空间以后,代码放一起,数据放一起,相对位置救恩确定了

  • 指令中填入新地址。

链接的好处:

  • 模块化,

    • 一个程序可以分模块

    • 公共库,如数学、C标准库

  • 效率高

    • 时间上,可以分开编译,只编译修改过的模块

    • 空间上,源文件无需包含共享库的源码,

链接的本质

例程

// main.c
int buf[2] = {1,2};

void swap();

int main()
{
    swap();
    return 0;
}
// swap.c
extern int buf[];

int *bufp0 = &buf[0];
static int *bufp1;

void swap()
{
    int temp;

    bufp1 = &buf[1];
    temp = *bufp0;
    *bufp0 = *bufp1;
    *bufp1 = temp;
}

每个模块有自己的代码数据。初始化的全局变量,未初始化的全局变量,静态变量,局部变量。

局部变量分配在栈中,不能在过程(函数)外被引用,因此不是符号定义。

  • gcc -O2 -g -o p main.c swap.c

两个源码分别转换生成 main.o 和 swap.o ,生成可重定位的二进制文件,此时这里面的符号还没有值,地址没有值。

链接过程的本质 : 合并相同的节

上面代码里,每个模块有的节 .text .data .bss

可执行文件存储

可执行文件里有一段,程序头表,程序头表描述了如何映射,即链接时,程序不知道是去哪里执行的,代码段会装到内存里去执行,具体装载的位置还不知道。链接的时候把可执行文件合并到了虚拟地址空间里,

在程序运行时的空间,

目标文件

目标文件指包含目标代码(机器语言代码)的文件。早期非标准,几种标准的格式

  • DOS 里 .com ,

  • linux 里 elf 格式

三类目标文件

  • 可重定位目标文件。代码和数据可以和其他可重定位文件合并为可执行文件

    • 每个 .o 由对应的 .c 生成

    • 每个 .o 文件代码和数据地址从 0 开始

  • 可执行目标文件

    • linux 默认为 a.out

    • windows 为 .exe

    • 包含的代码和数据可以直接被复制到内存执行

    • 代码和数据地址为虚拟地址空间中的地址

  • 共享的目标文件 (linux中的 *.so)

    • 特殊的可重定位目标文件,在运行时装入内存并自动链接,共享库文件

    • windows 中为 *.dll

可重定位文件和可执行文件都是 ELF 格式的文件,可以使用 file 指令查看。

ELF 文件的两种视图

  • 链接视图(被链接):可重定位目标文件

  • 执行视图(被执行):可执行目标文件

从链接视图来看,ELF 文件由节构成,节是 ELF 中由相同特征的最小处理单位

  • .text

  • .data

  • .rodata

  • .bss

从执行视图来看,ELF 文件由段组成,程序头表反映了不同节和段的关系。节到段的映射。比如 .data 节和 .bss 节映射到一个可读可写段中。

可重定位文件

.c 经过编译汇编生成 .o ,这个文件里有不同的节

  • ELF 头,

    • 包含16byte标识信息,

    • 文件l类型 (.o ,so)

    • 机器类型 (IA-32)

    • 节头表的偏移、节头表表项大小和个数

  • .text 节

    • 编译之后的代码部分

  • .rodata 节

    • 只读数据,如printf的格式串,switch case 的跳转表

  • .data 节

    • 已初始化的全部变量

  • .bss 节

    • 未初始化的全局变量

.bss 节仅是占位符,文件中不占用磁盘空间,仅在节头表里说有这么个东西。节头表指出长度。区分初始化和未初始化是为了空间效率。

上一页7.过程调用下一页1-3.数字电路、计算机组成

最后更新于10个月前

这有帮助吗?