天天看點

《程式員的自我修養-連結加載和庫》讀書筆記

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