Skip to content

QEMU User Mode 二进制翻译系统原理分析及使用方法

Avimitin edited this page May 26, 2022 · 17 revisions

QEMU

QEMU是一个具有跨平台的特性、可执行硬件虚拟化的开源托管虚拟机,可通过纯软件方式实现硬件的虚拟化,模拟外部硬件,为用户提供抽象、虚拟的硬件环境。

QEMU User Mode

QEMU既可实现全系统硬件虚拟化,也可在User Mode下通过为每个容器提供特定的命名空间实现容器化设计。在 User Mode 下,QEMU不会模拟所有硬件,而是通过内核代码的TCG (Tiny Code Generator)模块对异构应用的二进制代码进行翻译和转换。

Tiny Code Generator (微码生成器)

微码生成器(TCG)的作用就是翻译模拟的处理器指令流(被模拟处理器),然后通过TCG后端转换为主机指令流(执行QEMU的处理器)。

翻译机制结构图

TCG 定义了一系列IR (Intermediate Representation),将已经翻译的代码块放在转换缓存中,并通过跳转指令将源处理器的指令集和目标处理器的指令集链接在一起。当Hypervisor执行代码时,存放于转换缓存中的链接指令可以跳转到指定的代码块,目标二进制代码可不断调用已翻译代码块来运行,直到需要翻译新块为止。在执行的过程中,如果遇到了需要翻译的代码块,执行会暂停并跳回到Hypervisor,Hypervisor使用和协调TCG对需要进行二进制翻译的源处理器指令集进行转换和翻译并存储到转换缓存中。

  • 如果想移植QEMU到新架构,需要重点关注后端。
  • 如果想模拟一个新的处理器,需要重点关注前端。(比如添加一个新的RISC-V处理器) 源代码树中有许多文档可以帮助你了解这些模块是如何一起工作的

微码生成器

tcg/README · master · QEMU / QEMU

动态翻译后端

docs/devel/tcg.rst · master · QEMU / QEMU

解码器

StackOverflow 关于TCG 动态翻译的讨论

StackOverflow answer

binfmt_misc

Kernel Support for miscellaneous Binary Formats (binfmt_misc) binfmt_miscLinux内核的一种功能,它允许识别任意可执行文件格式,并将其传递给特定的用户空间应用程序,如模拟器和虚拟机。QEMU将注册的异构二进制程序拦截、转换成本地指令架构代码,同时按需从目标架构系统调用转换成宿主机架构系统调用,并将其转发至宿主机内核。通过 binfmt_misc 识别可执行文件格式并传递至 QEMU。这个内核特性允许你在 shell 中输入程序名称来执行几乎任何架构的程序(有些限制,后面会讲)。这包括例如编译的Java(TM)、PythonEmacs程序。要实现这一点,你需要告诉binfmt_misc 用哪个二进制文件调用哪个解释器。binfmt_misc通过将文件开头的一些字节与您提供的 Magic byte (屏蔽指定的位)匹配来识别二进制类型。binfmt_misc还可以识别文件名扩展名,比如 .com 或者 exe 。 如果要使用这个功能的话,首先要绑定binfmt_misc,可以通过以下命令来绑定:

mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc

这样绑定的话,系统重新启动之后就失效了。如果想让系统每次启动的时候都自动绑定的话,可以往 /etc/fstab 文件中加入下面这行:

none  /proc/sys/fs/binfmt_misc binfmt_misc defaults 0 0

绑定完之后,就可以通过向/proc/sys/fs/binfmt_misc/register(这个文件只能写不能读)文件中写入一行匹配规则字符串来告诉内核什么样的程序要用什么样的程序打开(一般使用echo命令)。这行字符串的格式如下:

:name:type:offset:magic:mask:interpreter:flags

每个字段都用冒号“:”分割。某些字段拥有默认值,或者只在前面字段被设置成了某个特定值后才有效,因此可以跳过某些字段的设置,但是必须保留相应的冒号分割符。各个字段的意义如下:

name

这个规则的名字,理论上可以取任何名字,只要不重名就可以了。但是为了方便以后维护一般都取一个有意义的名字,比如表示被打开文件特性的名字,或者要打开这个文件的程序的名字等;

type

表示如何匹配被打开的文件,只可以使用“E”或者“M”,只能选其一,两者不可共用。“E”代表只根据待打开文件的扩展名来识别,而“M”表示只根据待打开文件特定位置的几位魔数(Magic Byte)来识别;

offset

这个字段只对前面type字段设置成“M”之后才有效,它表示从文件的多少偏移开始查找要匹配的魔数。如果跳过这个字断不设置的话,默认就是0;

magic

它表示真正要匹配的魔数,如果type字段设置成“M”的话;或者表示文件的扩展名,如果type字段设置成“E”的话。对于匹配魔数来说,如果要匹配的魔数是ASCII码可见字符,可以直接输入,而如果是不可见的话,可以输入其16进制数值,前面加上“\x”或者“\x”(如果在Shell环境中的话。对于匹配文件扩展名来说,就在这里写上文件的扩展名,但不要包括扩展名前面的点号(“.”),且这个扩展名是大小写敏感的,有些特殊的字符,例如目录分隔符正斜杠(“/”)是不允许输入的;

mask

同样,这个字段只对前面type字段设置成“M”之后才有效。它表示要匹配哪些位,它的长度要和magic字段魔数的长度一致。如果某一位为1,表示这一位必须要与magic对应的位匹配;如果对应的位为0,表示忽略对这一位的匹配,取什么值都可以。如果是0xff的话,即表示全部位都要匹配,默认情况下,如果不设置这个字段的话,表示要与magic全部匹配(即等效于所有都设置成0xff)。还有同样对于NUL来说,要使用转义(\x00),否则对这行字符串的解释将到NUL停止,后面的不再起作用; interpreter 表示要用哪个程序来启动这个类型的文件,一定要使用全路径名,不要使用相对路径名;

flags

可选字段,控制解释器的行为 ,可选字段。每个参数控制一个行为。支持以下标志:

P- preserve-argv[0]

保留 argv[0] 参数,使用文件的完整路径覆盖原来的argv[0],比较常用的是‘P’(请注意,一定要大写),表示保留原始的argv[0]参数。这是什么意思呢?默认情况下,如果不设置这个标志的话,binfmt_misc 会将传给interpreter的第一个参数,即argv[0],修改成要被打开文件的全路径名。当设置了P之后,binfmt_misc会保留原来的argv[0],在原来的argv[0]和argv[1]之间插入一个参数,用来存放要被打开文件的全路径名。比如,如果想用程序/bin/foo来打开/usr/local/bin/blah这个文件,如果不设置P的话,传给程序/bin/foo的参数列表argv[]["/usr/local/bin/blah", "blah"] 而如果设置了‘P’之后,程序/bin/foo得到的参数列表是 ["/bin/foo", "/usr/local/bin/blah", "blah"]

O- open-binary

将二进制文件的完整路径传递给解释器作为参数。 当包含此标志时,binfmt_misc将打开文件以进行读取并将其描述符作为参数,而不是完整路径,从而允许解释器执行不可读取的二进制文件。 应小心使用此功能 - 不可信的解释器不会发出不可读取的二进制文件的内容。

C- credentials

Currently, the behavior of binfmt_misc is to calculate the credentials and security token of the new process according to the interpreter. When this flag is included, these attributes are calculated according to the binary. It also implies theOflag. 应该小心使用该特性,因为当使用binfmt_misc运行由root拥有的setuid二进制文件时,解释器将以root权限运行。

F- fix binary

当一个misc格式的文件启动时,binfmt_misc 默认以被动的形式来执行二进制文件。但是在使用 namespace changeroots 时并不好用,所以,F模式可以在安装模拟器之后以最快的方式启动并且使用已经在运行的镜像,也就是说,只要你安装了,就一直可用,不管你的环境如何变化。

限制:

  • 整个寄存器字符串不能超过1920个字符
  • 魔数(一般文件头特征) 必须驻留在文件的前128字节,即offset+size(magic)必须小于128
  • 解释器字符串不能超过127个字符

每次成功写入一行规则,都会在/proc/sys/fs/binfmt_misc/目录下,创建一个名字为输入的匹配规则字符串中name字段的文件。通过读取这个文件的内容,可以知道这条匹配规则当前的状态: cat /proc/sys/fs/binfmt_misc/<name> 而通过向这个文件中写入0或1,可以关闭或打开这条匹配规则,而写入-1表示彻底删除这条规则:

echo 0 > /proc/sys/fs/binfmt_misc/<name>    # Disable the match
echo 1 > /proc/sys/fs/binfmt_misc/<name>    # Enable the match
echo -1 > /proc/sys/fs/binfmt_misc/<name>   # Delete the match

不过前提是必须要切换到root用户才能有权限写入。在 /proc/sys/fs/binfmt_misc/ 目录下,还存在一个 status 文件,通过它可以查看和控制整个 binfmt_misc 的状态,而不光是单个匹配规则。可以查看当前binfmt_misc是否处于打开状态: cat /proc/sys/fs/binfmt_misc/status 也可以通过向它写入1或0来打开或关闭binfmt_misc:

echo 0 > /proc/sys/fs/binfmt_misc/status    # Disable binfmt_misc
echo 1 > /proc/sys/fs/binfmt_misc/status    # Enable binfmt_misc

如果想删除当前binfmt_misc中的所有匹配规则,可以向其传入-1: echo -1 > /proc/sys/fs/binfmt_misc/status # Disable all matches 这有几个官方给出的非常棒的例子来帮助你学习添加一个新的格式 一些例子 (假设你在 /proc/sys/fs/binfmt_misc 目录下): 启用 em86 支持 (like binfmt_em86, for Alpha AXP only):

echo ':i386:M::\x7fELF\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03:\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfb\xff\xff:/bin/em86:' > register 
echo ':i486:M::\x7fELF\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06:\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfb\xff\xff:/bin/em86:' > register

启用 DOS 应用 支持 (pre-configured dosemu hdimages):

echo ':DEXE:M::\x0eDEX::/usr/bin/dosexec:' > register

启用 wine执行 Windows exe 应用:

echo ':DOSWin:M::MZ::/usr/local/bin/wine:' > register

java 的支持可以看这里

Java(tm) Binary Kernel Support for Linux v1.03 Java(tm)

当前 qemu user 的一些几乎无法维护的问题

现在我们遇到好几个 qemu 挂死的版本,我们维护了好几个版本

  1. 首先是这个 always-malloc,我们手打了个 glib,稍微缓解了一些 https://github.com/ZenithalHourlyRate/glib
  2. 然后 qemu 在 futex pi 这个 syscall 上翻译不行,这在某些 glibc 版本下会导致 pthread 挂(影响所有 qemu-user 版本,不只 riscv),然后粗糙搓了一个 https://github.com/ZenithalHourlyRate/qemu
  3. 我们还发现一些情况下进程会 Z 掉(打 rust 的时候),我们猜测和这个 issue 有关 https://gitlab.com/qemu-project/qemu/-/issues/140 ,但看起来非常 non-trivial 不好修,称其为 vfork 吧
  4. 在 always-malloc 修了以后,我们依然会遇到和 1 一样的 futex hang 的问题(打 go 的时候),我们还没调查咋回事
Clone this wiki locally