xen hypercall 的应用层实现

一句话描述: xen hypercall 在应用层的实现,最终都变成对
 /proc/xen/privcmd 的 ioctl 系统调用

我们知道,xen 在应用层最上层的接口是 libxl , 基本上所以应用程序对xen的操作都通过 libxl 提供的API实现。 这里我们也从 libxl
入口探讨 hypercall 的实现,主要涉及的是 libxl context 初始化部分。所有的xl 调用,如 xl create/ xl list/ xl
destroy 都会创建一个上下文环境,这个上下文环境对所有 Xl 进程都是一致的(所以才能叫 context 嘛),什么意思呢 ? 比如你在一台设备上调用多次
Xl  create 启动多台虚拟机,那么这多个xl 进程共享一个context , 这究竟是怎么实现的
?一般,跨进程的context实现,就是在应用层维护一个基于共享内存的控制结构,当然要辅助各种同步机制,libxl
的上下文没有这么复杂,因为它本质上不是在应用层维护的,而是在domain0的内核维护的,为什么这么说呢? 看下面xl 初始化代码:


libxl/xl.c
int main(int argc, char **argv)
{
。。。。。。。
xl_ctx_alloc(); // xl 的main函数,初始化context
。。。。
}

libxl/libxl.c

int libxl_ctx_alloc(libxl_ctx **pctx, int version,
unsigned flags, xentoollog_logger * lg)
{
libxl_ctx *ctx = NULL;
struct stat stat_buf;
int rc;

if (version != LIBXL_VERSION) { rc = ERROR_VERSION; goto out; }

ctx = malloc(sizeof(*ctx));
memset(ctx, 0, sizeof(libxl_ctx));
ctx->lg = lg;

/* First initialise pointers etc. (cannot fail) */

ctx->nogc_gc.alloc_maxsize = -1;
ctx->nogc_gc.owner = ctx;

LIBXL_TAILQ_INIT(&ctx->occurred);

ctx->osevent_hooks = 0;

LIBXL_LIST_INIT(&ctx->pollers_event);
LIBXL_LIST_INIT(&ctx->pollers_idle);

LIBXL_LIST_INIT(&ctx->efds);
LIBXL_TAILQ_INIT(&ctx->etimes);

ctx->watch_slots = 0;
LIBXL_SLIST_INIT(&ctx->watch_freeslots);
libxl__ev_fd_init(&ctx->watch_efd);

LIBXL_TAILQ_INIT(&ctx->death_list);
libxl__ev_xswatch_init(&ctx->death_watch);

ctx->childproc_hooks = &libxl__childproc_default_hooks;
ctx->childproc_user = 0;

ctx->sigchld_selfpipe[0] = -1;

/* The mutex is special because we can‘t idempotently destroy it */

if (libxl__init_recursive_mutex(ctx, &ctx->lock) < 0) {
LIBXL__LOG(ctx, LIBXL__LOG_ERROR, "Failed to initialize mutex");
free(ctx);
ctx = 0;
}
rc = libxl__atfork_init(ctx);
if (rc) goto out;

rc = libxl__poller_init(ctx, &ctx->poller_app);
if (rc) goto out;

if ( stat(XENSTORE_PID_FILE, &stat_buf) != 0 ) {
LIBXL__LOG_ERRNO(ctx, LIBXL__LOG_ERROR, "Is xenstore daemon running?\n"
"failed to stat %s", XENSTORE_PID_FILE);
rc = ERROR_FAIL; goto out;
}

ctx->xch = xc_interface_open(lg,lg,0); // 获取 xc 控制接口
if (!ctx->xch) {
LIBXL__LOG_ERRNOVAL(ctx, LIBXL__LOG_ERROR, errno,
"cannot open libxc handle");
rc = ERROR_FAIL; goto out;
}

ctx->xsh = xs_daemon_open(); // 获取 xs 控制接口
if (!ctx->xsh)
ctx->xsh = xs_domain_open();
if (!ctx->xsh) {
LIBXL__LOG_ERRNOVAL(ctx, LIBXL__LOG_ERROR, errno,
"cannot connect to xenstore");
rc = ERROR_FAIL; goto out;
}

*pctx = ctx;
return 0;
}

可以看到, xl 的main函数里会调用 xl_ctx_alloc, 后者的具体实现在 libxl_ctx_alloc 函数里,这个函数除了初始化一堆
list 和 tailq 之外,有实质性的调用是  xc_interface_open 和 xs_daemon_open , 即获取对 xc 接口和
xs 接口的控制体,而这两种控制结构的获取最终都是通过打开文件系统获取某个fd来实现,
我们知道,文件系统是kernel维护的,所以说,libxl
的上下文环境更确切说是在 domain0 的 kernel 内部维护的。其中 ,xc 接口主要对应各种hypercall , xs 接口主要对应
xenstore 共享内存机制和事件机制。这里先不管xenstore机制,因为xen hypercall主要涉及的是xc接口的实现。

xc_interface_open的具体实现在 xc_interface_open_common:


// libxc/xc_private.c
static struct xc_interface_core *xc_interface_open_common(xentoollog_logger *logger,
xentoollog_logger *dombuild_logger,
unsigned open_flags,
enum xc_osdep_type type)
{
。。。。。
xch = malloc(sizeof(*xch));
if (!xch) {
xch = &xch_buf;
PERROR("Could not allocate new xc_interface struct");
goto err;
}
*xch = xch_buf;

if (!(open_flags & XC_OPENFLAG_DUMMY)) {
if ( xc_osdep_get_info(xch, &xch->osdep) < 0 ) // 打开动态库文件并获取符号地址
goto err;

xch->ops = xch->osdep.init(xch, type);// 调用xc控制器的初始化函数,获取真正的xc 控制结构
if ( xch->ops == NULL )
{
DPRINTF("OSDEP: interface %d (%s) not supported on this platform",
type, xc_osdep_type_name(type));
goto err_put_iface;
}

xch->ops_handle = xch->ops->open(xch);// 调用 xc 控制结构的open函数获取 ioctl 作用的 fd 文件描述符
if (xch->ops_handle == XC_OSDEP_OPEN_ERROR)
goto err_put_iface;
}

return xch;
}


static int xc_osdep_get_info(xc_interface *xch, xc_osdep_info_t *info)
{
int rc = -1;
const char *lib = getenv(XENCTRL_OSDEP);// 获取环境变量的值,该值执行 libxc 动态链接库文件位置
xc_osdep_info_t *pinfo;
void *dl_handle = NULL;

if ( lib != NULL )
{
if ( getuid() != geteuid() )
{
if ( xch ) ERROR("cannot use %s=%s with setuid application", XENCTRL_OSDEP, lib);
abort();
}
if ( getgid() != getegid() )
{
if ( xch ) ERROR("cannot use %s=%s with setgid application", XENCTRL_OSDEP, lib);
abort();
}

dl_handle = dlopen(lib, RTLD_LAZY|RTLD_LOCAL);// dlopen动态加载 libxl 动态库文件
if ( !dl_handle )
{
if ( xch ) ERROR("unable to open osdep library %s: %s", lib, dlerror());
goto out;
}

pinfo = dlsym(dl_handle, "xc_osdep_info");// dlsym 获取符号 xc_osdep_info
if ( !pinfo )
{
if ( xch ) ERROR("unable to find xc_osinteface_info in %s: %s", lib, dlerror());
goto out;
}

*info = *pinfo;
info->dl_handle = dl_handle;
}
}

我们看到,xc_interface_open_common的实现是打开环境变量 XENCTRL_OSDEP
指向的动态库文件,并dlsym获取里边的 xc_osdep_info 符号,然后调用符号地址的init函数,并将返回值赋值给 xch->ops ,最后调用
xch->ops->open函数,并将返回值赋值给 xch->ops_handle .

下面分析这段代码:

首先要理解,xc 接口主要用于实现 hypercall , 而系统调用要依赖于具体的domain0操作系统,如 linux / bsd
/ solaris 等多种OS
对应的实现是有所不同的,所以domain0为不同OS的情况下,返回的xc接口其内部实现必定不同,这种情况下,一般的做法是不同OS封装自己的实现,然后在xc_interface_open_common函数里判断OS类型并根据类型返回具体的实现,这是一种运行时做判断的方法,libxc
库没有这么做,它实际上是在编译前就做了判断,其实现如下:


首先,OS相关的代码被抽离出来,作为单独的文件,如xc_linux_osdep.c, xc_solaris.c, xc_netbsd.c ,其他文件是通用的代码,

其次, 在 configure 的时候检测具体的OS类型,然后编译对应的osdep代码,不匹配的osdep代码根本不编译

最后,不管哪种OS,最终libxc 库都编译为动态库 : libxenctrl.so.4.2.0,并置 XENCTRL_OSDEP 环境变量指向得到的动态库。

所以就看到了前面的实现:直接动态加载XENCTRL_OSDEP环境变量指向的动态库,并获取符号 "xc_osdep_info" 符号, 下面以
domain0 是 linux 系统为例,则获取的动态库符号对应的地址里的内容就是:

xc_osdep_info_t xc_osdep_info = { // linux 系统的 xc 接口
.name = "Linux Native OS interface",
.init = &linux_osdep_init,
.fake = 0,
};

则 xc_interface_open_common 函数调用的 init 实际调用的是 linux_osdep_init 函数,该函数实现如下:


libxc/xc_linux_osdep.c

static struct xc_osdep_ops *linux_osdep_init(xc_interface *xch, enum xc_osdep_type type)
{
switch ( type )
{
case XC_OSDEP_PRIVCMD:
return &linux_privcmd_ops; // hypercall 通过这个结构体实现
case XC_OSDEP_EVTCHN:
return &linux_evtchn_ops;
case XC_OSDEP_GNTTAB:
return &linux_gnttab_ops;
case XC_OSDEP_GNTSHR:
return &linux_gntshr_ops;
default:
return NULL;
}
}

可以看到,init函数主要是根据type类型返回一个控制结构体,如果是hypercall类型(对应XC_OSDEP_PRIVCMD),则返回的是 linux_privcmd_ops
这个结构体的指针,这个结构体的内容如下:


static struct xc_osdep_ops linux_privcmd_ops = {
.open = &linux_privcmd_open, // 这里主要干的是:fd = open("/proc/xen/privcmd", O_RDWR);
.close = &linux_privcmd_close,

.u.privcmd = {
.alloc_hypercall_buffer = &linux_privcmd_alloc_hypercall_buffer,
.free_hypercall_buffer = &linux_privcmd_free_hypercall_buffer,

.hypercall = &linux_privcmd_hypercall, // hypercall 调用
.map_foreign_batch = &linux_privcmd_map_foreign_batch,
.map_foreign_bulk = &linux_privcmd_map_foreign_bulk,
.map_foreign_range = &linux_privcmd_map_foreign_range,
.map_foreign_ranges = &linux_privcmd_map_foreign_ranges,
},
};

其中, .u.privcmd.hypercall 函数就是Xen hypercall 在应用层的实现,linux os dep 的实现如下:


static int linux_privcmd_hypercall(xc_interface *xch, xc_osdep_handle h, privcmd_hypercall_t *hypercall)
{
int fd = (int)h;
return ioctl(fd, IOCTL_PRIVCMD_HYPERCALL, hypercall); // 变成 ioctl 系统调用
}

可以看到,最终是转换为对参数 h 这个Fd 的 ioctl 调用,那么这个h是怎么来的?

回到 xc_interface_open_common函数的实现, 其获取动态库符号后调用的init函数其实是 linux_osdep_init 函数,则保留在 ctx->xch->ops 变量上的是 linux_privcmd_ops 结构体,随即调用 ctx->xch->ops_handle = xch->ops->open(xch) 其实就是调用 linux_privcmd_open 函数,该函数主要代码是 fd = open("/proc/xen/privcmd", O_RDWR); 所以,保留在 ctx->xch->ops_handle上的其实是 /proc/xen/privcmd 打开后的文件描述符。这样,后续进程其他地方需要调用hypercall,则通过 ctx->xch 结构体上的 ops 和 ops_handle , 就可以将各种 hypercall 调用变成对 /proc/xen/privcmd 文件描述符的 ioctl 调用
 

xen hypercall 的应用层实现,布布扣,bubuko.com

时间: 2025-01-16 19:56:01

xen hypercall 的应用层实现的相关文章

基于Xen实现一种domain0和domainU的应用层数据交互高效机制 - 2

继续昨天的思路,今天先google了类似的实现domain0和domainU之间数据传输的方案 [Xen-devel] XenStore as a data transfer path?  这篇帖子讨论了xenstore作为domain0和domainU传递自定义数据的可行性,在Xen架构里,xenstore用于domain0和多个domainU之间传递控制数据,根据Xenstore的文档,不适合用于传输过大的数据,它的设计目标是对domain0的应用层有非常好的可视性和可操作性(tdb格式数据

基于Xen实现一种domain0和domainU的应用层数据交互高效机制 - 3

继续 上一篇 的研究,结合 xen4.2.3 的代码分析,发现 xen4.2.3 的应用层工具库 tools 包含一个工具叫 libvchan ,其头文件描述如下: * This is a library for inter-domain communication. A standard Xen ring 34 * buffer is used, with a datagram-based interface built on top. The grant 35 * reference and

基于Xen实现一种domain0和domainU的应用层数据交互高效机制

项目里有一个需求,domain0的应用层需要定时给domainU(hvm windows)的应用层传递一块数据,原来的方案是在domainU的应用层架设一个http服务器,监听在某个端口,然后需要塞数据时,domain0的应用程序连接该端口,并通过http send发送数据.发送完会等待domainU 的应用程序返回一个标记. 无意间看到这篇论文<全虚拟化HVM和半虚拟化PV虚拟平台通信机制分析>,里边介绍hvm情况下domainU与Doamin0用户层的数据交互机制,根据文章介绍,尝试设计一

xen save/restore 过程

以下分析基于 xen4.2.3, 虚拟机都是hvm模式 使用libxl库有两种方式启动一个虚拟机,一种是 xl create xx.conf , 这种方式从一个配置文件开始启动一个虚拟机,速度相对较慢.另一种是xl restore checkpointfile , 这种从一个checkpoint文件启动(恢复)虚拟机,速度非常快. checkpointfile 可以认为是一个虚拟机快照,保存了虚拟机某一时刻的内存和设备状态,这里的'某一时刻'其实就是执行保存快照  xl save domainn

虚拟化技术之虚拟化技术介绍及Xen的应用实现

虚拟化技术是什么: 在计算机中,虚拟化(英语:Virtualization)是一种资源管理技术,是将计算机的各种实体资源,如服务器.网络.内存及存储等,予以抽象.转换后呈现出来,打破实体结构间的不可切割的障碍,使用户可以比原本的组态更好的方式来应用这些资源.这些资源的新虚拟部份是不受现有资源的架设方式,地域或物理组态所限制.一般所指的虚拟化资源包括计算能力和资料存储.--转自百度百科 为什么需要虚拟化:  虚拟化技术在近几年来非常的火热, 实际上在上个世纪60年代, 就已经有了虚拟化的实现.由于

ubuntu下安装使用vmware、kvm、xen

一. 概念介绍: (1)全虚拟化(Full Virtulization) 简介:主要是在客户操作系统和硬件之间捕捉和处理那些对虚拟化敏感的特权指令,使客户操作系统无需修改就能运行, 速度会根据不同的实现而不同,但大致能满足用户的需求.这种方式是业界现今最成熟和最常见的,而且属于 Hosted 模式和 Hypervisor 模式的都有,知名的产品有IBM CP/CMS,VirtualBox,KVM,VMware Workstation和VMware ESX(它在其4.0版,被改名为VMware v

[转]Adventures in Xen exploitation

Source:https://www.nccgroup.com/en/blog/2015/02/adventures-in-xen-exploitation/ ? tl;dr This post is about my experience trying to exploit the Xen SYSRET bug (CVE-2012-0217). This issue was patched in June 2012 and was disclosed in Xen Security Advis

云计算之虚拟化技术详解—Xen虚拟化实战

一.虚拟化的概述 虚拟化,是指通过虚拟化技术将一台计算机虚拟为多台逻辑计算机.在一台计算机上同时运行多个逻辑计算机,计算元件在虚拟的基础上而不是真实的基础上运行,是一个为了简化管理,优化资源的解决方案.如同空旷.通透的写字楼,整个楼层没有固定的墙壁,用户可以用同样的成本构建出更加自主适用的办公空间,进而节省成本,发挥空间最大利用率.这种把有限的固定的资源根据不同需求进行重新规划以达到最大利用率的思路,在IT领域就叫做虚拟化技术. 虚拟化技术可以扩大硬件的容量,简化软件的重新配置过程.CPU的虚拟

XEN虚拟化简介及XEN在CentOS 6.5上的安装

根据之前KVM虚拟化的整理,虚拟化技术分类如下: 虚拟化技术的分类: (1) 模拟:Emulation Qemu, PearPC, Bochs (2) 完全虚拟化:Full Virtualization, Native Virtualization HVM VMware Workstation, VirtualBox, VMware Server, Parallels Desktop, KVM, Xen (3) 半虚拟化:ParaVirtualization GuestOS:知晓自己是运行Vir