在 qemu-system-mips 中访问 0xCxxxxxxx 来宾内核指针
Accessing 0xCxxxxxxx guest kernel pointers within qemu-system-mips
在我基于 QEMU 的项目(系统仿真)中,我分析了客户机的各种内核结构 Linux。要读取来宾虚拟内存,我使用 cpu_memory_rw_debug()
函数。
特别是,我使用某种试探法在内核内存中搜索 struct module
链表。
以免假设此列表中元素的相关部分如下所示:
--------------------- ---------------------
| prev = 0xc1231234 | | prev = 0xc5675678 |
--------------------- ---------------------
| next = 0xc1122334 | | next = 0xc5566778 |
--------------------- ---------------------
| etc. | | etc. |
--------------------- ---------------------
当 QEMU 模拟 x86 或 ARM 时,prev/next 指针可以被 cpu_memory_rw_debug()
访问,它们实际上指向 previous/next 列表元素。
然而,当 QEMU 模拟 MIPS 时,我观察到以下奇怪的行为:虽然 prev/next 指针在列表中的每个元素中看起来都是有效的内核指针,但我无法通过 [=12] 访问它们的指针=],因为找不到对应的物理地址失败:访问权限没问题,虚拟CPU处于内核态,但是tlb->map_address()
失败。
因为我无法遍历链表,所以我试图一个一个地找到元素 - 只是为了看看它们的 prev/next 指针是什么样的 - 我实际上找到了所有元素,但所有其中有 0xAxxxxxxx
个地址,而不是 prev/next 暗示的 0xCxxxxxxx
。
执行物理地址查找的函数r4k_map_address()
如下所示(仅相关摘录):
#define KSEG0_BASE 0x80000000UL
#define KSEG1_BASE 0xA0000000UL
#define KSEG2_BASE 0xC0000000UL
#define KSEG3_BASE 0xE0000000UL
//..............
if (address < (int32_t)KSEG1_BASE) {
/* kseg0 */
if (kernel_mode) {
*physical = address - (int32_t)KSEG0_BASE;
*prot = PAGE_READ | PAGE_WRITE;
} else {
ret = TLBRET_BADADDR;
}
} else if (address < (int32_t)KSEG2_BASE) {
/* kseg1 */
if (kernel_mode) {
*physical = address - (int32_t)KSEG1_BASE;
*prot = PAGE_READ | PAGE_WRITE;
} else {
ret = TLBRET_BADADDR;
}
} else if (address < (int32_t)KSEG3_BASE) {
/* sseg (kseg2) */
if (supervisor_mode || kernel_mode) {
ret = env->tlb->map_address(env, physical, prot, real_address, rw, access_type);
} else {
ret = TLBRET_BADADDR;
}
也就是说,在 MIPS 上 0xC0000000...0xE0000000
范围的映射不同于低内核范围。
如果我用 *physical = address - (int32_t)KSEG1_BASE
直接映射替换 TLB 访问,我就可以正常工作,但肯定不是解决方案。
它看起来像是与 QEMU 相关的问题还是与 MIPS 相关的问题?我将不胜感激任何想法或调试方向。
底线是 cpu_memory_rw_debug()
在 qemu-system-mips 中不能可靠地工作。
原因是 QEMU 模拟了 MIPS 软件管理的 TLB。使用这种方法,只要 TLB 缓存中不存在虚拟->物理地址映射,QEMU 就会模拟 "TLB-miss" 异常,这应该由 OS 处理。遍历页面目录并填充 TLB 是 OS 的责任——QEMU(就像真正的 MIPS)不会那样做。
虽然此方法适用于来宾代码,但会导致无法使用
使用 cpu_memory_rw_debug()
读取来宾虚拟内存 - 它
mapped segments.
不能可靠地工作
至于为什么实际上驻留在 KSEG2 中的内核结构在 KSEG1 中观察到的问题 - 那只是因为 KSEG1 和 KSEG2 的某些虚拟范围对应于相同的物理页面。
在我基于 QEMU 的项目(系统仿真)中,我分析了客户机的各种内核结构 Linux。要读取来宾虚拟内存,我使用 cpu_memory_rw_debug()
函数。
特别是,我使用某种试探法在内核内存中搜索 struct module
链表。
以免假设此列表中元素的相关部分如下所示:
--------------------- ---------------------
| prev = 0xc1231234 | | prev = 0xc5675678 |
--------------------- ---------------------
| next = 0xc1122334 | | next = 0xc5566778 |
--------------------- ---------------------
| etc. | | etc. |
--------------------- ---------------------
当 QEMU 模拟 x86 或 ARM 时,prev/next 指针可以被 cpu_memory_rw_debug()
访问,它们实际上指向 previous/next 列表元素。
然而,当 QEMU 模拟 MIPS 时,我观察到以下奇怪的行为:虽然 prev/next 指针在列表中的每个元素中看起来都是有效的内核指针,但我无法通过 [=12] 访问它们的指针=],因为找不到对应的物理地址失败:访问权限没问题,虚拟CPU处于内核态,但是tlb->map_address()
失败。
因为我无法遍历链表,所以我试图一个一个地找到元素 - 只是为了看看它们的 prev/next 指针是什么样的 - 我实际上找到了所有元素,但所有其中有 0xAxxxxxxx
个地址,而不是 prev/next 暗示的 0xCxxxxxxx
。
执行物理地址查找的函数r4k_map_address()
如下所示(仅相关摘录):
#define KSEG0_BASE 0x80000000UL
#define KSEG1_BASE 0xA0000000UL
#define KSEG2_BASE 0xC0000000UL
#define KSEG3_BASE 0xE0000000UL
//..............
if (address < (int32_t)KSEG1_BASE) {
/* kseg0 */
if (kernel_mode) {
*physical = address - (int32_t)KSEG0_BASE;
*prot = PAGE_READ | PAGE_WRITE;
} else {
ret = TLBRET_BADADDR;
}
} else if (address < (int32_t)KSEG2_BASE) {
/* kseg1 */
if (kernel_mode) {
*physical = address - (int32_t)KSEG1_BASE;
*prot = PAGE_READ | PAGE_WRITE;
} else {
ret = TLBRET_BADADDR;
}
} else if (address < (int32_t)KSEG3_BASE) {
/* sseg (kseg2) */
if (supervisor_mode || kernel_mode) {
ret = env->tlb->map_address(env, physical, prot, real_address, rw, access_type);
} else {
ret = TLBRET_BADADDR;
}
也就是说,在 MIPS 上 0xC0000000...0xE0000000
范围的映射不同于低内核范围。
如果我用 *physical = address - (int32_t)KSEG1_BASE
直接映射替换 TLB 访问,我就可以正常工作,但肯定不是解决方案。
它看起来像是与 QEMU 相关的问题还是与 MIPS 相关的问题?我将不胜感激任何想法或调试方向。
底线是 cpu_memory_rw_debug()
在 qemu-system-mips 中不能可靠地工作。
原因是 QEMU 模拟了 MIPS 软件管理的 TLB。使用这种方法,只要 TLB 缓存中不存在虚拟->物理地址映射,QEMU 就会模拟 "TLB-miss" 异常,这应该由 OS 处理。遍历页面目录并填充 TLB 是 OS 的责任——QEMU(就像真正的 MIPS)不会那样做。
虽然此方法适用于来宾代码,但会导致无法使用
使用 cpu_memory_rw_debug()
读取来宾虚拟内存 - 它
mapped segments.
至于为什么实际上驻留在 KSEG2 中的内核结构在 KSEG1 中观察到的问题 - 那只是因为 KSEG1 和 KSEG2 的某些虚拟范围对应于相同的物理页面。