OpenOCD芯片调试和固件提取-以STM32为例
OpenOCD芯片调试及固件提取-以STM32为例
前言
OpenOCD支持多种处理器架构、接口和仿真器,并且可拓展,是一个相对通用的开源片上调试工具,主要用于嵌入式系统的调试、编程和测试。本文介绍其用法并用它来调试STM32芯片和提取固件。
除了OpenOCD,也可以用
STM32CubeProgrammer
等IDE读写固件 ,功能更全面,图形化程度更高,也有其它特定调试器的配套软件如针对JLINK的jflash等。相对其它工具,OpenOCD优势在于开源、灵活性强(兼容多种仿真器和芯片且可拓展)、轻量(命令行式操作),可以将其集成到其它工具或开发流程中。
如果STM32开启了RDP(读保护),则无法通过该方式读写flash,需要关闭保护或利用漏洞、故障注入等手段绕过保护。
设备基本信息
PC环境:ubuntu2004虚拟机
仿真器:CMSIS-DAP
MCU芯片型号:STM32F407ZGT6
打开数据手册查看内存 映射中Flash的位置:0x0800 0000-0x080F FFFF
OpenOCD简介
全称Open On-Chip Debugger(开放式片上调试器),由Dominic Rath开发,是其毕业论文的一部分,最初是针对ARM7和ARM9芯片的片上调试。
OpenOCD提供对嵌入式设备的调试烧录、系统编程和边界扫描功能,可以配合仿真器连接设备的调试接口(尤其是JTAG/SWD
)。
OpenOCD架构和原理
论文的第40页介绍了OpenOCD的架构,包含了如下几个模块
Daemon模块
:守护进程(daemon)是首先被初始化的部分,它控制着其余组件。守护进程通过评估命令行参数来管理程序配置,并使用CLI模块解析配置文件。每个模块都可以向CLI模块注册命令,这些命令既可用作配置语句,也可作为用户命令,或者在配置选项后续可能更改的情况下同时用于两者。JTAG模块
:JTAG模块是唯一直接访问被调试硬件的部分,它为其他模块提供了与目标设备通信的接口。Target模块
:目标(Target)模块封装了常见的调试功能,并向CLI模块注册特定于目标的命令。守护进程在等待新连接时通过访问目标模块来跟踪目标状态,并向GDB和CLI模块提供目标调试功能。GDB模块
:GDB模块在守护进程接受新的GDB连接后被调用,它将GDB远程串行协议映射到目标模块提供的接口上。GDB远程协议中定义了一种监控数据包(monitor packet),允许发送应由GDB服务器解释的命令。GDB模块将这些命令字符串交给CLI模块进行评估,并将任何回复返回给远程GDB,从而将GDB会话中可访问的功能扩展到各个模块提供的所有命令。CLI模块
:CLI模块实现了telnet服务器,处理配置解析,并允许其他模块注册命令。Flash模块
:闪存(Flash)模块仅通过命令接口访问,因为GDB协议未规定闪存操作,它利用Target模块提供的内存访问功能来编程闪存芯片。
Target模块将目标状态分为四种:unknown(未知)、reset(重置)、running(运行)和halt(停止)。其中比较重要的是运行和停止状态。类似gdb的调试,运行状态可以通过断点或CLI中的halt指令切换到停止状态;停止状态下可以下断点、检查寄存器和读写内存,也可以恢复到运行状态,有单步执行功能。更详细的介绍请参考论文6.4节。
OpenOCD安装
ubuntu上用apt包管理器安装即可
1 |
|
要获取指定版本的OpenOCD可以编译源码或下载编译好的release包。参考Getting OpenOCD。
OpenOCD使用
导入配置文件
OpenOCD需要导入正确的cfg
配置文件才能连接调试目标,其针对常见硬件提供了的现成的cfg文件,位于/usr/share/openocd/scripts
目录。
最基本的要求是导入target
和interface
中对应的cfg配置文件。target指芯片型号,interface指调试器类型。
如果没有现成的,我们也可以自己编写cfg文件。编写cfg文件用到的是TCL
脚本语言:OpenOCD开发文档-TCL 入门。
CLI交互
openocd导入配置文件
1 |
|
新开一个shell,telnet连接CLI(默认4444端口)
1 |
|
然后就可以和CLI交互了。一般刚连上目标处于运行状态,需要先输入halt
指令进入停止状态才能进行查看寄存器或访问内存等操作。
常用命令如下(截取自该博客):
1 |
|
更多指令参考:General Commands
GDB调试
获取交叉编译工具
需要从对应芯片的交叉编译工具链中获取专用的gdb
。
支持的目标架构分类
- AArch32:
- 裸机:
arm-none-eabi
- GNU/Linux硬浮点:
arm-none-linux-gnueabihf
- 裸机:
- AArch64:
- 裸机:
aarch64-none-elf
- GNU/Linux:
aarch64-none-linux-gnu
- GNU/Linux大端:
aarch64_be-none-linux-gnu
- 裸机:
进行调试
首先导入配置文件。
1 |
|
新开一个shell,运行gdb后输入target remote localhost:3333
连接到gdbserver。注意交叉编译工具链中的gdb名称可能不同。
1 |
|
这里的target.elf
指设备中正在运行的程序的可执行文件,通常在IDE编译后产生。
整体架构如下,Gdb Server端口是3333。
GDB常用指令(截取自该博客):
指令 | 简写 | 说明 |
---|---|---|
help |
h |
打印帮助信息 |
h next :打印 next 指令的用法说明 |
||
file |
指定程序文件 | |
load |
加载 file 指定的程序文件 | |
break |
b |
设置断点 |
b main :在 main 函数处设置断点 |
||
b 10 :在第 10 行设置断点 |
||
b test.c:10 : 在 test.c 的第 10 行设置断点 |
||
b if a == 1 : 当 a 等于 1 时打断 |
||
tbreak |
tb |
设置临时断点,只作用一次 |
info |
i |
显示调试信息 |
i b :显示所有断点 |
||
i display :显示所有表达式 |
||
delete |
d |
删除断点等 |
d :删除所有断点 |
||
d 2 :删除 2 号断点 |
||
run |
r |
从头开始执行程序 |
next |
n |
执行一条 C 语句,不进入函数 |
step |
s |
执行一条 C 语句,进入函数 |
nexti |
ni |
执行一条汇编语句,不进入函数 |
stepi |
si |
执行一条汇编语句,进入函数 |
continue |
c |
继续执行 |
finish |
fin |
跳出当前函数 |
until |
u |
跳出当前循环 |
backtrace |
bt |
打印所有栈帧 |
frame |
f |
打印当前栈帧 |
print |
p |
打印变量或表达式的值 |
display |
disp |
增加要显示值的表达式(disp+后缀/x=16进制,/i=汇编指令,/x=字符串等) |
call |
调用函数 | |
call func(1, 2) :调用 func 函数 |
||
tui enable |
- |
启用字符图形窗口 |
focus cmd :聚焦到 cmd 窗口 |
||
layout asm :显示汇编窗口,其他还有 src、regs、split |
||
winheight src -2 :调整源码窗口高度-2 |
||
tui disable |
关闭字符图形窗口 | |
disassemble <address> |
将给定的地址转换为汇编代码 | |
info threads |
显示所有线程的状态 | |
info line * <address> |
显示给定地址的 c++ 行源码信息 | |
info all-registers |
查看所有寄存器信息 |
STM32 固件提取
根据所用的仿真器和MCU芯片型号导入配置文件
1 |
|
然后telnet localhost 4444
连接CLI,halt
进入停止状态
最后用dump_image
命令,结合数据手册中flash在内存中的映射地址范围,提取固件并命名为flash.bin
1 |
|
STM32 Gdb调试
获取Linux系统下32位arm裸机的编译工具链
获取编译后的ELF可执行文件,笔者用keil编译烧录了一个点灯程序
是否生成ELF、文件名称和位置在魔法棒中设置
根据所用的仿真器和MCU芯片型号导入配置文件
1 |
|
解压编译工具链压缩包,运行bin目录下的arm-none-eabi-gdb
1 |
|
导入目标程序的ELF可执行文件
1 |
|
连接Gdb Server
1 |
|
启用字符图形窗口tui enable
,显示汇编layout asm
显示寄存器layout regs