gcc中的內嵌匯編語言(Intel i386平臺)

gcc中的內嵌匯編語言(Inteli386平臺)

.聲明
雖然Linux的核心代碼大部分是用C語言編寫的,但是不可避免的其中還是有一部分是用匯編語言寫成的。有些匯編語言代碼是直接寫在匯編源程序中的,特別是Linux的啟動代碼部分;還有一些則是利用gcc的內嵌匯編語言嵌在C語言程序中的。這篇文章簡單介紹了gcc中的內嵌式匯編語言,主要想幫助那些才開始閱讀Linux核心代碼的朋友們能夠更快的入手。
寫這篇文章的主要信息來源是GNU的兩個info文件:as.infogcc.info,如果你覺得這篇文章中的介紹還不夠詳細的話,你可以查閱這兩個文件。當然,直接查閱這兩個文件可以獲得更加權威的信息。如果你不想被這兩篇文檔中的一大堆信息搞迷糊的話,我建議你先閱讀一下這篇文章,然后在必要時再去查閱更權威的信息。
.簡介
Linux的核心代碼中,還是存在相當一部分的匯編語言代碼。如果你想順利閱讀Linux代碼的話,你不可能繞過這一部分代碼。在Linux使用的匯編語言代碼中,主要有兩種格式:一種是直接寫成匯編語言源程序的形式,這一部分主要是一些Linux的啟動代碼;另一部分則是利用gcc的內嵌式匯編語言語句asm嵌在LinuxC語言代碼中的。這篇文章主要是介紹第二種形式的匯編語言代碼。
首先,我介紹一下as支持的匯編語言的語法格式。大家知道,我們現在學習的匯編語言的格式主要是Intel風格的,而在Linux的核心代碼中使用的則是AT&T格式的匯編語言代碼,應該說大部分人對這種格式的匯編語言還不是很了解,所以我覺得有必要介紹一下。
接著,我主要介紹一下gcc的內嵌式匯編語言的格式。gcc的內嵌式匯編語言提供了一種在C語言源程序中直接嵌入匯編指令的很好的辦法,既能夠直接控制所形成的指令序列,又有著與C語言的良好接口,所以在Linux代碼中很多地方都使用了這一語句。
.AT&T的匯編語言語法格式
我想我們大部分人對Intel格式的匯編語言都很了解了。但是,在Linux核心代碼中,所有的匯編語言指令都是用AT&T格式的匯編語言書寫的。這兩種匯編語言在語法格式上有著很大的不同:
1.AT&T的匯編語言中,用'$'前綴表示一個立即操作數;而在Intel的格式中,立即操作數的表示不帶任何前綴符。例如:下面兩個語句是完全相同的:
*AT&T: pushl $4
*Intel: push 4
2.AT&TIntel的匯編語言格式中,源操作數和目標操作數的位置正好相反。Intel的匯編語言中,目標操作數在源操作數的左邊;而在AT&T的匯編語言中,目標操作數則在源操作數的右邊。例如:
*AT&T : addl $4,%eax
*Intel: add eax,4
3.AT&T的匯編語言中,操作數的字長是由操作碼助記符的最后一個字母決定的,后綴'b''w''l'分別表示操作數的字長為8比特(字節,byte),16比特(字,word)和32比特(長字,long),而Intel格式中操作數的字長是用“wordptr”或者“byte ptr”等前綴來表示的。例如:
*AT&T: movb FOO,%al
*Intel: mov al,byte ptr FOO
4. AT&T匯編指令中,直接遠跳轉/調用的指令格式是“lcall/ljmp$SECTION,$OFFSET”,同樣,遠程返回的指令是“lret$STACK-ADJUST”;而在Intel格式中,相應的指令分別為“call/jmpfar SECTION:OFFSET”和“ret farSTACK-ADJUST”
①AT&T匯編指令操作助記符命名規則
AT&T匯編語言中,操作碼助記符的后綴字符指定了該指令中操作數的字長。后綴字母'b''w''l'分別表示字長為8比特(字節,byte),16比特(字,word)和32比特(長字,long)的操作數。如果助記符中沒有指定字長后綴并且該指令中沒有內存操作數,匯編程序'as'會根據指令中指定的寄存器操作數補上相應的后綴字符。所以,下面的兩個指令具有相同的效果(這只是GNU的匯編程序as的一個特性,AT&TUnix匯編程序將沒有字長后綴的指令的操作數字長假設為32比特):
mov %ax,%bx
movw %ax,%bx
AT&T中幾乎所有的操作助記符與Intel格式中的助記符同名,僅有一小部分例外。操作數擴展指令就是例外之一。在AT&T匯編指令中,操作數擴展指令有兩個后綴:一個指定源操作數的字長,另一個指定目標操作數的字長。AT&T的符號擴展指令的基本助記符為'movs',零擴展指令的基本助記符為'movz'(相應的Intel指令為'movsx''movzx')。因此,'movsbl%al,%edx'表示對寄存器al中的字節數據進行字節到長字的符號擴展,計算結果存放在寄存器edx中。下面是一些允許的操作數擴展后綴:
*bl: 字節->長字
*bw: 字節->
*wl: ->長字
還有一些其他的類型轉換指令的對應關系:
*Intel *AT&T

⑴ cbw cbtw
符號擴展:al->ax
⑵ cwde cwtl
符號擴展:ax->eax
⑶ cwd cwtd
符號擴展:ax->dx:ax
⑷ cdq cltd
符號擴展:eax->edx:eax

還有一個不同名的助記符就是遠程跳轉/調用指令。在Intel格式中,遠程跳轉/調用指令的助記符為“call/jmpfar”,而在AT&T的匯編語言中,相應的指令為“lcall”和“ljmp”
②AT&T中寄存器的命名
AT&T匯編語言中,寄存器操作數總是以'%'作為前綴。80386芯片的寄存器包括:
⑴832位寄存器:'%eax','%ebx','%ecx','%edx','%edi','%esi','%ebp','%esp'
⑵816位寄存器:'%ax','%bx','%cx','%dx','%si','%di','%bp','%sp'
⑶88位寄存器:'%ah','%al','%bh','%bl','%ch','%cl','%dh','%dl'
⑷6個段寄存器:'%cs','%ds','%es','%ss','%fs','%gs'
⑸3個控制寄存器:'%cr0','%cr1','%cr2'
⑹6個調試寄存器:'%db0','%db1','%db2','%db3','%db6','%db7'
⑺2個測試寄存器:'%tr6','%tr7'
⑻8個浮點寄存器棧:'%st(0)','%st(1)','%st(2)','%st(3)','%st(4)','%st(5)','%st(6)','%st(7)'
*注:我對這些寄存器并不是都了解,這些資料只是摘自as.info文檔。
如果真的需要寄存器命名的資料,我想可以參考一下相應GNU工具的機器描述方面的源文件。
③AT&T中的操作碼前綴
段超越前綴'cs','ds','es','ss','fs','gs':當匯編程序中對內存操作數進行SECTION:MEMORY-OPERAND引用時,自動加上相應的段超越前綴。
操作數/地址尺寸前綴'data16','addr16':這些前綴將32位的操作數/地址轉化為16位的操作數/地址。
總線鎖定前綴'lock':總線鎖定操作。'lock'前綴在Linux核心代碼中使用很多,特別是SMP代碼中。
協處理器等待前綴'wait':等待協處理器完成當前操作。
指令重復前綴'rep','repe','repne':在串操作中重復指令的執行。
④AT&T中的內存操作數
Intel的匯編語言中,內存操作數引用的格式如下:
SECTION : [BASE + INDEX*SCALE + DISP]
而在AT&T的匯編語言中,內存操作數的應用格式則是這樣的:
%SECTION : DISP(BASE,INDEX,SCALE)
下面是一些內存操作數的例子:
*AT&T *Intel
⑴ -4(%ebp) [ebp-4]
⑵foo(,%eax,4) [foo+eax*4]
⑶ foo(,1) [foo]
⑷ %gs:foo gs:foo
還有,絕對跳轉/調用指令中的內存操作數必須以'*'最為前綴,否則as總是假設這是一個相對跳轉/調用指令。
⑤AT&T中的跳轉指令
as匯編程序自動對跳轉指令進行優化,總是使用盡可能小的跳轉偏移量。如果8比特的偏移量無法滿足要求的話,as會使用一個32位的偏移量,as匯編程序暫時還不支持16位的跳轉偏移量,所以對跳轉指令使用'addr16'前綴是無效的。
還有一些跳轉指令只支持8位的跳轉偏移量,這些指令包括:'jcxz','jecxz','loop','loopz','loope','loopnz''loopne'。所以,在as的匯編源程序中使用這些指令可能會出錯。(幸運的是,gcc并不使用這些指令)
AT&T匯編語言語法的簡單介紹差不多了,其中有些特性是as特有的。在Linux核心代碼中,并不涉及到所有上面這些提到的語法規則,其中有兩點規則特別重要:第一,as中對寄存器引用時使用前綴'%';第二,AT&T匯編語言中源操作數和目標操作數的位置與我們熟悉的Intel的語法正好相反。
.gcc的內嵌匯編語言語句asm
利用gccasm語句,你可以在C語言代碼中直接嵌入匯編語言指令,同時還可以使用C語言的表達式指定匯編指令所用到的操作數。這一特性提供了很大的方便。

要使用這一特性,首先要寫一個匯編指令的模板(這種模板有點類似于機器描述文件中的指令模板),然后要為每一個操作數指定一個限定字符串。例如:
extern __inline__ void change_bit(int nr,volatile void*addr)
{
__asm__ __volatile__( LOCK_PREFIX
"btcl%1,%0"
:"=m" (ADDR)
:"ir" (nr));
}
上面的函數中:
LOCK_PREFIX:這是一個宏,如果定義了__SMP__,擴展為"lock;",用于指定總線鎖定前綴,否則擴展為""
ADDR:這也是一個宏,定義為(*(volatilestruct __dummy *) addr)
"btcl%1,%0":這就是嵌入的匯編語言指令,btcl為指令操作碼,%1,%0是這條指令兩個操作數的占位符。后面的兩個限定字符串就用于描述這兩個操作數。
: "=m"(ADDR):第一個冒號后的限定字符串用于描述指令中的“輸出”操作數。刮號中的ADDR將操作數與C語言的變量聯系起來。這個限定字符串表示指令中的“%0”就是addr指針指向的內存操作數。這是一個“輸出”類型的內存操作數。
: "ir"(nr):第二個冒號后的限定字符串用于描述指令中的“輸入”操作數。這條限定字符串表示指令中的“%1”就是變量nr,這個的操作數可以是一個立即操作數或者是一個寄存器操作數。
*注:限定字符串與操作數占位符之間的對應關系是這樣的:在所有限定字符串中(包括第一個冒號后的以及第二個冒號后的所有限定字符串),最先出現的字符串用于描述操作數“%0”,第二個出現的字符串描述操作數“%1”,以此類推。
匯編指令模板
asm語句中的匯編指令模板主要由匯編指令序列和限定字符串組成。在一個asm語句中可以包括多條匯編指令。匯編指令序列中使用操作數占位符引用C語言中的變量。一條asm語句中最多可以包含十個操作數占位符:%0%1...%9。匯編指令序列后面是操作數限定字符串,對指令序列中的占位符進行限定。限定的內容包括:該占位符與哪個C語言變量對應,可以是什么類型的操作數等等。限定字符串可以分為三個部分:輸出操作數限定字符串(指令序列后第一個冒號后的限定字符串),輸入操作數限定字符串(第一個冒號與第二個冒號之間),還有第三種類型的限定字符串在第二個冒號之后。同一種類型的限定字符串之間用逗號間隔。asm語句中出現的第一個限定字符串用于描述占位符“%0”,第二個用于描述占位符“%1”,以此類推(不管該限定字符串的類型)。如果指令序列中沒有任何輸出操作數,那么在語句中出現的第一個限定字符串(該字符串用于描述輸入操作數)之前應該有兩個冒號(這樣,編譯器就知道指令中沒有輸出操作數)。
指令中的輸出操作數對應的C語言變量應該具有左值類型,當然對于輸出操作數沒有這種左值限制。
輸出操作數必須是只寫的,也就是說,asm對取出某個操作數,執行一定計算以后再將結果存回該操作數這種類型的匯編指令的支持不是直接的,而必須通過特定的格式的說明。如果匯編指令中包含了一個輸入-輸出類型的操作數,那么在模板中必須用兩個占位符對該操作數的不同功能進行引用:一個負責輸入,另一個負責輸出。例如:
asm ("addl %2,%0":"=r"(foo):"0"(foo),"g"(bar));
在上面這條指令中,“%0”是一個輸入-輸出類型的操作數,"=r"(foo)用于限定其輸出功能,該指令的輸出結果會存放到C語言變量foo中;指令中沒有顯式的出現“%1”操作數,但是針對它有一個限定字符串"0"(foo),事實上指令中隱式的“%1”操作數用于描述“%0”操作數的輸入功能,它的限定字符串中的"0"限定了“%1”操作數與“%0”具有相同的地址。可以這樣理解上述指令中的模板:該指令將“%1”和“%2”中的值相加,計算結果存放回“%0”中,指令中的“%1”與“%0”具有相同的地址。注意,用于描述“%1”"0"限定字符足以保證“%1”與“%0”具有相同的地址。但是,如果用下面的指令完成這種輸入-輸出操作就不會正常工作:
asm ("addl %2,%0":"=r"(foo):"r"(foo),"g"(bar));
雖然該指令中“%0”和“%1”同樣引用了C語言變量foo,但是gcc并不保證在生成的匯編程序中它們具有相同的地址。
還有一些匯編指令可能會改變某些寄存器的值,相應的匯編指令模板中必須將這種情況通知編譯器。所以在模板中還有第三種類型的限定字符串,它們跟在輸入操作數限定字符串的后面,之間用冒號間隔。這些字符串是某些寄存器的名稱,代表該指令會改變這些寄存器中的內容。
在內嵌的匯編指令中可能會直接引用某些硬件寄存器,我們已經知道AT&T格式的匯編語言中,寄存器名以“%”作為前綴,為了在生成的匯編程序中保留這個“%”號,在asm語句中對硬件寄存器的引用必須用“%%”作為寄存器名稱的前綴。如果匯編指令改變了硬件寄存器的內容,不要忘記通知編譯器(在第三種類型的限定串中添加相應的字符串)。還有一些指令可能會改變CPU標志寄存器EFLAG的內容,那么需要在第三種類型的限定字符串中加入"cc"
為了防止gcc在優化過程中對asm中的匯編指令進行改變,可以在"asm"關鍵字后加上"volatile"修飾符。
可以在一條asm語句中描述多條匯編語言指令;各條匯編指令之間用“;”或者“/n”隔開。
操作數限定字符
操作數限定字符串中利用規定的限定字符來描述相應的操作數,一些常用的限定字符有:(還有一些沒有涉及的限定字符,參見gcc.info
1"m":操作數是內存變量。
2"o":操作數是內存變量,但它的尋址方式必須是“偏移量”類型的,也就是基址尋址或者基址加變址尋址。
3"V":操作數是內存變量,其尋址方式非“偏移量”類型。
4"":操作數是內存變量,其地址自動增量。
6"r":操作數是通用寄存器。
7"i":操作數是立即操作數。(其值可在匯編時確定)
8"n":操作數是立即操作數。有些系統不支持除字(雙字節)以外的立即操作數,這些操作數要用"n"而不是"i"來描述。
9"g":操作數可以是立即數,內存變量或者寄存器,只要寄存器屬于通用寄存器。
10"X":操作數允許是任何類型。
11"0","1",...,"9":操作數與某個指定的操作數匹配。也就是說,該操作數就是指定的那個作數。例如,如果用"0"來描述"%1"操作數,那么"%1"引用的其實就是"%0"操作數。
12"p":操作數是一個合法的內存地址(指針)。
13"=":操作數在指令中是只寫的(輸出操作數)。
14"+":操作數在指令中是讀-寫類型的(輸入-輸出操作數)。
15"a":寄存器EAX
16"b":寄存器EBX
17"c":寄存器ECX
18"d":寄存器EDX
19"q":寄存器"a","b","c"或者"d"
20"A":寄存器"a"或者"d"
21"a":寄存器EAX
22"f":浮點數寄存器。
23"t":第一個浮點數寄存器。
24"u":第二個浮點數寄存器。
25"D":寄存器di
26"S":寄存器si
27"I":0-31之間的立即數。(用于32位的移位指令)
28"J":0-63之間的立即數。(用于64位的移位指令)
29"N":0-255之間的立即數。(用于"out"指令)
30"G":標準的80387浮點常數。
*注:還有一些不常見的限定字符并沒有在此說明,另外有一些限定字符,例如"%","&"等由于我缺乏編譯器方面的一些知識,所以我也不是很理解它們的含義,如果有高手愿意補充,不慎感激!不過在核心代碼中出現的限定字符差不多就是上面這些了。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/454817.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/454817.shtml
英文地址,請注明出處:http://en.pswp.cn/news/454817.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據庫學習,樹形結構的數據庫表Schema設計方案

2019獨角獸企業重金招聘Python工程師標準>>> 程序設計過程中,我們常常用樹形結構來表征某些數據的關聯關系,如企業上下級部門、欄目結構、商品分類等等,通常而言,這些樹狀結構需要借助于數據庫完成持久化。然而目前的各…

[轉載] 手工制作Win7 OEM版

只要往微軟MSDN原版ISO的sources目錄加個“$OEM$”文件夾,再刪除sources下面的ei.cfg文件就可以了。 來源:http://zxkh19501.blog.163.com/blog/static/1237851792010629113427594/轉載于:https://www.cnblogs.com/784040932/p/win7oem.html

mysql dbo_mysql-雙重分組

我的表有兩列:名稱和等級.看起來像這樣:NAME | GRADEAdam | 1Adam | 2Adam | 2Adam | 3Frank | 2Frank | 1現在,我想創建如下所示的視圖:NAME | GRADE 1 | GRADE 2 | GRADE 3Adam | 1 | 2 | 1Frank | 1 | 1 | 0我寫了這個:SELECT …

課堂作業整理三 (集合:list接口)

集合中 list的方法列表(Arraylist和Linkedlist) 方法名功能說明ArrayList()構造方法,用于創建一個空的數組列表add(E,e)將指定的元素添加到此列表的尾部get(int index)返回此列表中指…

LINUX系統移植(史上最全最細,強烈推薦)

Linux系統移植 目 錄 第一部分 前言...................................................................................................................................8 1 硬件環境................................................................................…

The serializable class XXX does not declare a static final serialVersionUID field of type long的警告...

原文: http://blog.csdn.net/ultrakang/article/details/41820543轉載于:https://www.cnblogs.com/Baronboy/p/7465508.html

Ubuntu17.04 之 systemd 設置開機啟動

Ubuntu從16.04開始不再使用 initd 管理系統,改用 systemd。 和 Centos 一樣,升級到 Centos7 之后使用 systemd 替代 init.d 為了像以前一樣,在/etc/rc.local中設置開機啟動程序,需要以下幾步: 1、鏈接文件 systemd 默…

replaceselection();java'_Java JTextComponent.replaceSelection方法代碼示例

import javax.swing.text.JTextComponent; //導入方法依賴的package包/類public void actionPerformed(final ActionEvent evt, final JTextComponent target) {if (target ! null) {if (!target.isEditable() || !target.isEnabled()) {target.getToolkit().beep();return;}Ed…

Systemd 入門教程之命令篇

Systemd 是 Linux 系統工具,用來啟動守護進程,已成為大多數發行版的標準配置。 本文介紹它的基本用法,分為上下兩篇。今天介紹它的主要命令,下一篇介紹如何用于實戰。 一、由來 歷史上,Linux 的啟動一直采用init進程。…

GCC生成的匯編代碼

假設我們寫了一個C代碼文件 code.c包含下面代碼: int accum 0; int sum(int x, int y) { int t x y; accum t; return t; } 這是用echo命令輸入源碼的效果,簡單的就是最好的:)一、查看GCC生成的匯編代碼在命令行…

php __FILE__,__CLASS__等魔術變量,及實例

php __FILE__,__CLASS__等魔術變量,及實例 今天看到一個魔術變量,是以前沒見過的,__DIR__,我查了查,發現原來是php5.3新增的,順便舉幾個例子,解釋一下php的魔術變量 1,__FILE__ 文件的完整路徑和…

java虛方法和抽象方法_虛方法和抽象方法--基礎回顧

抽象方法是只有定義、沒有實際方法體的函數,它只能在抽象函數中出現,并且在子類中必須重寫;虛方法則有自己的函數體,已經提供了函數實現,但是允許在子類中重寫或覆蓋。重寫的子類虛函數就是被覆蓋了。抽象方法使用abst…

jQuery高度及位置操作

1. 獲取滑輪位置&#xff0c;scrolltop:上下滾動的意思。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body><div style"height:100px;width:10…

you have mixed tabs and spaces fix this

http://blog.csdn.net/tonyyan19781/article/details/60882443Vs2013 IDE下&#xff0c;編輯C的工程源碼&#xff0c;在打開文件的時候&#xff0c;會出現 “ you have mixed tabs and spaces fix this ”&#xff0c; 然后給出三個選項 Tabify、Untabify、Dont show again。尤…

Systemd 入門教程之實戰篇

一、開機啟動 對于那些支持 Systemd 的軟件&#xff0c;安裝的時候&#xff0c;會自動在/usr/lib/systemd/system目錄添加一個配置文件。 如果你想讓該軟件開機啟動&#xff0c;就執行下面的命令&#xff08;以httpd.service為例&#xff09;。$ sudo systemctl enable httpd上…

從VC++到GCC移植:談兩者的語法差異

從VC到GCC移植&#xff1a;談兩者的語法差異 許式偉 &#xff08;版權聲明&#xff09; 2007-1-28 類型引用 template <classT>classFoo { typedef T::SomeType SomeType; };這段代碼在VC中一點問題也沒有&#xff0c;但是GCC并不允許&#xff0c;因為它不知道T::S…

牛客網Java刷題知識點之關鍵字static、static成員變量、static成員方法、static代碼塊和static內部類...

不多說&#xff0c;直接上干貨&#xff01; 牛客網Java刷題知識點之關鍵字static static代表著什么 在Java中并不存在全局變量的概念&#xff0c;但是我們可以通過static來實現一個“偽全局”的概念&#xff0c;在Java中static表示“全局”或者“靜態”的意思&#xff0c;用來修…

30天自制操作系統(二)匯編語言學習與Makefile入門

1 介紹文本編輯器這部分可直接略過2 繼續開發helloos.nas中核心程序之前的內容和啟動區以外的內容先不講了&#xff0c;因為還涉及到一些軟盤方面的知識。然后來講的是helloos.nas這個文件; hello-os ; TAB4ORG 0x7c00 ; 指明程序的裝載地址; 以下這部分記錄…

java房產源碼_基于jsp的房屋交易管理系統-JavaEE實現房屋交易管理系統 - java項目源碼...

基于jspservletpojomysql實現一個javaee/javaweb的房屋交易管理系統, 該項目可用各類java課程設計大作業中, 房屋交易管理系統的系統架構分為前后臺兩部分, 最終實現在線上進行房屋交易管理系統各項功能,實現了諸如用戶管理, 登錄注冊, 權限管理等功能, 并實現對各類房屋交易管…

Docker 精通之入門

Docker 精通系列 Docker 精通之入門Docker 精通之微服務Docker 精通之常用命令Docker 精通之 Dockerfile 2013年發布至今&#xff0c; Docker 一直廣受矚目&#xff0c;被認為可能會改變軟件行業。 但是&#xff0c;許多人并不清楚 Docker 到底是什么&#xff0c;要解決什么問…