偽指令不是指令,偽指令和指令的根本區別是經過編譯后會不會生成機器碼。 偽指令的意義在于指導編譯過程。 偽指令是和具體的編譯器相關的,我們使用gnu工具鏈,因此學習gnu環境下的匯編偽指令。
在 ARM 匯編中,偽指令(Pseudoinstruction)由匯編器解析,用于輔助程序編寫(如定義數據、劃分段、設置對齊等),不直接生成機器碼。以下是常用偽指令及其用法示例:
一、數據定義偽指令
用于在內存中定義數據(如整數、字符串、數組等)。
1.?
.byte
定義8 位整數(1 字節),可跟多個值,用逗號分隔。
.data byte1: .byte 0x10 ; 定義1字節數據0x10 byte2: .byte 0x20, 0x30 ; 連續定義兩個字節:0x20、0x30
2.?
.half
?/?.short
定義16 位整數(2 字節),兩者功能相同。
.data half1: .half 0x1234 ; 定義2字節數據0x1234 half2: .short 0x5678 ; 等價于.half,定義0x5678
3.?
.word
?/?.int
定義32 位整數(4 字節),ARM 架構中最常用的數據定義偽指令。
.data word1: .word 0x12345678 ; 定義4字節數據0x12345678 array: .word 1, 2, 3 ; 定義32位整數數組:1、2、3(占12字節)
4.?
.quad
?/?.long
定義64 位整數(8 字節),適用于支持 64 位的 ARM 架構(如 ARMv8)。
.data quad1: .quad 0x1122334455667788 ; 定義8字節數據
5.?
.asciz
?/?.ascii
.asciz
:定義以 null 結尾的字符串(C 風格字符串,自動添加\0
)。.ascii
:定義字符串,不自動添加 null 結尾。.data str1: .asciz "Hello" ; 存儲 'H','e','l','l','o','\0'(6字節) str2: .ascii "ARM" ; 存儲 'A','R','M'(3字節,無結束符)
6.?
.space
?/?.fill
.space n
:分配n 字節的未初始化空間。.fill n, size, val
:重復填充 n 次,每次填充 size 字節,值為 val(size 通常為 1、2、4)。.data buf: .space 100 ; 分配100字節緩沖區 arr: .fill 5, 4, 0x1234 ; 填充5個4字節數據,每個值為0x1234(共20字節)
更多待補充
數據只能在.data段下定義嗎??
在 ARM 匯編中,數據并非只能在
.data
段中定義,不同類型的數據適合放在不同的段中,常見的段包括.data
、.bss
、.rodata
等,各自有不同的用途和特性。1.?
.data
段:初始化的讀寫數據
- 用途:存放已初始化且需要修改的數據(如變量、數組等)。
- 特點:數據會被存儲在可執行文件中,加載到內存后可讀寫。
- 示例:
.data count: .word 100 ; 初始化的整數(可修改) buffer: .byte 0x01, 0x02 ; 初始化的字節數組(可修改)
2.?
.bss
段:未初始化或零初始化數據
- 用途:存放未初始化或初始值為 0的數據(如緩沖區、大數組等)。
- 特點:
- 不占用可執行文件的空間(僅記錄需要分配的大小)。
- 程序加載時由系統自動初始化為 0。
- 適合存儲大容量數據(節省文件大小)。
- 示例:
.bss big_buffer: .space 1024 ; 1024字節未初始化緩沖區(自動初始化為0) temp_var: .word 0 ; 初始值為0的變量(等效于未初始化)
3.?
.rodata
段:只讀數據
- 用途:存放初始化后不需要修改的數據(如常量、字符串字面量等)。
- 特點:
- 數據會被存儲在可執行文件中。
- 加載到內存后處于只讀區域(修改會觸發錯誤)。
- 提高安全性,適合保護常量數據。
- 示例:
.rodata PI: .word 0x4048F5C3 ; 浮點數π的32位表示(只讀) msg: .asciz "Hello" ; 只讀字符串(不可修改)
4. 自定義段(.section)
通過
.section
偽指令可創建自定義段,用于特殊用途(如硬件相關數據、特定內存區域的數據等)。示例:定義一個用于硬件寄存器映射的數據段
.section .hw_regs, "aw" ; "a"=可分配, "w"=可寫 uart_base: .word 0x12340000 ; UART寄存器基地址(自定義段數據)
總結:如何選擇段?
數據類型 推薦段 原因 已初始化且需要修改 .data
可讀寫,數據存儲在文件中 未初始化或初始值為 0 .bss
不占文件空間,自動初始化為 0 初始化后不需要修改 .rodata
只讀保護,防止意外修改 特殊用途(如硬件相關) 自定義段 靈活控制段屬性和內存布局 合理劃分數據段可以優化程序的內存使用效率,并提高代碼的安全性和可維護性。
如何訪問數據
用偽指令定義數據后,怎么訪問這個數據?
在 ARM 匯編中,訪問用偽指令定義的數據通常需要通過標號(Label)?獲取數據地址,再結合加載 / 存儲指令(如
LDR
、STR
)訪問數據。具體方法取決于數據的類型和位置,以下是常見場景及示例:一、訪問數據段中的初始化數據
假設已用偽指令在
.data
段定義數據,通過標號獲取地址后,用LDR
(加載)指令讀取數據到寄存器。示例 1:訪問 32 位整數(.word 定義)
.data ; 用偽指令定義數據并標記標號 num: .word 0x12345678 ; 32位數據,地址由標號num標識.text .global main main:; 步驟1:將數據地址加載到寄存器(用LDR =標號)LDR R0, =num ; R0 = num的地址(0x...); 步驟2:從地址加載數據到寄存器(用LDR 寄存器, [地址寄存器])LDR R1, [R0] ; R1 = 內存[R0] = 0x12345678; 后續操作(例如修改數據后存回)ADD R1, R1, #1 ; R1 = 0x12345679STR R1, [R0] ; 內存[R0] = R1(更新數據); 程序結束MOV PC, LR
示例 2:訪問字符串(.asciz 定義)
字符串本質是字節數組,可通過地址逐個訪問字符:
.data str: .asciz "Hello" ; 字符串:'H','e','l','l','o','\0'.text main:LDR R0, =str ; R0 = 字符串首地址; 訪問第一個字符(H的ASCII碼為0x48)LDRB R1, [R0] ; R1 = 0x48(LDRB:加載字節,自動零擴展); 訪問第二個字符(e的ASCII碼為0x65)LDRB R2, [R0, #1] ; R2 = 0x65(地址偏移+1字節)MOV PC, LR
二、訪問數組數據(多個連續元素)
對于
.word
、.byte
等定義的數組,通過地址 + 偏移量訪問元素:.data arr: .word 10, 20, 30, 40 ; 32位整數數組(每個元素4字節).text main:LDR R0, =arr ; R0 = 數組首地址; 訪問第1個元素(索引0):10LDR R1, [R0] ; R1 = 10; 訪問第2個元素(索引1):20(偏移4字節)LDR R2, [R0, #4] ; R2 = 20; 訪問第3個元素(索引2):30(用寄存器偏移)MOV R3, #8LDR R4, [R0, R3] ; R4 = 30MOV PC, LR
三、訪問.bss 段的未初始化數據
.bss
段數據僅分配空間(初始值為 0),訪問方式與.data
段相同:.bss buf: .space 16 ; 16字節未初始化緩沖區.text main:LDR R0, =buf ; R0 = 緩沖區首地址; 向緩沖區第1個字節寫入0x55MOV R1, #0x55STRB R1, [R0] ; STRB:存儲字節; 向緩沖區第5個字節(偏移4)寫入0xAAMOV R2, #0xAASTRB R2, [R0, #4]MOV PC, LR
四、關鍵指令總結
指令 用途 示例 LDR Rn, =label
將標號地址加載到寄存器 LDR R0, =num
LDR Rd, [Rn]
從 Rn 指向的地址加載 32 位數據 LDR R1, [R0]
LDRB Rd, [Rn]
從 Rn 指向的地址加載 8 位數據 LDRB R1, [R0]
LDRH Rd, [Rn]
從 Rn 指向的地址加載 16 位數據 LDRH R1, [R0]
STR Rd, [Rn]
將 32 位數據存儲到 Rn 指向的地址 STR R1, [R0]
STRB Rd, [Rn]
將 8 位數據存儲到 Rn 指向的地址 STRB R1, [R0]
STRH Rd, [Rn]
將 16 位數據存儲到 Rn 指向的地址 STRH R1, [R0]
核心邏輯
- 獲取地址:用?
LDR Rn, =label
?將數據標號的地址加載到寄存器(Rn)。- 訪問數據:根據數據寬度(8/16/32 位),用?
LDR
/STR
?及其變體(LDRB
/STRB
等),通過地址寄存器(Rn)讀寫內存。這種方式依賴匯編器對地址的解析和鏈接器的地址分配,最終實現對偽指令定義數據的訪問。
二、段定義偽指令
用于劃分程序的不同段(代碼段、數據段等),便于內存管理。
1.?
.text
定義代碼段(存放可執行指令),是程序的默認段。
.text ; 進入代碼段 start:MOV R0, #0 ; 指令存放于此段
2.?
.data
定義數據段(存放初始化的數據)。
.data ; 進入數據段 var: .word 0x1234 ; 初始化數據存放于此段
3.?
.bss
定義未初始化數據段(僅分配空間,初始值為 0),節省可執行文件大小。
.bss ; 進入未初始化數據段 buf: .space 200 ; 200字節緩沖區(初始為0)
4.?
.section
自定義段(靈活劃分特殊用途的段,如中斷向量表)。
.section .vector, "a" ; 定義名為.vector的段,"a"表示可分配 reset:B start ; 中斷向量表中的復位向量
更多待補充
三、符號與地址偽指令
用于聲明符號可見性、加載地址等。
1.?
.global
?/?.extern
.global sym
:聲明 sym 為全局符號(可被其他文件引用)。.extern sym
:聲明 sym 為外部符號(在其他文件中定義)。.global main ; 聲明main為全局符號(供鏈接器識別) .extern printf ; 聲明printf為外部符號(來自C庫)
2.?
.equ
定義符號常量(類似 C 中的
#define
),便于代碼維護。.equ MAX_LEN, 100 ; 定義常量MAX_LEN=100 .equ PI, 3.14 ; 也可定義浮點數(匯編器支持時).text start:MOV R1, #MAX_LEN ; 使用常量
3.?
=label
(地址加載偽指令)配合
LDR
指令,將標號的絕對地址加載到寄存器(實際會被匯編器轉換為合適的指令)。.data msg: .asciz "Hello".text main:LDR R0, =msg ; 將msg的地址加載到R0(等價于加載絕對地址)LDR R1, =0x12345678 ; 加載32位立即數(超出MOV指令范圍時用)
更多待補充
四、對齊與定位偽指令?
用于控制數據或指令在內存中的對齊方式(提高訪問效率)。
1.?
.align n
使當前地址對齊到
2^n
字節邊界(n 通常為 0~3,對應 1、2、4、8 字節對齊)。.data .align 2 ; 對齊到4字節邊界(2^2=4) val: .word 0x1234 ; val的地址必為4的倍數
2.?
.org addr
強制將當前地址設置為
addr
(常用于固定地址初始化,如硬件寄存器)。.org 0x40000000 ; 強制當前地址為0x40000000(假設為UART寄存器地址) uart_tx: .word 0 ; uart_tx的地址固定為0x40000000
更多待補充
五、其他常用偽指令
1.?
.end
標記程序結束,匯編器遇到此指令后停止處理。
.text start:MOV PC, LR .end ; 程序結束
2.?
.include
包含其他匯編文件(類似 C 的
#include
),便于代碼復用。.include "common.s" ; 包含common.s文件中的代碼
3.?
.thumb
?/?.arm
切換指令集:
.thumb
進入 Thumb 模式(16/32 位指令),.arm
進入 ARM 模式(32 位指令)。.arm ; 使用ARM指令集 MOV R0, #1.thumb ; 切換到Thumb指令集 MOV R1, #2
更多待補充?
總結?
偽指令是 ARM 匯編的 “輔助工具”,核心作用是:
- 定義數據(
.word
、.asciz
等);- 劃分內存段(
.text
、.data
等);- 控制符號與地址(
.global
、=label
等);- 優化內存對齊(
.align
)。靈活使用偽指令可使匯編代碼更清晰、易維護,同時適配不同的硬件和內存布局需求。