OpenOCD-JTAG调试
Todo
概述
-
学习文档 韦东山 Eclipse,OpenOCD,OpenJTAGv3.1嵌入式开发教程版本5.pdf
-
硬件连接: PC>JTAG调试器>CPU
-
软件控制:IDE(KEIL/ADS/)> GDB(指令)> OpenOCD(实际命令)> JTAG调试器> 单板
-
JTAG控制CPU功能:
- 当CPU的地址信号ADDR=xxx,停止CPU-硬件断点
- 当CPU的数据信号DATA=xxx,停止CPU--软件断点
- 重新运行CPU
- 读取R0,..寄存器
- 控制外设,内存
-
百问网的
OpenJTAG.exe
这个GUI实际是封装了openocd.exe
命令行- 设置Workdir到程序代码目录
- 点击
telnet
,或者直接cmd输入telnet 127.0.0.1 4444
- 使用help查看帮助或者查看`Eclipse,OpenOCD,OpenJTAGv3.1嵌入式开发教程版本5.pdf
断点
-
硬件断点:一个程序只能打两个断点(ARM7),可以调试ROM,NOR
设置CPU里面的JTAG比较器,使硬件断点 Addr=A,当CPU发出A地址时停止
CPU是如何运行?CPU需要取指令,也就是需要发出地址信号去取得指令,JTAG就检测到这个地址
-
软件断点,可以有无数个软件断点.前提是断点的地址可写,所以无法调试NOR,或者ROM上的程序,具体可以看下面的led.c的示例
- 设置CPU里面的JTAG比较强,使其值=一个特殊值
- 替换掉希望断点位置(A)的值=这个特殊值,做好备份
- 当CPU读取到这个特殊值,程序断点
- 重新运行时,恢复这个(A)位置的指令
快速使用
常用命令
halt 停止cpu
reg 查看寄存器
mdw 0 //memory display word 查看内存
mww 0 0x12345678 //memory write word
load_image leds.bin 0 //下载程序到0地址,然后可以使用mdw读取0,看看是不是程序的bin
resume 0 //指定地址运行,如果不指定地址,则恢复运行
reset 复位目标板子
reset halt
step 0 //执行第一句话,并halt
step //单步执行
bp设置断点
bp 0x6c 4 hw 在0x6c的地址位置设置断点,硬件断点
rpb 0x6c 取消断点
测试led的断点
//led.c
void wait(volatile unsigned long dly)
{
for(; dly > 0; dly--);
}
int main(void)
{
unsigned long i = 0;
GPFCON = GPF4_out|GPF5_out|GPF6_out;
while(1){
wait(30000);-------------------尝试在这里断点,就能观察灯的变化
GPFDAT = (~(i<<4));
if(++i == 8)
i = 0;
}
return 0;
}
//反汇编摘要
00000044 <main>:
44: e52de004 str lr, [sp, #-4]!
48: e24dd004 sub sp, sp, #4 ; 0x4
4c: e3a03000 mov r3, #0 ; 0x0
50: e58d3000 str r3, [sp]
54: e3a03456 mov r3, #1442840576 ; 0x56000000
58: e2833050 add r3, r3, #80 ; 0x50
5c: e3a02c15 mov r2, #5376 ; 0x1500
60: e5832000 str r2, [r3]
64: e3a00c75 mov r0, #29952 ; 0x7500
68: e2800030 add r0, r0, #48 ; 0x30
6c: ebffffe9 bl 18 <wait>---------------------------尝试在这里断点
70: e3a02456 mov r2, #1442840576 ; 0x56000000
74: e2822054 add r2, r2, #84 ; 0x54
78: e59d3000 ldr r3, [sp]
7c: e1a03203 mov r3, r3, lsl #4
80: e1e03003 mvn r3, r3
84: e5823000 str r3, [r2]
88: e59d3000 ldr r3, [sp]
8c: e2833001 add r3, r3, #1 ; 0x1
90: e58d3000 str r3, [sp]
94: e3530008 cmp r3, #8 ; 0x8
98: 1afffff1 bne 64 <main+0x20>
9c: e3a03000 mov r3, #0 ; 0x0
a0: e58d3000 str r3, [sp]
a4: eaffffee b 64 <main+0x20>
Disassembly of section .debug_line:
使用openocd命令来调试断点
halt
load_image leds.bin 0 //下载程序
resume 0 //指定地址运行,如果不指定地址,则恢复运行
halt
bp 0x6c 4 hw //在0x6c的地址位置设置断点,硬件断点
resume //反复这个断点,就能观察灯的变化了
测试软件断点是否改变ram值
//读取原来的值
> mdw 0x6c
0x0000006c: ebffffe9
//设置软件断点
> bp 0x6c 4
breakpoint set at 0x0000006c
//读回这个ram值,发现改变了
> mdw 0x6c
0x0000006c: deeedeee
//删除这个断点
> rbp 0x6c
//读回来
> mdw 0x6c
0x0000006c: ebffffe9
NAND调试(进阶)
程序概述:
- 链接地址在
0x30000,0000
,运行的时候应该位于0x3000,0000
- 直接烧写程序到内存中,也就是程序运行在0地址.并没有在加载地址
- 程序功能:main中点灯,但是直接烧录到内部ram,跑飞了
- 程序的bug在于,所有的代码都应该是位置无关的否则需要搬运代码
.text
.global _start
_start:
@函数disable_watch_dog, memsetup, init_nand, nand_read_ll在init.c中定义
ldr sp, =4096 @设置堆栈
bl disable_watch_dog @关WATCH DOG
bl memsetup @初始化SDRAM
bl nand_init @初始化NAND Flash
@将NAND Flash中地址4096开始的1024字节代码(main.c编译得到)复制到SDRAM中
@nand_read_ll函数需要3个参数:
ldr r0, =0x30000000 @1. 目标地址=0x30000000,这是SDRAM的起始地址
mov r1, #0 @2. 源地址 = 0
mov r2, #4096 @3. 复制长度= 2048(bytes),
bl nand_read @调用C函数nand_read
ldr sp, =0x34000000 @设置栈
ldr lr, =halt_loop @设置返回地址
ldr pc, =main @b指令和bl指令只能前后跳转32M的范围
@,所以这里使用向pc赋值的方法进行跳转
halt_loop:
b halt_loop
bug原因:mem_cfg_val是在栈,是位置无关的,但是他的初始值是去在链接地址取的,也就是0x3000000
后面
void memsetup()
{
unsigned long const mem_cfg_val[]={ 0x22011110, //BWSCON
0x00000700, //BANKCON0
0x00000700, //BANKCON1
0x00000700, //BANKCON2
0x00000700, //BANKCON3
0x00000700, //BANKCON4
0x00000700, //BANKCON5
0x00018005, //BANKCON6
0x00018005, //BANKCON7
0x008C07A3, //REFRESH
0x000000B1, //BANKSIZE
0x00000030, //MRSRB6
0x00000030, //MRSRB7
};
}
调试开始
>reset halt
> load_image nand.bin 0
1520 bytes written at address 0x00000000
downloaded 1520 bytes in 0.063003s (23.560 KiB/s)
执行第一句话step 0
也就是执行mov sp, #4096 ; 0x1000
,可以使用reg
看到sp=0x1000
step
执行跳转,可以发现pc=0x38,对应汇编
30000000 <_start>:
30000000: e3a0da01 mov sp, #4096 ; 0x1000
30000004: eb00000b bl 30000038 <disable_watch_dog>
然后一步一步step
,并使用poll
查看当前pc值等,使用mdw
查看该设置的内存
跳转到memsetup
,pc=0x08,反汇编先入栈,可以发现sp=4096-5*4=4076=0xFEC
可以使用 step 0,然后设置 bp 0x50 4 hw 断点 直接跳到想到的位置
然后在汇编代码,发现问题了
// ip=300005bc
30000050: e1a0400c mov r4, ip
30000054: e8b4000f ldmia r4!, {r0, r1, r2, r3}
//从r4中指向的内存给r0~r3
//也就是说从 300005bc读取一些数据,但是这个时候300005bc(sdram)并没有被初始化且进行代码搬运,所以这里的数据肯定有问题
//这里的其实就是那个局部数组的值 mem_cfg_val
300005bc <.rodata>:
300005bc: 22011110 andcs r1, r1, #4 ; 0x4
300005c0: 00000700 andeq r0, r0, r0, lsl #14
300005c4: 00000700 andeq r0, r0, r0, lsl #14
300005c8: 00000700 andeq r0, r0, r0, lsl #14
300005cc: 00000700 andeq r0, r0, r0, lsl #14
300005d0: 00000700 andeq r0, r0, r0, lsl #14
300005d4: 00000700 andeq r0, r0, r0, lsl #14
300005d8: 00018005 andeq r8, r1, r5
300005dc: 00018005 andeq r8, r1, r5
300005e0: 008c07a3 addeq r0, ip, r3, lsr #15
300005e4: 000000b1 streqh r0, [r0], -r1
300005e8: 00000030 andeq r0, r0, r0, lsr r0
300005ec: 00000030 andeq r0, r0, r0, lsr r0
Disassembly of section .comment:
OpenOCD
全称是(Open On-Chip Debugger)
使用前都需要打开OpenOCD,连接上开发板,然后打开telent,或者使用命令telnet 127.0.0.1 4444
启动OpenOCD
专家模式:对应比较自由高级的配置,我们一般直接用普通模式即可
- Interface
对应
OpenOCD\0.4.0\interface`中的选项 Target
对应OpenOCD\0.4.0\ target
和OpenOCD\0.4.0\board
启用telnet
Win7默认没有打开这个功能,需要在程序和功能>打开或关闭 windows 功能> TelentClient
打开
OpenOCD命令
这些命令都在telnet中运行,官方命令索引在这里,PDF文档OpenOCD User's Guide.pdf
-
记住的命令
reset halt resume step load_image
-
目标板状态处理命令(Target state handling)
poll 查询目标板当前状态 halt 中断目标板的运行 resume [address] 恢复目标板的运行,如果指定了 address,则从 address 处开始运行 step [address] 单步执行,如果指定了 address,则从 address 处开始执行一条指令 reset 复位目标板
-
断点命令
bp <addr> <length> [hw] 在地址 addr 处设置断点,指令长度为 length, hw 表示硬件断点 rbp <addr> 删除地址 addr 处的断点 内存访问指令(Memory access commands)
-
内存访问指令(Memory access commands)
mdw ['phys'] <addr> [count] 显示从(物理)地址 addr 开始的 count(缺省是 1)个字(4 字节) mdh ['phys'] <addr> [count] 显示从(物理)地址 addr 开始的 count(缺省是 1)个半字(2 字节) mdb ['phys'] <addr> [count] 显示从(物理)地址 addr 开始的 count(缺省是 1)个字节 mww ['phys'] <addr> <value> 向(物理)地址 addr 写入一个字,值为 value mwh ['phys'] <addr> <value> 向(物理)地址 addr 写入一个半字,值为 value mwb ['phys'] <addr> <value> 向(物理)地址 addr 写入一个字节,值为 value
-
内存装载命令,注意:下载程序之前先使用“ halt” 命令暂停单板,才能下载代码;如果使用“ poll” 命令发现单板的 MMU 或 D-cache 已经使能,则需要使用“ arm920t cp15 2 0” 、“ step”两条命令禁止 MMU 和 D-cache。
load_image <file> <address> [‘bin’|‘ihex’|‘elf’] ======将文件<file>载入地址为 address 的内存,格式有‘bin’、 ‘ihex’、 ‘elf’ dump_image <file> <address> <size> ======将内存从地址 address 开始的 size 字节数据读出,保存到文件<file>中 verify_image <file> <address> [‘bin’|‘ihex’|‘elf’] ======将文件<file>与内存 address 开始的数据进行比较,格式有‘bin’、 ‘ihex’、 ‘elf’
-
CPU 架构相关命令(Architecture Specific Commands)
reg 打印寄存器的值 arm7_9 fast_memory_access ['enable'|'disable'] =======使能或禁止“快速的内存访问” arm mcr cpnum op1 CRn op2 CRm value 修改协处理器的寄存器 =======比如: arm mcr 15 0 1 0 0 0 关闭 MMU arm mrc cpnum op1 CRn op2 CRm 读出协处理器的寄存器 =======比如: arm mcr 15 0 1 0 0 读出 cp15 协处理器的寄存器 1 arm920t cp15 regnum [value] 修改或读取 cp15 协处理器的寄存器 =======比如 arm920t cp15 2 0 关闭 MMU
-
其他命令
script <file> 执行 file 文件中的命令
OpenOCD烧录程序
load_image <file> <address> [‘bin’|‘ihex’|‘elf’]
======将文件<file>载入地址为 address 的内存,格式有‘bin’、 ‘ihex’、 ‘elf’
====load_image led.bin 0
SDRAM初始化
OpenOCD可以快速读写SDRAM,前提是需要程序先初始化好SDRAM
,这样之后可以烧录u-boot到sdram,然后通过u-boot烧录其他大程序.这里需要一段初始化sdram的位置无关的代码init.bin
//从 Nand Flash 启动
load_image init/init.bin 0x0
resume 0x0
//NOR 启动
load_image init/init.bin 0x40000000
resume 0x40000000
烧录u-boot,之后打开串口,就能看到跑起来了
halt
load_image u-boot/u-boot.bin 0x33f80000
resume 0x33f80000
uboot烧录其他程序(led/uboot)
- u-boot 的命令把所有的数字当作 16 进制,所以不管是否在数字前加前缀“ 0x”,这个数
字都是 16 进制的。 - 擦除 Flash 的长度、烧写的数据长度,这些数值都是根据要烧写的
文件的长度确定的。 u-boot.bin 的长度是 178704 字节,即 0x2BA10 字节,使用的长
度都是 0x30000,一是为了与 Flash 的可擦除长度相配(16K 的整数倍),二是方便。
GDB
- linux下 arm-linux-gdb,win下arm-elf-gdb
arm-elf-gdb nand_elf
target remote 127.0.0.1:3333
load
GDB命令
启动/退出 | |
---|---|
gdb [FILE] arm-elf-gdb [FILE] arm-linux-gdb [FILE] | 启动 gdb,调试 FILE(也可以先不指定文件) |
quit | 退出 gdb |
target remote ip:port | 远程连接 |
文件操作 | |
file |
载入文件 FILE,注意:不会下载到单板上 |
load [FILE] | 把文件下载到单板上,如果不指定 FILE,则下载之前指定 过的(比如 file 命令指定的,或是 gdb 运行时指定的文件) |
查看源程序 | |
list |
列出某个函数 |
list |
以当前源文件的某行为中间显示一段源程序 |
list | 接着前一次继续显示 |
break * | 在某个地址上设置断点,比如 break *0x84 |
list - | 显示前一次之前的源程序 |
list FILENAME:FUNCTION list FILENAME:LINENUM | 显示指定文件的一段程序 |
info source | 查看当前源程序 |
info stack | 查看堆栈信息 |
info args | 查看当前的参数 |
断点操作 | |
break |
在函数入口设置断点 |
break |
在当前源文件的某一行上设置断点 |
break FILENAME:LINENUM | 在指定源文件的某一行上设置断点 |
info br | 查看断点 |
delete |
删除断点 |
diable |
禁止断点 |
enable |
使能断点 |
监视点(watch)操作 | |
watch |
当指定变量被写时,程序被停止 |
rwatch |
当指定变量被读时,程序被停止 |
数据操作 | |
print < EXPRESSION > | 查看数据 |
set varible=value | 设置变量 |
x /NFU ADDR | 检查内存值 ① N 代表重复数 ② F 代表输出格式 x : 16 进制整数格式 d : 有符号十进制整数格式 u : 无符号十进制整数格式 f : 浮点数格式 ③ U 代表输出格式: b :字节(byte) h :双字节数值 w :四字节数值 g :八字节数值 比如“ x /4ub 0x0”将会显示 0 地址开始到 4 个字节 |
执行程序 | |
step next nexti | 都是单步执行: step 会跟踪进入一个函数, next 指令则不会进入函数 nexti 执行一条汇编指令 |
continue | 继续执行程序,加载程序后也可以用来启动程序 |
帮助 | |
help [command] | 列出帮助信息,或是列出某个命令的帮助信 |
其他命令 | |
monitor <command …> | 调用 gdb 服务器软件的命令,比如:“ monitor mdw 0x0” 就是调用 openocd 本身的命令“ mdw 0x0” |
使用条件
-
代码已经重定位,处于它的链接地址.为什么?【在源文件设置断点,其实是在链接地址设置断点,是根据链接地址去修改内存(软断点)】
-
链接脚本必须是按照固定格式
text,data,bss
分开 -
被调试的程序中含有debug信息,也就是编译elf时有
-g
选项%.o:%.c arm-linux-gcc -Wall -c -g -O2 -o $@ $< %.o:%.S arm-linux-gcc -Wall -c -g -O2 -o $@ $<
-
FAQ: 无法调试重定位的代码,那么代码搬运与sdram初始化怎么办? 使用opencod来执行,也就是再弄一个程序初始化sdram,使用openocd烧录
使用步骤
-
打开openocd,打开telent
-
如果是需要使用sdram的程序,则先在OpenOCD中先下载初始化sdram的程序
> load_image init/init.bin 0 > resume 0 > halt target state: halted target halted in ARM state due to debug-request, current mode: Supervisor cpsr: 0x200000d3 pc: 0x000000b8 MMU: disabled, D-Cache: disabled, I-Cache: enabled // 测试一下 sdram可用 > mdw 0x30000000 0x30000000: ea000017 > mww 0x30000000 0x12345678 > mdw 0x30000000 0x30000000: 12345678
-
打开cmd,输入
arn-elf-gdb leds_elf
启动gdb,指定程序 -
连接到OpenOCD
target remote 127.0.0.1:3333
-
下载程序
load
直接使用命令脚本 gdb.init : arm-elf-gdb -x gdb.init leds_elf
target remote localhost:3333
monitor halt
monitor arm920t cp15 2 0
monitor step
load
break main
continue
注意 gdb运行之后没有断点之后停不了了,需要在telnet使用halt
,然后GDB
界面才能继续输入
常用命令
si 执行一条指令
braek main.c:21 //在main.c的21行打断点
c 或者 continue 继续运行
print i //查看变量值
Eclipes
Eclipse 是 gdb(包括 arm-elf-gdb, arm-linux-gdb)的图形化前台,使用 Eclipse 进行调试实质
上是使用 gdb 进行调试。
使用 gdb 进行调试时, gdb 会用到程序的链接地址。比如在 main 函数打断点, gdb 会根
据 main 函数的链接地址找到内存上对应的指令,修改这条指令为一条特殊的指令。当程序执
行到这条特殊的指令时,就会停止下来。[也就是软件断点]
使用条件
- 程序应该位于它的链接地址上
- 如果用到SDRAM,先初始化SDRAM,然后下载程序到链接地址
简单工程
-
点击图标
Workbench
-
新建一个C工程
File -> New -> C Project
,选择“ Makefile project->Empty Projects”、“ Other Toolchain”
-
导入文件在
File -> Import
中的General>File System
-
工程设置
-
在“ Project” 菜单里,点击去掉“ Build Automatically”前面的标
记,表示不自动编译工程 -
在“ Project” 菜单里,点击
clean
,去除Start a build immediately
-
-
编译,其实在目录下直接
make
也是可以的了,已经安装后工具链了- 使用
Project
中的build all
和build project
都可以了 - 使用
clean
也行了 - 注意,make是不一样的,一个是arm-linux,另一个是arm-elf
- 使用
-
调试配置
-
参考下面uboot的图配,非uboot不需要配置source选项,命令行也不需要第一个路径配置
-
去除debug中的
stop on startup at main
-
project> debug config
选择Zylin Native
,new或者双击都可以,出现配置 -
Main> C/C++ Application
选择调试的elfleds.elf
-
Debugger> Debugger
选择EmbeddedGDB
,下面的main选择arm-elf-gdb
或者是C:\Program Files\yagarto\bin\arm-elf-gdb.exe
-
GDB command file 可以选择,也可以不选
,其实就是提前运行的命令target remote localhost:3333 monitor halt monitor arm mcr 15 0 1 0 0 0 monitor step 0 load break main continue
但是虽然这里设置了断点,貌似有点问题,依然需要在
command
输入load break main continue
-
注意
如果有时候没有看到debug窗口,右上角点一下debug视图,然后F5试试
有时候使用clean,需要看下是不是有debug存在着,需要关掉
u-boot工程
调试网上下载的 u-boot 时,需要定义 CONFIG_SKIP_LOWLEVEL_INIT,它表示
“跳过底层的初始始化”,就是不要初始化存储控制器,不要再次复制 u-boot 本身到 SDRAM
中。对于韦东山的的 u-boot,已经增加的自动识别代码,无需定义这个宏。
-
import相关文件
-
设置命令如下,这个是为了建立路径对应
set substitute-path /work/eclipse_projects/u-boot/u-boot-1.1.6_OpenJTAG E:/Eeclipse_projects/u-boot/u-boot-1.1.6_OpenJTAG load break start_armboot c
然后再source中删除原来的default,添加如下(这个实际是上面的命令在生效)
\work\projects\OpenPDA\u-boot-1.1.6_OpenJTAG E:\Eeclipse_projects\u-boot\u-boot-1.1.6_OpenJTAG\
STM32烧写程序
halt
flash probe 0
flash write_image erase STM3210B.bin 0x08000000
verify_image STM3210B.bin 0x08000000