嵌入式系统设计与实践:Linux篇
上QQ阅读APP看书,第一时间看更新

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