x86 emulation library
libx86emu is a small library to emulate x86 instructions. The focus here is not a complete emulation (go for qemu for this) but to cover enough for typical firmware blobs.
At the moment 'regular' 32-bit instructions are covered together with basic protected mode support.
Not done are fpu, mmx, or any of the other instruction set extensions.
The library lets you
- intercept any memory access or directly map real memory ranges
- intercept any i/o access, map real i/o ports, or block any real i/o
- intercept any interrupt
- provides hook to run after each instruction
- recognizes a special x86 instruction that can trigger logging
- has integrated logging to
- trace code execution, including register content and decoded instruction
- trace memory and i/o accesses
- provide statistics about accessed memory locations, i/o ports, and interrupts
Major versions
Programs should generally work fine with newer library versions without any changes (except re-compiling).
Version 3
Extend API to include CPUID and MSR handlers.
Version 2
Essentially the same API as version 1. The major difference is that version 2 is re-entrant (no global state variable).
Version 1
Version 1 relies internally on a global variable x86emu
holding the emulator state. It has been eliminated in version 2.
Downloads
Get the latest version from the openSUSE Build Service.
Examples
Have a look at this minimalistic demo program.
The library is used by hwinfo to emulate Video BIOS (VBE) calls.
API functions
x86emu_new
Create new emulation object
x86emu_t *x86emu_new(unsigned def_mem_perm, unsigned def_io_perm);
def_mem_perm are the default permissions for memory accesses, def_io_perm for io. See x86emu_set_perm(), x86emu_set_io_perm().
Free object later with x86emu_done().
x86emu_done
Delete emulation object
x86emu_t *x86emu_done(x86emu_t *emu);
Frees all memory; returns NULL;
x86emu_clone
Clone emulation object
x86emu_t *x86emu_clone(x86emu_t *emu);
Creates a copy of emu. Free the copy later with x86emu_done().
x86emu_reset
Reset cpu state
void x86emu_reset(x86emu_t *emu);
Does a normal cpu reset (clear registers, set cs:eip).
x86emu_run
Start emulation
unsigned x86emu_run(x86emu_t *emu, unsigned flags);
Flags:
X86EMU_RUN_TIMEOUT
X86EMU_RUN_MAX_INSTR
X86EMU_RUN_NO_EXEC
X86EMU_RUN_NO_CODE
X86EMU_RUN_LOOP
X86EMU_RUN_TIMEOUT
: setemu->timeout
to max. seconds to run.X86EMU_RUN_MAX_INSTR
: setemu->max_instr
to max. instructions to emulate.
Return value indicates why x86emu_run()
stopped (see flags).
x86emu_stop
Stop emulation
void x86emu_stop(x86emu_t *emu);
Use this function in callbacks (e.g. interrupt handler) to tell the emulator
to stop. The emulator returns from x86emu_run()
when the current instruction
has been finished.
x86emu_set_log
Set log buffer
void x86emu_set_log(x86emu_t *emu, char *buffer, unsigned buffer_size, x86emu_flush_func_t flush);
typedef void (* x86emu_flush_func_t)(x86emu_t *emu, char *buf, unsigned size);
If the log buffer is full, flush()
is called (if not NULL). The buffer is freed in x86emu_done()
.
x86emu_log
Write to log
void x86emu_log(x86emu_t *emu, const char *format, ...) __attribute__ ((format (printf, 1, 2)));
x86emu_clear_log
Clear log
void x86emu_clear_log(x86emu_t *emu, int flush);
Clear log buffer. If flush != 0, write current log via flush() function (see x86emu_set_log()).
x86emu_dump
Dump emulator state
void x86emu_dump(x86emu_t *emu, int flags);
Flags:
X86EMU_DUMP_REGS
X86EMU_DUMP_MEM
X86EMU_DUMP_ACC_MEM
X86EMU_DUMP_INV_MEM
X86EMU_DUMP_ATTR
X86EMU_DUMP_ASCII
X86EMU_DUMP_IO
X86EMU_DUMP_INTS
X86EMU_DUMP_TIME
Writes emulator state to log.
x86emu_set_perm
Memory permissions
void x86emu_set_perm(x86emu_t *emu, unsigned start, unsigned end, unsigned perm);
perm
is a bitmask of:
X86EMU_PERM_R
X86EMU_PERM_W
X86EMU_PERM_X
X86EMU_PERM_VALID
X86EMU_ACC_R
X86EMU_ACC_W
X86EMU_ACC_X
X86EMU_ACC_INVALID
X86EMU_PERM_{R,W,X}
: memory is readable, writable, executableX86EMU_PERM_VALID
: memory has been initialized (say, been written to)X86EMU_ACC_{R,W,X}
: memory has been read, written, executedX86EMU_ACC_INVALID
: there was an invalid access (e.g. tried to read but not readable)
x86emu_set_page
Direct memory access
void x86emu_set_page(x86emu_t *emu, unsigned offset, void *address);
Map memory area of X86EMU_PAGE_SIZE
size at address into emulator at offset. offset
must be X86EMU_PAGE_SIZE
aligned, address needs not.
Memory permissions still apply (via x86emu_set_perm()
).
If address is NULL, switch back to emulated memory.
x86emu_set_io_perm
io permissions
void x86emu_set_io_perm(x86emu_t *emu, unsigned start, unsigned end, unsigned perm);
perm
: see x86emu_set_perm()
.
x86emu_reset_access_stats
Reset memory access statistics
void x86emu_reset_access_stats(x86emu_t *emu);
Resets the X86EMU_ACC_*
bits for the whole memory (see x86emu_set_perm()
).
x86emu_set_code_handler
Execution hook
x86emu_code_handler_t x86emu_set_code_handler(x86emu_t *emu, x86emu_code_handler_t handler);
typedef int (* x86emu_code_handler_t)(x86emu_t *emu);
If defined, the function is called before a new instruction is decoded and emulated. If logging is enabled the current cpu state has already been logged. If the function returns a value != 0, the emulation is stopped.
x86emu_set_intr_handler
Set interrupt handler
x86emu_intr_handler_t x86emu_set_intr_handler(x86emu_t *emu, x86emu_intr_handler_t handler);
typedef int (* x86emu_intr_handler_t)(x86emu_t *emu, u8 num, unsigned type);
type:
INTR_TYPE_SOFT
INTR_TYPE_FAULT
and bitmask of:
INTR_MODE_RESTART
INTR_MODE_ERRCODE
If defined, the interrupt handler is called at the start of the interrupt handling procedure. The handler should return 1 to indicate the interrupt handling is complete and the emulator can skip its own interrupt processing or 0 to indicate the emulator should continue with normal interrupt processing.
x86emu_set_memio_handler
Set alternative callback function that handles memory and io accesses
x86emu_memio_handler_t x86emu_set_memio_handler(x86emu_t *emu, x86emu_memio_handler_t handler);
typedef unsigned (* x86emu_memio_handler_t)(x86emu_t *emu, u32 addr, u32 *val, unsigned type);
type: one of
X86EMU_MEMIO_8
X86EMU_MEMIO_16
X86EMU_MEMIO_32
X86EMU_MEMIO_8_NOPERM
and one of:
X86EMU_MEMIO_R
X86EMU_MEMIO_W
X86EMU_MEMIO_X
X86EMU_MEMIO_I
X86EMU_MEMIO_O
Returns old function.
x86emu_set_cpuid_handler
Execution hook
x86emu_cpuid_handler_t x86emu_set_cpuid_handler(x86emu_t *emu, x86emu_cpuid_handler_t handler);
typedef void (* x86emu_cpuid_handler_t)(x86emu_t *emu);
Set a callback function that handles the CPUID instruction. Allows the user to use the host's CPUID or provide a custom implementation to emulate a specific CPU.
Returns old function.
There's no default implementation. Without the handler installed the programm will raise an #UD exception.
x86emu_set_rdmsr_handler
Execution hook
x86emu_rdmsr_handler_t x86emu_set_rdmsr_handler(x86emu_t *emu, x86emu_rdmsr_handler_t handler);
typedef void (* x86emu_rdmsr_handler_t)(struct x86emu_s *);
Set alternative callback function that handles the RDMSR instruction. Allows the user to use the host's MSR or provide a custom implementation to emulate a specific platform.
Returns old function.
The default callback function uses the msr array in the x86emu_t structure to read MSRs from and updates the msr_perm array.
x86emu_set_wrmsr_handler
Execution hook
x86emu_wrmsr_handler_t x86emu_set_wrmsr_handler(x86emu_t *emu, x86emu_wrmsr_handler_t handler);
typedef void (* x86emu_wrmsr_handler_t)(struct x86emu_s *);
Set alternative callback function that handles the WRMSR instruction. Allows the user to use the host's MSR or provide a custom implementation to emulate a specific platform.
Returns old function.
The default callback function uses the msr array in the x86emu_t structure to write MSRs to and updates the msr_perm array.
x86emu_intr_raise
Raise an interrupt
void x86emu_intr_raise(x86emu_t *emu, u8 intr_nr, unsigned type, unsigned err);
The interrupt is handled before the next instruction. For type see
x86emu_set_intr_func()
; if INTR_MODE_ERRCODE
is set, err is the error
code pushed to the stack.
memory access functions
unsigned x86emu_read_byte(x86emu_t *emu, unsigned addr);
unsigned x86emu_read_byte_noperm(x86emu_t *emu, unsigned addr);
unsigned x86emu_read_word(x86emu_t *emu, unsigned addr);
unsigned x86emu_read_dword(x86emu_t *emu, unsigned addr);
void x86emu_write_byte(x86emu_t *emu, unsigned addr, unsigned val);
void x86emu_write_byte_noperm(x86emu_t *emu, unsigned addr, unsigned val);
void x86emu_write_word(x86emu_t *emu, unsigned addr, unsigned val);
void x86emu_write_dword(x86emu_t *emu, unsigned addr, unsigned val);
Convenience functions to access emulator memory. Memory access restrictions
(see x86emu_set_perm()
) apply except for x86emu_*_noperm()
which do not
check permissions.
x86emu_set_seg_register
Set segment register
void x86emu_set_seg_register(x86emu_t *emu, sel_t *seg, u16 val);
R_CS_SEL, R_DS_SEL, R_ES_SEL, R_FS_SEL, R_GS_SEL, R_SS_SEL
Example:
x86emu_set_seg_register(emu, emu->x86.R_CS_SEL, 0x7c0);
Debug instruction
If the X86EMU_TRACE_DEBUG
flags is set, the emulator interprets a special debug instruction:
db 0x67, 0xeb, LEN, DATA...
which is basically a short jump with address size prefix (an instruction normally not used). LEN is the size of DATA.
DATA can be:
db 0x01
db STRING
Print STRING to log. STRING is not 0-zerminated.
db 0x02
dd flags
Set trace flags.
db 0x03
dd flags
Clear trace flags.
db 0x04
dd flags
Dump emulator state. For flags, see x86emu_dump()
.
db 0x05
Reset memory access stats. See x86emu_reset_access_stats()
.
openSUSE Development
To build, simply run make
. Install with make install
.
Basically every new commit into the master branch of the repository will be auto-submitted to all current SUSE products. No further action is needed except accepting the pull request.
Submissions are managed by a SUSE internal jenkins node in the InstallTools tab.
Each time a new commit is integrated into the master branch of the repository, a new submit request is created to the openSUSE Build Service. The devel project is system:install:head.
*.changes
and version numbers are auto-generated from git commits, you don't have to worry about this.
The spec file is maintained in the Build Service only. If you need to change it for the master
branch,
submit to the
devel project
in the build service directly.
Development happens exclusively in the master
branch. The branch is used for all current products.
You can find more information about the changes auto-generation and the tools used for jenkis submissions in the linuxrc-devtools documentation.