GDB/ARMulator 使用方法

21 篇文章 1 订阅

http://blog.chinaunix.net/uid-23325954-id-65698.html



1.GDB/ARMulator基本介绍
GDB/ARMulator
GDB自带的一个ArmulatorARM模拟器,实际上应该是有不止一个软件包含这个功能,例如ADS,他们都叫Armulator),不过我查看GDB的源代码都是对ARM6的模拟,而现在比较常见的是打过uclinux开发组patchGDB/ARMulator,这样这个模拟器主要就是对Atmel AT91扳进行了模拟,而我所学习的也是这个版本,更多的信息可以到下面的URL去看。
http://www.uclinux.org/pub/uClinux/utilities/armulator/



2.GDB/ARMulator
编译安装和基本使用
2.1.编译安装

如果我记得不错的话,在uclinux网站提供的arm工具链中的GDB就是打过patch的版本,你可以直接使用其。不过因为在下面要通过GDB调试的方式来帮助阅读其代码,所以在这里我决定是编译一个。
首先是在上面提到的地址下载到GDB代码以及patch,然后:
tar vxjf gdb-5.0.tar.bz2
gunzip < gdb-5.0-uclinux-armulator-20021127.patch.gz | patch -p0
cd gdb-5.0
./configure --target=arm-elf --prefix=/usr/local --without-gtk-prefix --without-gtk-exec-prefix --disable-gtktest
make
make install


2.2.
基本使用
将你做好的文件系统镜象文件改名为boot.rom(这个文件和下面用的linux.2.4.x文件都可以在上面提到的地址下到)做软连接也可以,然后在这个目录中
arm-elf-gdb linux.2.4.x
target sim
load
run
这样你就能看见uclinux在模拟器上跑起来了。



3.GDB/ARMulator
代码阅读记录
GDB
相关的代码很多,跟ARMulator相关的代码也不少,所以代码的阅读主要是跟着刚才跑通起uclinux使用的命令涉及的代码,其中很多地方都是用gba调试arm-elf-gdb的方式帮助阅读。


3.1.arm-elf-gdb linux.2.4.x

3.1.1.
sim的初始化
因为sim是作为gdb的一个target(应该可以称为调试目标吧呵呵)存在的,所以在gdb运行的时候,要先对这个target进行初始化。在remote-sim.c文件中的函数_initialize_remote_sim就是对这个target的初始化函数,而这个函数将在启动的时候被调用。在这个函数中有3个操作:
1个操作,调用init_gdbsim_ops函数,这个函数用来初始化全局结构变量gdbsim_ops,其的结构类型是target_ops,每个gdb中的target都有这么一个结构,这个结构定义在target.h文件中。这个变量的作用就是当调试开始的时候,gdb可以通过结构找到相应的对target进行控制的函数。
2个操作,调用add_target将前面初始化好的gdbsim_ops增加到target_structs中用来方便系统对这个结构的使用。
3个操作,add_comgdb增加了一个命令sim,这个命令通过调用sim_do_command这个函数可以帮助实现向sim发送命令,这个功能将在后面进行一次实际的应用,在那再详细介绍。
还有一点比较重要,sim目录下包含了很多cpu相关的目录,其中就有arm目录,这就是最基本的ARMulator的代码,gdbARMulator的控制就是通过target的接口函数调用ARMulator的函数来完成的。

3.1.2.
linux.2.4.x的读入
因为这个读入的操作跟使用命令file进行读入是基本一样的,所以对file命令的过程进行分析。
在用户执行file命令后,将调用exec.c文件中的函数file_command。在这个函数调用的函数exec_file_command用来将可执行文件打开,在其中target_preopen函数会检查前面是否有文件打开执行等等,并向用户提问是否进行清除,exec_file_attach则是具体的打开文件的操作,在这里打开了一个可执行文件结构exec_bfd,这将在后面被用到。symbol_file_command函数用来将符号表等进行初始化。
而在启动对可执行文件的读入在main.c文件中的函数captured_mainexec_file_commandsymbol_file_command进行了调用。


3.2.target sim
在这里主要完成对sim的初始化的工作,这些工作是通过调用remote-sim.c文件中的函数gdbsim_open函数来完成的。
在开始的时候初始化了一些参数,然后传递给了wrapper.c文件中的sim_open函数,在这里设置了ARMulator的引点以及一些其他的东西。
而后调用函数push_targetgdbsim_ops增加到target_stack列表上相应的层次上,当然具体这步的意义我还不是很清晰。

最后用参数-1调用了gdbsim_fetch_register函数,当这个函数的参数为-1的时候,将以参数015调用这个函数,代表对arm16个寄存器的初始化。
在这些调用的时候只做了简单的检查就调用了wrapper.c中的函数sim_fetch_register,在这个函数中第一步是调用函数init,这个函数中进行了大部分的ARMulator初始化工作。

init函数中可以看到定义了一个static变量done,用来保证初始化只进行一次。ARMul_EmulateInit函数对ARMul_ImmedTableARMul_BitList,不过我没能看出这2个东西的具体作用。

ARMul_NewState
函数对保存着Armulator的所有方面状态的state进行了空间分配和其中元素中的初始化,具体每个元素的定义可以看armdefs.hARMul_State定义中的每个元素的注释,写的很明确。在这个函数最后调用了函数ARMul_Reset,这里是对state的进一步的初始化。
ARMul_Reset函数最开始的部分是根据state->prog32Sig也就是系统模式是否用32位模式,来设置Reg[15]Cpsr(26位模式中cpsrr15),在其后调用的函数ARMul_CPSRAltered是在修改过Cpsr后对state中其他参数值进行更新的函数。
在后面调用的mmu_reset是对state->mmu中内容进行初始化的函数。
其后是mem_reset函数,其主要作用是对state->mem也就是ARMulator中的内存进行初始化,其中还包括了将相应的文件系统内存镜象(也就是内个boot.rom)读入内存的过程,这些要做的工作都是根据全局结构数组mem_banks的值来进行的。具体是根据数组mem_banks中元素的数量来对内存进行循环的初始化,第一部是根据mem_banks元素中相应长度指定mem.rom_size中相应元素的长度并给指针mem.rom分配内存,这样就分配了内存块,如果在mem_banks的元素中filename不为空,则就将文件打开并根据big_endian进行处理后写入内存块中。这个mem_banks实际上并不单纯是在初始化的使用,在Armulator实际运行中,对内存进行读写的时候,也会根据读写的地址查找相应的其中的元素,使用这个元素中指定好的读写函数。这里提到的内存可以参看armmem.c文件。
接着被调用的函数是io_reset,这个函数初始化了state->io,这个结构的就是系统中的基本设备io设备。
最后调用了函数lcd_disable,禁止了lcd设备的工作,然后ARMul_Reset函数返回,ARMul_NewState函数也就结束了。

接着是根据big_endian设置state->bigendSig,也就是来设置ARMulator的引点。

下面调用的2个函数ARMul_MemoryInitARMul_OSInit都是没做什么工作,所以不做分析。

ARMul_CoProInit
函数是对ARMulator中的协处理器进行了初始化。协处理器在ARMulator被处理成了ARMul_State结构中的几个函数指针CPInitCPExit(2个在启动和停止的时候被调用)LDCSTCMRCMCRCDP(这几个代表了相应的协处理器指令)CPReadCPWrite(这两个的作用还没看明白)2个数据指针CPDataCPRegWords(前面一个好象在具体代码中没用到,后面这个没搞清楚作用)组成,这样ARMulator就可以很方便的拥有扩展性很好的16个协处理器。
ARMul_CoProInit函数中,先是循环调用ARMul_CoProDetach函数对16CP进行了简单的函数初始化,然后给CP15进行了单独的函数初始化,CP15arm中是跟mmu设置有关的cp,所以这里也进行的是mmu函数的初始化。接着是循环调用了每个cpCPInit函数,然后返回。

接着在后面设置done1表明init操作已经做过了。

后面3行我个人觉得是不必要,第一行ARMul_SelectProcessor函数实际就设置了个arm的模式,前面已经设置过了。第二行我个人认为不光是不必要,根本就是错的,设置armuser32模式,我个人觉得一般系统启动都该是svc32启动吧,不然的话很多特权操作怎么执行。第三行就更不用说了,又执行了一次ARMul_Reset,实际上在这个函数中已经将上一行的修改覆盖掉了。所以我觉得这3行根本没什么用。

后面的部分应该是gdbsim_fetch_register函数本来该做的工作,也就是用来取得寄存器的函数,所以我觉得这个init函数放在这个函数中被执行是个有点点奇怪的过程,我是没有理解,如果我改代码的话,我会将其放到gdbsim_open去调用。


3.3.load
此命令的作用是将要调试的机器代码装入ARMulator的内存,注意前面装载的是文件系统。
首先被调用的是remote-sim.c中的函数gdbsim_load,接着就在这个函数中调用了wrapper.c中的函数sim_load
在这个函数中第一个被调用的函数是sim-load.c中的sim_load_file,上面提到的从文件中读出机器代码然后装入ARMulator的内存的工作都是在这个函数中执行的。这里进行的工作就是先将elf文件打开,然后分析,调用函数指针do_write也就是前面传递来的函数sim_write将读出的节的内容按照其地址和长度写入ARMulator的内存。最后将打开elf文件的操作指针返回。
下面进行的是设置启动点也就是PC寄存器值的过程,因为刚才的情况,在这里启动点也就是PC还是被设置成了0
然后函数就返回了,基本的load操作也就完成了。


3.4.run
在执行指令后被调用的是remote-sim.c中的函数gdbsim_create_inferior在开始是对当前情况进行了一些检查和清除过去信息的工作。
接着做的是根据函数gdbsim_create_inferior的参数exec_fileargs构造命令行启动参数argv,其中exec_file中是前面打开的kernel的完整路径名,而args中就是run后面跟着的执行参数,如果没有就是空字符串,整理后的多层指针中就依次在每个指针中存储了启动参数。

接着调用wrapper.c文件中的函数sim_create_inferior来对ARMulator进行近一步的初始化。
一开始又是一次对ARMulatorPC寄存器的初始化,这次因为是按照参数可执行文件句柄结构abfd也就是全局变量exec_bfd进行的。这个变量是在3.2部分介绍的过程初始化的,所以PC也是由这个结构决定的。
然后是根据argv的值对state->CommandLine进行初始化,实际就是依次将其中参数用空格分隔拷贝进去。
最后是根据envstate->MemSize进行设置。函数返回。

设置记录模拟器下层进程pidinferior_pid42,这么做因为在当前的情况下并没有单独的进程进行模拟,所以只进行这个设置表明模拟已经开始。
调用函数insert_breakpoints将刚才清除掉的断点重新加入代码中。用函数clear_proceed_status清楚掉以前程序执行的信息。

调用infrun.c中的函数proceed开始ARMulator的运行。这个函数的3个参数的意义分别是:
addr
,运行开始的位置,如果是-1则从停止的地方也就是pc寄存器指定的位置开始执行。
siggnal
,好象跟退出信号有关的一个参数,我还没搞清楚作用。
step
,运行的方式,如果为非0则每执行一条指令都进入trap方便调试。
在这个函数中开始是一些初始化工作,主要是对infrun.c中函数wait_for_inferior的调用。在这个函数中对remote-sim.c文件中的wait_for_inferior进行了调用,在这个函数中调用了wrapper.c函数中的sim_resume

sim_resume
函数是Armulator启动arm指令执行的地方。在这个函数中会根据执行的参数来使用2个不同的执行函数ARMul_DoInstr()ARMul_DoProg(),这2个函数的区别是一个单步执行而一个连续执行指令。这2个函数都是调用armemu.c中的函数ARMul_Emulate32

ARMul_Emulate32
函数就是ARMulator的核心解码和执行函数。因为各种原因,笔记就记录到这里。



4.
GDB/ARMulator增加个小功能单步调试自动反编译
这里的目标就是给ARMulator增加一个单步自动反编译显示的功能,让ARMulator在单步的时候可以自动显示下一条要执行的汇编代码。
这个功能具体的实现可以看一下附件中带的2个修改好的文件,将这2个文件按照其所在目录拷贝进前面已经打好补丁的gdb目录中,然后编译安装就可以了。
现在我就来介绍一下我增加的代码,所有我修改的代码我都放在了
//teawater add for next_dis 2004.6.9--------------------------------------------
//AJ2D--------------------------------------------------------------------------
中。


首先是在armdefs.hARMul_State结构也就是表明ARMulator状态的state变量的结构增加了一个元素disassemble,用它来标明现在反编译功能是否打开的。
然后在wrapper.cinit函数中state->disassemble=0;让此功能默认为关闭的状态。
下面对wrapper.c文件中的sim_do_command函数进行修改,这个函数的作用在3.1.1提到过,可以用来是用sim命令向ARMulator发送信息,这里就用上了这个功能。参数cmd就是sim命令后面跟着的参数,通过在函数中对这个参数进行判断然后设置state->disassemble的值,通过对值的判断就可以实现对此功能的打开和关系。完成后调用 sim disassemble on 或者 sim disas on 就可以打开功能,调用 sim disassemble off 或者 sim disas off 就可以关闭功能。

修改函数sim_resume,在ARMul_DoInstr执行之后如果state->disassemble不为0就执行反编译函数,也就是显示单步执行一条指令后就反编译state->Reg[15]位置(pc寄存器,下一条要执行指令的位置)的代码。其中要注意的是对TFLAG的判断也就是对state->TFlag的判断是用来确定ARMulator当前是工作在thumb指令模式下还是arm指令模式下,并根据情况用不同的参数调用反编译函数tea_print_insn

下面来介绍一下反编译函数tea_print_insn。单独依靠ARMul_Emulate32函数中提供的信息进行解码和显示也是可以的(以前看过objdumpi386反编译的代码,跟arm一比,arm的反编译工作确实不算多了),但并不必要,因为gdb有自己的反编译disassemble命令。
因为中间涉及的函数比较多,而且很多是static的并不可用,所以我直接介绍可用的接口函数了。这两个函数是arm-dis.c文件中的print_insn_big_armprint_insn_big_arm,可名字就可以看出他们基本情况都是一样的,只是一个是对big endian反编译,而另一个对little endian反编译。这2个函数用的参数一样:
pc
,用来标明要反编译代码的位置。
info
,反编译其他相关的操作信息都存储在这里,所以在tea_print_insn函数中主要工作就是对一个disassemble_info类型结构变量的初始化。
这个结构定义在dis-asm.h中,在其中有比较详细的注释,下面介绍一下对其初始化的过程。
先将结构中全部数据初始化为0
结构中的fprintf_func函数指针和stream配合使用来输出反编译的结果,因为fprintf_func函数指针参数的结构类似fprintf,所以我将其初始化为fprintf,而stream初始化为stdout
endian
看名字也知道是用来标记当前endian,根据state->bigendSig设置为BFD_ENDIAN_BIG或者BFD_ENDIAN_LITTLE
read_memory_func
函数指针指向的函数用来在反编译的时候被调用来取得指定位置的代码,我单独写了一个用来做这个工作的函数tea_read_memory_func,其几个参数的作用为:memaddr,指定要读取的目标地址。myaddr,存放取得到数据的地址。length,要取数据的长度。info,就是前面传递给反编译函数的这个结构。返回0表示成功非0则是出错号码。
memory_error_func
函数指针指向的函数用来在当上个函数出错的时候调用这个函数来根据错误返回值来进行处理,我单独写了一个函数tea_memory_error_func
memory_error_func
函数指针指向的函数用来输出指定的地址,我让其指向tea_print_address_func函数。
symbol_at_address_func
函数指针指向的函数用来确定给出的地址是否是一个符号,我用了generic_symbol_at_address函数。
disassembler_options
是一个指向其他可用参数信息的,在反汇编函数中会根据这里的信息进行一些设置,我通过在其中设置force-thumbno-force-thumb来设置反汇编函数的进行arm反编译还是thumb反编译。
其他的参数信息因为在这里没用到所以就不介绍了。

这样在使用ARMulator的时候,在调试模式下使用命令sim disassemble on打开功能,然后用stepi进行单步执行的时候,就能看见下一条要被执行的代码了。



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值