《現代X86彙編語言程式設計》的示範程式在windows作業系統“Core”CPU的操作環境下,采用C++語言嵌套intel風格的MASN彙編語言進行編寫,本文拟在Linux作業系統、“Celeron N2840”CPU的環境下,使用C語言嵌套AT&T彙編格式,對書中第一個示範程式ch02_01(中文版第18頁)進行改寫。改寫的程式中變量名稱、變量值、程式結構與示範程式一緻,其餘内容均屬原創。文中主要涉及的知識包括:1、如何在C語言中嵌套彙編語言;2、如何使用gdb調試工具;3、C語言中對變量的定義和指派存放在哪些硬體空間中。程式實作的功能是對變量a、b、c、d進行指派,并輸出a+b+c-d的計算值。
一、在同一檔案夾内建立.c、.asm、Makefile檔案
$touch ch02_01.c
$touch ch02_01_01.asm
$touch Makefile
說明:書中的彙程式設計式檔案名為ch02_01.asm,若采用這個檔案名,在編譯時c程式與彙程式設計式都會産生同名的目标檔案ch02_01.o,導緻編譯失敗。是以彙程式設計式與C程式不能同名。
二、編輯ch02_01.c檔案如下:
#include<stdio.h>
#include<inttypes.h> //該頭檔案的作用是進行轉換進制,如果沒有轉換進制,彙編引用時會被認為是十六進制數
int64_t IntegerAddSub_(int64_t a,int64_t b,int64_t c,int64_t d); //定義即将引用的彙編函數IntegerAddSub_
int main(void)
{
int64_t a;
int64_t b;
int64_t c;
int64_t d;
int result; //由于IntegerAddSub_函數已經轉化為十進制,是以result變量不需在轉進制
a=10;
b=20;
c=30;
d=18;
result=IntegerAddSub_(a,b,c,d); //引起彙編函數
printf("test1\n %ld\n %ld\n %ld\n %ld\n result=%" PRIi64 "\n",a,b,c,d,result);
//PRIi64兩邊有空格,表示列印64位有符号資料,i表示有符号,本案例也可換為%ld
a=101;
b=34;
c=5; //原書案例中此數為負數,本例直接采用負數會出現計算錯誤,因時間關系,以後在繼續研究如何采用負值計算
d=18;
result=IntegerAddSub_(a,b,c,d); //再次引用彙編函數
printf("test2\n %ld\n %ld\n %ld\n %ld\n result=%" PRIi64 "\n",a,b,c,d,result);
return 0;
}
說明:書中的示範程式可以對有符号的負數進行計算,由于時間關系,這次改寫尚不能滿足負數計算,暫時隻能進行無符号的正整數計算。
三、編輯ch02_01_01.asm檔案(以下先展示錯誤代碼,錯誤代碼由書中代碼直接改寫,後面再說明如何調試、修正)
.text //以下為代碼段
.global IntegerAddSub_ //定義标簽IntegerAddSub_,該标簽處的代碼将被C程式作為函數引用
IntegerAddSub_: //标簽入口
movq %rcx,%rax //将寄存器rcx的值賦予rax,意味着第一個變量a的值在rcx中
addq %rdx,%rax //将寄存器rdx與rax的值相加,并将和值存入rax中,意味着第二個變量b存放在rdx中
addq %r8d,%rax //同上
subq %r9d,%rax //将寄存器rax的值減去r9d中,計算值存入rax
ret
四、編輯Makefile檔案
ch02_01:ch02_01_01.o ch02_01.c //表示可執行程式ch02_01由ch02_01_01.o和ch02_01.c共同形成
gcc ch02_01_01.o ch02_01.c -o ch02_01 //用gcc編譯、連結ch02_01_01.o和ch02_01.c,形成可執行檔案ch02,主要開頭縮進要用“TAB”鍵,不能用空格鍵。
ch02_01_01.o:ch02_01_01.asm //表示目标檔案ch02_01_01由彙程式設計式ch02_01_01.asm形成
as ch02_01_01.asm -o ch02_01_01.o //用as編譯彙程式設計式ch02_01_01.asm,形成目标檔案ch02_01_01.o
五、編譯、運作可執行檔案
$make
$./ch02_01
六、調試
步驟五中程式可以正常通過編譯,但是執行可執行檔案後産生錯誤的計算結果,用gdb調試,檢視錯誤原因。
$gdb ./ch02_01
(gdb)break main //在C程式main 函數處打斷
(gdb)run //運作程式,運作至main函數入口處會被中斷
(gdb)disas //顯示彙編代碼
顯示的彙編代碼如下:
push %rbp //在為變量提供位址前,将rbp的位址壓棧儲存
mov %rsp,%rbp //将rsp的位址指派于rbp
sub $0x30,%rsp
movq $0xa,-0x8(%rbp) //rbp位址偏移-0x8的位置處存放數值0xa,即十進制10
movq $0x14,-0x10(%rbp) //rbp位址偏移-0x10的位置處存放數值0x14,即十進制20
movq $0x1e,-0x18(%rbp) //rbp位址偏移-0x18的位置處存放數值0x1e,即十進制30
movq $0x12,-0x20(%rbp) //rbp位址偏移-0x20的位置處存放數值0x12,即十進制18
mov -0x20(%rbp),%rcx //将rbp位址偏移-0x20處的資料指派于rcx
mov -0x18(%rbp),%rdx //将rbp位址偏移-0x18處的資料指派于rdx
mov -0x10(%rbp),%rsi //将rbp位址偏移-0x10處的資料指派于rsi
mov -0x8(%rbp),%rax //将rbp位址偏移-0x8處的資料指派于rax
mov %rax,%rdi
call ...<IntegerAddSub_> //調用IntegerAddSub_函數
說明:由上可知變量a、b、c、d的指派分别存放在寄存器rax、rsi、rdx、rcx中,變量指派位置與書中代碼展示的分别位于ecx、edx、r8d、r9d不同,原因可能是因為使用的CPU不同,是以CPU指令集不同。
七、修正彙程式設計式如下
.text
.global IntegerAddSub_
IntegerAddSub_:
addq %rsi,%rax
addq %rdx,%rax
subq %rcx,%rax
ret
八、編譯、運作
$make
$./ch02_01
最終産生正确計算結果。