版本

QEMU版本:4.1.0 KVM/Linux kernel版本:4.20.17

QEMU/KVM初始化过程,QEMU部分

vl.c文件是QEMU的main函数所在的文件,main函数是qemu的入口,在函数的开始会有一个大循环负责解析命令行参数。之后QEMU的初始化主要分三个阶段,中间会穿插一些设备的初始化。

内存布局的初始化

内存布局初始化函数为cpu_exec_init_all(),该函数在早起版本QEMU中也负责讲bootloader和内核等加载入内存,在现在的版本中,该函数只负责初始化客户机物理内存布局和IO布局和注册操作函数。

void cpu_exec_init_all(void)
{
    qemu_mutex_init(&ram_list.mutex);
    /* The data structures we set up here depend on knowing the page size,
     * so no more changes can be made after this point.
     * In an ideal world, nothing we did before we had finished the
     * machine setup would care about the target page size, and we could
     * do this much later, rather than requiring board models to state
     * up front what their requirements are.
     */
    finalize_target_page_bits();
    io_mem_init();
    memory_map_init();
    qemu_mutex_init(&map_client_list_lock);
}

KVM初始化

configure_accelerator()负责初始化kvm。这个函数会根据配置文件或者命令行参数初始化对应的acce,包括tcg、kvm、xen。该函数调用accel_init_machine(),这个函数又调用对应类的init_machine方法。

for (tmp = accel_list; !accel_initialised && tmp && *tmp; tmp++) {
        acc = accel_find(*tmp);
        if (!acc) {
            continue;
        }
        ret = accel_init_machine(acc, ms);
        if (ret < 0) {
            init_failed = true;
            error_report("failed to initialize %s: %s",
                         acc->name, strerror(-ret));
        } else {
            accel_initialised = true;
        }
    }
static int accel_init_machine(AccelClass *acc, MachineState *ms)
{
    ObjectClass *oc = OBJECT_CLASS(acc);
    const char *cname = object_class_get_name(oc);
    AccelState *accel = ACCEL(object_new(cname));
    int ret;
    ms->accelerator = accel;
    *(acc->allowed) = true;
    ret = acc->init_machine(ms);
    if (ret < 0) {
        ms->accelerator = NULL;
        *(acc->allowed) = false;
        object_unref(OBJECT(accel));
    } else {
        object_set_accelerator_compat_props(acc->compat_props);
    }
    return ret;
}

在kvm部分中,init_machine方法是kvm-all.c中的kvm_init()函数,这个函数很长,他的主要功能是使用ioctl通知kvm创建一个新的vm,确定kvm所支持的功能符合qemu所需要的功能需求,注册内存和IO事件通知回调函数。

s->vmfd = -1;
    s->fd = qemu_open("/dev/kvm", O_RDWR);
    if (s->fd == -1) {
        fprintf(stderr, "Could not access KVM kernel module: %m\n");
        ret = -errno;
        goto err;
    }

    ret = kvm_ioctl(s, KVM_GET_API_VERSION, 0);
    if (ret < KVM_API_VERSION) {
        if (ret >= 0) {
            ret = -EINVAL;
        }
        fprintf(stderr, "kvm version too old\n");
        goto err;
    }

    if (ret > KVM_API_VERSION) {
        ret = -EINVAL;
        fprintf(stderr, "kvm version not supported\n");
        goto err;
    }

    kvm_immediate_exit = kvm_check_extension(s, KVM_CAP_IMMEDIATE_EXIT);
    s->nr_slots = kvm_check_extension(s, KVM_CAP_NR_MEMSLOTS);
    
....
 do {
        ret = kvm_ioctl(s, KVM_CREATE_VM, type);
    } while (ret == -EINTR);

这个函数最后会调用kvm_arch_init()去进行一些架构特有的初始化,这个函数在x86中是target/i386/kvm.c中的kvm_arch_init()。在x86中这个函数主要是设置EPT支持等x86虚拟化架构特有的功能。

主机初始化

这部分是工作最多,最复杂的初始化部分。这部分的功能主要包括创建并初始化vCPU、加载bootloader、kernel等到虚拟机物理内存,初始化总线等。这部分代码入口函数是machine_run_board_init(),这个函数的调用是main函数初始化部分最后的几个,之初始化就完成了。

void machine_run_board_init(MachineState *machine)
{
    MachineClass *machine_class = MACHINE_GET_CLASS(machine);

    numa_complete_configuration(machine);
    if (nb_numa_nodes) {
        machine_numa_finish_cpu_init(machine);
    }

    /* If the machine supports the valid_cpu_types check and the user
     * specified a CPU with -cpu check here that the user CPU is supported.
     */
    if (machine_class->valid_cpu_types && machine->cpu_type) {
        ObjectClass *class = object_class_by_name(machine->cpu_type);
        int i;

        for (i = 0; machine_class->valid_cpu_types[i]; i++) {
            if (object_class_dynamic_cast(class,
                                          machine_class->valid_cpu_types[i])) {
                /* The user specificed CPU is in the valid field, we are
                 * good to go.
                 */
                break;
            }
        }

        if (!machine_class->valid_cpu_types[i]) {
            /* The user specified CPU is not valid */
            error_report("Invalid CPU type: %s", machine->cpu_type);
            error_printf("The valid types are: %s",
                         machine_class->valid_cpu_types[0]);
            for (i = 1; machine_class->valid_cpu_types[i]; i++) {
                error_printf(", %s", machine_class->valid_cpu_types[i]);
            }
            error_printf("\n");

            exit(1);
        }
    }

    machine_class->init(machine);
}

这个函数最终会调用machine_class的init方法。这个方法在x86上的实现是hw/i386/pc_piix.c中的pc_init1()。这个函数非常长与复杂,这里我们关心两部分内容。

vCPU初始化

pc_cpus_init(pcms);

    if (kvm_enabled() && pcmc->kvmclock_enabled) {
        kvmclock_create();
    }

调用pc_cpus_init()进行vcpu的创建和初始化。这个函数会调用pc_new_cpu()创建规定数量的vCPU。pc_new_cpu()最终将调用x86_cpu_realizefn(),这个函数又会调用qemu_init_vcpu()。qemu_init_vcpu()中会创建一个线程,线程函数为qemu_kvm_cpu_thread_fn(),这将是之后gust实际执行的时候的函数。qemu_kvm_cpu_thread_fn()开始会调用kvm_init_vcpu(),这个函数会使用ioctl通知kvm创建新vcpu。kvm_init_vcpu()又会调用kvm_arch_init_vcpu()对vCPU进行架构特定的初始化。

镜像加载

pc_memory_init()在非xen的情况下将被调用,这个函数将具体的初始化内存和IO的各个区域并调用bochs_bios_init和load_linux加载内核。

运行

QEMU

之前创建的线程中有一个大循环,执行kvm_cpu_exec()

do {
        if (cpu_can_run(cpu)) {
            r = kvm_cpu_exec(cpu);
            if (r == EXCP_DEBUG) {
                cpu_handle_guest_debug(cpu);
            }
        }
        qemu_wait_io_event(cpu);
    } while (!cpu->unplug || cpu_can_run(cpu));

kvm_cpu_exec()函数会调用ioctl让kvm执行客户机代码,一直到在需要进行IO模拟等操作。此时从内核返回,由qemu处理这些操作,之后返回kvm继续执行。

KVM

在qemu通过ioctl进入内核后,将进入kvm_arch_vcpu_ioctl_run函数。

	vcpu_load(vcpu);
	kvm_sigset_activate(vcpu);
	kvm_load_guest_fpu(vcpu);
...
if (kvm_run->immediate_exit)
		r = -EINTR;
	else
		r = vcpu_run(vcpu);

这个函数将调用vcpu_run,vcpu_run中存在一个大循环

for (;;) {
		if (kvm_vcpu_running(vcpu)) {
			r = vcpu_enter_guest(vcpu);
		} else {
			r = vcpu_block(kvm, vcpu);
		}

		if (r <= 0)
			break;

		kvm_clear_request(KVM_REQ_PENDING_TIMER, vcpu);
		if (kvm_cpu_has_pending_timer(vcpu))
			kvm_inject_pending_timer_irqs(vcpu);

		if (dm_request_for_irq_injection(vcpu) &&
			kvm_vcpu_ready_for_interrupt_injection(vcpu)) {
			r = 0;
			vcpu->run->exit_reason = KVM_EXIT_IRQ_WINDOW_OPEN;
			++vcpu->stat.request_irq_exits;
			break;
		}

		kvm_check_async_pf_completion(vcpu);

		if (signal_pending(current)) {
			r = -EINTR;
			vcpu->run->exit_reason = KVM_EXIT_INTR;
			++vcpu->stat.signal_exits;
			break;
		}
		if (need_resched()) {
			srcu_read_unlock(&kvm->srcu, vcpu->srcu_idx);
			cond_resched();
			vcpu->srcu_idx = srcu_read_lock(&kvm->srcu);
		}
	}

在大循环中,将调用vcpu_enter_guest。vcpu_enter_guest代码也很长,会调用kvm_x86ops->run,为开始客户机执行做准备,这个方法的实现是vmx_vcpu_run,最终将调用___vmx_vcpu_run,该函数实际上是一个汇编写的trampoline。而在vcpu_enter_guest退出时,将调用kvm_x86_ops->handle_exit(vcpu)处理。

static int (*kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {
[EXIT_REASON_EXCEPTION_NMI]           = handle_exception,
[EXIT_REASON_EXTERNAL_INTERRUPT]      = handle_external_interrupt,
[EXIT_REASON_TRIPLE_FAULT]            = handle_triple_fault,
[EXIT_REASON_NMI_WINDOW]      = handle_nmi_window,
[EXIT_REASON_IO_INSTRUCTION]          = handle_io,
[EXIT_REASON_CR_ACCESS]               = handle_cr,
[EXIT_REASON_DR_ACCESS]               = handle_dr,
[EXIT_REASON_CPUID]                   = handle_cpuid,
[EXIT_REASON_MSR_READ]                = handle_rdmsr,
[EXIT_REASON_MSR_WRITE]               = handle_wrmsr,
[EXIT_REASON_PENDING_INTERRUPT]       = handle_interrupt_window,
[EXIT_REASON_HLT]                     = handle_halt,
[EXIT_REASON_INVD]      = handle_invd,
[EXIT_REASON_INVLPG]      = handle_invlpg,
[EXIT_REASON_VMCALL]                  = handle_vmcall,
[EXIT_REASON_VMCLEAR]              = handle_vmx_insn,
[EXIT_REASON_VMLAUNCH]                = handle_vmx_insn,
[EXIT_REASON_VMPTRLD]                 = handle_vmx_insn,
[EXIT_REASON_VMPTRST]                 = handle_vmx_insn,
[EXIT_REASON_VMREAD]                  = handle_vmx_insn,
[EXIT_REASON_VMRESUME]                = handle_vmx_insn,
[EXIT_REASON_VMWRITE]                 = handle_vmx_insn,
[EXIT_REASON_VMOFF]                   = handle_vmx_insn,
[EXIT_REASON_VMON]                    = handle_vmx_insn,
[EXIT_REASON_TPR_BELOW_THRESHOLD]     = handle_tpr_below_threshold,
[EXIT_REASON_APIC_ACCESS]             = handle_apic_access,
[EXIT_REASON_WBINVD]                  = handle_wbinvd,
[EXIT_REASON_XSETBV]                  = handle_xsetbv,
[EXIT_REASON_TASK_SWITCH]             = handle_task_switch,
[EXIT_REASON_MCE_DURING_VMENTRY]      = handle_machine_check,
[EXIT_REASON_EPT_VIOLATION]      = handle_ept_violation,
[EXIT_REASON_EPT_MISCONFIG]           = handle_ept_misconfig,
[EXIT_REASON_PAUSE_INSTRUCTION]       = handle_pause,
[EXIT_REASON_MWAIT_INSTRUCTION]      = handle_invalid_op,
[EXIT_REASON_MONITOR_INSTRUCTION]     = handle_invalid_op,
};

问题

最近在从事和VMI相关的一些工作,在过程中遇到了一个问题.一般来说,在X86体系下,CR3寄存器可以用来唯一的标识一个进程,所以非常重要.而最近在使用CR3标识进程的时候,发现一个进程在mm_struct.pgd中的值不是其CR3实际的值.

CR3值的由来

在Linux内核中,每个线程都有一个task_struct结构,这个结构体里mm成员是mm_struct结构体的指针,代表着该线程的内存地址布局.一个线程组(进程)中所有的线程共享一个mm_struct. mm_struct结构体中pgd变量即是指向该线程的顶级页表目录. 对于一个内核线程,它没有自己的页表,在调度算法调度到内核线程时,将复用上一个用户态线程的页表项,并将上一个线程的mm_struct指针赋给该内核线程task_struct结构体的active_mm成员.

现象

借助qemu+kvm,可以观察到虚拟机中的CR3赋值.一般来说,CR3的序列是随机的,每个进程都有一个特有的CR3.而在对系统较新的虚拟机(Ubuntu 1804)观察CR3可以看到,CR3的值成对出现,会出现一大一小的CR3值,两者相差0x1000,而且两者被装载到CR3的次数相近. 通过内存搜索的方法,对两个CR3进行回溯发现,小的那个CR3有对应的task_struct,而大的那个却没有.

KPTI

在2017年的时候,有一篇论文讲到如何实现内核空间和用户态空间的隔离以加固KASLR.之后出现了Meltdown和Spectre漏洞.而这周隔离机制正好可以用来抵御Meltdown,被并入内核主线.

实现

KPTI实现非常简单,具体可以看原文.大致来说是在进程创建时候会alloc8K对齐的2个page,一个是正常的页表,一个是没有映射内核地址空间的页表.在从内核态切换到用户态时候,会将CR3+0x1000. 这样一来一个进程实际上有两个CR3,一个是跑在内核态时,值即使进程的mm_struct.pgd,另一个跑在用户态时,值为之前的值加0x1000.

前言

本文是针对两篇论文的总结,发表于2019 Usenix Security

FuzziFication: Anti-Fuzzing Techniques

AntiFuzz: Impeding Fuzzing Audits of Binary Executables

背景

fuzz技术已经被大规模应用,是至今为止最为有效的自动化漏洞挖掘技术。但是,目前为止并没有针对fuzz的反制技术。在很多场景下,恶意的攻击者会使用fuzz对软件进行漏洞挖掘,进而实施攻击。 代码混淆技术在较早就出现了,代码混淆技术主要针对的是逆向分析,特别是静态分析。当然,也有混淆算法针对动态分析的,但这类算法大多对性能影响巨大而不实用。 借鉴传统代码混淆的思路,可以设计一套针对fuzz的代码混淆算法。

Fuzzer的类型

盲Fuzzer

这一类fuzzer是最原始的fuzz,他们只是生产随机的输入,然后观测程序运行的结果,例如是否崩溃等。这类fuzzer可以主要分为两类,一类是随机生成输入。另一类针对输入有良好定义的格式,根据格式生成输入,一般fuzz的目标拥有类似parser的逻辑模块。

覆盖导向的Fuzzer

这类fuzzer是主流,著名的AFL就是这类fuzzer。这类fuzzer通过记录下路径覆盖信息,指导测试用例的生成。测试信息的搜集主要分三类。

静态插桩

需要程序源代码,使用此类方法的主要有AFL、ANGORA、LIBFUZZER、HONGGFUZZ。

动态插桩

针对无法获取程序的源代码的情况下,可以考虑动态插桩。动态插桩的主要方法有QEMU、PIN、DYNAMORIO、DYNINST。VUZZER和STEELIX即是使用以上的方法。一些AFL的变体也使用以上的插桩方法。DRILLER和T-FUZZ则是依赖由AFL和基于QEMU的插桩。

硬件支持的插桩

这类插桩可以提高效率,主要是依靠Intel PT技术记录程序历史路径。KAFL即是基于Intel PT的。

混合Fuzzer

这类Fuzzer将组合一些程序分析技术,包括污点分析和符号执行,用来覆盖到难以到达的路径。主要有DRILLER、QSYM、T-FUZZ。

Fuzz的特征

覆盖率导向

fuzz依赖于获取覆盖率信息,认为越大的覆盖率意味着越可能产生crash。绝大多数的fuzz都依赖于覆盖率来产生新的测试用例

可检测的crash

fuzz需要能够监测到crash,才能判断测试用例是否有效。

可高效运行

fuzz需要程序每秒能够运行多次。一个运行一次需要很久的程序是难以fuzz的

条件可解

最近的混合fuzz通过结合污点分析和符号执行来到达一些难以到达的路径,这要求这些条件是符号执行和污点分析能够求解的。

Fuzz对抗

针对覆盖率导向

插入大量分支无效代码

这是最简单的方法。通过插入大量fake code,根据输入的hash值决定跳转的位置,这将导致fuzzer花费大量时间测试无效代码。为了降低对效率的影响,想要将这些代码插在针对输入的错误处理部分。这个方法也是最容易被自动化移除的,通过借助控制流和数据流分析技术可以一定程度的移除这类fake code。

基于ROP

思想和上一个类似,即是根据输入的hash跳转执行,从而欺骗fuzzer找到新路径。但这里可以重用已有代码。将所有函数的epilogue分组,相同的分为一组。在函数返回时,根据输入的hash确定所使用epilogue,由于同组所有epilogue是相同的,不影响函数返回。这种方法相较于上一种难以去除。

针对高效运行

加入时延

错误处理部分和正常场景下很少执行的部分在时延代码。这两类代码块在正常情况下不会被执行,而在fuzz时则可能被频繁执行。时延代码可以使用类似CSmath的高运输负载的代码,这样能保证在程序分析下存在数据流和控制流依赖而难以被去除。

针对可检测的crash

反调试

使用各种反调试技巧

针对条件可解

增加间接数据流依赖

主要针对污点分析。间接数据流依赖一直是污点分析中难以解决的问题。

爆炸路径条件

针对符号执行。最简单的一种方法是在输入和魔数进行比较时候,计算他们的hash并进行比较

总结

对抗是需要成本的,无论哪个混淆算法,代价都是性能的损失,在实际当中需要衡量好安全和性能的平衡。一种可使用的方法是先确定正常场景下难以进入的代码块,在这类代码块中插入各种保护,同时结合常规代码保护方法。

写在前面

作为QEMU的入门,这篇文章主要是帮我理清楚QEMU中QOM部分内容的。本文主要分成两部分,一部分介绍QOM的主要数据结构和大致的实现,一部分是一些实例,这些实例实际上代码中注释的例子。

QOM主要数据结构的关系

这里借用一张现成的大佬画的图,借助这张图能够更好的理清QOM的各个数据结构。 QOM

TypeImpl

struct TypeImpl
{
    const char *name;

    size_t class_size;

    size_t instance_size;

    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;

    int num_interfaces;
    InterfaceImpl interfaces[MAX_INTERFACES];
};

这个数据结构存储类的基本信息,QEMU中存在一个全局的hash表,储存所有注册的该数据结构的指针。

利用TypeInfo这个结构,注册TypeImpl。TypeInfo结构实际上是和TypeImpl对应的。

struct TypeInfo
{
    const char *name; // 这个类型的名字
    const char *parent;  //这个类型的父类的名字

    size_t instance_size; //对象对应数据结构的size

    // instance如何初始化和最后的垃圾回收
    void (*instance_init)(Object *obj);
    void (*instance_post_init)(Object *obj);
    void (*instance_finalize)(Object *obj);

    bool abstract; //这个类是否是抽象的,也就是是否有虚拟函数
    size_t class_size; //类对应数据结构的size

    // 类如何初始化和最后的垃圾回收
    void (*class_init)(ObjectClass *klass, void *data);
    void (*class_base_init)(ObjectClass *klass, void *data);
    void (*class_finalize)(ObjectClass *klass, void *data);
    void *class_data;

    // 这个类所实现的接口
    InterfaceInfo *interfaces;
};

QOM提供type_register和type_register_static方法,调用这两个方法注册一个Type,需要传进去的参数就是TypeInfo的指针。

TypeImpl *type_register(const TypeInfo *info)
{
    assert(info->parent);
    return type_register_internal(info);
}

TypeImpl *type_register_static(const TypeInfo *info)
{
    return type_register(info);
}

static TypeImpl *type_register_internal(const TypeInfo *info)
{
    TypeImpl *ti;
    ti = type_new(info);

    type_table_add(ti);
    return ti;
}

type_new函数实际就是利用info队impl进行填充。

Object和ObjectClass

Object可以认为是一个属性或者状态的集合,而ObjectClass则是方法或函数指针的集合,所以可以看到CPU的Object结构名称是CPUState。 struct ObjectClass是所有类的基类。

struct ObjectClass
{
    /*< private >*/
    Type type;  /**/
    GSList *interfaces; //该属性是接口链表节点

    const char *object_cast_cache[OBJECT_CLASS_CAST_CACHE]; //用于做cast操作时的缓存
    const char *class_cast_cache[OBJECT_CLASS_CAST_CACHE];

    ObjectUnparent *unparent;
};

这个结构也是所有接口的基类,一个类可以实现多个接口,interface作为链表的head,该类实现的所有接口的InterfaceClass结构构成了链表。

struct Object是所有对象的基对象。

struct Object
{
    /*< private >*/
    ObjectClass *class;  //指向对应的类的数据结构的指针
    ObjectFree *free;    //当引用计数为0时调用
    GHashTable *properties;  //Object中的所有属性的hash表
    uint32_t ref;        //对象的引用计数
    Object *parent;      //指向父对象的指针
};

列出的这些属性都是私有的。properties中的属性才是可被访问和修改的,这个hash表的每个entry是一个key value对,key是属性的名称,value是ObjectProperty结构,这个结构定义如下:

typedef struct ObjectProperty
{
    gchar *name;
    gchar *type;
    gchar *description;
    ObjectPropertyAccessor *get;
    ObjectPropertyAccessor *set;
    ObjectPropertyResolve *resolve;
    ObjectPropertyRelease *release;
    void *opaque;
} ObjectProperty;

在QOM中,利用object_property_add函数可向一个对象中加入属性,这个函数通常在对象的构造函数中调用。同时QOM还提供了其他的get和set函数用于对属性的读写。

ObjectProperty *
object_property_add(Object *obj, const char *name, const char *type,
                    ObjectPropertyAccessor *get,
                    ObjectPropertyAccessor *set,
                    ObjectPropertyRelease *release,
                    void *opaque, Error **errp)
{
    ObjectProperty *prop;
    size_t name_len = strlen(name);

    if (name_len >= 3 && !memcmp(name + name_len - 3, "[*]", 4)) {
        int i;
        ObjectProperty *ret;
        char *name_no_array = g_strdup(name);

        name_no_array[name_len - 3] = '\0';
        for (i = 0; ; ++i) {
            char *full_name = g_strdup_printf("%s[%d]", name_no_array, i);

            ret = object_property_add(obj, full_name, type, get, set,
                                      release, opaque, NULL);
            g_free(full_name);
            if (ret) {
                break;
            }
        }
        g_free(name_no_array);
        return ret;
    }

    if (g_hash_table_lookup(obj->properties, name) != NULL) {
        error_setg(errp, "attempt to add duplicate property '%s'"
                       " to object (type '%s')", name,
                       object_get_typename(obj));
        return NULL;
    }

    prop = g_malloc0(sizeof(*prop));

    prop->name = g_strdup(name);
    prop->type = g_strdup(type);

    prop->get = get;
    prop->set = set;
    prop->release = release;
    prop->opaque = opaque;

    g_hash_table_insert(obj->properties, prop->name, prop);
    return prop;
}

接口

在TypeImpl和TypeInfo中的InterfaceImpl和InterfaceInfo这两个结构是一样,只包含一个指示type的字符串,接口的数据结构是

struct InterfaceClass
{
    ObjectClass parent_class;  //它的父类就是ObjectClass
    /*< private >*/
    ObjectClass *concrete_class;   //实现这个接口的类的指针
    Type interface_type;           //这个interface的类型(TypeImpl*指针),这个属性指示要实现的接口类型。
};

这个数据结构保持了类实际实现接口的方法的指针。 所有对接口都继承自一个特殊的空接口,用于快速检查当前的TypeImpl是否是一个接口。

动态cast

cast赋予了对象多态的能力,QOM中通过object_class_dynamic_cast函数进行动态的cast

ObjectClass *object_class_dynamic_cast(ObjectClass *class,
                                       const char *typename)
{
    ObjectClass *ret = NULL;
    TypeImpl *target_type;
    TypeImpl *type;

    if (!class) {
        return NULL;
    }

    /* A simple fast path that can trigger a lot for leaf classes.  */
    type = class->type;
    if (type->name == typename) {
        return class;
    }

    target_type = type_get_by_name(typename);
    if (!target_type) {
        /* target class type unknown, so fail the cast */
        return NULL;
    }

    if (type->class->interfaces &&
            type_is_ancestor(target_type, type_interface)) { //通过检查是否为type_interface的子孙快速确定是否为接口
        int found = 0;
        GSList *i;

        for (i = class->interfaces; i; i = i->next) {
            ObjectClass *target_class = i->data;

            if (type_is_ancestor(target_class->type, target_type)) {
                ret = target_class;
                found++;
            }
         }

        /* The match was ambiguous, don't allow a cast */
        if (found > 1) {
            ret = NULL;
        }
    } else if (type_is_ancestor(type, target_type)) {
        ret = class;
    }

    return ret;
}

QOM的使用实例

创建一个新的类

这个类继承自DeviceClass

#include "qdev.h"

#define TYPE_MY_DEVICE "my-device"

// No new virtual functions: we can reuse the typedef for the
// superclass.
typedef DeviceClass MyDeviceClass;
typedef struct MyDevice
{
    DeviceState parent;

    int reg0, reg1, reg2;
} MyDevice;

static const TypeInfo my_device_info = {
    .name = TYPE_MY_DEVICE,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(MyDevice),
};
static void my_device_register_types(void)
{
    type_register_static(&my_device_info);
}

type_init(my_device_register_types)

DEFINE_TYPES宏可以用来方便的注册多个typeinfo

static const TypeInfo device_types_info[] = {
    {
        .name = TYPE_MY_DEVICE_A,
        .parent = TYPE_DEVICE,
        .instance_size = sizeof(MyDeviceA),
    },
    {
        .name = TYPE_MY_DEVICE_B,
        .parent = TYPE_DEVICE,
        .instance_size = sizeof(MyDeviceB),
    },
};

定义三个宏方便进行cast

#define MY_DEVICE_GET_CLASS(obj) \
        OBJECT_GET_CLASS(MyDeviceClass, obj, TYPE_MY_DEVICE)
#define MY_DEVICE_CLASS(klass) \
        OBJECT_CLASS_CHECK(MyDeviceClass, klass, TYPE_MY_DEVICE)
#define MY_DEVICE(obj) \
        OBJECT_CHECK(MyDevice, obj, TYPE_MY_DEVICE)

类的初始化

这里的类的初始化是指类本身的初始化,而不是对象的初始化,类的初始化只会执行一次,并且保证一个类在执行初始化函数前,他的所有父类均被初始化。类的初始化化函数中可以对父类的虚函数进行重载。

#include "qdev.h"

void my_device_class_init(ObjectClass *klass, void *class_data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    dc->reset = my_device_reset;
}

static const TypeInfo my_device_info = {
    .name = TYPE_MY_DEVICE,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(MyDevice),
    .class_init = my_device_class_init, /*在类初始化时就会调用这个函数,将虚拟函数赋值*/
};

添加新的虚函数则需要类定义自己的结构并给TypeInfo中的class_size变量赋值,每个函数最好有一个wrapper函数方便调用。

#include "qdev.h"

typedef struct MyDeviceClass
{
    DeviceClass parent;
    void (*frobnicate) (MyDevice *obj);
} MyDeviceClass;

static const TypeInfo my_device_info = {
    .name = TYPE_MY_DEVICE,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(MyDevice),
    .abstract = true, // or set a default in my_device_class_init
    .class_size = sizeof(MyDeviceClass),
};

void my_device_frobnicate(MyDevice *obj)
{
    MyDeviceClass *klass = MY_DEVICE_GET_CLASS(obj);
    klass->frobnicate(obj);
}

接口

接口是一个特殊的类,只有class而没有object,class中包含实现方法的函数指针。

重载函数

在QOM中,所有的函数实际上都是虚函数,可以被重载的,如果一个类重载了一个函数,之后对象调用该方法时会直接调用重载的函数,所以类有责任检查是否应该调用原函数。

typedef struct MyState MyState;

typedef void (*MyDoSomething)(MyState *obj);

typedef struct MyClass {
    ObjectClass parent_class;

    MyDoSomething do_something;
} MyClass;

static void my_do_something(MyState *obj)
{
    // do something
}

static void my_class_init(ObjectClass *oc, void *data)
{
    MyClass *mc = MY_CLASS(oc);

    mc->do_something = my_do_something;
}

static const TypeInfo my_type_info = {
    .name = TYPE_MY,
    .parent = TYPE_OBJECT,
    .instance_size = sizeof(MyState),
    .class_size = sizeof(MyClass),
    .class_init = my_class_init,
};

typedef struct DerivedClass {
    MyClass parent_class;

    MyDoSomething parent_do_something;
} DerivedClass;

static void derived_do_something(MyState *obj)
{
    DerivedClass *dc = DERIVED_GET_CLASS(obj);

    // do something here
    dc->parent_do_something(obj);
    // do something else here
}

static void derived_class_init(ObjectClass *oc, void *data)
{
    MyClass *mc = MY_CLASS(oc);
    DerivedClass *dc = DERIVED_CLASS(oc);

    dc->parent_do_something = mc->do_something;
    mc->do_something = derived_do_something;
}

static const TypeInfo derived_type_info = {
    .name = TYPE_DERIVED,
    .parent = TYPE_MY,
    .class_size = sizeof(DerivedClass),
    .class_init = derived_class_init,
};

写在前面

以下是魔塔大陆世界的设定集的一部分,与现实世界物理法则没有任何联系。翻译这个的目的是为了使魔塔大陆的世界观更加严谨。如有错误请发邮件给我。

波动概论

物质的构成与定常D波