【软件开发底层知识修炼】十二 C/C++语言中内嵌汇编语言(asm)
上一篇文章學習了鏈接腳本的語法與相關概念:鏈接腳本的概念
在繼續學習鏈接器的內容的同時,先學習一個新內容:內嵌匯編。
GCC編譯器一般支持C/C++內嵌匯編語言,這樣可以實現語言本身無法實現的內容。我們本文主要介紹C語言中的內嵌匯編,C++語言也是一樣的規則。
首先要知道以下內容
x86匯編的兩種語法:intel語法和AT&T語法
x86匯編一直存在兩種不同的語法,在intel的官方文檔中使
用intel語法,Windows也使用intel語法,而UNIX平臺的匯編器一
直使用AT&T語法,所以本文是AT&T語法。 mov %edx,%eax 這條
指令如果用intel語法來寫,就是 mov eax,edx ,寄存器名不加 % 號,
并且源操作數和目標操作數的位置互換。本文不詳細討論這兩種
語法之間的區別,讀者可以參考[AssemblyHOWTO]。
介紹x86匯編的書很多,UNIX平臺的書都采用AT&T語法,例
如[GroudUp],其它書一般采用intel語法,例如[x86Assembly]。
1、C語言中的內嵌匯編
首先看一下C語言中的內嵌匯編語法格式:
- 上述的volatile 關鍵字在以后會講解具體作用,這里知道它是禁止編譯器優化即可
- 可選參數的意思是這些參數可以沒有
- 匯編指令是必須有的項
上述的解釋還不是很好理解,我們可以看一個示例:
在以上的內嵌匯編中,我們可以來分析一下它的語法規則:
- “movl %1, %0\n” 這句話中,%的意思是占位的意思。%1代表是一個input對應的寄存器,%0代表的是result對應的寄存器。他們所對應的寄存器是任意的,因為下面的限制符前面是 r ,代表編譯器自動將通用寄存器與變量進行關聯。當然這里也可以直接指定通用寄存器不用占位符。
- 上面的圖中說r這個限制符只是建議編譯器用通用寄存器來與變量相關,但是編譯器不一定聽,比如如果在某個時候通用寄存器已經被被人占用,此時編譯器就不會使用通用寄存器來關聯我們的變量。
- 帶 = 號的是輸出參數,即result是輸出參數
- input是輸入參數
- 上述匯編的意思是將輸入參數input的值,傳送給輸出參數的值result。所以執行完上述匯編代碼后,result與input的值都為1.
看了上面的解釋,我們大概學會了內嵌匯編的組成,大致有匯編指令,這是必須存在的,可選參數,這是不必須存在的。
我們注意到上面的限制符 r 代表編譯器指定一個通用寄存器關聯變量,這是讓編譯器做主。但是我們也可以自己做主,自己指定一個寄存器來關聯我們的變量。那么,都有哪些限制符以及他們對應的寄存器呢?常用的見下表:
上述的r代表通用寄存器,很明顯與我們剛學習的一樣。
1.1 代碼實驗
在知道了上述規則之后,我們來看看一個例子:
9-1.c
#include <stdio.h>int main() {int result = 0;int input = 1;int a = 1;int b = 2;asm volatile ("movl %1, %0\n": "=r"(result): "r"(input));printf("result = %d\n", result);printf("input = %d\n", input);asm volatile ("movl %%eax, %%ecx\n""movl %%ebx, %%eax\n""movl %%ecx, %%ebx\n": "=a"(a), "=b"(b): "a"(a), "b"(b));printf("a = %d\n", a);printf("b = %d\n", b);return 0; }編譯:
- gcc 9-1.c
運行結果如下:
- 分析上述代碼
-
分析第二個代碼塊
- "=a"(a), "=b"(b) 代表輸出參數,且將EAX寄存器與變量a關聯,將EBX寄存器與b相連
- "a"(a), "b"(b) 代表輸入參數,且將EAX寄存器與變量a關聯,將EBX寄存器與b相連
- movl %%eax, %%ecx\n" 將EAX寄存器的值(也就是a的值)傳送給ECX寄存器
- movl %%ebx, %%eax\n" 將EBX寄存器的值(也就是b的值)傳送給EAX,相當于將b賦給a
- movl %%ecx, %%ebx\n" 將ECX寄存器的值(也就是a的值)傳送給EBX,相當于將a賦給b
經過了上面的操作,就交換了a與b的值。
2、使用內嵌匯編進行系統調用
在程序中我們經常使用printf打印東西。中所周知,printf是一個系統函數,我們如何在不使用printf的前提下打印字符串?
使用內嵌匯編可以進行系統調用。
可以通過INT 0X80H 指令進行系統調用:
- INT指令用于使用Linux內核服務(這是一個中斷指令,眾所周知中斷會使執行流切換到內核)
- 80H是一個中斷向量號,用于執行系統調用
我們還是具體來看一個例子吧:
上面的解釋是非常清楚的。注意區分傳遞的是立即數還是占位符即可。
同時,上面的例子 加了保留列表。它的意思是保留寄存器,不用于關聯變量,因為這些寄存器已經被我們用于做系統調用以及傳參數了。
那么上述匯編代碼執行后參數s與參數l會被傳給系統調用函數sys_write 。
下面再給出一個示例來看看與上面的示例有什么區別:
上面這個示例沒有可選參數與保留列表。也就是沒有輸入輸出參數,沒有保留列表。
除了上述的區別,還有什么區別???
很明顯,這個寄存器的前面是一個%,而剛剛那個是%%兩個百分號。這是什么原因?
那么在內嵌匯編中就有如下注意事項:
-
嵌入匯編時,除了匯編語言的指令不能省略,可選參數與保留列表都可以省略
-
當省略的參數在中間時, 對應分隔符 “ : ”不可省略.如下圖中的輸出參數的分隔符:
-
當省略保留列表時,對應的“ : ”可忽略
-
當省略可選參數時,寄存器前使用 % 作為前綴
-
當有可選參數時,寄存器前使用 %% 作為前綴
2.1 代碼實驗
在學習了上述的一系列原理后,我們寫出如下代碼:
9-2.c
#include <stdio.h>int main() {char* s = "D.T.Software\n";int l = 13;printf("main begin\n");asm volatile("movl $4, %%eax\n""movl $1, %%ebx\n""movl %0, %%ecx\n""movl %1, %%edx\n""int $0x80 \n":: "r"(s), "r"(l): "eax", "ebx", "ecx", "edx");asm volatile("movl $1, %eax\n""movl $42, %ebx\n""int $0x80 \n");printf("main end\n");return 0; }運行結果:
可以看出,我們使用內嵌匯編打印出了"D.T.Software\n"; 并且,在第二個匯編代碼塊中,進行系統調用調用了sys_exit 函數,直接退出進程運行了,所以第二個printf("main end\n"); 并沒有執行。
當然我們也可以使用以下命令查看最近一次的一個進程退出時的退出狀態碼:
- echo $?
得出:
很明顯,退出狀態碼是42,正好與我們程序中寫的一樣。
3、 總結
- C程序中支持內嵌匯編
- 通過寄存器到匯編的關聯,可以實現匯編到C程序的交互
- 內嵌匯編代碼時,可以使用占位符指定交互的變量
- 限制符建議編譯器將合適的寄存器關聯到變量
- 通過內嵌匯編可以直接進行系統調用
本文參考狄泰軟件學院相關課程
想學習的可以加狄泰軟件學院群,
群聊號碼:199546072
學習探討加個人(可以免費幫忙下載CSDN資源):
qq:1126137994
微信:liu1126137994
總結
以上是生活随笔為你收集整理的【软件开发底层知识修炼】十二 C/C++语言中内嵌汇编语言(asm)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: halcon/c++接口基础 之内存管理
- 下一篇: halcon/c++接口基础 之异常处理