分类: 其他平台
2014-06-18 11:25:13
S2E客户虚拟机机器状态描述
1、客户机虚拟机器状态概念和作用
为承载客户操作系统,Qemu虚拟机对客户机所必须的硬件设备(如处理器、内存以及硬盘、总线等大量外设)进行了软件仿真。在现实执行环境中某一时刻机器处于一个相对稳定的状态,这类状态的保持需要借助硬件设备的物理特性,如电平,磁场等,而虚拟机并无硬件支持,因此,实现对此类机器状态的结构化描述以及相关操作十分重要,如在Qemu中cpu相关描述和操作被封装到CPUX86State类中。
S2E符号执行平台依托于Qemu虚拟机建立,为了实现符号变量引入、存储和传播的目的,S2E对Qemu虚拟机的机器状态描述进行了改写和扩展:在保证客户虚拟机具体执行的机器状态机制正常运行的同时,增加了符号执行机器状态描述机制。这两台机制既要保证一致,又要相互隔离,以实现符号执行过程与具体执行过程之间的无缝切换。
本章主要对寄存器和内存的机器状态描述进行分析,阐述S2E利用符号机器状态描述实现对符号执行过程的支持。
2、客户机机器状态描述机制
S2E客户机机器状态描述机制分为两方面:具体机器状态描述和符号机器状态描述,两者的区别在于操作数据是具体值还是符号变量。从符号变量的引入、存储,到传播、运算,甚至最终的路径约束求解都依赖于符号机器状态的支持。符号机器状态和具体机器状态是对相同时刻机器状态的不同描述,因此在数据结构的设计上,它们共同存在,互不冲突,本节先从数据结构入手介绍机器状态描述的大致框架,而针对寄存器和内存的机器状态描述进行详细介绍。
(1)涉及数据结构——MemoryObject、ObjectState、AddressSpace
S2E符号执行平台可以多路径执行,为使用户感受到并发执行的假象,S2E为每条独立执行的路径保存一份独立的机器状态(如计时器等),这样也可以对不同执行过程进行隔离,避免相互干扰。基于Klee的ExecutionState类,S2E继承出S2EExecutionState,用以表征程序状态树中的各个执行路径状态。但是,事实上,同一时刻这些执行状态只有一个是活跃的(由m_active成员标记)。
对于每个客户机的机器位置(寄存器、内存等),S2E维护了两种存储:“唯一”的宿主机器存储位置和每个路径分支的备份存储位置。在实现中统一用Memory类型对象记录前者,而ObjectState记录后者,而这两者是通过AddressSpace联系在一起,指定了一组特定路径上的机器位置集合。
在ObjectState中,存在数据域表征具体和符号机器状态:
class ObjectState {
......
uint8_t *concreteStore; //具体值
......
ref
......
}
Qemu虚拟机向宿主机申请内存空间,并通过转化使客户机物理内存地址与宿主机虚拟内存地址一一对应,提供给客户机内存仿真效果。而MemoryObject类中就维护着客户机位置在宿主机中的存储位置,即“唯一”的宿主机器存储位置。
class MemoryObject {
friend class STPBuilder;
private:
static int counter;
public:
unsigned id;
uint64_t address; //指定了客户机器位置在宿主机的对应位置。
/// size in bytes
unsigned size;
std::string name;
......
//描述了内存块的特性
bool isUserSpecified;
bool isSharedConcrete;
/// True if the object value can be ignored in local consistency
bool isValueIgnored;
......
}
AddressSpace负责维护、管理前两者之间的对应关系:在机器状态保存时,将MemoryObject对象中的信息存至当前路径对应的ObjectState对象的成员变量ConcreteStore中;机器状态恢复时,将该成员变量的值恢复至对应地址处。
class AddressSpace {
......
MemoryMap objects; //MemoryObject和ObjectState的映射
// ExecutionState that owns this AddressSpace
ExecutionState *state;
......
}
//映射类型定义
typedef ImmutableMap
3、寄存器和内存相关机器状态
(1)寄存器相关机器状态描述
CPU相关的具体机器状态在S2E中是以CPUX86State描述的。通过操作该类型的对象可以完成对客户机处理器机器状态的操作。在对处理器中寄存器状态进行描述时,根据寄存器的不同特性,将寄存器分为两类:一类是可符号化的寄存器,如通用寄存器;另一类是不可符号化的寄存器,如程序计数器、ldt\gdt和cr系列寄存器,其符号化并没有什么意义,会对整个客户操作系统执行造成重大影响。
typedef struct CPUX86State {
target_ulong regs[CPU_NB_REGS]; //通用寄存器
......
target_ulong eip; //程序计数器以及其他特殊寄存器
......
/* segments */
SegmentCache segs[6]; /* selector values */
SegmentCache ldt;
SegmentCache tr;
SegmentCache gdt; /* only base and limit are used */
SegmentCache idt; /* only base and limit are used */
target_ulong cr[5]; /* NOTE: cr1 is unused */
......
}
在处理器初始化过程中,首先将这两部分映射到不同的内存空间,并赋予不同属性。
/* Add registers and eflags area as a true symbolic area 可符号化的寄存器内存分配*/
initialState->m_cpuRegistersState =
addExternalObject(*initialState, cpuEnv,
offsetof(CPUX86State, eip),
/* isReadOnly = */ false,
/* isUserSpecified = */ false,
/* isSharedConcrete = */ false);
/* Add the rest of the structure as concrete-only area 不可符号化的寄存器内存分配*/
initialState->m_cpuSystemState =
addExternalObject(*initialState,
((uint8_t*)cpuEnv) + offsetof(CPUX86State, eip),
sizeof(CPUX86State) - offsetof(CPUX86State, eip),
/* isReadOnly = */ false,
/* isUserSpecified = */ true,
/* isSharedConcrete = */ true);
(2)内存相关状态描述
同样,对内存机器状态的管理也是首先向宿主机申请内存空间作为客户机内存空间,在这里是以MemoryObject的大小为单位向宿主机申请,见S2EExecutor.cpp中的registerRam()函数。
//对申请的到的内存空间进行分块
for(uint64_t addr = hostAddress; addr < hostAddress+size;
addr += S2E_RAM_OBJECT_SIZE) {
std::stringstream ss;
ss << name << "_" << std::hex << (addr-hostAddress);
//使用MemoryObject 对象对内存块进行管理
MemoryObject *mo = addExternalObject(
*initialState, (void*) addr, S2E_RAM_OBJECT_SIZE, false,
/* isUserSpecified = */ true, isSharedConcrete,
isSharedConcrete && !saveOnContextSwitch && StateSharedMemory);
mo->setName(ss.str());
......
}