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::{
129        PatchBlock, PatchBlockId, PatchCatalog, PatchSite, PatchSiteId, PatchableBlock,
130        PatchableSite,
131    },
132    rwinfo::{
133        CpuRwFlags, INVALID_PHYS_ID, InstControlFlow, InstRwFlags, InstRwInfo, InstSameRegHint,
134        OpRwFlags, OpRwInfo,
135    },
136    section::{FinalizedSection, Section},
137    target::Environment,
138};
139#[cfg(feature = "jit")]
140pub use core::buffer::LoadedRelocatedCode;
141
142use ::core::fmt;
143
144/// Error type shared by all backends and the core code-management machinery.
145///
146/// The variant set follows AsmJit's `Error` codes where the same failure mode
147/// exists here; detailed context is carried by nested backend and linker
148/// errors such as [`AsmError::X86`] and [`AsmError::Link`].
149#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
150pub enum AsmError {
151    InvalidPrefix,
152    InvalidOperand,
153    InvalidImmediate,
154    InvalidInstruction,
155    OutOfMemory,
156    InvalidState,
157    TooManyHandles,
158    InvalidArgument,
159    /// Invalid or incompatible architecture (AsmJit's `kErrorInvalidArch`).
160    InvalidArch,
161    /// No code was generated (AsmJit's `kErrorNoCodeGenerated`), e.g. linking
162    /// with no sections.
163    NoCodeGenerated,
164    /// A relocation or defined symbol references a label that was never bound
165    /// (AsmJit's unbound-label diagnostics).
166    UnboundLabel,
167    FailedToOpenAnonymousMemory,
168    TooLarge,
169    /// An in-memory image link failed; the nested error identifies the
170    /// section, symbol, or relocation involved.
171    Link(LinkError),
172    X86(X86Error),
173    /// The selected instruction requires a CPU feature disabled by the target.
174    MissingCpuFeature {
175        feature: &'static str,
176    },
177    UnsupportedInstruction {
178        reason: &'static str,
179    },
180}
181
182impl fmt::Display for AsmError {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        match self {
185            AsmError::InvalidPrefix => write!(f, "invalid prefix"),
186            AsmError::InvalidOperand => write!(f, "invalid operand"),
187            AsmError::InvalidInstruction => write!(f, "invalid instruction"),
188            AsmError::OutOfMemory => write!(f, "out of memory"),
189            AsmError::InvalidState => write!(f, "invalid state"),
190            AsmError::TooManyHandles => write!(f, "too many handles"),
191            AsmError::InvalidArgument => write!(f, "invalid argument"),
192            AsmError::InvalidImmediate => write!(f, "invalid immediate"),
193            AsmError::InvalidArch => write!(f, "invalid or incompatible architecture"),
194            AsmError::NoCodeGenerated => write!(f, "no code generated"),
195            AsmError::UnboundLabel => write!(f, "unbound label"),
196            AsmError::FailedToOpenAnonymousMemory => {
197                write!(f, "failed to open anonymous memory")
198            }
199            AsmError::TooLarge => write!(f, "too large"),
200            AsmError::Link(error) => write!(f, "link error: {error}"),
201            AsmError::X86(e) => write!(f, "x86 error: {}", e),
202            AsmError::MissingCpuFeature { feature } => {
203                write!(f, "missing CPU feature: {}", feature)
204            }
205            AsmError::UnsupportedInstruction { reason } => {
206                write!(f, "unsupported instruction: {}", reason)
207            }
208        }
209    }
210}
211
212impl From<X86Error> for AsmError {
213    fn from(err: X86Error) -> Self {
214        AsmError::X86(err)
215    }
216}
217
218impl ::core::error::Error for AsmError {}
219
220/// Detailed x86/x64 encoding error, wrapped by [`AsmError::X86`].
221#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
222pub enum X86Error {
223    InvalidPrefix {
224        prefix: u64,
225        reason: &'static str,
226    },
227    InvalidOperand {
228        operand_index: usize,
229        reason: &'static str,
230    },
231    InvalidInstruction {
232        opcode: u64,
233        reason: &'static str,
234    },
235    InvalidEncoding {
236        encoding: u8,
237        reason: &'static str,
238    },
239    InvalidModRM {
240        modrm: u8,
241        reason: &'static str,
242    },
243    InvalidSIB {
244        sib: u8,
245        reason: &'static str,
246    },
247    InvalidDisplacement {
248        value: i64,
249        size: usize,
250        reason: &'static str,
251    },
252    InvalidImmediate {
253        value: i64,
254        size: usize,
255        reason: &'static str,
256    },
257    InvalidRegister {
258        reg_id: u32,
259        reg_type: &'static str,
260        reason: &'static str,
261    },
262    InvalidMemoryOperand {
263        base: Option<u32>,
264        index: Option<u32>,
265        scale: u8,
266        offset: i64,
267        reason: &'static str,
268    },
269    InvalidVSIB {
270        index_reg: u32,
271        reason: &'static str,
272    },
273    InvalidMasking {
274        mask_reg: u32,
275        reason: &'static str,
276    },
277    InvalidBroadcast {
278        reason: &'static str,
279    },
280    InvalidRoundingControl {
281        rc: u64,
282        reason: &'static str,
283    },
284    InvalidEVEX {
285        field: &'static str,
286        reason: &'static str,
287    },
288    InvalidVEX {
289        field: &'static str,
290        reason: &'static str,
291    },
292    TooLongInstruction {
293        length: usize,
294        max_length: usize,
295    },
296    SegmentOverrideNotAllowed {
297        segment: u8,
298        reason: &'static str,
299    },
300    AddressSizeMismatch {
301        expected: usize,
302        actual: usize,
303    },
304    OperandSizeMismatch {
305        expected: usize,
306        actual: usize,
307    },
308    InvalidRIPRelative {
309        offset: i64,
310        reason: &'static str,
311    },
312    InvalidLabel {
313        label_id: u32,
314        reason: &'static str,
315    },
316    InvalidSymbol {
317        symbol_id: u32,
318        reason: &'static str,
319    },
320    InvalidRelocation {
321        reloc_type: &'static str,
322        reason: &'static str,
323    },
324    InvalidOperandCombination {
325        mnemonic: &'static str,
326    },
327}
328
329impl fmt::Display for X86Error {
330    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331        match self {
332            X86Error::InvalidPrefix { prefix, reason } => {
333                write!(f, "invalid prefix 0x{:x}: {}", prefix, reason)
334            }
335            X86Error::InvalidOperand {
336                operand_index,
337                reason,
338            } => write!(f, "invalid operand {}: {}", operand_index, reason),
339            X86Error::InvalidInstruction { opcode, reason } => {
340                write!(f, "invalid instruction 0x{:x}: {}", opcode, reason)
341            }
342            X86Error::InvalidEncoding { encoding, reason } => {
343                write!(f, "invalid encoding {}: {}", encoding, reason)
344            }
345            X86Error::InvalidModRM { modrm, reason } => {
346                write!(f, "invalid ModRM byte 0x{:02x}: {}", modrm, reason)
347            }
348            X86Error::InvalidSIB { sib, reason } => {
349                write!(f, "invalid SIB byte 0x{:02x}: {}", sib, reason)
350            }
351            X86Error::InvalidDisplacement {
352                value,
353                size,
354                reason,
355            } => write!(
356                f,
357                "invalid displacement 0x{:x} (size {}): {}",
358                value, size, reason
359            ),
360            X86Error::InvalidImmediate {
361                value,
362                size,
363                reason,
364            } => {
365                write!(
366                    f,
367                    "invalid immediate 0x{:x} (size {}): {}",
368                    value, size, reason
369                )
370            }
371            X86Error::InvalidRegister {
372                reg_id,
373                reg_type,
374                reason,
375            } => write!(
376                f,
377                "invalid register {} (type {}): {}",
378                reg_id, reg_type, reason
379            ),
380            X86Error::InvalidMemoryOperand {
381                base,
382                index,
383                scale,
384                offset,
385                reason,
386            } => write!(
387                f,
388                "invalid memory operand [base={:?}, index={:?}, scale={}, offset={}]: {}",
389                base, index, scale, offset, reason
390            ),
391            X86Error::InvalidVSIB { index_reg, reason } => {
392                write!(f, "invalid VSIB index register {}: {}", index_reg, reason)
393            }
394            X86Error::InvalidMasking { mask_reg, reason } => {
395                write!(f, "invalid mask register {}: {}", mask_reg, reason)
396            }
397            X86Error::InvalidBroadcast { reason } => {
398                write!(f, "invalid broadcast: {}", reason)
399            }
400            X86Error::InvalidRoundingControl { rc, reason } => {
401                write!(f, "invalid rounding control 0x{:x}: {}", rc, reason)
402            }
403            X86Error::InvalidEVEX { field, reason } => {
404                write!(f, "invalid EVEX field '{}': {}", field, reason)
405            }
406            X86Error::InvalidVEX { field, reason } => {
407                write!(f, "invalid VEX field '{}': {}", field, reason)
408            }
409            X86Error::TooLongInstruction { length, max_length } => write!(
410                f,
411                "instruction too long: {} bytes (max {})",
412                length, max_length
413            ),
414            X86Error::SegmentOverrideNotAllowed { segment, reason } => {
415                write!(f, "segment override {} not allowed: {}", segment, reason)
416            }
417            X86Error::AddressSizeMismatch { expected, actual } => write!(
418                f,
419                "address size mismatch: expected {} bytes, got {}",
420                expected, actual
421            ),
422            X86Error::OperandSizeMismatch { expected, actual } => write!(
423                f,
424                "operand size mismatch: expected {} bytes, got {}",
425                expected, actual
426            ),
427            X86Error::InvalidRIPRelative { offset, reason } => {
428                write!(f, "invalid RIP-relative offset {}: {}", offset, reason)
429            }
430            X86Error::InvalidLabel { label_id, reason } => {
431                write!(f, "invalid label {}: {}", label_id, reason)
432            }
433            X86Error::InvalidSymbol { symbol_id, reason } => {
434                write!(f, "invalid symbol {}: {}", symbol_id, reason)
435            }
436            X86Error::InvalidRelocation { reloc_type, reason } => {
437                write!(f, "invalid relocation {}: {}", reloc_type, reason)
438            }
439            X86Error::InvalidOperandCombination { mnemonic } => {
440                write!(
441                    f,
442                    "invalid operand combination for instruction `{}`",
443                    mnemonic
444                )
445            }
446        }
447    }
448}
449
450impl ::core::error::Error for X86Error {}