Gdb支持远程调试,但是gdb中内嵌了模拟器。这个是从网上找到的一个gdb中内嵌的模拟器的分析的文章,留下来,权当抛砖引玉,希望能得到添加其他模拟器的方法。以下是转载内容。
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所真正使用的(第二种是早期使用的方法),下面就分析了函数直接调用法.
函数直接调用
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.
#1 static void
#2 init ()
#3
#4 static int done;
#5
if (!done)
#6
{
#7
ARMul_EmulateInit ();
file://Call
this routine once to set up the emulator‘s tables.
#8
state = ARMul_NewState ();
#9
state->bigendSig = (big_endian ? HIGH : LOW);
#10
RMul_MemoryInit (state, mem_size);
file://原始代码中的内存初始,但现在无用
#11
ARMul_OSInit (state);
file://预装系统初始化
#12
ARMul_CoProInit (state);
file://协处理器初始
#13
state->verbose = verbosity;
#14
done = 1;
#15
file://the
below is added for AT91
#16
ARMul_SelectProcessor(state, ARM600);
#17
ARMul_SetCPSR(state, USER32MODE);
#18
ARMul_Reset(state);
#19
}
#20 }
因为这是补丁代码,难免又冗余出现,实际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结构来描述的:
根据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中的四个变量:
b.
读写操作
无论是CPU指令还是Gdb调试时读写内存或IO空间,最后都将要落到/armvirt.c/getword(), /armvirt.c/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.
源文件描述
摘要
这个方法是由Steve (
sac@cygnus.com) 修改原RDP方法而来的,Steve本人的描述如下:
/gdb/target.c,/gdb/remote_sim.c以及在/sim/arm/wrapper.c是在Armulator和Gdb的通信中起着至关重要做用的几个文件.所有的Gdb调试命令最后都是通过在target.h里定义的target_ops结构中的函数指针调用在/sim/arm/wrapper.c中型如sim_xxx的函数完成的.以前这些sim_xxx函数是位于/sim/common中的,是建立RDP通讯的关键,代码修改后此目录中的文件不再有用,被wrapper.c取而代之了.
/gdb/target.c,/gdb/remote_sim.c以及在/sim/arm/wrapper.c是在Armulator和Gdb的通信中起着至关重要做用的几个文件.所有的Gdb调试命令最后都是通过在target.h里定义的target_ops结构中的函数指针调用在/sim/arm/wrapper.c中型如sim_xxx的函数完成的.以前这些sim_xxx函数是位于/sim/common中的,是建立RDP通讯的关键,代码修改后此目录中的文件不再有用,被wrapper.c取而代之了.
要清楚Armulator的执行过程就要从它的启动说起,当你在Gdb中键入target sim 去激活Amulator后Gdb首先进行命令行解释,并将current_target指针指向sim变量,即将Armulator的调试函数集赋予Gdb,随后的函数调用堆栈如下:
--gdbsim_open (…) in remote-sim.c.
--sim_open(…) in /sim/arm/wrapper.c
--*current_target->to_fetch_registers(-1)
--sim_fetch_register(-1)
sim_fetch_register (sd, rn, memory, length)
{
ARMword regval;
init ();
file://就在这,Amulator进行了初始化
…
}
--sim_open(…) in /sim/arm/wrapper.c
--*current_target->to_fetch_registers(-1)
--sim_fetch_register(-1)
sim_fetch_register (sd, rn, memory, length)
{
}
至此Armulator被装载完毕,其后Gdb就是通过target_ops(定义在target.h)结构中的各个函数指针来完成对它的调试工.
#1
#2
#3
#4
#5
#6
#7
#8
#9
#10
#11
#12
#13
#14
#15
#16
#17
#18
#19
#20
typedef struct mem_bank_t {
ARMword
(*read_word)(ARMul_State *state, ARMword addr);
void
(*write_word)(ARMul_State *state, ARMword addr, ARMword data);
unsigned long
addr, len;
char
*filename;
} mem_bank_t;
file://定义在armmem.h中
Armulator的整个内存则是又此结构的数组static mem_bank_t mem_banks[]管理的.
AT91的memory map如下:
static mem_bank_t mem_banks[] = {
{ real_read_word,
real_write_word,
0x01000000, 0x00400000, },
{ real_read_word,
_write_word,
0x01400000, 0x00400000, "boot.rom"},
{ real_read_word,
real_write_word,
0x02000000, 0x00400000, },
{ real_read_word,
real_write_word,
0x02400000, 0x00001000, },
{ real_read_word,
_write_word,
0x04000000, 0x00400000, "boot.rom"},
{ real_read_word,
real_write_word,
0x00000000, 0x00004000, },
{ io_read_word,
io_write_word,
0xf0000000, 0x10000000, },
{ fail_read_word,
fail_write_word,
0,
0 }
};
} mem_bank_t;
Armulator的整个内存则是又此结构的数组static mem_bank_t mem_banks[]管理的.
AT91的memory map如下:
static mem_bank_t mem_banks[] = {
};
State->Exception : 中断使能标志.
State->NresetSig : reset 中断信号.
State->NirqSig : irq 中断信号.
State->NfiqSig : fiq 中断信号.
所以当你的虚拟外设产生中断时,你只要调用/sim/arm/armio.c/ update_int()即可:
static void update_int(ARMul_State *state)
{
ARMword
requests = state->io.intsr & state->io.intmr;
state->NfiqSig = (requests & 0x000f) ? LOW : HIGH;
state->NirqSig = (requests & 0xfff0) ? LOW : HIGH;
}
State->NresetSig : reset 中断信号.
State->NirqSig : irq 中断信号.
State->NfiqSig : fiq 中断信号.
所以当你的虚拟外设产生中断时,你只要调用/sim/arm/armio.c/ update_int()即可:
static void update_int(ARMul_State *state)
{
}
--getword()/putword()
--mmu_read_data()/mmu_write_data() in armmmu.c
--real_read_data()/real_write_data() :读写ram,rom
io_read_data()/io_write_data() :读写IO空间
_write_data()/fail_read_data()/fail_write_data() :非法读写,如地址错误,rom读操作等.
--mmu_read_data()/mmu_write_data() in armmmu.c
io_read_data()/io_write_data() :读写IO空间
_write_data()/fail_read_data()/fail_write_data() :非法读写,如地址错误,rom读操作等.
The original files
|
The modified codes
|
File descriptions
|
arminit.c
|
arminit.c
|
初始代码
|
armemu.c
|
armemu.c
|
指令流模拟
|
armvirt.c
|
armvirt.c
|
内存读写
|
armsupp.c
|
armsupp.c
|
辅助指令流模拟
|
armcopro.c
|
armcopro.c
|
协处理器模拟(可忽略)
|
armos.c
|
armos.c
|
初始操作系统(可忽略)
|
|
armmem.c
|
内存管理
|
|
armmmu.c
|
Mmu模拟
|
|
armio.c
|
IO设备模拟
|
wrapper.c
|
wrapper.c
|
和Gdb通讯的例程
|