OpenOCD-JTAG调试

Posted zongzi10010

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

技术分享图片

专家模式:对应比较自由高级的配置,我们一般直接用普通模式即可

  • Interface对应 OpenOCD.4.0interface`中的选项
  • Target对应OpenOCD.4.0 targetOpenOCD.4.0oard

启用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 显示指定文件的一段程序
info source 查看当前源程序
info stack 查看堆栈信息
info args 查看当前的参数
断点操作
break 在函数入口设置断点
break 在当前源文件的某一行上设置断点
break 在指定源文件的某一行上设置断点
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 [email protected] $<
    
    %.o:%.S
     arm-linux-gcc -Wall -c -g -O2 -o [email protected] $<
  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  //查看变量值

技术分享图片

Eclipes

Eclipse 是 gdb(包括 arm-elf-gdb, arm-linux-gdb)的图形化前台,使用 Eclipse 进行调试实质
上是使用 gdb 进行调试。

使用 gdb 进行调试时, gdb 会用到程序的链接地址。比如在 main 函数打断点, gdb 会根
据 main 函数的链接地址找到内存上对应的指令,修改这条指令为一条特殊的指令。当程序执
行到这条特殊的指令时,就会停止下来。[也就是软件断点]

使用条件

  1. 程序应该位于它的链接地址上
  2. 如果用到SDRAM,先初始化SDRAM,然后下载程序到链接地址

简单工程

  1. 点击图标Workbench技术分享图片

  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 Filesyagartoinarm-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,已经增加的自动识别代码,无需定义这个宏。

  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,添加如下(这个实际是上面的命令在生效)

    workprojectsOpenPDAu-boot-1.1.6_OpenJTAG
    E:Eeclipse_projectsu-bootu-boot-1.1.6_OpenJTAG

    技术分享图片

STM32烧写程序

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

技术分享图片













以上是关于OpenOCD-JTAG调试的主要内容,如果未能解决你的问题,请参考以下文章

谷歌浏览器调试jsp 引入代码片段,如何调试代码片段中的js

PHP代码-psysh调试代码片段工具

方便调试使用的代码片段

运行/调试你的PHP代码

eclipse 断点调试

在 intellij 上调试评估表达式