• 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.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 文件中的命令
      

    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 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存在着,需要关掉

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

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

      mark

    STM32烧写程序

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

    mark

  • 相关阅读:
    没有服务商如何购买ERP的序列号?
    智能ERP主副机设置
    银盒子·序列号购买(2018-12-05)
    简易付微信收款提示支付失败
    简易付无法登录的解决方案
    Orchard详解--第八篇 拓展模块及引用的预处理
    Orchard详解--第七篇 拓展模块(译)
    Orchard详解--第六篇 CacheManager 2
    Orchard详解--第五篇 CacheManager
    Orchard详解--第四篇 缓存介绍
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/9784797.html
Copyright © 2020-2023  润新知