前言
我们现在在构建好分页模式的基础上,来通过对mov指令的分析来实现对bochs的Non-PAE-4K分页机制的分析,在分析过程中,我们会看到关于对页属性的各种检查,之后再来总结。
注意,其中可能涉及缓存部分(TLB),这个我们决定放在后面分析,因此如果遇到TLB信息,我们暂且跳过,不做过多解读。
反汇编定位代码
我们现在先将ex11-2/protected.asm中测试二的代码注释掉,为分析直观,将mov[xx],0 改为 mov[xx],4. 然后按照我们上篇文章补充的Shell脚本来进行编译,内容如下:
; 测试二:在 CR0.WP=0时,在0级代码里写0x400000地址
mov DWORD [0x400000], 4
使用IDA来逆向这部分,定位其汇编地址,内容如下:

现在通过bochs来运行到bochs反汇编代码,内容如下:

堆栈分析-1
其核心代码在 BX_CPU_C::access_write_linear(...) 中,从cpu->execute1(...)到access_write_linear(...) 经过一系列函数,这些函数并没有什么核心代码,只是基本单纯地封装调用,我们直接展示其函数堆栈。
> bochs.exe!BX_CPU_C::access_write_linear(unsigned __int64 laddr, unsigned int len, unsigned int curr_pl, unsigned int xlate_rw, unsigned int ac_mask, void * data) Line 2402 C++
bochs.exe!BX_CPU_C::write_linear_dword(unsigned int s, unsigned __int64 laddr, unsigned int data) Line 102 C++
bochs.exe!BX_CPU_C::write_virtual_dword(unsigned int s, unsigned __int64 offset, unsigned int data) Line 200 C++
bochs.exe!BX_CPU_C::MOV_EdIdM(bxInstruction_c * i) Line 30 C++
bochs.exe!BX_CPU_C::cpu_loop() Line 109 C++
access_write_linear(...)函数分析
我们现在来分析access_write_linear(..)的代码,其函数定义如下:
int BX_CPU_C::access_write_linear(
bx_address laddr, // 线性地址 0x400000
unsigned len, // 数据长度 4
unsigned curr_pl, // 当前的CPL 0
unsigned xlate_rw, // 读写 BX_WRITE
Bit32u ac_mask, // 0x3
void *data // 写入数据的指针
){
....
}
在这部分函数中,有两个非常重要的代码,下面我们会重点分析这两部分代码。
translate_linear(...)函数是将线性地址转化为物理地址,其中有一个tlbEntry,这个是TLB页缓的,如果该地址在tlb中存在缓存,则优先从缓存中获取。
bx_bool user = (curr_pl == 3);
BX_CPU_THIS_PTR address_xlation.paddress1 =
translate_linear(tlbEntry, laddr, user, xlate_rw);
下面这个是写入数据,此时已经拿到物理地址了,将该函数写入地址。
access_write_physical(
BX_CPU_THIS_PTR address_xlation.paddress1, len, data);
translate_linear(...)函数分析
这部分代码如下,先来检查当前权限,我们此时仅是写,这里计算出lpf,这个就是线性地址取整数,通过这个值在tlb缓存中查询(暂时不分析),如果可以查询到,直接从tlb查询到对应的物理地址,直接返回。
unsigned isWrite = rw & 1; // write or r-m-w
unsigned isExecute = (rw == BX_EXECUTE);
unsigned isShadowStack = (rw & 4); // 4 if shadowstack and 0 otherwise
bx_address lpf = LPFOf(laddr); // return laddr & 0xfffffffffffff000;
// 从缓存中查询
if (! isExecute && TLB_LPFOf(tlbEntry->lpf) == lpf){
...
}
下面这段代码就是判断寻址模式的代码,很好理解,其先来检查cr0寄存器代码是否开启,然后检查是否开启Long_mode模式,如果开启直接选择4-level分页模式解析,否则再来判断是否开启PAE,其对应选择PAE与NonPAE两种分页模式。
我们这里肯定是调用translate_linear_legacy(...),这里legacy是"传统"的含义,对应的就是我们所讲的Non-Page分页模式,我们继续来分析这个函数。
if(BX_CPU_THIS_PTR cr0.get_PG())
{
BX_DEBUG(("page walk for%s address 0x" FMT_LIN_ADDRX, isShadowStack ? " shadow stack" : "", laddr));
#if BX_CPU_LEVEL >= 6
#if BX_SUPPORT_X86_64
if (long_mode())
paddress = translate_linear_long_mode(laddr, lpf_mask, pkey, user, rw);
else
#endif
if (BX_CPU_THIS_PTR cr4.get_PAE())
paddress = translate_linear_PAE(laddr, lpf_mask, user, rw);
else
#endif
paddress = translate_linear_legacy(laddr, lpf_mask, user, rw);
#endif
} else {
// no paging
paddress = (bx_phy_address) laddr;
combined_access |= (BX_MEMTYPE_WB << 9); // act as memory type by paging is WB
}
translate_linear_legacy(...)函数分析
该函数表示来解析NonPage分页模式获取物理页,下面是这部分的核心代码,之后我们会围绕这部分代码来进行分析。
bx_phy_address BX_CPU_C::translate_linear_legacy(bx_address laddr, Bit32u &lpf_mask, unsigned user, unsigned rw)
{
bx_phy_address entry_addr[2], ppf = (Bit32u) BX_CPU_THIS_PTR cr3 & BX_CR3_PAGING_MASK;
Bit32u entry[2];
BxMemtype entry_memtype[2] = { 0 };
int leaf;
lpf_mask = 0xfff;
Bit32u combined_access = (BX_COMBINED_ACCESS_WRITE | BX_COMBINED_ACCESS_USER);
Bit32u curr_entry = (Bit32u) BX_CPU_THIS_PTR cr3;
for (leaf = BX_LEVEL_PDE;; --leaf) {
entry_addr[leaf] = ppf + ((laddr >> (10 + 10*leaf)) & 0xffc);
}
access_read_physical(entry_addr[leaf], 4, &entry[leaf]);
curr_entry = entry[leaf];
if (!(curr_entry & 0x1)) {
BX_DEBUG(("%s: entry not present", bx_paging_level[leaf]));
page_fault(ERROR_NOT_PRESENT, laddr, user, rw);
}
ppf = curr_entry & 0xfffff000;
if (leaf == BX_LEVEL_PTE) break;
#if BX_CPU_LEVEL >= 5
if ((curr_entry & 0x80) != 0 && BX_CPU_THIS_PTR cr4.get_PSE()) {
// 4M paging, only if CR4.PSE enabled, ignore PDE.PS otherwise
if (curr_entry & PAGING_PDE4M_RESERVED_BITS) {
BX_DEBUG(("PSE PDE4M: reserved bit is set: PDE=0x%08x", entry[BX_LEVEL_PDE]));
page_fault(ERROR_RESERVED | ERROR_PROTECTION, laddr, user, rw);
}
// make up the physical frame number
ppf = (curr_entry & 0xffc00000);
#if BX_PHY_ADDRESS_WIDTH > 32
ppf |= ((bx_phy_address)(curr_entry & 0x003fe000)) << 19;
#endif
lpf_mask = 0x3fffff;
break;
}
#endif
combined_access &= curr_entry; // U/S and R/W
}
...
...
}
首先,其先通过cpu.cr3来获取pde页表的基址 0x100000,代码如下,我们也展示出在汇编中的相关代码。
ppf = (Bit32u) BX_CPU_THIS_PTR cr3 & BX_CR3_PAGING_MASK;
;*** 32-bit paging �� ***
%define PDT32_BASE 100000h
mov eax, PDT32_BASE
mov cr3, eax
之后来构建变量combined_access,这里这么写的原因是页权限读写是由多级页表项所共同决定的,因此我们先预设个最大值,之后每读一个页表项,则来进行一个&运算,最后可以得到该页的最终访问值。
Bit32u combined_access = (BX_COMBINED_ACCESS_WRITE | BX_COMBINED_ACCESS_USER);
下面来到核心循环部分,注意该结构体,并没有终止条件,因此其退出是break(该循环中没有false),我们是Non-Page,因此从PDE开始递归搜索,leaf = BX_LEVEL_PDE。
enum {
BX_LEVEL_PML4 = 3,
BX_LEVEL_PDPTE = 2,
BX_LEVEL_PDE = 1,
BX_LEVEL_PTE = 0
};
for (leaf = BX_LEVEL_PDE;; --leaf) {
...
}
下面这部分很好理解,就是来计算地址偏移,注意其 (laddr >> 20) & 0xffc,相当于 (laddr >> 22) * 4,因为在PDT中项的长度为4个字节,这样entry_addr[1]中存储着PDE项了。
entry_addr[leaf] = ppf + ((laddr >> (10 + 10*leaf)) & 0xffc);
// 之前用c++模拟的代码
*offset_1 = (address >> 22);
然后调用access_read_physical(...)这个函数来获取PTT的地址,放在entry[1]中。
access_read_physical(entry_addr[leaf], 4, &entry[leaf]);
拿到该页之后其判断p位是否有效,然后判断当前是否在BX_LEVEL_PTE,如果是则说明解析完成,否则退出。当然我们现在还在BX_LEVEL_PDE,还要继续往下分析。
curr_entry = entry[leaf];
if (!(curr_entry & 0x1)) {
BX_DEBUG(("%s: entry not present", bx_paging_level[leaf]));
page_fault(ERROR_NOT_PRESENT, laddr, user, rw);
}
ppf = curr_entry & 0xfffff000;
if (leaf == BX_LEVEL_PTE) break;
下面这个就是来判断是否是大页PS(4M)模式了,其除了判断PDE项,还要来判断CR4.PSE位来查看CPU是否开启这个模式。如果是大页模式这直接获取对应的地址,然后break掉不往下继续解析。我们这是4K模式,因此继续往下解析。
if ((curr_entry & 0x80) != 0 && BX_CPU_THIS_PTR cr4.get_PSE()) {
// 4M paging, only if CR4.PSE enabled, ignore PDE.PS otherwise
if (curr_entry & PAGING_PDE4M_RESERVED_BITS) {
BX_DEBUG(("PSE PDE4M: reserved bit is set: PDE=0x%08x", entry[BX_LEVEL_PDE]));
page_fault(ERROR_RESERVED | ERROR_PROTECTION, laddr, user, rw);
}
// make up the physical frame number
ppf = (curr_entry & 0xffc00000);
#if BX_PHY_ADDRESS_WIDTH > 32
ppf |= ((bx_phy_address)(curr_entry & 0x003fe000)) << 19;
#endif
lpf_mask = 0x3fffff;
break;
}
下面这个就是来更新当前项的访问模式,使用&运算符,内容如下:
combined_access &= curr_entry; // U/S and R/W
之后开启第二次循环,leaf--,达到BX_LEVEL_PTE模式,再来经历一轮读取内存等操作,最后遇到下面这条语句来结束循环,之后来进行返回。
ppf = curr_entry & 0xfffff000;
if (leaf == BX_LEVEL_PTE) break;
我们返回的就是pff这个|combined_access,我们所需要的线性地址对应的物理地址以及读写权限。这段代码后半部分是各种权限检查,我们先绕过去,不继续分析了。
return (ppf | combined_access);
access_write_physical(...)函数分析
该函数调用堆栈如下,这部分代码没有什么好分析的,就是单纯的函数调用。
> bochs.exe!WriteHostDWordToLittleEndian(unsigned int * hostPtr, unsigned int nativeVar32) Line 545 C++
bochs.exe!BX_MEM_C::writePhysicalPage(BX_CPU_C * cpu, unsigned __int64 addr, unsigned int len, void * data) Line 99 C++
bochs.exe!BX_CPU_C::access_write_physical(unsigned __int64 paddr, unsigned int len, void * data) Line 2599 C++
bochs.exe!BX_CPU_C::access_write_linear(unsigned __int64 laddr, unsigned int len, unsigned int curr_pl, unsigned int xlate_rw, unsigned int ac_mask, void * data) Line 2410 C++
值得注意的是下面代码,在WritePhysicalPage中,传递的指针通过 get_vector(a20addr)这个函数返回得到,可以看出其物理页面存储在 mm.blocks[block]结构体中,然后通过 (Bit32u)(addr & (BX_MEM_BLOCK_LEN-1)) 来获取对应的偏移。
Mem & (0x1000 -1) 这种算法表示获取Mem后面0x1000的内容,这种写法要明确。
这里的mm.blocks[..]我们在前面bochs初始化章节有过讲解,可以回去翻阅相关内容。
WriteHostDWordToLittleEndian((Bit32u*) BX_MEM_THIS get_vector(a20addr), *(Bit32u*)data);
BX_CPP_INLINE Bit8u* BX_MEM_C::get_vector(bx_phy_address addr)
{
Bit32u block = (Bit32u)(addr / BX_MEM_BLOCK_LEN);
#if (BX_LARGE_RAMFILE)
if (!BX_MEM_THIS blocks[block] || (BX_MEM_THIS blocks[block] == BX_MEM_THIS swapped_out))
#else
if (!BX_MEM_THIS blocks[block])
#endif
allocate_block(block);
return BX_MEM_THIS blocks[block] + (Bit32u)(addr & (BX_MEM_BLOCK_LEN-1));
}
最后,这就是将数据写入进去
BX_CPP_INLINE void WriteHostDWordToLittleEndian(Bit32u *hostPtr, Bit32u nativeVar32)
{
*(hostPtr) = nativeVar32;
}
总结
分析完bochs对这边代码的实现,现在这里只是分析了一个大体流程,我们可以看到三种分页模式的解析,之后我们会着重来分析有关属性权限检查。
比较重要的几个函数
access_write_physical - 写入物理页
translate_linear - 线性地址转换为物理地址
translate_linear_long_mode - LongMode模式页解析
translate_linear_PAE - PAE模式解析
translate_linear_legacy - Non-PAE模式解析
下面是我们在源码中分析得到的一种取值技巧,其OFFSET来定义来获取后面多少位,我们这里是12,表示获取后面12位,最后输出结果 0x234。 这种技巧我们之前也经常遇到过,但是可能当时无心或者技术不行,根本学不会,现在来看真的很巧妙。
#define OFFSET 12
int main(int argc, char const* argv[]) {
int laddr = 0x1234;
int offset = 0x1234 & ((1 << OFFSET) - 1);
cout << hex << offset << endl; // 234
return 0;
}
964




被折叠的 条评论
为什么被折叠?



