OpenOCD-JTAG調試



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功能:

    1. 當CPU的地址信號ADDR=xxx,停止CPU-硬件斷點
    2. 當CPU的數據信號DATA=xxx,停止CPU--軟件斷點
    3. 重新運行CPU
    4. 讀取R0,..寄存器
    5. 控制外設,內存
  • 百問網的OpenJTAG.exe這個GUI實際是封裝了openocd.exe命令行

    1. 設置Workdir到程序代碼目錄
    2. 點擊telnet,或者直接cmd輸入telnet 127.0.0.1 4444
    3. 使用help查看幫助或者查看`Eclipse,OpenOCD,OpenJTAGv3.1嵌入式開發教程版本5.pdf

斷點

  • 硬件斷點:一個程序只能打兩個斷點(ARM7),可以調試ROM,NOR

    設置CPU里面的JTAG比較器,使硬件斷點 Addr=A,當CPU發出A地址時停止

    CPU是如何運行?CPU需要取指令,也就是需要發出地址信號去取得指令,JTAG就檢測到這個地址

  • 軟件斷點,可以有無數個軟件斷點.前提是斷點的地址可寫,所以無法調試NOR,或者ROM上的程序,具體可以看下面的led.c的示例

    1. 設置CPU里面的JTAG比較強,使其值=一個特殊值
    2. 替換掉希望斷點位置(A)的值=這個特殊值,做好備份
    3. 當CPU讀取到這個特殊值,程序斷點
    4. 重新運行時,恢復這個(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調試(進階)

程序概述:

  1. 鏈接地址在0x30000,0000,運行的時候應該位於0x3000,0000
  2. 直接燒寫程序到內存中,也就是程序運行在0地址.並沒有在加載地址
  3. 程序功能:main中點燈,但是直接燒錄到內部ram,跑飛了
  4. 程序的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

mark

專家模式:對應比較自由高級的配置,我們一般直接用普通模式即可

  • Interface對應 OpenOCD\0.4.0\interface`中的選項
  • Target對應OpenOCD\0.4.0\ targetOpenOCD\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 文件中的命令
    

mark

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 的整數倍),二是方便。

mark

mark

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”

使用條件

  1. 代碼已經重定位,處於它的鏈接地址.為什么?【在源文件設置斷點,其實是在鏈接地址設置斷點,是根據鏈接地址去修改內存(軟斷點)】

  2. 鏈接腳本必須是按照固定格式text,data,bss分開

  3. 被調試的程序中含有debug信息,也就是編譯elf時有-g選項

    %.o:%.c
    	arm-linux-gcc -Wall -c -g -O2 -o $@ $<
    
    %.o:%.S
    	arm-linux-gcc -Wall -c -g -O2 -o $@ $<
    
  4. FAQ: 無法調試重定位的代碼,那么代碼搬運與sdram初始化怎么辦? 使用opencod來執行,也就是再弄一個程序初始化sdram,使用openocd燒錄

使用步驟

  1. 打開openocd,打開telent

  2. 如果是需要使用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
    
  3. 打開cmd,輸入arn-elf-gdb leds_elf啟動gdb,指定程序

  4. 連接到OpenOCDtarget remote 127.0.0.1:3333

  5. 下載程序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  //查看變量值

mark

Eclipes

Eclipse 是 gdb(包括 arm-elf-gdb, arm-linux-gdb)的圖形化前台,使用 Eclipse 進行調試實質
上是使用 gdb 進行調試。

使用 gdb 進行調試時, gdb 會用到程序的鏈接地址。比如在 main 函數打斷點, gdb 會根
據 main 函數的鏈接地址找到內存上對應的指令,修改這條指令為一條特殊的指令。當程序執
行到這條特殊的指令時,就會停止下來。[也就是軟件斷點]

使用條件

  1. 程序應該位於它的鏈接地址上
  2. 如果用到SDRAM,先初始化SDRAM,然后下載程序到鏈接地址

簡單工程

  1. 點擊圖標 Workbenchmark

  2. 新建一個C工程File -> New -> C Project,選擇 “ Makefile project->Empty Projects”、“ Other Toolchain”

  3. 導入文件在 File -> Import中的General>File System

  4. 工程設置

    • 在“ Project” 菜單里,點擊去掉“ Build Automatically”前面的標
      記,表示不自動編譯工程

    • 在“ Project” 菜單里,點擊clean,去除Start a build immediately

  5. 編譯,其實在目錄下直接make也是可以的了,已經安裝后工具鏈了

    • 使用Project中的build allbuild project都可以了
    • 使用clean也行了
    • 注意,make是不一樣的,一個是arm-linux,另一個是arm-elf
  6. 調試配置

    • 參考下面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存在着,需要關掉

mark

mark

u-boot工程

調試網上下載的 u-boot 時,需要定義 CONFIG_SKIP_LOWLEVEL_INIT,它表示
“跳過底層的初始始化”,就是不要初始化存儲控制器,不要再次復制 u-boot 本身到 SDRAM
中。對於韋東山的的 u-boot,已經增加的自動識別代碼,無需定義這個宏。

  1. import相關文件

  2. 設置命令如下,這個是為了建立路徑對應

    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\
    

    mark

STM32燒寫程序

halt
flash probe 0
flash write_image erase STM3210B.bin 0x08000000
verify_image STM3210B.bin 0x08000000

mark


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM