1、C嵌套匯編
首先說一下關於GCC編譯嵌有匯編語言的c語言吧,GCC編譯的匯編語言不是我們上課時學的Intel x86匯編,而是AT&T匯編,兩者的區別可以查看《Gcc使用的內嵌匯編語法格式小教程》。
下面是內嵌匯編的格式:語法:__asm__(“instruction
…… instruction"); //Linux gcc中支持(注意asm的下划線均為兩個否則GCC將會無法編譯。)
__asm{
instruction
instruction
}; //ADS中支持(注意asm的下划線均為兩個否則GCC將會無法編譯。)
asm(“instruction [; instruction]”); //ARM C++中使用
例1是我在linux環境下,編的嵌有匯編程序的c語言,並通過了GCC的編譯:
例1:
#include<stdio.h>
int plus(int a,int b)
{
__asm__
(
“add %1,%0\n\t”:”+r”(a):”r”(b)
);
return (c);
}
int main()
{int a,b,c;
a=2;
b=1;
c=plus(a,b);
printf(“c=%d\n”,c);
}
這個程序應該是很簡單的,但關鍵是子函數中嵌入的那段匯編程序,具體的寫法可以參看其他文章。
例2同樣是c語言中嵌入了匯編,與例1不同的是,這個程序的編譯環境為ADS。
例2
#include <stdio.h>
void my_strcpy(char* src, const char* dst){
int ch;
__asm{
loop:
LDRB ch, [src], #1
STRB ch, [dst], #1
CMP ch, #0
BNE loop
};
}
int main(void){
const char* a = "Hello World!";
char b[20];
__asm{
MOV R0, a
MOV R1, b
BL my_strcpy, {R0, R1}
};
printf("Original String: %s\n",a);
printf("Copied String: %s\n",b);
return 0;
}
一定要注意例1與例2中匯編語言的語法格式。
2、C語言調用匯編
再說一下如何將一個c語言文件與一個匯編文件通過ADS環境編譯,並通過ATX進行DEBUG調試的。先看一下下面的例3。
例3
Cfile.c
#include <stdio.h>
extern void strcopy(char *d, const char *s);
int main()
{ const char *srcstr = "abcde";
char dststr[32];
/* dststr is an array since we're going to change it */
printf("Before copying:\n");
printf(" '%s'\n '%s'\n",srcstr,dststr);
strcopy(dststr,srcstr);
printf("After copying:\n");
printf(" '%s'\n '%s'\n",srcstr,dststr);
return 0;
}
Asmfile.s
AREA SCopy, CODE, READONLY
EXPORT strcopy
strcopy
; r0 points to destination string
; r1 points to source string
LDRB r2, [r1],#1 ; load byte and update address
STRB r2, [r0],#1 ; store byte and update address;
CMP r2, #0 ; check for zero terminator
BNE strcopy ; keep going if not
MOV pc,lr ; Return
END ;注意!!匯編代碼編寫時一定要縮進,否則編譯將會出錯
這是一個c語言調用匯編的例子,功能是為了實現字符串的拷貝,其中匯編文件為字符串拷貝的功能子函數。在這里需要說明的是c語言調用匯編語言的一些基本規則,首先是參數傳遞的規則,c語言的函數前4個參數通過R0-R3來傳遞,其它參數通過堆棧(FD)傳遞,且這種傳遞是單項的,即匯編語言中的R0-R3的值不會再回傳給c語言。拿例3舉例來說,當在語言中調用strcopy(dststr,srcstr);時,字符串dststr的首地址將會傳給r0,srcstr的首地址將會傳給r1,當匯編語言拿到這兩個寄存器時,就會通過地址依次加1的形式進行地址內容的復制也就是字符串的復制,當復制到最后一個字母e時,通過比較r2寄存器中的值是否為0來判斷是否調出匯編程序(因為在c語言中聲明字符串時末尾被自動的添加了一個\0),這里需要注意的是,此時寄存器r0的值為指向源字符串末尾的’\0’的地址值,而寄存器r1的值為指向已經拷貝過的目的字符串中的”e”的地址值,當調出匯編程序時,r0,r1這兩個值將不會回傳給strcopy(dststr,srcstr);中的兩個參數dststr和srcstr,這兩個參數的值仍然是c語言在初始化這兩個字符串時指向字符串的首地址,這一點可以通過ATX調試時觀察寄存器的變化情況來證明。但是為什么地址值沒有變化,但卻實現了字符串的拷貝了呢?這主要時因為通過匯編程序,雖然沒有改變兩個指針的位置,但卻改變了兩個字符串所在內存地址中的內容,這種方式就是c語言中常說的引用方式,即dststr和r0起初指向的是同一內存空間,但是字符串復制時只是利用r0來復制的,而dststr的位置卻沒有發生變化。因此在c語言中輸出字符串時並不需要將dststr減去字符串的個數來實現指向字符串的首地址。
這個程序中第二個需要注意的地方是,匯編程序段中的起到臨時存放字符串的r2寄存器,很奇怪的是這個地方的寄存器不能換成r4,如果換成r4的話,輸出的結果就會有問題,這一點我現在還沒有找到答案,希望將來某一天能遇見高人給我指點一下。
最后需要注意的地方是在匯編程序末尾一定要加上MOV pc, lr
用ADS編譯后,兩個文件會被自動的鏈接,並在工程文件夾下生成一個.o文件,這個文件就是將來要下到開發板上的二進制文件,其中還有一個.axf的鏡像文件,這個文件是用來進行ATX調試的,默認的單步調試是在反匯編中進行的,這就會給調試程序帶來極大的不便,通過自己的摸索,發現可以通過設置strong source實現在c語言中進行單步調試,兩外在單步調試中通過watch來觀察c語言中的形參的值和地址的變化情況,便於程序的調試,需要強調的一點時,匯編程序與c程序的文件名不能相同,否則將無法用ATX進行調試。
另外,在匯編程序中訪問c程序全局變量的例子。程序中變量globvl是在c程序中聲明的全局變量。在匯編程序中首先用IMPORT偽操作聲明該變量;再將其內存地址讀入到寄存器R1中;再將其值讀入到寄存器R0中;修改后再將寄存器R0的值賦於變量globvl。請參看例4
例4
#include <stdio.h>
int globvl;
int main(){
globvl = 0;
asmsub();
printf(“globvl = %d”, globvl);
return 0;
}
AREA globals, CODE, READONLY
EXPORT asmsub
IMPORT globvl
asmsub
LDR r1, =globvl
LDR r0, [r1]
ADD r0, r0, #2
STR r0, [r1]
MOV pc, lr
END ;注意!!匯編代碼編寫時一定要縮進,否則編譯將會出錯
3、匯編調用c
最后我再談一下如何在匯編中調用c,看一下例5
例5
int g(int a, int b, int c, int d, int e)
{
return a + b + c + d + e;
}
;匯編程序調用c程序g()計算5個整數i, 2*i, 3*i, 4*i, 5*i的和
EXPORT f
AREA f, CODE, READONLY
IMPORT g ;使用偽操作數IMPORT聲明c程序g()
STR lr, [sp,#-4]! ;保存返回地址
ADD r1, r0, r0 ;假設進入程序f時,r0中的值為i,r1值設為2*i
ADD r2, r1, r0 ;r2的值設為3*i
ADD r3, r1, r2 ;r3的值設為5*i
STR r3, [sp, # -4]! ;第五個參數5*i通過數據棧傳遞
ADD r3, r1, r1 ;r4值設為4*i
BL g ;調用c程序g()
ADD sp, sp, #4 ;調整數據棧指針,准備返回
LDR pc, [sp], #4 ;返回
END ;注意!!匯編代碼編寫時一定要縮進,否則編譯將會出錯
注意,c語言最終返回的五個數之和放到了r0寄存器中。
