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 栏特别有用——你能一眼看出内存里是不是藏着字符串。

我的习惯: 调试 DDR 初始化时,我会先用 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? 我个人的经验是:如果你在调试一个硬件寄存器(比如 GPIO 控制寄存器),需要反复写同一个地址来观察效果,那就用 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。

避坑指南: 我曾经在调试 NAND Flash 驱动时,想用 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 知识体系图:内存操作命令全景

下面这张图帮你理清这六个命令的关系和适用场景。

U-Boot 内存操作命令全景图 U-Boot 内存操作命令全景图 内存操作命令 查看与修改 批量操作 验证 md mm nm mw cp cmp 使用场景速查 • md:查看 DDR 初始化结果、检查寄存器值 • mm / nm:交互式修改内存、调试硬件寄存器 • mw:清空 buffer、初始化内存池、填充测试数据 • cp + cmp:拷贝数据并验证完整性(调试 DMA 时常用)

4.9 实战组合拳:一条龙调试流程

最后,我分享一个我在项目中常用的调试套路。假设我要验证一段 DDR 是否正常工作:

  1. 清空目标区域:mw.l 0x80000000 0x0 1000
  2. 写入已知数据:mw.l 0x80000000 0xdeadbeef 1000
  3. 拷贝到另一区域:cp.l 0x80000000 0x90000000 1000
  4. 比较验证:cmp.l 0x80000000 0x90000000 1000
  5. 查看结果:md.l 0x80000000 10

如果 cmp 没有输出,说明 DDR 读写和拷贝都正常。如果 cmp 报错,你就知道问题出在哪了。嗯,这套流程我用了快十年,从来没让我失望过。