《程序员的自我修养-链接加载和库》读书笔记

链接LINK 事实上属于非常基础的知识,但是真正理解的人并不多,包括目前的我。

共分为4章:

第一部分简介:

1.2 万变不离其宗

CPU 、内存、I/O 控制芯片 重要。I/O设备指的是显示设备、键盘、软盘、磁盘等。早期,CPU的核心频率和内存一样,都是通过IO控制芯片连接在同一个总线 BUS上的。这是因为早期的I/O设备速度慢,比如仅仅是字符终端。

后来,由于CPU频率提升,为协调CPU、内存、高速图形设备的关系,设计了高速北桥芯片。如果那些低速的设备如磁盘USB键盘也直接放到北桥,那么北桥就会相当复杂,于是专门设计了南桥用了放低速设备。20世纪90年代,系统总线是PCI,低速是ISA总线,

1.4.2 设备的驱动

在OS成熟以前,程序员需要同硬件打交道,比如A型号的显卡和B型号的显卡的接口完全不同。OS成熟以后,硬件被抽象成一系列概念。

1.5 & 1.6 很基础且重要,不过暂且先不看。

第二部分 编译和链接:

通常 编译+链接 = Build,事实上,分为预处理、编译、汇编、链接 4步。

2.1.1 预编译

根据文章内容,本博主写个例子:

main.c



#include "main.h"
int global = 2;

#if  0
void unusefulFun(void)
{
	static int jjjjj = 12;
	jjjjj++;
}
#endif

//after gcc 4.4
#pragma GCC push_options
#pragma GCC optimize ("O0")
static void hello_2(void)
{
	global++;
}
#pragma GCC pop_options


static  void hello(void)
{
	global += NIHAO;
	hello_2();
}

int main(void)
{	
	hello();
}

main.h

void hello(void);


#define NIHAO   100

然后 用$ arm-none-eabi-gcc -E main.c -o main.i

得到main.i,如下:

# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "main.c"


# 1 "main.h" 1


void hello(void);
# 4 "main.c" 2
int global = 2;
# 15 "main.c"
#pragma GCC push_options
#pragma GCC optimize ("O0")
static void hello_2(void)
{
 global++;
}
#pragma GCC pop_options


static void hello(void)
{
 global += 100;
 hello_2();
}

int main(void)
{
 hello();
}

关于生成的行号,文章这么写的“添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号”。

2.1.2 编译

编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件,这个过程往往是我们所说的整个程序构建的核心部分,也是最复杂的部分之一。

 

然后:

$ arm-none-eabi-gcc -S main.i -o main.s
main.c:24:13: error: static declaration of 'hello' follows non-static declaration
   24 | static  void hello(void)
      |             ^~~~~
In file included from main.c:3:
main.h:3:6: note: previous declaration of 'hello' was here
    3 | void hello(void);
      |      ^~~~~

可见代码有个bug。就是函数有static,但是这个staic的函数被放到了.h里面。

于是修改代码把static  void hello(void)  改为void hello(void)

于是  arm-none-eabi-gcc -E main.c -o main.i 然后arm-none-eabi-gcc -S main.i -o main.s (当然也可以这两步合为一句 arm-none-eabi-gcc -S main.c -o main.s,这样就直接生成main.s, 中间的main.i看不到了 )

得到main.s 如下:

	.cpu arm7tdmi
	.eabi_attribute 20, 1
	.eabi_attribute 21, 1
	.eabi_attribute 23, 3
	.eabi_attribute 24, 1
	.eabi_attribute 25, 1
	.eabi_attribute 26, 1
	.eabi_attribute 30, 6
	.eabi_attribute 34, 0
	.eabi_attribute 18, 4
	.file	"main.c"
	.text
	.global	global
	.data
	.align	2
	.type	global, %object
	.size	global, 4
global:
	.word	2
	.text
	.align	2
	.arch armv4t
	.syntax unified
	.arm
	.fpu softvfp
	.type	hello_2, %function
hello_2:
	@ Function supports interworking.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 1, uses_anonymous_args = 0
	@ link register save eliminated.
	str	fp, [sp, #-4]!
	add	fp, sp, #0
	ldr	r3, .L2
	ldr	r3, [r3]
	add	r3, r3, #1
	ldr	r2, .L2
	str	r3, [r2]
	nop
	add	sp, fp, #0
	@ sp needed
	ldr	fp, [sp], #4
	bx	lr
.L3:
	.align	2
.L2:
	.word	global
	.size	hello_2, .-hello_2
	.align	2
	.global	hello
	.syntax unified
	.arm
	.fpu softvfp
	.type	hello, %function
hello:
	@ Function supports interworking.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 1, uses_anonymous_args = 0
	push	{fp, lr}
	add	fp, sp, #4
	ldr	r3, .L5
	ldr	r3, [r3]
	add	r3, r3, #100
	ldr	r2, .L5
	str	r3, [r2]
	bl	hello_2
	nop
	sub	sp, fp, #4
	@ sp needed
	pop	{fp, lr}
	bx	lr
.L6:
	.align	2
.L5:
	.word	global
	.size	hello, .-hello
	.align	2
	.global	main
	.syntax unified
	.arm
	.fpu softvfp
	.type	main, %function
main:
	@ Function supports interworking.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 1, uses_anonymous_args = 0
	push	{fp, lr}
	add	fp, sp, #4
	bl	hello
	mov	r3, #0
	mov	r0, r3
	sub	sp, fp, #4
	@ sp needed
	pop	{fp, lr}
	bx	lr
	.size	main, .-main
	.ident	"GCC: (GNU Arm Embedded Toolchain 9-2020-q2-update) 9.3.1 20200408 (release)"

2.1.3  汇编

汇编器是把汇编代码转变成机器可以执行的指令,汇编相对比较简单。

然后arm-none-eabi-gcc -c main.s -o main.o,得到main.o

用ultraEdit打开得到:

2.1.4 链接:

链接通过是一个让人比较费解的过程。也是本书的主要内容。
2.2 编译器做了什么(词法分析-语法分析-语义分析-中间语言生成-目标代码生成与优化)

2.3 链接器的年龄比编译器长

博主总结:汇编的程序发生变化,就需要低级的繁琐的调整相关地址。现代软件开发规模庞大,模块化开发更利于阅读、理解、重用代码,因此这些模块之间最后组合成单一的程序。这些模块拼接的过程就是本书的一个主题:链接。

2.4 模块拼装---静态链接

链接的过程就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确的衔接。链接过程主要包括了地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定位(Relocation)等这些步骤。

最基本的静态连接过程为,将米格模块的源代码文件(如.C)文件经过编译器编译成目标文件(.Obj),目标文件和库(Library)一起链接形成最终可执行文件。

第三章 目标文件里有什么

目标文件是源代码编译后但未进行链接的那些中间文件(Windows的obj和Linux的.o)

3.1 目标文件的格式

PC平台流行的可执行文件格式主要是windows下的PE以及linux的ELF(Executable Linkable Format)。目标文件(.obj .o)跟可执行文件的内容与结构很相似。。广义上看,目标文件和可执行文件可以看成是一种类型的文件。

3.2 目标文件是什么样的

目标文件里的内容除了编译后的机器指令代码、数据外,还有链接时需要的一些信息,如符号表、调试信息、字符串等。一般目标文件将这些信息按不同的属性,以“节(Section)”的形式存储,有时候也叫做“段”(Segment)。

 

代码段属于程序指令,数据段和.BSS属于程序数据。为什么要把程序指令和程序数据分开存放?

好处有:

(1)数据可以读写,而程序只读。放在不同区域可以防止程序的指令被有意或者无意改变。

(2)由于现代的缓存Cache被设计为数据缓存和指令缓存分离,所以程序指令和数据分开存放有利于CPU的缓存命中率。

(3)最重要的原因:当多个程序副本指令一样时.....

3.3 挖掘SimpleSection.o

真正了不起的程序员堆自己的程序的每一个字节都了如指掌。

$ arm-none-eabi-objdump -h main.o

main.o:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000084  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000004  00000000  00000000  000000b8  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  000000bc  2**0
                  ALLOC
  3 .comment      0000004d  00000000  00000000  000000bc  2**0
                  CONTENTS, READONLY
  4 .ARM.attributes 0000002a  00000000  00000000  00000109  2**0
                  CONTENTS, READONLY
$ arm-none-eabi-objdump -x main.o

main.o:     file format elf32-littlearm
main.o
architecture: armv4t, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x00000000
private flags = 5000000: [Version5 EABI]

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000084  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000004  00000000  00000000  000000b8  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  000000bc  2**0
                  ALLOC
  3 .comment      0000004d  00000000  00000000  000000bc  2**0
                  CONTENTS, READONLY
  4 .ARM.attributes 0000002a  00000000  00000000  00000109  2**0
                  CONTENTS, READONLY
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
00000000 l     F .text  00000030 hello_2
00000000 l    d  .comment       00000000 .comment
00000000 l    d  .ARM.attributes        00000000 .ARM.attributes
00000000 g     O .data  00000004 global
00000030 g     F .text  00000034 hello
00000064 g     F .text  00000020 main


RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE
00000028 R_ARM_V4BX        *ABS*
0000002c R_ARM_ABS32       global
0000005c R_ARM_V4BX        *ABS*
00000060 R_ARM_ABS32       global
0000006c R_ARM_CALL        hello
00000080 R_ARM_V4BX        *ABS*
$ arm-none-eabi-size main.o
   text    data     bss     dec     hex filename
    132       4       0     136      88 main.o

3.3.1

$ arm-none-eabi-objdump -s -d main.o

main.o:     file format elf32-littlearm

Contents of section .text:
 0000 04b02de5 00b08de2 1c309fe5 003093e5  ..-......0...0..
 0010 013083e2 10209fe5 003082e5 0000a0e1  .0... ...0......
 0020 00d08be2 04b09de4 1eff2fe1 00000000  ........../.....
 0030 00482de9 04b08de2 20309fe5 003093e5  .H-..... 0...0..
 0040 643083e2 14209fe5 003082e5 ebffffeb  d0... ...0......
 0050 0000a0e1 04d04be2 0048bde8 1eff2fe1  ......K..H..../.
 0060 00000000 00482de9 04b08de2 feffffeb  .....H-.........
 0070 0030a0e3 0300a0e1 04d04be2 0048bde8  .0........K..H..
 0080 1eff2fe1                             ../.
Contents of section .data:
 0000 02000000                             ....
Contents of section .comment:
 0000 00474343 3a202847 4e552041 726d2045  .GCC: (GNU Arm E
 0010 6d626564 64656420 546f6f6c 63686169  mbedded Toolchai
 0020 6e20392d 32303230 2d71322d 75706461  n 9-2020-q2-upda
 0030 74652920 392e332e 31203230 32303034  te) 9.3.1 202004
 0040 30382028 72656c65 61736529 00        08 (release).
Contents of section .ARM.attributes:
 0000 41290000 00616561 62690001 1f000000  A)...aeabi......
 0010 05345400 06020801 09011204 14011501  .4T.............
 0020 17031801 19011a01 1e06               ..........

Disassembly of section .text:

00000000 <hello_2>:
   0:   e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
   4:   e28db000        add     fp, sp, #0
   8:   e59f301c        ldr     r3, [pc, #28]   ; 2c <hello_2+0x2c>
   c:   e5933000        ldr     r3, [r3]
  10:   e2833001        add     r3, r3, #1
  14:   e59f2010        ldr     r2, [pc, #16]   ; 2c <hello_2+0x2c>
  18:   e5823000        str     r3, [r2]
  1c:   e1a00000        nop                     ; (mov r0, r0)
  20:   e28bd000        add     sp, fp, #0
  24:   e49db004        pop     {fp}            ; (ldr fp, [sp], #4)
  28:   e12fff1e        bx      lr
  2c:   00000000        .word   0x00000000

00000030 <hello>:
  30:   e92d4800        push    {fp, lr}
  34:   e28db004        add     fp, sp, #4
  38:   e59f3020        ldr     r3, [pc, #32]   ; 60 <hello+0x30>
  3c:   e5933000        ldr     r3, [r3]
  40:   e2833064        add     r3, r3, #100    ; 0x64
  44:   e59f2014        ldr     r2, [pc, #20]   ; 60 <hello+0x30>
  48:   e5823000        str     r3, [r2]
  4c:   ebffffeb        bl      0 <hello_2>
  50:   e1a00000        nop                     ; (mov r0, r0)
  54:   e24bd004        sub     sp, fp, #4
  58:   e8bd4800        pop     {fp, lr}
  5c:   e12fff1e        bx      lr
  60:   00000000        .word   0x00000000

00000064 <main>:
  64:   e92d4800        push    {fp, lr}
  68:   e28db004        add     fp, sp, #4
  6c:   ebfffffe        bl      30 <hello>
  70:   e3a03000        mov     r3, #0
  74:   e1a00003        mov     r0, r3
  78:   e24bd004        sub     sp, fp, #4
  7c:   e8bd4800        pop     {fp, lr}
  80:   e12fff1e        bx      lr

 

4.5静态库链接

查看.a 里面有什么?

找到安装目录,arm-none-eabi-ar -t libc.a

4.6.1 链接控制脚本

arm-none-eabi-ld -verbose   查看默认链接脚本

4.6.3 使用ld链接脚本

目标文件、库文件就是输入,链接结果输出的可执行文件就是输出。

把输入文件中的段称为输入段,输出文件中的段称为输出段。简单来讲,控制链接的过程无非是控制输入段如何变成输出段,比如哪些输入段要合并一个输出段,哪些输入段要丢弃;指定输出段的名字、装载地址、属性等等。

第一句 意思是设置当前的虚拟地址。就是tinytext的起始虚拟地址是0x0804800 + SIZEOF_HEADERS。

第二句是段转换规则,就是将所有输入文件的名字为".text"、“.data”、“.rodata”的段依次合并到输出文件的tinytext。

 

4.6.4 ld链接脚本的语法

链接脚本语句分两种,一种是命令语句,一种是赋值语句。

(1)赋值语句必须以作为分隔符,命令语句可以用换行作为分隔符

(2)表达式、运算符  同C语言一样

(3)/**/是注释,包含分号的,应该用双引号引用起来。

secname是输出段的段名,secname后面必须有一个空格符,这样使得输出段名不会有歧义。contents描述了一套规则和条件。

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
链接器和加载器殿堂级经典书籍 不管你的编程语言是什么,不管你的平台是什么,你很可能总是会涉及链接器和加载器的功能。但是你知道如何最大限度地利用它们吗?只有现在,随着《链接器和加载器》的出版,总算有一本深入完整地彻底揭示编译时和运行时过程的权威著作了。 《链接器和加载器》首先通过实例深入浅出地阐述了在不同的编译器和操作系统中链接加载过程的差异。在这个基础上,作者提出了清晰实用的忠告,来帮助你创建更快、更清晰的代码。你将会学习如可规避和Windows DLL相关的陌阱,充分利用UNX ELF模式等。如果你对程序设计抱有非常认真的态度,那么你可以通过这本书充分地理解这个领域内最难惶的主题之一。《链接器和加载器》对于编译器和操作系统课程同样也是一本理想的补充读物, 本书特性 覆盖了 windows,UNIX,LInux Beos和其它操作系统的动态链接过程。 解释了Java链接模式,以及它是如何应用在网络小应用程序和可扩展Java代码中的。 帮助你编写更优雅、更高效的代码,以及构建能够被更加高效地编译、加载和运行的应用程序。 包含了一个用Perl构建链接器的练习项目,项目文件可以从网络下载得到。 John R. Levine是很多书籍的作者或合作者,包括Lex&Yacc;(O'Reilly), Programming for Graphics Files in C and C++(Wiley),以及The Internet for Dummies (IDG),他还是Journal ofcLanguage Translation的荣誉退休发行人、comp.compilers新闻组的长期仲裁人员,以及某个最早的商用Fortran 77编译器的创建者。他在耶鲁大学获得了计算机科学的博士学位。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值