3.1 as86
彙編器
linux 0.1x
系統中使用了兩種彙編器(Assembler)
。一種是能產生16
位代碼的as86
彙編器,配套ld86
鏈接器;另一種是GNU
的彙編器gas(as)
,使用GNU ld
鏈接器。
編譯器和鏈接器的源代碼可以從FTP
服務器ftp.funet.fi
上或從網站www.oldlinux.org
下載。
3.1.1 as86
彙編語言語法
彙編器專門用來把低級語言程序編譯成含機器碼的二進制程序或目標文件。
as [options] -o objfile srcfile
3.1.2 as86
彙編語言程序
!
! boot.s -- bootsect.S frame-work.Usign code 0x07 replace 1 charater of string msg1 , and display on line one of screen..
!
.globl begtext, begdata, begbss, endtext, enddata, endbss !globl identifiers for ld86 link.
.text !body
begtext:
.data
begdata:
.bss !uninitialized data
begbss:
.text !body
BOOTSEG = 0x07c0 !BIOS loading original address of bootsect.
entry start !Notice program start from here.
start:
jmpi go, BOOTSEG !Segment jump.
go:
mov ax, cs
mov ds, ax
mov es, ax
mov [msg1+17], ah
mov cx, #20
mov dx, #0x1004 !line 17 & column 4 of screen
mov bx, #0x000c !red
mov bp, #msg1 !located the display string
mov ax, #0x1301 !write string and move cusor to the end.
int 0x10 !BIOS interrupt 0x10, function is 0x13, child fuction 01.
loop0:
jmp loop0
msg1: .ascii "Loading system ..."
.byte 13,10
.org 510 !It is mean start store follow statement from 510(0x1fe)
.word 0xaa55
.text
endtext:
.data
enddata:
.bss
endbss:
該程序是一個簡單的引導扇區啓動程序。編譯鏈接產生的執行程序可以放入軟盤第一個扇區直接用來引導計算機啓動。啓動後會在屏幕第17
行第5
列處顯示紅色字符串'Loading system ......
,並且光標下移一行。然後在第27
行死循環。
以感嘆號’!
或者分號‘:’
開始的語句均爲註釋文字。
‘.globl'
是彙編指示符(或成爲彙編僞指令、僞操作符)。彙編指示符均以一個字符’.'
開始,並且不會在編譯時產生任何代碼。
第14
行上的標識符entry
是保留關鍵字,用於迫使鏈接器ld86
在生成的可執行文件中包括進其後指定的標號start
。
第16
行上是一個段間(Inter-segment)
遠跳轉語句,就跳轉到下一條指令。
3.1.3 as86
彙編語音程序的編譯和鏈接
[/root]# as86 -0 -a -o boot.o boot.s //編譯。生成與 as 部分兼容的目標文件。
[/root]# ld86 -0 -a -o boot.o boot.s //鏈接。去掉符號信息。
[/root]# dd bs=32 if=boot of=/dev/fd0 skip=1 //寫入軟盤或Image 盤文件中
3.1.4 as86
&ld86
的使用方法和選項
3.2 GNU as
彙編
上節介紹的as86
彙編器僅用於編譯內核中的boot/bootsect.S
引導扇區程序和實模式下的設置程序boot/setup.s
。內核中其餘所有彙編程序(包括C
語言產生的彙編程序)均使用gas
來編譯。
3.2.1 編譯as
彙編語言程序
as [ option ] [ -o objfile ] [ srcfile.s ... ]
3.2.2 as
彙編語法
as
彙編器使用AT&T
系統V
的彙編語法(以下簡稱AT&T
語法)。
1.彙編程序預處理
as
彙編器具有對彙編語言程序的簡單預處理功能。
2.符號、語句和常數
符號(Symbol
)是由字符組成的標識符,組成符號的有效字符取自大小寫字符集、數字和3
字符“-”。“.”、“$”
。
語句(Statement
)以換行符或者行分割字符“;
”作爲結束。
若在一行的最後使用反斜槓字符"\"
(在換行符前),可以使語句使用多行。
語句由零個或多個標號(label
)開始,後面可以跟隨一個確定語句類型的關鍵符號。
表3-1
as
彙編器支持的轉義字符序列
轉義碼 | 說明 |
---|---|
\b |
退格符(Backspace ),值爲0x08 |
\f |
換頁符(FormFeed) , 值爲0x0C |
\n |
換行符(NewLine) ,值爲0x0A |
\r |
回車符(Carriage-Return) ,值爲0x0D |
\NNN |
3 個八進制表示的字符代碼 |
\xNN... |
16 進制數表示的字符代碼 |
\\ |
反斜槓字符 |
\" |
表示雙引號 |
3.2.3 指令語句、操作數和尋址
指令(Instructions)
是CPU
執行的操作,通常也稱操作碼(Opcode)
。
操作數(Operand)
是指令操作的對象。
地址(Address)
是指定數據在內存中的位置。
指令語句運行時通常由4
部分:標號,操作碼,操作數,註釋。
操作數可以是立即數、寄存器值、內存值。一個間接操作數(Indirect Operand)
含有實際操作數值的地址值。
#1) 立即操作數前需要加一個“$"
字符前綴。
#2) 寄存器名前需要加一個”%“
字符前綴。
#3) 內存操作數由變量名或者含有變量地址的一個騎車去指定。變量名隱含指出了變量的地址,並指示CPU
引用該地址處內存的內容。
1.指令操作碼的命名
AT&T
語法中指令操作碼名稱(指令助記符)最後一個字符用來指明操作數的寬度。
AT&T
與Intel
語法中幾乎所有指令操作碼的名稱都相同,只有幾個例外。例如,使用符號擴展從%al
移動到%edx
的AT&T
語句是”movsbl %al, %edx"
,即從byte
到long
是bl
,其他類似。
表3-2``AT&T
語法與Intel語法中轉換指令的對應關係
AT&T |
Intel |
說明 |
---|---|---|
cbtw |
cbw |
把%al 中的字節值符號擴展到%ax 中 |
cwtl |
cwde |
把%ax 中的字節值符號擴展到%eax 中 |
cwtd |
cwd |
把%ax 中的字節值符號擴展到%dx:%ax 中 |
cltd |
cdq |
把%eax 中的字節值符號擴展到%edx:%eax 中 |
2.指令操作碼前綴
操作碼前綴用於修飾隨後的操作碼。
例如,串掃描指令scas
使用前綴執行重複操作:
repne scas %es:(%edi), %al
表3-3
操作碼前綴列表
操作碼前綴 | 說明 |
---|---|
cs,ds,ss,es,fs,gs |
區覆蓋操作碼前綴。通過指定使用 區:內存操作數 內存引用形式會自動添加這種前綴 |
data16,addr16 |
操作數\地址寬度前綴。這兩個前綴會把32 位操作數\地址改變爲16 位的操作數\地址。注意,as 不支持16 位尋址方式。 |
lock |
總線鎖存前綴。用於在指令執行期間禁止中斷(僅對某些指令有效,參見80x86 手冊)。 |
wait |
協處理器指令前綴。等待協處理器完成當前指令的執行。對於80386/80387 組合用不着這個前綴 |
rep,repe,repne |
串指令操作前綴。使串指令重複執行%ecx 中指定的次數 |
3.內存引用
Intel
語法的間接內存引用形式:
section:[base + index*scale + disp]
AT&T
語法形式:
section:disp(base, index, scale)
At&T
引用例子:
movl var, %eax # 把內存地址`var`處的內容放入寄存器`%eax`中。
movl %cs:var, %eax # 把代碼段中內存地址 var 處的內容放入 %eax 中。
movb $0x0a, %es:(%ebx) # 把字節值 0x0a 保存到 es 段的 %ebx 指定的偏移處。
movl $var, %eax # 把 var 的地址放入 %eax 中。
movl array(%esi), %eax # 把 array+%esi 確定的內存地址處的內容放入 %eax 中。
movl (%ebx, %esi, 4), %eax # 把 %ebx+%esi*4 確定的內存地址處的內容放入 %eax 中。
movl array(%ebx, %esi, 4), %eax # 把 array+%ebx+%esi*4 確定的內存地址處的內容放入 %eax 中。
movl -4(%ebp), %eax # 把 %ebp -4 內存地址處 的內容放入 %eax 中,默認段 %ss 。
movl foo(, %eax, 4), %eax # 把內存地址 foo + %eax * 4 處內容放入 %eax 中, 默認段 %ds 。
4.跳轉指令
跳轉指令用於把執行行點轉移到程序另一個位置處繼續執行下去。
jmp NewLoc # 直接跳轉。無條件直接跳轉到標號 NewLoc 處繼續執行。
jmp *%eax # 間接跳轉。寄存器 %eax 的值是跳轉的目標位置。
jmp *(%eax) # 間接跳轉。從 %eax 指明的地址處讀取跳轉的目標位置。
3.2.4 區與重定位
區(Section)
(也稱爲段、節或部分)用於表示一個地址範圍,操作系統講會以相同的方式對待和出來在該地址範圍中的數據信息。
鏈接器ld
會把輸入的目標文件中的內容按照一定規律組合生成一個可執行程序。
爲區 分配運行時刻的地址的操作數就被稱作重定位(Relocation)操作。
as
彙編器輸出產生的目標文件中至少具有3
個區,正文(.text)
、數據(.data)
和區(.bss)
。
爲了執行重定位操作,在每次涉及目標文件中的一個地址時,ld
必須知道:
#1) 目標文件中對一個地址的引用是從什麼地方算起的?
#2) 該引用的字節長度是多少?
#3) 該地址引用的是哪一個區?(地址)- (區的開始地址)的值等於多少?
#4) 對地址的引用與指令計數器PC(Programing Counter)
相關麼?
1.鏈接器涉及的區
2.子區
彙編取得的字節數據通常位於 text
和data
區中。as
彙編器允許利用子區(Subsection)
來將某個區中可能分佈着一些不相鄰的數據組在彙編後聚集在一起存放。
使用子區是可選的。如果不使用子區,那麼所有對象都會被放在子區0
中。每個區都有一個位置計數器(Location Counter)
,它會對每個彙編進該區的字節進行計數。
3.bss
區
bss
區用於存儲局部公共變量。
3.2.5 符號
標號(Label)
是後面緊隨一個冒號的符號。
符號名以一個字母或"."、"_"
字符之一開始。
1.特殊點符號
特殊符號"."
表示as
彙編的當前地址。因此表達式"mylab:.long ."
就會把mylab
定義爲包含它自己所處的地址值。給"."
賦值就如同彙編命令".org"
的作用。因此表達式".=.+4"
與“。space 4"
完全相同。
2.符號屬性
除了名字以外,每個符號都有”值“和”類型“屬性。根據輸出格式不同,符號也可以具有輔助屬性。如果不定義就使用一個符號,as
就會假設其所有均爲0
。這指示該符號是一個外部定義的符號。
符號通常是32位的。ld
會對未定義符號的值進行特殊處理。符號的類型屬性含有用於鏈接器和調試器的重定位信息、指示符號是外部的表示以及一些其他可選的信息。
3.2.6 as
彙編命令
彙編命令是指示彙編器操作方式的僞指令。
1. .align abs-expr1, abs-expr2, abs-expr3
.align
是存儲對齊彙編命令,用於在當前子區中把位置計數器值設置(增加)到下一個指定存儲邊界處。
2. .ascii “string”…
從位置計數器所指當前位置爲字符串分配空間並存儲字符串,可使用逗號分開寫出多個字符串。
3. .asciz “string”…
該彙編命令與".ascii"
類似,但是每個字符串後面會自動添加NULL
字符。
4. .byte expressions
該彙編命令定義0
個或多個用逗號分開的字節值。每個表達式的值是1
個字節。
5. .comm symbol, length
在.bss
區中聲明一個命名的公共區域。
6. .data subsection
該彙編命令通知as
把隨後的語句彙編到編號爲subsection
的data
子區中。
7. .desc symbol, abs-expr
用絕對表達式的值設置符號symbol
的描述符字段n_desc
的16
位值。
8. .fill repeat,size,value
該彙編命令會產生數個(repeat
個)大小爲size
字節的重複拷貝。
9. .global symbol
該彙編命令會使得鏈接器ld
能看見符號symbol
。
10. .int expressions(.long exoressions)
該彙編命令在某個區中設置0
個或多個整數值(8038系統爲4B
,同 .long
)。每個用逗號分開的表達式的值就是運行時刻的值。
11. .lcomm symbol, length
爲符號symbol
指定的局部公共區域保留長度爲length
字節的空間。
12. .octa bignums
指定0
個或多個用逗號分開的16B
大數(.byte, .word, .long, .quad, .octa 分別對應1\2\4\8\16字節數)
。
13. .org new_lc, fill
把當前區的位置計數器設置爲值 new_lc
。當位置計數器值增長時,所跳躍過的字節將被填入值fill
。
14. .quad bignums
指定0
個或多個用逗號分開的8B
大數bignums
。
15. .short expressions (.word expressions)
指定0
個或多個用逗號分開的2
字節數。
16. .space size, fill
產生size
個字節,每個字節填值fill
。
17. .string “string”
定義一個或多個用逗號分開的字符串。
18. .text subsection
通知as
把隨後的語句彙編進編號爲subsection
的子區中。
3.2.7 編寫16
位代碼
as
不區分16
位和32
位彙編語句,取決於.code16
還是.code32
。
3.2.8 AS
彙編器命令行選項
-a
:開啓程序列表
-f
:快速操作
-o
:指定輸出的目標文件名。
-R
:組合數據區和代碼區。
-W
:取消警告信息。
3.3 C
語言程序
3.3.1 C
程序編譯和鏈接
3.3.2 嵌入式彙編
基本格式:
asm("彙編語句"
:輸出寄存器
:輸入寄存器
:會被修改的寄存器);
除第一行以外,後面帶冒號的行若不適用就都可以省略。
asm是內聯彙編語句關鍵詞;
”彙編語句“寫彙編指令的地方;
”輸出寄存器“表示當這段嵌入式彙編執行完之後,哪些寄存器用於存放輸出數據(對應C
語言表達式值或一個內存地址)。
”輸入寄存器“表示在開始執行彙編代碼時,這裏指向的一些寄存器中應存放的輸入值。
”會被修改的寄存器“表示你已對其中列出的寄存器中的值進行了改動,gcc
編譯器不能再依賴與它原先對這些寄存器加載的值。
e.g.
kernel/traps.c
# define get_seg_byte(seg, addr) \ //宏函數名稱
({ \
register char _res; \ // 定義了一個寄存器變量 _res 。
_asm_("push %%fs; \ // 首先保存 fs 寄存器原值(段選擇符)。
mov %%ax, %%fs; \ //然後用 seg 設置 fs 。
movb %%fs:%2, %%al; \ //取 seg:addr 處 1 字節內容到 al 寄存器中。
pop %%fs" \ //恢復 fs 寄存器原值。
:"=a" (_res) \ //輸出寄存器列表。
:"0" (seg),"m" (*(addr))); \ //輸入寄存器列表。
_res;})
此代碼定義了一個嵌入式彙編語言宏函數。通常使用匯編語句最方便的方式是把它們放在一個宏內。
爲了讓 GCC 編譯產生的彙編語言程序中寄存器前有一個百分號”%“,在嵌入式彙編語句寄存器名稱前就必須寫上兩個百分號”%%“。
”=a"
中的a
稱爲加載代碼,“=”
表示輸出寄存器,並且其中的值講被輸出替代。
加載代碼是CPU
寄存器、內存地址以及一些數值的簡寫字母代號。
第9
行表示在這段代碼開始時將seg
放到eax
寄存器中,“0”
表示使用了與上面相同位置上的輸出寄存器。
(*(addr))
表示一個內存偏移地址值。爲了在上面彙編語句中使用該地址,嵌入式彙編程序規定把輸出和輸入寄存器統一按順序編號,順序是從輸出寄存器序列從左到右從上到下以"%0"
開始,分別記爲%0、%1、...%9
,。因此,輸出寄存器的編號是%0
(這裏只有一個輸出寄存器),輸入寄存器前一部分(“0“(seg))
的編號是%1
,而後部分的編號是%2
。上面地6
行上的%2
即代表(*(addr))
這個內存偏移量。
表3-4
常用寄存器加載代碼說明
代碼 | 說明 | 代碼 | 說明 |
---|---|---|---|
a |
使用寄存器eax |
m |
使用內存地址 |
b |
使用寄存器ebx |
o |
使用內存地址並可以加載偏移量 |
c |
使用寄存器ecx |
I |
使用常數0-31 |
d |
使用寄存器edx |
J |
使用常量0-63 |
S |
使用寄存器esi |
K |
使用常數0-255 |
D |
使用寄存器edi |
L |
使用常量0-65535 |
q |
使用動態分配字節可尋址寄存器(eax, ebx, ecx, edx ) |
M |
使用常量0-3 |
r |
使用任意動態分配的寄存器 | N |
使用1 字節常量(0-255) |
g |
使用統一有效的地址即可(eax, ebx, ecx, edx 或內存變量) |
O |
使用常量0-31 |
A |
使用eax 與edx 聯合(64 位) |
= |
輸出操作數。輸出值講替換前值 |
+ |
表示操作數可讀寫 | & |
早起會變的(earlyclobber )操作數。表示使用玩操作數之前,內容會改變。 |
第4~7
行代碼的作用:
首先,fs
段寄存器內容入棧;
其次,eax
段值賦給fs
段寄存器;
再者,fs:(*(addr))
所指定的字節放入al
寄存器中。
e.g.
asm("cld\n\t"
"rep\n\t"
"stol"
: /* 沒有輸出寄存器 */
: "c"(count-1), "a"(fill_value), "D"(dest)
: "%ecx", "%edi");
前3
行是通常的彙編語句,用來清方向,重複保存值\n\n\t
爲換行符和製表符(對齊程序作用)。
第5
行的含義是:將count-1
的值加載到ecx
,將fill_value
加載到eax
,dest
加載到edi
。
gcc
會自動優化操作,
e.g.
asm("leal (%1, %1, 4), %0"
: "=r"(y)
: "0"(x));
該例子計算 x*5
的值,其中%0
(輸出寄存器)和%1
(輸入寄存器)是gcc
自動分配的寄存器。
注:如果輸入寄存器爲"0"
或者爲空的話,說明使用與相應輸出一樣的寄存器。
該例子中,若r
爲eax
,則
”leal (eax, eax, 4), eax"
可以通過關鍵詞volatile
來取消gcc
自動優化,代碼如下:
_asm_ _volatile_ (...);
關鍵詞volatile
也可以放在函數名前修飾函數,通知gcc
該函數不會返回。
e.g.
mm/memory.c
31 volatile void do_exit(long code);
32
33 static inline vocatile void oom(void)
34 {
35 printk("out of memory\n\r");
36 do_exit(SIGSEGV);
37 }
練習:(未看懂)
字符串命令查看附件1
。
3.3.3 圓括號中的組合語句
花括號"{}"
用於把變量聲明和語句組合成一個複合語句(組合語句)或一個語句塊(等同於一條語句)。
組合語句的右花括號後面不再使用分好。
圓括號中的組合語句“({...})”
,可以在GNU C
中當一個表達式使用。
e.g.
({ int y = foo(); int z;
if (y > 0 ) z = y;
else z = -y;
3 + z; })
解釋:
表達式(“3+z”)
的值等價於整個圓括號闊住的語句的值。若最後一句不是表達式,那麼整個語句表達式具有void
屬性,即沒有值。該表達式裏聲明的任何局部變量都會在整塊語句結束後失效。
該語句和普通表達式一樣。例如:
int i = 該語句;
這種表達式通常用來定義宏。
e.g.
init/main.c
69 # define CMOS_READ(addr) ({ \ // 反斜槓連接兩行語句
70 outb_p(0x80 | addr, 0x70); \ //首先向 I/O 端口 0x70輸出欲讀取的位置 addr。
71 inb_p(0x71); \ //然後從端口 0x71 讀入該位置處的值作爲返回值。
72 })
include/asm/io.h
05 # define inb(port) ({ \
06 unsigned char _v; \
07 _asm_ volatile ("inb %%dx, %%al":"=a" (_v):"d" (port)); \
08 _v; \
09 })
3.3.4 寄存器變量
GNU C
對C
語言的另一個擴充是允許我們把一些變量值放到CPU
寄存器中,即寄存器變量。
寄存器變量分全局變量和局部變量。
定義局部寄存器變量的形式:
register int res _asm_("ax");
ax
是變量res
希望使用的寄存器。
定義這樣一個寄存器變量並不會專門保留這個寄存器不派其他用途,也並不保證編譯出來的代碼會把變量一直放在指定的寄存器中。
3.3.5 內聯函數
在程序中,通過把一個函數聲明爲內聯(inline)
函數,就可以讓gcc
把函數的代碼集成調用到該函數的代碼中區。這樣處理的函數可以區掉函數調用時進入和退出時間開銷,從而寬度能夠加快執行速度。
內聯韓式嵌入調用者代碼中的操作是一種優化操作,因此只有進行優化編譯時纔會執行代碼嵌入處理。若編譯過程中沒有使用優化選項-O
,那麼內聯函數的代碼就不會被真正地嵌入到調用者代碼中,而是隻作爲普通函數調用來處理。
把一個函數聲明爲內聯函數的方法是在函數聲明中使用關鍵字"inline"
。
e.g
fs/inode.c
inline int inc( int *a)
{
(*a)++;
}
函數中的某些語句用法可能會使的內聯函數的替換操作無法正常進行,或者不適合進行替換操作。
例如,使用了可變參數、內存分配函數malloca()
、可變長度數據類型變量、非局部goto
語句以及遞歸函數。
編譯時,可以使用選項-Winline
讓gcc
對標誌成inline
但不能被替換的函數給出警告信息以及不能替換的原因。
當一個函數定義中既使用inline
關鍵字,又使用static
關鍵詞,即像下面文件fs/inode.c
中的內聯函數定義一樣,那麼如果所有對該內聯函數的調用都被替換二集成在調用者代碼中,並且程序中沒有引用過該內聯函數的地址,則該內聯函數自身的彙編代碼就不會被引用。
在這種情況下,除非我們在編譯過程中使用選項 -fkeep-inline-function
,否則gcc
就不會爲該內聯函數自身生成生成彙編代碼。
20 static inline void wait_on_inode(struct m_inode * inode)
21 {
22 cli();
23 while (inode->i_lock)
24 sleep_on(&inode->i_wait);
25 sti();
26 }
C99
默認”省略“了static
,爲了兼容C99
,最好使用inlineq
和static
組合。否則需要使用選項 --std=gnu89
。
如果在定義一個函數時還指定了inline
和extern
關鍵詞,那麼該函數定義僅用於內聯集成,並且在任何情況下都不會單獨產生該函數自身的彙編代碼,即使明確引用了該函數的地址也不會產生。
關鍵詞inline
和extern
組合在一起的作用機會類同一個宏定義。使用這種組合方式就是把帶有組合關鍵詞的一個函數定義放在.h
頭文件中,並且把不含關鍵詞的另一個相同函數定義放在一個庫文件中。此時頭文件中的定義大多數對該函數的調用被替換嵌入。如果還有未被替換的對該函數的調用,那麼就會使用(引用)程序文件中或庫中的副本。
e.g.
include/string.h、lib/string.c
字符串命令查看附件1
。
3.4 C
與彙編程序的相互調用
爲了提高代碼執行效率,內核源代碼中有的地方直接使用了彙編語言編制。
3.4.1 C
函數調用機制
函數調用操作包括從一塊代碼到另一塊代碼之間的雙向數據傳遞和執行控制轉移。數據傳遞通過函數參數和返回值來進行。
1. 棧幀結構和控制轉移方式
大多數CPU
上的程序失效使用棧來支持函數調用操作。棧被用來傳遞函數參數、存儲返回信息、臨時保存寄存器原有值以備恢復以及用來存儲局部數據。
單個函數調用操作所使用的棧部分被稱爲棧幀(stack frame
)結構。如圖3-4
。.
棧結構的兩端由兩個指針來指定。
寄存器ebp
通常用作幀指針(frame pointer)
,
esp
則用作棧指針(stack pointer)
。
esp
會隨數據出棧和入棧而移動。
棧是往低(小)地址放心擴展的,而esp
指向當前棧頂處的元素。
指令CALL
和RET
用於處理函數調用和返回操作。
Intel
慣例,
寄存器eax、edx
和ecx
的內容必須由調用者自己負責。
寄存器ebx、esi
和edi
以及ebp、esp
的內容必須由被調用者負責保護。
2. 函數調用示例
/* exch.c */
void swap( int *a, int *b)
{
int c;
c = *a, *a = *b, *b = c;
}
int main()
{
int a, b;
a = 16, b = 32;
swap( &a, &b);
return(a - b);
}
這兩個函數的棧指針結構如圖3-5
所示。
圖中的位置信息相對於寄存器ebp
中的幀指針。
棧幀左邊的數字指出了相對於幀指針的地址偏移值。
(在像GDB
這樣的調試器中,這些數值都用2
的補碼錶示,e.g. -4 = 0xFFFF FFFC ; -12 = 0xFFFF FFF4
)
使用命令
gcc -Wall -S -o exch.s exch.c
生成C
程序對應的彙編程序exch.s
代碼
3. main()
也是一個函數
在編譯鏈接時它將會作爲crt0.s
彙編程序的函數被調用。
crt0.s
是一個樁(stub)
程序,名稱中的"ctr"
是“C run-time”
的縮寫。
Linux 0.12
中的crt0.s
彙編程序如下:
3.4.2 在彙編程序中調用C
函數
彙編程序調用一個C
函數時,程序需要首先按照逆向順序把函數參數壓入棧,即函數最後(最右邊)一個參數先入棧,如圖3-6
。
在執行CALL
指令時,CPU
會把CALL
指令的下一條指令的地址壓入棧中(圖3-6
中的EIP
)。
即使沒有事先將參數壓入棧,被調用函數還是會以EIP
位置以上的棧中的其他內容作爲自己的參數使用。
e.g. parts
3.4.3 在C
程序中調用匯編函數
包含兩個函數的彙編程序callee.s
如下。
該彙編文件中的第1
個函數mywirte()
利用系統中斷0x80
調用系統調用sys_write(int fd, char *buf, int count)
實現在屏幕上顯示信息。對應的系統功能號
Note:C
語言調用匯編失敗(待解決)。
3.5 Linux 0.12
目標文件格式
Linux 0.12
使用兩張編譯器來生成內核代碼文件,第一種是as86
和ld86
,第二種是GNU
的彙編器as(gas)
和C
語言編譯器gcc
以及相應的鏈接程序`gld``。
3.5.1 目標文件格式
a.out
文件是一種被稱爲彙編與鏈接輸出(Assembly & linker editor output)
的目標文件格式。
a.out
格式7
個區的基本定義和用途是:
#1) 執行頭部分(exec header)
。該部分中包含由一些參數(exec 結構)
,是有關目標文件的整體結構信息。例如代碼和數據區的長度、未初始化數據區的長度、對應源程序文件名以及目標文件創建時間等。
內核使用這些參數把執行文件加載到內存中並執行,而鏈接程序(ld)
使用這些參數將一些模塊文件組合成一個可執行文件。這是目標文件唯一必要的組成部分。
#2) 代碼區(text segment0
.由編譯器或彙編器生成的二進制指令代碼和數據信息,含有程序執行時被加載到內存中的指令代碼和相關數據。能以制度形式加載。
#3) 數據區(data segment)
。由編譯器或彙編器生成的二進制指令和數據信息,這部分含有已經初始化過的數據,總是被加載到可讀寫的內存中。
#4) 代碼重定位信息(text relocation)
。這部分含有供鏈接程序使用的記錄數據。在組合目標模塊文件時用於定位代碼段中的指針或地址。當鏈接程序需要改變目標代碼的地址時就需要修正和維護這些地方。
#5) 數據重定位部分(data relocation)
。類似於代碼重定位部分的作用,但是用於數據段中指針的重定位。
#6) 符號表(symbol table)
。這部分用於含有供鏈接程序使用的記錄數據。這些記錄數據保存這模塊文件中定義的全局符號以及需要從其他模塊文件中輸入的符號,或者是由鏈接器定義的符號,用於在模塊文件之間對命名的變量和函數(符號)進行交叉引用。
#7) 字符串表部分(string table)
。該部分含有與符號名相對應的字符串,供調試程序調試目標代碼,與鏈接過程無關。這些信息可包含源程序代碼和行號、局部符號以及數據結構描述信息等。
1. 執行頭部分
目標文件的文件頭中含有一個長度爲32B
的exec
數據結構,通常稱爲文件頭結構或執行頭結構。
Linux 0.12
系統使用了其中兩種類型:
模塊目標文件使用了OMAGIC (Old Magic)
類型的a.out
格式,它指明文件是目標文件或者是不純的可執行文件。其魔數是0x107
。
執行文件使用了ZMAGIC
類型的a.out
格式,它指明文件爲需求分頁處理(demand-paging,即需求加載,load on demand )
的可執行文件。其魔數是0x10b
。
這兩種格式主要區別在於它們對各個部分的存儲分配方式上。
執行頭結構中的a_text
和a_data
字段分別指明後面只讀的代碼段和可讀寫數據段的字節長度。
a_entry
字段指定了程序代碼開始執行的地址,而a_syms、a_trsize和a_drsize
字段則分別說明了數據段後符號表、代碼和數據段重定位信息的大小。
2. 重定位信息部分
重定位的功能有兩個。一是當代碼段被重定位到一個不同的基地址處時,重定位項則用於指出需要修改的地方。二是在模塊文件中存在對未定義符號引用時,當此未定義符號最終被定義時鏈接程序就可以使用相應重定位項對符號的值進行修正。
3. 符號表和字符串部分
由於GNU gcc
編譯器允許任意長度的標識符,因此標誌符字符串都位於符號表後的字符串表中。
符號的主要類型包括:
#1) text、data或bss
指明是本模塊文件中定義的符號。此時符號值是模塊中該符號的可重定位地址。
#2) abs
指明符號是一個絕對的(固定的)不可重定位的符號。符號的值就是該固定值。
#3) undef
指明是一個本模塊文件中未定義的符號。此時符號值通常爲0
。
3.5.2 Linux 0.12
的目標文件格式
查看執行文件頭結構的具體值
[/usr/root]# gcc -c -o name.o name.c
[/usr/root]# gcc -o name name.o
[/usr/root]#
[/usr/root]# hexdump -x name.o
[/usr/root]# objdump -h name.o
[/usr/root]# hexdump -x name | more
[/usr/root]# objdump -h name
刪除執行文件中的符號表信息命令。
[/usr/root]# strip exch
磁盤上a.out
執行文件的各區在進程邏輯地址空間中的對應關係如圖3-8
所示。
3.5.3 鏈接程序輸出
鏈接程序對輸入的一個或多個模塊文件以及相關的庫函數模塊進行處理,最終生成相應的二進制執行文件或一個由所有模塊組合而成的大模塊文件。此過程中,鏈接程序的首要任務是給執行文件(或者輸出的模塊文件)進行空間分配操作。
每個模塊文件中包括幾種類型的段,鏈接程序的第二個任務就是把所有模塊中相同類型的段組合連接在一起,在輸出文件中爲指定段類型形成單一一個段。
3.5.4 鏈接程序預定義變量
在鏈接過程中,鏈接器ld
和ld86
會使用變量記錄執行程序中每個段的邏輯地址。
鏈接預定義的外部變量通常至少有etext、_etext、edata、_edate、end
和_end
。
下面程序可以顯示出幾個變量的地址。
運行結果爲:
可以看出帶與不帶下劃線"_"
符號的地址值是相同的。
3.5.5 System.map
文件
當運行GNU
鏈接器gld(ld)
時若使用了"-M"
選項,或者使用了"-nm“
命令,則會在標準輸出設備(通常是屏幕)上打印處鏈接映像(link map)
信息,即指由鏈接程序產生的目標程序內存地址映像信息。其中列出了程序段裝入到內存中的位置信息。具體有:
#1) 目標文件即符號信息映射到內存中的位置。
#2) 公共符號如何放置。
#3) 鏈接中包含的所有文件成員及其引用的符號。
在編譯內核時,Linux/MakeFile
文件產生的System.map
文件就用於存放內核符號表信息。
符號表是所有內核符號機器對應地址的一個列表,當然也包括上面說明的_etext、_edata
和_end
等符號的地址信息。
符號表樣例如下:
第1
欄指明符號值(地址);第2
欄是符號類型,指明符號位於目標文件的哪個區(Sections)
或其屬性;第3
欄是對應的符號名稱。
dmi_broken
的變量位於內核地址0x03441a0
處。
3.6 Make
程序和Makefile
文件
有關make
的詳細使用方法請參考《GNU make使用手冊》
。
3.6.1 Makefile
文件內容
一個Makefie
文件可以包括五種元素:顯示規則、隱含規則、變量定義、指示符和註釋信息。
**顯示規則(explicit rules
)**用於指定何時以及怎麼樣重新編譯一個或多個被稱作規則的目標(rule's targets)
的文件。規則中明確列出了目標所依賴的被稱作爲目標的先決條件(或依賴)的其他文件,同時也會給出用於創建或更新目標的命令。
**隱含規則(implicit rules
**則是根據目標和對象的名稱來確定何時和如何重新編譯一個或多個被稱作規則的目標的文件。
**變量定義(variable definitions
)**用於在一行上爲一個變量定義一個文本字符串。
**指示符(directives)
**是make
的一個命令,用於指示其在讀取makefile
文件時執行的特定操作。
**註釋(comments)
**是指Makefile
文件以”#”
字符開始的文字部分。
3.6.2 Makefile
文件中的規則
簡單的Makefile
文件中含有一些如下形式的規則。這些規則主要用來描述**操作對象(源文件和目標文件)**之間的依賴關係。
target
(目標)對象通常是指程序生成的一個文件的名稱,
例如它可以是一個可執行文件或者一個以".o"
結尾的目標文件(Object file)
。
目標也可以是所要採取活動的名稱,
例如“清理”("clean")
。
prerequisite
(先決條件或稱依賴對象)是用以創建target
所必要或者依賴的一系列文件或其他目標。
command
(命令)是值make
所執行的操作,通常就是一些shell
命令,是生成target
需要執行的操作。
3.6.3 Makefile
文件示例
當make
依據Makefile
文件中的內容重新編譯C
文件時, 僅會對每個修改過的C
文件進行重新編譯。
Makefile
示例文件中的內容描述了一個名爲eidt
的執行文件依賴於8
個目標文件的方式,以及這8
個目標文件又是如何依賴於8
個C
源文件和3
個頭文件的。
要使用該Makefile
創建執行文件“edit
,只需在命令行上簡單地鍵入make
即可。
若要使用該Makefile
從當前目錄中刪除編譯得到的執行文件和所有目標文件,只需要鍵入make clean
。
在該Makefile
文件中,規則的目標包括執行文件edit
和.o
目標文件(object file)”main.o"
、”kbd.o"
等。先決條件(或依賴條件)文件是諸如"main.o"
和"defs.h"
等源文件。
當目標是一個文件時,那麼其先決條件中的任何依賴條件被修改過時就需要進行重新編譯或鏈接。
Makefile
中規則的目標和先決條件的下一行是shell
命令。
3.6.4 make
處理Makefile
文件的方式
默認情況下,make
會從Makefile
文件中第一個目標開始執行(不包括"."
開始的目標)。
該目標被稱爲Makefile
的默認最終目標(default goal)
。最終目標就是make
努力嘗試更新的目標。
3.6.5 Makefile
中的變量
定義變量的格式:
objects = something ...
引用變量格式:
$objects
3.6.6 讓make
自動推斷命令----(point)
make
隱含規則:
根據目標文件的命名形式使用"cc -c"
命令根據相應的.c
文件更新對應的.o
文件。
e.g.
它會使用"cc -c main.c -o main.o"
把"main.c"
編譯成"main.o"
。因此我們可以省略.o
目標文件規則中的命令。
當一個.c
文件被以這種方式自動地使用,那麼它會被自動地添加到先決條件(依賴條件)中。因此我們可以省略規則先決條件中的".c"
文件----假定我們同時省略了命令。
上述示例可以更新爲:
3.6.7 隱含規則中的自動變量
略
[附1] 字符串處理指令
(1)
lodsb
、lodsw
:把DS:SI
指向的存儲單元中的數據裝入AL
或AX
,然後根據DF
標誌(df=0)
增(df=1)
減SI
(2)
stosb
、stosw
:把AL
或AX
中的數據裝入ES:DI
指向的存儲單元,然後根據DF
標誌(df=0)
增(df=1)
減DI
(3)
movsb
、movsw
:把DS:SI
指向的存儲單元中的數據裝入ES:DI
指向的存儲單元中,然後根據DF
標誌分別(df=0)
增(df=1)
減SI
和DI
(4)
scasb
、scasw
:把AL
或AX
中的數據與ES:DI
指向的存儲單元中的數據相減,影響標誌位,然後根據DF
標誌分別(df=0)
增(df=1)
減SI
和DI
(5)
cmpsb
、cmpsw
:把DS:SI
指向的存儲單元中的數據與ES:DI
指向的存儲單元中的數據相減,影響標誌位,然後根據DF
標誌分別(df=0)
增(df=1)
減SI
和DI
(6)
rep
:重複其後的串操作指令。重複前先判斷CX
是否爲0
,爲0
就結束重複,否則CX
減1
,重複其後的串操作指令。主要用在MOVS
和STOS
前。一般不用在LODS
前。
上述指令涉及的寄存器:
段寄存器DS
和ES
、變址寄存器SI
和DI
、累加器AX
、計數器CX
涉及的標誌位:DF、AF、CF、OF、PF、SF、ZF
Content
文章目錄
- 3.1 `as86`彙編器
- 3.2 `GNU as`彙編
- 3.2.1 編譯`as`彙編語言程序
- 3.2.2 `as`彙編語法
- 3.2.3 指令語句、操作數和尋址
- 3.2.4 區與重定位
- 3.2.5 符號
- 3.2.6 `as`彙編命令
- 1. .align abs-expr1, abs-expr2, abs-expr3
- 2. .ascii "string"...
- 3. .asciz "string"...
- 4. .byte expressions
- 5. .comm symbol, length
- 6. .data subsection
- 7. .desc symbol, abs-expr
- 8. .fill repeat,size,value
- 9. .global symbol
- 10. .int expressions(.long exoressions)
- 11. .lcomm symbol, length
- 12. .octa bignums
- 13. .org new_lc, fill
- 14. .quad bignums
- 15. .short expressions (.word expressions)
- 16. .space size, fill
- 17. .string "string"
- 18. .text subsection
- 3.2.7 編寫`16`位代碼
- 3.2.8 `AS`彙編器命令行選項
- 3.3 `C`語言程序
- 3.4 `C`與彙編程序的相互調用
- 3.5 `Linux 0.12`目標文件格式
- 3.6 `Make`程序和`Makefile`文件
- 3.6.1 `Makefile`文件內容
- 3.6.2 `Makefile`文件中的規則
- 3.6.3 `Makefile`文件示例
- 3.6.4 `make`處理`Makefile`文件的方式
- 3.6.5 `Makefile`中的變量
- 3.6.6 讓`make`自動推斷命令----`(point)`
- 3.6.7 隱含規則中的自動變量
- [附1] 字符串處理指令
- Content