Version | 2.0.0 |
---|
Official API document by kabeor
Unicorn Engine is a lightweight, multi-platform, multi-architecture CPU simulator framework, the current version is based on [Qemu](https://www.qemu. org/) 5.0.1 development, it can replace the execution of CPU simulation code, commonly used in program virtualization, malicious code analysis, Fuzzing, etc. This project is used in [Qiling Virtual Framework] (https://github.com/qilingframework/ qiling), Radare2 Reverse Analysis Framework, GEF (pwn analysis plugin for gdb), [Pwndbg] (https://github.com/pwndbg/pwndbg), Angr Symbol Execution Framework and many other famous projects.
Unicorn official website: http://www.unicorn-engine.org
git clone https://github.com/unicorn-engine/unicorn.git
Linux & MacOS
Ubuntu
sudo apt install cmake pkg-config
MacOS
brew install cmake pkg-config
Compile with the following command
mkdir build; cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make
Windows
Compiling with Microsoft MSVC compiler
//Install cmake and Microsoft Visual Studio (>=16.8)
//Compile using the following command in Visual Studio Command Prompt
mkdir build; cd build
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
nmake
Compile lib and dll (old) in VS GUI
Source code: https://github.com/unicorn-engine/unicorn/archive/master.zip
Unzip after download
The file structure is as follows:
. <- main engine core engine + README + compilation documentation COMPILE.TXT etc.
├── bindings <- bindings
│ ├── dotnet <- .Net bindings + test code
│ ├── go <- go bindings + test code
│ ├── haskell <- Haskell bindings + test code
│ ├── java <- Java bindings + test code
│ ├── pascal <- Pascal bindings + test code
│ ├── python <- Python bindings + test code
│ ├── ruby <- Ruby bindings + test code
│ ├── rust <- Rust bindings + test code
│ └── vb6 <- VB6 binding + test code
├── docs <- docs
├── glib_compat <- Compatibility library modified from glib 2.64.4
├── include <- C header files
├── msvc <- Microsoft Visual Studio Support (Windows)
├── qemu <- qemu (modified) source code
├── samples <- Unicorn usage example
└── tests <- C language test case
The following demonstrates Windows10 compiled with Visual Studio 2019
Open the msvc folder, the internal structure is as follows
VS opens the unicorn.sln project file, the solution automatically loads these
If you need all of them, just compile them directly. You only need a few of them. Right-click Solution -> Properties -> Configuration Properties -> Build Options and check the support items you need.
Multiple project actions can also be configured in the startup project as follows
After compilation, the unicorn.lib static compilation library and the unicorn.dll dynamic library will be generated in the Debug directory of the current folder, so that you can start developing with Unicorn
The latest compiled version currently provided by the official is 1.0.3 version, you can edit the source code of the latest version yourself to obtain more available APIs.
Win32: https://github.com/unicorn-engine/unicorn/releases/download/2.0.0/unicorn-2.0.0-win32.zip
Win64: https://github.com/unicorn-engine/unicorn/releases/download/2.0.0/unicorn-2.0.0-win64.zip
Note: Choosing x32 or x64 will affect the architecture developed later
Click Compile, go to unicorn\msvc\x32 or x64\Debug or Release to find unicorn.dll and unicorn.lib
Click [here] for other compilation methods (https://github.com/unicorn-engine/unicorn/blob/master/docs/COMPILE.md)
- Python module
pip install unicorn
//If the 1.x version has been installed, you can upgrade directly by the following command
pip install unicorn --upgrade
- MacOS HomeBrew Pack
brew install unicorn
(Take Windows VS2019 as an example) Create a new VS project, copy all the header files and compiled lib and dll files in ..\unicorn-master\include\unicorn to the main directory of the new project
In the VS solution, add the existing item unicorn.h to the header file, add unicorn.lib to the resource file, rebuild the solution
Next test our generated Unicorn engine
The main file code is as follows
Code
#include <iostream>
#include "unicorn/unicorn.h"
// command to simulate
#define X86_CODE32 "\x41\x4a" // INC ecx; DEC edx
// initial address
#define ADDRESS 0x1000000
int main()
{
uc_engine* uc;
uc_err err;
int r_ecx = 0x1234; // ECX register
int r_edx = 0x7890; // EDX register
printf("Emulate i386 code\n");
// X86-32bit mode initialization simulation
err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}
// Request 2MB of memory for the emulator
uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
// write the command to be emulated into memory
if (uc_mem_write(uc, ADDRESS, X86_CODE32, sizeof(X86_CODE32) - 1)) {
printf("Failed to write emulation code to memory, quit!\n");
return -1;
}
// initialize registers
uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx);
uc_reg_write(uc, UC_X86_REG_EDX, &r_edx);
printf(">>> ECX = 0x%x\n", r_ecx);
printf(">>> EDX = 0x%x\n", r_edx);
// mock code
err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(X86_CODE32) - 1, 0, 0);
if (err) {
printf("Failed on uc_emu_start() with error returned %u: %s\n",
err, uc_strerror(err));
}
// print register value
printf("Emulation done. Below is the CPU context\n");
uc_reg_read(uc, UC_X86_REG_ECX, &r_ecx);
uc_reg_read(uc, UC_X86_REG_EDX, &r_edx);
printf(">>> ECX = 0x%x\n", r_ecx);
printf(">>> EDX = 0x%x\n", r_edx);
uc_close(uc);
return 0;
}
The running result is as follows
ecx+1 and edx-1 simulate successfully.
Index
Architecture choice
Code
typedef enum uc_arch {
UC_ARCH_ARM = 1, // ARM architecture (including Thumb, Thumb-2)
UC_ARCH_ARM64, // ARM-64, also known as AArch64
UC_ARCH_MIPS, // Mips architecture
UC_ARCH_X86, // X86 architecture (including x86 & x86-64)
UC_ARCH_PPC, // PowerPC architecture
UC_ARCH_SPARC, // Sparc schema
UC_ARCH_M68K, // M68K architecture
UC_ARCH_RISCV, // RISCV architecture
UC_ARCH_S390X, // S390X architecture
UC_ARCH_TRICORE, // TriCore architecture
UC_ARCH_MAX,
} uc_arch;
Mode selection
Code
typedef enum uc_mode {
UC_MODE_LITTLE_ENDIAN = 0, // little endian mode (default)
UC_MODE_BIG_ENDIAN = 1 << 30, // big endian mode
//arm/arm64
UC_MODE_ARM = 0, // ARM mode
UC_MODE_THUMB = 1 << 4, // THUMB mode (including Thumb-2)
// Deprecated, use UC_ARM_CPU_* and uc_ctl instead
UC_MODE_MCLASS = 1 << 5, // ARM's Cortex-M series
UC_MODE_V8 = 1 << 6, // ARMv8 A32 encodings for ARM
UC_MODE_ARMBE8 = 1 << 10, // Big endian data and little endian code are only compatible with UC1 version
// arm (32bit) cpu type
// Deprecated, use UC_ARM_CPU_* and uc_ctl instead
UC_MODE_ARM926 = 1 << 7, // ARM926 CPU type
UC_MODE_ARM946 = 1 << 8, // ARM946 CPU type
UC_MODE_ARM1176 = 1 << 9, // ARM1176 CPU type
// mips
UC_MODE_MICRO = 1 << 4, // MicroMips mode (not supported yet)
UC_MODE_MIPS3 = 1 << 5, // Mips III ISA (not supported yet)
UC_MODE_MIPS32R6 = 1 << 6, // Mips32r6 ISA (not supported yet)
UC_MODE_MIPS32 = 1 << 2, // Mips32 ISA
UC_MODE_MIPS64 = 1 << 3, // Mips64 ISA
//x86/x64
UC_MODE_16 = 1 << 1, // 16-bit mode
UC_MODE_32 = 1 << 2, // 32-bit mode
UC_MODE_64 = 1 << 3, // 64-bit mode
// ppc
UC_MODE_PPC32 = 1 << 2, // 32-bit mode
UC_MODE_PPC64 = 1 << 3, // 64-bit mode (not supported yet)
UC_MODE_QPX = 1 << 4, // Quad Processing eXtensions mode (not supported yet)
// sparc
UC_MODE_SPARC32 = 1 << 2, // 32-bit mode
UC_MODE_SPARC64 = 1 << 3, // 64-bit mode
UC_MODE_V9 = 1 << 4, // SparcV9 mode (not supported yet)
// riscv
UC_MODE_RISCV32 = 1 << 2, // 32-bit mode
UC_MODE_RISCV64 = 1 << 3, // 64-bit mode
// m68k
} uc_mode;
Error type, which is the return value of uc_errno()
Code
typedef enum uc_err {
UC_ERR_OK = 0, // no error
UC_ERR_NOMEM, // out of memory: uc_open(), uc_emulate()
UC_ERR_ARCH, // unsupported architecture: uc_open()
UC_ERR_HANDLE, // unavailable handle
UC_ERR_MODE, // Unavailable/unsupported architecture: uc_open()
UC_ERR_VERSION, // unsupported version (or language binding)
UC_ERR_READ_UNMAPPED, // Exit simulation due to reading on unmapped memory: uc_emu_start()
UC_ERR_WRITE_UNMAPPED, // Exit simulation due to writing on unmapped memory: uc_emu_start()
UC_ERR_FETCH_UNMAPPED, // Exit simulation due to fetching data in unmapped memory: uc_emu_start()
UC_ERR_HOOK, // invalid hook type: uc_hook_add()
UC_ERR_INSN_INVALID, // Exit simulation due to invalid instruction: uc_emu_start()
UC_ERR_MAP, // invalid memory map: uc_mem_map()
UC_ERR_WRITE_PROT, // Stop simulation due to conflict with UC_MEM_WRITE_PROT: uc_emu_start()
UC_ERR_READ_PROT, // Stop simulation due to conflict with UC_MEM_READ_PROT: uc_emu_start()
UC_ERR_FETCH_PROT, // Stop simulation due to conflict with UC_MEM_FETCH_PROT: uc_emu_start()
UC_ERR_ARG, // invalid argument supplied to uc_xxx function
UC_ERR_READ_UNALIGNED, // unaligned read
UC_ERR_WRITE_UNALIGNED, // unaligned write
UC_ERR_FETCH_UNALIGNED, // unaligned fetch
UC_ERR_HOOK_EXIST, // Hook for this event already exists
UC_ERR_RESOURCE, // Insufficient resources: uc_emu_start()
UC_ERR_EXCEPTION, // unhandled CPU exception
} uc_err;
All memory access types for UC_HOOK_MEM_*
Code
typedef enum uc_mem_type {
UC_MEM_READ = 16, // memory is read from..
UC_MEM_WRITE, // memory write to..
UC_MEM_FETCH, // memory is fetched
UC_MEM_READ_UNMAPPED, // unmapped memory read from ..
UC_MEM_WRITE_UNMAPPED, // Unmapped memory is written to..
UC_MEM_FETCH_UNMAPPED, // unmapped memory is fetched
UC_MEM_WRITE_PROT, // Memory write protected, but mapped
UC_MEM_READ_PROT, // Memory read protected, but mapped
UC_MEM_FETCH_PROT, // memory not executable, but mapped
UC_MEM_READ_AFTER, // memory is read from (address successfully accessed)
} uc_mem_type;
All hook type parameters of uc_hook_add()
Code
typedef enum uc_hook_type {
// Hook all interrupt/syscall events
UC_HOOK_INTR = 1 << 0,
// Hook a specific instruction - only a very small subset of instructions is supported
UC_HOOK_INSN = 1 << 1,
// Hook a piece of code
UC_HOOK_CODE = 1 << 2,
// Hook basic block
UC_HOOK_BLOCK = 1 << 3,
// Hook for reading memory on unmapped memory
UC_HOOK_MEM_READ_UNMAPPED = 1 << 4,
// Hook invalid memory write event
UC_HOOK_MEM_WRITE_UNMAPPED = 1 << 5,
// invalid memory for Hook execution event
UC_HOOK_MEM_FETCH_UNMAPPED = 1 << 6,
// Hook read protected memory
UC_HOOK_MEM_READ_PROT = 1 << 7,
// Hook write-protected memory
UC_HOOK_MEM_WRITE_PROT = 1 << 8,
// Hook memory on non-executable memory
UC_HOOK_MEM_FETCH_PROT = 1 << 9,
// Hook memory read event
UC_HOOK_MEM_READ = 1 << 10,
// Hook memory write event
UC_HOOK_MEM_WRITE = 1 << 11,
// Hook memory acquisition execution event
UC_HOOK_MEM_FETCH = 1 << 12,
// Hook memory read events, only addresses that can be successfully accessed are allowed
// Callback will be triggered after successful read
UC_HOOK_MEM_READ_AFTER = 1 << 13,
// Hook invalid instruction exception
UC_HOOK_INSN_INVALID = 1 << 14,
// Hook new (execution flow) edge generating events. May be useful in program analysis.
// Note: This Hook differs from UC_HOOK_BLOCK in two ways:
// 1. This Hook is called before the instruction is executed.
// 2. This Hook is only called when the generated event is triggered.
UC_HOOK_EDGE_GENERATED = 1 << 15,
// Hook specific tcg opcode. Usage is similar to UC_HOOK_INSN.
UC_HOOK_TCG_OPCODE = 1 << 16,
} uc_hook_type;
Macro definition Hook type
Code
// Hook events for all unmapped memory accesses
#define UC_HOOK_MEM_UNMAPPED (UC_HOOK_MEM_READ_UNMAPPED + UC_HOOK_MEM_WRITE_UNMAPPED + UC_HOOK_MEM_FETCH_UNMAPPED)
// Hook all illegal access events to protected memory
#define UC_HOOK_MEM_PROT (UC_HOOK_MEM_READ_PROT + UC_HOOK_MEM_WRITE_PROT + UC_HOOK_MEM_FETCH_PROT)
// Hook all events that read memory illegally
#define UC_HOOK_MEM_READ_INVALID (UC_HOOK_MEM_READ_PROT + UC_HOOK_MEM_READ_UNMAPPED)
// Hook all events that are illegally written to memory
#define UC_HOOK_MEM_WRITE_INVALID (UC_HOOK_MEM_WRITE_PROT + UC_HOOK_MEM_WRITE_UNMAPPED)
// Hook all illegal memory acquisition events
#define UC_HOOK_MEM_FETCH_INVALID (UC_HOOK_MEM_FETCH_PROT + UC_HOOK_MEM_FETCH_UNMAPPED)
// Hook all illegal memory access events
#define UC_HOOK_MEM_INVALID (UC_HOOK_MEM_UNMAPPED + UC_HOOK_MEM_PROT)
// Hook events for all valid memory accesses
// Note: UC_HOOK_MEM_READ fires before UC_HOOK_MEM_READ_PROT and UC_HOOK_MEM_READ_UNMAPPED,
// So this Hook might trigger some invalid reads.
#define UC_HOOK_MEM_VALID (UC_HOOK_MEM_READ + UC_HOOK_MEM_WRITE + UC_HOOK_MEM_FETCH)
Memory area mapped by uc_mem_map() and uc_mem_map_ptr() Use uc_mem_regions() to retrieve a list of this memory region
Code
typedef struct uc_mem_region {
uint64_t begin; // area start address (inclusive)
uint64_t end; // area end address (inclusive)
uint32_t perms; // memory permissions for the region
} uc_mem_region;
All query type parameters of uc_query()
Code
typedef enum uc_query_type {
// Dynamically query the current hardware mode
UC_QUERY_MODE = 1,
UC_QUERY_PAGE_SIZE, // pagesize of query engine instance
UC_QUERY_ARCH, // the schema type of the query engine instance
UC_QUERY_TIMEOUT, // Query whether to stop simulation due to timeout (if result = True it means yes)
} uc_query_type;
All query type parameters of uc_ctl()
Code
// uc_ctl implementation is similar to Linux ioctl but slightly different
//
// uc_control_type is organized in uc_ctl as follows:
//
// R/W NR Reserved Type
// [ ] [ ] [ ] [ ]
// 31 30 29 26 25 16 15 0
//
// @R/W: Whether the operation is a read/write access.
// @NR: Number of parameters.
// @Reserved: 0, reserved for future extensions.
// @Type: enum in uc_control_type.
// No input and output parameters.
#define UC_CTL_IO_NONE (0)
// Only input parameters for a write operation.
#define UC_CTL_IO_WRITE (1)
// Only output parameters for a read operation.
#define UC_CTL_IO_READ (2)
// The parameter contains both read and write operations.
#define UC_CTL_IO_READ_WRITE (UC_CTL_IO_WRITE | UC_CTL_IO_READ)
#define UC_CTL(type, nr, rw) \
(uc_control_type)((type) | ((nr) << 26) | ((rw) << 30))
#define UC_CTL_NONE(type, nr) UC_CTL(type, nr, UC_CTL_IO_NONE)
#define UC_CTL_READ(type, nr) UC_CTL(type, nr, UC_CTL_IO_READ)
#define UC_CTL_WRITE(type, nr) UC_CTL(type, nr, UC_CTL_IO_WRITE)
#define UC_CTL_READ_WRITE(type, nr) UC_CTL(type, nr, UC_CTL_IO_READ_WRITE)
// The control chain is organized in a tree structure.
// If a control state does not have `Set` or `Get` filled in for @args, then r/o or w/o.
typedef enum uc_control_type {
// current mode.
// Read: @args = (int*)
UC_CTL_UC_MODE = 0,
// current page size.
// Write: @args = (uint32_t)
// Read: @args = (uint32_t*)
UC_CTL_UC_PAGE_SIZE,
// Current schema.
// Read: @args = (int*)
UC_CTL_UC_ARCH,
// Current timeout.
// Read: @args = (uint64_t*)
UC_CTL_UC_TIMEOUT,
// Multiple exit points are allowed.
// Without this control state, read/set exit points will not work.
// Write: @args = (int)
UC_CTL_UC_USE_EXITS,
// Current input number.
// Read: @args = (size_t*)
UC_CTL_UC_EXITS_CNT,
// current input.
// Write: @args = (uint64_t* exits, size_t len)
// @len = UC_CTL_UC_EXITS_CNT
// Read: @args = (uint64_t* exits, size_t len)
// @len = UC_CTL_UC_EXITS_CNT
UC_CTL_UC_EXITS,
// Set the cpu mode of the uc instance.
// Note this option can only be set before any Unicorn
// API is called except for uc_open.
// Write: @args = (int)
// Read: @args = (int*)
UC_CTL_CPU_MODEL,
// Query the tb (translation block) cache for a specific address
// Read: @args = (uint64_t, uc_tb*)
UC_CTL_TB_REQUEST_CACHE,
// disable tb (translation block) cache for specific address
// Write: @args = (uint64_t, uint64_t)
UC_CTL_TB_REMOVE_CACHE,
// disable all tb (translation blocks)
// no parameters
UC_CTL_TB_FLUSH
} uc_control_type;
Used with uc_context_*() to manage opaque storage of CPU contexts
Code
struct uc_context;
typedef struct uc_context uc_context;
Permissions for the newly mapped area
Code
typedef enum uc_prot {
UC_PROT_NONE = 0, //None
UC_PROT_READ = 1, //read
UC_PROT_WRITE = 2, //write
UC_PROT_EXEC = 4, //executable
UC_PROT_ALL = 7, //all permissions
} uc_prot;
Index
unsigned int uc_version(unsigned int *major, unsigned int *minor);
Used to return the major and minor version information of the Unicorn API
@major: API major version number
@minor: API minor version number
@return hexadecimal number, calculation method (major << 8 | minor)
Hint: The return value can be compared with the macro UC_MAKE_VERSION
source code implementation
unsigned int uc_version(unsigned int *major, unsigned int *minor)
{
if (major != NULL && minor != NULL) {
*major = UC_API_MAJOR; //macro
*minor = UC_API_MINOR; //macro
}
return (UC_API_MAJOR << 8) + UC_API_MINOR; //(major << 8 | minor)
}
Unchangeable after compilation, custom versions are not accepted
Example of use:
#include <iostream>
#include "unicorn/unicorn.h"
using namespace std;
int main()
{
unsigned int version;
version = uc_version(NULL,NULL);
cout << hex << version << endl;
return 0;
}
output:
get version number 1.0.0
bool uc_arch_supported(uc_arch arch);
Sure Unicorn Whether the current architecture is supported
@arch: Architecture type (UC_ARCH_*)
@return Return if supported True
source code implementation
bool uc_arch_supported(uc_arch arch)
{
switch (arch) {
#ifdef UNICORN_HAS_ARM
case UC_ARCH_ARM: return true;
#endif
#ifdef UNICORN_HAS_ARM64
case UC_ARCH_ARM64: return true;
#endif
#ifdef UNICORN_HAS_M68K
case UC_ARCH_M68K: return true;
#endif
#ifdef UNICORN_HAS_MIPS
case UC_ARCH_MIPS: return true;
#endif
#ifdef UNICORN_HAS_PPC
case UC_ARCH_PPC: return true;
#endif
#ifdef UNICORN_HAS_SPARC
case UC_ARCH_SPARC: return true;
#endif
#ifdef UNICORN_HAS_X86
case UC_ARCH_X86: return true;
#endif
/* invalid or disabled schema */
default: return false;
}
}
Example of use:
#include <iostream>
#include "unicorn/unicorn.h"
using namespace std;
int main()
{
cout << "support UC_ARCH_X86 Architecture:" << uc_arch_supported(UC_ARCH_X86) << endl;
return 0;
}
output:
uc_err uc_open(uc_arch arch, uc_mode mode, uc_engine **uc);
create new Unicorn example
@arch: Architecture type (UC_ARCH_*)
@mode: hardware mode Depend on UC_MODE_* combination
@uc: Orientation uc_enginepointer, Update when you return
@returnreturn on success UC_ERR_OK , otherwise return uc_err Other error types for enumeration
source code implementation
uc_err uc_open(uc_arch arch, uc_mode mode, uc_engine **result)
{
struct uc_struct *uc;
if (arch < UC_ARCH_MAX) {
uc = calloc(1, sizeof(*uc)); //申请内存
if (!uc) {
// 内存不足
return UC_ERR_NOMEM;
}
uc->errnum = UC_ERR_OK;
uc->arch = arch;
uc->mode = mode;
// 初始化
// uc->ram_list = { .blocks = QTAILQ_HEAD_INITIALIZER(ram_list.blocks) };
uc->ram_list.blocks.tqh_first = NULL;
uc->ram_list.blocks.tqh_last = &(uc->ram_list.blocks.tqh_first);
uc->memory_listeners.tqh_first = NULL;
uc->memory_listeners.tqh_last = &uc->memory_listeners.tqh_first;
uc->address_spaces.tqh_first = NULL;
uc->address_spaces.tqh_last = &uc->address_spaces.tqh_first;
switch(arch) { // Preprocessing according to the architecture
default:
break;
#ifdef UNICORN_HAS_M68K
case UC_ARCH_M68K:
if ((mode & ~UC_MODE_M68K_MASK) ||
!(mode & UC_MODE_BIG_ENDIAN)) {
free(uc);
return UC_ERR_MODE;
}
uc->init_arch = m68k_uc_init;
break;
#endif
#ifdef UNICORN_HAS_X86
case UC_ARCH_X86:
if ((mode & ~UC_MODE_X86_MASK) ||
(mode & UC_MODE_BIG_ENDIAN) ||
!(mode & (UC_MODE_16|UC_MODE_32|UC_MODE_64))) {
free(uc);
return UC_ERR_MODE;
}
uc->init_arch = x86_uc_init;
break;
#endif
#ifdef UNICORN_HAS_ARM
case UC_ARCH_ARM:
if ((mode & ~UC_MODE_ARM_MASK)) {
free(uc);
return UC_ERR_MODE;
}
if (mode & UC_MODE_BIG_ENDIAN) {
uc->init_arch = armeb_uc_init;
} else {
uc->init_arch = arm_uc_init;
}
if (mode & UC_MODE_THUMB)uc->thumb = 1;
break;
#endif
#ifdef UNICORN_HAS_ARM64
case UC_ARCH_ARM64:
if (mode & ~UC_MODE_ARM_MASK) {
free(uc);
return UC_ERR_MODE;
}
if (mode & UC_MODE_BIG_ENDIAN) {
uc->init_arch = arm64eb_uc_init;
} else {
uc->init_arch = arm64_uc_init;
}
break;
#endif
#if defined(UNICORN_HAS_MIPS) || defined(UNICORN_HAS_MIPSEL) || defined(UNICORN_HAS_MIPS64) || defined(UNICORN_HAS_MIPS64EL)
case UC_ARCH_MIPS:
if ((mode & ~UC_MODE_MIPS_MASK) ||
!(mode & (UC_MODE_MIPS32|UC_MODE_MIPS64))) {
free(uc);
return UC_ERR_MODE;
}
if (mode & UC_MODE_BIG_ENDIAN) {
#ifdef UNICORN_HAS_MIPS
if (mode & UC_MODE_MIPS32)
uc->init_arch = mips_uc_init;
#endif
#ifdef UNICORN_HAS_MIPS64
if (mode & UC_MODE_MIPS64)
uc->init_arch = mips64_uc_init;
#endif
} else { // 小端序
#ifdef UNICORN_HAS_MIPSEL
if (mode & UC_MODE_MIPS32)
uc->init_arch = mipsel_uc_init;
#endif
#ifdef UNICORN_HAS_MIPS64EL
if (mode & UC_MODE_MIPS64)
uc->init_arch = mips64el_uc_init;
#endif
}
break;
#endif
#ifdef UNICORN_HAS_SPARC
case UC_ARCH_SPARC:
if ((mode & ~UC_MODE_SPARC_MASK) ||
!(mode & UC_MODE_BIG_ENDIAN) ||
!(mode & (UC_MODE_SPARC32|UC_MODE_SPARC64))) {
free(uc);
return UC_ERR_MODE;
}
if (mode & UC_MODE_SPARC64)
uc->init_arch = sparc64_uc_init;
else
uc->init_arch = sparc_uc_init;
break;
#endif
}
if (uc->init_arch == NULL) {
return UC_ERR_ARCH;
}
if (machine_initialize(uc))
return UC_ERR_RESOURCE;
*result = uc;
if (uc->reg_reset)
uc->reg_reset(uc);
return UC_ERR_OK;
} else {
return UC_ERR_ARCH;
}
}
Notice: uc_open Will request heap memory,Must use after use uc_close freed,Otherwise leakage will occur
Example of use:
#include <iostream>
#include "unicorn/unicorn.h"
using namespace std;
int main()
{
uc_engine* uc;
uc_err err;
//// initialization X86-32bit pattern simulator
err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc Engine created successfully" << endl;
//// closure uc
err = uc_close(uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_close() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc Engine shut down successfully" << endl;
return 0;
}
output
uc_err uc_close(uc_engine *uc);
Closing a uc instance will free the memory. It cannot be restored after closing.
@uc: pointer to the pointer returned by uc_open()
@return Returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_close(uc_engine *uc)
{
int i;
struct list_item *cur;
struct hook *hook;
// Clean up internal data
if (uc->release)
uc->release(uc->tcg_ctx);
g_free(uc->tcg_ctx);
// clean up CPU.
g_free(uc->cpu->tcg_as_listener);
g_free(uc->cpu->thread);
// clean up all objects.
OBJECT(uc->machine_state->accelerator)->ref = 1;
OBJECT(uc->machine_state)->ref = 1;
OBJECT(uc->owner)->ref = 1;
OBJECT(uc->root)->ref = 1;
object_unref(uc, OBJECT(uc->machine_state->accelerator));
object_unref(uc, OBJECT(uc->machine_state));
object_unref(uc, OBJECT(uc->cpu));
object_unref(uc, OBJECT(&uc->io_mem_notdirty));
object_unref(uc, OBJECT(&uc->io_mem_unassigned));
object_unref(uc, OBJECT(&uc->io_mem_rom));
object_unref(uc, OBJECT(uc->root));
// free memory
g_free(uc->system_memory);
// release the associated thread
if (uc->qemu_thread_data)
g_free(uc->qemu_thread_data);
// free other data
free(uc->l1_map);
if (uc->bounce.buffer) {
free(uc->bounce.buffer);
}
g_hash_table_foreach(uc->type_table, free_table, uc);
g_hash_table_destroy(uc->type_table);
for (i = 0; i < DIRTY_MEMORY_NUM; i++) {
free(uc->ram_list.dirty_memory[i]);
}
// Free hook and hook list
for (i = 0; i < UC_HOOK_MAX; i++) {
cur = uc->hook[i].head;
// hook Can exist in multiple lists, and the release time can be obtained by counting
while (cur) {
hook = (struct hook *)cur->data;
if (--hook->refs == 0) {
free(hook);
}
cur = cur->next;
}
list_clear(&uc->hook[i]);
}
free(uc->mapped_blocks);
// finally release uc itself
memset(uc, 0, sizeof(*uc));
free(uc);
return UC_ERR_OK;
}
The usage example is the same as uc_open()
uc_err uc_query(uc_engine *uc, uc_query_type type, size_t *result);
Query the internal state of the engine
@uc: the handle returned by uc_open()
@type: the type of the enumeration in uc_query_type
@result: pointer to hold the internal state being queried
@return: Returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_query(uc_engine *uc, uc_query_type type, size_t *result)
{
if (type == UC_QUERY_PAGE_SIZE) {
*result = uc->target_page_size;
return UC_ERR_OK;
}
if (type == UC_QUERY_ARCH) {
*result = uc->arch;
return UC_ERR_OK;
}
switch(uc->arch) {
#ifdef UNICORN_HAS_ARM
case UC_ARCH_ARM:
return uc->query(uc, type, result);
#endif
default:
return UC_ERR_ARG;
}
return UC_ERR_OK;
}
Example of use:
#include <iostream>
#include "unicorn/unicorn.h"
using namespace std;
int main()
{
uc_engine* uc;
uc_err err;
//// Initialize emulator in X86-32bit mode
err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc instance created successfully" << endl;
size_t result[] = {0};
err = uc_query(uc, UC_QUERY_ARCH, result); // query schema
if (!err)
cout << "Query success: " << *result << endl;
err = uc_close(uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_close() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc instance closed successfully" << endl;
return 0;
}
output
The result of the schema query is 4, which corresponds to UC_ARCH_X86
uc_err uc_errno(uc_engine *uc);
When an API function fails, report the last error number. Once accessed, uc_errno may not retain the original value.
@uc: the handle returned by uc_open()
@return: Returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_errno(uc_engine *uc)
{
return uc->errnum;
}
Example of use:
#include <iostream>
#include "unicorn/unicorn.h"
using namespace std;
int main()
{
uc_engine* uc;
uc_err err;
err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc instance created successfully" << endl;
err = uc_errno(uc);
cout << "Error number: " << err << endl;
err = uc_close(uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_close() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc instance closed successfully" << endl;
return 0;
}
output
No error, output error number is 0
const char *uc_strerror(uc_err code);
Returns an explanation for the given error number
@code: error number
@return: string pointer to the interpretation of the given error number
source code implementation
const char *uc_strerror(uc_err code)
{
switch(code) {
default:
return "Unknown error code";
case UC_ERR_OK:
return "OK (UC_ERR_OK)";
case UC_ERR_NOMEM:
return "No memory available or memory not present (UC_ERR_NOMEM)";
case UC_ERR_ARCH:
return "Invalid/unsupported architecture (UC_ERR_ARCH)";
case UC_ERR_HANDLE:
return "Invalid handle (UC_ERR_HANDLE)";
case UC_ERR_MODE:
return "Invalid mode (UC_ERR_MODE)";
case UC_ERR_VERSION:
return "Different API version between core & binding (UC_ERR_VERSION)";
case UC_ERR_READ_UNMAPPED:
return "Invalid memory read (UC_ERR_READ_UNMAPPED)";
case UC_ERR_WRITE_UNMAPPED:
return "Invalid memory write (UC_ERR_WRITE_UNMAPPED)";
case UC_ERR_FETCH_UNMAPPED:
return "Invalid memory fetch (UC_ERR_FETCH_UNMAPPED)";
case UC_ERR_HOOK:
return "Invalid hook type (UC_ERR_HOOK)";
case UC_ERR_INSN_INVALID:
return "Invalid instruction (UC_ERR_INSN_INVALID)";
case UC_ERR_MAP:
return "Invalid memory mapping (UC_ERR_MAP)";
case UC_ERR_WRITE_PROT:
return "Write to write-protected memory (UC_ERR_WRITE_PROT)";
case UC_ERR_READ_PROT:
return "Read from non-readable memory (UC_ERR_READ_PROT)";
case UC_ERR_FETCH_PROT:
return "Fetch from non-executable memory (UC_ERR_FETCH_PROT)";
case UC_ERR_ARG:
return "Invalid argument (UC_ERR_ARG)";
case UC_ERR_READ_UNALIGNED:
return "Read from unaligned memory (UC_ERR_READ_UNALIGNED)";
case UC_ERR_WRITE_UNALIGNED:
return "Write to unaligned memory (UC_ERR_WRITE_UNALIGNED)";
case UC_ERR_FETCH_UNALIGNED:
return "Fetch from unaligned memory (UC_ERR_FETCH_UNALIGNED)";
case UC_ERR_RESOURCE:
return "Insufficient resource (UC_ERR_RESOURCE)";
case UC_ERR_EXCEPTION:
return "Unhandled CPU exception (UC_ERR_EXCEPTION)";
case UC_ERR_TIMEOUT:
return"Emulation timed out (UC_ERR_TIMEOUT)";
}
}
Example of use:
#include <iostream>
#include "unicorn/unicorn.h"
using namespace std;
int main()
{
uc_engine* uc;
uc_err err;
err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc instance created successfully" << endl;
err = uc_errno(uc);
cout << "Error number: " << err << " Error description: " << uc_strerror(err) <<endl;
err = uc_close(uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_close() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc instance closed successfully" << endl;
return 0;
}
output
uc_err uc_reg_write(uc_engine *uc, int regid, const void *value);
write value to register
@uc: the handle returned by uc_open()
@regid: the ID of the register to be modified
@value: pointer to the value to which the register will be modified
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_reg_write(uc_engine *uc, int regid, const void *value)
{
return uc_reg_write_batch(uc, ®id, (void *const *)&value, 1);
}
uc_err uc_reg_write_batch(uc_engine *uc, int *ids, void *const *vals, int count)
{
int ret = UC_ERR_OK;
if (uc->reg_write)
ret = uc->reg_write(uc, (unsigned int *)ids, vals, count); //write in the structure
else
return UC_ERR_EXCEPTION;
return ret;
}
Example of use:
#include <iostream>
#include "unicorn/unicorn.h"
using namespace std;
int main()
{
uc_engine* uc;
uc_err err;
err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc instance created successfully" << endl;
int r_eax = 0x12;
err = uc_reg_write(uc, UC_X86_REG_ECX, &r_eax);
if (!err)
cout << "Write successful: " << r_eax << endl;
err = uc_close(uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_close() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc instance closed successfully" << endl;
return 0;
}
output
uc_err uc_reg_read(uc_engine *uc, int regid, void *value);
read register value
@uc: the handle returned by uc_open()
@regid: the register ID to be read
@value: pointer to the holding register value
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_reg_read(uc_engine *uc, int regid, void *value)
{
return uc_reg_read_batch(uc, ®id, &value, 1);
}
uc_err uc_reg_read_batch(uc_engine *uc, int *ids, void **vals, int count)
{
if (uc->reg_read)
uc->reg_read(uc, (unsigned int *)ids, vals, count);
else
return -1;
return UC_ERR_OK;
}
Example of use:
#include <iostream>
#include "unicorn/unicorn.h"
using namespace std;
int main()
{
uc_engine* uc;
uc_err err;
err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc instance created successfully" << endl;
int r_eax = 0x12;
err = uc_reg_write(uc, UC_X86_REG_ECX, &r_eax);
if (!err)
cout << "Write successful: " << r_eax << endl;
int recv_eax;
err = uc_reg_read(uc, UC_X86_REG_ECX, &recv_eax);
if (!err)
cout << "Read successfully: " << recv_eax << endl;
err = uc_close(uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_close() with error returned: %u\n", err);
return -1;
}
if (!err)
cout << "uc instance closed successfully" << endl;
return 0;
}
output
uc_err uc_reg_write_batch(uc_engine *uc, int *regs, void *const *vals, int count);
Write multiple values to multiple registers at the same time
@uc: the handle returned by uc_open()
@regid: an array storing multiple register IDs that will be written to
@value: pointer to an array holding multiple values
@count: length of the *regs and *vals arrays
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_reg_write_batch(uc_engine *uc, int *ids, void *const *vals, int count)
{
int ret = UC_ERR_OK;
if (uc->reg_write)
ret = uc->reg_write(uc, (unsigned int *)ids, vals, count);
else
return UC_ERR_EXCEPTION;
return ret;
}
Example of use:
#include <iostream>
#include <string>
#include "unicorn/unicorn.h"
using namespace std;
int syscall_abi[] = {
UC_X86_REG_RAX, UC_X86_REG_RDI, UC_X86_REG_RSI, UC_X86_REG_RDX,
UC_X86_REG_R10, UC_X86_REG_R8, UC_X86_REG_R9
};
uint64_t vals[7] = { 200, 10, 11, 12, 13, 14, 15 };
void* ptrs[7];
int main()
{
int i;
uc_err err;
uc_engine* uc;
// set up register pointers
for (i = 0; i < 7; i++) {
ptrs[i] = &vals[i];
}
if ((err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc))) {
uc_perror("uc_open", err);
return 1;
}
// reg_write_batch
printf("reg_write_batch({200, 10, 11, 12, 13, 14, 15})\n");
if ((err = uc_reg_write_batch(uc, syscall_abi, ptrs, 7))) {
uc_perror("uc_reg_write_batch", err);
return 1;
}
// reg_read_batch
memset(vals, 0, sizeof(vals));
if ((err = uc_reg_read_batch(uc, syscall_abi, ptrs, 7))) {
uc_perror("uc_reg_read_batch", err);
return 1;
}
printf("reg_read_batch = {");
for (i = 0; i < 7; i++) {
if (i != 0) printf(", ");
printf("%" PRIu64, vals[i]);
}
printf("}\n");
uint64_t var[7] = { 0 };
for (int i = 0; i < 7; i++)
{
cout << syscall_abi[i] << " ";
printf("%" PRIu64, vals[i]);
cout << endl;
}
return 0;
}
output
uc_err uc_reg_read_batch(uc_engine *uc, int *regs, void **vals, int count);
Read the values of multiple registers at the same time.
@uc: the handle returned by uc_open()
@regid: an array storing multiple register IDs that will be read
@value: pointer to an array holding multiple values
@count: length of the *regs and *vals arrays
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_reg_read_batch(uc_engine *uc, int *ids, void **vals, int count)
{
if (uc->reg_read)
uc->reg_read(uc, (unsigned int *)ids, vals, count);
else
return -1;
return UC_ERR_OK;
}
The usage example is the same as uc_reg_write_batch().
uc_err uc_mem_write(uc_engine *uc, uint64_t address, const void *bytes, size_t size);
Writes a piece of bytecode in memory.
@uc: the handle returned by uc_open()
@address: the starting address of the write byte
@bytes: a pointer to a pointer containing the data to be written to memory
@size: Size of memory to write.
Note: @bytes must be large enough to contain @size bytes.
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_mem_write(uc_engine *uc, uint64_t address, const void *_bytes, size_t size)
{
size_t count = 0, len;
const uint8_t *bytes = _bytes;
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
if (!check_mem_area(uc, address, size))
return UC_ERR_WRITE_UNMAPPED;
// Memory regions can overlap adjacent memory blocks
while(count < size) {
MemoryRegion *mr = memory_mapping(uc, address);
if (mr) {
uint32_t operms = mr->perms;
if (!(operms & UC_PROT_WRITE)) // no write protection
// mark as writable
uc->readonly_mem(mr, false);
len = (size_t)MIN(size - count, mr->end - address);
if (uc->write_mem(&uc->as, address, bytes, len) == false)
break;
if (!(operms & UC_PROT_WRITE)) // no write protection
// set write protection
uc->readonly_mem(mr, true);
count += len;
address += len;
bytes += len;
} else // this address has not been mapped yet
break;
}
if (count == size)
return UC_ERR_OK;
else
return UC_ERR_WRITE_UNMAPPED;
}
Example of use:
#include <iostream>
#include <string>
#include "unicorn/unicorn.h"
using namespace std;
#define X86_CODE32 "\x41\x4a" // INC ecx; DEC edx
#define ADDRESS 0x1000
int main()
{
uc_engine* uc;
uc_err err;
err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}
uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
if (uc_mem_write(uc, ADDRESS, X86_CODE32, sizeof(X86_CODE32) - 1)) {
printf("Failed to write emulation code to memory, quit!\n");
return -1;
}
uint32_t code;
if(uc_mem_read(uc,ADDRESS,&code, sizeof(code))) {
printf("Failed to read emulation code to memory, quit!\n");
return -1;
}
cout << hex << code << endl;
err = uc_close(uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_close() with error returned: %u\n", err);
return -1;
}
return 0;
}
output
uc_err uc_mem_read(uc_engine *uc, uint64_t address, void *bytes, size_t size);
Read bytes from memory.
@uc: the handle returned by uc_open()
@address: the starting address of the read byte
@bytes: a pointer to a pointer containing the data to read from memory
@size: Size of memory to read.
Note: @bytes must be large enough to contain @size bytes.
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_mem_read(uc_engine *uc, uint64_t address, void *_bytes, size_t size)
{
size_t count = 0, len;
uint8_t *bytes = _bytes;
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
if (!check_mem_area(uc, address, size))
return UC_ERR_READ_UNMAPPED;
// Memory regions can overlap adjacent memory blocks
while(count < size) {
MemoryRegion *mr = memory_mapping(uc, address);
if (mr) {
len = (size_t)MIN(size - count, mr->end - address);
if (uc->read_mem(&uc->as, address, bytes, len) == false)
break;
count += len;
address += len;
bytes += len;
} else // this address has not been mapped yet
break;
}
if (count == size)
return UC_ERR_OK;
else
return UC_ERR_READ_UNMAPPED;
}
The usage example is the same as uc_mem_write()
uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, uint64_t timeout, size_t count);
Simulate machine code for a specified amount of time.
@uc: the handle returned by uc_open()
@begin: The address to start the simulation
@until: The address where the simulation stops (when this address is reached)
@timeout: The duration of the simulated code (in microseconds). When this value is 0, the code will be simulated with no time limit until the simulation is complete.
@count: The number of instructions to simulate. When this value is 0, all executable code will be simulated until the simulation is complete
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_emu_start(uc_engine* uc, uint64_t begin, uint64_t until, uint64_t timeout, size_t count)
{
// reset the counter
uc->emu_counter = 0;
uc->invalid_error = UC_ERR_OK;
uc->block_full = false;
uc->emulation_done = false;
uc->timed_out = false;
switch(uc->arch) {
default:
break;
#ifdef UNICORN_HAS_M68K
case UC_ARCH_M68K:
uc_reg_write(uc, UC_M68K_REG_PC, &begin);
break;
#endif
#ifdef UNICORN_HAS_X86
case UC_ARCH_X86:
switch(uc->mode) {
default:
break;
case UC_MODE_16: {
uint64_t ip;
uint16_t cs;
uc_reg_read(uc, UC_X86_REG_CS, &cs);
// offset the IP and CS added later
ip = begin-cs*16;
uc_reg_write(uc, UC_X86_REG_IP, &ip);
break;
}
case UC_MODE_32:
uc_reg_write(uc, UC_X86_REG_EIP, &begin);
break;
case UC_MODE_64:
uc_reg_write(uc, UC_X86_REG_RIP, &begin);
break;
}
break;
#endif
#ifdef UNICORN_HAS_ARM
case UC_ARCH_ARM:
uc_reg_write(uc, UC_ARM_REG_R15, &begin);
break;
#endif
#ifdef UNICORN_HAS_ARM64
case UC_ARCH_ARM64:
uc_reg_write(uc, UC_ARM64_REG_PC, &begin);
break;
#endif
#ifdef UNICORN_HAS_MIPS
case UC_ARCH_MIPS:
// TODO: MIPS32/MIPS64/BIGENDIAN etc
uc_reg_write(uc, UC_MIPS_REG_PC, &begin);
break;
#endif
#ifdef UNICORN_HAS_SPARC
case UC_ARCH_SPARC:
// TODO: Sparc/Sparc64
uc_reg_write(uc, UC_SPARC_REG_PC, &begin);
break;
#endif
}
uc->stop_request = false;
uc->emu_count = count;
// If no count is required, remove the count hook
if (count <= 0 && uc->count_hook != 0) {
uc_hook_del(uc, uc->count_hook);
uc->count_hook = 0;
}
// Set the count hook to record the number of instructions
if (count > 0 && uc->count_hook == 0) {
uc_err err;
// The callback to the count instruction must run before all other operations, so the hook must be inserted at the beginning of the hook list, not appended
uc->hook_insert = 1;
err = uc_hook_add(uc, &uc->count_hook, UC_HOOK_CODE, hook_count_cb, NULL, 1, 0);
// revert to uc_hook_add()
uc->hook_insert = 0;
if (err != UC_ERR_OK) {
return err;
}
}
uc->addr_end = until;
if (timeout)
enable_emu_timer(uc, timeout * 1000); // microseconds -> nanoseconds
if (uc->vm_start(uc)) {
return UC_ERR_RESOURCE;
}
// simulation complete
uc->emulation_done = true;
if (timeout) {
// wait for timeout
qemu_thread_join(&uc->timer);
}
if(uc->timed_out)
return UC_ERR_TIMEOUT;
return uc->invalid_error;
}
Example of use:
#include <iostream>
#include <string>
#include "unicorn/unicorn.h"
using namespace std;
#define X86_CODE32 "\x33\xC0" // xor eax, eax
#define ADDRESS 0x1000
int main()
{
uc_engine* uc;
uc_err err;
int r_eax = 0x111;
err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_open() with error returned: %u\n", err);
return -1;
}
uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);
if (uc_mem_write(uc, ADDRESS, X86_CODE32, sizeof(X86_CODE32) - 1)) {
printf("Failed to write emulation code to memory, quit!\n");
return -1;
}
uc_reg_write(uc, UC_X86_REG_EAX, &r_eax);
printf(">>> before EAX = 0x%x\n", r_eax);
err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(X86_CODE32) - 1, 0, 0);
if (err) {
printf("Failed on uc_emu_start() with error returned %u: %s\n",
err, uc_strerror(err));
}
uc_reg_read(uc, UC_X86_REG_EAX, &r_eax);
printf(">>> after EAX = 0x%x\n", r_eax);
err = uc_close(uc);
if (err != UC_ERR_OK) {
printf("Failed on uc_close() with error returned: %u\n", err);
return -1;
}
return 0;
}
output
uc_err uc_emu_stop(uc_engine *uc);
stop simulation
Usually called from a callback function registered through the tracing API.
@uc: the handle returned by uc_open()
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_emu_stop(uc_engine *uc)
{
if (uc->emulation_done)
return UC_ERR_OK;
uc->stop_request = true;
if (uc->current_cpu) {
// exit the current thread
cpu_exit(uc->current_cpu);
}
return UC_ERR_OK;
}
Example of use:
uc_emu_stop(uc);
```
### uc_hook_add
````c
uc_err uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback,
void *user_data, uint64_t begin, uint64_t end, ...);
Register the callback of the hook event, which will be called when the hook event is triggered.
@uc: the handle returned by uc_open()
@hh: The handle obtained by registering the hook. Used in uc_hook_del()
@type: hook type
@callback: the callback to run when the instruction is hit
@user_data: User-defined data. Will be passed to the last parameter of the callback function @user_data
@begin: The starting address of the callback effective area (inclusive)
@end: The end address of the callback effective area (inclusive)
Note 1: The callback will be called only if the address of the callback is in [@begin, @end]
Note 2: If @begin > @end, the callback will be called whenever this hook type is triggered
@...: variable arguments (depending on @type)
Note: If @type = UC_HOOK_INSN, here is the instruction ID (eg: UC_X86_INS_OUT)
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback,
void *user_data, uint64_t begin, uint64_t end, ...)
{
int ret = UC_ERR_OK;
int i = 0;
struct hook *hook = calloc(1, sizeof(struct hook));
if (hook == NULL) {
return UC_ERR_NOMEM;
}
hook->begin = begin;
hook->end = end;
hook->type = type;
hook->callback = callback;
hook->user_data = user_data;
hook->refs = 0;
*hh = (uc_hook)hook;
// UC_HOOK_INSN has an extra parameter: the instruction ID
if (type & UC_HOOK_INSN) {
va_list valist;
va_start(valist, end);
hook->insn = va_arg(valist, int);
va_end(valist);
if (uc->insn_hook_validate) {
if (!uc->insn_hook_validate(hook->insn)) {
free(hook);
return UC_ERR_HOOK;
}
}
if (uc->hook_insert) {
if (list_insert(&uc->hook[UC_HOOK_INSN_IDX], hook) == NULL) {
free(hook);
return UC_ERR_NOMEM;
}
} else {
if (list_append(&uc->hook[UC_HOOK_INSN_IDX], hook) == NULL) {
free(hook);
return UC_ERR_NOMEM;
}
}
hook->refs++;
return UC_ERR_OK;
}
while ((type >> i) > 0) {
if ((type >> i) & 1) {
if (i < UC_HOOK_MAX) {
if (uc->hook_insert) {
if (list_insert(&uc->hook[i], hook) == NULL) {
if (hook->refs == 0) {
free(hook);
}
return UC_ERR_NOMEM;
}
} else {
if (list_append(&uc->hook[i], hook) == NULL) {
if (hook->refs == 0) {
free(hook);
}
return UC_ERR_NOMEM;
}
}
hook->refs++;
}
}
i++;
}
if (hook->refs == 0) {
free(hook);
}
return ret;
}
Example of use:
#include <iostream>
#include <string>
#include "unicorn/unicorn.h"
using namespace std;
int syscall_abi[] = {
UC_X86_REG_RAX, UC_X86_REG_RDI, UC_X86_REG_RSI, UC_X86_REG_RDX,
UC_X86_REG_R10, UC_X86_REG_R8, UC_X86_REG_R9
};
uint64_t vals[7] = { 200, 10, 11, 12, 13, 14, 15 };
void* ptrs[7];
void uc_perror(const char* func, uc_err err)
{
fprintf(stderr, "Error in %s(): %s\n", func, uc_strerror(err));
}
#define BASE 0x10000
// mov rax, 100; mov rdi, 1; mov rsi, 2; mov rdx, 3; mov r10, 4; mov r8, 5; mov r9, 6; syscall
#define CODE "\x48\xc7\xc0\x64\x00\x00\x00\x48\xc7\xc7\x01\x00\x00\x00\x48\xc7\xc6\x02\x00\x00\x00\x48\xc7 \xc2\x03\x00\x00\x00\x49\xc7\xc2\x04\x00\x00\x00\x49\xc7\xc0\x05\x00\x00\x00\x49\xc7\xc1\x06\x00\x00 \x00\x0f\x05"
void hook_syscall(uc_engine* uc, void* user_data)
{
int i;
uc_reg_read_batch(uc, syscall_abi, ptrs, 7);
printf("syscall: {");
for (i = 0; i < 7; i++) {
if (i != 0) printf(", ");
printf("%" PRIu64, vals[i]);
}
printf("}\n");
}
void hook_code(uc_engine* uc, uint64_t addr, uint32_t size, void* user_data)
{
printf("HOOK_CODE: 0x%" PRIx64 ", 0x%x\n", addr, size);
}
int main()
{
int i;
uc_hook sys_hook;
uc_err err;
uc_engine* uc;
for (i = 0; i < 7; i++) {
ptrs[i] = &vals[i];
}
if ((err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc))) {
uc_perror("uc_open", err);
return 1;
}
printf("reg_write_batch({200, 10, 11, 12, 13, 14, 15})\n");
if ((err = uc_reg_write_batch(uc, syscall_abi, ptrs, 7))) {
uc_perror("uc_reg_write_batch", err);
return 1;
}
memset(vals, 0, sizeof(vals));
if ((err = uc_reg_read_batch(uc, syscall_abi, ptrs, 7))) {
uc_perror("uc_reg_read_batch", err);
return 1;
}
printf("reg_read_batch = {");
for (i = 0; i < 7; i++) {
if (i != 0) printf(", ");
printf("%" PRIu64, vals[i]);
}
printf("}\n");
// syscall
printf("\n");
printf("running syscall shellcode\n");
if ((err = uc_hook_add(uc, &sys_hook, UC_HOOK_CODE, hook_syscall, NULL, 1, 0))) {
uc_perror("uc_hook_add", err);
return 1;
}
if ((err = uc_mem_map(uc, BASE, 0x1000, UC_PROT_ALL))) {
uc_perror("uc_mem_map", err);
return 1;
}
if ((err = uc_mem_write(uc, BASE, CODE, sizeof(CODE) - 1))) {
uc_perror("uc_mem_write", err);
return 1;
}
if ((err = uc_emu_start(uc, BASE, BASE + sizeof(CODE) - 1, 0, 0)))) {
uc_perror("uc_emu_start", err);
return 1;
}
return 0;
}
output
Hook for every instruction
uc_err uc_hook_del(uc_engine *uc, uc_hook hh);
delete a registered hook event
@uc: the handle returned by uc_open()
@hh: the handle returned by uc_hook_add()
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_hook_del(uc_engine *uc, uc_hook hh)
{
int i;
struct hook *hook = (struct hook *)hh;
for (i = 0; i < UC_HOOK_MAX; i++) {
if (list_remove(&uc->hook[i], (void *)hook)) {
if (--hook->refs == 0) {
free(hook);
break;
}
}
}
return UC_ERR_OK;
}
Example of use:
if ((err = uc_hook_add(uc, &sys_hook, UC_HOOK_CODE, hook_syscall, NULL, 1, 0))) {
uc_perror("uc_hook_add", err);
return 1;
}
if ((err = uc_hook_del(uc, &sys_hook))) {
uc_perror("uc_hook_del", err);
return 1;
}
uc_err uc_mem_map(uc_engine *uc, uint64_t address, size_t size, uint32_t perms);
Map a block of memory for the simulation.
@uc: the handle returned by uc_open()
@address: The starting address of the new memory region to map to. This address must be aligned to 4KB, otherwise a UC_ERR_ARG error will be returned.
@size: The size of the new memory area to map to. This size must be a multiple of 4KB, otherwise a UC_ERR_ARG error will be returned.
@perms: Permissions for the newly mapped area. Arguments must be UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC or a combination of these, otherwise a UC_ERR_ARG error is returned.
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_mem_map(uc_engine *uc, uint64_t address, size_t size, uint32_t perms)
{
uc_err res;
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
res = mem_map_check(uc, address, size, perms); //memory safety check
if (res)
return res;
return mem_map(uc, address, size, perms, uc->memory_map(uc, address, size, perms));
}
The usage example is the same as uc_hook_add()
uc_err uc_mem_map_ptr(uc_engine *uc, uint64_t address, size_t size, uint32_t perms, void *ptr);
Map existing host memory in the simulation.
@uc: the handle returned by uc_open()
@address: The starting address of the new memory region to map to. This address must be aligned to 4KB, otherwise a UC_ERR_ARG error will be returned.
@size: The size of the new memory area to map to. This size must be a multiple of 4KB, otherwise a UC_ERR_ARG error will be returned.
@perms: Permissions for the newly mapped area. Arguments must be UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC or a combination of these, otherwise a UC_ERR_ARG error is returned.
@ptr: Pointer to host memory backing the newly mapped memory. The size of the mapped host memory should be the same as or larger than size and at least map with PROT_READ|PROT_WRITE, otherwise no mapping is defined.
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_mem_map_ptr(uc_engine *uc, uint64_t address, size_t size, uint32_t perms, void *ptr)
{
uc_err res;
if (ptr == NULL)
return UC_ERR_ARG;
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
res = mem_map_check(uc, address, size, perms); //memory safety check
if (res)
return res;
return mem_map(uc, address, size, UC_PROT_ALL, uc->memory_map_ptr(uc, address, size, perms, ptr));
}
The usage example is the same as uc_mem_map()
uc_err uc_mem_unmap(uc_engine *uc, uint64_t address, size_t size);
Unmap the emulated memory area
@uc: the handle returned by uc_open()
@address: The starting address of the new memory region to map to. This address must be aligned to 4KB, otherwise a UC_ERR_ARG error will be returned.
@size: The size of the new memory area to map to. This size must be a multiple of 4KB, otherwise a UC_ERR_ARG error will be returned.
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_mem_unmap(struct uc_struct *uc, uint64_t address, size_t size)
{
MemoryRegion *mr;
uint64_t addr;
size_t count, len;
if (size == 0)
// no regions to unmap
return UC_ERR_OK;
// address must be aligned to uc->target_page_size
if ((address & uc->target_page_align) != 0)
return UC_ERR_ARG;
// size must be a multiple of uc->target_page_size
if ((size & uc->target_page_align) != 0)
return UC_ERR_ARG;
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
// Check if the entire block requested by the user is mapped
if (!check_mem_area(uc, address, size))
return UC_ERR_NOMEM;
// If this region spans adjacent regions, it may be necessary to split the region
addr = address;
count = 0;
while(count < size) {
mr = memory_mapping(uc, addr);
len = (size_t)MIN(size - count, mr->end - addr);
if (!split_region(uc, mr, addr, len, true))
return UC_ERR_NOMEM;
// unmap
mr = memory_mapping(uc, addr);
if (mr != NULL)
uc->memory_unmap(uc, mr);
count += len;
addr += len;
}
return UC_ERR_OK;
}
Example of use:
if ((err = uc_mem_map(uc, BASE, 0x1000, UC_PROT_ALL)))) {
uc_perror("uc_mem_map", err);
return 1;
}
if ((err = uc_mem_unmap(uc, BASE, 0x1000))) {
uc_perror("uc_mem_unmap", err);
return 1;
}
uc_err uc_mem_protect(uc_engine *uc, uint64_t address, size_t size, uint32_t perms);
Set permissions for emulated memory
@uc: the handle returned by uc_open()
@address: The starting address of the new memory region to map to. This address must be aligned to 4KB, otherwise a UC_ERR_ARG error will be returned.
@size: The size of the new memory area to map to. This size must be a multiple of 4KB, otherwise a UC_ERR_ARG error will be returned.
@perms: New permissions for the mapped area. Arguments must be UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC or a combination of these, otherwise a UC_ERR_ARG error is returned.
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_mem_protect(struct uc_struct *uc, uint64_t address, size_t size, uint32_t perms)
{
MemoryRegion *mr;
uint64_t addr = address;
size_t count, len;
bool remove_exec = false;
if (size == 0)
// trivial case, no change
return UC_ERR_OK;
// address must be aligned to uc->target_page_size
if ((address & uc->target_page_align) != 0)
return UC_ERR_ARG;
// size must be multiple of uc->target_page_size
if ((size & uc->target_page_align) != 0)
return UC_ERR_ARG;
// check for only valid permissions
if ((perms & ~UC_PROT_ALL) != 0)
return UC_ERR_ARG;
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
// check that user's entire requested block is mapped
if (!check_mem_area(uc, address, size))
return UC_ERR_NOMEM;
// Now we know entire region is mapped, so change permissions
// We may need to split regions if this area spans adjacent regions
addr = address;
count = 0;
while(count < size) {
mr = memory_mapping(uc, addr);
len = (size_t)MIN(size - count, mr->end - addr);
if (!split_region(uc, mr, addr, len, false))
return UC_ERR_NOMEM;
mr = memory_mapping(uc, addr);
// will this remove EXEC permission?
if (((mr->perms & UC_PROT_EXEC) != 0) && ((perms & UC_PROT_EXEC) == 0))
remove_exec = true;
mr->perms = perms;
uc->readonly_mem(mr, (perms & UC_PROT_WRITE) == 0);
count += len;
addr += len;
}
// if EXEC permission is removed, then quit TB and continue at the same place
if (remove_exec) {
uc->quit_request = true;
uc_emu_stop(uc);
}
return UC_ERR_OK;
}
Example of use:
if ((err = uc_mem_protect(uc, BASE, 0x1000, UC_PROT_ALL))) { //Readable, writable and executable
uc_perror("uc_mem_protect", err);
return 1;
}
uc_err uc_mem_regions(uc_engine *uc, uc_mem_region **regions, uint32_t *count);
Retrieve information about memory mapped by uc_mem_map() and uc_mem_map_ptr().
This API allocates memory for @regions, which the user must then free() to avoid memory leaks.
@uc: the handle returned by uc_open()
@regions: Pointer to an array of uc_mem_region structures. Requested by Unicorn, these memory must be freed by uc_free()
@count: pointer to the number of uc_mem_region structures contained in @regions
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
Source code analysis
Code
uint32_t uc_mem_regions(uc_engine *uc, uc_mem_region **regions, uint32_t *count)
{
uint32_t i;
uc_mem_region *r = NULL;
*count = uc->mapped_block_count;
if (*count) {
r = g_malloc0(*count * sizeof(uc_mem_region));
if (r == NULL) {
// Not enough storage
return UC_ERR_NOMEM;
}
}
for (i = 0; i < *count; i++) {
r[i].begin = uc->mapped_blocks[i]->addr;
r[i].end = uc->mapped_blocks[i]->end - 1;
r[i].perms = uc->mapped_blocks[i]->perms;
}
*regions = r;
return UC_ERR_OK;
}
Example of use:
#include <iostream>
#include <string>
#include "unicorn/unicorn.h"
using namespace std;
int main()
{
uc_err err;
uc_engine* uc;
if ((err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc))) {
uc_perror("uc_open", err);
return 1;
}
if ((err = uc_mem_map(uc, BASE, 0x1000, UC_PROT_ALL)))) {
uc_perror("uc_mem_map", err);
return 1;
}
uc_mem_region *region;
uint32_t count;
if ((err = uc_mem_regions(uc, ®ion, &count))) {
uc_perror("uc_mem_regions", err);
return 1;
}
cout << "start address: 0x" << hex << region->begin << " end address: 0x" << hex << region->end << " memory permissions: " <<region->perms < < "Number of allocated memory blocks: " << count << endl;
if ((err = uc_free(region))) { ////Pay attention to freeing memory
uc_perror("uc_free", err);
return 1;
}
return 0;
}
output
uc_err uc_free(void *mem);
Free the memory allocated by uc_mem_regions()
@mem: memory allocated by uc_mem_regions (returns *regions)
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_free(void *mem)
{
g_free(mem);
return UC_ERR_OK;
}
void g_free(gpointer ptr)
{
free(ptr);
}
Use the same example as uc_mem_regions()
uc_err uc_context_alloc(uc_engine *uc, uc_context **context);
Allocate a region that can be used with uc_context_{save,restore} to perform fast save/rollback of the CPU context, including registers and internal metadata. Contexts cannot be shared between engine instances with different schemas or modes.
@uc: the handle returned by uc_open()
@context: Pointer to uc_engine*. When this function returns successfully, it will be updated with a pointer to the new context. These allocated memory must then be freed using uc_context_free().
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
source code implementation
uc_err uc_context_alloc(uc_engine *uc, uc_context **context)
{
struct uc_context **_context = context;
size_t size = uc->cpu_context_size;
*_context = g_malloc(size);
if (*_context) {
(*_context)->jmp_env_size = sizeof(*uc->cpu->jmp_env);
(*_context)->context_size = size - sizeof(uc_context) - (*_context)->jmp_env_size;
return UC_ERR_OK;
} else {
return UC_ERR_NOMEM;
}
}
```
</details>
Example of use
```cpp
#include <iostream>
#include <string>
#include "unicorn/unicorn.h"
using namespace std;
#define ADDRESS 0x1000
#define X86_CODE32_INC "\x40" // INC eax
int main()
{
uc_engine* uc;
uc_context* context;
uc_err err;
int r_eax = 0x1; // EAX register
printf("===================================\n");
printf("Save/restore CPU context in opaque blob\n");
err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
if (err) {
printf("Failed on uc_open() with error returned: %u\n", err);
return 0;
}
uc_mem_map(uc, ADDRESS, 8 * 1024, UC_PROT_ALL);
if (uc_mem_write(uc, ADDRESS, X86_CODE32_INC, sizeof(X86_CODE32_INC) - 1)) {
printf("Failed to write emulation code to memory, quit!\n");
return 0;
}
// initialize register
uc_reg_write(uc, UC_X86_REG_EAX, &r_eax);
printf(">>> Running emulation for the first time\n");
err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(X86_CODE32_INC) - 1, 0, 0);
if (err) {
printf("Failed on uc_emu_start() with error returned %u: %s\n",
err, uc_strerror(err));
}
printf(">>> Emulation done. Below is the CPU context\n");
uc_reg_read(uc, UC_X86_REG_EAX, &r_eax);
printf(">>> EAX = 0x%x\n", r_eax);
// Allocate and save CPU context
printf(">>> Saving CPU context\n");
err = uc_context_alloc(uc, &context);
if (err) {
printf("Failed on uc_context_alloc() with error returned: %u\n", err);
return 0;
}
err = uc_context_save(uc, context);
if (err) {
printf("Failed on uc_context_save() with error returned: %u\n", err);
return 0;
}
printf(">>> Running emulation for the second time\n");
err = uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(X86_CODE32_INC) - 1, 0, 0);
if (err) {
printf("Failed on uc_emu_start() with error returned %u: %s\n",
err, uc_strerror(err));
}
printf(">>> Emulation done. Below is the CPU context\n");
uc_reg_read(uc, UC_X86_REG_EAX, &r_eax);
printf(">>> EAX = 0x%x\n", r_eax);
// restore CPU context
err = uc_context_restore(uc, context);
if (err) {
printf("Failed on uc_context_restore() with error returned: %u\n", err);
return 0;
}
printf(">>> CPU context restored. Below is the CPU context\n");
uc_reg_read(uc, UC_X86_REG_EAX, &r_eax);
printf(">>> EAX = 0x%x\n", r_eax);
// release the CPU context
err = uc_context_free(context);
if (err) {
printf("Failed on uc_free() with error returned: %u\n", err);
return;
}
uc_close(uc);
}
```
output
![image.png](https://raw.githubusercontent.com/kabeor/Unicorn-Engine-Documentation/master/API_Doc_Pic/1_21.png)
### uc_context_save
```c
uc_err uc_context_save(uc_engine *uc, uc_context *context);
```
Save the current CPU context
```
@uc: the handle returned by uc_open()
@context: the handle returned by uc_context_alloc()
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
```
<details><summary> source code implementation </summary>
```c
uc_err uc_context_save(uc_engine *uc, uc_context *context)
{
struct uc_context *_context = context;
memcpy(_context->data, uc->cpu->env_ptr, _context->size);
return UC_ERR_OK;
}
```
</details>
The usage example is the same as [uc_context_alloc()](#uc_context_alloc)
### uc_context_restore
```c
uc_err uc_context_restore(uc_engine *uc, uc_context *context);
```
Restore saved CPU context
```
@uc: the handle returned by uc_open()
@context: handle returned by uc_context_alloc() and saved with uc_context_save
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
```
<details><summary> source code implementation </summary>
```c
uc_err uc_context_restore(uc_engine *uc, uc_context *context)
{
struct uc_context *_context = context;
memcpy(uc->cpu->env_ptr, _context->data, _context->size);
return UC_ERR_OK;
}
```
</details>
The usage example is the same as [uc_context_alloc()](#uc_context_alloc)
### uc_context_size
```c
size_t uc_context_size(uc_engine *uc);
```
Returns the size required to store the cpu context. Can be used to allocate a buffer to contain the cpu context and call uc_context_save directly.
```
@uc: uc_open() the returned handle
@return The size required to store the cpu context, of type size_t.
```
<details><summary> source code implementation </summary>
```c
size_t uc_context_size(uc_engine *uc)
{
return sizeof(uc_context) + uc->cpu_context_size + sizeof(*uc->cpu->jmp_env);
}
```
</details>
The usage example is the same as [uc_context_alloc()](#uc_context_alloc)
### uc_context_free
```c
uc_err uc_context_free(uc_context *context);
```
Free the memory allocated by [uc_context_alloc()](#uc_context_alloc)
```
@context: uc_context created by uc_context_alloc
@return returns UC_ERR_OK if successful, otherwise returns other error types enumerated by uc_err
```
The usage example is the same as [uc_context_alloc()](#uc_context_alloc)