4. 内存操作命令:md、mm、nm、mw、cp、cmp
调试嵌入式系统,说白了就是在跟内存打交道。U-Boot 提供了一套非常趁手的内存操作命令,能让你直接读写、填充、拷贝和比较物理内存。我当年第一次调板子时,就是靠这些命令把一块“死”掉的 DDR 救回来的。
这六个命令,我习惯把它们分成三组:
- 查看与修改:md、mm、nm
- 批量操作:mw、cp
- 验证:cmp
下面我们一个一个过。
4.1 md — 内存显示
md 的全称是 memory display。它的作用就是把指定地址的内存内容 dump 出来给你看。
基本用法:
md [.b, .w, .l] 地址 [长度]
这里的 .b 表示字节(8位),.w 表示半字(16位),.l 表示字(32位)。如果不指定,默认是 .l。
举个例子:
U-Boot> md.b 0x80000000 20
80000000: 12 34 56 78 9a bc de f0 11 22 33 44 55 66 77 88 .4Vx......"3DUfw.
80000010: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................
左边是地址,中间是十六进制数据,右边是 ASCII 字符显示。这个 ASCII 栏特别有用——你能一眼看出内存里是不是藏着字符串。
md.b 看一段全 0 区域,再用 md.l 看一段已知数据区域。如果读出来全是 0xdeadbeef 或者全是 0xffffffff,那基本可以断定 DDR 没配好。
4.2 mm — 内存修改(自动递增地址)
mm 是 memory modify。它进入一个交互式修改模式,每次修改完一个地址,地址自动加 1(按当前数据宽度)。
用法:
mm [.b, .w, .l] 地址
实际操作:
U-Boot> mm.l 0x80000000
80000000: 00000000 ? 0x12345678
80000004: 00000000 ? 0xdeadbeef
80000008: 00000000 ? .
输入新值后回车,地址自动跳到下一个。输入 .(点号)退出。
4.3 nm — 内存修改(按指定地址)
nm 是 memory modify with specified address。它和 mm 很像,唯一的区别是:nm 不会自动递增地址,每次修改的都是你指定的那个地址。
用法:
nm [.b, .w, .l] 地址
举个例子:
U-Boot> nm.l 0x80000000
80000000: 00000000 ? 0x12345678
80000000: 12345678 ? 0xabcdef01
80000000: abcdef01 ? .
看到没?地址一直是 0x80000000,不会变。这个命令特别适合反复修改同一个寄存器。
nm。如果你在填充一段连续的内存区域(比如构建一个简单的数据结构),那就用 mm。
4.4 mw — 内存填充
mw 是 memory write。它用指定的值填充一段连续的内存区域。这个命令在初始化内存、清空 buffer 时非常常用。
用法:
mw [.b, .w, .l] 地址 值 [长度]
例子:
U-Boot> mw.l 0x80000000 0x0 100
这条命令把从 0x80000000 开始的 0x100 个字(注意是字,不是字节)全部写成 0。
如果你想填字节:
U-Boot> mw.b 0x80000000 0xff 400
这条命令把 1024 个字节全部填成 0xff。
mw.l 清空一段内存,结果忘了指定长度,U-Boot 默认只写了一个字。后来排查了半天才发现是这里的问题。所以我的习惯是——永远显式指定长度,哪怕只写一个字节。
4.5 cp — 内存拷贝
cp 是 memory copy。它把一段内存的数据拷贝到另一段内存。
用法:
cp [.b, .w, .l] 源地址 目标地址 长度
例子:
U-Boot> cp.l 0x80000000 0x90000000 100
这条命令把 0x80000000 开始的 0x100 个字拷贝到 0x90000000。
cp 命令不会检查源地址和目标地址是否重叠。如果重叠了,结果是不确定的。我见过有人用 cp 把代码段覆盖了,然后板子直接变砖。所以拷贝前,一定确认好源和目标没有重叠。
4.6 cmp — 内存比较
cmp 是 memory compare。它比较两段内存是否完全相同。
用法:
cmp [.b, .w, .l] 地址1 地址2 长度
例子:
U-Boot> cmp.l 0x80000000 0x90000000 100
如果两段内存完全一致,U-Boot 会静默返回(没有输出)。如果有差异,它会打印出第一个不一致的地址和值。
比如:
U-Boot> cmp.l 0x80000000 0x90000000 100
byte at 0x80000010 (0x12345678) != byte at 0x90000010 (0x00000000)
Total of 1 byte(s) were the same
这个输出其实有点误导——它说 Total of 1 byte(s) were the same,意思是只有第一个字节相同?不对,它实际的意思是“发现了 1 处不同”。嗯,U-Boot 的提示信息确实容易让人困惑,你记住这个坑就行。
cmp 来验证 cp 是否成功。先 cp 拷贝,再 cmp 比较,如果没输出,说明拷贝没问题。这个组合拳在调试 DMA 驱动时特别好用。
4.7 命令对比速查表
| 命令 | 全称 | 核心行为 | 典型场景 |
|---|---|---|---|
md |
memory display | 显示内存内容 | 查看 DDR 初始化结果、检查寄存器值 |
mm |
memory modify | 交互式修改,地址自动递增 | 填充一段连续内存、构建数据结构 |
nm |
memory modify (fixed addr) | 交互式修改,地址不变 | 反复修改同一个寄存器 |
mw |
memory write | 用固定值填充一段区域 | 清空 buffer、初始化内存池 |
cp |
memory copy | 从源地址拷贝到目标地址 | 搬运代码、备份数据、测试 DMA |
cmp |
memory compare | 比较两段内存是否一致 | 验证拷贝结果、检查内存完整性 |
4.8 知识体系图:内存操作命令全景
下面这张图帮你理清这六个命令的关系和适用场景。
4.9 实战组合拳:一条龙调试流程
最后,我分享一个我在项目中常用的调试套路。假设我要验证一段 DDR 是否正常工作:
- 清空目标区域:
mw.l 0x80000000 0x0 1000 - 写入已知数据:
mw.l 0x80000000 0xdeadbeef 1000 - 拷贝到另一区域:
cp.l 0x80000000 0x90000000 1000 - 比较验证:
cmp.l 0x80000000 0x90000000 1000 - 查看结果:
md.l 0x80000000 10
如果 cmp 没有输出,说明 DDR 读写和拷贝都正常。如果 cmp 报错,你就知道问题出在哪了。嗯,这套流程我用了快十年,从来没让我失望过。