- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
(这个问题并不是针对 VirtualBox 或 x86 本身,但由于它们是我所知道的最好的例子,我将引用它们并询问 VBox 如何处理一些场景。如果你知道VBox 未使用的其他解决方案中,也请考虑提及它们。)
我已经阅读了 how VirtualBox does software virtualization ,但我不明白以下内容。
Before executing ring 0 code, CSAM [Code Scanning and Analysis Manager] scans it recursively to discover problematic instructions. PATM [Patch Manager] then performs in-situ patching, i.e. it replaces the instruction with a jump to hypervisor memory where an integrated code generator has placed a more suitable implementation. In reality, this is a very complex task as there are lots of odd situations to be discovered and handled correctly. So, with its current complexity, one could argue that PATM is an advanced in-situ recompiler.
call foo
foo:
mov EAX, 1234
mov EDX, [ESP]
cmp EDX, EAX
jne bar
call do_something_special_if_return_address_was_1234
bar:
...
1234
,如果是,它会做一些特殊的事情。显然打补丁会改变返回地址,所以我们需要能够处理它。
call
可能是最常见的(也是最常见的)。这是否意味着 VirtualBox 必须分析并可能修补它在环 0 中看到的每个 call
指令?这不会使性能下降吗?他们如何以高性能处理这个问题? (他们在文档中提到的情况非常模糊,所以我很困惑为什么他们没有提到这种常见的指令,如果它发生。如果这不是问题,我不明白为什么。)mov
指令(和 push
指令,以及所有其他写入内存的指令)现在都必须被分析并可能被修补,可能是重复的,因为它可能正在修改被修补的代码。这似乎从本质上将所有访客环 0 代码退化为近乎完整的软件仿真(因为在重新编译期间不知道移动的目标),这将使虚拟化成本飙升,但这不是我得到的印象阅读文档。这不是问题吗?这是如何有效处理的? 最佳答案
至少对于 QEMU
,似乎答案是,即使在翻译的代码中,也有一个单独的模拟“堆栈”,其设置的值与 native 运行时代码具有的值相同,而这个“堆栈”是由模拟代码,它看到的值与 native 运行时相同。
这意味着无法将模拟代码转换为使用 call
, ret
,或直接使用任何其他堆栈指令,因为这些指令不会使用模拟堆栈。因此,这些调用被跳转到 thunk 代码的各个位所取代,这些代码在调用等效的翻译代码方面做了正确的事情。
QEMU 的详细信息
OP 的(合理的)假设似乎是 call
和 ret
指令将出现在翻译后的二进制文件中,堆栈将反射(reflect)动态翻译代码的地址。实际发生的(在 QEMU 中)是 call
和 ret
指令被删除并替换为不使用堆栈的控制流,并且堆栈上的值设置为与它们在 native 代码中相同的值。
也就是说,OP 的心智模型是代码翻译的结果有点像原生代码,有一些补丁和修改。至少在 QEMU 的情况下,情况并非如此——每个基本块都通过 Tiny Code Generator (TCG) 进行了大量翻译。 ,首先是中间表示,然后是目标架构(即使源和目标架构相同,就像我的情况一样)。 This deck对许多技术细节有很好的概述,包括如下所示的 TCG 概述。
生成的代码通常与输入代码完全不同,并且通常会增加大约 3 倍的大小。寄存器通常很少使用,您经常会看到背靠背的冗余序列。与这个问题特别相关的是,基本上所有的控制流指令都大不相同,所以 ret
和 call
native 代码中的指令几乎从未翻译成简单的 call
或 ret
在翻译的代码中。
一个例子:首先,一些带有 return_address()
的 C 代码调用只返回返回地址和 main()
打印此功能:
#include <stdlib.h>
#include <stdio.h>
__attribute__ ((noinline)) void* return_address() {
// stuff here?
return __builtin_return_address(0);
}
int main(int argc, char **argv) {
void *a = return_address();
printf("%p\n", a);
}
noinline
这里很重要,否则
gcc
只需内联函数并将地址直接硬编码到程序集中即可,而无需制作
call
或者完全访问堆栈!
gcc -g -O1 -march=native
这编译为:
0000000000400546 <return_address>:
400546: 48 8b 04 24 mov rax,QWORD PTR [rsp]
40054a: c3 ret
000000000040054b <main>:
40054b: 48 83 ec 08 sub rsp,0x8
40054f: b8 00 00 00 00 mov eax,0x0
400554: e8 ed ff ff ff call 400546 <return_address>
400559: 48 89 c2 mov rdx,rax
40055c: be 04 06 40 00 mov esi,0x400604
400561: bf 01 00 00 00 mov edi,0x1
400566: b8 00 00 00 00 mov eax,0x0
40056b: e8 c0 fe ff ff call 400430 <__printf_chk@plt>
400570: b8 00 00 00 00 mov eax,0x0
400575: 48 83 c4 08 add rsp,0x8
400579: c3 ret
return_address()
返回
[rsp]
就像OP的例子一样。
main()
函数将其粘贴在
rdx
,其中
printf
将从中读取它。
return_address
所见。成为调用后的指令,
0x400559
:
400554: e8 ed ff ff ff call 400546 <return_address>
400559: 48 89 c2 mov rdx,rax
person@host:~/dev/test-c$ ./qemu-test
0x400559
person@host:~/dev/test-c$ qemu-x86_64 ./qemu-test
0x400559
-d in_asm,out_asm
QEMU 的选项以查看它是如何制作此代码的。
IN
部分是 native 代码,而
OUT
是 QEMU 将其转换为的内容——抱歉 AT&T 语法,我不知道如何使用
change that in QEMU ):
IN: main
0x000000000040054b: sub $0x8,%rsp
0x000000000040054f: mov $0x0,%eax
0x0000000000400554: callq 0x400546
OUT: [size=123]
0x557c9cf33a40: mov -0x8(%r14),%ebp
0x557c9cf33a44: test %ebp,%ebp
0x557c9cf33a46: jne 0x557c9cf33aac
0x557c9cf33a4c: mov 0x20(%r14),%rbp
0x557c9cf33a50: sub $0x8,%rbp
0x557c9cf33a54: mov %rbp,0x20(%r14)
0x557c9cf33a58: mov $0x8,%ebx
0x557c9cf33a5d: mov %rbx,0x98(%r14)
0x557c9cf33a64: mov %rbp,0x90(%r14)
0x557c9cf33a6b: xor %ebx,%ebx
0x557c9cf33a6d: mov %rbx,(%r14)
0x557c9cf33a70: sub $0x8,%rbp
0x557c9cf33a74: mov $0x400559,%ebx
0x557c9cf33a79: mov %rbx,0x0(%rbp)
0x557c9cf33a7d: mov %rbp,0x20(%r14)
0x557c9cf33a81: mov $0x11,%ebp
0x557c9cf33a86: mov %ebp,0xa8(%r14)
0x557c9cf33a8d: jmpq 0x557c9cf33a92
0x557c9cf33a92: movq $0x400546,0x80(%r14)
0x557c9cf33a9d: mov $0x7f177ad8a690,%rax
0x557c9cf33aa7: jmpq 0x557c9cef8196
0x557c9cf33aac: mov $0x7f177ad8a693,%rax
0x557c9cf33ab6: jmpq 0x557c9cef8196
0x557c9cf33a74: mov $0x400559,%ebx
0x557c9cf33a79: mov %rbx,0x0(%rbp)
rbp
访问)。之后,请注意没有
call
指示
return_address
.相反,我们有:
0x557c9cf33a92: movq $0x400546,0x80(%r14)
0x557c9cf33a9d: mov $0x7f177ad8a690,%rax
0x557c9cf33aa7: jmpq 0x557c9cef8196
r14
似乎是指向某个内部 QEMU 数据结构的指针(即,不用于保存来自模拟程序的值)。以上推
0x400546
(它是 native 代码中
return_address
函数的地址)转换为
r14
指向的结构体的字段, 棒
0x7f177ad8a690
在
rax
,然后跳转到
0x557c9cef8196
.最后一个地址在生成的代码中随处可见(但它的定义没有),似乎是某种内部调度或 thunk 方法。据推测,它使用本地地址,或者更有可能使用
rax
中的神秘值。派送翻译
return_address
方法,看起来像这样:
----------------
IN: return_address
0x0000000000400546: mov (%rsp),%rax
0x000000000040054a: retq
OUT: [size=64]
0x55c131ef9ad0: mov -0x8(%r14),%ebp
0x55c131ef9ad4: test %ebp,%ebp
0x55c131ef9ad6: jne 0x55c131ef9b01
0x55c131ef9adc: mov 0x20(%r14),%rbp
0x55c131ef9ae0: mov 0x0(%rbp),%rbx
0x55c131ef9ae4: mov %rbx,(%r14)
0x55c131ef9ae7: mov 0x0(%rbp),%rbx
0x55c131ef9aeb: add $0x8,%rbp
0x55c131ef9aef: mov %rbp,0x20(%r14)
0x55c131ef9af3: mov %rbx,0x80(%r14)
0x55c131ef9afa: xor %eax,%eax
0x55c131ef9afc: jmpq 0x55c131ebe196
0x55c131ef9b01: mov $0x7f9ba51f7713,%rax
0x55c131ef9b0b: jmpq 0x55c131ebe196
ebp
中设置了用户“堆栈” (从
r14 + 0x20
获取它,这可能是模拟的机器状态结构)并最终从“堆栈”(行
mov 0x0(%rbp),%rbx
)中读取并将其存储到由
r14
指向的区域中(
mov %rbx,0x80(%r14)
)。
jmpq 0x55c131ebe196
,它转移到 QEMU 结语例程:
0x55c131ebe196: add $0x488,%rsp
0x55c131ebe19d: pop %r15
0x55c131ebe19f: pop %r14
0x55c131ebe1a1: pop %r13
0x55c131ebe1a3: pop %r12
0x55c131ebe1a5: pop %rbx
0x55c131ebe1a6: pop %rbp
0x55c131ebe1a7: retq
rsp
指向的真正堆栈。 .
rsp
指向的真正堆栈由QEMU控制实现模拟控制流,模拟代码不直接访问。
rsp
的值而不是
[rsp]
指向的内容)。
__attribute__ ((noinline)) void* return_address() {
return __builtin_frame_address(0);
}
0x7fffad33c100
的地址但返回地址如
0x40007ffd00
在 QEMU 下。不过,这应该不是问题,因为没有有效的程序应该依赖于堆栈地址的确切绝对值。它不仅通常没有定义和不可预测,而且在最近的操作系统中,由于堆栈
ASLR 它确实被设计为完全不可预测。 (Linux 和 Windows 都实现了这一点)。上面的程序每次我本地运行时都会返回一个不同的地址(但在 QEMU 下是相同的地址)。
argc
有条件地调用的函数)。所以一般来说,将新代码加载到内核中,或者加载到用户模式仿真中的进程中,是由相同的机制处理的:代码将在第一次调用时被简单地翻译。
关于compilation - 动态重新编译如何处理软件虚拟化中的指令指针检查?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45253035/
我刚接触 C 语言几周,所以对它还很陌生。 我见过这样的事情 * (variable-name) = -* (variable-name) 在讲义中,但它到底会做什么?它会否定所指向的值吗? 最佳答案
我有一个指向内存地址的void 指针。然后,我做 int 指针 = void 指针 float 指针 = void 指针 然后,取消引用它们以获取值。 { int x = 25; vo
我正在与计算机控制的泵进行一些串行端口通信,我用来通信的 createfile 函数需要将 com 端口名称解析为 wchar_t 指针。 我也在使用 QT 创建一个表单并获取 com 端口名称作为
#include "stdio.h" #include "malloc.h" int main() { char*x=(char*)malloc(1024); *(x+2)=3; --
#include #include main() { int an_int; void *void_pointer = &an_int; double *double_ptr = void
对于每个时间步长,我都有一个二维矩阵 a[ix][iz],ix 从 0 到 nx-1 和 iz 从 0 到 nz-1。 为了组装所有时间步长的矩阵,我定义了一个长度为 nx*nz*nt 的 3D 指针
我有一个函数,它接受一个指向 char ** 的指针并用字符串填充它(我猜是一个字符串数组)。 *list_of_strings* 在函数内部分配内存。 char * *list_of_strings
我试图了解当涉及到字符和字符串时,内存分配是如何工作的。 我知道声明的数组的名称就像指向数组第一个元素的指针,但该数组将驻留在内存的堆栈中。 另一方面,当我们想要使用内存堆时,我们使用 malloc,
我有一个 C 语言的 .DLL 文件。该 DLL 中所有函数所需的主要结构具有以下形式。 typedef struct { char *snsAccessID; char *
指针, C语言的精髓 莫队先咕几天, 容我先讲完树剖 (因为后面树上的东西好多都要用树剖求 LCA). 什么是指针 保存变量地址的变量叫做指针. 这是大概的定义, 但是Defad认为
我得到了以下数组: let arr = [ { children: [ { children: [], current: tru
#include int main(void) { int i; int *ptr = (int *) malloc(5 * sizeof(int)); for (i=0;
我正在编写一个程序,它接受一个三位数整数并将其分成两个整数。 224 将变为 220 和 4。 114 将变为 110 和 4。 基本上,您可以使用模数来完成。我写了我认为应该工作的东西,编译器一直说
好吧,我对 C++ 很陌生,我确定这个问题已经在某个地方得到了回答,而且也很简单,但我似乎找不到答案.... 我有一个自定义数组类,我将其用作练习来尝试了解其工作原理,其定义如下: 标题: class
1) this 指针与其他指针有何不同?据我了解,指针指向堆中的内存。如果有指向它们的指针,这是否意味着对象总是在堆中构造? 2)我们可以在 move 构造函数或 move 赋值中窃取this指针吗?
这个问题在这里已经有了答案: 关闭 11 年前。 Possible Duplicate: C : pointer to struct in the struct definition 在我的初学者类
我有两个指向指针的结构指针 typedef struct Square { ... ... }Square; Square **s1; //Representing 2D array of say,
变量在内存中是如何定位的?我有这个代码 int w=1; int x=1; int y=1; int z=1; int main(int argc, char** argv) { printf
#include #include main() { char *q[]={"black","white","red"}; printf("%s",*q+3); getch()
我在“C”类中有以下函数 class C { template void Func1(int x); template void Func2(int x); }; template void
我是一名优秀的程序员,十分优秀!