Search code examples
carmqemubare-metal

Qemu plugin functions - how to access guest memory and registers


Background

Qemu version 4.2.0, released Dec '19, included a new functionality for something called TCG Plugins. They have a few examples in the tests/plugins directory, and the API is more or less defined in qemu-plugin.h.

This file defines two enumerated types, qemu_plugin_cb_flags and qemu_plugin_mem_rw, which are passed into functions that register callbacks. These enums seem to indicate whether the callbacks will read or write CPU registers or memory. However, all of the example plugins use QEMU_PLUGIN_CB_NO_REGS, and only 2 of the plugins use the memory access enum. hotpages.c and mem.c use QEMU_PLUGIN_MEM_RW as the default for registering a memory callback (qemu_plugin_register_vcpu_mem_cb). mem.c has an argument when the plugin is loaded to choose if it's read or write, however, it doesn't seem to make any difference in the callback function.

Question

My question is, how do I access the guest memory and registers from the plugin callback function? The API seems to indicate that it is possible, since the callback registering requires you to say if you will access them, and if it's RW or just read.

Are there any examples of using this part of the API? I realize this is a very new part of Qemu functionality.

Code

When you register a callback on an instruction, like in insn.c, you can get the virtual address of the instruction.

uint64_t insn_vaddr = qemu_plugin_insn_vaddr(insn);

I am running a baremetal ARM program, and this virtual address seems to correlate to the address of the instruction in the ELF file.

Inside memory callback functions, you can call qemu_plugin_get_hwaddr to get the hardware address of the memory access, but I'm not sure exactly what that struct represents.

Related

This answer is 7 years old, and suggests using the GDB interface. My question is specifically related to using the TCG plugin functionality.


Solution

  • I just got to that exact same problem. It seems like the qemu team really tried to prevent us from using CPU or memory stuff from a plugin. I tried including the headers I needed by modifying the Makefiles, but those headers aren't supposed to be included from external code like plugins. I couldn't manage to make it compile.

    As you said, there are flags that suggest that this is possible. My guess is that the feature wasn't implemented completely, maybe this will be possible soon enough.

    In the meantime, as we wait for a proper method to do this, here's how I hacked it:

    Getting a register

    In my case, the CPU is an ARM. I'll show the code first and then explain.

    void *qemu_get_cpu(int index);
    
    static uint32_t get_cpu_register(unsigned int cpu_index, unsigned int reg) {
        uint8_t* cpu = qemu_get_cpu(cpu_index);
        return *(uint32_t*)(cpu + 33488 + 5424 + reg * 4);
    }
    

    I first declare the qemu_get_cpu function, since we can't include its header. That function returns a CPUState*. Since my CPU is an ARM, I know that pointer is actually an ARMCPU*. As inheritance is implemented in qemu, a cast from that CPUState* to ARMCPU* is a no-op, so nothing to do there.

    Then, looking in target/arm/cpu.h, we can see that struct:

    struct ARMCPU {
        /*< private >*/
        CPUState parent_obj;
        /*< public >*/
    
        CPUNegativeOffsetState neg;
        CPUARMState env;
        // ...
    

    I used this compiler trick to get the size of CPUState and CPUNegativeOffsetState, which are in my case 33488 and 5424, respectively. This gives us the offset of the CPUARMState which starts as follow:

    typedef struct CPUARMState {
        /* Regs for current mode.  */
        uint32_t regs[16];
        // ...
    

    So the registers are just at the beginning, that's why I use reg * 4.

    Now that we can read our register, the next step is...

    Reading from memory

    This one is easier, I got it from gdbstub.c in qemu itself:

    void cpu_physical_memory_rw(uint64_t addr, uint8_t *buf,
                                uint64_t len, int is_write);
    
    // and in my function:
        char name[9] = {0};
        cpu_physical_memory_rw(name_addr, name, 8, 0);
    

    We just declare the method we need and call it. It seems that the method never fails, reading from unmapped memory does nothing.