關於AT&T 彙編:64 位與32 位的區別
1. 64位系統中函式呼叫不再採用單純壓棧的方式:下面的內容出自WikiPedia:Parameters in registers:RDI, RSI, RDX, RCX, R8, R9, XMM0–7Parameter order on stack:RTL (C)Stack cleanup by:Caller(呼叫者清理)Notes:Stack aligned on 16 bytes boundary.Red zone below stack.下面的內容摘自WikiPedia:The first six integer or pointer arguments are passed in registers RDI, RSI, RDX, RCX, R8, and R9, while XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for floating point arguments.additional arguments are passed on the stack and the return value is stored in RAX.
2. 系統呼叫的改變:首先,int 0x80 指令被syscall 取代。在如下/usr/include/asm/unistd_32.h和/usr/include/asm/unistd_64.h兩個檔案可以找到系統呼叫的差別。
最後一點:系統呼叫的引數傳遞不再使用32 位下的暫存器,而是和函式呼叫使用的暫存器一致。(摘自WikiPedia)The first six integer or pointer arguments are passed in registers RDI, RSI, RDX, RCX, R8, and R9, while XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for floating point arguments.For system calls, R10 is used instead of RCX.
64位的函式呼叫的例子:
.section .data strFormat: .asciz "%s\n" strUseLibc: .asciz "Hi,you called c lib:)" strUseSyscall: .asciz "Hi,you called syscall.\n" endOfStrUseSyscall: .text .globl _start _start: # movq $strFormat, %rdi movq $strUseLibc, %rsi call printf # int 0x80 syscall movq $1, %rdi movq $strUseSyscall, %rsi movq $(endOfStrUseSyscall-strUseSyscall), %rdx movq $1, %rax syscall # movq $127, %rdi # 0 movq $60, %rax syscall ret
as test.s -o test.o
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -lc test.o
32 位程式的例子:
//for32c.c
int main(int argc, char **argv)
{
sayhello(argv[0]);
return 0;
}
#for32s.s
.section .data
strFormat:
.asciz "%s\n"
.section .text
.globl sayhello
sayhello:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl 8(%ebp), %eax
movl %eax,4(%esp)
movl $strFormat, (%esp)
call printf
addl $8,%esp
popl %ebp
ret
#movq %rdi, %rsi
#movq $strFormat, %rdi
#call printf
#ret
如下編譯:
as --32 for32s.s -o for32s.o
gcc -m32 for32s.o for32c.c
一.宣告雖然Linux的核心程式碼大部分是用C語言編寫的,但是不可避免的其中還是有一部分是用匯編語言寫成的。有些組合語言程式碼是直接寫在彙編源程式中的,特別是Linux的啟動程式碼部分;還有一些則是利用gcc的內嵌組合語言嵌在C語言程式中的。這篇文章簡單介紹了gcc中的內嵌式組合語言,主要想幫助那些才開始閱讀Linux核心程式碼的朋友們能夠更快的入手。
寫這篇文章的主要資訊來源是GNU的兩個info檔案:as.info和gcc.info,如果你覺得這篇文章中的介紹還不夠詳細的話,你可以查閱這兩個檔案。當然,直接查閱這兩個檔案可以獲得更加權威的資訊。如果你不想被這兩篇文件中的一大堆資訊搞迷糊的話,我建議你先閱讀一下這篇文章,然後在必要時再去查閱更權威的資訊。
二.簡介
在Linux的核心程式碼中,還是存在相當一部分的組合語言程式碼。如果你想順利閱讀Linux程式碼的話,你不可能繞過這一部分程式碼。在Linux使用的組合語言程式碼中,主要有兩種格式:一種是直接寫成組合語言源程式的形式,這一部分主要是一些Linux的啟動程式碼;另一部分則是利用gcc的內嵌式組合語言語句asm嵌在Linux的C語言程式碼中的。這篇文章主要是介紹第二種形式的組合語言程式碼。
首先,我介紹一下as支援的組合語言的語法格式。大家知道,我們現在學習的組合語言的格式主要是Intel風格的,而在Linux的核心程式碼中使用的則是AT&T格式的組合語言程式碼,應該說大部分人對這種格式的組合語言還不是很瞭解,所以我覺得有必要介紹一下。
接著,我主要介紹一下gcc的內嵌式組合語言的格式。gcc的內嵌式組合語言提供了一種在C語言源程式中直接嵌入彙編指令的很好的辦法,既能夠直接控制所形成的指令序列,又有著與C語言的良好介面,所以在Linux程式碼中很多地方都使用了這一語句。
三.gcc的內嵌組合語言語句asm
利用gcc的asm語句,你可以在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:這也是一個巨集,定義為(*(volatile struct __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."+":運算元在指令中是讀-寫型別的(輸入-輸出運算元)。
22."f":浮點數暫存器。
23."t":第一個浮點數暫存器。
24."u":第二個浮點數暫存器。
27."I":0-31之間的立即數。(用於32位的移位指令)
28."J":0-63之間的立即數。(用於64位的移位指令)
29."N":0-255之間的立即數。(用於"out"指令)
30."G":標準的80387浮點常數。
注:還有一些不常見的限定字元並沒有在此說明,另外有一些限定字元,例如"%","&"等由於我缺乏編譯器方面的一些知識,所以我也不是很理解它們的含義,如果有高手願意補充,不慎感激!不過在核心程式碼中出現的限定字元差不多就是上面這些了。
對《gcc中的內嵌組合語言》一文的補充說明
初次接觸到AT&T格式的彙編程式碼,看著那一堆莫名其妙的怪符號,真是有點痛不欲生的感覺,只好慢慢地去啃gcc文件,在似懂非懂的狀態下過了一段時間。後來又在網上找到了靈溪寫的《gcc中的內嵌組合語言》一文,讀後自感大有裨益。幾個月下來,接觸的原始碼多了以後,慢慢有了一些經驗。為了使初次接觸AT&T格式的彙編程式碼的同志不至於遭受我這樣的痛苦,就整理出該文來和大家共享.如有錯誤之處,歡迎大家指正,共同提高。
本文主要以舉例的方式對gcc中的內嵌組合語言進行進一步的解釋。
一、gcc對內嵌組合語言的處理方式
gcc在編譯內嵌組合語言時,採取的步驟如下:
變數輸入:根據限定符的內容將輸入運算元放入合適的暫存器,如果限定符指定為立即數("i")或記憶體變數("m"),則該步被省略,如果限定符沒有具體指定輸入運算元的型別(如常用的"g"),gcc會視需要決定是否將該運算元輸入到某個暫存器.這樣每個佔位符都與某個暫存器,記憶體變數,或立即數形成了一一對應的關係.這就是對第二個冒號後內容的解釋.如::"a"(foo),"i"(100),"m"(bar)表示%0對應eax暫存器,%1對應100,%2對應記憶體變數bar.
生成程式碼:然後根據這種一一對應的關係(還應包括輸出操作符),用這些暫存器,記憶體變數,或立即數來取代彙編程式碼中的佔位符(則有點像巨集操作),注意,則一步驟並不檢查由這種取代操作所生成的彙編程式碼是否合法,例如,如果有這樣一條指令asm("movl %0,%1"::"m"(foo),"m"(bar));如果你用gcc -c -S選項編譯該原始檔,那麼在生成的彙編檔案中,你將會看到生成了movl foo,bar這樣一條指令,這顯然是錯誤的.這個錯誤在稍後的編譯檢查中會被發現.
變數輸出:按照輸出限定符的指定將暫存器的內容輸出到某個記憶體變數中,如果輸出運算元的限定符指定為記憶體變數("m"),則該步驟被省略.這就是對第一個冒號後內容的解釋,如:asm("mov %0,%1":"=m"(foo),"=a"(bar):);編譯後為:
#APP
movl foo,eax
#NO_APP
movl eax,bar
該語句雖然有點怪怪的,但它很好的體現了gcc的運作方式。
再以arch/i386/kernel/apm.c中的一段程式碼為例,我們來比較一下它們編譯前後的情況:
源程式 編譯後的彙編程式碼
__asm__ (
"pushl %%edi\n\t"
"pushl %%ebp\n\t"
"lcall %%cs:\n\t"
"setc %%al\n\t"
"addl %1,%2\n\t"
"popl %%ebp\n\t"
"popl %%edi\n\t"
:"=a"(ea),"=b"(eb),
"=c"
相關推薦
關於AT&T 彙編:64 位與32 位的區別
下面列出一些不同之處,可以參考這個文件,將會有更詳細的資訊(System V Application Binary Interface AMD64 Architecture Processor Supplement)。 1. 64位系統中函式呼叫不再採用單純壓棧的方式:下
DLL的64位與32位
1、 在64位的windows系統中,一個64位程序不能載入一個32位dll,同理一個32位程序也不能載入一個64位dll。也就是說64bit的作業系統需要注意一個對應關係,64bit的EXE對應64bitDLL,32bit的EXE對應32bit的DLL。 如果你使用
64位與32位系統中函式呼叫中暫存器使用規則
32位系統: Calling Conventions The x86 architecture has several different calling conventions. Fortunately, they all follow the same register
iOS7----64位與32位 對比 資料型別
轉自:http://blog.csdn.net/fhbystudy/article/details/12752885 可用如sizeof(char),sizeof(char*)等得出 32位編譯器 char :1個位元組 char*(即指標變數): 4個位元組(
說說 MD5 加密後的型別(16位與 32位的區別)
MD5 加密後的位數一般為兩種,16 位與 32 位。16 位實際上是從 32 位字串中,取中間的第 9 位到第 24 位的部分,用 Java 語言來說,即: str.substring(8, 24); MD5 加密後的字串又分為大寫與小寫兩種。 所以一個
linux-AT&T彙編,把32位暫存器的值以16進位制字串打印出來
將暫存器的值以16進位制顯示,程式如下:.section .bbs .lcomm buf,10 #定義一個10位元組長度的記憶體區,用來儲存計算出來的字元 .section .text .globl _start _start: //初始化暫存器 movl $0x01abc
64位機器與32位機器的區別
文章目錄 一、區別 二、參考 一、區別 比如32位機器 運算器一次最多可以處理32位資料 暫存器最大寬度32 暫存器和運算器之間的通路為32位 如下面的16位機器,運算器和暫存器之間通路為16位。暫存器
BadImageFormatException:如果在安裝 32 位 Oracle 客戶端元件的情況下以 64 位模式執行
前言 在使用VS2008中伺服器資源管理器連線Oracle資料庫時,嘗試載入 Oracle客戶端庫時引發 BadImageFormatException,在網上下載了個instantclient_11_2進行配置後解決了該問題。 配置過程及相關資訊 專案環境:
64位和32位的暫存器和彙編的比較
64位暫存器分配的不同 區別有: 64位有16個暫存器,32位只有8個。但是32位前8個都有不同的命名,分別是e _ ,而64位前8個使用了r代替e,也就是r _。e開頭的暫存器命名依然可以直接運用於相應暫存器的低32位。而剩下的暫存器名則是從r8
WIN8 與WIN7的64位及32位 分別對Legacy BIOS+MBR和UEFI+GPT兩種啟動方式和分割槽架構下的安裝可行性分析
微軟系統、相關產品官方映象下載:http://msdn.itellyou.cn/關於MBR、GPT、ESP、MSR、EFI、UEFI和WIN8中SECURE BOOT的基礎知識:1.MBR分割槽表:Master Boot Record,即硬碟主引導記錄分割槽表,只支援容量在 2.1TB 以下的硬碟,超過2
Delphi 64與32位的差異
相同點: 在Delphi 64位版本中,UnicodeString,AnsiString,WideString在使用上與32沒有區別,只是索引變成了64位,如:S[I]中的I變成了64位。 Singed types Delphi/32 Delphi/64
Linux在64位執行32位程式(安裝32位庫:rpm、deb)
記錄: ------------------------------------------------------------------- centos7.x 安裝了glibc:glibc-2.17-157.el7.i686 glibc-2.17 nodeps安裝,即可
經常聽說AT&T彙編、Intel彙編,還能聽到ARM彙編,這個ARM彙編與前兩個有什麼關聯?
origin: https://zhidao.baidu.com/question/424592744355848412.htmlAT&T彙編和Intel彙編,是兩種不同組合語言格式,與具體CPU關係不大,只是
Win7 64位登錄檔與32位登錄檔的區別
本文介紹如何通過使用 64 位版本 Windows 檢視 Windows 登錄檔。 64 位版本 Windows 中的登錄檔分為 32 位登錄檔項和 64 位登錄檔項。許多 32 位登錄檔項與其相應的 64 位登錄檔項同名,反之亦然。 64 位版本 Windows
Visual Studio中根據系統區分引用64位、32位DLL動態庫文件的配置方法
問題 eight 找到 下拉 float c# more 遇到 語法 原來使用Win7的32位系統,進行C#工程的開發,後來重裝系統,換成了win7的64位系統 調試原來的工程,由於在其中引用了“SQLite”的32位的dll,導致在64為位下程序無法運行(但是編譯可以通
Ubuntu 64位安裝32位執行庫
學嵌入式時安裝了 Ubuntu 16.04 x64 ,安裝交叉編譯工具鏈的時候出了問題: $ ./arm-none-linux-gnueabi-gcc -v -bash: ./arm-none-linux-gnueabi-gcc: No such file or director
Ubuntu12.04 64位 安裝32位執行庫
在編譯linux核心的時候提示gcc no found 但我直接使用的是gcc 的絕對路徑,使用 arm-linux-gcc -v 命令也是提示沒有這個路徑, 這是因為交叉編譯器是32位的,需要32位的執行庫,以前我們可以使用命令 apt-get install ia32-lib
VC程式裡判斷系統是64位還是32位的正確方法
VC程式裡判斷系統是64位還是32位的正確方法 BOOL IsWow64() { typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); &nbs
轉:快速判斷一個32位的字中是否存在值為"0"的byte
http://www.spongeliu.com/421.html p { margin-bottom: 0.25cm; line-height: 120% } a:link { } 首先為什麼要做這樣的判斷呢? 當你要strcpy活著strcmp或者hash一個字串的時候,傳統的方法是
一個C程式辨別系統是64位還是32位
知識儲備: 計算機的位數取決CPU中暫存器的寬度,具體來說就是算術邏輯運算單元(ALU)的寬度,用來表徵計算機的計算能力,ALU一次可以計算最大長度整數即計算機的位數。 在32bit的計算機中,ALU一次可以計算的最大整數為4個位元組。 在64bit的計算機中。ALU一次可以計算的最