Pico裸机2(汇编基础)

作者:fanged日期:2026/1/1

既然都裸机了,还是简单回顾一下汇编吧。。。

1 概念

来自:https://redfoxsec.com/blog/introduction-to-assembly-language/

汇编基本上就是机器码。汇编语言是一种直接对应处理器指令集的低级语言,它以人类可读的形式表达机器指令,是软件与硬件之间几乎最底层的一层接口;每一条汇编指令几乎都能映射为一条机器指令,能够精确控制寄存器、内存、指令顺序和硬件状态,因此被广泛用于启动代码、中断处理、上下文切换和性能或时序极端敏感的场景。

1.2 寄存器

一些必要了解的寄存器

寄存器作用
r0–r3参数 / 返回值
r4–r11被调用者保存
r12临时
sp
lr返回地址
pc程序计数器

特殊寄存器

寄存器作用
CPSR当前状态(中断 / 模式)
SPSR异常返回用

1.3 基础指令

类别指令名说明例子
数据传送MOV将源操作数(寄存器或立即数)传送到目标寄存器MOV R0, #10 (R0 = 10)
MVN取反传送(先按位取反再传送)MVN R0, #0 (R0 = -1)
算术运算ADD加法运算ADD R0, R1, R2 (R0 = R1 + R2)
SUB减法运算SUB R0, R1, #5 (R0 = R1 - 5)
MUL乘法运算(注意:操作数必须是寄存器)MUL R0, R1, R2 (R0 = R1 * R2)
逻辑运算AND按位“与”AND R0, R0, #0xFF (只保留低8位)
ORR按位“或”ORR R0, R0, #1 (将最低位置1)
EOR按位“异或”EOR R0, R1, R2 (R1和R2异或存入R0)
BIC位清除(Bit Clear,将对应位清零)BIC R0, R0, #0b101 (清除第0和第2位)
比较指令CMP比较两个数(本质是执行减法并更新状态标志)CMP R0, #10 (比较R0是否等于10)
TST测试位(本质是执行按位与并更新状态标志)TST R0, #1 (测试最低位是否为1)
内存访问LDR从内存加载数据到寄存器 (Load)LDR R0, [R1] (将R1指向地址的值读入R0)
STR将寄存器数据保存到内存 (Store)STR R0, [R1] (将R0的值存入R1指向的地址)
分支跳转B无条件跳转到某个标签B label (跳转到标签label处执行)
BL带链接跳转(保存返回地址到 LR,用于调用函数)BL function (调用子程序)
BX跳转并切换指令集(常用于从函数返回)BX LR (从子程序返回)
栈操作PUSH将寄存器列表压入堆栈PUSH {R4, LR} (保护R4和返回地址)
POP从堆栈弹出数据到寄存器列表POP {R4, PC} (恢复R4并返回)

这里MOV和LDR/STR看着有点类似,查了一下在X86,确实只有一个MOV,ARM为了提高效率,分成几个了。

2 环境搭建

实在不想本地搭建环境了。直接用web的吧,查了一下,有两个网站不错。

第一个是运行汇编的网站CPUlator

https://cpulator.01xz.net/?sys=arm

第二个是查看C代码到汇编的映射网站Compiler Explorer (Godbolt)

https://godbolt.org/

最后一个是动画显示过程的。VisUAL (ARMv7 Visualizer),不过这个要安装一下。

https://salmanarif.bitbucket.io/visual/

3 一些小验证

3.1 WSL的汇编程序

hello.asm

1section .data
2    msg db "Hello", 10      ; 10 是换行符的 ASCII 
3
4section .text
5    global _start
6
7_start:
8    ; --- 调用系统函数:write(1, msg, 6) ---
9    mov rax, 1              ; 系统调用号 1  sys_write
10    mov rdi, 1              ; 文件描述符 1  stdout (标准输出)
11    mov rsi, msg            ; 字符串的地址
12    mov rdx, 6              ; 字符串长度 (H-e-l-l-o + 换行)
13    syscall                 ; 触发内核调用
14
15    ; --- 调用系统函数:exit(0) ---
16    mov rax, 60             ; 系统调用号 60  sys_exit
17    mov rdi, 0              ; 退出状态码码 0 (代表无错误)
18    syscall                 ; 触发内核调用

编译运行

1# 1. 将汇编代码编译为目标文件 (ELF64 格式)
2nasm -f elf64 hello.asm -o hello.o
3
4# 2. 将目标文件链接为可执行文件
5ld hello.o -o hello
6
7# 3. 运行
8./hello

很简单就出结果Hello了。

这个汇编和一般用的裸片汇编还是不同,还是运行在OS之上。地址空间应该是虚拟地址,但是寄存器是实际的寄存器。核心就是两个syscall。

这里就不细看流程了,主要是看看符号和链接。

hp@DESKTOP-430500P:~/test/asm$ readelf -h hello
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x401000
Start of program headers: 64 (bytes into file)
Start of section headers: 8448 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 3
Size of section headers: 64 (bytes)
Number of section headers: 6
Section header string table index: 5

开始的地址是0x401000,这个是MMU的地址。有3个program headers,有6个section headers(这里就是.text .data .symtab .strtab .shstrtab存放位置)。这里的数据,有些放到RAM,有些放到FLASH,在Linux,这个是内核来做。在裸片,这部分操作要自己做。

这里的6个sections,通过命令readelf -S hello可以看到。

hp@DESKTOP-430500P:~/test/asm$ readelf -S hello
There are 6 section headers, starting at offset 0x2100:

Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000401000 00001000
0000000000000027 0000000000000000 AX 0 0 16
[ 2] .data PROGBITS 0000000000402000 00002000
0000000000000006 0000000000000000 WA 0 0 4
[ 3] .symtab SYMTAB 0000000000000000 00002008
00000000000000a8 0000000000000018 4 3 8
[ 4] .strtab STRTAB 0000000000000000 000020b0
0000000000000027 0000000000000000 0 0 1
[ 5] .shstrtab STRTAB 0000000000000000 000020d7
0000000000000027 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)

查看符号则是 readelf -s hello

hp@DESKTOP-430500P:~/test/asm$ readelf -s hello

Symbol table '.symtab' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.asm
2: 0000000000402000 0 NOTYPE LOCAL DEFAULT 2 msg
3: 0000000000401000 0 NOTYPE GLOBAL DEFAULT 1 _start
4: 0000000000402006 0 NOTYPE GLOBAL DEFAULT 2 __bss_start
5: 0000000000402006 0 NOTYPE GLOBAL DEFAULT 2 _edata
6: 0000000000402008 0 NOTYPE GLOBAL DEFAULT 2 _end

这里可以看到定义的字符串,start位置,bss(未初始化变量)区,数据结束区位置。

program headers可以通过-l查看。00是文件头,01是代码段,02是数据段。

hp@DESKTOP-430500P:~/test/asm$ readelf -l hello

Elf file type is EXEC (Executable file)
Entry point 0x401000
There are 3 program headers, starting at offset 64

Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000000e8 0x00000000000000e8 R 0x1000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
0x0000000000000027 0x0000000000000027 R E 0x1000
LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000
0x0000000000000006 0x0000000000000006 RW 0x1000

Section to Segment mapping:
Segment Sections...
00
01 .text
02 .data

里面的所有符号可以用nm查看符号表。这个和readelf -s很相似,可以说是青春版。

hp@DESKTOP-430500P:~/test/asm$ nm hello
0000000000402006 D __bss_start
0000000000402006 D _edata
0000000000402008 D _end
0000000000401000 T _start
0000000000402000 d msg

最后还有一个是map文件。在link的时候用命令生成。

ld hello.o -o hello -Map hello.map

这个文件的内容很多,这里暂时就不细看了。。。

可以参考这个文章:https://mp.weixin.qq.com/s/j55ahfm3jradpmVg6hPy4g

3.2 ARM最小启动代码

start.s

1.section .vectors, "ax"
2.global _start
3_start:
4    .word 0x20080000        @ 0x00: 初始栈顶地址 (RAM 末尾)
5    .word reset_handler + 1 @ 0x04: 复位向量 (注意:Thumb 模式地址必须+1)
6    .word 0                 @ 0x08: NMI
7    .word 0                 @ 0x0C: HardFault
8    @ ... 后续项填充 0 或跳转
9
10.text
11reset_handler:
12    @ Cortex-M 硬件会自动加载第一个字到 SP,所以这里可以不用手动 ldr sp
13    bl main
14    b .

这里除了上面的寄存器和指令,还有一个就是标签。比如:

.global _start

_start:

.global 开头的就是全局变量,没有的就是局部变量。跟着就是标签的定义,所有的标签都不会执行,主要是给GDB调试然后跳转用。

.section开始就是指定放在哪个区。区的定义在linker那个ld文件中指定。这里所有的代码都会放到vector区。

首先就是一个向量表,起始是在reset_handler,之后设置了栈指针后就直接到main。

pico.ld

MEMORY
{
/* RP2350 的 Flash 和 RAM 地址示例 */
FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048K
RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512K
}

SECTIONS
{
.text : {
KEEP(*(.vectors)) /* 强制把向量表放在最前面 */
*(.text*)
} > FLASH

.bss : {
*(.bss*)
} > RAM
}

最后的main最简单

int main()

{

return 0;

}

编译命令:

arm-none-eabi-gcc -x assembler-with-cpp -c -mcpu=cortex-m33 -mthumb start.s -o start.o

arm-none-eabi-gcc -c -mcpu=cortex-m33 -mthumb -O2 main.c -o main.o

arm-none-eabi-gcc start.o main.o -o project.elf -mcpu=cortex-m33 -mthumb -nostartfiles -T pico.ld -Wl,--gc-sections

首先还是readelf

E:\test\pico2\test\test>readelf readelf -h project.elf
readelf: Error: 'readelf': No such file

File: project.elf
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: ARM
Version: 0x1
Entry point address: 0x10000000
Start of program headers: 52 (bytes into file)
Start of section headers: 4544 (bytes into file)
Flags: 0x5000200, Version5 EABI, soft-float ABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 1
Size of section headers: 40 (bytes)
Number of section headers: 7
Section header string table index: 6

可以看到,起始是0x10000000,这个和os上的区别很大。这个是RP2040 XIP Flash映射起始地址,也就是flash的起始地址。program headers只有1个,但是section headers有7个。

E:\test\pico2\test\test>readelf -S project.elf
There are 7 section headers, starting at offset 0x11c0:

Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 10000000 001000 00001c 00 AX 0 0 4
[ 2] .ARM.attributes ARM_ATTRIBUTES 00000000 00101c 000030 00 0 0 1
[ 3] .comment PROGBITS 00000000 00104c 000045 01 MS 0 0 1
[ 4] .symtab SYMTAB 00000000 001094 0000c0 10 5 10 4
[ 5] .strtab STRTAB 00000000 001154 000030 00 0 0 1
[ 6] .shstrtab STRTAB 00000000 001184 00003a 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), y (purecode), p (processor specific)

作用如下:

.text 代码,主要内容都在这个部分。
.comment 注释
.ARM.attributes ARM 架构专用属性
.symtab 符号表
.strtab 符号名字符串
.shstrtab section名字表

program header在这里只有一个。

E:\test\pico2\test\test>readelf -l project.elf

Elf file type is EXEC (Executable file)
Entry point 0x10000000
There is 1 program header, starting at offset 52

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x001000 0x10000000 0x10000000 0x0001c 0x0001c R E 0x1000

Section to Segment mapping:
Segment Sections...
00 .text

nm的结果

E:\test\pico2\test\test>nm project.elf
10000000 t $d
10000010 t $t
10000018 t $t
10000000 T _start
10000019 T main
10000010 t reset_handler

这里T是全局符号,t是局部符号。$d是数据,$t是指令。

好了,先到这里吧。。。


Pico裸机2(汇编基础)》 是转载文章,点击查看原文


相关推荐


Flutter 开发实战:解决华为 HarmonyOS 任务列表不显示 App 名称的终极指南
雨夜寻晴天2025/12/22

问题背景 在 Flutter 应用开发中,我们最近遇到了一个棘手的兼容性问题:在部分 华为手机(HarmonyOS 4.2.0,如 Mate 30 Pro 5G) 上,应用运行时的最近任务列表(Overview Screen)中,只显示应用图标,却不显示应用名称(App Name)。 虽然我们在 AndroidManifest.xml 中正确配置了 android:label,但在 HarmonyOS 系统上依然无效。这不仅影响用户体验,也可能导致应用在审核时被拒(如华为应用市场审核指南第 2.


【鸿蒙开发案例篇】定点出击!鸿蒙6.0视频碰一碰流转+实时进度同步案例
威哥爱编程2025/12/14

兄弟们抄家伙!今天V哥要用鸿蒙6.0的分布式能力撕碎视频跨设备流转的防线!目标:手机碰一下车机/平板,视频秒级切换+进度毫秒级同步,全程零手动干预!以下基于HarmonyOS 6.0(API 21)的ArkTS实战核弹代码已就位👇 联系V哥获取 鸿蒙学习资料 🔥 一、技术架构:分布式视频作战链 核心武器库: 碰一碰触发:NFC+分布式设备管理(@ohos.distributedDeviceManager) 进度同步引擎:AVSession Kit(@kit.AVSessionKit) 数


【Claude Code】长时间运行 agents 的有效控制框架
是魔丸啊2025/12/6

转载 Harness原意是马具,从马具控制马匹,引申为一个控制和管理框架,用于封装、引导和监督AI代理的行为。后文为了保持原意,作为专有名词不翻译了。 文中提到的initializer agent / coding agent,并不是claude code已经内置的机制,而是一个最佳实践,需要自行配置。如果想要prompt,可以去这里寻找: github.com/anthropics/… 随着 AI agents 变得更加强大,开发者越来越多地要求它们承担需要跨越数小时甚至数天工作的复杂任务


《 Linux 修炼全景指南: 七 》 指尖下的利刃:深入理解 Vim 的高效世界
Lenyiin2025/11/28

摘要 本篇《Linux Vim 入门指南》从零开始,系统而全面地介绍了 Vim 的操作理念、基础模式、光标移动、文本编辑、搜索替换、可视化模式、多窗口与多文件协作等核心能力,并深入讲解 Vim 配置、插件体系与高效编辑技巧。文章不仅涵盖新手最容易踩的坑,还通过实战示例带你完成一次完整的编辑任务,使读者不但 “会用 Vim”,更真正理解 Vim 背后的高效思维方式。无论你是 Linux 新手,还是想进一步提升编辑效率的开发者,这篇指南都将成为你学习 Vim 的最佳起点。 1、引言:为什么学 Vi


RAG索引流程详解:如何高效解析文档构建知识库
北辰alk2026/1/9

引言:为什么文档解析是RAG的基石? 在RAG(检索增强生成)系统中,文档解析是整个知识库构建的第一步,也是最关键的一步。就像建房子需要打好地基一样,良好的文档解析质量直接决定了后续检索和生成的效果。今天,我们就深入探讨RAG索引流程中的文档解析技术。 一、RAG文档解析的整体架构 首先,让我们通过一个流程图了解完整的解析流程: ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐ │                 


【SpringBoot】从学会使用maven开始
那我掉的头发算什么2026/1/17

🎬 那我掉的头发算什么:个人主页 🔥 个人专栏: 《javaSE》《数据结构》《数据库》《javaEE》 ⛺️待到苦尽甘来日 引言 当我们在创建一个新的idea项目时,不知道大家注意过没有 在这个页面中除了IntelliJ选项之外,还有一个Maven选项。而这个Maven恰好就是我们今天这篇文章的重头戏! 文章目录 引言创建Maven项目pom文件项目基本信息GAVproperties依赖管理核心:dependencies与depe


CSDN创作变现活动!社区镜像或使用视频教程分别单个最高得 80 元,收益上不封顶!
CSDN官方博客2026/1/26

CSDN AI 社区是聚焦 AI 技术产业落地的开发者服务平台(官方入口),核心为创作者搭建技术价值转化桥梁,AI社区涵盖: 镜像市场(社区镜像)、算力市场等模块。 本次推出镜像创作激励活动,以下是方案活动规则、参与要求及激励政策,保障创作者权益与活动有序开展。 一、活动总则 活动时间: 2026年1月1日 - 2026年2月28日 现金奖励: 1、按照官方指定镜像任务创作,单个社区镜像奖励 30-80元现金

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客