首页
社区
课程
招聘
[原创]QEMU逃逸初探
发表于: 2021-1-22 00:05 20267

[原创]QEMU逃逸初探

2021-1-22 00:05
20267

一直想入门一下虚拟化安全这一块,前一阵一直在研究内核方向的内容,正好这几天也换一换脑子。

本文目标:

1.对 QEMU虚拟机 结构有一个初步的介绍及认识。

2.初识 QOM(Qemu Object Model) 对象模型。

3.通过分析 Blizzard CTF 2017 练习qemu调试等内容。

4.实现 qemu逃逸 弹个 gnome-calculator。

题目:Blizzard CTF 2017

strng虚拟机的源码如下:

https://github.com/rcvalle/blizzardctf2017/blob/master/strng.c

在虚拟机中不论是kvm还是qemu有几个关键的地址。

GVA:guest virtual address(虚拟机中的虚拟地址)

GPA:guest physical address(虚拟机中的物理地址)

HVA:host virtual address(宿主机中的虚拟地址)

HPA: host physical address(宿主机中的物理地址)

这里跟kvm一样。HVA对应GPA。

整体地址的话:从GVA -> GPA -> HVA -> HPA 这样的转换。

而GPA实际是由宿主机进程mmap出来的空间。

而在qemu-kvm架构下,QEMU充当kvm的前端,传递IO。kvm负责做内存以及CPU的虚拟化。

我们使用如下脚本启动 strng 虚拟机。

注意最后一行将22端口映射到宿主机的5555端口。所以我们可以通过:

登录虚拟机。

VM escape - QEMU Case Study 中给出了一个 mmu.c 用来做地址转换,其代码如下:

官方文档:https://www.kernel.org/doc/Documentation/vm/pagemap.txt

这个mmu.c是要跑在我们虚拟机里的。

用户名:ubuntu,密码:passw0rd。

编译mmu.c:

然后将其scp到虚拟机里:

可以看到已经有了如下内容

然后sudo运行:

接下来使用qemu attach上去:

首先找出qemu的进程pid,然后attach:

之后我们找到这里:

可以看到对应的就是我们这个字符串的地址:

0x7fc0a7e00000 就是我们虚拟机的物理地址为0的位置。

通过源码我们可以看到,strng主要实现了:strng_pmio_xxxx 以及 strng_mmio_xxxx 也就是mmio和pmio这两个东西。

简单来说就是IO设备与内存共享同样的地址空间,以此来进行交互。

内存和I/O设备有各自的地址空间,不共享内存(被隔离)。而通过 in、out、outw,、outl 等指令实现交互。

PCI 是 Peripheral Component Interconnect 的缩写。

Q:什么是PCI设备?

A:符合 PCI 总线标准的设备就被称为 PCI 设备,PCI 总线架构中可以包含多个 PCI 设备。图中的 Audio、LAN 都是一个 PCI 设备。PCI 设备同时也分为主设备和目标设备两种,主设备是一次访问操作的发起者,而目标设备则是被访问者。

而在我们的strng虚拟机中:

lspci查看我们的pci设备。再看启动命令,发现有一行:

实际这就是告诉我们,strng是以虚拟机中的一个 pci设备 存在的。

通过阅读源码,我们也可发现确实有一个函数负责pci设备(strng)的注册:pci_strng_register_types()

而在lspci的内容中我们发现了:

这样一行unclassfied的设备。他的设备 id是0x11e9,猜测这个就是strng。

根据 https://man7.org/linux/man-pages/man8/lspci.8.html

lspci输出的内容对应的格式如下:[domain:]bus:device.function

lspci -t -v 可以树状显示。

那么 strng 的总线对应的就是 00:03.0 ,域为[0000]

通过 lspci -v

我们可以查看显示所有设备的详细信息。

我们可以得到如下两个有用的信息:

接下来,我们可以通过 ls -la /sys/devices/pci0000\:00/0000\:00\:03.0/ 查看其对应的 resource 文件。

resource0 对应MMIO空间。

resource1 对应PMIO空间。

从左至右依次是:起始地址、结束地址、标志位。

第一行是MMIO、第二行是PMIO。

虽然是第一次接触qemu-pwn,不过对于著名的QOM模型早已经有所耳闻,于是就跟着QOM模型梳理一下strng的源码,加深一下理解。

Everything in QOM is a device

QOM是QEMU 在C的基础上自己实现的一套面向对象机制,负责将device、bus等设备都抽象成为对象。

下面给出一些重要的QOM Terminology(术语)

来源:https://www.linux-kvm.org/images/f/f6/2012-forum-QOM_CPU.pdf

• Type(类型) ‒ Defines class

• Class(类) ‒ Stores static data & virtual method pointers ‒ Lazily initialized from type and, optionally, static data(存储静态数据以及一些函数指针)

• Object(对象) ‒ Stores dynamic data (“chunk of allocated memory”) (储存动态数据)

• Property(属性) ‒ Accessor to dynamic object data ‒ Inspectable via monitor interface(访问对象中的动态数据,通过interface可以进行监控)

在QOM中有几个重要的结构体

https://github.com/qemu/qemu/blob/master/include/qom/object.h

类比到 strng 源码:

可以看到,在 pci_strng_register_types 中首先定义了 TypeInfo strng_info 并对其中一些内容进行赋值。

在代码最后部分,首先调用 type_init() 负责注册。

最终调用了 register_module_init()

主要做了如下事情:

而需要注意的是,由于 __attribute__((constructor)) 的存在,这一部分内容作为 构造函数 都将在main函数之前调用。

具体的规则可以看:

C Language Constructors and Destructors with GCC

总结一下:

在QEMU真正启动之前,所有 module_entry 都被插入 init_type_list 。并且已经确定了初始化函数。

而接下来,在qemu启动阶段main函数调用的时候,会对 init_type_list[Module_Init_Type] 链表中的每一个 ModuleEntry 调用其 init 函数。所以对于我们的strng里就是 pci_strng_register_types()

pci_strng_register_types() 中首先对设备的一些信息做了初始化,接着调用了:type_register_static(&strng_info)

定义如下:

type_register_static(&strng_info) 最终调用了 type_register_internal(&strng_info)

至此,这个 ModuleEntryTypeImpl表 中的注册就算完成了。

ObjectClass是所有class的基类,定义如下:

在前面几步中,实际上我们只完成了 Module的注册,而没有真正完成 类的初始化

接下来我们通过 TypeInfo strng_info 中的 .class_init = strng_class_init 简单看一下 Type(类)的初始化

在初始化每一个 type 的时候调用的是: type_initialize 函数。

给出对应的注释后的源码:

注意,此处的我们说的类实际是 类对象,对应基类ObjectClass

Object是 类实例对象,是所有 类实例对象的基类

在我们初始化了对应的 ObjectClass 后,接下来初始化 Object

Object的创建由 k->realize = pci_testdev_realize; 函数实现,不同于 type 和 class 的构造,Object根据需要创建的。

而一个Object对应着一个我们指定的具体的device

比如:

这里的strng。

我们以strng为例,用户可以定义Type的自己的Object结构体,继承自PCIDevice

其初始化由:pci_strng_realize(PCIDevice *pdev, Error **errp) 负责。

其中的 STRNGState 定义如下:

至此 QOM最基本的四个type相关的结构体就介绍完了。

初始化顺序可以认为是:TypeInfo -> TypeImpl -> ObjectClass -> Object(自选)

qemu使用 MemoryRegion 来表示对应的内存空间,相应的每一个 MemoryRegion 都有对应的 MemoryRegionOps 来描述其操做。

比如在strng中举这样一对儿例子:

有点类似于内核中的 file ops。

出了前四个结构体,还有 属性 之类的结构体等等,不过跟我们这里准备看的关系不大,就不赘述了。有兴趣的可以自行了解。

Property 作为属性模板,用于创建属性对象。

同时,属性又分为静态属性与动态属性。

直接把qemu拉到ida里逆向。

然后去搜索strng。

首先将函数中的 opaque参数 进行 Set Ivar typeSTRNGState *,注意不是转成 struct *,此时的 STRNGState 已经是typedef后的一个type了。

然后再去structures里找。

在IDA中的 STRNGState 如下:

mmio读的操作,如果size=4,并且addr是4的倍数。

那么取 STRNGState 中regs区域中2addr 的内容返回。即 (&regs + 2*addr)返回。

首先 Choice = addr* 1/4

已知我们的 MMIO空间为0到255,总共256大小。

我们先打开对应的 /sys/devices/pci0000\:00/0000\:00\:03.0/resource0 ,然后将其以 MAP_SHARED 映射。那么我们可以通过操作这一块映射空间来读写操作mmio空间。

接下来只要addr越界即可。

但是实际调试的过程中,我发现根本就写不上去。

If you are thinking, “oh we have out-of-bound access here because addr is not checked anywhere”, you would be wrong. The PCI device internally checks if the addr you are trying to access is within the boundaries of the MMIO thus only 256 bytes.

PCI设备在内部会检查这个地址是否越界,所以这里的任意写其实无法利用。

这里可以看到:

如果我们可以控制 opaque->addr 那么就相当于控制了下标,可以进行任意读了。

刚刚的pmio_read我们得出结论,如果能控制 opaque->addr ,那么就可以做到任意读取。

(1)而在pmio_write中 当addr为0时,可以直接控制 opaque->addr = val ,也就是说此时我们的 任意读 已经有了。

(2)而当我们能控制 opaque->addr 后,相当于我们可以控制v5,v5 = (opaque->addr>>2),进而如果v5不等于3,可以通过v5来做任意写

(3)当v5等于3时,会对 opaque->rand_r 产生一个调用,第一个参数为 opaque->regs[2]的地址 ,如果我们将regs[2]处的内容写成某个命令。然后任意读rand_r地址,计算libc,任意写 为system,那么就可以实现 任意命令执行

总结一下思路:

(1)首先将字符串 “gnome-calculator” 通过MMIO_write写到regs[2]的位置。

(2)通过控制 opaque->addr = val 进行任意读取,读srand的地址,计算libc。

(3)通过任意写改写 rand_r 函数的地址为system,然后执行 (opaque->rand_r)(&opaque->regs[2], 4LL, val); 弹计算器。

对于pmio,我们使用<sys/io.h>中的outl,intl进行读写。

而此时我们的addr就相当于读写的端口port。

如果需要打断点,我们直接 b strng_pmio_read 即可

最终效果

VM escape - QEMU Case Study

Linux用户空间将虚拟地址转化为物理地址

浅谈内存映射I/O(MMIO)与端口映射I/O(PMIO)的区别

浅谈 Linux 内核开发之 PCI 设备驱动

QEMU学习笔记——QOM(Qemu Object Model)

QEMU中KVM的初始化调用路径

KVM Log 1-QEMU Object Module (QOM)

https://github.com/grandemk/qemu_devices/blob/master/hello_tic.c

 
 
 
 
 
 
 
 
 
 
 
 
 
                        Guest' processes
                     +--------------------+
Virtual addr space   |                    |
                     +--------------------+                                    (GVA)
                     |                    |
                     \__   Page Table     \__
                        \                    \
                         |                    |  Guest kernel
                    +----+--------------------+----------------+
Guest's phy. memory |    |                    |                |            (GPA)
                    +----+--------------------+----------------+
                    |                                          |
                    \__                                        \__
                       \                                          \
                        |             QEMU process                 |
                   +----+------------------------------------------+
Virtual addr space |    |                                          |         (HVA)
                   +----+------------------------------------------+
                   |                                               |
                    \__                Page Table                   \__
                       \                                               \
                        |                                               |
                   +----+-----------------------------------------------++
Physical memory    |    |                                               ||    (HPA)
                   +----+-----------------------------------------------++
                        Guest' processes
                     +--------------------+
Virtual addr space   |                    |
                     +--------------------+                                    (GVA)
                     |                    |
                     \__   Page Table     \__
                        \                    \
                         |                    |  Guest kernel
                    +----+--------------------+----------------+
Guest's phy. memory |    |                    |                |            (GPA)
                    +----+--------------------+----------------+
                    |                                          |
                    \__                                        \__
                       \                                          \
                        |             QEMU process                 |
                   +----+------------------------------------------+
Virtual addr space |    |                                          |         (HVA)
                   +----+------------------------------------------+
                   |                                               |
                    \__                Page Table                   \__
                       \                                               \
                        |                                               |
                   +----+-----------------------------------------------++
Physical memory    |    |                                               ||    (HPA)
                   +----+-----------------------------------------------++
 
 
 
 
./qemu-system-x86_64 \
    -m 1G \
    -device strng \
    -hda my-disk.img \
    -hdb my-seed.img \
    -nographic \
    -L pc-bios/ \
    -enable-kvm \
    -device e1000,netdev=net0 \
    -netdev user,id=net0,hostfwd=tcp::5555-:22
./qemu-system-x86_64 \
    -m 1G \
    -device strng \
    -hda my-disk.img \
    -hdb my-seed.img \
    -nographic \
    -L pc-bios/ \
    -enable-kvm \
    -device e1000,netdev=net0 \
    -netdev user,id=net0,hostfwd=tcp::5555-:22
ssh ubuntu@127.0.0.1 -p 5555
ssh ubuntu@127.0.0.1 -p 5555
 
---[ mmu.c ]---
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <inttypes.h>
 
#define PAGE_SHIFT  12
#define PAGE_SIZE   (1 << PAGE_SHIFT)    //4096
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN     ((1ull << 55) - 1)
 
int fd;
 
uint32_t page_offset(uint32_t addr)
{
    return addr & ((1 << PAGE_SHIFT) - 1);
}
 
uint64_t gva_to_gfn(void *addr)
{
    uint64_t pme, gfn;
    size_t offset;
    offset = ((uintptr_t)addr >> 9) & ~7;
    lseek(fd, offset, SEEK_SET);
    read(fd, &pme, 8);
    if (!(pme & PFN_PRESENT))
        return -1;
    gfn = pme & PFN_PFN;
    return gfn;
}
 
uint64_t gva_to_gpa(void *addr)
{
    uint64_t gfn = gva_to_gfn(addr);//这里的addr属于gva,获取对应的page frame number
    assert(gfn != -1);
    return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);//返回对应的GPA
}
 
int main()
{
    uint8_t *ptr;
    uint64_t ptr_mem;
 
    fd = open("/proc/self/pagemap", O_RDONLY);    //通过读取pagemap文件,可以得到进程从虚拟地址到物理地址映射的信息
    if (fd < 0) {
        perror("open");
        exit(1);
    }
 
    ptr = malloc(256);            //首先开256的空间
    strcpy(ptr, "Where am I?"); //将字符串拷贝过去
    printf("%s\n", ptr);       
    ptr_mem = gva_to_gpa(ptr);    //guest virtual mem -> guest physicals mem
 
    //此时的ptr_mem对应的是虚拟机里字符串所在的物理内存地址。
    printf("Your physical address is at 0x%"PRIx64"\n", ptr_mem);
 
    getchar();
    return 0;
}
---[ mmu.c ]---
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <inttypes.h>
 
#define PAGE_SHIFT  12
#define PAGE_SIZE   (1 << PAGE_SHIFT)    //4096
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN     ((1ull << 55) - 1)
 
int fd;
 
uint32_t page_offset(uint32_t addr)
{
    return addr & ((1 << PAGE_SHIFT) - 1);
}
 
uint64_t gva_to_gfn(void *addr)
{
    uint64_t pme, gfn;
    size_t offset;
    offset = ((uintptr_t)addr >> 9) & ~7;
    lseek(fd, offset, SEEK_SET);
    read(fd, &pme, 8);
    if (!(pme & PFN_PRESENT))
        return -1;
    gfn = pme & PFN_PFN;
    return gfn;
}
 
uint64_t gva_to_gpa(void *addr)
{
    uint64_t gfn = gva_to_gfn(addr);//这里的addr属于gva,获取对应的page frame number
    assert(gfn != -1);
    return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);//返回对应的GPA
}
 
int main()
{
    uint8_t *ptr;
    uint64_t ptr_mem;
 
    fd = open("/proc/self/pagemap", O_RDONLY);    //通过读取pagemap文件,可以得到进程从虚拟地址到物理地址映射的信息
    if (fd < 0) {
        perror("open");
        exit(1);
    }
 
    ptr = malloc(256);            //首先开256的空间
    strcpy(ptr, "Where am I?"); //将字符串拷贝过去
    printf("%s\n", ptr);       
    ptr_mem = gva_to_gpa(ptr);    //guest virtual mem -> guest physicals mem
 
    //此时的ptr_mem对应的是虚拟机里字符串所在的物理内存地址。
    printf("Your physical address is at 0x%"PRIx64"\n", ptr_mem);
 
    getchar();
    return 0;
}
 
 
 
root@ubuntu:~/strng# gcc mmu.c -static -m32 -o mmu
root@ubuntu:~/strng# gcc mmu.c -static -m32 -o mmu
root@ubuntu:~/strng# scp -P5555 mmu ubuntu@127.0.0.1:/home/ubuntu
The authenticity of host '[127.0.0.1]:5555 ([127.0.0.1]:5555)' can't be established.
ECDSA key fingerprint is SHA256:zoKoYVjPeMIA7zfSHVgA4+Q9bMN1IExTtD0rxTU7cLg.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[127.0.0.1]:5555' (ECDSA) to the list of known hosts.
ubuntu@127.0.0.1's password:
mmu
root@ubuntu:~/strng# scp -P5555 mmu ubuntu@127.0.0.1:/home/ubuntu
The authenticity of host '[127.0.0.1]:5555 ([127.0.0.1]:5555)' can't be established.
ECDSA key fingerprint is SHA256:zoKoYVjPeMIA7zfSHVgA4+Q9bMN1IExTtD0rxTU7cLg.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[127.0.0.1]:5555' (ECDSA) to the list of known hosts.
ubuntu@127.0.0.1's password:
mmu
ubuntu@ubuntu:~$ ls
mmu
ubuntu@ubuntu:~$ ls
mmu
ubuntu@ubuntu:~$ sudo ./mmu
sudo: unable to resolve host ubuntu
Where am I?
Your physical address is at 0x30bda008
ubuntu@ubuntu:~$ sudo ./mmu
sudo: unable to resolve host ubuntu
Where am I?
Your physical address is at 0x30bda008
root@ubuntu:~/strng# ps -ef | grep qemu
root      20265  20264  0 Jan18 pts/2    00:03:36 ./qemu-system-x86_64 -m 1G -device strng -hda my-disk.img -hdb my-seed.img -nographic -L pc-bios/ -enable-kvm -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22
root@ubuntu:~/strng# ps -ef | grep qemu
root      20265  20264  0 Jan18 pts/2    00:03:36 ./qemu-system-x86_64 -m 1G -device strng -hda my-disk.img -hdb my-seed.img -nographic -L pc-bios/ -enable-kvm -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22
 
 
 
 
 
 
 
 
 
 
ubuntu@ubuntu:~$ lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)    //主板芯片
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 03)
00:02.0 VGA compatible controller: Device 1234:1111 (rev 02)                    //显卡
00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10)
00:04.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 03)//网卡
ubuntu@ubuntu:~$ lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)    //主板芯片
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 03)
00:02.0 VGA compatible controller: Device 1234:1111 (rev 02)                    //显卡
00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10)
00:04.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 03)//网卡
-device strng \
-device strng \
 
 
00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10)
00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10)
 
 
 
ubuntu@ubuntu:~$ lspci -t -v
-[0000:00]-+-00.0  Intel Corporation 440FX - 82441FX PMC [Natoma]
           +-01.0  Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
           +-01.1  Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
           +-01.3  Intel Corporation 82371AB/EB/MB PIIX4 ACPI
           +-02.0  Device 1234:1111
           +-03.0  Device 1234:11e9
           \-04.0  Intel Corporation 82540EM Gigabit Ethernet Controller
ubuntu@ubuntu:~$ lspci -t -v
-[0000:00]-+-00.0  Intel Corporation 440FX - 82441FX PMC [Natoma]
           +-01.0  Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
           +-01.1  Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
           +-01.3  Intel Corporation 82371AB/EB/MB PIIX4 ACPI
           +-02.0  Device 1234:1111
           +-03.0  Device 1234:11e9
           \-04.0  Intel Corporation 82540EM Gigabit Ethernet Controller
 
ubuntu@ubuntu:~$ lspci -v
 
00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10)
    Subsystem: Red Hat, Inc Device 1100
    Physical Slot: 3
    Flags: fast devsel
    Memory at febf1000 (32-bit, non-prefetchable) [size=256]    //这里给出了mmio的地址空间0xfebf1000
    I/O ports at c050 [size=8]                                    //pmio端口为0xc050
ubuntu@ubuntu:~$ lspci -v
 
00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10)
    Subsystem: Red Hat, Inc Device 1100
    Physical Slot: 3
    Flags: fast devsel
    Memory at febf1000 (32-bit, non-prefetchable) [size=256]    //这里给出了mmio的地址空间0xfebf1000
    I/O ports at c050 [size=8]                                    //pmio端口为0xc050
 
ubuntu@ubuntu:~$ ls -la /sys/devices/pci0000\:00/0000\:00\:03.0/
 
-r--r--r--  1 root root 4096 Jan 19 11:51 resource
-rw-------  1 root root  256 Jan 19 14:36 resource0
-rw-------  1 root root    8 Jan 19 14:36 resource1
ubuntu@ubuntu:~$ ls -la /sys/devices/pci0000\:00/0000\:00\:03.0/
 
-r--r--r--  1 root root 4096 Jan 19 11:51 resource
-rw-------  1 root root  256 Jan 19 14:36 resource0
-rw-------  1 root root    8 Jan 19 14:36 resource1
 
ubuntu@ubuntu:~$ cat /sys/devices/pci0000\:00/0000\:00\:03.0/resource
 
/*   start addr           end addr                flags      */
0x00000000febf1000 0x00000000febf10ff 0x0000000000040200    (MMIO)
0x000000000000c050 0x000000000000c057 0x0000000000040101    (PMIO)
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
ubuntu@ubuntu:~$ cat /sys/devices/pci0000\:00/0000\:00\:03.0/resource
 
/*   start addr           end addr                flags      */
0x00000000febf1000 0x00000000febf10ff 0x0000000000040200    (MMIO)
0x000000000000c050 0x000000000000c057 0x0000000000040101    (PMIO)
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
 
 
 
 
 
 
 
 
 
struct TypeInfo
{       
    const char *name;                       
    const char *parent;                        //父类
 
    size_t instance_size;
    size_t instance_align;
    void (*instance_init)(Object *obj);        //实例初始化函数
    void (*instance_post_init)(Object *obj);
    void (*instance_finalize)(Object *obj);    //实例“析构”函数
 
    bool abstract;                            //是否为抽象类
    size_t class_size;                        //类大小,官方文档中提出.class_size字段设置为sizeof(MyClass)
 
    void (*class_init)(ObjectClass *klass, void *data);    //在所有父类被初始化完成后调用的子类初始化函数,可用于override virtual methods
    void (*class_base_init)(ObjectClass *klass, void *data);
    void *class_data;
 
    InterfaceInfo *interfaces;                //封装了const char *type;
};
struct TypeInfo
{       
    const char *name;                       
    const char *parent;                        //父类
 
    size_t instance_size;
    size_t instance_align;
    void (*instance_init)(Object *obj);        //实例初始化函数
    void (*instance_post_init)(Object *obj);
    void (*instance_finalize)(Object *obj);    //实例“析构”函数
 
    bool abstract;                            //是否为抽象类
    size_t class_size;                        //类大小,官方文档中提出.class_size字段设置为sizeof(MyClass)
 
    void (*class_init)(ObjectClass *klass, void *data);    //在所有父类被初始化完成后调用的子类初始化函数,可用于override virtual methods
    void (*class_base_init)(ObjectClass *klass, void *data);
    void *class_data;
 
    InterfaceInfo *interfaces;                //封装了const char *type;
};
static void pci_strng_register_types(void)
{
    static const TypeInfo strng_info = {    //这里就是一个TypeInfo结构体
        .name          = "strng",
        .parent        = TYPE_PCI_DEVICE,    //父类为 TYPE_PCI_DEVICE
        .instance_size = sizeof(STRNGState),
        .instance_init = strng_instance_init,
        .class_init    = strng_class_init,    //类初始化,虚函数覆写
    };
 
    type_register_static(&strng_info);
}
type_init(pci_strng_register_types)
static void pci_strng_register_types(void)
{
    static const TypeInfo strng_info = {    //这里就是一个TypeInfo结构体
        .name          = "strng",
        .parent        = TYPE_PCI_DEVICE,    //父类为 TYPE_PCI_DEVICE
        .instance_size = sizeof(STRNGState),
        .instance_init = strng_instance_init,
        .class_init    = strng_class_init,    //类初始化,虚函数覆写
    };
 
    type_register_static(&strng_info);
}
type_init(pci_strng_register_types)
 
#define type_init(function) module_init(function, MODULE_INIT_QOM)
 
#define module_init(function, type)                                        \
static void __attribute__((constructor)) do_qemu_init_ ## function(void)
{                                                                            \
    register_module_init(function,type);                                   \
}
#define type_init(function) module_init(function, MODULE_INIT_QOM)
 
#define module_init(function, type)                                        \
static void __attribute__((constructor)) do_qemu_init_ ## function(void)
{                                                                            \
    register_module_init(function,type);                                   \
}
typedef struct ModuleEntry
{
    void (*init)(void);
    QTAILQ_ENTRY(ModuleEntry) node;
    module_init_type type;
} ModuleEntry;
 
static void init_lists(void)
{
    static int inited;
    int i;
 
    if (inited) {
        return;
    }
 
    for (i = 0; i < MODULE_INIT_MAX; i++) {
        QTAILQ_INIT(&init_type_list[i]);
    }
 
    QTAILQ_INIT(&dso_init_list);
 
    inited = 1;
}
 
 
static ModuleTypeList *find_type(module_init_type type)
{
    init_lists();
 
    return &init_type_list[type];
}
 
void register_module_init(void (*fn)(void), module_init_type type)
{
    ModuleEntry *e;
    ModuleTypeList *l;
 
    e = g_malloc0(sizeof(*e));
    e->init = fn;
    e->type = type;
 
    l = find_type(type);        //调用init_lists()做了初始化
 
    QTAILQ_INSERT_TAIL(l, e, node);
}
typedef struct ModuleEntry
{
    void (*init)(void);
    QTAILQ_ENTRY(ModuleEntry) node;
    module_init_type type;
} ModuleEntry;
 
static void init_lists(void)
{
    static int inited;
    int i;
 
    if (inited) {
        return;
    }
 
    for (i = 0; i < MODULE_INIT_MAX; i++) {
        QTAILQ_INIT(&init_type_list[i]);
    }
 
    QTAILQ_INIT(&dso_init_list);
 
    inited = 1;
}
 
 
static ModuleTypeList *find_type(module_init_type type)
{
    init_lists();
 
    return &init_type_list[type];
}
 
void register_module_init(void (*fn)(void), module_init_type type)
{
    ModuleEntry *e;
    ModuleTypeList *l;
 
    e = g_malloc0(sizeof(*e));
    e->init = fn;
    e->type = type;
 
    l = find_type(type);        //调用init_lists()做了初始化
 
    QTAILQ_INSERT_TAIL(l, e, node);
}
 
 
 
 
 
 
struct TypeImpl
{
    const char *name;
 
    size_t class_size;
 
    size_t instance_size;
    size_t instance_align;
 
    void (*class_init)(ObjectClass *klass, void *data);
    void (*class_base_init)(ObjectClass *klass, void *data);
 
    void *class_data;
 
    void (*instance_init)(Object *obj);
    void (*instance_post_init)(Object *obj);
    void (*instance_finalize)(Object *obj);
 
    bool abstract;
 
    const char *parent;
    TypeImpl *parent_type;
 
    ObjectClass *class;                        //指向 ObjectClass 的指针
 
    int num_interfaces;
    InterfaceImpl interfaces[MAX_INTERFACES];
};
struct TypeImpl
{
    const char *name;
 
    size_t class_size;
 
    size_t instance_size;
    size_t instance_align;
 
    void (*class_init)(ObjectClass *klass, void *data);
    void (*class_base_init)(ObjectClass *klass, void *data);
 
    void *class_data;
 
    void (*instance_init)(Object *obj);
    void (*instance_post_init)(Object *obj);
    void (*instance_finalize)(Object *obj);
 
    bool abstract;
 
    const char *parent;
    TypeImpl *parent_type;
 
    ObjectClass *class;                        //指向 ObjectClass 的指针
 
    int num_interfaces;
    InterfaceImpl interfaces[MAX_INTERFACES];
};
static TypeImpl *type_register_internal(const TypeInfo *info)
{
    TypeImpl *ti;
    ti = type_new(info); //根据info信息,创建一个TypeImpl对象
 
    type_table_add(ti); //将新建的TypeImpl对象注册到全局哈希表 type_table 中
    return ti;
}
static TypeImpl *type_register_internal(const TypeInfo *info)
{
    TypeImpl *ti;
    ti = type_new(info); //根据info信息,创建一个TypeImpl对象
 
    type_table_add(ti); //将新建的TypeImpl对象注册到全局哈希表 type_table 中
    return ti;
}
struct ObjectClass
{
    /* private: */
    Type type;
    GSList *interfaces;
 
    const char *object_cast_cache[OBJECT_CLASS_CAST_CACHE];
    const char *class_cast_cache[OBJECT_CLASS_CAST_CACHE];
 
    ObjectUnparent *unparent;
 
    GHashTable *properties;
};
struct ObjectClass
{
    /* private: */
    Type type;
    GSList *interfaces;
 
    const char *object_cast_cache[OBJECT_CLASS_CAST_CACHE];
    const char *class_cast_cache[OBJECT_CLASS_CAST_CACHE];
 
    ObjectUnparent *unparent;
 
    GHashTable *properties;
};
 
 
 
static void type_initialize(TypeImpl *ti)    //针对每一个注册的 TypeImpl 去做type 初始化
{
    TypeImpl *parent;
 
    //在 TypeImpl 中有 ObjectClass *class   
    if (ti->class) {        //如果 ti->class存在,那么说明初始化过了,直接return
        return;
    }
 
    ti->class_size = type_class_get_size(ti);
    ti->instance_size = type_object_get_size(ti);
 
    if (ti->instance_size == 0) {    //如果instance_size为0,标记为抽象类
        ti->abstract = true;
    }
    if (type_is_ancestor(ti, type_interface)) {
         ......
    }
    ti->class = g_malloc0(ti->class_size);
 
    parent = type_get_parent(ti);  // 是否有父类?即是否存在继承关系?
    if (parent) {                   // 如果存在继承关系的话
        type_initialize(parent);   //初始化父类
        GSList *e;
        int i;
 
        g_assert(parent->class_size <= ti->class_size);
        g_assert(parent->instance_size <= ti->instance_size);
        memcpy(ti->class, parent->class, parent->class_size);//首先用父类数据 初始化
        ti->class->interfaces = NULL;
 
        for (e = parent->class->interfaces; e; e = e->next) {
            InterfaceClass *iface = e->data;
            ObjectClass *klass = OBJECT_CLASS(iface);
 
            type_initialize_interface(ti, iface->interface_type, klass->type);//初始化对应接口
        }
 
        for (i = 0; i < ti->num_interfaces; i++) {
            TypeImpl *t = type_get_by_name(ti->interfaces[i].typename);
            if (!t) {
                error_report("missing interface '%s' for object '%s'",
                             ti->interfaces[i].typename, parent->name);
                abort();
            }
            for (e = ti->class->interfaces; e; e = e->next) {
                TypeImpl *target_type = OBJECT_CLASS(e->data)->type;
 
                if (type_is_ancestor(target_type, t)) {
                    break;
                }
            }
 
            if (e) {
                continue;
            }
 
            type_initialize_interface(ti, t, t);    //初始化接口
        }
    }
 
    /* 建立存放属性的hash*/
    ti->class->properties = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
                                                  object_property_free);
 
    ti->class->type = ti;
 
    while (parent) {
        /* 对于父类来说,如果定义了class_base_init,就调用它 */
        if (parent->class_base_init) {
            parent->class_base_init(ti->class, ti->class_data);
        }
        parent = type_get_parent(parent);
    }
 
    /* 调用当前TypeImpl的class_init */
    if (ti->class_init) {
        ti->class_init(ti->class, ti->class_data);
    }
}
static void type_initialize(TypeImpl *ti)    //针对每一个注册的 TypeImpl 去做type 初始化
{

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-1-22 00:19 被Roland_编辑 ,原因:
收藏
免费 10
支持
分享
最新回复 (13)
雪    币: 50
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
666
2021-1-22 06:13
0
雪    币: 14517
活跃值: (17538)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2021-1-22 08:40
0
雪    币: 7048
活跃值: (3527)
能力值: ( LV12,RANK:340 )
在线值:
发帖
回帖
粉丝
4
牛逼, 感谢分享~~~~
2021-1-23 16:34
0
雪    币: 3738
活跃值: (3872)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2021-1-23 16:56
0
雪    币: 213
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
感谢分享,大牛就是666
2021-1-23 23:52
0
雪    币: 355
活跃值: (435)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
感谢分享
2021-1-24 10:12
0
雪    币: 8715
活跃值: (8619)
能力值: ( LV13,RANK:570 )
在线值:
发帖
回帖
粉丝
8
想请教个问题..."而 0x7fc0a7e00000 就是我们虚拟机的物理地址为0的位置。"这个地址是怎么算出来的?
2021-3-19 14:32
0
雪    币: 3840
活跃值: (1920)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
9
r0Cat 想请教个问题..."而 0x7fc0a7e00000 就是我们虚拟机的物理地址为0的位置。"这个地址是怎么算出来的?
不是算的, 那个地址空间其实就是虚拟机的物理内存空间, 同时也是qemu进程的虚拟地址空间. 物理内存都是从0开始的, 所以, 在qemu进程中, 这个虚拟地址就代表虚拟机的物理地址
2021-4-9 11:08
0
雪    币: 8715
活跃值: (8619)
能力值: ( LV13,RANK:570 )
在线值:
发帖
回帖
粉丝
10
音货得福 不是算的, 那个地址空间其实就是虚拟机的物理内存空间, 同时也是qemu进程的虚拟地址空间. 物理内存都是从0开始的, 所以, 在qemu进程中, 这个虚拟地址就代表虚拟机的物理地址
意思是他是个固定值,不像64位系统一样这个基址老变是吧了解了,多谢
2021-4-9 12:28
0
雪    币: 3840
活跃值: (1920)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
11
r0Cat 意思是他是个固定值,不像64位系统一样这个基址老变是吧了解了,多谢
我想你可能不太明白虚拟机物理内存空间的地址和进程的虚拟空间地址? 你注意该地址对应的那张图片, 该地址空间为1G, 虚拟机给的物理内存也是1G, 所以它就是虚拟机的物理内存空间, 而虚拟机的物理内存空间其实就是qemu进程的某段虚拟内存空间. 如同图中那个地址起始. 如果下一次qemu启动, 进程空间地址随机化变了, 只要找到这个大小为1G的空间, 就可以确认是虚拟机的物理内存空间.
2021-4-14 15:08
1
雪    币: 8715
活跃值: (8619)
能力值: ( LV13,RANK:570 )
在线值:
发帖
回帖
粉丝
12
音货得福 我想你可能不太明白虚拟机物理内存空间的地址和进程的虚拟空间地址? 你注意该地址对应的那张图片, 该地址空间为1G, 虚拟机给的物理内存也是1G, 所以它就是虚拟机的物理内存空间, 而虚拟机的物理内存空 ...
哦,我明白了 感谢大佬耐心讲解 
2021-4-14 16:12
0
雪    币: 207
活跃值: (445)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
r0Cat 想请教个问题..."而 0x7fc0a7e00000 就是我们虚拟机的物理地址为0的位置。"这个地址是怎么算出来的?
也可以通过在 gdb 中 search 那个字符串,Where am i,gdb 会找到一些存在这些字符串的地址,可以减去左侧的相对偏移,再根据空间大小也可以得出虚拟机的物理内存空间在qemu进程中所对应的起始地址。通过这两个关键信息就可以确认了。
2021-5-21 13:22
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14

 就是通过内存块大小来定位的吗?

 qemu进程的1G虚拟内存对应

 guest os的1G物理内存是吧?


2021-7-3 09:47
0
游客
登录 | 注册 方可回帖
返回
//