GNU汇编基础

1. 概述

GNU汇编器(AS)是GNU工具链的组成部分,采用跨平台的统一语法。在嵌入式开发中,它常用于处理器初始化、中断处理等底层硬件操作。本文以ARM Cortex-A7为例,讲解GNU汇编的核心语法与实战应用。

2. 基础语法规则

2.1 语句结构

GNU 汇编每行一条语句,格式固定为 label: instruction @ comment,三部分均为可选(无标号则省略 “label:”,无注释则省略 “@ comment”)。

  • label(标号):表示地址位置,用于跳转、函数入口或数据地址,必须以 : 结尾(如 _start:LED_Init:)。
  • instruction(指令):可是 ARM 汇编指令(如 MOVLDR)或伪操作(如 .global.section)。
  • comment(注释):用 @ 开头(单行),或 /* ... */(多行),作用同 C 语言注释,仅用于说明,不参与编译。

示例

_start:
    MOV R0, #0x12      @ 设置 R0 = 0x12
    ADD R1, R0, #1     /* R1 = R0 + 1 */

2.2 大小写规范

  • 指令、伪指令、寄存器等可以全部大写或小写
  • 禁止大小写混用
  • 推荐使用小写以提高可读性

3. 段定义与常用伪操作

GNU 汇编通过 “段” 划分代码、数据区域,需用伪操作(以 . 开头的指令)定义,伪操作仅指导汇编器编译,不生成机器码。

3.1 预定义段(核心必用)

汇编器预定义了 4 个核心段,无需自定义,直接用 .section 声明即可,各段作用如下:

段名 作用 特点
.text 存放代码(指令) 只读,加载到内存可执行区域
.data 存放已初始化的数据(如全局变量) 可读写,占用 Flash 和内存空间
.bss 存放未初始化的数据 仅占内存空间,Flash 中不存储(上电清零)
.rodata 存放只读数据(如常量、字符串) 只读,可优化到 .text 段节省空间

示例:定义段

.section .text    @ 声明当前代码属于代码段
.global _start    @ 导出_start为全局符号(链接器需此作为入口)
_start:           @ 程序入口标号(链接脚本需指定ENTRY(_start))

.section .data    @ 声明数据段
num1: .long 0X12345678  @ 已初始化数据:4字节(long)变量num1=0X12345678

.section .bss     @ 声明未初始化数据段
buf: .space 1024   @ 分配1024字节空间(buf为起始地址,上电后值为0)

3.2 常用伪操作

伪操作 格式 作用 示例
.global .global symbol 导出符号(让链接器 / 其他文件可见) .global LED_On(导出 LED_On 函数)
.equ .equ 变量名, 表达式 定义常量(类似 C 的 #define) .equ GPIO1_ADDR, 0X0209C004
.align .align 对齐字节数 数据 / 代码地址对齐(需 2 的幂,如 4/8) .align 4(4 字节对齐,避免硬件错误)
.byte/.short/.long .byte 数值 定义 1/2/4 字节数据 .byte 0XAB(1 字节)、.long 0X1234(4 字节)
.space .space 长度, 填充值 分配指定字节空间(填充值默认 0) .space 8(分配 8 字节,值为 0)
.end .end 标识源文件结束(可省略,编译器自动识别)

4. 函数设计与调用规则

GNU 汇编函数需遵循 ATPCS 规则(ARM 函数调用标准),核心是 “寄存器使用约定” 和 “现场保护”,否则会导致数据混乱或函数无法返回。

4.1 函数基本结构

汇编函数由 “函数标号 + 函数体 + 返回指令” 组成,返回指令需根据场景选择(BX LRPOP {..., PC})。

/* 函数名:Add(实现 a + b)
   输入:R0 = a,R1 = b
   输出:R0 = a + b
*/
Add:
    ADD R0, R0, R1  @ 函数体:R0 = R0 + R1(利用输入寄存器存结果)
    BX LR           @ 返回指令:将LR的值赋给PC,回到调用处

4.2 关键规则:现场保护与恢复

现场保护的核心是 “被调用者保存寄存器” 需压栈(避免修改后影响调用者),ATPCS 规则明确:

  • 调用者保存寄存器:R0~R3、R12(调用者负责保存,被调用者可直接修改);
  • 被调用者保存寄存器:R4~R11、LR(被调用者若使用,必须压栈保存,退出时恢复)。

示例:带现场保护的函数

/* 需要保护现场的复杂函数示例 */
Complex_Func:
    push {r4-r6, lr}      @ 保存非易失寄存器
    
    mov r4, r0           @ 使用r4(非易失,已保护)
    bl  Some_Sub_Func    @ 调用子函数(会修改lr)
    
    add r0, r4, #1       @ 使用已保护的r4
    pop {r4-r6, pc}      @ 恢复寄存器并通过pc返回

4.3 函数调用场景

场景 1:汇编调用 C 函数

汇编调用 C 函数时,需按规则传递参数(R0~R3 传前 4 个参数,超过 4 个用栈),且用 BL 指令(保存返回地址到 LR)。

.global _start
_start:
    /* 1. 设置栈(C函数需栈才能运行,栈地址需是4字节对齐) */
    LDR SP, =0X80200000  @ 栈地址设为0X80200000(需是空闲内存区域)
    
    /* 2. 传递参数(C函数 void C_LED_On(int pin, int status)) */
    MOV R0, #3           @ R0 = pin(第一个参数)
    MOV R1, #1           @ R1 = status(第二个参数)
    
    /* 3. 调用C函数(BL保存LR,执行完返回当前处) */
    BL C_LED_On          
    
    /* 4. 死循环(C函数返回后防止跑飞) */
Loop:
    B Loop

场景 2:C 调用汇编函数

C 调用汇编函数时,需在汇编中用 .global 导出函数名,C 中用 extern 声明,参数传递规则同上。

/* 汇编文件:add.s */
.global Add  @ 导出Add函数,供C调用
Add:
    ADD R0, R0, R1  @ R0=输入a,R1=输入b,R0=结果(C中接收返回值)
    BX LR
/* C文件:main.c */
extern int Add(int a, int b);  // 声明汇编函数
int main() {
    int res = Add(3, 5);       // 调用汇编函数,res=8
    return 0;
}

5. GUN核心指令详解

ARM 指令是汇编的 “执行单元”,Cortex-A7 常用指令按功能分类,以下为嵌入式开发高频指令(需重点掌握)。

5.1 数据传输指令(寄存器 / 特殊寄存器)

用于寄存器间、寄存器与特殊寄存器(CPSR/SPSR)的数据传递,核心指令 3 个:

指令 格式 作用 示例
MOV MOV Rd, Rn / #immed 寄存器→寄存器,或立即数→寄存器(仅 16 位立即数) MOV R0, R1(R0=R1)、MOV R0, #0X12
MRS MRS Rd, 特殊寄存器 特殊寄存器→通用寄存器(仅能读特殊寄存器) MRS R0, CPSR(R0=CPSR,读处理器状态)
MSR MSR 特殊寄存器,Rd 通用寄存器→特殊寄存器(仅能写特殊寄存器) MSR CPSR, R0(CPSR=R0,修改处理器状态)

示例:

mov r0, #0x1234          @ 立即数→寄存器
mov r1, r0               @ 寄存器→寄存器

mrs r0, cpsr             @ 特殊寄存器→通用寄存器
msr cpsr, r0             @ 通用寄存器→特殊寄存器

5.2 存储器访问指令(寄存器↔内存 / 外设)

ARM 不能直接访问内存 / 外设寄存器,需通过 LDR(读)和 STR(写)指令,支持按字节、半字、字(32 位)操作。

指令 格式 作用 示例(访问 I.MX6U GPIO1_GDIR)
LDR LDR Rd, [Rn, #offset] 内存(Rn+offset)→寄存器(字操作) LDR R1, [R0](R1 = *R0,读地址值)
LDRB LDRB Rd, [Rn, #offset] 内存→寄存器(字节操作,8 位) LDRB R1, [R0, #1](读 R0+1 地址的 1 字节)
LDRH LDRH Rd, [Rn, #offset] 内存→寄存器(半字操作,16 位) LDRH R1, [R0](读 R0 地址的 2 字节)
STR STR Rd, [Rn, #offset] 寄存器→内存(字操作) STR R1, [R0](*R0 = R1,写地址值)
STRB STRB Rd, [Rn, #offset] 寄存器→内存(字节操作) STRB R1, [R0, #2](写 R0+2 地址 1 字节)
STRH STRH Rd, [Rn, #offset] 寄存器→内存(半字操作) STRH R1, [R0](写 R0 地址 2 字节)

实战场景:配置 GPIO 为输出并点亮 LED

.equ GPIO1_GDIR, 0X0209C004  @ GPIO1方向寄存器地址
.equ GPIO1_DR,   0X0209C000  @ GPIO1数据寄存器地址

LED_On:
    /* 1. 配置GPIO为输出(GDIR对应位设1) */
    LDR R0, =GPIO1_GDIR  @ R0 = 寄存器地址
    LDR R1, [R0]         @ R1 = 当前GDIR值(读)
    ORR R1, R1, #(1<<5)  @ 第5位设1(配置引脚为输出)
    STR R1, [R0]         @ 写回GDIR(完成配置)
    
    /* 2. 点亮LED(DR对应位设0,假设低电平点亮) */
    LDR R0, =GPIO1_DR    @ R0 = 数据寄存器地址
    LDR R1, [R0]         @ R1 = 当前DR值
    BIC R1, R1, #(1<<5)  @ 第5位设0(点亮)
    STR R1, [R0]         @ 写回DR
    BX LR

5.3 压栈 / 出栈指令(现场保护)

用于批量保存 / 恢复寄存器,依赖栈指针(SP),Cortex-A 用 满递减栈(FD)(SP 指向最后入栈的元素,栈从高地址向低地址增长),核心指令 2 个:

指令 格式 作用 等价指令(FD 模式)
PUSH PUSH 寄存器列表入栈(批量保存) STMFD SP!,
POP POP 栈数据恢复到寄存器列表(批量恢复) LDMFD SP!,

示例:

Interrupt_Handler:
    push {r0-r3, r12, lr}   @ 中断现场保护
    mrs r0, spsr
    push {r0}
    
    /* 中断处理代码 */
    
    pop {r0}
    msr spsr, r0
    pop {r0-r3, r12, pc}    @ 直接恢复pc实现返回

5.4 跳转指令(程序流控制)

用于改变程序执行顺序,核心指令 3 个,区别在于 “是否保存返回地址” 和 “是否切换指令集”:

指令 格式 作用 适用场景
B B label 直接跳转,不保存返回地址(单向跳转) 跳 main 函数(不返回汇编)、死循环
BL BL label 跳转前保存返回地址到 LR(可返回) 调用函数(需返回当前处)
BX BX Rm 间接跳转(地址存 Rm),支持切换指令集 函数返回(BX LR)、跳不同指令集代码

示例:汇编初始化后跳 C main

.global _start
_start:
    /* 1. 初始化栈(C函数运行依赖栈) */
    LDR SP, =0X80200000  @ 栈地址需是空闲内存(如DDR中的地址)
    
    /* 2. 初始化DDR(若代码在Flash,需将数据加载到DDR,此处省略) */
    BL DDR_Init           @ 调用汇编的DDR初始化函数(需返回)
    
    /* 3. 跳C main函数(不返回,用B指令) */
    B main                @ 跳main,不再回到_start

6. 附录:常用指令速查表

指令类型 指令 功能简述 格式示例
数据传输 MOV 寄存器 / 立即数传输 MOV R0, R1
MRS 特殊寄存器→通用寄存器 MRS R0, CPSR
MSR 通用寄存器→特殊寄存器 MSR CPSR, R0
存储器访问 LDR 内存→寄存器(字) LDR R1, [R0]
STR 寄存器→内存(字) STR R1, [R0]
LDRB 内存→寄存器(字节) LDRB R1, [R0, #1]
压栈出栈 PUSH 寄存器入栈 PUSH
POP 栈→寄存器 POP
跳转 B 直接跳转(不返回) B main
BL 跳转 + 保存 LR(可返回) BL DDR_Init
BX 间接跳转(地址在寄存器) BX LR
算术运算 ADD 加法 ADD R0, R0, R1
SUB 减法 SUB R0, R0, #1
逻辑运算 ORR 按位或 ORR R1, R1, #(1<<5)
BIC 按位清除 BIC R1, R1, #(1<<5)
posted @ 2025-11-07 22:30  比特向阳  阅读(7)  评论(0)    收藏  举报