Skip to main content

asmkit/
lib.rs

1//! # asmkit
2//!
3//! ### Overview
4//! asmkit is a portable assembler toolkit built around an AsmJit-style instruction-database
5//! model. It is a small, efficient, `no_std` library for encoding machine code without being
6//! tied to a specific platform. Key features include:
7//!
8//! - **Multi-Architecture Support**: x86/x64, RISC-V, and AArch64. Each backend follows the
9//!   same uniform model: a dense `InstId` enum backed by generated instdb tables,
10//!   and a single checked raw emit entry point.
11//! - **Generated emitter traits**: per-mnemonic traits (e.g. `MovEmitter`) with impls for
12//!   the sized register wrappers, so register constants and integer immediates are passed
13//!   directly (`asm.mov(RAX, 42)` — no dereferencing).
14//! - **Read/write effects**: `query_rw_info(&Inst) -> InstRwInfo` per architecture, over the
15//!   architecture-tagged [`Inst`].
16//! - **Deferred emission**: [`Builder`] records instructions and label binds
17//!   and replays them into any [`InstSink`] (implemented by every architecture's `Assembler`).
18//! - **Minimal Dependencies**:
19//! - - `libc`, `intrusive-collections`, `errno` - For JIT support.
20//! - - `paste`, `bitflags`, `cfgenius`, `num-traits` - Utility crates that simplify repetitive
21//!     arch-specific declarations.
22//! - - `smallvec` - Avoids frequent heap allocation during code generation.
23//! - **Code Relocations**: Provides a CodeBuffer interface to handle relocations, allowing
24//!   the insertion of symbols into the API seamlessly.
25//! - **Portability**: Built to run on any platform, with the architecture-specific parts of
26//!   the library being independent of the platform on which asmkit is built.
27//!
28//! ### From assembly to execution
29//!
30//! The API story mirrors AsmJit's (`CodeHolder` → `JitAllocator`), with
31//! [`CodeBuffer`] playing the `CodeHolder` role:
32//!
33//! 1. Emit into a [`CodeBuffer`] through a backend `Assembler`.
34//! 2. Finalize with [`CodeBuffer::finish`], optionally combining several modules —
35//!    [`Section`]s or plain buffers — into one image with [`Linker`].
36//! 3. Load with [`CodeBufferFinalized::allocate`] (no relocations),
37//!    [`allocate_relocated`](CodeBufferFinalized::allocate_relocated),
38//!    or [`allocate_resolved`](CodeBufferFinalized::allocate_resolved)
39//!    (external symbols resolved via [`ExternalName`]).
40//! 4. Find entry points with
41//!    [`CodeBufferFinalized::defined_symbol_offset`] /
42//!    [`defined_symbol_str`](CodeBufferFinalized::defined_symbol_str)
43//!    and call through `rx() + offset`.
44//!
45//! External names are either string [`ExternalName::Symbol`]s or Cranelift-style
46//! [`ExternalName::User`] namespace+index keys (`extern_user` / `bind_symbol`), so
47//! hosts that do not use string symbols can still link and resolve.
48//!
49//! Void mnemonic methods retain the first emission error in the [`CodeBuffer`].
50//! [`CodeBuffer::finish`] returns it, alongside finalization, linking, loading,
51//! and patching errors, as [`AsmError`].
52//!
53//! ### Usage
54//!
55//! To use the library simply import the module for the architecture you want to emit code
56//! for, e.g. `use asmkit::x86::*;`; this includes all the code required to generate code for
57//! that platform.
58//!
59//! Example:
60//!
61//! ```rust
62//! # #[cfg(all(feature = "jit", feature = "x86"))]
63//! # {
64//! use asmkit::{Arch, CodeBuffer, Environment, JitAllocator};
65//! use asmkit::x86::*;
66//!
67//! let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
68//! {
69//! let mut asm = Assembler::new(&mut buf);
70//!
71//! // Typed sized registers and plain integer immediates.
72//! asm.mov(RAX, 5);
73//! asm.add(RAX, 37);
74//! asm.ret();
75//! }
76//!
77//! let result = buf.finish().expect("assembly failed");
78//! let mut jit = JitAllocator::new(Default::default());
79//! // you can also use jit.alloc + jit.write manually.
80//! let span = result
81//!     .allocate(&mut jit)
82//!     .expect("failed to allocate JIT-code");
83//!
84//! // JIT Allocator uses dual-mapping: it allocates two pages which map to same physical space
85//! // and you write to executable code through `span.rw()` pointer while you can execute `span.rx()`.
86//! let f: extern "C" fn() -> u64 = unsafe { std::mem::transmute(span.rx()) };
87//! #[cfg(all(unix, target_arch = "x86_64"))] // can run only on x64 and on SystemV platforms.
88//! assert_eq!(f(), 42);
89//! # }
90//! ```
91
92#![cfg_attr(not(test), no_std)]
93
94extern crate alloc;
95
96#[cfg(feature = "aarch64")]
97pub mod aarch64;
98pub(crate) mod core;
99#[cfg(feature = "riscv")]
100pub mod riscv;
101#[cfg(feature = "jit")]
102pub(crate) mod util;
103#[cfg(feature = "x86")]
104pub mod x86;
105
106#[cfg(feature = "jit")]
107pub use core::jit_allocator::{JitAllocator, JitAllocatorOptions, ResetPolicy, Span};
108#[cfg(feature = "aarch64")]
109pub use core::target::AArch64Feature;
110#[cfg(feature = "riscv")]
111pub use core::target::RiscVFeature;
112#[cfg(feature = "x86")]
113pub use core::target::X86Feature;
114pub use core::{
115    arch_traits::Arch,
116    buffer::{
117        Addend, AsmReloc, CodeBuffer, CodeBufferFinalized, CodeOffset, Constant, ConstantData,
118        ExternalName, LabelUse, Reloc, RelocDistance, RelocTarget, UserExternalName,
119    },
120    builder::{Builder, InstSink, Node},
121    globals::{CondCode, InstOptions},
122    inst::Inst,
123    linker::{LinkError, Linker},
124    operand::{
125        BaseMem, BaseReg, Imm, ImmType, Label, Operand, OperandCast, OperandSignature, OperandType,
126        RegGroup, RegMask, RegTraits, RegType, Sym, imm,
127    },
128    patch::{PatchBlock, PatchBlockId, PatchCatalog, PatchSite, PatchSiteId},
129    rwinfo::{
130        CpuRwFlags, INVALID_PHYS_ID, InstControlFlow, InstRwFlags, InstRwInfo, InstSameRegHint,
131        OpRwFlags, OpRwInfo,
132    },
133    section::{FinalizedSection, Section},
134    target::Environment,
135};
136#[cfg(feature = "jit")]
137pub use core::{buffer::LoadedRelocatedCode, patch::LoadedPatchableCode};
138
139use ::core::fmt;
140
141/// Error type shared by all backends and the core code-management machinery.
142///
143/// The variant set follows AsmJit's `Error` codes where the same failure mode
144/// exists here; detailed context is carried by nested backend and linker
145/// errors such as [`AsmError::X86`] and [`AsmError::Link`].
146#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
147pub enum AsmError {
148    InvalidPrefix,
149    InvalidOperand,
150    InvalidImmediate,
151    InvalidInstruction,
152    OutOfMemory,
153    InvalidState,
154    TooManyHandles,
155    InvalidArgument,
156    /// Invalid or incompatible architecture (AsmJit's `kErrorInvalidArch`).
157    InvalidArch,
158    /// No code was generated (AsmJit's `kErrorNoCodeGenerated`), e.g. linking
159    /// with no sections.
160    NoCodeGenerated,
161    /// A relocation or defined symbol references a label that was never bound
162    /// (AsmJit's unbound-label diagnostics).
163    UnboundLabel,
164    FailedToOpenAnonymousMemory,
165    TooLarge,
166    /// An in-memory image link failed; the nested error identifies the
167    /// section, symbol, or relocation involved.
168    Link(LinkError),
169    X86(X86Error),
170    /// The selected instruction requires a CPU feature disabled by the target.
171    MissingCpuFeature {
172        feature: &'static str,
173    },
174    UnsupportedInstruction {
175        reason: &'static str,
176    },
177}
178
179impl fmt::Display for AsmError {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        match self {
182            AsmError::InvalidPrefix => write!(f, "invalid prefix"),
183            AsmError::InvalidOperand => write!(f, "invalid operand"),
184            AsmError::InvalidInstruction => write!(f, "invalid instruction"),
185            AsmError::OutOfMemory => write!(f, "out of memory"),
186            AsmError::InvalidState => write!(f, "invalid state"),
187            AsmError::TooManyHandles => write!(f, "too many handles"),
188            AsmError::InvalidArgument => write!(f, "invalid argument"),
189            AsmError::InvalidImmediate => write!(f, "invalid immediate"),
190            AsmError::InvalidArch => write!(f, "invalid or incompatible architecture"),
191            AsmError::NoCodeGenerated => write!(f, "no code generated"),
192            AsmError::UnboundLabel => write!(f, "unbound label"),
193            AsmError::FailedToOpenAnonymousMemory => {
194                write!(f, "failed to open anonymous memory")
195            }
196            AsmError::TooLarge => write!(f, "too large"),
197            AsmError::Link(error) => write!(f, "link error: {error}"),
198            AsmError::X86(e) => write!(f, "x86 error: {}", e),
199            AsmError::MissingCpuFeature { feature } => {
200                write!(f, "missing CPU feature: {}", feature)
201            }
202            AsmError::UnsupportedInstruction { reason } => {
203                write!(f, "unsupported instruction: {}", reason)
204            }
205        }
206    }
207}
208
209impl From<X86Error> for AsmError {
210    fn from(err: X86Error) -> Self {
211        AsmError::X86(err)
212    }
213}
214
215impl ::core::error::Error for AsmError {}
216
217/// Detailed x86/x64 encoding error, wrapped by [`AsmError::X86`].
218#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
219pub enum X86Error {
220    InvalidPrefix {
221        prefix: u64,
222        reason: &'static str,
223    },
224    InvalidOperand {
225        operand_index: usize,
226        reason: &'static str,
227    },
228    InvalidInstruction {
229        opcode: u64,
230        reason: &'static str,
231    },
232    InvalidEncoding {
233        encoding: u8,
234        reason: &'static str,
235    },
236    InvalidModRM {
237        modrm: u8,
238        reason: &'static str,
239    },
240    InvalidSIB {
241        sib: u8,
242        reason: &'static str,
243    },
244    InvalidDisplacement {
245        value: i64,
246        size: usize,
247        reason: &'static str,
248    },
249    InvalidImmediate {
250        value: i64,
251        size: usize,
252        reason: &'static str,
253    },
254    InvalidRegister {
255        reg_id: u32,
256        reg_type: &'static str,
257        reason: &'static str,
258    },
259    InvalidMemoryOperand {
260        base: Option<u32>,
261        index: Option<u32>,
262        scale: u8,
263        offset: i64,
264        reason: &'static str,
265    },
266    InvalidVSIB {
267        index_reg: u32,
268        reason: &'static str,
269    },
270    InvalidMasking {
271        mask_reg: u32,
272        reason: &'static str,
273    },
274    InvalidBroadcast {
275        reason: &'static str,
276    },
277    InvalidRoundingControl {
278        rc: u64,
279        reason: &'static str,
280    },
281    InvalidEVEX {
282        field: &'static str,
283        reason: &'static str,
284    },
285    InvalidVEX {
286        field: &'static str,
287        reason: &'static str,
288    },
289    TooLongInstruction {
290        length: usize,
291        max_length: usize,
292    },
293    SegmentOverrideNotAllowed {
294        segment: u8,
295        reason: &'static str,
296    },
297    AddressSizeMismatch {
298        expected: usize,
299        actual: usize,
300    },
301    OperandSizeMismatch {
302        expected: usize,
303        actual: usize,
304    },
305    InvalidRIPRelative {
306        offset: i64,
307        reason: &'static str,
308    },
309    InvalidLabel {
310        label_id: u32,
311        reason: &'static str,
312    },
313    InvalidSymbol {
314        symbol_id: u32,
315        reason: &'static str,
316    },
317    InvalidRelocation {
318        reloc_type: &'static str,
319        reason: &'static str,
320    },
321    InvalidOperandCombination {
322        mnemonic: &'static str,
323    },
324}
325
326impl fmt::Display for X86Error {
327    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328        match self {
329            X86Error::InvalidPrefix { prefix, reason } => {
330                write!(f, "invalid prefix 0x{:x}: {}", prefix, reason)
331            }
332            X86Error::InvalidOperand {
333                operand_index,
334                reason,
335            } => write!(f, "invalid operand {}: {}", operand_index, reason),
336            X86Error::InvalidInstruction { opcode, reason } => {
337                write!(f, "invalid instruction 0x{:x}: {}", opcode, reason)
338            }
339            X86Error::InvalidEncoding { encoding, reason } => {
340                write!(f, "invalid encoding {}: {}", encoding, reason)
341            }
342            X86Error::InvalidModRM { modrm, reason } => {
343                write!(f, "invalid ModRM byte 0x{:02x}: {}", modrm, reason)
344            }
345            X86Error::InvalidSIB { sib, reason } => {
346                write!(f, "invalid SIB byte 0x{:02x}: {}", sib, reason)
347            }
348            X86Error::InvalidDisplacement {
349                value,
350                size,
351                reason,
352            } => write!(
353                f,
354                "invalid displacement 0x{:x} (size {}): {}",
355                value, size, reason
356            ),
357            X86Error::InvalidImmediate {
358                value,
359                size,
360                reason,
361            } => {
362                write!(
363                    f,
364                    "invalid immediate 0x{:x} (size {}): {}",
365                    value, size, reason
366                )
367            }
368            X86Error::InvalidRegister {
369                reg_id,
370                reg_type,
371                reason,
372            } => write!(
373                f,
374                "invalid register {} (type {}): {}",
375                reg_id, reg_type, reason
376            ),
377            X86Error::InvalidMemoryOperand {
378                base,
379                index,
380                scale,
381                offset,
382                reason,
383            } => write!(
384                f,
385                "invalid memory operand [base={:?}, index={:?}, scale={}, offset={}]: {}",
386                base, index, scale, offset, reason
387            ),
388            X86Error::InvalidVSIB { index_reg, reason } => {
389                write!(f, "invalid VSIB index register {}: {}", index_reg, reason)
390            }
391            X86Error::InvalidMasking { mask_reg, reason } => {
392                write!(f, "invalid mask register {}: {}", mask_reg, reason)
393            }
394            X86Error::InvalidBroadcast { reason } => {
395                write!(f, "invalid broadcast: {}", reason)
396            }
397            X86Error::InvalidRoundingControl { rc, reason } => {
398                write!(f, "invalid rounding control 0x{:x}: {}", rc, reason)
399            }
400            X86Error::InvalidEVEX { field, reason } => {
401                write!(f, "invalid EVEX field '{}': {}", field, reason)
402            }
403            X86Error::InvalidVEX { field, reason } => {
404                write!(f, "invalid VEX field '{}': {}", field, reason)
405            }
406            X86Error::TooLongInstruction { length, max_length } => write!(
407                f,
408                "instruction too long: {} bytes (max {})",
409                length, max_length
410            ),
411            X86Error::SegmentOverrideNotAllowed { segment, reason } => {
412                write!(f, "segment override {} not allowed: {}", segment, reason)
413            }
414            X86Error::AddressSizeMismatch { expected, actual } => write!(
415                f,
416                "address size mismatch: expected {} bytes, got {}",
417                expected, actual
418            ),
419            X86Error::OperandSizeMismatch { expected, actual } => write!(
420                f,
421                "operand size mismatch: expected {} bytes, got {}",
422                expected, actual
423            ),
424            X86Error::InvalidRIPRelative { offset, reason } => {
425                write!(f, "invalid RIP-relative offset {}: {}", offset, reason)
426            }
427            X86Error::InvalidLabel { label_id, reason } => {
428                write!(f, "invalid label {}: {}", label_id, reason)
429            }
430            X86Error::InvalidSymbol { symbol_id, reason } => {
431                write!(f, "invalid symbol {}: {}", symbol_id, reason)
432            }
433            X86Error::InvalidRelocation { reloc_type, reason } => {
434                write!(f, "invalid relocation {}: {}", reloc_type, reason)
435            }
436            X86Error::InvalidOperandCombination { mnemonic } => {
437                write!(
438                    f,
439                    "invalid operand combination for instruction `{}`",
440                    mnemonic
441                )
442            }
443        }
444    }
445}
446
447impl ::core::error::Error for X86Error {}