Gdb/Armulator源代码分析
作者Email: Anti_chen2000@sohu.com 摘要 Gdb/Armulator 是Gdb自带的arm7模拟器,是调试arm程序的一个好工具.而了解它的原码结构对扩展它的IO功能有重要意义.本文介绍了从Armulator的启动到其内部运作和IO扩展的大部分原代码功能. 说明 源代码用的是gdb-5.0.tar+ gdb-5.0-uclinux-armulator-20021127.patch A. 和GDB间的通迅 Armulator一般和Gdb通讯有两种方式,其一是在Gdb内部直接调用模拟器的相关函数,另一方法则是用pipe或socket传递RDP协议来连接Gdb和Amulator.而第一种方法是现在Gdb/Armulator所真正使用的(第二种是早期使用的方法),下面就分析了函数直接调用法. 函数直接调用 这个方法是由Steve (sac@cygnus.com) 修改原RDP方法而来的,Steve本人的描述如下: It likes to use TCP/IP between the simulator and the host, which is I've added created a new Makefile.in (the original in Makefile.orig) It should be possible (barring major changes in the layout of (Except that I changed armos.c to work more simply with our 以下是RDP 通讯和直接函数调用的图示: javascript:window.open(this.src);" style="cursor:pointer;"/> 要清楚Armulator的执行过程就要从它的启动说起,当你在Gdb中键入target sim 去激活Amulator后Gdb首先进行命令行解释,并将current_target指针指向sim变量,即将Armulator的调试函数集赋予Gdb,随后的函数调用堆栈如下: --àgdbsim_open (…) in remote-sim.c. 至此Armulator被装载完毕,其后Gdb就是通过target_ops(定义在target.h)结构中的各个函数指针来完成对它的调试工. B. Armulator 内部机制 a. 初始化 从上述可知整个模拟器的初始化入口是在wrapper.c中的init( )函数,那么它到底又做了些什么呢? (原始的Gdb5.0中的Armulator是模拟ARM7DTMI 的,而补丁代码修改了memory map 并添加了timer 和uart 的IO能力使其能够模拟AT91.因为后者是对前者的增强,所以我们的分析以后者为准) Once the armulator to reset ,the ARMul_NewState will be called.And its task is to malloc a ARMul_state stuct which saves the armulator’s states and initialize it .And the ARMul_MemoryInit() will malloc 4m ram for you. 因为这是补丁代码,难免又冗余出现,实际10-11行的两处掉用是没有实际意义的,而12行是协处理器的初始化,因为并没又模拟协处理器所以此处只是以备扩展. 重点的初始化过程是在ARMul_NewState(…)中的.首先它给模拟器的核心状态结构ARMul_State分配了空间,这个结构里保存了Armulator的所有方面的状态,包括arm寄存器,流水线状态等等. 并赋予初值,我们以后就用state表示之.然后调用ARMul_Reset(…)进行更近一步的设置.而后者又主要完成模拟器内存结构的分配和rom映象的加载--/sim/arm/armmem.c/mem_reset(…),IO设备的状态初始—/sim/arm/armio.c/io_reset(…),你也可在这添加你的初始代码.到这就完成了Armulator的装载. (大家注意到18行也调用了ARMul_Reset(…),这是一个BUG,使得模拟器进行了两次内存分配,而浪费了系统内存.此处可删去.) Memory map 是所有模拟器的关键.Armulator由AT91向其他MCU移植时Memory map又是首先要处理的.Armulator的各个内存区是由mem_bank_t结构来描述的: typedef struct mem_bank_t { 根据mem_banks,mem_reset( )将分配空间,加载boot.rom文件. (原来的内存是由ARMul_MemoryExit( )释放的,但补丁后的代码就没了释放功能,这也是需要纠正的地方) a. 指令流 Armulator 加载完成后,就开始等待Gdb的运行命令了.最终/sim/wrapper.c/sim_resume( )是启动arm指令执行的地方. Sim_resume( )根据Gdb的要求选择用/sim/arm/arminit.c/ARMul_DoInstr()还是用/sim/arm/arminit.c/ARMul_DoProg()来调用 流水线模拟函数/sim/arm/armemu.c/ARMul_Emulate32().ARMul_DoInstr()和ARMul_DoProg()的区别就是一个单步执行,一个连续执行指令. ARMul_DoProg()又不停的判断state->Emulate是否为STOP,如果是,模拟器又将停下等待Gdb的调试. 而在arm/armemu.c, /arm/armvirt.c 和 /arm/armsupp.c中的函数则模拟指令预取,指令译码,指令执行以及数据回写的功能.这三个文件时可以说时Armulator的核心! b. 中断 Armulator 的中断机制主要靠以下两个例程实现: 1.IntPending(): 用来检测state中的各个中断标志是否置位,从而判断是否又需要中断. 2.ARMul_Abort():当需要中断时,用来改变处理器模式,并将pc指向相应的中断向量. 在流水线函数ARMul_Emulate32()执行当中,有多处调用IntPending() 去检测中断.而Ispending() 也十分简单,它仅仅判断state中的四个变量: State->Exception : 中断使能标志. b. 读写操作 无论是CPU指令还是Gdb调试时读写内存或IO空间,最后都将要落到/armvirt.c/getword(), /armvirt.c/putword()这两个函数身上. 在原始代码中这两个函数马上就进行内存数组的读写了.而补丁代码的流程如下: --àgetword()/putword() 可以看出最后几个函数的选择是由读写地址在相应的mem_bank_t结构中的读写函数指针决定的. c. 设备同步 写这篇文章的初衷是让读者能很快进入Armulator的移植和IO扩展的实际工作中去.所以这里有必要讨论一下IO设备和CPU的同步问题.很显然我们模拟的设备不能太快,也不能太慢.快了CPU正常的指令流将被堵塞,慢了就无法反映操作系统的实时性.也就是说设备的速度和指令流要有个比例关系,即要有一定的同步. Armulator 中有个很好的接口: ARMul_ScheduleEvent (ARMul_State * state, unsigned long delay, unsigned (*what) ()) in armvirt.c 它的目的就是注册你的同步例程,并且每个时钟周期即ARMul_Emulate32()将调用ARMul_ScheduleEvent()查看是否需要同步你的设备. 也许你在ARMul_Emulate32()中还发现了/sim/arm/armio.c/io_do_cycle(),没错它是AT91的timer和uart用来和指令流同步的函数,但我并不赞成你象这样把自己的同步例程直接放入指令执行过程中,破坏代码的结构性. a. 源文件描述
|