Advance's profile虚言之境PhotosBlogLists Tools Help

Advance

Occupation
Location
Interests
实际意义上的闲人+废人一个
There are no photo albums.
August 09

浅谈模拟环境下JIT技术的实现(ARM部分)

标题:浅谈模拟环境下JIT技术的实现(ARM部分)
作者:LOVEHINA-AVC
类型:跨平台应用
对应级别:中级
声明:没有什么特别的声明,转载时注明作者及出处就可以了
 
JIT,即Just In Time,在程序设计领域,它指的是一种将字节码即时编译为本地代码的技术。本文所说的JIT并非我们熟知的将平台无关代码编译成平台相关代码的技术,实际上,它是一种变相JIT的实现,其本质内容是将平台相关的代码转译到另外一个平台上运行,该技术适合RISC到RISC及RISC到CISC的转换。为什么说基于RISC架构的指令集适合JIT?原因只有一个——RISC架构要求CPU指令拥有统一的长度,我们可以在预处理期间就正确的反汇编所有指令的内容。这一点是很重要的,作为对比,我们来看看CISC——它的指令长度可变,并且没有边界对齐要求,这使得反汇编器无法精确的反汇编每一条指令,假如我们在代码段中插入一些数据,或者干脆使用几条花指令,一个再聪明的反汇编器也可能产生错误的反汇编代码。
JIT首先要求译码器能够正确的反汇编并翻译目标代码,例如,一个典型的ARM满降序栈寄存器保护指令
stmfd sp!, {r0-r12, lr}
将被转译为以下本地代码:
Arm32Context STRUCT
 r0  dd ?
 r1  dd ?
 r2  dd ?
 r3  dd ?
 r4  dd ?
 r5  dd ?
 r6  dd ?
 r7  dd ?
 r8  dd ?
 r9  dd ?
 r10  dd ?
 r11  dd ?
 r12  dd ?
 r13  dd ?
 r14  dd ?
 pc  dd ?
 cpsr  dd ?
 spsr_svc dd ?
 spsr_irq dd ?
 spsr_abt dd ?
 spsr_und dd ?
 spsr_fiq dd ?
 r8_fiq  dd ?
 r9_fiq  dd ?
 r10_fiq  dd ?
 r11_fiq  dd ?
 r12_fiq  dd ?
 r13_fiq  dd ?
 r14_fiq  dd ?
 r13_svc  dd ?
 r14_svc  dd ?
 r13_irq  dd ?
 r14_irq  dd ?
 r13_abt  dd ?
 r14_abt  dd ?
 r13_und  dd ?
 r14_und  dd ?
ENDS
 push edi
 lea edx,[esi + Arm32Context.r12] ;esi = context
 mov edi,[esi + Arm32Context.r13]
 mov eax,[esi + Arm32Context.r14]
 sub edi,4
 mov ecx,13
 mov [edi],eax
 mov [esi + Arm32Context.r0],ebx ;如果使用了临时寄存器来优化模拟指令的存取速度(此处为ebx=r0, edi=cpsr),它们所包含的内容就必须被先行写回目标CPU的上下文存储单元
0:
 sub edi,4
 mov eax,[edx]
 sub edx,4
 mov [edi],eax
 dec ecx
 jnz <0
 mov [esi + Arm32Context.r13],edi
 add [esi + Arm32Context.pc],4
 pop edi
注意:如果目标指令的寄存器列表不连续,并且拥有2个以上的不连续区段,则应当将压栈循环分割成各个独立的mov指令组以提高执行效率。对此译码器必须具备一定的识别能力,并根据寄存器列表的内容来决定使用何种本地代码生成策略。最终生成的本地代码将可能被加载到任意基址的内存空间当中,因此所有对外部地址的引用(call [mem]、mov r32,[mem]等)都必须加以修正(类似于DLL的IAT处理),否则这些代码将无法正常执行。对于ARM特权级状态转换,相关的代码应当被插入到任何一个试图改变cpsr寄存器内容的本地指令组当中。一个常见的做法是置换影子寄存器组与当前寄存器的值,并设置相应的模拟处理器状态标志。

  32位ARM处理器支持ARM与Thumb两种长度不同的指令集,并通过cpsr中的某个位域来决定当前的执行模式。在转译阶段我们无从得知何处为ARM指令,何处为Thumb指令,因此译码器必须生成两份不同的指令翻译,一份完全以ARM模式翻译,另一份完全以THUMB模式翻译。译码器可以选择对无效指令不敏感,并为每一条无效指令生成一组异常响应代码(最节省空间的如int 3中断),这样即使执行到了无效指令也可以进入相应的异常处理模块。通常情况下,当使用bx/blx指令间接寻址,且目标地址的低位被置位时,处理器将切换到Thumb模式执行,否则切换到ARM模式,这意味着任何一种模式都可以在不切换当前模式的情况下跳转到任意地址。我们需要四个地址映射表,分别对应ARM_BIOS、ARM_ROM、THUMB_BIOS和THUMB_ROM,表中的每一项都是目标指令对应的本地指令地址,以此来快速实现目标地址(虚拟地址)到本地地址(实际地址)的转换。
下面是ARM/Thumb指令bx r0转译后的代码:
 mov eax,[esi + Arm32Context.r0] ;esi = context
 jmp BranchExchangeProc
BranchExchangeProc: ;公共部分
 test eax,1
 jnz >0
ExecutingInArmMode:
 
 cmp eax,0x8000000 ;假设ROM加载于0x8000000,若目标地址低于该值,则接下来引用的地址映射表将是BIOS(FLASH1)而不是ROM(FLASH2)
 jae >1
 mov edx,[ArmAddressMappingTableForBios]
 jmp >2
1:
 mov edx,[ArmAddressMappingTableForRom]
2:
 and eax,0xFFFFFF
 jmp [edx + eax]
ExecutingInThumbMode:
0:
 dec eax
 cmp eax,0x8000000
 jae >3
 mov edx,[ThumbAddressMappingTableForBios]
 jmp >4
3:
 mov edx,[ThumbAddressMappingTableForRom]
4:
 and eax,0xFFFFFF
 jmp [edx + eax * 2] ;Thumb的指令长度为16bits,因此需要的映射地址量为ARM模式的2倍

关于模拟地址空间的问题:
  与传统的x86不同,ARM仅通过映射内存地址的方式来访问外围设备的控制寄存器及通信端口等资源。FLASH1、FLASH2和RAM的基址各不相同,且RAM可以含有多个BANK组,每个BANK都被映射到不同的地址上面,这就要求模拟器必须拥有辨识地址范围的功能,以便将特定区域的内存访问重新映射到实际分配的地址当中,并根据需要转换成对模拟部件的行为控制。为了达成这一目的,模拟器通常要对目标指令访问的内存空间做多次确认,这意味着任何一个对托管内存的访问都要被分析、重定位,这会大大降低一些指令的模拟执行效率。在此我推荐一个更为行之有效的方法来管理内存访问——利用虚拟内存机制模拟目标平台的平坦内存模型。例如,以下对VirtualAlloc的调用将保留256Mbytes的地址空间:
 invoke VirtualAlloc, NULL, 10000000h, MEM_RESERVE, PAGE_NOACCESS
由此得到的内存总基址,将被视为在装载本地转译代码时对引用地址的修正基准。接下来我们可以在这个地址上分配提交的内存页,并根据目标平台的硬件约定在合适的地址上载入BIOS(FLASH1)和ROM(FLASH2)数据。由于先前对保留页面赋予了PAGE_NOACCESS属性,任何对无效区域的访问都会产生一个线程级的GPE(一般保护异常),我们可以在预先安装的SEH(结构化异常处理)中得到该异常的详细信息,并根据引用内存的地址做进一步分类处理。

一些优化方案:

常量引用分析:
  如果某条目标指令以寄存器间接寻址的方式访问内存区域(或执行bx跳转),在译码期间就可以向前搜索对该寄存器进行立即数赋值的指令,如mov r0, #imm、ldr r0, =imm(引用的字常量必须位于只读代码段),并实行寄存器内容预测,以免去辨识地址范围的开销,提高本地代码的执行效率。如果能够找到匹配项,且匹配指令与当前指令之间不存在任何跳转及可能改变该寄存器内容的指令,则寄存器的内容是能够被预测的,对相关访问区域的判断可以在本地转译代码生成阶段被预先处理。
寄存器缓冲:
  经常被访问的模拟寄存器,如r0~r3、cpsr等,应当在本地寄存器中建立长期有效的副本。但x86仅有7个通用寄存器,显然无法满足需求,我们仅能退而求其次,将使用频率最高的r0与cpsr映射到本地寄存器当中。在实行这一优化策略时,有一点是需要注意的——不要使用eax和edx,ebp也最好不要使用。这是因为一些x86指令的操作数限定为eax与edx(典型的如mul/div),而ebp在作为操作数时产生的指令有时要比其它操作数更长(虽然并不会消耗额外的时钟周期)。
寄存器合并与传播优化:
  这是一种在一串连续目标指令(可包含立即数寻址跳转)当中求出某个或数个模拟寄存器的最大作用域、期间不将临时寄存器的内容写回CPU上下文的优化方式。
July 30

Back from HongKong

在度过了一个美好的星期之后全身心的投入到工作和学习当中去吧
June 29

EFZ.NET ver1.21 (Hyper Speed Version) was released

ver1.21 (2006.6.29)
*Upgraded the speed of all transport modes, especially in async mode.
 
This version has a bug that sometimes you may not be able to quit the game by pressing ESC key, it will be fixed in next version.
 
 
Download Link:
June 27

有空的话想做一下这本手册的翻译

怎样优化Pentium系列处理器的代码

Copyright © 1996, 2000 by Agner Fog. Last modified 2000-07-03.
云风 (
Cloud Wu) 译 http://www.codingnow.com 翻译中...(13.3%)

 

http://www.codingnow.com/2000/download/cpentopt.htm
 
全篇都是精华,无奈云风工作室的主页停止更新很久了,这里才翻译了13%,甚感可惜。
暂时也只是说一下而已了,真要做完的话大概会发到看雪去吧XD
June 26

EFZ.NET ver1.20 (Hyper Speed Version) was released

I tried to save the time while transfering data and finally I got a delayed but even smooth speed under the new transport mode in this version.

 

ver1.20 (2006.6.26)
*Added a new transport mode, which is able to transfering action(input) data without pause the game.

 

Never try to set the skip frequency to more than 3 frames in async mode, or you will see "what is delay" clearly.

 

Download Link:

http://www.mbrcn.net/down/EFZ_NET_120_Release_HyperSpeedVersion.zip

June 23

要死了要死了

连续一个星期通宵夜战……
写程序、看书,偶尔再逛逛论坛,天很快又亮起来了。现在感觉好累呀,甚至有点呼吸困难的味道。即便是知道这样做对身体不好,但也只有在晚上,我的思维才能够活跃起来。ヤベー!これどうしよう…

June 15

关于CreateService与SystemLoadAndCallImage的区别

几个众所周知的区别:
 
CreateService是一个文档化Win32 API,
而ZwSetSystemInformation是一个没有文档化的Native API
 
使用StartService加载的驱动需要注册,ZwSetSystemInformation则不用
 
ControlService提供停止驱动的服务,且驱动可以被系统自动的卸载;使用ZwSetSystemInformation的话,你就需要另外想办法来卸载驱动映像
 
一些容易被忽视的区别:
 
由StartService加载的驱动,执行DriverEntry时的进程上下文是System Process,而ZwSetSystemInformation的进程上下文是Current Process
 
StartService调用ZwLoadDriver加载的驱动映像默认是不可分页的,ZwSetSystemInformation则正好相反。如果没有妥善处理这个细节上的问题,你的某些代码将可能在执行期间,由于调用线程的IRQL>PASSIVE_LEVEL兼代码页没有被换回物理内存而引发一个BSOD
 

这是一个访问计数器