Skip to main content

Overview

iSH includes experimental support for running x86_64 (64-bit) code alongside the primary x86 (32-bit) emulation. This port extends the emulator to handle 64-bit registers, addressing modes, and instructions while maintaining compatibility with the existing 32-bit infrastructure.

Building with 64-bit Support

Meson Configuration

To build iSH with 64-bit guest support, use the guest_arch option:
meson configure -Dguest_arch=x86_64
This sets the ISH_GUEST_64BIT preprocessor flag throughout the codebase:
# Guest architecture (x86 or x86_64)
if get_option('guest_arch') == 'x86_64'
    add_project_arguments('-DISH_GUEST_64BIT=1', language: 'c')

Dependencies

The 64-bit port requires Zydis, a fast x86/x86_64 disassembler library, for instruction decoding:
if get_option('guest_arch') == 'x86_64'
    # Zydis for x86_64 instruction decoding
    zydis_options = [
        'default_library=static',
        'examples=disabled',
        'tools=disabled',
    ]
    zydis_proj = subproject('zydis', default_options: zydis_options)
    zydis_dep = zydis_proj.get_variable('zydis_dep')
else
    zydis_dep = []
endif

Architecture Changes

CPU State Extensions

The CPU state structure in emu/cpu.h adapts based on the ISH_GUEST_64BIT flag:

32-bit Register Macros

// 32-bit register macros - provide access to eXX, XX, Xl, Xh
#define _REG(n) \
    union { \
        dword_t e##n; \
        word_t n; \
    }
#define _REGX(n) \
    union { \
        dword_t e##n##x; \
        word_t n##x; \
        struct { \
            byte_t n##l; \
            byte_t n##h; \
        }; \
    }

64-bit Register Macros

#ifdef ISH_GUEST_64BIT
// 64-bit register macros - provide access to rXX, eXX, XX, Xl, Xh
#define _REG(n) \
    union { \
        qword_t r##n; \
        dword_t e##n; \
        word_t n; \
    }
#define _REGX(n) \
    union { \
        qword_t r##n##x; \
        dword_t e##n##x; \
        word_t n##x; \
        struct { \
            byte_t n##l; \
            byte_t n##h; \
        }; \
    }
#define _REG64(n) \
    union { \
        qword_t r##n; \
        dword_t r##n##d; \
        word_t r##n##w; \
        byte_t r##n##b; \
    }
#endif

Extended Registers

In 64-bit mode, the CPU state includes r8-r15:
#ifdef ISH_GUEST_64BIT
    // x86_64 additional registers r8-r15
    _REG64(8);
    _REG64(9);
    _REG64(10);
    _REG64(11);
    _REG64(12);
    _REG64(13);
    _REG64(14);
    _REG64(15);
#endif

Instruction Pointer

#ifdef ISH_GUEST_64BIT
    union {
        qword_t rip;
        dword_t eip;  // Low 32 bits accessible as eip
    };
#else
    dword_t eip;
#endif

XMM Registers

64-bit mode expands XMM registers from 8 to 16:
#ifdef ISH_GUEST_64BIT
    union xmm_reg xmm[16];  // x86_64 has 16 XMM registers
#else
    union xmm_reg xmm[8];
#endif

Instruction Decoding

decode64.c and decode64.h

The 64-bit port uses a completely separate instruction decoder powered by Zydis:
// Decoded instruction for 64-bit
struct decoded_inst64 {
    ZydisMnemonic mnemonic;         // Zydis mnemonic
    uint8_t length;                 // Instruction length in bytes
    uint8_t operand_count;          // Number of operands (up to 4)
    struct decoded_op64 operands[4];

    // Prefix information
    bool has_lock;
    bool has_rep;
    bool has_repne;
    bool has_segment_override;
    ZydisRegister segment;

    // Original Zydis structures (for detailed access if needed)
    ZydisDecodedInstruction raw_inst;
    ZydisDecodedOperand raw_operands[ZYDIS_MAX_OPERAND_COUNT];
};
Key decoder functions:
  • decode64_init() - Initialize Zydis for 64-bit long mode
  • decode64_inst() - Decode a single instruction
  • decode64_is_branch(), decode64_is_call(), decode64_is_ret() - Instruction classification
  • zydis_reg_to_arg64() - Convert Zydis registers to internal format

Operand Types

enum arg64 {
    // 64-bit GPRs (matches register order)
    arg64_rax, arg64_rcx, arg64_rdx, arg64_rbx,
    arg64_rsp, arg64_rbp, arg64_rsi, arg64_rdi,
    arg64_r8,  arg64_r9,  arg64_r10, arg64_r11,
    arg64_r12, arg64_r13, arg64_r14, arg64_r15,
    // XMM registers
    arg64_xmm0,  arg64_xmm1,  /* ... */ arg64_xmm15,
    // Other operand types
    arg64_imm,      // Immediate value
    arg64_mem,      // Memory operand
    arg64_rip_rel,  // RIP-relative addressing (common in x86_64)
    arg64_gs,       // GS segment
    arg64_fs,       // FS segment (TLS)
};

Code Generation

gen64.c vs gen.c

The build system selects the appropriate code generator:
# Use gen64.c for 64-bit guest, gen.c for 32-bit guest
if get_option('guest_arch') == 'x86_64'
    emu_src += 'asbestos/gen64.c'
else
    emu_src += 'asbestos/gen.c'
endif
gen64.c is significantly larger (331,926 bytes vs 20,945 bytes for gen.c) due to the expanded instruction set and addressing modes of x86_64.

Gadget Directories

Directory Structure

Gadgets are organized by host and guest architecture:
# Select gadget directory based on host and guest architecture
# For 64-bit guest, use gadgets-<host>-64 directory
if get_option('guest_arch') == 'x86_64'
    gadgets = 'asbestos/gadgets-' + host_machine.cpu_family() + '-64'
else
    gadgets = 'asbestos/gadgets-' + host_machine.cpu_family()
endif
Examples:
  • gadgets-aarch64 - 32-bit guest on ARM64 host
  • gadgets-aarch64-64 - 64-bit guest on ARM64 host
  • gadgets-x86_64 - 32-bit guest on x86_64 host

Gadget Assembly Files

Each gadget directory contains:
emu_src += [
    gadgets+'/entry.S',     // Entry/exit points
    gadgets+'/memory.S',    // Memory operations
    gadgets+'/control.S',   // Control flow
    gadgets+'/math.S',      // Arithmetic operations
    gadgets+'/bits.S',      // Bit manipulation
    gadgets+'/string.S',    // String operations
    gadgets+'/misc.S',      // Miscellaneous
]
The 64-bit gadget files are substantially larger to handle the extended instruction set.

Current Status

The 64-bit port is experimental. Key considerations:
  • Decoder: Uses Zydis for robust instruction decoding
  • Gadgets: 64-bit gadget implementations exist for AArch64 hosts
  • Register State: Full 64-bit register support with backward compatibility for 32-bit sub-registers
  • Addressing: Supports RIP-relative addressing and 64-bit memory operands
  • TLS: Includes FS/GS base register support for thread-local storage

What Works

  • Basic 64-bit instruction execution
  • Extended register access (r8-r15)
  • 64-bit addressing modes
  • RIP-relative addressing
  • Extended XMM register set (16 registers)

Build Testing

To test the 64-bit build:
meson setup build -Dguest_arch=x86_64
cd build
ninja

Implementation Notes

Portable Macros

Code uses portable macros to work with both architectures:
// Portable register access macros
#ifdef ISH_GUEST_64BIT
#define CPU_IP(cpu) ((cpu)->rip)
#define CPU_SP(cpu) ((cpu)->rsp)
#else
#define CPU_IP(cpu) ((cpu)->eip)
#define CPU_SP(cpu) ((cpu)->esp)
#endif

Crash Handler

The crash handler in main.c displays appropriate register state:
#ifdef ISH_GUEST_64BIT
    fprintf(stderr, "x86_64 CPU state at crash:\n");
    fprintf(stderr, "  RIP=0x%llx RSP=0x%llx RBP=0x%llx\n",
            (unsigned long long)cpu->rip, (unsigned long long)cpu->rsp,
            (unsigned long long)cpu->rbp);
    fprintf(stderr, "  RAX=0x%llx RBX=0x%llx RCX=0x%llx RDX=0x%llx\n",
            (unsigned long long)cpu->rax, (unsigned long long)cpu->rbx,
            (unsigned long long)cpu->rcx, (unsigned long long)cpu->rdx);
#else
    fprintf(stderr, "x86 CPU state at crash:\n");
    fprintf(stderr, "  EIP=0x%x ESP=0x%x EBP=0x%x\n",
            cpu->eip, cpu->esp, cpu->ebp);
#endif

See Also

Build docs developers (and LLMs) love