CTF Wiki
  • 简介
  • 如何使用 CTF Wiki
  • introduction
    • CTF 历史
    • CTF 竞赛模式简介
    • CTF 竞赛内容
    • 线下攻防经验小结
    • CGC 超级挑战赛
    • 学习资源
  • misc
    • 杂项简介
    • 取证隐写前置技术
    • 信息搜集技术
    • encode
      • 通信领域常用编码
      • 计算机相关的编码
      • 现实世界中常用的编码
    • picture
      • 图片分析简介
      • JPG
      • PNG
      • GIF
    • audio
      • 音频隐写
    • archive
      • RAR 格式
      • ZIP 格式
    • traffic
      • 流量包分析简介
      • 协议分析概述
        • Wireshark
        • DNS
        • HTTP
        • HTTPS
        • FTP
        • USB
        • WIFI
      • 数据提取
      • PCAP 文件修复
    • disk-memory
      • 磁盘内存分析
      • 题目
    • shellcode
    • other
      • pyc
  • web
    • Web 简介
    • XSS
    • php
      • PHP 代码审计
    • SQL 注入
      • sqlmap绕过脚本
      • 各版本数据库语句备忘
    • CSRF
    • SSRF
  • reverse
    • 软件逆向工程简介
    • identify-encode-encryption
      • 常见加密算法和编码识别
    • language
      • 简介
      • go
        • Golang 逆向入门
      • python
        • Python 逆向入门
      • rust
        • Rust 逆向入门
    • maze
      • 迷宫问题
    • obfuscate
      • 控制流平坦化
      • 花指令
      • movofuscator
      • Self-Modified Code
    • vm
      • 虚拟机分析
    • platform
      • linux
        • Detecting Breakpoints
        • Detecting debugging
        • False Disassembly
        • LD_PRELOAD
      • windows
        • anti-debug
          • CheckRemoteDebuggerPresent
          • 反调试技术例题
          • Heap Flags
          • Interrupt 3
          • IsDebuggerPresent
          • 花指令
          • NtGlobalFlag
          • NtQueryInformationProcess
          • The Heap
          • Thread Local Storage(TLS)
          • ZwSetInformationThread
        • unpack
          • 一步到达 OEP 法
          • ESP 定律法
          • DUMP 及 IAT 重建
          • 最后一次异常法
          • 手动查找 IAT 并使用 ImportREC 重建
          • 内存镜像法
          • 保护壳简介
          • SFX 法
          • 单步跟踪法
          • DLL 文件脱壳
    • tools
      • constraint
        • z3
      • debug
        • gdb
        • ollydbg
        • windbg
        • x64dbg/x32dbg
      • simulate-execution
        • angr
        • Unicorn Engine
      • static-analyze
        • dnspy
        • Ghidra
        • IDA Pro
        • jadx
  • crypto
    • 密码学简介
    • asymmetric
      • 介绍
      • discrete-log
        • 离散对数
        • ECC
        • ElGamal
      • knapsack
        • 背包加密
      • lattice
        • CVP
        • 基本介绍
        • 格基规约算法
        • 格概述
      • rsa
        • RSA 选择明密文攻击
        • RSA 复杂题目
        • Coppersmith 相关攻击
        • 公钥指数相关攻击
        • 模数相关攻击
        • Bleichenbacher's attack
        • RSA 侧信道攻击
        • RSA 介绍
        • d_attacks
          • 私钥 d 相关攻击
          • 扩展维纳攻击
    • attack-summary
      • 简介
      • 比特攻击
      • 中间相遇攻击 - MITM
    • basic
      • 基础数学知识
    • blockcipher
      • AES
      • ARX: Add-Rotate-Xor
      • DES
      • IDEA
      • 块加密
      • Simon and Speck Block Ciphers
      • mode
        • CBC
        • CFB
        • CTR
        • ECB
        • 分组模式
        • OFB
        • Padding Oracle Attack
        • 填充方式
        • PCBC
    • certificate
      • 证书格式
    • classical
      • 古典密码简介
      • 单表代换加密
      • 其它类型加密
      • 多表代换加密
      • 总结
    • hash
      • Hash Attack
      • 综合题目
      • Fowler–Noll–Vo hash function
      • 哈希函数
      • MD5
      • SHA1
    • signature
      • DSA
      • ElGamal
      • 数字签名
      • RSA 数字签名
    • streamcipher
      • 流密码
      • fsr
        • 反馈移位寄存器
        • 线性反馈移位寄存器 - LFSR
        • 非线性反馈移位寄存器
      • lcg
        • 题目
        • 线性同余生成器
      • prng
        • 密码安全伪随机数生成器
        • 伪随机数生成器介绍
        • 题目
      • special
        • RC4
  • pwn
    • MacOS
    • misc-os
    • 概述
      • stackoverflow
        • 执行 Shellcode
        • 栈介绍
        • 栈溢出原理
    • browser
      • Chrome
      • Firefox
      • Safari
    • hardware
      • 简介
        • side-channel
          • prefetch side-channel attack
      • trusted-computing
        • 可信执行环境
    • linux
      • kernel-mode
        • 基础知识
        • Introduction
          • DoS
          • Information Disclosure
          • Introduction
            • Change Others
            • Change Self
        • Introduction
          • Introduction
            • 信息泄漏
            • Misc
          • Introduction
            • Kernel Stack Canary
          • Introduction
            • inner-kernel
              • 内部隔离
            • Introduction
              • KPTI - Kernel Page Table Isolation
              • 用户代码不可执行
              • 用户数据不可访问
          • Introduction
            • FGKASLR
            • KASLR
        • Introduction
          • 编译内核驱动
          • 内核下载与编译
          • Qemu 模拟环境
          • Real Device
        • exploitation
          • heap
            • 内核堆概述
            • buddy
              • Cross-Cache Overflow & Page-level Heap Fengshui
              • Page-level UAF
            • slub
              • freelist 劫持
              • Heap Spray
              • kernel UAF
          • race
            • Double Fetch
            • userfaultfd 的使用
          • rop
            • bypass-smep
            • ret2dir
            • 利用 pt_regs 构造通用内核 ROP
            • ret2usr(已过时)
            • Kernel ROP
          • tricks
            • 在内存中直接搜索 flag
      • user-mode
        • environment
        • fmtstr
          • 检测
          • 例子
          • 利用
          • 原理介绍
        • integeroverflow
          • 整数溢出
        • io-file
          • glibc 2.24下 IO_FILE 的利用
          • 伪造vtable劫持程序流程
          • FSOP
          • FILE结构
        • mitigation
          • Canary
        • race-condition
          • introduction
          • 题目
        • summary
          • 获取地址
          • shell 获取小结
          • 控制程序执行流
        • Type Confusion
        • Uninitialized Memory
        • heap
          • mallocng
          • ptmalloc2
            • Chunk Extend and Overlapping
            • Fastbin Attack
            • 堆概述
            • 堆相关数据结构
            • 堆溢出
            • House Of Einherjar
            • House Of Force
            • House of Lore
            • House of Orange
            • House of Pig
            • House of Rabbit
            • House of Roman
            • 堆利用
            • Large Bin Attack
            • 通过堆进行信息泄漏
            • 堆中的 Off-By-One
            • 堆中的检查
            • tcache makes heap exploitation easy again
            • Unlink
            • Unsorted Bin Attack
            • Use After Free
            • implementation
              • 基础操作
              • 释放内存块
              • 堆初始化
              • malloc_state 相关函数
              • 申请内存块
              • 测试支持
              • 深入理解堆的实现
              • tcache
        • stackoverflow
          • arm
            • 环境搭建
            • Arm ROP
          • mips
            • mips - ROP
          • RISC-V
          • x86
            • 基本 ROP
            • 花式栈溢出技巧
            • 中级ROP
            • 栈介绍
            • 栈溢出原理
            • advanced-rop
              • 高级 ROP
              • ret2dlresolve
              • ret2VDSO
              • SROP
    • sandbox
      • Chroot
      • Docker
      • Namespace
      • python
        • Python 沙盒
      • seccomp
        • C 沙盒逃逸
      • Shell Sandbox
    • virtualization
      • basic-knowledge
        • 虚拟化技术简介
        • CPU 虚拟化
        • IO 虚拟化
        • 内存虚拟化
      • parallels
        • Parallels
      • VirtualBox
      • VMWare
      • qemu
        • basic-knowledge
          • QEMU 设备模拟
          • QEMU 内存管理
        • environment
          • 编写 QEMU 模拟设备
          • QEMU 下载与编译
        • exploitation
          • QEMU 逃逸入门
          • 越界读写
  • Android 安全
    • basic_develop
      • Android 开发基础
    • Android 应用运行机制简述
      • Android 中 Java 层的运行机制
        • dex
          • DEX文件
          • ODEX文件
        • smali
          • Smali
      • native_layer
        • so 介绍
    • basic_reverse
      • Android 关键代码定位
      • Android 逆向基本介绍
      • dynamic
        • Android 动态调试
        • IDA 动态调试原生层程序
        • IDA 动态调试 smali 代码
      • static
        • 静态分析综合题目
        • 静态分析 java 层例子
        • 静态分析原生层程序
  • blockchain
    • Blockchain Security Challenges
    • Blockchain Security Overview
    • ethereum
      • Ethereum Basics
      • Ethereum Overview
      • Ethereum Opcodes
      • 学习资源
      • Smart Contract Reverse
      • Function Selector and Argument Encoding
      • Ethereum Storage
      • attacks
        • Airdrop Hunting
        • Arbitrary Writing
        • CREATE2
        • Delegatecall
        • Introduction
        • Jump Oriented Programming
        • Integer Overflow and Underflow
        • Randomness
        • Re-Entrancy
        • Short Address Attack
        • Uninitialized Storage Pointer
    • publicblockchain
      • Public Blockchain Security Overview
      • Blockchain Weaknesses
  • assembly
    • ARM
    • MIPS
    • x86_x64
  • executable
    • elf
      • 程序加载
      • 程序执行流程
      • linking
        • 程序链接
        • Symbol Reslove
      • structure
        • ELF 文件
        • Code Section
        • Data Related Sections
        • Dynamic Sections
        • Misc Sections
        • Sections
        • String Sections
        • .symtab: Symbol Table
    • pe
      • PE 文件格式
      • 导出表
      • 导入表
      • 基址重定位表
  • ics
    • ICS_CTF 竞赛
    • ICS_CTF 发现
    • ICS_CTF 利用
    • ICS_CTF 学习资源
  • contribute
    • 贡献之前
    • 基本贡献方式
    • 贡献文档要求
    • 翻译
  • write up
    • 浙江工业大学CTF赛事
      • 2023第四届“安恒杯”CTF新生赛题解
Powered by GitBook
On this page
  • QEMU Object Model
  • I、TypeInfo - 类的基本属性
  • II、Class - 类的静态内容
  • III、Object - 类的实例对象
  • IV、类的创建与释放
  • Qemu 中 PCI 设备的编写
  • REFERENCE
  1. pwn
  2. virtualization
  3. qemu
  4. environment

编写 QEMU 模拟设备

PreviousenvironmentNextQEMU 下载与编译

Last updated 1 year ago

本节我们将介绍如何在 QEMU 中编写一个新的模拟设备。

注1:在开始之前你可能需要补充一些

注2:qemu 官方在 hw/misc/edu.c 中也提供了一个教学用的设备样例,red hat 则在 hw/misc/pci-testdev.c 中提供了一个测试设备,我们可以参考这两个设备来构建我们的设备

QEMU Object Model

在 Qemu 当中有着一套叫做 Qemu Object Model 的东西来实现面向对象,主要由这四个组件构成:

  • Type:用来定义一个「类」的基本属性,例如类的名字、大小、构造函数等。

  • Class:用来定义一个「类」的静态内容,例如类中存储的静态数据、方法函数指针等。

  • Object:动态分配的一个「类」的具体的实例(instance),储存类的动态数据。

  • Property:动态对象数据的访问器(accessor),可以通过监视器接口进行检查。

类似于 Golang,在 QOM 当中使用成员嵌套的方式来完成类的继承,父类作为类结构体的第一个成员 parent 而存在,因此也不支持多继承。

参见

I、TypeInfo - 类的基本属性

TypeInfo 这一结构体用来定义一个 类 的基本属性,该结构体定义于 include/qom/object.h 当中:

/**
 * TypeInfo:
 * @name: 类型名.
 * @parent: 父类型名.
 * @instance_size: 对象大小 (#Object 的衍生物). 
 *   若 @instance_size 为 0, 则对象的大小为其父类的大小
 * @instance_init: 该函数被调用以初始化对象(译注:构造函数). 
 *   (译注:调用前)父类已被初始化,因此子类只需要初始化他自己的成员。
 * @instance_post_init: 该函数被调用以结束一个对象的初始化,
 *   在所有的 @instance_init 函数被调用之后.
 * @instance_finalize: 该函数在对象被析构时调用. 其在
 *   父类的 @instance_finalize 被调用之前被调用.
 *   在该函数中一个对象应当仅释放该对象特有的成员。
 * @abstract: 若该域为真,则该类为一个虚类,不能被直接实例化。
 * @class_size: 这个对象的类对象的大小 (#Object 的衍生物)
 *   若 @class_size 为 0, 则类的大小为其父类的大小。
 *   这允许一个类型在没有添加额外的虚函数时避免实现一个显式的类型。
 * @class_init: 该函数在所有父类初始化结束后被调用,
 *   以允许一个类设置他的默认虚方法指针.
 *   这也允许该函数重写父类的虚方法。
 * @class_base_init: 在所有的父类被初始化后、但
 *   在类自身初始化前,为所有的基类调用该函数。
 *   该函数用以撤销从父类 memcpy 到子类的影响.
 * @class_data: 传递给 @class_init 与 @class_base_init 的数据,
 *   这会在建立动态类型时有用。
 * @interfaces: 与这个类型相关的接口. 
 *   其应当指向一个以 0 填充元素结尾的静态数组
 */
struct TypeInfo
{
    const char *name;
    const char *parent;

    size_t instance_size;
    void (*instance_init)(Object *obj);
    void (*instance_post_init)(Object *obj);
    void (*instance_finalize)(Object *obj);

    bool abstract;
    size_t class_size;

    void (*class_init)(ObjectClass *klass, void *data);
    void (*class_base_init)(ObjectClass *klass, void *data);
    void *class_data;

    InterfaceInfo *interfaces;
};

当我们在 Qemu 中要定义一个类的时候,我们实际上需要定义一个 TypeInfo 类型的变量,以下是一个在 Qemu 定义一个自定义类的例子:

static const TypeInfo a3_type_info = {
    .name = "a3_type",
    .parent = TYPE_OBJECT,
    .interfaces = (InterfaceInfo[]) {
        { },
    },
}

static void a3_register_types(void) {
    type_register_static(&a3_type_info);
}

type_init(a3_register_types);

type_init() 其实就是 constructor 这一 gcc attribute 的封装,其作用就是将一个函数加入到一个 init_array 当中,在 Qemu 程序启动时在进入到 main 函数之前会先调用 init_array 中的函数,因此这里会调用我们自定义的函数,其作用便是调用 type_register_static() 将我们自定义的类型 a3_type_info 注册到全局的类型表中。

II、Class - 类的静态内容

当我们通过一个 TypeInfo 结构体定义了一个类之后,我们还需要定义一个 Class 结构体来定义这个类的静态内容,包括函数表、静态成员等,其应当继承于对应的 Class 结构体类型,例如我们若是要定义一个新的机器类,则其 Class 应当继承于 MachineClass。

所有 Class 结构体类型的最终的父类都是 ObjectClass 结构体:

/**
 * ObjectClass:
 *
 * 所有类的基类.  #ObjectClass 仅包含一个整型类型 handler
 */
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 A3Class
{
    /*< private >*/
    ObjectClass parent;
}

完成 Class 的定义之后我们还应当在前面定义的 a3_type_info 中添加上 Class size 与 Class 的构造函数:

static void a3_class_init(ObjectClass *oc, void *data)
{
    // 这里的 oc 参数便是新创建的 Class,全局只有一个该实例
    // 我们应当 cast 为我们自己的 Class 类型,之后再进行相应操作
    // do something
}

static const TypeInfo a3_type_info = {
    .name = "a3_type",
    .parent = TYPE_OBJECT,
    .class_size = sizeof(A3Class),
    .class_init = a3_class_init,
    .interfaces = (InterfaceInfo[]) {
        { },
    },
}

III、Object - 类的实例对象

我们还需要定义一个相应的 Object 类型来表示一个实例对象,其包含有这个类实际的具体数据,且应当继承于对应的 Object 结构体类型,例如我们若是要定义一个新的机器类型,其实例类型应当继承自 MachineState

所有 Object 结构体类型的最终的父类都是 Object 结构体:

/**
 * Object:
 *
 * 所有对象的基类。该对象的第一个成员为一个指向 #ObjectClass 的指针。
 * 因为 C 中将一个结构体的第一个成员组织在该结构体的 0 字节起始处,
 * 只要任何的子类将其父类作为第一个成员,我们都能直接转化为一个 #Object.
 *
 * 因此, #Object 包含一个对对象类的引用作为其第一个成员。 
 * 这允许在运行时识别对象的真实类型
 */
struct Object
{
    /*< private >*/
    ObjectClass *class;
    ObjectFree *free;
    GHashTable *properties;
    uint32_t ref;
    Object *parent;
};

下面是一个示例:

struct A3Object
{
    /*< private >*/
    Object parent;
}

完成 Object 的定义之后我们还应当在前面定义的 a3_type_info 中添加上 Object size 与 Object 的构造函数:

static void a3_object_init(Object *obj)
{
    // 这里的 obj 参数便是动态创建的类型实例
    // do something
}

static const TypeInfo a3_type_info = {
    .name = "a3_type",
    .parent = TYPE_OBJECT,
    .instance_init = a3_object_init,
    .instance_size = sizeof(A3Object),
    .class_size = sizeof(A3Class),
    .class_init = a3_class_init,
    .interfaces = (InterfaceInfo[]) {
        { },
    },
}

IV、类的创建与释放

类似于在 C++ 当中使用 new 与 delete 来创建与释放一个类实例,在 QOM 中我们应当使用 object_new() 与 object_delete() 来创建与销毁一个 QOM 类实例,本质上就是 分配/释放类空间 + 显示调用构造/析构函数

QOM 判断创建类实例的类型是通过类的名字,即 TypeInfo->name,当创建类实例时 Qemu 会遍历所有的 TypeInfo 并寻找名字匹配的那个,从而调用到对应的构造函数,并将其基类 Object->class 指向对应的 class

下面是一个示例:

// create a QOM object
A3Object *a3obj = object_new("a3_type");
// delete a QOM object
object_delete(a3obj);

Qemu 中 PCI 设备的编写

在补充了这么多的 Qemu 相关的知识之后,现在我们可以开始在 Qemu 中编写 PCI 设备了,这里笔者将编写一个最简单的 Qemu 设备,并将源码放在 hw/misc/a3dev.c 中

Qemu 当中 PCI 设备实例的基类是 PCIDevice,因此我们应当创建一个继承自 PCIDevice 的类来表示我们的设备实例,这里笔者仅声明了两个 MemoryRegion 用作 MMIO 与 PMIO,以及一个用作数据存储的 buffer:

#define A3DEV_BUF_SIZE 0x100

typedef struct A3PCIDevState {
    /*< private >*/
    PCIDevice parent_obj;

    /*< public >*/
    MemoryRegion mmio;
    MemoryRegion pmio;
    uint8_t buf[A3DEV_BUF_SIZE];
} A3PCIDevState;

以及定义一个空的 Class 模板,继承自 PCI 设备的静态类型 PCIDeviceClass,不过这一步并不是必须的,事实上我们可以直接用 PCIDeviceClass 作为我们设备类的 Class:

typedef struct A3PCIDevClass {
    /*< private >*/
    PCIDeviceClass parent;
} A3PCIDevClass;

以及两个将父类转为子类的宏,因为 QOM 基本函数传递的大都是父类指针,所以我们需要一个宏来进行类型检查 + 转型,这也是 Qemu 中惯用的做法:

#define TYPE_A3DEV_PCI "a3dev-pci"
#define A3DEV_PCI(obj) \
    OBJECT_CHECK(A3PCIDevState, (obj), TYPE_A3DEV_PCI)
#define A3DEV_PCI_GET_CLASS(obj) \
    OBJECT_GET_CLASS(A3PCIDevClass, obj, TYPE_A3DEV_PCI)
#define A3DEV_PCI_CLASS(klass) \
    OBJECT_CLASS_CHECK(A3PCIDevClass, klass, TYPE_A3DEV_PCI)

下面我们开始定义 MMIO 与 PMIO 的操作函数,这里笔者就简单地设置为读写设备内部的 buffer,并声明上两个 MemoryRegion 对应的函数表,需要注意的是这里传入的 hwaddr 类型参数其实为相对地址而非绝对地址:

static uint64_t
a3dev_read(void *opaque, hwaddr addr, unsigned size)
{
    A3PCIDevState *ds = A3DEV_PCI(opaque);
    uint64_t val = ~0LL;

    if (size > 8)
        return val;

    if (addr + size > A3DEV_BUF_SIZE)
        return val;
    
    memcpy(&val, &ds->buf[addr], size);
    return val;
}

static void
a3dev_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
{
    A3PCIDevState *ds = A3DEV_PCI(opaque);

    if (size > 8)
        return ;

    if (addr + size > A3DEV_BUF_SIZE)
        return ;
    
    memcpy(&ds->buf[addr], &val, size);
}

static uint64_t
a3dev_mmio_read(void *opaque, hwaddr addr, unsigned size)
{
    return a3dev_read(opaque, addr, size);
}

static uint64_t
a3dev_pmio_read(void *opaque, hwaddr addr, unsigned size)
{
    return a3dev_read(opaque, addr, size);
}

static void
a3dev_mmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
{
    a3dev_write(opaque, addr, val, size);
}

static void
a3dev_pmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
{
    a3dev_write(opaque, addr, val, size);
}

static const MemoryRegionOps a3dev_mmio_ops = {
    .read = a3dev_mmio_read,
    .write = a3dev_mmio_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
};

static const MemoryRegionOps a3dev_pmio_ops = {
    .read = a3dev_pmio_read,
    .write = a3dev_pmio_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
};

然后是设备实例的初始化函数,在 PCIDeviceClass 当中定义了一个名为 realize 的函数指针,当 PCI 设备被载入时便会调用这个函数指针指向的函数来初始化,所以这里我们也定义一个自己的初始化函数,不过我们需要做的工作其实基本上就只有初始化两个 MemoryRegion,memory_region_init_io() 会为这两个 MemoryRegion 进行初始化的工作,并设置函数表为我们指定的函数表,pci_register_bar() 则用来注册 BAR:

static void a3dev_realize(PCIDevice *pci_dev, Error **errp)
{
    A3PCIDevState *ds = A3DEV_PCI(pci_dev);

    memory_region_init_io(&ds->mmio, OBJECT(ds), &a3dev_mmio_ops,
                        pci_dev, "a3dev-mmio", A3DEV_BUF_SIZE);
    pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &ds->mmio);
    memory_region_init_io(&ds->pmio, OBJECT(ds), &a3dev_pmio_ops,
                        pci_dev, "a3dev-pmio", A3DEV_BUF_SIZE);
    pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &ds->pmio);
}

最后是 Class 与 Object(也就是 instance)的初始化函数,这里需要注意的是在 Class 的初始化函数中我们应当设置父类 PCIDeviceClass 的一系列基本属性(也就是 PCI 设备的基本属性):

static void a3dev_instance_init(Object *obj)
{
    // do something
}

static void a3dev_class_init(ObjectClass *oc, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(oc);
    PCIDeviceClass *pci = PCI_DEVICE_CLASS(oc);

    pci->realize = a3dev_realize;
    pci->vendor_id = PCI_VENDOR_ID_QEMU;
    pci->device_id = 0x1919;
    pci->revision = 0x81;
    pci->class_id = PCI_CLASS_OTHERS;

    dc->desc = "arttnba3 test PCI device";
    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
}

最后就是为我们的 PCI 设备类型注册 TypeInfo 了,这里别忘了我们的接口中应当增加上 PCI 的接口:

static const TypeInfo a3dev_type_info = {
    .name = TYPE_A3DEV_PCI,
    .parent = TYPE_PCI_DEVICE,
    .instance_init = a3dev_instance_init,
    .instance_size = sizeof(A3PCIDevState),
    .class_size = sizeof(A3PCIDevClass),
    .class_init = a3dev_class_init,
    .interfaces = (InterfaceInfo[]) {
        { INTERFACE_CONVENTIONAL_PCI_DEVICE },
        { },
    },
};

static void a3dev_register_types(void) {
    type_register_static(&a3dev_type_info);
}

type_init(a3dev_register_types);

最后我们在 meson 构建系统中加入我们新增的这个设备,在 hw/misc/meson.build 中加入如下语句:

softmmu_ss.add(when: 'CONFIG_PCI_A3DEV', if_true: files('a3dev.c'))

并在 hw/misc/Kconfig 中添加如下内容,这表示我们的设备会在 CONFIG_PCI_DEVICES=y 时编译:

config PCI_A3DEV
    bool
    default y if PCI_DEVICES
    depends on PCI

之后编译 Qemu 并附加上 -device a3dev-pci ,之后随便起一个 Linux 系统,此时使用 lspci 指令我们便能看到我们新添加的 pci 设备:

我们可以使用如下程序来测试我们的设备的输入输出,需要注意的是这需要 root 权限:

PMIO,使用 iopl 更改端口权限后便能通过 in/out 类指令读写端口

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/io.h>

int main(int argc, char **argv, char **envp)
{
        unsigned short port_addr;

        if (argc < 2) {
                puts("[x] no port provided!");
                exit(EXIT_FAILURE);
        }

        if (iopl(3) < 0) {
                puts("[x] no privilege!");
                exit(EXIT_FAILURE);
        }

        port_addr = atoi(argv[1]);

        printf("[+] a3dev port addr start at: %d\n", port_addr);

        puts("[*] now writing into a3dev-pci...");

        for (int i = 0; i < 0x100 / 4; i++) {
                outl(i, port_addr + i * 4);
        }

        puts("[+] writing done!");

        printf("[*] now reading from a3dev-pci...");
        for (int i = 0; i < 0x100 / 4; i++) {
                if (i % 8 == 0) {
                        printf("\n[--%d--]", port_addr + i * 4);
                }
                printf(" %d ", inl(port_addr + i * 4));
        }

        puts("\n[+] reading done!");
}

MMIO,使用 mmap 映射 sys 目录下设备的 resource0 文件即可直接读写

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/mman.h>

void mmio_write(uint32_t *addr, uint32_t val)
{
        *addr = val;
}

uint32_t mmio_read(uint32_t *addr)
{
        return *addr;
}

int main(int argc, char **argv, char **envp)
{
        uint32_t *mmio_addr;
        int dev_fd;

        dev_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0",
                        O_RDWR | O_SYNC);
        if (dev_fd < 0) {
                puts("[x] failed to open mmio file! wrong path or no root!");
                exit(EXIT_FAILURE);
        }

        mmio_addr = (uint32_t*)
                mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, 0);
        if (mmio_addr == MAP_FAILED) {
                puts("failed to mmap!");
                exit(EXIT_FAILURE);
        }

        puts("[*] start writing to a3dev-pci...");
        for (int i = 0; i < 0x100 / 4; i++) {
                mmio_write(mmio_addr + i, i);
        }
        puts("[+] write done!");

        printf("[*] start reading from a3dev-pci...");
        for (int i = 0; i < 0x100 / 4; i++) {
                if (i % 8 == 0) {
                        printf("\n[--%p--]", mmio_addr);
                }
                printf(" %u ", mmio_read(mmio_addr + i));
        }
        puts("\n[+] read done!");
}

REFERENCE

PCI 设备的基础知识
这个ppt
【VIRT.0x00】Qemu - I:Qemu 简易食用指南
QOM Vadis?Taking Objects To The CPU And Beyond
在 QEMU 中模拟设备 - 知乎
使用 lspci 查看新添加的 pci 设备