*关于openocd
openocd是一个开源的调试工具,支持一些主流的CPU,不过目前来看,支持ARM是比较多的。其主页为http://openocd.berlios.de/ . 目前openocd开发得还比较频繁,我现在用的svn revision是1833。当然,如果有新的话,最好尝试最新的revision。如果遇到了问题,比如编译通不过之类的,可以再试试稍微旧一点的版本。
编译和安装openocd的方法:
首先从svn 上将最新的代码checkout下来:
svn co svn://svn.berlios.de/openocd/trunk openocd
这会将代码checkout到当前目录的openocd目录中,然后,可以进入到openocd目录中开始编译和安装:
cd openocd
./bootstrap
./configure --enable-parport
make
sudo make install
上面的./configure --enable-parport是指编译对并口jtag转换小板的支持,如wiggler。openocd也支持其他一些jtag转换板,可以使用./configure --help查看。因为我只有qq2440自带的那个并口小板,所以启用了并口支持。
*关于gdb
用gdb调试arm程序,需要一个专门针对arm的gdb,而一般x86系统上安装的是针对x86的gdb,是不能用来调试arm代码的。这同交叉编译的概念是一致的,我们需要这样一个gdb,他运行在我们的开发平台上,一般是X86 PC机,但他针对的调试对象是ARM的。
arm-linux-gdb的编译:
首先从ftp://ftp.gnu.org上去下载最新的gdb源码,我下载的是gdb-6.8.tar.bz2。编译安装的过程是:
tar -jxf gdb-6.8.tar.bz2
cd gdb-6.8
./configure --target=arm-linux --program-prefix=arm-linux-
make
sudo make install
这样就得到了arm-linux-gdb, 不过我一般不使用make install将这个arm-linux-gdb装到系统里面去,而是直接运行源码里面编译出来的gdb可执行文件。怎么样方便就看你的习惯了。
*关于jtag并口小板
其实目前的一些并口小板,绝大多数用的都是一个原理,就是直接利用PC机的并口来模拟JTAG时序。之所以会有这么多的变体,主要的差别是接法不一样,也就是说用的并口上数据端口的不同的bit,但是本质上是没有区别的。
openocd的parport驱动支持一些常见的并口小板,在openocd的src/jtag/parport.c里面,有一个这样的数组:
static cable_t cables[] =
{
/* name tdo trst tms tck tdi srst o_inv i_inv init exit led */
{ "wiggler", 0x80, 0x10, 0x02, 0x04, 0x08, 0x01, 0x01, 0x80, 0x80, 0x80, 0x00 },
{ "wiggler2", 0x80, 0x10, 0x02, 0x04, 0x08, 0x01, 0x01, 0x80, 0x80, 0x00, 0x20 },
{ "wiggler_ntrst_inverted",
0x80, 0x10, 0x02, 0x04, 0x08, 0x01, 0x11, 0x80, 0x80, 0x80, 0x00 },
{ "old_amt_wiggler", 0x80, 0x01, 0x02, 0x04, 0x08, 0x10, 0x11, 0x80, 0x80, 0x80, 0x00 },
{ "arm-jtag", 0x80, 0x01, 0x02, 0x04, 0x08, 0x10, 0x01, 0x80, 0x80, 0x80, 0x00 },
{ "chameleon", 0x80, 0x08, 0x04, 0x01, 0x02, 0x10, 0x00, 0x80, 0x00, 0x00, 0x00 },
{ "dlc5", 0x10, 0x00, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00 },
{ "triton", 0x80, 0x08, 0x04, 0x01, 0x02, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00 },
{ "lattice", 0x40, 0x10, 0x04, 0x02, 0x01, 0x08, 0x00, 0x00, 0x18, 0x18, 0x00 },
{ "flashlink", 0x20, 0x10, 0x02, 0x01, 0x04, 0x20, 0x30, 0x20, 0x00, 0x00, 0x00 },
/* Altium Universal JTAG cable. Set the cable to Xilinx Mode and wire to target as follows:
HARD TCK - Target TCK
HARD TMS - Target TMS
HARD TDI - Target TDI
HARD TDO - Target TDO
SOFT TCK - Target TRST
SOFT TDI - Target SRST
*/
{ "altium", 0x10, 0x20, 0x04, 0x02, 0x01, 0x80, 0x00, 0x00, 0x10, 0x00, 0x08 },
{ NULL, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
};
这个接口提定义了一些常见的并口小板,可以看到,无非就是将jtag接口的信号接到了不同的数据位上。我看了下,qq2440所带的并口小板接法对应这个chameleon。不过我对这个小板又做了点改造,具体的看下面。
*关于jtag接口
jtag接口中,必要的信号是TMS, TCK, TDI, TDO, TRST和SRST是可选的信号,其中TRST用来复位TAP控制器,SRST用来复位目标CPU。正是因为这两个信号是可选的,所以造成了一些混乱。对openocd来说,最理想的情况是,它能够分别控制TRST和SRST两个信号。这样的话,他就可以先设置SRST并保持住,让目标CPU保持在复位的状态,然后用TRST信号复位TAP控制器,最后再取消SRST信号,这样就可以让目标CPU复位后就处于debug状态。这样的前提是,目标CPU和jtag小板都要支持这两个信号,S3C2440以及qq2440的设计是支持这两个信号的,但是,附带的那个并口小板就有些问题了。并口小板上只连接了四个必要的信号,并没有接TRST和SRST。我对这个小板的改造就是手工补焊上这两根线。
并口小板上的HC541是个8通道的驱动器,起电平转换用,TMS,TCK,TDI,TDO,四个信号占用了4个通道,另四个通道空着,我所补焊的几根线,就是利用其中的两个通道补上TRST和SRST。可以看看上面的cables数组的定义,其中的chameleon定义和svn上的源码是有点区别的。trst的对应值是0x08, srst对应的值是0x10,这可以看出补焊线的方法:我用并口8位数据口的第4位(0x08)作trst,第5位(0x10)作srst。有机会我在把改后的照片发上来,其实很简单的,有点电路知识的人应该可以自己搞定。
*openocd的使用
关于openocd的说明,最好的办法当然是看openocd的文档。我这里所列的,是目前我所能实现的一些东西,当然还有很多我也在研究中,比如如何用他来烧写flash。
首先openocd需要一个配置文件,配置文件的写法很灵活,可以使用一个文件,也可以使用-f 参数指定的多个文件,还可以在一个配置文件中包含另一个文件。我这里用的是最直接的办法,在当前目录下放一个openocd.cfg 这样,当运行openocd的时候,默认就会找这个配置文件,我的配置文件的内容为:
telnet_port 4000
gdb_port 3000
interface parport
parport_port 0x378
parport_cable chameleon
jtag_speed 0
source [find target/samsung_s3c2440.cfg]
$_TARGETNAME configure -event reset-init {
mww 0x48000000 0x22111112
mww 0x48000004 0x00000700
mww 0x48000008 0x00000700
mww 0x4800000c 0x00000700
mww 0x48000010 0x00000700
mww 0x48000014 0x00000700
mww 0x48000018 0x00000700
mww 0x4800001c 0x00018009
mww 0x48000020 0x00018009
mww 0x48000024 0x008e04eb
mww 0x48000028 0x000000b2
mww 0x4800002c 0x00000030
mww 0x48000030 0x00000030
}
前两行指定了telnet 和 gdb连接时所使用的端口,然后就指定了并口的地址和并口小板的类型。source用来包含另一个文件,find的默认查找路径是/usr/local/lib/openocd。 最后的reset-init定义了一个event,当系统复位的时候,会执行这些命令,以上这些命令用来初始化SDRAM控制器。
准备好配置文件后,可以在当前目录下运行openocd,注意要使用root权限,并确保jtag已经连接好,开发板电源已经打开。
fox@NA ~ $ sudo openocd
Open On-Chip Debugger 0.2.0-in-development (2009-05-19-10:39) svn:1833M
BUGS? Read http://svn.berlios.de/svnroot/repos/openocd/trunk/BUGS
$URL: svn://svn.berlios.de/openocd/trunk/src/openocd.c $
jtag_speed: 0
Info : JTAG tap: s3c2440.cpu tap/device found: 0x0032409d (Manufacturer: 0x04e, Part: 0x0324, Version: 0x0)
Info : JTAG Tap/device matched
Warn : no tcl port specified, using default port 6666
如果提示 “Info : JTAG tap: s3c2440.cpu tap/device found: 0x0032409d (Manufacturer: 0x04e, Part: 0x0324, Version: 0x0)”, 表示openocd已经通过jtag连接上了目标CPU,我们的任务也完成了一半,注意openocd会停在这个地方,是正常的,所以下面的操作要在另一个终端窗口里面进行。
然后就可以用telnet连上openocd了:
fox@NA ~ $ telnet 127.0.0.1 4000
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Open On-Chip Debugger
>
在">"提示符后就可以输入openocd的命令了,还是那句话,看openocd的文档。输入help也会出一个命令的帮助列表出来。常用的命令:
reset [halt|init|run] 复位目标CPU,复位后可以使halt, init或run。halt标示立即挂起,init表示要运行init脚本,run标示复位后不挂起CPU,让CPU继续运行。
halt 挂起目标板的CPU。
resume 恢复CPU的运行
step 单步
mdb/mdh/mdw 显示内存(字节,半字,字)
mwb/mwh/mww 写内存(字节,半字,字)
reg 显示寄存器
load_image 载入数据到内存中指定的地方
上面的mdb/mdh/mdw/mwb/mwh/mww这些指令如果在CPU开启了MMU的状态下,后面的地址会被解释为虚拟地址。如果想访问物理地址,可以使用对应的 arm920t mdb_phys, arm920t_mdh_phys等等,详见help。
*用arm-linux-gdb配合openocd来调试程序
目前,我对gdb的使用还不是很熟悉,基本上是摸着石头过河的状况。因此下面肯定有很多地方显得比较笨拙,哎,这个只能在学习中慢慢改进了。
首先启动arm-linux-gdb,直接运行就可以,出现提示符后,可以在gdb里面连接上openocd:
GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-pc-linux-gnu --target=arm-linux".
(gdb) target remote 127.0.0.1:3000
Remote debugging using 127.0.0.1:3000
0x00000038 in ?? ()
还是按照我的习惯,先从最简单的开始。这里就不调试什么kernel啊,从最简单的裸奔程序开始,首先,来个只有汇编的。作为例子,下面这个简单得不能再简单了:
.text
.global _start
_start:
mov r0, #0
mov r1, #0
mov r2, #0
loop:
b loop
文件保存为test1.S,注意是大写的“S”, 然后用arm-linux-gcc进行编译和连接:
fox@NA ~ $ arm-linux-gcc -g -c test1.S -o test1.o
fox@NA ~ $ arm-linux-ld -g -Ttext 0x30000000 test1.o -o test1.elf
上面用 -Ttext指定了代码的加载地址,0x30000000就是qq2440上SDRAM映射的地址。
把编译出来的目标文件加载到SDRAM中有两个方法,一个是用openocd的load_image命令,不过load_image命令不认识elf格式,所以要用objcopy将.text节从elf文件里面取出来,然后在用openocd的load_image将其加载到指定的地方,也就是0x30000000。
还有一个方法,我一般在gdb里面直接载入elf文件,elf里面已经包含了载入地址,入口点等信息,直接用gdb的load命令就可以了,如下:
(gdb) load test1.elf
Loading section .text, size 0x10 lma 0x30000000
Start address 0x30000000, load size 16
Transfer rate: 1 KB/sec, 16 bytes/write.
(gdb) file test1.elf
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Load new symbol table from "/home/fox/test1.elf"? (y or n) y
Reading symbols from /home/fox/test1.elf...done.
(gdb) i r
r0 0x0 0
r1 0x53000000 1392508928
r2 0x6 6
r3 0x50000010 1342177296
r4 0x0 0
r5 0x0 0
r6 0x0 0
r7 0x0 0
r8 0x594 1428
r9 0xf673eefd 4134792957
r10 0x33f12fb4 871444404
r11 0x0 0
r12 0x2 2
sp 0x33defe90 0x33defe90
lr 0x594 1428
pc 0x30000000 0x30000000 <_start>
fps 0x0 0
cpsr 0x6000005f 1610612831
file指令是用载入符号和调试信息,后面用 i r来显示寄存器信息,可以看到pc都已经设置好了。用n命令就可以单步调试了,不过gdb我用的不太熟,还要摸索。
*用arm-linux-gdb调试c代码
如果要编写调试一段裸奔的c代码,其实很简单,对于c代码的运行,只需要一个条件,就是要准备好栈。一般做法是写一小段汇编引导程序,设置好栈之后,再跳转到c代码里面。汇编代码test2.S如下:
.text
.global _start
_start:
ldr sp, =0x34000000
bl mymain
loop:
b loop
上面的 ldr sp, =0x34000000 就是将栈指针设置在64M SDRAM空间的最后,因为栈是向内存地址小的方向增长的。bl用来跳转到c代码中, test2.c如下:
#define GPBCON (*(unsigned long*)0x56000010)
#define GPBDAT (*(unsigned long*)0x56000014)
#define GPBUP (*(unsigned long*)0x56000018)
int mymain()
{
volatile unsigned long v = GPBCON;
v &= 0xFFFc03FF;
v |= 0x00015400;
GPBCON = v;
v = GPBDAT;
v |= 0x000001e0;
GPBDAT = v; /* turn off all LEDs */
v &= ~0x000001e0;
GPBDAT = v; /* turn on all LEDs */
return 0;
}
编译过程
arm-linux-gcc -g -c test2.S -o test2.o
arm-linux-gcc -g -c test2.c -o test2_main.o
arm-linux-ld -g -Ttext 0x30000000 test2.o test2_main.o -o test2.elf
在gdb环境中载入并调试的过程为:
GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-pc-linux-gnu --target=arm-linux".
(gdb) target remote 127.0.0.1:3000
Remote debugging using 127.0.0.1:3000
0x3000009c in ?? ()
(gdb) load test2.elf
Loading section .text, size 0xa8 lma 0x30000000
Start address 0x30000000, load size 168
Transfer rate: 4 KB/sec, 168 bytes/write.
(gdb) file test2.elf
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from /home/fox/test2.elf...done.
(gdb) i r
r0 0x0 0
r1 0x104 260
r2 0x61e 1566
r3 0x56000014 1442840596
r4 0x20000 131072
r5 0x33f00000 871366656
r6 0x0 0
r7 0x0 0
r8 0x594 1428
r9 0xf673eefd 4134792957
r10 0xc8 200
r11 0x33fffffc 872415228
r12 0x34000000 872415232
sp 0x33ffffec 0x33ffffec
lr 0x30000008 805306376
pc 0x30000000 0x30000000 <_start>
fps 0x0 0
cpsr 0x800000d3 2147483859
Current language: auto; currently asm
(gdb) b mymain
Breakpoint 1 at 0x3000001c: file test2.c, line 7.
(gdb) c
Continuing.
Breakpoint 1, mymain () at test2.c:7
7 volatile unsigned long v = GPBCON;
Current language: auto; currently c
(gdb) n
8 v &= 0xFFFc03FF;
(gdb) n
9 v |= 0x00015400;
(gdb) n
10 GPBCON = v;
(gdb) n
13 v = GPBDAT;
(gdb) n
14 v |= 0x000001e0;
(gdb) n
15 GPBDAT = v; /* turn off all LEDs */
(gdb) n
17 v &= ~0x000001e0;
(gdb) n
18 GPBDAT = v; /* turn on all LEDs */
(gdb) n
20 return 0;
(gdb)
可以看到,可以用b设置断点,用n进行源码级的调试,单步跟踪,也能观察到开发板上的所有LED先熄灭,再点亮。关于gdb的更多操作,也正在学习中。