title: OpenOCD-JTAG調試
tags: ARM
date: 2018-10-13 23:36:28
Todo
- [ ] JTAG 調試linux內核
- [ ] linux下使用OpenOCD調試
- [x] win下使用OpenOCD調試
概述
-
學習文檔 韋東山 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