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++); } }