当前位置: 首页 > news >正文

Linux: qemu-user-static 是如何工作的?

文章目录

  • 1. 前言
  • 2. qemu-user-static 的工作原理

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. qemu-user-static 的工作原理

我们通常使用交叉编译的方式来构建 ARM 程序,譬如有一个 hello.c 程序,其代码如下:

// hello.c

#include <stdio.h>

int main(void)
{
	printf("Hello, World!\n");
	return 0;
}

x86 系统下将其交叉编译为 aarch64 程序:

$ aarch64-linux-gnu-gcc -o hello hello.c
$ file hello
hello: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=c4367fc3135655c88a487baf4a4ac0f04bcff318, for GNU/Linux 3.7.0, not stripped

试着运行:

$ ./hello
bash: ./hello: 无法执行二进制文件: 可执行文件格式错误

安装 qemu-user-static 后再运行:

$ sudo apt-get install qemu-user-static
$ ./hello
aarch64-binfmt-P: Could not open '/lib/ld-linux-aarch64.so.1': No such file or directory

看到变化了吗?虽然仍然不能运行,但是错误提示不一样了:安装 qemu-user-static 之前,提示的是 可执行文件格式错误,即系统不支持 aarch64 格式的二进制文件执行;安装 qemu-user-static 之后,系统已经支持aarch64 格式的二进制文件执行,只是提示在 x86 系统中找不到动态链接库 /lib/ld-linux-aarch64.so.1 。这是显然的,ld-linux-aarch64.so.1aarch64 架构的动态链接解释器程序,默认 x86 系统下是不存在 /lib/ld-linux-aarch64.so.1 文件的。既然是找不到这个动态链接库,有两个办法解决,第一个是放置一个这个文件,第二个是静态链接,我们采用第二种:

$ aarch64-linux-gnu-gcc -static -o hello hello.c
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=7bb03bb527641bda2d2ee1f0a1a5e9e02d932c5f, for GNU/Linux 3.7.0, not stripped
$ ./hello
Hello, World!

程序居然能执行了!这是什么情况?是不是有点颠覆认知?原来一切的秘密,来自于 Linux 内核的 binfmt_misc 模块功能,也就是允许用户自定义可执行二进制文件格式和其翻译执行程序(如 qemu-user-static),然后通过翻译执行。如我们的例子中,就是这样一个工作过程:

                          qemu-user-static
hello (aarch64 程序) ---------------------------> 在 x86 上执行
                     翻译 aarch64 指令为 x86 指令 

Linux 内核 binfmt_misc 模块提供了一个用户空间接口:

$ sudo mount | grep "binfmt"
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=29,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=18043)
binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,nosuid,nodev,noexec,relatime)

目录 /proc/sys/fs/binfmt_misc 在安装 qemu-user-static 之前,如下:

$ ls /proc/sys/fs/binfmt_misc/ -l
total 0
--w------- 1 root root 0 318 13:40 register
-rw-r--r-- 1 root root 0 318 13:40 status

在安装 qemu-user-static 之后,如下:

$ ls /proc/sys/fs/binfmt_misc
llvm-14-runtime.binfmt  qemu-aarch64  qemu-cris     qemu-microblaze  qemu-mipsel     qemu-ppc64    qemu-s390x  qemu-sparc32plus  register
llvm-18-runtime.binfmt  qemu-alpha    qemu-hexagon  qemu-mips        qemu-mipsn32    qemu-ppc64le  qemu-sh4    qemu-sparc64      status
python2.7               qemu-arm      qemu-hppa     qemu-mips64      qemu-mipsn32el  qemu-riscv32  qemu-sh4eb  qemu-xtensa
python3.10              qemu-armeb    qemu-m68k     qemu-mips64el    qemu-ppc        qemu-riscv64  qemu-sparc  qemu-xtensaeb

可以看到,除了之前的 registerstatus 文件外,多了很多其它程序。但即使还不理解这里的细节,我想读者也能猜测到,安装 qemu-user-static 后,可以支持在 x86 系统下通过 qemu-user-static 运行 alpha,arm,riscv,...... 等等平台的程序了。

那这一切是怎么做到的呢?我们来看下 binfmt_misc 的内核源码。首先,模块注册了 binfmt_misc 类型的文件系统:

/* fs/binfmt_misc.c */

static struct file_system_type bm_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "binfmt_misc",
	.mount		= bm_mount,
	.kill_sb	= kill_litter_super,
};
MODULE_ALIAS_FS("binfmt_misc");

static int __init init_misc_binfmt(void)
{
	int err = register_filesystem(&bm_fs_type);
	...
	return err;
}

然后在将 binfmt_misc 文件系统挂载到 /proc/sys/fs 下时,建立了文件节点 registerstatus

/* fs/binfmt_misc.c */

// bm_mount() -> ... -> bm_fill_super()

static const struct file_operations bm_status_operations = {
	.read		= bm_status_read,
	.write		= bm_status_write,
	.llseek		= default_llseek,
};

/* Superblock handling */

static const struct super_operations s_ops = {
	.statfs		= simple_statfs,
	.evict_inode	= bm_evict_inode,
};

static int bm_fill_super(struct super_block *sb, void *data, int silent)
{
	int err;
	static const struct tree_descr bm_files[] = {
		[2] = {"status", &bm_status_operations, S_IWUSR|S_IRUGO},
		[3] = {"register", &bm_register_operations, S_IWUSR},
		/* last one */ {""}
	};

	err = simple_fill_super(sb, BINFMTFS_MAGIC, bm_files);
	if (!err)
		sb->s_op = &s_ops;
	return err;
}

static struct dentry *bm_mount(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data)
{
	return mount_single(fs_type, flags, data, bm_fill_super);
}

于是用户空间看到了 /proc/sys/fs/binfmt_misc/{register,status} 两个节点。status可读写的,用来控制开启和关闭 binfmt_misc 模块功能;register 是只写的,用来注册用户自定义的可执行二进制格式和翻译执行程序,如:

echo ':qemu-aarch64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00:\xff\xff\xff\xff\xff\xff\xff\xfc\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-aarch64-static:OC' | sudo tee /proc/sys/fs/binfmt_misc/register

这里是注册 aarch64 的解释执行程序为 /usr/bin/qemu-aarch64-static 。这个细节在函数 bm_register_write() 中:

/* fs/binfmt_misc.c */

static LIST_HEAD(entries); /* 自定义可执行二进制文件格式处理接口信息列表 */

/* /register */

static ssize_t bm_register_write(struct file *file, const char __user *buffer,
			       size_t count, loff_t *ppos)
{
	Node *e;
	struct inode *inode;
	...

	/*
	 * 设定翻译解释器程序等信息,如:
	 * /usr/libexec/qemu-binfmt/aarch64-binfmt-P
	 */
	e = create_entry(buffer, count);

	...

	inode = bm_get_inode(sb, S_IFREG | 0644);

	...

	/* 一种新的格式对应一个文件节点,如 /proc/sys/fs/binfmt_misc/qemu-aarch64 */
	e->dentry = dget(dentry);
	inode->i_private = e;
	inode->i_fop = &bm_entry_operations;

	d_instantiate(dentry, inode);
	write_lock(&entries_lock);
	list_add(&e->list, &entries);
	write_unlock(&entries_lock);

	...
}

看一下注册的 qemu-aarch64 类型注册信息:

$ cat /proc/sys/fs/binfmt_misc/qemu-aarch64
enabled
interpreter /usr/libexec/qemu-binfmt/aarch64-binfmt-P
flags: POCF
offset 0
magic 7f454c460201010000000000000000000200b700
mask ffffffffffffff00fffffffffffffffffeffffff

好了,现在注册了自定义可执行二进制格式的翻译解释程序,那是怎么被触发的呢?看一下代码细节。首先向系统注册 misc_format 二进制格式:

/* fs/binfmt_misc.c */

static struct linux_binfmt misc_format = {
	.module = THIS_MODULE,
	.load_binary = load_misc_binary,
};

static int __init init_misc_binfmt(void)
{
	int err = register_filesystem(&bm_fs_type);
	if (!err)
		insert_binfmt(&misc_format); /* 注册 misc_format */
	return err;
}
/* include/linux/binfmts.h */

/* Same as above, but adds a new binfmt at the top of the list */
static inline void insert_binfmt(struct linux_binfmt *fmt)
{
	__register_binfmt(fmt, 1);
}
/* fs/exec.c */

static LIST_HEAD(formats);
static DEFINE_RWLOCK(binfmt_lock);

void __register_binfmt(struct linux_binfmt * fmt, int insert)
{
	BUG_ON(!fmt);
	if (WARN_ON(!fmt->load_binary))
		return;
	write_lock(&binfmt_lock);
	insert ? list_add(&fmt->lh, &formats) :
		 list_add_tail(&fmt->lh, &formats);
	write_unlock(&binfmt_lock);
}

然后在执行二进制格式时,如果不匹配到任何其它已指格式和架构的时候,在执行 misc_format 中注册的、匹配的某种自定义格式的翻译执行程序:

do_execveat_common()
	exec_binprm()
		search_binary_handler()

int search_binary_handler(struct linux_binprm *bprm)
{
	...
	retry:
	read_lock(&binfmt_lock);
	list_for_each_entry(fmt, &formats, lh) {
		...
		retval = fmt->load_binary(bprm); /* load_misc_binary() */
		...
	}
	...
}
/* fs/binfmt_misc.c */

static int load_misc_binary(struct linux_binprm *bprm)
{
	Node *fmt;
	struct file *interp_file = NULL;
	int retval;
	int fd_binary = -1;

	...

	/* to keep locking time low, we copy the interpreter string */
	read_lock(&entries_lock);
	fmt = check_file(bprm); /* 找到匹配的格式 */
	if (fmt)
		dget(fmt->dentry);
	read_unlock(&entries_lock);
	
	...

	if (fmt->flags & MISC_FMT_OPEN_FILE) {
		interp_file = filp_clone_open(fmt->interp_file);
		if (!IS_ERR(interp_file))
			deny_write_access(interp_file);
	} else {
		interp_file = open_exec(fmt->interpreter);
	}

	/* 重置解释器程序,如更新为 /usr/libexec/qemu-binfmt/aarch64-binfmt-P */
	bprm->file = interp_file;
	...

	/* 用新的解释器程序 (如 /usr/libexec/qemu-binfmt/aarch64-binfmt-P) 来执行程序 */
	retval = search_binary_handler(bprm);
	...
}

到此,整个 qemu-user-static 的主干工作流程已经分析完毕。

相关文章:

  • 初探自定义注意力机制:DAttention的设计与实现
  • 力扣128. 最长连续序列 || 452. 用最少数量的箭引爆气球
  • 如何打造安全稳定的亚马逊采购测评自养号下单系统?
  • 【微知】ip命令如何查看路由表?如何查看IPv6的路由表?(ip r s、ip -6 r s)
  • 【Netty】SimpleChannelInboundHandler如何根据数据类型处理消息
  • 区块链 智能合约安全 | 整型溢出漏洞
  • 对于memset(b, 1, sizeof b)赋值为16843009情况
  • Ansys 2024 R1 安装出现错误码-8544解决方法
  • SPACE_GAME
  • Qt-搭建开发环境
  • 【新能源汽车“心脏”赋能:三电系统研发、测试与应用匹配的恒压恒流源技术秘籍】
  • TF中 Arg 节点
  • 【canvas】一键自动布局:如何让流程图节点自动找到最佳位置
  • 【错误解决】ollama使用huggingface拉取模型异常
  • 第七章-PHP字符串操作
  • 精准git动图拆解​
  • 【NTP系列】chrony同步原理
  • java版鸿鹄招采系统源码 招投标系统源码 供应商招投标平台源码
  • 使用Mybatis 连接数据库 项目示例
  • 图解LLM智能体(LLM Agents):构建与运作机制的全面解析
  • 阚吉林任重庆市民政局党组书记,原任市委组织部主持日常工作的副部长
  • 碧桂园:砸锅卖铁保交房、持续推进保主体,尽快让公司恢复正常经营
  • 长江画派创始人之一、美术家鲁慕迅逝世,享年98岁
  • 美英达成贸易协议,美股集体收涨
  • 谜语的强制力:弗洛伊德与俄狄浦斯
  • 这个五月,有三部纪录电影值得一看