- 嵌入式系統設計與實踐:Linux篇
- 季江民
- 1839字
- 2020-04-03 09:21:46
3.4 匯編語言的程序結構及在ADS環境下調試
在ARM(Thumb)匯編語言程序中,以程序段為單位組織代碼。段是相對獨立的指令或數據序列,具有特定的名稱。段可以分為代碼段和數據段,代碼段的內容為執行代碼,數據段存放代碼運行時需要用到的數據。一個匯編程序至少應該有一個代碼段,當程序較長時,可以分割為多個代碼段和數據段,多個段在程序編譯鏈接時最終形成一個可執行的映象文件。
3.4.1 匯編語言程序結構
以下是一個匯編語言源程序的基本結構。
AREA Init, CODE, READONLY ENTRY …… END ;匯編語言源程序的基本結構,分號為注釋 AREA EX2, CODE, READONLY ;AREA指令定義一個名為EX2程序段,屬性為只讀
例3.1 定義一個代碼段arm,其屬性為只讀,首先分別給3個變量賦值,給x、y賦兩個值,給stack_top賦一個地址,程序代碼如下,請閱讀程序寫出最后r0的值及調試程序及地址0x1000上的內容,程序名為ex3_1.s。
AREA arm , CODE , READONLY ;要有空格 x EQU 45 ; 不能有空格 y EQU 64 stack_top EQU 0x1000 ENTRY MOV sp, #stack_top MOV r0, #x STR r0, [sp] MOV r0, #y LDR r1, [sp] ADD r0, r0, r1 STR r0, [sp] Stop B Stop END
注意:
標簽必須在一行的開頭頂格寫,不能有空格;
ARM指令前要留有空格,可全部大寫或小寫,但不要大小寫混合使用;
注釋使用“; ”。
3.4.2 匯編語言編輯、運行與調試
(1)運行ADS1.2集成開發環境,點擊File|New,在New對話框中,選擇Project中的ARM Executable Image選項,在Project name欄中輸入項目的名稱ex3_1,點擊“確定”按鈕保存項目,如圖3.6所示。

圖3.6 輸入工程名
(2)在新建的工程中,選擇Debug版本,如圖3.7所示,使用Edit|Debug Settings菜單對Debug版本進行參數設置。

圖3.7 選擇Debug
(3)在圖3.8中,點擊Debug Setting按鈕,彈出如圖3.9所示的窗口,選中Target Setting項,在Post-linker欄中選中ARM fromELF項,如圖3.9所示。按OK確定。這是為生成可執行的代碼的初始開關。

圖3.8 點擊Debug Settings圖標

圖3.9 選擇ARM from ELF
(4)在圖3.10中,點擊ARM Assembler,在Architecture or Processer欄中選ARM9TDMI,這是要編譯的CPU核。

圖3.10 選擇設備的處理器
(5)在圖3.11中,點擊ARM linker,在output欄中設定程序的代碼段地址,以及數據使用的地址。圖中的RO Base欄中填寫程序代碼存放的起始地址,RW Base欄中填寫程序數據存放的起始地址。該地址是屬于SDRAM的地址。

圖3.11 存儲器讀寫地址設置
在Options欄中,如圖3.12所示,Image entry point要填寫程序代碼的入口地址,其他保持不變,如果是在SDRAM中運行,則可在0x0c000000—0x0cffffff中選值,這是16MSDRAM的地址,但是這里用的是起始地址,所以必須把你的程序空間給留出來,并且還要留出足夠的程序使用的數據空間,而且還必須是4字節對齊的地址(ARM狀態)。通常入口點Image entry point為0xc100000, ro_base也為0xc100000。

圖3.12 程序執行入口地址設置
在Layout欄中,如圖3.13所示,在Place at beginning of image框內,需要填寫項目的入口程序的目標文件名,如,整個工程項目的入口程序是ex3_1.s,那么應在Object/Symbol處填寫其目標文件名ex3_1.o,在Section處填寫程序入口的起始段標號。它的作用是通知編譯器,整個項目的開始運行是從該地址段開始的。

圖3.13 輸入目標文件名及標號
(7)在圖3.14中,在Debug Setting對話框中點擊左欄的ARM fromELF項,在Output file name欄中設置輸出文件名*.bin,前綴名可以自己取,在Output format欄中選擇Plain binary,這是設置要下載到Flash中的二進制文件。圖3.14中使用的是ex3_1.bin.

圖3.14 輸入可在開發板上執行的文件名
(8)輸入匯編源程序
點擊File|New,在New對話框中,選擇File選項,在File name欄中輸入文件名ex3_1.s,如圖3.15所示,點擊“確定”按鈕后輸入源程序并保存。

圖3.15 輸入匯編程序名
(9)裝載文件ex3_1.s
在如圖3.16所示工程ex3_1.mcp中File的空白處點擊右鍵,選擇菜單項Add Files,導入文件ex3_1.s,并按圖3.17的選項,點擊按鈕[ OK ]。

圖3.16 導入文件ex3_1.s

圖3.17 選擇調試類型
(10)在ADS1.2集成開發環境(CodeWarrior for ARM Developer Suite)選擇菜單Project|Debug。
(11)如果是模擬調試,選擇AXD中菜單Options→Configure Target→選擇ARMUL,如圖3.18所示。

圖3.18 選擇仿真調試
ADS開發工具中分別支持兩種情況的目標調試。ARMUL目標環境配置,此是AXD鏈接到用軟件模擬的目標機;第二是選擇ADP目標環境配置,它是AXD使用Angel調試協議鏈接到開發板硬件進行的調試。
(12)程序調試
① 查看/修改存儲器內容
在AXD窗口中,點擊Processor Views|Memory,可以在Memory start address輸入存儲器的起始地址,查看存儲器某地址上的內容,雙擊某一數據,可以修改此存儲單元的內容。
② 在命令行窗口執行AXD命令
在AXD窗口中,點擊System Views|Command Line Interface,在提示符>下可以輸入命令進行調試。
③ 監視變量變化
在AXD窗口中,點擊Processor Views|Watch,用鼠標選中某變量,單擊鼠標右鍵,在彈出的菜單中選中Add to watch,此變量顯示在watch窗口中。
④ 設置斷點
將光標定位在要設置斷點的某語句處,按F9鍵。調試的結果如圖3.19所示,圖中顯示了斷點、Watch中寄存器的值、存儲器從起始地址0x1000開始的存儲內容。

圖3.19 程序ex3_1.s調試情況
思考:在ADS環境下調試下列源程序(ASM.S)代碼。
rGPFCON EQU 0x56000050 rGPFDAT EQU 0x56000054 rGPFUP EQU 0x56000058 AREA Init , CODE , READONLY ENTRY ResetEntry ldr r0, =rGPFCON ldr r1, =0x4000 str r1, [r0] ldr r0, =rGPFUP ldr r1, =0xffff str r1, [r0] ldr r2, =rGPFDAT ledloop ldr r1, =0x1ffff str r1, [r2] bl delay ldr r1, =0x0 str r1, [r2] bl delay b ledloop 延時子程序 delay ldr r3, =0x1ffff delay1 sub r3, r3, #1 cmp r3, #0x0 bne delay1 mov pc, lr END
3.5 匯編語言與C/C++的混合編程
在應用系統的程序設計中,若所有的編程任務均用匯編語言來完成,其工作量是可想而知的,同時,也不利于系統升級或應用軟件移植。事實上,ARM體系結構支持C/C++以及與匯編語言的混合編程。在一個完整的程序設計中,除了初始化部分用匯編語言完成以外,其主要的編程任務一般都用C/C++ 完成。
匯編語言與C/C++的混合編程通常有以下幾種方式:
(1)在C/C++代碼中嵌入匯編指令;
(2)在匯編語言程序和C/C++的程序之間進行變量的互訪;
(3)匯編語言程序、C/C++程序間的相互調用。
在以上的幾種混合編程技術中,必須遵守一定的調用規則,如物理寄存器的使用、參數的傳遞等,這對于初學者來說,無疑顯得過于煩瑣。在實際的編程應用中,使用較多的方式是:程序的初始化部分用匯編語言完成,然后用C/C++完成主要的編程任務,程序在執行時首先完成初始化過程,然后跳轉到C/C++程序代碼中,匯編語言程序和C/C++程序之間一般沒有參數的傳遞,也沒有頻繁的相互調用,因此,整個程序的結構顯得相對簡單,容易理解。
3.5.1 C語言程序調用匯編語言程序
1.在C語言中內嵌匯編
在C語言中內嵌的匯編指令包含大部分的ARM和Thumb指令,不過其使用方式與匯編文件中的指令有些不同,存在一些限制,主要有下面幾個方面:
(1)不能直接向PC寄存器賦值,程序跳轉要使用B或者BL指令;
(2)在使用物理寄存器時,不要使用過于復雜的C語言表達式,避免物理寄存器沖突;
(3)R12和R13可能被編譯器用來存放中間編譯結果,計算表達式值時可能將R0到R3、R12及R14用于子程序調用,因此要避免直接使用這些物理寄存器;
(4)一般不要直接指定物理寄存器,而讓編譯器進行分配。
(a)匯編指令以語句塊形式嵌入在C語言程序的函數中,其使用格式為:
_asm { 匯編語句 }
(b)匯編指令以函數形式嵌入在C語言程序的函數中,其使用格式為:
_asm int函數名(形式參數表) { 匯編代碼 }
例3.2 在C語言程序中嵌入匯編語句的例子,即將匯編指令以語句塊形式嵌入在C語言程序中。
#include<stdio.h> int add(int i, int j) { int sum; _asm { ADD sum, i, j } return sum; } int main() { int x, y; scanf("%d %d", &x, &y); printf("%d+%d=%d\n", x, y, add(x, y)); return 0; }
請建立一個工程,調試上述程序。
例3.3 請在ADS環境中調試下列程序,程序表明如何在C語言程序中內嵌匯編語言。
#include<stdio.h> void my_strcpy(const char *src, char *dest) { char ch; _asm { loop : ldrb ch , [src], #1 strb ch , [dest], #1 cmp ch, #0 bne loop } } int main() { char *a = "forget it and move on! "; char b[64]; my_strcpy(a, b); printf("original: %s", a); printf("copyed: %s", b); return 0; }
2.在C語言程序中調用以函數形式構成的匯編指令
C語言程序調用匯編語言程序時,匯編語言程序的書寫也要遵循ATPCS規則,以保證程序調用時參數正確傳遞。在C語言程序中調用匯編語言子程序的方法為:首先在匯編程序中使用EXPORT偽指令聲明被調用的子程序,表示該子程序將在其他文件中被調用;然后在C語言程序中使用extern關鍵字聲明要調用的匯編子程序為外部函數。
例如:在一個匯編源文件中定義了如下求和函數。
EXPORT add ; //聲明add子程序將被外部函數調用 …… add ; //求和子程序add ADD r0, r0, r1 MOV pc, lr ……
在一個C語言程序的main()函數中對add匯編子程序進行了調用:
extern int add (int x, int y); //聲明add為外部函數 void main( ) { int a=1, b=2, c; c=add(a, b); //調用add子程序 …… }
當main()函數調用add匯編語言子程序時,變量a、b的值會給了r0和r1,返回結果由r0帶回,并賦值給變量c。函數調用結束后,變量c的值變成3。
例如:
建立文件add.s,代碼如下:
EXPORT add AREA add, CODE, READONLY ENTRY ADD r0, r0, r1 MOV pc, lr END C程序代碼為: #include<stdio.h> extern int add(int x, int y); int main() { int x, y; scanf("%d %d", &x, &y); printf("%d+%d=%d\n", x, y, add(x, y)); return 0; }
程序調試方法
首先建立一個工程test。
按照圖3.20、圖3.21所示建立源程序main.c與add.s,請注意選中“Add to Project”,并分別輸入C語言源程序與匯編語言源程序。

圖3.20 編輯匯編文件add.s

圖3.21 編輯C程序文件main.c
3.在圖3.22中設置ARM Linker中Output的RO Base地址設為0x400000

圖3.22 程序調試
4.設置程序開始執行的地址,如圖3.23所示。在ARM Linker的Options標簽中

圖3.23 程序執行起始地址
5.如圖3.24所示,設置程序從main.o開始執行

圖3.24 設置起始執行程序main.o
6.執行菜單Procject下的make命令,編譯程序
7.執行程序run,如圖3.25所示

圖3.25 程序執行結果
3.5.2 匯編程序調用C語言程序
在匯編程序中調用C語言程序,格式較為簡單。其格式為:
BL C函數名
例如:
第一步:新建一個工程項目test3.mcp后再新建一個init.s匯編語言程序,這個程序是該項目文件的入口程序,程序代碼為:
AREA asm, CODE, READONLY IMPORT add ENTRY LDR r0, =0x1 LDR r1, =0x20 LDR r2, =0x2 BL add ; result saved in r0 B . END
第二步:新建一個main.c程序,程序代碼為:
int add(int a, int b, int c) { int sum=0, i; for(i=a; i<=b; i=i+c) sum=sum+i; return sum; }
第三步:在ADS1.2集成開發環境(CodeWarrior for ARM Developer Suite)選擇微處理器、RO Base地址、程序執行的首地址、程序開始執行的函數Init.o等環境參數。
第四步:選擇菜單Project|Make后,點擊Project|Debug,轉入AXD環境。
第五步:在AXD環境中,點擊Ecxute|Go,然后進行單步調試。
第六步:在調試過程中把變量r0、r1、r2、i、sum添加到Watch窗口,觀察這些變量的變化情況。
思考:請調試下列程序。
(1)建立文件init.s,代碼如下:
AREA Init , CODE, READONLY ENTRY ResetEntry IMPORT Main EXPORT delay delay sub r0, r0, #1 cmp r0, #0x0 bne delay mov pc, lr END
(2)C語言程序代碼為:
#include<stdio.h> #define rGPFCON ( * ( volatile unsigned *)0x56000050) #define rGPFDAT ( * ( volatile unsigned *)0x56000054) #define rGPFUP ( * ( volatile unsigned *)0x56000058) extern delay(int time); int main() { rGPFCON=0x4000 ; rGPFUP=0xffff ; while(1) { rGPFDAT=0xff ; delay(0xbffff); rGPFDAT=0x0 ; delay(0xbffff); rGPFUP=0xffff ; } return 0; }
思考與實驗
一、判斷題
1.ARM中有下列匯編語言語句:
ldr r0, =rGPFCON
表示將寄存器rGPFCON的內容存放到寄存器r0中。( )
2.ARM中有下列匯編語言語句:
ldr r1, =0x4000
表示將立即數0x4000加載到r1寄存器中。( )
3.ARM中有下列匯編語言語句:
str r1 , [r0]表示將r1中的數據存放到寄存器r0中。( )
4.ARM中有下列匯編語言語句:
ldr r1 , [r2]
表示將r2中的數據作為地址,取出此地址中的數據保存在r1中。( )
5.ARM中有下列匯編語言語句:
ldr r0 , [r1, #4]
表示將寄存器r1的內容加上4,然后把此數保存在寄存器r0中。( )
6.ARM中有下列匯編語言語句:
b ledon
表示調用子程序ledon。( )
7.ARM中有下列匯編語言語句:
sub r0, r0, #1
表示r0+1地址上的內容存放到寄存器r0中。( )
8.ARM中有下列匯編語言語句:
cmp r0, #x0
表示將r0的值與0進行比較。( )
9.ARM中有下列匯編語言語句:
ldr r0, =rGPFCON
str r1 , [r0]
表示將r0中的數據存放到寄存器r1中。( )
二、程序調試
1.在ADS中調試下列程序。
/* main.c */ #include <stdio.h> extern void asm_strcpy(const char *src, char *dest); int main() { const char *s = "seasons in the sun"; char d[32]; asm_strcpy(s, d); printf("source: %s", s); printf(" destination: %s", d); return 0; } ;匯編語言程序作為函數調用 AREA asmfile, CODE, READONLY EXPORT asm_strcpy asm_strcpy loop ldrb r4, [r0], #1 address increment after read cmp r4, #0 beq over strb r4, [r1], #1 b loop over mov pc, lr END
2.在匯編和C之間通過定義全局變量實現數據傳送,請調試程序。
main.c文件 #include <stdio.h> int gVar_1 = 12; extern asmDouble(void); int main() { printf("original value of gVar_1 is: %d", gVar_1); asmDouble(); printf(" modified value of gVar_1 is: %d", gVar_1); return 0; } 匯編語言文件 AREA asmfile, CODE, READONLY EXPORT asmDouble IMPORT gVar_1 asmDouble ldr r0, =gVar_1 ldr r1, [r0] mov r2, #2 mul r3, r1, r2 str r3, [r0] mov pc, lr END
3.閱讀下列匯編程序,并在ADS環境下上機調試。
AREA LDR_STR_LSL_LSR, CODE, READONLY ENTRY ;******************************************************************** ******** ; 加載/存儲指令以及移位指令 ;******************************************************************** ******** start PRO1 LDR R0, =0x0000 LDR R1, =0x0004 LDR R0, [R1] ;將存儲器地址為R1的字數據讀入寄存器R0 LDR R0, =0x0000 LDR R1, =0x0004 LDR R2, =0x0008 LDR R0, [R1, R2] ;將存儲器地址為R1+R2的字數據讀入寄存器R0 LDR R0, =0x0000 LDR R1, =0x0004 LDR R2, =0x0008 LDR R0, [R1], R2 ;將存儲器地址為R1的字數據讀入寄存器R0,并將新地址R1+R2寫入 R1 LDR R0, =0x0000 LDR R1, =0x0004 LDR R0, [R1, #8] ;將存儲器地址為R1+8的字數據讀入寄存器R0 AND R0, R0, #0 ;保持R0的0位,其于位清0 LDR R1, =0X0004 LDR R0, [R1, #8]! ;將存儲器地址為R1+8的字數據讀入寄存器R0,并將新地址R1+8寫 入R1 LDR R0, =0x0000 LDR R1, =0x0004 LDR R0, [R1], #8 ;將存儲器地址為R1 的字數據讀入寄存器R0,并將新地址R1+8 寫入 R1 LDR R0, =0x0000 LDR R1, =0x0004 LDR R2, =0x0008 LDR R0, [R1, R2, LSL#2]! ;將存儲器地址為R1+R2×4的字數據讀入寄存器R0,并將新地址R1+R2×4寫入R1 LDR R0, =0x0000 LDR R1, =0x0004 LDR R2, =0x0008 LDR R0, [R1], R2, LSR#2 ;將存儲器地址為R1的字數據讀入寄存器R0,并將新地址R1+R2/4寫入R1 PRO2 LDR R0, =0x0000 LDR R1, =0x0004 STR R0, [R1], #8;將R0中的字數據寫入以R1為地址的存儲器中,并將新地址R1+8寫 入R1 STR R0, [R1, #8] ;將R0中的字數據寫入以R1+8為地址的存儲器中 B PRO1 END
4.下列是匯編程序調用C語言程序一個示例,請分析程序。
;******************************************************************** ***** ; Institute of Automation, Chinese Academy of Sciences ;File Name: Init.s ;Description: ;Author: JuGuang, Lee ;Date: ;******************************************************************** **** IMPORT Main ;通知編譯器該標號為一個外部標號 ;定義一個代碼段 AREA Init, CODE, READONLY ;定義程序的入口點 ENTRY ;初始化系統配置寄存器 LDR R0, =0x3FF0000 LDR R1, =0xE7FFFF80 STR R1, [R0] ;初始化用戶堆棧 LDR SP, =0x3FE1000 ;跳轉到Main( )函數處的C/C++代碼執行 BL Main ;標識匯編程序的結束 END 以上的程序段完成一些簡單的初始化,然后跳轉到Main( )函數所標識的C/C++代碼處執行 主要的任務,此處的Main僅為一個標號,也可使用其他名稱,與C語言程序中的main( ) 函數沒有關系。 /******************************************************************** * Institute of Automation, Chinese Academy of Sciences * File Name: main.c * Description: P0, P1 LED Flash. * Author: JuGuang, Lee * Date: ********************************************************************/ void Main(void) { int i; *((volatile unsigned long *) 0x3ff5000) = 0x0000000f; while(1) { *((volatile unsigned long *) 0x3ff5008) = 0x00000001; for(i=0; i<0x7fFFF; i++); *((volatile unsigned long *) 0x3ff5008) = 0x00000002; for(i=0; i<0x7FFFF; i++); } }