連結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描述了一套規則和條件。