ARM裸机程序备忘录之一(ARM汇编)

本文介绍ARM汇编语言的基础知识,包括存储器访问类指令、算术逻辑指令、寻址方式及程序控制伪指令等内容。此外,还介绍了ARM处理器的运行模式、可执行文件格式、交叉工具链使用及连接器脚本的编写。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.ARM汇编

       (1).存储器访问类指令:


MOV:从另一个寄存器、被移位的寄存器、或一个立即值装载一个值到目的寄存器

            mov  R0,,R0       ;R0 = R0  相当于NOP

            mov  R0,  R0,LSL#3    ;R0=R0*8

            mov   PC,R14       ;退出到调用者,注意R14寄存器链接寄存器LR


STR:将寄存器中的值存储到内存中

           STR    Rd,[Rbase]       ;存储Rd到Rbase所包含的有效地址

           STR    Rd,[Rbase,Rindex]     ;存储Rd到Rbase+Rindex所合成的有效地址

           STR    Rd,[Rbase,#index]      ;存储Rd到Rbase+index所合成的有效地址。#index是一个立即数

           STR    Rd,[Rbase,Rindex]!     ;存储Rd到Rbase+Rindex所合成的有效地址,并且把这个新地址写回到Rbase 

           STR    Rd,[Rbase,#index]!     ;存储Rd到Rbase+index所合成的有效地址,并且把这个新地址写回到Rbase 

           STR    Rd,[Rbase],Rindex     ;存储Rd到Rbase所包含的有效地址,并且把Rbase+Rindex新地址写回到Rbase 

           STR    Rd,[Rbase,Rindex,LSL#2]     ;存储Rd到Rbase+Rindex*4所合成的有效地址

           STR    Rd,place      ;存储Rd到PC+place所包含的有效地址

LDR:从内存中装载32位数据到指定的寄存器中

         操作同STR,是其逆操作


ADD:把两个操作数加起来,把结果放置到目的寄存器中。ADC是带进位的ADD

        ADD R0,R1,R2        ;R0=R1+R2

        ADD R0,R1,#256    ;R0=R1+256

        ADD R0,R2,R3,LSL#1        ;R0=R2+(R3<<1)


SUB:把两个操作数相减,把结果放置到目的寄存器中。SBC是带错位的SUB。

       SUB   R0,R1,R2    ;R0=R1-R2

       SUB   R0,R1,#256    ;R0=R1-256

       SUB   R0,R2,R3,LSL#1    ;R0=R2-(R3<<1)


AND:在两个操作数上进行逻辑与,把结果放置到目的寄存器

      AND  R0,R0,#3      ;R0 =   保持R0的位0和1,丢弃其他的位


ORR:在两个操作数上进行逻辑或,把结果放置到目的寄存器

     ORR  R0,R0,#3      ;R0 =   设置R0的位0和1


EOR:在两个操作数上进行逻辑异或,把结果放置到目的寄存器中

     EOR  R0,R0,#3      ;反转R0中的位0和1


CMP:把两个操作数相减,并把结果反映到状态标志位上。

     CMP  R1,R2     :如果R1>R2,则此后有HI后缀的指令将被执行,否则有LS后缀的指令将被执行。


TST:将两个操作数进行位与操作,并把结果反映到状态标志位Zero上

     TST  R0,#%1          ;测试在R0中是否设置了位0,如果是,之后带NE后缀的指令将被执行。


BIC:在一个32位数据中清楚指定的位

      BIC  R0,R0,#%1011    ;清除R0中的位0、1和3.保持其余的不变


MVN:把一个被取反的值传送到一个寄存器

      MVN  R0,#4     ;R0=-5

      MVN  R0,#0      ;R0=-1


二.ARM寻址方式:

1.立即寻址:取出指令也就取出了可以立即使用的操作数

        SUB  R0 ,R0,#1      ;R0减1,结果放入R0,并影响标志位

        MOV R0,#0xFF000   ;将立即数0xff装入寄存器R0

2.寄存器寻址,操作数的值在寄存器中,指令执行时直接取出寄存器值来操作

        MOV  R1,R2      ;将R2的值存入R1

        SUB   R0,R1,R2    ;将R1的值减去R2的值,结果保存到R0

3.寄存器移位寻址:第二个寄存器操作数在与第一个寄存器操作数结合之前,选择进行移位操作

       MOV   R0,R2,LSL #3   ;R2的值左移三位,结果放入R0,即R0=R2*8

       ANDS  R1,R1,R2,LSL R3   ;R2的值左移R3位,然后和R1相“与”操作,结果放入R1

4.寄存器间接寻址:寄存器中保存操作数的指针地址

      LDR  R1,[R2]    ;将R2指向的存储单元的数据读出保存在R1中

      SWP  R1,R1,[R2]    ;将寄存器R1的值和R2指定的存储单元的内容交换

5.基址变址寻址:将基址寄存器的内容与指令中给出的偏移量相加,形成操作数的有效地址

     LDR  R0,[R1,#4]    ;读出

     STR  R0,[R2,#4];将R0的值存入R2+4地址的存储单元中


6.多寄存器寻址:一次传送几个寄存器值,允许一条指令传送16个寄存器的任何子集或所有寄存器

     LDMIA   R1!,{R2-R7,R12};将R1指向的单元中的数据读出到R2~R7、R12中,然后R1自动加1

     STMIA   R0!,{R2-R7,R12};将寄存器R2~R7、R12的值保存到R0指向的存储单元中(R0自动加1)


7.相对寻址: 相对寻址是基址寻址的一种变通。由程序计数器PC提供基准地址,指令中的地址码字段作为偏移量,两者相加后得到的地址即为操作数的有效地址。

    BL  SUBR1   ;跳转到SUBR1子程序

    ……

    LOOP  MOV  R6,#1

    …… 

    SUBR1……

三.程序控制伪指令

(1)符号定义伪指令:用于定义ARM汇编中的变量、对变量赋值以及定义寄存器的别名等操作。常见的符号定义伪指令有如下几种:

定义全局变量

GBLA:定义一个全局的数字变量,并初始化为0   GBLA  Test1

GBLL:定义一个全局的逻辑变量,并初始化为F(假)

GBLS:定义一个全局的字符串变量,并初始化为空


定义局部变量:同理全局变量,指令为LCLA,LCLL,LCLS


对变量赋值:SETA(数字)、SETL(逻辑)、SETS(字符串


为通用寄存器列表定义名称的RLIST


(2)数据定义伪指令:一般用于为特定的数据分配存储单元,同时可以完成已分配单元的初始化


DCB  伪指令用于分配一片连续的字节存储单元并用伪指令中指定的表达式初始化。DCB可以用“=”代替

         Str  DCB  “This is a Test!”   ;分配一片连续的字节存储单元并初始化


SPACE   用于分配一片连续的存储区域并初始化为0.其中表达式为要分配的字节数。SPACE也可以用“%”代替

         DataSpace  SPACE  100    ;连续分配100个字节存储单元并初始化为0


MAP   定义一个结构化的内存表的首地址。可以用“^”代替通常与FIELD伪指令配合使用来定义结构化的内存表

        MAP  0x100,R0   ;定义结构化内存表首地址的值为0x100+R0


FIELD:用于定义一个结构化内存表中的数据域。FIELD也可用“#”代替。表达式的值为当前数据域在内存表中所占的字节数。

       MAP  0x100    ;定义结构化内存表首地址的值为0x100

       A    FILED   16   ;定义A的长度为16字节,位置为0X100

       B    FIELD    32  ;定义B的长度为32字节,位置为0x110

       S    FIELD    256   ;定义S的长度为256字节,位置为0x130


(3)汇编控制伪指令

 汇编控制伪指令用于控制汇编程序的执行流程,常用的汇编控制伪指令包括以下:

IF 逻辑表达式

         指令序列1

ELSE     

        指令序列2

ENDIF


WHILE  逻辑表达式

     指令序列

WEND


(4)其他伪指令

AREA:用于定义一个代码段或者数据段。用法:AREA  段名  属性1,属性2,……

   常用属性:CODE属性,用于定义代码段,默认为READONLY。

                    DATA属性,用于定义数据段,默认为READWRITE

                    READONLY属性,指定本段为只读

                    READWRITE属性:指定本段为可读可写

AREA  start,CODE,READONLY   ;生命代码段start,属性为只读


ALIGN:用于通过添加填充字节的方式,是当前位置满足一定的对齐方式。表达式的值用于指定对齐大小,取值是2的幂

             AREA  Init,CODE,READONLY,ALIGN=3   ;指定后面的指令为8字节对齐


ENTRY:指定汇编程序的入口点。一个完整的汇编程序至少有一个ENTRY(也可以有多个,真正的入口点由连接器脚本指定),但一个源文件中最多一个ENTRY


EQU:用于为程序中的常量、标号、等定义一个等效的字符名称,类似于C语言中的#define,可用“*”代替


Test  EQU  50     ;定义标号Test的值为50

ADDr  EQU  0x55,CODE32    ;定义Addr的值为0x55,且该处为32位的ARM指令


EXPORT在程序中声明一个全局的标号,该标号可在其他的文件中引用。

IMPORT 通知编译器使用的标号在其他的源文件中定义,但要在当前源文件中引用。

END:用于通知编译器已经到了源程序的结尾


四.ARM处理器的运行模式


用户模式(usr)    10000     ARM处理器正常的程序执行状态

快速中断模式(fiq)10001   用于高速数据传输或者通道处理

外部中断模式(irq) 10010  用于通用的中断处理

管理模式(svc)    10011   操作系统使用的保护模式

数据访问终止(abt) 10111  当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护

未定义指令中止模式(und) 11011  当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真

系统模式(sys)   11111   运行具有特权的操作系统任务




使用uboot的printf:

查看System.map文件 ,找到printf地址,然后定义函数:void (*show)(char*,...) = 0xc7e11650;使用如下:

  1. void (*show)(char *,...) = 0xc7e11650;
  2. int main(void)
  3. {
  4.     show("hello world./n");
  5.     return 0;
  6. }
这样就可以在uboot的环境下使用uboot实现的printf函数了






可执行文件格式:

ELF

      Linux下标准可执行结构,带有elf文件格式信息,需要解析器才能运行。还有一种是早期的较为简单的可执行文件结构。但是在新版本里out就是ELF格式。

BIN

      bin文件是由ELF转化而来的,专门用于下载到设备。想对于ELF他有两个变化。第一,bin只有基本的text/bss/data段,没有调试信息。第二,因为bin要下载到设备,所以里面各段的地址是按照相应地址对齐烧录,两段之间很可能用0补充。

      ELF转换为BIN:arm-linux-objcopy  -O binary  -S  elf_name  bin_name(-O 表示输出格式  -S表示不从源文件中复制重定位信息和符号信息到目标文件中)

HEX

      HEX文件主要是把BIN内容转换成相应的ASCII的文本格式,并且每一行都带下载地址。


交叉工具连:

arm-linux-gcc:arm平台编译器,和x86的gcc的用法类似,gcc在/usr/include寻找头文件,arm-在交叉工具链的安装目录寻找头文件

arm-linux-ld:arm平台链接器,-T指定连接器脚本,产生的文件是elf格式(后缀名不一定写为elf)

arm-linux-readelf:参数-a 查看所有elf文件信息,例如大小端,magic,平台架构(file命令也可查看架构),动态库

arm-linux-objdump:反汇编器。使用-D -S选项。在编译程序时,使用-g选项(arm-linux-gcc -g)可以添加调试信息,反编译后的代码能够更好和c程序一起查看

arm-linux-objcopy:二进制文件拷贝转化。-O binary,(arm-linux-objcopy -O binary hello.elf hello.bin)可以把ELF文件格式的文件转换成不附带ELF信息的二进制文件


连接器脚本:

     一个可执行程序通常是由:代码段,数据段,bss段构成的。同样,在用于链接这个程序的链接器脚本中,就会反应出这几个段的信息。

    脚本框架,指定段

    程序起始位置(“.”表示当前地址)

    段地址对齐

    使用变量(bss_end-bss_start 能够计算出bss段所占的空间)

    指定代码段首文件

一个uboot.lds的例子:

OUTPUT_FORMAT("elf32­littlearm", "elf32­littlearm", "elf32­littlearm")
  ;指定输出可执行文件是elf格式,32位ARM指令,小端
OUTPUT_ARCH(arm)
  ;指定输出可执行文件的平台为ARM
ENTRY(_start)
  ;指定输出可执行文件的起始代码段为_start.
SECTIONS
{
        . = 0x00000000 ; 从0x0位置开始
        . = ALIGN(4) ; 代码以4字节对齐
        .text : ;指定代码段
        {
          cpu/arm920t/start.o (.text) ; 代码的第一个代码部分
          *(.text) ;其它代码部分
        }
        . = ALIGN(4) 
        .rodata : { *(.rodata) } ;指定只读数据段
        . = ALIGN(4);
        .data : { *(.data) } ;指定读/写数据段
        . = ALIGN(4);
        .got : { *(.got) } ;指定got段, got段式是uboot自定义的一个段, 非标准段
        __u_boot_cmd_start = . ;把__u_boot_cmd_start赋值为当前位置, 即起始位置
        .u_boot_cmd : { *(.u_boot_cmd) } ;指定u_boot_cmd段, uboot把所有的uboot命令放在该段.
        __u_boot_cmd_end = .;把__u_boot_cmd_end赋值为当前位置,即结束位置
        . = ALIGN(4);
        __bss_start = .; 把__bss_start赋值为当前位置,即bss段的开始位置
        .bss : { *(.bss) }; 指定bss段
        _end = .; 把_end赋值为当前位置,即bss段的结束位置
}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值