一、命令行调试(gdb)
程序参考自
http://balau82.wordpress.com/2010/08/17/debugging-arm-programs-inside-qemu/
启动的汇编片段(startup.s)
.global _Reset _Reset: LDR sp, =stack_top BL c_entry B .
链接脚本(test.ld)
ENTRY(_Reset) SECTIONS { . = 0x10000; .startup . : { startup.o(.text) } .text : { *(.text) } .data : { *(.data) } .bss : { *(.bss) } . = . + 0x1000; /* 4kB of stack memory(4kB的堆栈内存)*/ stack_top = .; }
(修改二进制文件布局的另一种做法是使用ld的命令行,或既使用-T开关又使用命令行)
主程序(test.c):
volatile unsigned int * const UART0DR = (unsigned int *)0x101f1000; void print_uart0(const char *s) { while(*s != '\0') { /* Loop until end of string (循环直至字符串结束)*/ *UART0DR = (unsigned int)(*s); /* Transmit char (发送字符)*/ s++; /* Next char (下一个字符)*/ } } void c_entry() { print_uart0("Hello world!\n"); }
(UART是串口的底层实现,严格来说它是一块芯片,但在嵌入式开发中经常指操纵串口的I/O端口和特殊寄存器,使用方式和内存访问很相似,所以经常用于输出调试信息)
qemu调试批处理文件(qemu_test.bat):
"D:\java\qemu-0.9.0-arm\qemu-system-arm.exe" -s -S -M versatilepb -m 128M -kernel test.bin pause
(我使用的是模拟ARM的qemu,注意qemu-system-arm和-M开关,versatile pb是一种使用ARM926EJ-S的开发板)
gdb调试脚本(gdbinit.txt):
file test.elf target remote localhost:1234 b c_entry cont
(可以手工输入到gdb控制台,b是breakpoint的缩写)
(file命令用于加载调试信息,所以elf文件在编译时需要加入-g开关)
(target用于远程调试,前提是远程机器运行gdbserver监听1234端口,这里qemu模拟器已经有gdbserver的功能)
(设置程序参数可以用set args,运行程序可以用run)
构建文件(Makefile):
# see http://balau82.wordpress.com/2010/08/17/debugging-arm-programs-inside-qemu/ # for debug: # make clean all # for release: # make clean all DEBUG="" PROJECT := test OBJS := startup.o test.o DEBUG := -g ARGS := -mcpu=arm926ej-s ${DEBUG} EMU := qemu_test.bat AS := arm-none-eabi-as ${ARGS} CC := arm-none-eabi-gcc ${ARGS} LD := arm-none-eabi-ld -T ${PROJECT}.ld -Map ${PROJECT}.map GDB := arm-none-eabi-gdb --command=gdbinit.txt RUN := arm-none-eabi-run SIZE := arm-none-eabi-size OBJCOPY := arm-none-eabi-objcopy -O binary RM := rm -f all:${PROJECT}.bin ${PROJECT}.bin:${PROJECT}.elf ${SIZE} $< ${OBJCOPY} $< $@ ${PROJECT}.elf:${OBJS} ${LDSCRIPT} ${LD} ${OBJS} -o $@ %.o:%.s ${AS} $< -o $@ %.o:%.c ${CC} -c ${ARGS} $< -o $@ #run:all # ${GDB} ${PROJECT}.elf run:all start ${EMU} ${GDB} clean: ${RM} *.o *.elf *.bin *.map
(我写的Makefile实在不敢恭维...,惯常方法是使用CFLAGS变量)
(变量值可以用make的参数覆盖,例如make clean all DEBUG="")
(tab缩进是必须的,否则Makefile无法使用)
编译这个Makefile工程需要以下东西
1. Sourcery G++ Lite Edition for ARM
http://www.codesourcery.com/sgpp/lite/arm
获得其中已经编译好的交叉编译器(可以直接在windows下使用)
2. MSys/MinGW
http://sourceforge.net/projects/mingw/
获得其中的make工具
3. QEMU on Windows (arm)
http://www.h7.dion.ne.jp/~qemu-win/
下载for ARM平台的那个
http://www.h6.dion.ne.jp/~kazuw/qemu-win/qemu-0.9.0-arm.zip
在MSys的/etc/profile.d目录中创建sourcery.sh
#!/bin/sh export PATH="/arm-2010.09/bin:$PATH"
(可以在msys中手工输入export PATH="/arm-2010.09/bin:$PATH"更改环境变量,大小写敏感)
把Sourcery G++的arm工具链加入MSys的PATH中。
然后运行msys.bat,切换到Makefile工程的目录下执行
make clean all
重建工程,生成带调试信息的test.bin和符号表test.map
rm -f *.o *.elf *.bin *.map
arm-none-eabi-as -mcpu=arm926ej-s -g startup.s -o startup.o
arm-none-eabi-gcc -mcpu=arm926ej-s -g -c -mcpu=arm926ej-s -g test.c -o test.o
arm-none-eabi-ld -T test.ld -Map test.map startup.o test.o -o test.elf
arm-none-eabi-size test.elf
text data bss dec hex filename
144 0 0 144 90 test.elf
arm-none-eabi-objcopy -O binary test.elf test.bin
(clean和all不是指硬盘上的文件,而是特定的目标)
然后执行
make run
运行qemu和gdb进行调试
常用语句
* list:显示附近代码
* step:步进
* display / undisplay / print:计算表达式/取消计算表达式/临时计算表达式
* break / continue:设置断点 / 继续执行
* bt:调用堆栈回溯
20110502:
这里有个教程链接集(日文)
http://850mb.net/pukiwiki/index.php?gdb
http://rat.cis.k.hosei.ac.jp/article/devel/debugongccgdb3.html
break b ブレークポイントの設定 【b】添加断点
continue c プログラムの再開 【c】继续执行程序
command comm ブレークポイントヒット時の動作の設定 【comm】设置断点命中时的动作
delete d ブレークポイントの削除 【d】删除断点
delete display d d displayの削除 【d d】删除
display disp 常に表示 【d】普通的变量值显示
finish fin 現在の関数の終了まで実行 【fin】执行直至现在的函数结束
frame f 関数フレームの移動 【f】移动函数帧
help h ヘルプを表示 【h】查看帮助
info i 各種情報のリストを表示 【i】获取各种信息
info breakpoints i b ブレークポイント情報を表示 【i b】获取断点信息
info display i di displayの状態を表示 【i di】获取变量显示信息
info watchpoints i wat ウォッチポイント情報を表示 【i wat】获取watch值信息
list l ソースプログラムを表示 【l】显示源程序
next n ステップアウト実行 【n】step out步出
print p 式を評価して結果を表示 【p】计算表达式的值
printf printf printのフォーマット表示 【printf】格式化计算表达式的值
ptype pt 式の型を表示 (詳細) 【ptype】详细的原型
quit q gdbの終了 【quit】退出gdb会话
return ret 現在の関数をその場で終了 【ret】现在的函数在这里返回
run r プログラムの実行開始 【r】程序开始执行
rwatch rw 読み出しウォッチポイントの設定【rw】读取watch设置
set set 変数の値の設定など 【set】设置变量值
step s ステップイン実行 【step】step in步进
watch wa 書き込みウォッチポイントの設定 【wa】设置写入的watch点
whatis wha 式の型を表示 (簡易) 【wha】简单的表达式类型
上面的【】表示gdb命令缩写
二、使用Eclipse CDT和qemu进行ARM二进制程序调试
1. Sourcery G++ Lite Edition for ARM
下载第一个EABI版的IA32 Windows Installer即可
http://www.codesourcery.com/sgpp/lite/arm
默认安装在C:\Program Files\CodeSourcery\Sourcery G++ Lite
选择Do not modify PATH(不加入PATH,不过加入也无妨,
因为有交叉编译器前缀可以区分)
另一种选择是下载绿色版IA32 Windows TAR
随便找个地方解压即可用(好像MinGW那样)
2. JDK 1.6
JRE应该也可以,主要是后面Eclipse需要用到
http://www.oracle.com/technetwork/java/javase/downloads/index.html
3. MSys/MinGW
真正有用的是MSys,不过不排除你整个MinGW都装了(我试过在线装MinGW,结果顺带把MSys也装好了)
好像有单独的安装文件,但最好在线安装
http://sourceforge.net/projects/mingw/
MSys的好处是自带了make工具,不需要使用Sourcery工具链那个。
另外还有很多类Unix的系统工具,对使用Makefile很有帮助。
4. Eclipse CDT
最新版可能不支持GNU ARM Eclipse Plug-in,所以我用CDT 6.0.2 for Eclipse Galileo:
http://www.eclipse.org/downloads/packages/eclipse-ide-cc-developers/galileosr2
点击下载这个:
Windows 32-bit
然后解压即可。
启动前最好写个批处理导入PATH。
set PATH= set PATH=C:\Program Files\CodeSourcery\Sourcery G++ Lite\bin;%PATH% set PATH=D:\java\jdk1.6.0_20\bin;%PATH% set PATH=C:\MinGW\msys\1.0\bin;%PATH% start eclipse.exe
重命名为start_cdt.bat,放入我的cdt目录中
D:\java\eclipse-cpp-helios-SR2-win32\eclipse
双击启动Eclipse
把Workspace设置为C:\Documents and Settings\Administrator\workspace_arm
勾选默认,然后按OK
5. GNU ARM Eclipse Plug-in
http://sourceforge.net/projects/gnuarmeclipse/
把zip下载到本地,然后把其中的plugins和features两个文件夹复制到CDT目录下
D:\java\eclipse-cpp-helios-SR2-win32\eclipse
然后重新启动Eclipse CDT
可以发现在File->New->C Project时
Project type:会多出两项
* ARM Cross Target Application
* ARM Cross Target Static Library
选择ARM Cross Target Application->ARM Windows GCC (Sourcery G++ Lite)
Project Name:填helloworld
创建新的C工程。
创建完后会发现左面的工程树会显示
C:\Program Files\CodeSourcery\Sourcery G++ Lite\
的头文件工程树,这是自动检测的结果,可以查看工程选项
右键->Properties->C/C++ Build->Discovery Options
默认勾选了自动扫描
Automate discovery of paths and symbols
可能插件会自动在PATH环境变量中搜索带arm-none-eabi-前缀的exe
以确定需要使用的gcc
另一个要留意的地方是
右键->Properties->C/C++ Build->Settings
这里可以设置各种编译参数,并且附有一些关键开关的英文说明。
Binary Parser选项页默认选中GNU Elf Parser,
用于在工程树中直接查看生成debug版elf文件的类型和变量信息。
6. 创建C工程
File->New->Source File
Source File:填入main.c
输入如下内容
/* * main.c * * Created on: 2011-3-27 * Author: Administrator */ #includeint main() { printf("Hello, world!\n"); return 0; }
然后菜单->Project->Clean->Clean projects selected below
清除然后重新编译helloworld
输出如下信息:
cs-make all
Building file: ../main.c
Invoking: ARM Sourcery Windows GCC C Compiler
arm-none-eabi-gcc -O0 -Wall -Wa,-adhlns="main.o.lst" -c -fmessage-length=0 -MMD -MP -MF"main.d" -MT"main.d" -mcpu=cortex-m3 -mthumb -g3 -gdwarf-2 -o"main.o" "../main.c"
Finished building: ../main.c
Building target: helloworld.elf
Invoking: ARM Sourcery Windows GCC C Linker
arm-none-eabi-gcc -nostartfiles -Wl,-Map,helloworld.map -mcpu=cortex-m3 -mthumb -g3 -gdwarf-2 -o"helloworld.elf" ./main.o
c:/program files/codesourcery/sourcery g++ lite/bin/../lib/gcc/arm-none-eabi/4.5.1/../../../../arm-none-eabi/bin/ld.exe: warning: cannot find entry symbol _start; defaulting to 00008000
c:/program files/codesourcery/sourcery g++ lite/bin/../lib/gcc/arm-none-eabi/4.5.1/../../../../arm-none-eabi/lib/thumb2\libc.a(lib_a-sbrkr.o): In function `_sbrk_r':
sbrkr.c:(.text+0x12): undefined reference to `_sbrk'
c:/program files/codesourcery/sourcery g++ lite/bin/../lib/gcc/arm-none-eabi/4.5.1/../../../../arm-none-eabi/lib/thumb2\libc.a(lib_a-writer.o): In function `_write_r':
writer.c:(.text+0x16): undefined reference to `_write'
c:/program files/codesourcery/sourcery g++ lite/bin/../lib/gcc/arm-none-eabi/4.5.1/../../../../arm-none-eabi/lib/thumb2\libc.a(lib_a-closer.o): In function `_close_r':
closer.c:(.text+0x12): undefined reference to `_close'
c:/program files/codesourcery/sourcery g++ lite/bin/../lib/gcc/arm-none-eabi/4.5.1/../../../../arm-none-eabi/lib/thumb2\libc.a(lib_a-lseekr.o): In function `_lseek_r':
lseekr.c:(.text+0x16): undefined reference to `_lseek'
c:/program files/codesourcery/sourcery g++ lite/bin/../lib/gcc/arm-none-eabi/4.5.1/../../../../arm-none-eabi/lib/thumb2\libc.a(lib_a-readr.o): In function `_read_r':
readr.c:(.text+0x16): undefined reference to `_read'
c:/program files/codesourcery/sourcery g++ lite/bin/../lib/gcc/arm-none-eabi/4.5.1/../../../../arm-none-eabi/lib/thumb2\libc.a(lib_a-fstatr.o): In function `_fstat_r':
fstatr.c:(.text+0x14): undefined reference to `_fstat'
c:/program files/codesourcery/sourcery g++ lite/bin/../lib/gcc/arm-none-eabi/4.5.1/../../../../arm-none-eabi/lib/thumb2\libc.a(lib_a-isattyr.o): In function `_isatty_r':
isattyr.c:(.text+0x12): undefined reference to `_isatty'
collect2: ld returned 1 exit status
cs-make: *** [helloworld.elf] Error 1
提示说找不到符号
(如果使用GNU/Linux工具链而非EABI工具链,则没有此问题。GNU/Linux工具链是基于Linux系统的ABI,所以可以使用C库,而EABI工具链则不行)
删除helloworld工程。
重新创建一个Makefile工程
Makefile project->ARM Windows GCC (Sourcery G++ Lite)
工程名称为test
创建完后工程树显示红色的叉叉(因为找不到Makefile)
无视之,重要是要手工修改工程选项:
1) 工程树->右键->Properties->C/C++ Build->Discovery Options
Discovery profile:选择
Managed Build System - per project scanner info profile (Sourcery G++ Lite Windows/C)
下面的Compiler invocation command会自动变成arm-none-eabi-gcc
2) 工程树->右键->Properties->C/C++ Build->Settings->Binary Parsers
勾选Elf Parser。
3) 工程树->右键->Properties->C/C++ General->Paths and Symbols->Includes
可以手工修改头文件和库文件的搜索路径。
最后把前面手工写的Makefile工程的所有文件复制过来(直接粘贴到工程树上)
再按菜单->Project->Clean->Clean projects selected below
重新编译这个工程,使那个红色的叉叉消失:
make all
arm-none-eabi-as -mcpu=arm926ej-s -g startup.s -o startup.o
arm-none-eabi-gcc -mcpu=arm926ej-s -g -c -mcpu=arm926ej-s -g test.c -o test.o
arm-none-eabi-ld -T test.ld -Map test.map startup.o test.o -o test.elf
arm-none-eabi-size test.elf
text data bss dec hex filename
144 0 0 144 90 test.elf
arm-none-eabi-objcopy -O binary test.elf test.bin
发现工程树发生一些变化:
由于前面设置了Elf Parser,test.elf、startup.o和test.o被自动识别为arm/le格式
Windows->Show View打开Make Targets视图
右键->New
Target name:填入clean all
按OK,再展开树,双击clean all即可重新编译工程,
不需要按菜单->Project->Clean->Clean projects selected below那么麻烦了。
7. 调试
把qemu_test.bat修改为使用绝对路径的bin
"D:\java\qemu-0.9.0-arm\qemu-system-arm.exe" -s -S -M versatilepb -m 128M -kernel "C:\Documents and Settings\Administrator\workspace_arm\test\test.bin" pause
双击工程树中的qemu_test.bat运行qemu。
然后点击上方的绿色小虫子旁边的下拉,
选择Debug Configuration,
双击C/C++ Application,创建一个test Default条目,
然后切换到Debugger页面,
Main->Debugger:选择gdbserver Debugger
Main->GDB debugger:改为arm-none-eabi-gdb
Main->GDB command set:改为Standard (Windows)
Main->Stop on Startup at改为c_entry(根据需要,见test.c)
Main->Use full file path to set breakpoints取消勾选
(因为这个选项可能影响断点添加)
Connection->Type:改为TCP
Connection->Port number:改为1234
Main->C/C++ Applicaton:填入test.elf
(test.elf是用于让arm-none-eabi-gdb加载调试版的符号信息,所以不要填test.bin)
然后按Debug按钮调试(必须保证前面的qemu已经正常运行起来)
弹出窗口Confirm Perspective Switch
勾选Remember my decision,然后Yes切换到Debug视角。
Perspective(视角)是Eclipse的布局,由右上角的标签页来切换,不同于视图。
在调试视角中Debug视图有三个按钮表示
Step Into
Step Over
Step Return
用于单步调试。
也可以双击代码左侧空白处添加断点,然后按Debug视图上的Resume继续运行程序。
如果程序在断点处中断,可以使用以下特性来调试:
* Variables视图中查看局部变量值(包括字符串)。
* Variables视图中添加全局变量的监视
* Debug视图使用Instruction Stepping Mode进行指令步进
* Debug视图的调用堆栈回溯(自动跳到外层调用所在的文件行)
* Registers视图的寄存器数值查看(好像可以立即修改值)
* Expressions视图添加表达式监视点(它比查看局部变量更灵活,但可以是较复杂的表达式)
缺点有:
* 每次调试需要重启qemu(qemu无法被远程重启)。
* 内存查看可能会导致qemu崩溃。
(eclipse貌似无法保存debug会话到工程目录中)
(如果学过TCP/IP原理,这种调试方式很容易理解,调试时qemu是TCP服务器,gdb是TCP客户端,而CDT充当gdb的图形界面。insight的调试机制类似)
三、使用arm-none-eabi-run和arm-none-eabi-gdb
arm-none-eabi-gdb可以识别由当前工具链编译获得的调试版elf文件(即,使用gcc -g开关编译和链接的二进制文件)中的符号(如果无法识别,在gdb中使用file test.elf将获得警告信息)但貌似无法使用target sim进行调试,官方解释说因为Lite版工具链无调试能力(?)。如果使用-T generic-hosted.ld开关(即使用本机的Linker Script),编译得到的文件可以用arm-none-eabi-run运行(生成的elf文件是pe格式的?),此时编译的源文件即使使用了printf这样的库函数也可以正常通过(如果手工写ld脚本,一般要自己实现printf之类的库函数,或者使用-lc显式包含libc.a)。
(20110404)(这是误区!)如果使用Sourcery G++提供的工具链,应该注意eabi和GNU/ARM工具链的区别(前者一般用于编译内核故不使用libc,后者则是编译在ARM Linux上运行的应用程序,故允许编译静态链接libc的。
(未完成)
四、使用skyeye
调试方法和qemu类似,但配置和输入输出方式和qemu不同,详细见
http://sourceforge.net/apps/trac/skyeye/wiki/Linux
它可以和CDT、gdb和insight这些工具交互实现断点调试(类似于qemu)
主要问题是:
* skyeye.conf和skyeye的版本有关,可能导致运行失败
* skyeye.conf的配置缺乏详细的说明和示例
* skyeye对高版本gdb的支持似乎有问题
(未完成)
五、使用insight
(未完成)
六、使用VisualBoyAdvance
VisualBoyAdvance是gba在windows上的模拟器,
可以模拟低端的ARM处理器ARM7TDMI
http://sourceforge.net/projects/vba/
内置gba调试功能
(未完成)