iced x86 Instruction Decoder, Disassembler, and Assembler

repository·master·Indexed 26 days ago

https://github.com/icedland/iced

A high-performance, memory-efficient x86 (16/32/64-bit) instruction decoder, disassembler, and assembler supporting all Intel and AMD instructions. Written in Rust with bindings for .NET, Java, Python, JavaScript, and Lua, it provides rich metadata on register/memory usage and supports multiple formatting styles including MASM, NASM, GAS (AT&T), and Intel (XED).

Tokens
28.6K
Snippets
38
Records
60
Agent score
85%

What's inside iced

  1. Overview of iced x86 instruction processing

    master

    iced is a high-performance x86 (16/32/64-bit) instruction decoder, disassembler, and assembler. It supports all Intel and AMD instructions and is designed for correctness through extensive testing against tools like xed, gas, objdump, masm, dumpbin, nasm, and ndisasm, as well as fuzzing.

    Key features include:

    • High Performance: Decodes >250 MB/s and decode+format >130 MB/s in Rust.
    • Memory Efficient: Decoded instructions are only 40 bytes, and the decoder performs no memory allocations.
    • Flexible Formatting: Supports masm, nasm, gas (AT&T), and Intel (XED) formats with extensive customization.
    • Rich Metadata: Provides APIs to retrieve instruction information such as read/written registers, memory access, rflags bits, CPUID feature flags, and control flow info.
    • Assembler Capabilities: Allows creating instructions via code (e.g., asm.mov(eax, edx)) and re-encoding decoded instructions at specific addresses.
  2. Overview of iced x86

    master
    iced is a high-performance, correct x86 (16/32/64-bit) instruction decoder, disassembler, and assembler written in 100% Java. It supports all Intel and AMD instructions and is designed for speed, decoding at rates exceeding 100 MB/s with minimal memory allocation.
  3. Use IntelFuzzer to generate valid and invalid instructions

    master

    IntelFuzzer is a tool used to generate instruction sequences to verify the robustness of the iced decoder.

    Verification Goals:

    • Ensure all invalid opcodes and invalid encodings of valid opcodes fail to be decoded.
    • Ensure all other instructions are decoded successfully and that the decoded instruction length is correct.

    Note on Accuracy: This tool does not verify if the decoder produced the correct instruction or correct operands. To verify instruction/operand accuracy, you should disassemble all valid instructions using a high base address (RIP) value.

  4. Get instruction info (registers, memory, control flow) in iced-x86 JS

    master

    To extract detailed information about decoded instructions—such as used registers, memory access patterns, and control flow—use the Instruction methods in conjunction with an InstructionInfoFactory.

    Important: Memory Management Since this is a WebAssembly-based binding, you must manually call .free() on objects returned by the factory or methods that allocate Wasm memory to prevent memory leaks. This includes:

    • InstructionInfoFactory
    • Instruction
    • InstructionInfo (returned by infoFactory.info(instr))
    • OpCode (returned by instr.opCode)
    • ConstantOffsets (returned by decoder.getConstantOffsets(instr))
    • Individual regInfo objects from usedRegisters()
    • Individual memInfo objects from usedMemory()
    const { 
        Decoder, 
        DecoderOptions, 
        Instruction, 
        InstructionInfoFactory 
    } = require("iced-x86");
    
    const exampleBitness = 64;
    const exampleCode = new Uint8Array([0x48, 0x89, 0x5C, 0x24, 0x10]); // ... example bytes
    const exampleRip = 0x00007FFAC46ACDA4n;
    
    const decoder = new Decoder(exampleBitness, exampleCode, DecoderOptions.None);
    decoder.ip = exampleRip;
    
    const infoFactory = new InstructionInfoFactory();
    const instr = new Instruction();
    
    while (decoder.canDecode) {
        decoder.decodeOut(instr);
        
        // Get offsets for relocations
        const offsets = decoder.getConstantOffsets(instr);
        
        // Get detailed info (registers/memory)
        const info = infoFactory.info(instr);
        
        // Access used registers
        for (const regInfo of info.usedRegisters()) {
            console.log("Used reg:", regInfo.register, regInfo.access);
            regInfo.free(); // MUST FREE
        }
    
        // Access used memory
        for (const memInfo of info.usedMemory()) {
            console.log("Used mem:", memInfo.segment, memInfo.base, memInfo.displacement);
            memInfo.free(); // MUST FREE
        }
    
        info.free(); // MUST FREE
        offsets.free(); // MUST FREE
    }
    
    // Final cleanup
    instr.free();
    infoFactory.free();
    decoder.free();
  5. Assemble instructions using the `code_asm` feature

    master

    The code_asm feature provides a high-level CodeAssembler API to create instructions using a fluent syntax (e.g., a.xor(eax, ecx)?) instead of verbose Instruction::with*() functions.

    Setup: This feature is not enabled by default. Add it to your Cargo.toml:

    [dependencies.iced-x86]
    version = "1.21.0"
    features = ["code_asm"]

    Key Capabilities:

    • Memory Operands: Automatically created when adding/subtracting from a register (e.g., rax + 0). Use qword_ptr(), dword_bcst(), byte_ptr(), etc., to specify size hints.
    • Segment Overrides: Use segment methods like .fs() on a pointer (e.g., ptr(rax).fs()).
    • Mnemonics: Most mnemonics are methods on the assembler. Some require an _<opcount> suffix (e.g., ret_1(123)).
    • Prefixes: Supported via methods (e.g., a.rep().stosd()?).
    • Labels: Create and reference labels using create_label() and set_label(). Labels can be used for jumps or RIP-relative addressing.
    • Encoding Control: Use set_prefer_vex(false) to force non-VEX encoding, or call .vex()/.evex() to override encoding for specific instructions.
    • Output: Use assemble(address) to get encoded bytes, or instructions()/take_instructions() to retrieve the internal instruction list.
    use iced_x86::code_asm::*;
    
    let mut a = CodeAssembler::new(64)?;
    
    // Register and memory operands
    let _ = rax;
    let _ = rax + rcx * 4 - 123;
    let _ = qword_ptr(123);
    
    // Mnemonics and prefixes
    a.push(rcx)?;
    a.rep().stosd()?;
    
    // Labels
    let mut loop_lbl1 = a.create_label();
    a.set_label(&mut loop_lbl1)?;
    
    // Encoding
    a.evex().vucomiss(xmm31, xmm15.sae())?;
    
    // Finalize
    let bytes = a.assemble(0x1234_5678)?;
  6. Disassemble and format x86 instructions

    master

    To disassemble raw bytes, use ByteArrayCodeReader to wrap your byte array, then initialize a Decoder with the appropriate bitness (e.g., 16, 32, or 64). Iterate through the bytes by calling decoder.decode() until the end of the buffer is reached. To convert instructions into human-readable text, use a formatter such as NasmFormatter, MasmFormatter, GasFormatter (AT&T), or IntelFormatter (XED).

    import com.github.icedland.iced.x86.*;
    import com.github.icedland.iced.x86.dec.*;
    import com.github.icedland.iced.x86.fmt.*;
    import com.github.icedland.iced.x86.fmt.nasm.*;
    
    // ... setup codeBytes, bitness, and RIP ...
    ByteArrayCodeReader codeReader = new ByteArrayCodeReader(codeBytes);
    Decoder decoder = new Decoder(64, codeReader);
    decoder.setIP(0x00007FFAC46ACDA4L);
    
    NasmFormatter formatter = new NasmFormatter();
    StringOutput output = new StringOutput();
    
    while (decoder.getIP() < endRip) {
        Instruction instr = decoder.decode();
        formatter.format(instr, output);
        System.out.println(output.toStringAndReset());
    }
  7. Run Iced unit tests

    master

    When running unit tests for the Iced project, avoid using Visual Studio's Test Explorer as it is slow and may only display a fraction (approximately 1/3) of the available tests.

    For optimal performance, use the command line. The recommended execution order from fastest to slowest is:

    1. xunit.console.exe (Fastest)
    2. dotnet test
    3. Visual Studio Test Explorer (Slowest)

    When running tests, use the -noappdomain option to disable running tests in a new AppDomain. This prevents unnecessary serialization and deserialization of data, significantly improving execution speed.

  8. Create and encode instructions

    master

    Use the BlockEncoder to assemble a sequence of instructions into a byte buffer. This is useful for generating machine code or re-encoding decoded instructions.

    Workflow

    1. Create Instructions: Use Instruction.create(code, ...) to define individual instructions. For example, Instruction.create(Code.Push_r64, Register.RBP).
    2. Handle Branches: For branch instructions, use Instruction.create_branch(code, target_ip). The target_ip should match the IP of the destination instruction.
    3. Set IPs: Use instr:set_ip(id) to manually assign instruction pointers, which is critical for resolving branch targets.
    4. Encode: Pass the list of instructions to BlockEncoder.encode(bitness, instrs, target_rip). This method automatically handles branch optimizations (e.g., converting a long jump to a short jump if possible).

    Key Classes

    • Instruction.create(code, ...): Creates an instruction.
    • Instruction.create_branch(code, target_ip): Creates a branch instruction.
    • MemoryOperand.with_base_displ(base, displacement): Creates memory operands (e.g., for lea or mov).
    • BlockEncoder.encode(bitness, instrs, target_rip): Encodes a block of instructions into a result containing code_buffer.
  9. Disassemble old or deprecated CPU instructions

    master

    By default, iced-x86 does not decode deprecated or CPU-specific instructions (like Cyrix, Centaur ALTINST, or MPX) to avoid conflicts with newer instructions using similar opcodes. To disassemble these, you must pass specific DecoderOptions to the Decoder constructor.

    Available DecoderOptions mentioned:

    • DecoderOptions.MPX
    • DecoderOptions.MOV_TR
    • DecoderOptions.CYRIX
    • DecoderOptions.CYRIX_DMI
    • DecoderOptions.ALTINST
    • (Others like UMOV, KNC are also supported)
    import com.github.icedland.iced.x86.*;
    import com.github.icedland.iced.x86.dec.*;
    import com.github.icedland.iced.x86.fmt.*;
    import com.github.icedland.iced.x86.fmt.nasm.*;
    
    final class Main {
        public static void main(String[] args) {
            byte[] codeBytes = new byte[] {
                // bndmov bnd1,[eax]
                0x66, 0x0F, 0x1A, 0x08,
                // mov tr3,esi
                0x0F, 0x26, (byte)0xDE,
                // rdshr [eax]
                0x0F, 0x36, 0x00,
                // dmint
                0x0F, 0x39,
                // svdc [eax],cs
                0x0F, 0x78, 0x08,
                // cpu_read
                0x0F, 0x3D,
                // pmvzb mm1,[eax]
                0x0F, 0x58, 0x08,
                // frinear
                (byte)0xDF, (byte)0xFC,
                // altinst
                0x0F, 0x3F,
            };
    
            final int decoderOptions = DecoderOptions.MPX | DecoderOptions.MOV_TR |
                DecoderOptions.CYRIX | DecoderOptions.CYRIX_DMI | DecoderOptions.ALTINST;
            ByteArrayCodeReader codeReader = new ByteArrayCodeReader(codeBytes);
            Decoder decoder = new Decoder(32, codeReader, decoderOptions);
            decoder.setIP(0x731E_0A03);
    
            NasmFormatter formatter = new NasmFormatter();
            formatter.getOptions().setSpaceAfterOperandSeparator(true);
            StringOutput output = new StringOutput();
    
            Instruction instr = new Instruction();
            while (codeReader.canReadByte()) {
                decoder.decode(instr);
                formatter.format(instr, output);
                System.out.println(String.format("%08X %s", instr.getIP(), output.toStringAndReset()));
            }
        }
    }
  10. Build iced-x86 JavaScript bindings

    master

    To build the iced-x86 WebAssembly bindings, you need Rust, wasm-pack, and the wasm32-unknown-unknown target. You can use --features to include only what you need to reduce the size of the resulting .wasm, .ts, and .js files.

    For a typical web application using a bundler (like Webpack), use the following command:

    cd src/rust/iced-x86-js
    wasm-pack build --mode force --target bundler -- --no-default-features --features "decoder fast_fmt"

    To target Node.js, change --target bundler to --target nodejs.

    cd src/rust/iced-x86-js
    wasm-pack build --mode force --target bundler -- --no-default-features --features "decoder fast_fmt"