OPENJTAG调试学习(三):使用 gdb 命令行进行调试

使用 gdb 命令行进行调试

1 gdb 命令简介

  • 启动与退出
    启动与退出

  • 文件操作

文件操作

  • 查看源程序

查看源程序

  • 断点操作
    断点操作

  • 监视点操作
    监视点操作

  • 数据操作

数据操作

  • 执行程序
    执行程序

  • 帮助
    帮助

  • 其他命令

其他命令

2 使用 arm-elf-gdb 命令调试程序(以 S3C2440 为例)

以调试光盘上的 leds 程序为例(注:先启动 OpenOCD,如果想调试 S3C6410,OpenJTAGGUI 的“Work Dir”设为 E:\eclipse_projects\6410):

在这里插入图片描述
如果不想输入那么多命令,可以把它们写入一个文件,比如 gdb.init;然后这样启动 gdb:

arm-elf-gdb -x gdb.init leds_elf

附 gdb.init 的内容:

target remote localhost:3333 
monitor halt 
monitor arm920t cp15 2 0 // 对于S3C6410,这句改为 monitor arm mcr 15 0 1 0 0 0
monitor step 
load 
break main // 对于S3C6410,有些例子没有main函数,有xxxxx函数,这句改为:break xxxxx
continue

3 使用 Eclipse 进行开发

Eclipse 是 gdb 的图行化前台,如果不想以命令行的方式使用 GDB,可以使用 Eclipse。

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

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

所以,使用 gdb 调试时,程序应该位于它的链接地址上。这也是下面调试 u-boot 时,要先初始化 SDRAM 或 DDR,然后把 u-boot 下载到 0x33f80000(S3C2410/S3C2440)或0x57e00000(S3C6410)再调试的原因。

如果要调试的程序的链接地址在片内 RAM 上,就不需要初始化 SDRAM 或 DDR 了。

1 启动 Eclipse,进行简单设置

在 Eclipse 的安装目录 C:\Program Files\eclipse 下,双击 启动 Eclipse,等待一会,可以看到如图 2.38 所示界面。在里面设置工作目录为 E:\eclipse_projects,并选择“Use this as the default and do not ask again”,表示使用 E:\eclipse_projects 作为缺省目录,并不再提示。

在这里插入图片描述

在图 2.38 中,点击 OK 后,得到如图 2.39 所示界面。

在这里插入图片描述

图 2.39 中右边的图标名为“Workbench”,点击它,得到图 2.40 所示界面。

在这里插入图片描述

2 新建一个 Eclipse 工程

  • (1) 创建工程:
    在图 2.40 的界面中,选择菜单项“File -> New -> C Project”,如图 2.41 所示。

在这里插入图片描述
在随后出现的窗口中(如图 2.42 所示),指定工程名字、选择“Makefile project->EmptyProjects”、“Other Toolchain”,然后点击“Finish”按钮。

在这里插入图片描述

  • (2) 添加文件:

在创建 leds 工程时,如果 E:\eclipse_projects\leds 目录下已经有源文件,则这些文件会被自动加入工程中。如果 E:\eclipse_projects\leds 目录是空的话,要添加新文件时,可以使用以下方法导入文件。

下面先在 E:\eclipse_projects 下建立一个测试文件 test_file_to_add.txt,然后以它为例介绍怎么添加文件到工程里。

选择菜单项“File -> Import…”,然后选择“File System”作为文件来源,如图 2.33 所示。

在这里插入图片描述
点击“Next”,打开“Import”对话框,在里面指定源目录“From directory”、选择要导入的文件、指定目的目录“Into folder”。如图 2.44,点击“Finish”按钮即可添加文件。

在这里插入图片描述
至此,新的 Eclipse 工程建立完毕,如图 2.45 所示。双击左边的文件名,即可打开进行编辑。

在这里插入图片描述

  • 3 编译、清除程序

注意:很多程序无法在 windows 下编译,比如 u-boot、linux 内核,它们只能在 Linux 下编译,然后把可执行程序拿到 windows 下来调试。对于这些程序,本小节的内容就用不上。

Eclipse 工程建立好后,在“Project”菜单里,点击去掉“Build Automatically”前面的标记,表示不自动编译工程,如图 2.46 所示

在这里插入图片描述
现在可以使用按钮来编译、清除程序了。点击菜单“Project -> Build Project”开始编译程序,如图 2.47 所示;编译过程见图 2.48。

在这里插入图片描述
在这里插入图片描述
编译的效果与在命令行执行“make all”命令完全一样。

清除程序的方法是,选择菜单“Project -> Clean…”(如图 2.49),即可弹出一个对话框(如图 2.50)。

在这里插入图片描述
在这里插入图片描述
在图 2.50 中,不要选择“Start a build immediately”,然后点击“OK”即可清除程序。

4 使用 Eclipse 调试 Linux 内核

注意: OpenJTAG 的光盘里并没有附带 Linux 内核,要调试内核,需要在你的开发板光盘上取得源码,并编译出 vmlinux、uImage。

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

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

所以,使用 gdb 调试时,程序应该位于它的链接地址上。这也是前面调试 u-boot 时,要先初始化 SDRAM 或 DDR,然后把 u-boot 下载到 0x33f80000(S3C2410/S3C2440)或0x57e00000(S3C6410)的原因。

在 Eclipse 下调试 Linux 内核,与调试 u-boot 的方法几乎一样。差别在于 Linux 的链接地址一般是虚拟地址,比如S3C2410/S3C2440 的内核的链接地址是 0xC0008000,而这个地址在启动 MMU 之前是不对应实际内存的。

所以,在启动 MMU 之前,不能使用 gdb 进行调试。要先使用 openocd 本身的命令让它执行到 MMU 启动完毕;启动了 MMU 之后,就可以使用 gdb 进行调试了。

MMU 何时启动完毕?这需要看 Linux 的源代码。对于 ARM 架构的内核,一般是执行到arch/arm/kernel/head.S 里的以下红色的代码时,就表示 MMU 已经启动了。

__turn_mmu_on: 
	mov r0, r0 
	mcr p15, 0, r0, c1, c0, 0 @ write control reg 
	mrc p15, 0, r3, c0, c0, 0 @ read id reg 
	mov r3, r3 
	mov r3, r3 
	mov pc, r13

要用 eclipse 调试内核,分为这 4 步:

  • ① 先使用 u-boot 把内核读到内存里去:从 Flash 上读或通过网络下载都可以。
  • ② 在 openocd 的 telnet 界面使用“硬件断点”在“__turn_mmu_on”对应的物理内存上打断点。
  • ③ 启动内核,当内核停在“__turn_mmu_on”后,继续执行若干次“step”命令,看到 MMU启动后,删除步骤②设置的硬件断点。
  • ④ 使用 eclipse 来进行源码级的调试

补充知识:ARM 架构的处理器只有两个硬件断点(breakpoint/watchpoint),软件断点是使用一个硬件断点来模拟的,可以实现无数个软件断点。

ARM 的两个硬件断点,可以设置为监测地址信号、监测数据信号。换句话说就是可以设置为这两种情况:

  • a. 当 CPU 发出的地址信号等于某个值时,停止;
  • b. 当 CPU 发出的数字信号等于某个值时,停止。

硬件断点就是设置为第一种情况,使用硬件断点的话,在程序里最多只能设置两个断点(两个地址)。

软件断点就是设置为第二种情况,使用时一般是把程序里要打断点的指令取出保存,然后替换为某个特定的数据,当 CPU 执行到那条指令时,在取指过程中数据信号上出现了这个数据,CPU 就会停止;

当继续执行时,会先执行之前保存下来的指令。调试程序时,可以打上无数个软件断点。

硬件断点的好处是不会修改程序,只要程序执行到某个地址,就会停下来

软件断点会“保存、修改、恢复执行”断点的指令,这也意味着打断点时,程序必须已经位于它的加载地址上

而我们执行第①步时,内核放在哪里是很随意的。为避免麻烦,第②步使用“硬件断点”。

下面以调试 S3C2440 的内核为例进行演示。

  • ① 在 u-boot 操作界面,下载内核到内存(下面的命令使用 tftp 命令下载内核,供参考):
OpenJTAG> set ipaddr 192.168.1.17
OpenJTAG> set serverip 192.168.1.5
OpenJTAG> tftp 32000000 uImage
ERROR: resetting DM9000 -> not responding 
dm9000 i/o: 0x20000000, id: 0x90000a46 
DM9000: running in 16 bit mode 
MAC: 08:00:3e:26:0a:5b 
could not establish link 
TFTP from server 192.168.1.5; our IP address is 192.168.1.17 
Filename 'uImage'.
Load address: 0x32000000 
Loading: ################################################################# 
 ################################################################# 
 ################################################################# 
 ################################################################# 
 ################################################################# 
 ##################################### 
done 
Bytes transferred = 1848720 (1c3590 hex)
  • ② 在 openocd 的 telnet 操作界面,设置断点:
    看编译内核时得到的 System.map 文件,搜索“__turn_mmu_on”得到如下一行:
c0008060 t __turn_mmu_on

一开始还没有启动 MMU,对于 S3C2410/S3C2440,“c0008060”对应的物理地址为“0x30008060”。使用以下命令设置断点(必须设置硬件断点):

halt # 先停止开发板 
bp 0x30008060 4 hw # 设置硬件断点 
resume # 继续运行

uImage 的开始有个自解压的程序,它把真正的内核解压出来放在 0x30008000 开始的内存,解压完后再跳到 0x30008000 去执行真正的内核。

自解压的程序从 0x30008000 开始执行,解压得到的内核也是从 0x30008000 运行。所以内核启动时,0x30008060 这个地址执行到两次。

  • ③ 在 u-boot 里启动内核:

执行以下命令:

bootm 32000000

开发板会立刻在 0x30008060 处停止运行,当前运行的是内核的自解压程序,在 openocd的 telnet 操作界面以下命令让开发板继续执行:

resume

然后开发板会再次在 0x30008060 处停止运行,当前运行的是真正的、解压后的内核,在openocd 的 telnet 操作界面执行两次“step”命令,即可看到以下字样:

MMU: enabled, D-Cache: enabled, I-Cache: enabled

然后就可以在 telnet 操作界面里删除断点了,执行“rbp 0x30008060”即可。
所有在 openocd 的 telnet 操作界面执行的命令如图 2.80 所示,注意与 u-boot 的配合。

在这里插入图片描述

  • ④ 在 eclipse 里继续调试内核:
    在 eclipse 里的设置与调试 u-boot 时是差不多的了,如图 2.81~2.84 所示。有两点小差别:
  • a. 图 2.82 里没有使用 s3c2440_gdb.init。这个文件的作用是让 gdb 连接到 openocd,并且初始化内存。执行到本步骤,u-boot 早已经初始化内存,并且早把内核加载到内存里了,所以不
    要再次初始化内存。
  • b. 图 2.83 里的命令如下,第一条命令是让 gdb 连接到 openocd:
target remote 127.0.0.1:3333 # 图2.82里没用s3c2440_gdb.init,所以要在命令里指定gdb的目标 
set substitute-path /work/projects/jz2440/linux-2.6.22.6 E:/eclipse_projects/linux-2.6.22.6_jz2440 # 内核的编译路径、当前路径 
break start_kernel # 不要使用load命令再次加载,因为内核已经在内存里了,直接打断点
c # 继续运行

在这里插入图片描述
注意:u-boot 里用 uImage,这里用 vmlinux。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

参考资料:

《Eclipse,OpenOCD,OpenJTAG教程》

猜你喜欢

转载自blog.csdn.net/weixin_45264425/article/details/132004195