X86 emulation library
GitHub - wfeldt/libx86emu: x86 emulation library
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: set emu->timeout to max. seconds to run.
X86EMU_RUN_MAX_INSTR: set emu->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, executable
X86EMU_PERM_VALID: memory has been initialized (say, been written to)
X86EMU_ACC_{R,W,X}: memory has been read, written, executed
X86EMU_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:

Print STRING to log. STRING is not 0-zerminated.

Set trace flags.

Clear trace flags.

Dump emulator state. For flags, see x86emu_dump().

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.
