汇编语言的基础使用及ARM 裸机开发环境搭建

发布于:2025-09-11 ⋅ 阅读:(21) ⋅ 点赞:(0)

本篇简单介绍了关于汇编的一些基础指令的用法,但关于栈、寄存器等问题已经补充完整,同时加入了ARM 裸机开发环境搭建操作教程,汇编会用的语言基础环境依然是keil 4,但需要有相关支持,其他更多的指令建议大家多查手册来学习

学习核心目标

编写ARM 启动代码,为 C 语言运行搭建基础环境,核心任务包括:

  1. 初始化异常向量表(处理复位、中断等异常)
  2. 初始化各工作模式的栈指针寄存器(C 语言函数调用依赖栈)
  3. 开启 ARM 内核中断允许(配置 CPSR 寄存器)
  4. 将工作模式切换为User 模式(用户程序运行模式)
  5. 引导程序进入 C 语言main函数执行

一、ARM 汇编基础(格式与伪指令)

1. 汇编程序基本结构(汇编中注释用;)

ARM 汇编通过伪指令指导汇编器工作,非处理器指令,核心结构如下:(注意格式:最前面要加tab键)

    AREA reset, CODE, READONLY  ; 定义代码段,段名reset(复位向量段),属性只读
    CODE32                      ; 后续指令使用32位ARM指令集(Thumb为16位,需用THUMB伪操作)
    ENTRY                       ; 标记程序入口(复位后第一个执行的指令位置)

    ; 具体内容

    END                         ; 标记程序结束

2. 关键伪指令解析

伪操作 作用说明
AREA 定义段(代码段 / 数据段 / 栈段),格式:AREA 段名, 段属性, 访问权限
CODE32/THUMB 指定指令集类型:32 位 ARM 指令集 / 16 位 Thumb 指令集
ENTRY 标记程序入口,一个工程仅一个 ENTRY(复位向量段需包含 ENTRY)
END 标记汇编文件结束,汇编器遇到 END 后停止处理

二、核心 ARM 指令

1. 数据传送指令(MOV/MVN)

(1)MOV 指令(数据移动 / 赋值)
  • 功能:将立即数或寄存器值传送到目标寄存器(类似 C 语言的 = 赋值)

  • 指令格式

    格式 说明 示例
    MOV{S}<c> Rd, #const 立即数传送到 Rd MOV R0, #0x8(R0=8)
    MOV{S}<c> Rd, Rm Rm 值传送到 Rd MOV R1, R0(R1=R0)
    MOV{S}<c> Rd, Rm, <shift> Rm 移位后传送到 Rd(移位量 0-31) MOV R6, R0, LSL #31(左移 31 位)

关键注意点

  • 移位操作支持LSL(逻辑左移)、LSR(逻辑右移)、ASR(算术右移)、ROR(循环右移)、RRX(带进位循环右移,无需移位量)
  • 立即数需满足12 位立即数规则<z在SUB指令部分所写>
(2)MVN 指令(按位取反)
  • 功能:将立即数或寄存器值按位取反后传送到目标寄存器(类似 C 语言 ~)
  • 指令格式MVN{S}<c> Rd, #const / MVN{S}<c> Rd, Rm{, <shift>}
  • egMVN R0, #0x0(R0=0xFFFFFFFF,因为 0x0 取反为全 1)

2. 算术运算指令(ADD/SUB/CMP)

(1)ADD 指令(加法)
  • 功能:两个操作数相加,结果存入目标寄存器(类似 C 语言+

  • 指令格式

    格式 说明 示例
    ADD{S}<c> Rd, Rn, #const Rn + 立即数 → Rd ADD R6, R0, #0xF0
    ADD{S}<c> Rd, Rn, Rm{, <shift>} Rn + (Rm 移位后) → Rd ADD R7, R0, R1, LSL #1(R0+2*R1)
  • 注意:无ADD Rd, #a, #b格式(C 语言a+b在编译阶段计算,无需机器指令)

(2)SUB 指令(减法)
  • 功能:两个操作数相减,结果存入目标寄存器(类似 C 语言-

  • 指令格式(与 ADD 类似):

    • SUB{S}<c> Rd, Rn, #const(Rn - 立即数 → Rd)
    • SUB{S}<c> Rd, Rn, Rm{, <shift>}(Rn - 移位后的 Rm → Rd)
  • 关键补充:

    什么是立即数?

    立即数是直接嵌入在指令中的常数,无需从内存、寄存器中读取,CPU 可直接用该常数参与运算,它的核心特点是 “指令自带数据”,能减少内存访问次数,提升指令执行效率。

    12 位立即数规则

    • 判断标准:一个数展开为 32 位二进制后,存在偶数位循环右移,使移位后高 24 位全 0,低 8 位为有效imm8(循环移位次数2N,N=0~15 -> 2N = 0~30)
    • 原因:ARM 指令为 32 位,立即数由4位循环移位量(N)+8位imm8组成,共 12 位(即imm12
    • eg#0x80000000(二进制1000...0000)可通过imm8=0x80、循环移位2*15=30位得到,属于合法立即数;#0x101无法通过该规则得到,为非法立即数
(3)CMP 指令(比较)
  • 功能:比较两个数(本质是SUB运算,但不保存结果,仅更新 CPSR 标志位)[通过做差得到的正负值来判断二者大小]
  • 指令格式CMP<c> Rn, #const / CMP<c> Rn, Rm{, <shift>}
  • 等价关系CMP R0, R1 ≡ SUBS R0, R1S表示更新标志位)
  • 用途:配合条件跳转指令(如BLEBGT)实现分支逻辑

用subs实现指令比较(cmp作用)流程图

条件判断标志NZCV
        CPSR寄存器中条件判断标志位
        N: 符号标志位:上条指令执行结果最高位bit31为1,则 N = 1, 当结果作为有符号解释时为负值;
        Z: 零值标志位:上条指令执行结果为0(即bit0 - bit31 均为0),则 Z = 1;
        C: 进位标志位:进行无符号解读,如果在加法过程中进位或者减法时没有借位,则为 C = 1,否则 C = 0
        V: 溢出标志位:进行有符号解读,是否发生溢出 -2^31 - 2^31-1(两个正数加得负数,两个负数加得正数)
        条件码:eq ge gt le lt al(无条件执行)
        equal:等于
        not equal:不等于

3. 位操作指令(BIC/ORR)

(1)BIC 指令(指定位清0)
  • 功能:将 Rn 的指定位清 0(按 Rm / 立即数的位掩码),结果存入 Rd(类似 C 语言Rn & ~mask
  • 指令格式BIC{S}<c> Rd, Rn, #const / BIC{S}<c> Rd, Rn, Rm{, <shift>}
  • eg
    MOV R0, #0xFFFFFFFF  ; R0=全1
    MOV R1, #1
    BIC R2, R0, R1, LSL #31  ; R0的bit31清0 → R2=0x7FFFFFFF
    
(2)ORR 指令(指定位置1)
  • 功能:将 Rn 的指定位置 1(按 Rm / 立即数的位掩码),结果存入 Rd(类似 C 语言Rn | mask
  • 指令格式ORR{S}<c> Rd, Rn, #const / ORR{S}<c> Rd, Rn, Rm{, <shift>}
  • eg
    MOV R0, #0x00        ; R0=全0
    MOV R1, #1
    ORR R8, R0, R1, LSL #31  ; R0的bit31置1 → R8=0x80000000
    

4. 内存访问指令(LDR)

LDR 指令(加载)
  • 功能:从内存地址读取数据到寄存器
  • 核心格式
    • 加载立即数地址:LDR Rd, =const(伪指令,将 32 位地址存入 Rd,解决MOV Rd, #const无法处理大地址的问题)
    • 加载标签地址:LDR Rd, <label>(将标签对应的内存地址值存入 Rd)
  • egLDR SP, =0x40001000(初始化栈指针为0x40001000MOV SP, #0x40001000会报错,因地址超 12 位立即数范围)
STR (存放指令)​

功能:​​ 将寄存器中的数据写入​(存储)到指定的内存地址中。

核心格式:​STR<c> <Rt>, <addressing_mode>

5. 跳转与函数调用指令(B/BL/BX)

指令 功能说明 示例 用途场景
B 无条件 / 条件跳转,不保存返回地址 B loop(跳转到 loop 标签) 循环、简单分支
BL 跳转并保存返回地址到LR(Link Register) BL func(调用 func 函数) 函数调用(需返回主调函数)
BX 跳转并切换指令集(ARM/Thumb) BX LR(从 LR 恢复 PC,返回) 函数返回(等价于MOV PC, LR
  • 等价关系B fun ≡ LDR PC, =fun(PC 为程序计数器,指向当前执行指令地址 + 8)

6. 寄存器组操作指令(STMFD/LDMFD)

用于栈操作(保护 / 恢复现场),核心是多寄存器的批量存储 / 加载,基于 ARM 的 “满减栈”

  • 栈类型说明

满栈:栈指针(SP)指向最后一个已使用的栈元素
减栈:入栈时 SP 先减 4(32 位寄存器),再存储数据;出栈时先读取数据,再 SP 加 4

  • STMFD(批量存储 / 入栈)

格式:STMFD SP!, {Rlist}(!表示入栈后自动更新 SP)
功能:将Rlist(如R0-R12, LR)中的寄存器值依次入栈,保护现场
eg:STMFD SP!, {R0-R12, LR}(函数调用前保护主调函数的寄存器和返回地址)

  • LDMFD(批量加载 / 出栈)

格式:LDMFD SP!, {Rlist}(!表示出栈后自动更新 SP)
功能:将栈中数据依次加载到Rlist,恢复现场
eg:LDMFD SP!, {R0-R12, PC}(函数返回前恢复寄存器,并将 LR 值写入 PC,实现返回)

7. 系统寄存器指令(MRS/MSR)

用于读写特殊功能寄存器(如 CPSR),核心用途是修改处理器工作模式

  • MRS(读取系统寄存器)

格式:MRS Rd, SpecReg(SpecReg 为特殊寄存器,如 CPSR)
功能:将特殊寄存器的值读取到通用寄存器 Rd
eg:MRS R0, CPSR(读取当前程序状态寄存器 CPSR 到 R0)

  • MSR(写入系统寄存器)

格式:MSR SpecReg, Rd / MSR SpecReg, #const
功能:将 Rd 或立即数的值写入特殊寄存器

切换到 User 模式)
MRS R0, CPSR       ; 读取CPSR
BIC R0, R0, #0x1FF ; 清除CPSR的M域(bit0-bit8,控制工作模式)
ORR R0, R0, #0x10  ; 设置M域为0x10(User模式)
MSR CPSR, R0       ; 写入CPSR,完成模式切换

核心指令总结

指令类型

指令

功能

示例

补充说明

数据传送

MOV/MVN

传送/取反数据

MOV R0, #8/ MVN R0, #0

MVN实现按位取反(~)

算术运算

ADD/SUB/CMP

加减法/比较

ADD R1, R0, #10/ CMP R0, R1

CMP通过减法更新标志位(N/Z/C/V)

位操作

BIC/ORR

位清除/置位

BIC R0, R0, #0xF/ ORR R0, R0, #0x1

BIC等价于 AND NOT

内存访问

LDR/STR

加载/存储数据

LDR R0, [R1]/ STR R0, [R1]

LDR可加载立即数地址(伪指令)

跳转调用

B/BL/BX

跳转/函数调用/返回

BL func/ BX LR

BL自动保存返回地址到 LR

寄存器组操作

STMFD/LDMFD

批量存储/加载(栈操作)

STMFD SP!, {R0-R12, LR}

满减栈​:SP先减后存(入栈)

LDMFD SP!, {R0-R12, PC}

出栈时恢复 PC实现返回

系统寄存器操作

MRS/MSR

读写特殊寄存器(如CPSR)

MRS R0, CPSR

用于修改处理器模式或中断使能

MSR CPSR_c, #0x10

0x10切换到 ​User模式

三、函数调用与参数传递

1. 循环实现(对应 C 语言循环结构)

循环三要素
        循环结束条件
        推动循环趋向终结的语句
        循环的循环体

(1)do-while 循环(先执行循环体,再判断条件)

C 语言原码:

int i = 0;
int sum = 0;
do {
    sum += i;
    i++;
} while (i <= 100);

汇编实现:

    MOV R0, #0            ; R0 = i = 0
    MOV R1, #0            ; R1 = sum = 0
loop
    ADD R1, R1, R0        ; sum += i
    ADD R0, R0, #1        ; i++
    CMP R0, #100          ; 比较i和100
    BLE loop              ; 若i<=100,跳回loop(BLE=Branch if Less than or Equal)
(2)while/for 循环(先判断条件,再执行循环体)

C 语言原码

int i = 0;
int sum = 0;
while (i <= 100) {
    sum += i;
    i++;
}

汇编实现:

    MOV R0, #0               ; R0 = i = 0
    MOV R1, #0               ; R1 = sum = 0
loop
    CMP R0, #100             ; 先判断i<=100?
    BGT finish               ; 若i>100,跳至finish(BGT=Branch if Greater Than)
    ADD R1, R1, R0           ; sum += i
    ADD R0, R0, #1           ; i++
    B loop                   ; 跳回loop继续判断
finish
    B finish                 ; 死循环(防止程序跑飞)

2. 函数定义与调用(含现场保护)

(1)函数调用核心问题
  • 问题 1:被调函数修改主调函数的寄存器,导致数据丢失
  • 问题 2:函数嵌套时,LR被覆盖,无法正确返回
  • 解决方案:栈保护现场(入栈保存寄存器,出栈恢复)
(2)汇编函数调用(嵌套调用)
PRESERVE8               ; 栈8字节对齐,解决C函数调用报错
AREA func_demo, CODE, READONLY
ENTRY

main:
LDR SP, =0x40001000     ; 初始化栈指针(关键:必须先初始化栈)
STMFD SP!, {R0-R12, LR} ; 保护main的现场(寄存器+返回地址)
BL func0                ; 调用func0,LR保存main的返回地址
LDMFD SP!, {R0-R12, LR} ; 恢复main的现场
MOV R3, #300            ;

3. 函数调用与参数传递

参数传递规则

前4个参数​:通过 R0-R3传递。

更多参数​:通过栈传递(从右向左压栈)。

返回值​:32位通过 R0,64位通过 R0+R1

汇编调用C函数
MOV R0, #10     ; 参数1
    MOV R1, #20     ; 参数2
    BL  c_func      ; 调用C函数
C调用汇编函数
extern int asm_func(int a, int b);  // 声明汇编函数
int result = asm_func(10, 20);      // 调用

4. 栈保护与现场恢复

保护现场​(压栈)
STMFD SP!, {R0-R12, LR}  ; 保存寄存器及返回地址
恢复现场​(弹栈)
LDMFD SP!, {R0-R12, PC}  ; 恢复寄存器并返回(PC=LR)

5. 混合编程注意事项

栈对齐​:使用 PRESERVE8确保8字节对齐。

寄存器保护​:被调函数需保护 R4-R11(若修改)。

指令集切换​:BX LR可自动切换ARM/Thumb模式。

异常处理​:软中断(SWI #7)需在异常向量表中定义处理逻辑。

四、IMX6ULL开发环境搭建

1.开发板介绍

型号​:正点原子 IMX6ULL-Mini开发板

  • 核心配置

SOC​:NXP i.MX6ULL Cortex-A7单核处理器

主频:528MHz(工业级)/800MHz(商业级)

封装:467引脚 GBA

内存​:512MB DDR3L RAM

存储​:8GB eMMC(支持SD卡/NAND/eMMC启动)

屏幕​:4.3寸(800×480分辨率)

  • 硬件结构

核心板​(6层板):通过双列直插与底板连接

底板外设

        红色LED:用户可编程控制

        蓝色LED:电源指示灯(常亮)

        510Ω限流电阻:保护LED灯珠

2.开发工具安装与配置

1. 代码编辑器(Windows端)​

工具​:Visual Studio Code

插件​:安装 Arm Assembly(支持汇编语法高亮)

工程创建​:

创建目录:D:\IMX6ULL\led_asm  
创建文件:start.S(注意大写S后缀,支持预处理)
2. 交叉编译器(Ubuntu端)​

安装步骤

# 拷贝编译器到指定目录
sudo cp gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz /usr/local/arm/

# 解压并清理
cd /usr/local/arm
sudo tar xvf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
sudo rm gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz

配置环境变量

# 编辑.bashrc
vi ~/.bashrc
# 末尾添加:
export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/

# 生效配置
source ~/.bashrc
reboot  # 重启虚拟机

验证安装

arm-linux-gnueabihf-gcc -v  # 输出版本信息即成功
3. FTP文件传输服务

Ubuntu端配置​:

# 安装vsftpd
sudo apt-get install vsftpd

# 修改配置
sudo vi /etc/vsftpd.conf
# 确保以下两项未注释:
local_enable=YES
write_enable=YES

# 重启服务
sudo /etc/init.d/vsftpd restart
sudo /etc/init.d/vsftpd status  # 确认状态正常

Windows端安装FileZilla

运行安装包:FileZilla_3.39.0_win64-setup_bundled.exe

勾选“创建桌面快捷方式”

连接与文件上传

主机​:Ubuntu的IP(如192.168.71.134

用户名​:可设置为Ubuntu的用户名(或自行设置)

密码​:可设置为Ubuntu的密码(或自行设置)

操作​:右侧窗口(本地)拖拽文件至左侧(远程)即可上传

​注意事项

  • 编译器路径​:务必确认PATH配置正确,否则编译命令无法识别
  • FTP权限​:确保Ubuntu用户目录有读写权限(通过write_enable=YES开启)
  • 网络连通​:Windows与Ubuntu需在同一局域网,IP地址根据实际修改

网站公告

今日签到

点亮在社区的每一天
去签到