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