├── .github └── workflows │ └── Build_and_test.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── build_docs.sh ├── doc ├── .gitignore ├── examples │ ├── README.md │ ├── bf-interpreter │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── bf-jit │ │ ├── Cargo.toml │ │ ├── src │ │ │ ├── aarch64.rs │ │ │ ├── riscv64.rs │ │ │ └── x64.rs │ │ └── test.bin │ ├── hello world.bf │ └── hello-world │ │ ├── Cargo.toml │ │ └── src │ │ ├── aarch64.rs │ │ ├── riscv64.rs │ │ └── x64.rs ├── formatting.css ├── hack.js ├── index.md ├── insref │ ├── Cargo.toml │ └── src │ │ ├── export.rs │ │ └── main.rs ├── langref_aarch64.md ├── langref_common.md ├── langref_riscv.md ├── langref_x64.md ├── post.html ├── pre.html ├── releasenotes.md └── tutorial.md ├── plugin ├── Cargo.toml └── src │ ├── arch │ ├── aarch64 │ │ ├── aarch64data.rs │ │ ├── ast.rs │ │ ├── compiler.rs │ │ ├── debug.rs │ │ ├── encoding_helpers.rs │ │ ├── matching.rs │ │ ├── mod.rs │ │ ├── opmap.rs │ │ └── parser.rs │ ├── mod.rs │ ├── riscv │ │ ├── ast.rs │ │ ├── compiler.rs │ │ ├── debug.rs │ │ ├── matching.rs │ │ ├── mod.rs │ │ ├── opmap.rs │ │ ├── parser.rs │ │ └── riscvdata.rs │ └── x64 │ │ ├── ast.rs │ │ ├── compiler.rs │ │ ├── debug.rs │ │ ├── gen_opmap.rs │ │ ├── mod.rs │ │ ├── parser.rs │ │ └── x64data.rs │ ├── common.rs │ ├── directive.rs │ ├── lib.rs │ ├── parse_helpers.rs │ └── serialize.rs ├── runtime ├── Cargo.toml └── src │ ├── aarch64.rs │ ├── cache_control.rs │ ├── components.rs │ ├── lib.rs │ ├── mmap.rs │ ├── relocations.rs │ ├── riscv.rs │ ├── x64.rs │ └── x86.rs ├── testing ├── Cargo.toml ├── src │ └── main.rs └── tests │ ├── aarch64_0.rs │ ├── aarch64_1.rs │ ├── aarch64_2.rs │ ├── aarch64_3.rs │ ├── aarch64_4.rs │ ├── aarch64_5.rs │ ├── aarch64_6.rs │ ├── aarch64_7.rs │ ├── aarch64_8.rs │ ├── aarch64_cache_coherency.rs │ ├── aarch64_complex.rs │ ├── aarch64_immediate_checking.rs │ ├── bugreports.rs │ ├── directives.rs │ ├── gen_aarch64 │ ├── aarch64_tests_0.rs.gen │ ├── aarch64_tests_1.rs.gen │ ├── aarch64_tests_2.rs.gen │ ├── aarch64_tests_3.rs.gen │ ├── aarch64_tests_4.rs.gen │ ├── aarch64_tests_5.rs.gen │ ├── aarch64_tests_6.rs.gen │ ├── aarch64_tests_7.rs.gen │ └── aarch64_tests_8.rs.gen │ ├── gen_riscv32 │ ├── riscv32_tests_0.rs.gen │ └── riscv32_tests_1.rs.gen │ ├── gen_riscv64 │ ├── riscv64_tests_0.rs.gen │ ├── riscv64_tests_1.rs.gen │ └── riscv64_tests_2.rs.gen │ ├── gen_x64 │ ├── amd.rs.gen │ ├── avx.rs.gen │ ├── avx2.rs.gen │ ├── avx512.rs.gen │ ├── avx512bw.rs.gen │ ├── avx512cd.rs.gen │ ├── avx512dq.rs.gen │ ├── avx512er.rs.gen │ ├── avx512ifma.rs.gen │ ├── avx512pf.rs.gen │ ├── avx512vbmi.rs.gen │ ├── avx512vl.rs.gen │ ├── bmi1.rs.gen │ ├── bmi2.rs.gen │ ├── cyrix.rs.gen │ ├── directstores.rs.gen │ ├── fma.rs.gen │ ├── fpu.rs.gen │ ├── generic.rs.gen │ ├── invpcid.rs.gen │ ├── mmx.rs.gen │ ├── mpx.rs.gen │ ├── prefetchwt1.rs.gen │ ├── rtm.rs.gen │ ├── sha.rs.gen │ ├── sse.rs.gen │ ├── sse2.rs.gen │ ├── sse3.rs.gen │ ├── sse41.rs.gen │ ├── sse42.rs.gen │ ├── sse4a.rs.gen │ ├── sse5.rs.gen │ ├── ssse3.rs.gen │ ├── tbm.rs.gen │ ├── tdnow.rs.gen │ └── vmx.rs.gen │ ├── riscv32_0.rs │ ├── riscv32_1.rs │ ├── riscv64_0.rs │ ├── riscv64_1.rs │ ├── riscv64_2.rs │ ├── riscv_cache_coherency.rs │ ├── riscv_complex.rs │ ├── riscv_dynamic.rs │ ├── riscv_extra_insns.rs │ ├── x64_0_complex.rs │ ├── x64_0_relocations.rs │ ├── x64_amd.rs │ ├── x64_avx.rs │ ├── x64_avx2.rs │ ├── x64_avx512.rs │ ├── x64_avx512bw.rs │ ├── x64_avx512cd.rs │ ├── x64_avx512dq.rs │ ├── x64_avx512er.rs │ ├── x64_avx512ifma.rs │ ├── x64_avx512pf.rs │ ├── x64_avx512vbmi.rs │ ├── x64_avx512vl.rs │ ├── x64_bmi1.rs │ ├── x64_bmi2.rs │ ├── x64_cyrix.rs │ ├── x64_directstores.rs │ ├── x64_fma.rs │ ├── x64_fpu.rs │ ├── x64_generic.rs │ ├── x64_invpcid.rs │ ├── x64_mmx.rs │ ├── x64_mpx.rs │ ├── x64_prefetchwt1.rs │ ├── x64_rtm.rs │ ├── x64_sha.rs │ ├── x64_sse.rs │ ├── x64_sse2.rs │ ├── x64_sse3.rs │ ├── x64_sse41.rs │ ├── x64_sse42.rs │ ├── x64_sse4a.rs │ ├── x64_sse5.rs │ ├── x64_ssse3.rs │ ├── x64_tbm.rs │ ├── x64_tdnow.rs │ └── x64_vmx.rs └── tools ├── README.md ├── aarch64_compile_tests.py ├── aarch64_data ├── tl_advsimd.py ├── tl_float.py ├── tl_fpsimd.py ├── tl_general.py ├── tl_sve.py └── tl_system.py ├── aarch64_emit_tests.py ├── aarch64_gen_opmap.py ├── aarch64_gen_tests.py ├── riscv_compile_tests.py ├── riscv_emit_tests.py ├── riscv_gen_opmap.py ├── riscv_gen_tests.py └── riscv_load_data.py /.github/workflows/Build_and_test.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master, dev ] 6 | pull_request: 7 | branches: [ master, dev ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build and run tests 20 | run: | 21 | cd testing && cargo update && cargo test -j 1 --verbose 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.txt 3 | *.sublime-* 4 | *.exe 5 | *.pdb 6 | *.swp 7 | 8 | .python-version 9 | *.prv 10 | 11 | target/ 12 | build_docs/ 13 | Cargo.lock 14 | 15 | testing/tests/bf_interpreter*.rs 16 | testing/tests/bf_jit*.rs 17 | testing/tests/hello_world*.rs 18 | 19 | __pycache__ 20 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tools/riscv_data/riscv-opcodes"] 2 | path = tools/riscv_data/riscv-opcodes 3 | url = git@github.com:riscv/riscv-opcodes.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | script: 5 | - cd testing && cargo update && cargo test -j 1 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "plugin", 6 | "runtime", 7 | "testing", 8 | "doc/insref", 9 | "doc/examples/bf-interpreter", 10 | "doc/examples/bf-jit", 11 | "doc/examples/hello-world", 12 | ] 13 | 14 | [workspace.package] 15 | authors = ["Alexander Stocko ", "CensoredUsername "] 16 | version = "3.2.0" 17 | edition = "2021" 18 | 19 | documentation = "https://censoredusername.github.io/dynasm-rs/language/index.html" 20 | repository = "https://github.com/CensoredUsername/dynasm-rs" 21 | 22 | readme = "README.md" 23 | keywords = ["jit", "dynasm", "dynasmrt", "dynasm-rs", "assembler"] 24 | license = "MPL-2.0" 25 | 26 | # Source Code Form notice (MPL-2.0 Exhibit A) 27 | # This notice applies to all source files within this workspace. 28 | # 29 | # This Source Code Form is subject to the terms of the Mozilla Public 30 | # License, v. 2.0. If a copy of the MPL was not distributed with this 31 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Dynamic assembler written in Rust for Rust. 2 | 3 | The purpose of this tool is to ease the creation of programs that require run-time assembling. 4 | 5 | It is compatible with stable `rustc` 1.77 and higher. 6 | 7 | [![Build status](https://github.com/CensoredUsername/dynasm-rs/actions/workflows/Build_and_test.yml/badge.svg?branch=master)](https://github.com/CensoredUsername/dynasm-rs/actions/workflows/Build_and_test.yml) 8 | [![](https://img.shields.io/crates/v/dynasm.svg)](https://crates.io/crates/dynasm) 9 | 10 | `##dynasm-rs` on irc.libera.chat 11 | 12 | ## Features 13 | 14 | - Fully integrated in the Rust toolchain, no other tools necessary. 15 | - The assembly is optimized into a series of `Vec.push` and `Vec.extend` statements. 16 | - Errors are almost all diagnosed at compile time in a clear fashion. 17 | - Write the to be generated assembly inline in nasm-like syntax using a simple macro. 18 | 19 | ## Documentation 20 | 21 | [Documentation](https://CensoredUsername.github.io/dynasm-rs/language/index.html). 22 | [Release notes](https://github.com/CensoredUsername/dynasm-rs/blob/master/doc/releasenotes.md). 23 | 24 | ## Architecture support 25 | 26 | - Supports the x64/x86 instruction sets in long and protected mode with every AMD/Intel/VIA extension except for AVX-512. 27 | - Supports the aarch64 instruction set up to ARMv8.4 except for SVE instructions. The development of this assembler backend has been generously sponsored by the awesome folks at [Wasmer](https://github.com/wasmerio/wasmer)! 28 | - Supports the riscv32 and riscv64 instruction sets, with many extensions. The development of these assembler backends was sponsored by [Wasmer](https://github.com/wasmerio/wasmer) as well! 29 | 30 | ## Example 31 | 32 | ```rust 33 | use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; 34 | 35 | use std::{io, slice, mem}; 36 | use std::io::Write; 37 | 38 | fn main() { 39 | let mut ops = dynasmrt::x64::Assembler::new().unwrap(); 40 | let string = "Hello World!"; 41 | 42 | dynasm!(ops 43 | ; .arch x64 44 | ; ->hello: 45 | ; .bytes string.as_bytes() 46 | ); 47 | 48 | let hello = ops.offset(); 49 | dynasm!(ops 50 | ; .arch x64 51 | ; lea rcx, [->hello] 52 | ; xor edx, edx 53 | ; mov dl, BYTE string.len() as _ 54 | ; mov rax, QWORD print as _ 55 | ; sub rsp, BYTE 0x28 56 | ; call rax 57 | ; add rsp, BYTE 0x28 58 | ; ret 59 | ); 60 | 61 | let buf = ops.finalize().unwrap(); 62 | 63 | let hello_fn: extern "win64" fn() -> bool = unsafe { mem::transmute(buf.ptr(hello)) }; 64 | 65 | assert!(hello_fn()); 66 | } 67 | 68 | pub extern "win64" fn print(buffer: *const u8, length: u64) -> bool { 69 | io::stdout() 70 | .write_all(unsafe { slice::from_raw_parts(buffer, length as usize) }) 71 | .is_ok() 72 | } 73 | ``` 74 | 75 | ## Background 76 | 77 | This project is heavily inspired by [Dynasm](http://luajit.org/dynasm.html) 78 | 79 | ## Sponsorship 80 | 81 | The development of the Aarch64 assembler backend has been sponsored by [Wasmer](https://github.com/wasmerio/wasmer). 82 | 83 | ## License 84 | 85 | Mozilla Public License, v. 2.0, see LICENSE 86 | 87 | Copyright 2016 CensoredUsername 88 | 89 | ## Guaranteed to be working compiler versions 90 | 91 | This project used to be a compiler plugin, so for old compilers, here's a list of which version of dynasm was guaranteed to work with which compiler. 92 | As the project has since transitioned to be a proc macro, this is not relevant for modern versions of the compiler. 93 | 94 | - `v0.2.0`: `rustc 1.27.0-nightly (ac3c2288f 2018-04-18)` 95 | - `v0.2.1`: `rustc 1.28.0-nightly (a1d4a9503 2018-05-20)` 96 | - `v0.2.3`: `rustc 1.31.0-nightly (96cafc53c 2018-10-09)` 97 | -------------------------------------------------------------------------------- /build_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | 5 | shopt -s globstar 6 | 7 | echo "build individual documentation" 8 | cargo doc -p dynasm --no-deps 9 | cargo doc -p dynasmrt --no-deps 10 | 11 | echo "remove old docs build" 12 | rm -rf build_docs 13 | 14 | echo "build directory structure" 15 | mkdir ./build_docs 16 | mkdir ./build_docs/language 17 | 18 | echo "create instruction reference markdown file" 19 | (cd doc/insref && cargo update && cargo run -- x64 > ../instructionref_x64.md && cargo run -- aarch64 > ../instructionref_aarch64.md && cargo run -- riscv > ../instructionref_riscv.md) 20 | 21 | echo "build plugin docs" 22 | for f in ./doc/*.md; do 23 | rustdoc $f -o ./build_docs/language --markdown-no-toc --html-before-content=./doc/pre.html --html-after-content=./doc/post.html --markdown-css=./formatting.css 24 | done 25 | 26 | echo "copy over the docs folders" 27 | cp -r ./target/doc/* ./build_docs/ 28 | 29 | echo "Do some css linking" 30 | cd build_docs/language 31 | RUSTDOC=$(echo ../static.files/rustdoc*.css) 32 | LIGHT=$(echo ../static.files/light*.css) 33 | cd ../.. 34 | RUSTDOC=$RUSTDOC LIGHT=$LIGHT envsubst < ./doc/formatting.css > ./build_docs/language/formatting.css 35 | 36 | 37 | echo "insert javascript" 38 | cat ./doc/hack.js >> ./build_docs/static.files/main-*.js 39 | 40 | echo "copy docs examples to tests" 41 | declare -a examples=("bf-jit" "hello-world" "bf-interpreter") 42 | for EX in "${examples[@]}" 43 | do 44 | TARGET=$(echo $EX | tr - _) 45 | if [ -f "./doc/examples/${EX}/src/main.rs" ]; then 46 | cp "./doc/examples/${EX}/src/main.rs" "./testing/tests/${TARGET}.rs" 47 | echo -n -e "#[test]\nfn ex_${TARGET}()\n{\n main();\n}\n" >> \ 48 | "./testing/tests/${TARGET}.rs" 49 | fi 50 | if [ -f "./doc/examples/${EX}/src/x64.rs" ]; then 51 | echo -n -e "#[cfg(target_arch=\"x86_64\")]\nmod test {\n" > \ 52 | "./testing/tests/${TARGET}_x64.rs" 53 | cat "./doc/examples/${EX}/src/x64.rs" >> "./testing/tests/${TARGET}_x64.rs" 54 | echo -n -e "\n#[test]\nfn ex_${TARGET}()\n{\n main();\n}\n}\n" >> \ 55 | "./testing/tests/${TARGET}_x64.rs" 56 | fi 57 | if [ -f "./doc/examples/${EX}/src/aarch64.rs" ]; then 58 | echo -n -e "#[cfg(target_arch=\"aarch64\")]\nmod test {\n" > \ 59 | "./testing/tests/${TARGET}_aarch64.rs" 60 | cat "./doc/examples/${EX}/src/aarch64.rs" >> "./testing/tests/${TARGET}_aarch64.rs" 61 | echo -n -e "\n#[test]\nfn ex_${TARGET}()\n{\n main();\n}\n}\n" >> \ 62 | "./testing/tests/${TARGET}_aarch64.rs" 63 | fi 64 | if [ -f "./doc/examples/${EX}/src/riscv64.rs" ]; then 65 | echo -n -e "#[cfg(target_arch=\"riscv64\")]\nmod test {\n" > \ 66 | "./testing/tests/${TARGET}_riscv64.rs" 67 | cat "./doc/examples/${EX}/src/riscv64.rs" >> "./testing/tests/${TARGET}_riscv64.rs" 68 | echo -n -e "\n#[test]\nfn ex_${TARGET}()\n{\n main();\n}\n}\n" >> \ 69 | "./testing/tests/${TARGET}_riscv64.rs" 70 | fi 71 | done 72 | 73 | if [ "$1" == "commit" ]; then 74 | echo "cloning gh-pages into a temporary directory" 75 | git clone --branch gh-pages --depth 1 "git@github.com:CensoredUsername/dynasm-rs.git" deploy_docs 76 | git gc 77 | cd deploy_docs 78 | git config user.name "CensoredUsername" 79 | git config user.email "cens.username@gmail.com" 80 | cp ../build_docs/* ./ -r 81 | git add . 82 | git commit -m "Rebuild docs" 83 | git push origin gh-pages 84 | cd .. 85 | rm deploy_docs -rf 86 | fi 87 | 88 | exit 89 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | instructionref*.md 2 | -------------------------------------------------------------------------------- /doc/examples/README.md: -------------------------------------------------------------------------------- 1 | This folder contains several example dynasm-rs based projects. 2 | If the project contains arch-specific assembly, it will contain a binary target per architecture. 3 | 4 | Each of them can be executed with the normal `cargo run`, i.e. `cargo run --bin=aarch64` for the aarch64 version. 5 | -------------------------------------------------------------------------------- /doc/examples/bf-interpreter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bf-int" 3 | version = "0.1.0" 4 | authors = ["CensoredUsername "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /doc/examples/bf-interpreter/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, BufRead, Write, stdin, stdout, BufReader, BufWriter}; 2 | use std::env; 3 | use std::fs::File; 4 | 5 | const TAPE_SIZE: usize = 30000; 6 | 7 | struct Interpreter<'a> { 8 | pub input: Box, 9 | pub output: Box, 10 | pub loops: Vec, 11 | pub tape: [u8; TAPE_SIZE], 12 | pub tape_index: usize, 13 | pub pos: usize 14 | } 15 | 16 | impl<'a> Interpreter<'a> { 17 | fn new(input: Box, output: Box) -> Interpreter<'a> { 18 | Interpreter { 19 | input: input, 20 | output: output, 21 | loops: Vec::new(), 22 | tape: [0; TAPE_SIZE], 23 | tape_index: 0, 24 | pos: 0 25 | } 26 | } 27 | 28 | fn run(&mut self, program: &[u8]) -> Result<(), &'static str> { 29 | while let Some(&c) = program.get(self.pos) { 30 | self.pos += 1; 31 | 32 | match c { 33 | b'<' => { 34 | let amount = count_leading_chars(&program[self.pos..], b'<'); 35 | self.pos += amount; 36 | 37 | self.tape_index = self.tape_index.wrapping_sub(amount + 1); 38 | while self.tape_index >= TAPE_SIZE { 39 | self.tape_index = self.tape_index.wrapping_add(TAPE_SIZE); 40 | } 41 | }, 42 | b'>' => { 43 | let amount = count_leading_chars(&program[self.pos..], b'>'); 44 | self.pos += amount; 45 | 46 | self.tape_index += amount + 1; 47 | while self.tape_index >= TAPE_SIZE { 48 | self.tape_index -= TAPE_SIZE; 49 | } 50 | }, 51 | b'+' => { 52 | let amount = count_leading_chars(&program[self.pos..], b'+'); 53 | self.pos += amount; 54 | if let Some(a) = self.tape[self.tape_index].checked_add(amount as u8 + 1) { 55 | self.tape[self.tape_index] = a; 56 | } else { 57 | return Err("An overflow occurred"); 58 | } 59 | }, 60 | b'-' => { 61 | let amount = count_leading_chars(&program[self.pos..], b'-'); 62 | self.pos += amount; 63 | if let Some(a) = self.tape[self.tape_index].checked_sub(amount as u8 + 1) { 64 | self.tape[self.tape_index] = a; 65 | } else { 66 | return Err("An overflow occurred"); 67 | } 68 | }, 69 | b',' => { 70 | let err = self.output.flush().is_err(); 71 | if self.input.read_exact(&mut self.tape[self.tape_index..self.tape_index + 1]).is_err() || err { 72 | return Err("IO error"); 73 | } 74 | }, 75 | b'.' => { 76 | if self.output.write_all(&self.tape[self.tape_index..self.tape_index + 1]).is_err() { 77 | return Err("IO error"); 78 | } 79 | }, 80 | b'[' => { 81 | if self.tape[self.tape_index] == 0 { 82 | let mut nesting = 1; 83 | let amount = program[self.pos..].iter().take_while(|x| match **x { 84 | b'[' => {nesting += 1; true}, 85 | b']' => {nesting -= 1; nesting != 0}, 86 | _ => true 87 | }).count() + 1; 88 | if nesting != 0 { 89 | return Err("[ without matching ]"); 90 | } 91 | self.pos += amount; 92 | } else { 93 | self.loops.push(self.pos); 94 | } 95 | }, 96 | b']' => { 97 | if self.tape[self.tape_index] == 0 { 98 | self.loops.pop(); 99 | } else if let Some(&loc) = self.loops.last() { 100 | self.pos = loc; 101 | } else { 102 | return Err("] without matching ["); 103 | } 104 | }, 105 | _ => () 106 | } 107 | } 108 | 109 | if self.loops.len() != 0 { 110 | return Err("[ without matching ]"); 111 | } 112 | Ok(()) 113 | } 114 | } 115 | 116 | fn count_leading_chars(program: &[u8], c: u8) -> usize { 117 | program.iter().take_while(|x| **x == c).count() 118 | } 119 | 120 | fn main() { 121 | let mut args: Vec<_> = env::args().collect(); 122 | if args.len() != 2 { 123 | println!("Expected 1 argument, got {}", args.len()); 124 | return; 125 | } 126 | let path = args.pop().unwrap(); 127 | 128 | let mut f = if let Ok(f) = File::open(&path) { f } else { 129 | println!("Could not open file {}", path); 130 | return; 131 | }; 132 | 133 | let mut buf = Vec::new(); 134 | if let Err(_) = f.read_to_end(&mut buf) { 135 | println!("Failed to read from file"); 136 | return; 137 | } 138 | 139 | let mut interp = Interpreter::new( 140 | Box::new(BufReader::new(stdin())), 141 | Box::new(BufWriter::new(stdout())) 142 | ); 143 | if let Err(e) = interp.run(&buf) { 144 | println!("{}", e); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /doc/examples/bf-jit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bf-jit" 3 | version = "0.1.0" 4 | authors = ["CensoredUsername "] 5 | edition = "2021" 6 | 7 | [[bin]] 8 | name = "bf-jit-x64" 9 | path = "src/x64.rs" 10 | 11 | [[bin]] 12 | name = "bf-jit-aarch64" 13 | path = "src/aarch64.rs" 14 | 15 | [[bin]] 16 | name = "bf-jit-riscv64" 17 | path = "src/riscv64.rs" 18 | 19 | [dependencies] 20 | itertools = "0.14.0" 21 | 22 | [dependencies.dynasmrt] 23 | path = "../../../runtime" 24 | -------------------------------------------------------------------------------- /doc/examples/bf-jit/src/x64.rs: -------------------------------------------------------------------------------- 1 | use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; 2 | 3 | use itertools::Itertools; 4 | use itertools::multipeek; 5 | 6 | use std::io::{Read, BufRead, Write, stdin, stdout, BufReader, BufWriter}; 7 | use std::env; 8 | use std::fs::File; 9 | use std::slice; 10 | use std::mem; 11 | use std::u8; 12 | 13 | const TAPE_SIZE: usize = 30000; 14 | 15 | macro_rules! my_dynasm { 16 | ($ops:ident $($t:tt)*) => { 17 | dynasm!($ops 18 | ; .arch x64 19 | ; .alias a_state, rcx 20 | ; .alias a_current, rdx 21 | ; .alias a_begin, r8 22 | ; .alias a_end, r9 23 | ; .alias retval, rax 24 | $($t)* 25 | ) 26 | } 27 | } 28 | 29 | macro_rules! prologue { 30 | ($ops:ident) => {{ 31 | let start = $ops.offset(); 32 | my_dynasm!($ops 33 | ; sub rsp, 0x28 34 | ; mov [rsp + 0x30], rcx 35 | ; mov [rsp + 0x40], r8 36 | ; mov [rsp + 0x48], r9 37 | ); 38 | start 39 | }}; 40 | } 41 | 42 | macro_rules! epilogue { 43 | ($ops:ident, $e:expr) => {my_dynasm!($ops 44 | ; mov retval, $e 45 | ; add rsp, 0x28 46 | ; ret 47 | );}; 48 | } 49 | 50 | macro_rules! call_extern { 51 | ($ops:ident, $addr:expr) => {my_dynasm!($ops 52 | ; mov [rsp + 0x38], rdx 53 | ; mov rax, QWORD $addr as _ 54 | ; call rax 55 | ; mov rcx, [rsp + 0x30] 56 | ; mov rdx, [rsp + 0x38] 57 | ; mov r8, [rsp + 0x40] 58 | ; mov r9, [rsp + 0x48] 59 | );}; 60 | } 61 | 62 | struct State<'a> { 63 | pub input: Box, 64 | pub output: Box, 65 | tape: [u8; TAPE_SIZE], 66 | } 67 | 68 | struct Program { 69 | code: dynasmrt::ExecutableBuffer, 70 | start: dynasmrt::AssemblyOffset, 71 | } 72 | 73 | 74 | impl Program { 75 | fn compile(program: &[u8]) -> Result { 76 | let mut ops = dynasmrt::x64::Assembler::new().unwrap(); 77 | let mut loops = Vec::new(); 78 | let mut code = multipeek(program.iter().cloned()); 79 | 80 | let start = prologue!(ops); 81 | 82 | while let Some(c) = code.next() { 83 | match c { 84 | b'<' => { 85 | let amount = code.take_while_ref(|x| *x == b'<').count() + 1; 86 | my_dynasm!(ops 87 | ; sub a_current, (amount % TAPE_SIZE) as _ 88 | ; cmp a_current, a_begin 89 | ; jae >wrap 90 | ; add a_current, TAPE_SIZE as _ 91 | ;wrap: 92 | ); 93 | } 94 | b'>' => { 95 | let amount = code.take_while_ref(|x| *x == b'>').count() + 1; 96 | my_dynasm!(ops 97 | ; add a_current, (amount % TAPE_SIZE) as _ 98 | ; cmp a_current, a_end 99 | ; jb >wrap 100 | ; sub a_current, TAPE_SIZE as _ 101 | ;wrap: 102 | ); 103 | }, 104 | b'+' => { 105 | let amount = code.take_while_ref(|x| *x == b'+').count() + 1; 106 | if amount > u8::MAX as usize { 107 | return Err("An overflow occurred"); 108 | } 109 | my_dynasm!(ops 110 | ; add BYTE [a_current], amount as _ 111 | ; jo ->overflow 112 | ); 113 | }, 114 | b'-' => { 115 | let amount = code.take_while_ref(|x| *x == b'-').count() + 1; 116 | if amount > u8::MAX as usize { 117 | return Err("An overflow occurred"); 118 | } 119 | my_dynasm!(ops 120 | ; sub BYTE [a_current], amount as _ 121 | ; jo ->overflow 122 | ); 123 | }, 124 | b',' => { 125 | my_dynasm!(ops 126 | ;; call_extern!(ops, State::getchar) 127 | ; cmp al, 0 128 | ; jnz ->io_failure 129 | ); 130 | }, 131 | b'.' => { 132 | my_dynasm!(ops 133 | ;; call_extern!(ops, State::putchar) 134 | ; cmp al, 0 135 | ; jnz ->io_failure 136 | ); 137 | }, 138 | b'[' => { 139 | let first = code.peek() == Some(&b'-'); 140 | if first && code.peek() == Some(&b']') { 141 | code.next(); 142 | code.next(); 143 | my_dynasm!(ops 144 | ; mov BYTE [a_current], 0 145 | ); 146 | } else { 147 | let backward_label = ops.new_dynamic_label(); 148 | let forward_label = ops.new_dynamic_label(); 149 | loops.push((backward_label, forward_label)); 150 | my_dynasm!(ops 151 | ; cmp BYTE [a_current], 0 152 | ; jz =>forward_label 153 | ;=>backward_label 154 | ); 155 | } 156 | }, 157 | b']' => { 158 | if let Some((backward_label, forward_label)) = loops.pop() { 159 | my_dynasm!(ops 160 | ; cmp BYTE [a_current], 0 161 | ; jnz =>backward_label 162 | ;=>forward_label 163 | ); 164 | } else { 165 | return Err("] without matching ["); 166 | } 167 | }, 168 | _ => (), 169 | } 170 | } 171 | if loops.len() != 0 { 172 | return Err("[ without matching ]"); 173 | } 174 | 175 | my_dynasm!(ops 176 | ;; epilogue!(ops, 0) 177 | ;->overflow: 178 | ;; epilogue!(ops, 1) 179 | ;->io_failure: 180 | ;; epilogue!(ops, 2) 181 | ); 182 | 183 | let code = ops.finalize().unwrap(); 184 | Ok(Program { 185 | code: code, 186 | start: start, 187 | }) 188 | } 189 | 190 | fn run(self, state: &mut State) -> Result<(), &'static str> { 191 | let f: extern "win64" fn(*mut State, *mut u8, *mut u8, *const u8) -> u8 = 192 | unsafe { mem::transmute(self.code.ptr(self.start)) }; 193 | let start = state.tape.as_mut_ptr(); 194 | let end = unsafe { start.offset(TAPE_SIZE as isize) }; 195 | let res = f(state, start, start, end); 196 | if res == 0 { 197 | Ok(()) 198 | } else if res == 1 { 199 | Err("An overflow occurred") 200 | } else if res == 2 { 201 | Err("IO error") 202 | } else { 203 | panic!("Unknown error code"); 204 | } 205 | } 206 | } 207 | 208 | impl<'a> State<'a> { 209 | unsafe extern "win64" fn getchar(state: *mut State, cell: *mut u8) -> u8 { 210 | let state = &mut *state; 211 | let err = state.output.flush().is_err(); 212 | (state.input.read_exact(slice::from_raw_parts_mut(cell, 1)).is_err() || err) as u8 213 | } 214 | 215 | unsafe extern "win64" fn putchar(state: *mut State, cell: *mut u8) -> u8 { 216 | let state = &mut *state; 217 | state.output.write_all(slice::from_raw_parts(cell, 1)).is_err() as u8 218 | } 219 | 220 | fn new(input: Box, output: Box) -> State<'a> { 221 | State { 222 | input: input, 223 | output: output, 224 | tape: [0; TAPE_SIZE], 225 | } 226 | } 227 | } 228 | 229 | 230 | fn main() { 231 | let mut args: Vec<_> = env::args().collect(); 232 | if args.len() != 2 { 233 | println!("Expected 1 argument, got {}", args.len()); 234 | return; 235 | } 236 | let path = args.pop().unwrap(); 237 | 238 | let mut f = if let Ok(f) = File::open(&path) { 239 | f 240 | } else { 241 | println!("Could not open file {}", path); 242 | return; 243 | }; 244 | 245 | let mut buf = Vec::new(); 246 | if let Err(_) = f.read_to_end(&mut buf) { 247 | println!("Failed to read from file"); 248 | return; 249 | } 250 | 251 | let mut state = State::new(Box::new(BufReader::new(stdin())), 252 | Box::new(BufWriter::new(stdout()))); 253 | let program = match Program::compile(&buf) { 254 | Ok(p) => p, 255 | Err(e) => { 256 | println!("{}", e); 257 | return; 258 | } 259 | }; 260 | if let Err(e) = program.run(&mut state) { 261 | println!("{}", e); 262 | return; 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /doc/examples/bf-jit/test.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CensoredUsername/dynasm-rs/d3446db33c23c7da0ecdf59f119dd25264cdd886/doc/examples/bf-jit/test.bin -------------------------------------------------------------------------------- /doc/examples/hello world.bf: -------------------------------------------------------------------------------- 1 | ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>. -------------------------------------------------------------------------------- /doc/examples/hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-world" 3 | version = "0.1.0" 4 | authors = ["CensoredUsername "] 5 | edition = "2021" 6 | 7 | [[bin]] 8 | name = "hello-world-x64" 9 | path = "src/x64.rs" 10 | 11 | [[bin]] 12 | name = "hello-world-aarch64" 13 | path = "src/aarch64.rs" 14 | 15 | [[bin]] 16 | name = "hello-world-riscv64" 17 | path = "src/riscv64.rs" 18 | 19 | [dependencies] 20 | 21 | [dependencies.dynasmrt] 22 | path = "../../../runtime" 23 | -------------------------------------------------------------------------------- /doc/examples/hello-world/src/aarch64.rs: -------------------------------------------------------------------------------- 1 | use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; 2 | 3 | use std::{io, slice, mem}; 4 | use std::io::Write; 5 | 6 | fn main() { 7 | let mut ops = dynasmrt::aarch64::Assembler::new().unwrap(); 8 | let string = "Hello World!"; 9 | 10 | dynasm!(ops 11 | ; .arch aarch64 12 | ; ->hello: 13 | ; .bytes string.as_bytes() 14 | ; .align 8 15 | ; ->print: 16 | ; .u64 print as _ 17 | ); 18 | 19 | let hello = ops.offset(); 20 | dynasm!(ops 21 | ; .arch aarch64 22 | ; adr x0, ->hello 23 | ; movz x1, string.len() as u32 24 | ; ldr x9, ->print 25 | ; str x30, [sp, #-16]! 26 | ; blr x9 27 | ; ldr x30, [sp], #16 28 | ; ret 29 | ); 30 | 31 | let buf = ops.finalize().unwrap(); 32 | 33 | let hello_fn: extern "C" fn() -> bool = unsafe { mem::transmute(buf.ptr(hello)) }; 34 | 35 | assert!(hello_fn()); 36 | } 37 | 38 | pub extern "C" fn print(buffer: *const u8, length: u64) -> bool { 39 | io::stdout() 40 | .write_all(unsafe { slice::from_raw_parts(buffer, length as usize) }) 41 | .is_ok() 42 | } 43 | -------------------------------------------------------------------------------- /doc/examples/hello-world/src/riscv64.rs: -------------------------------------------------------------------------------- 1 | use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; 2 | 3 | use std::{io, slice, mem}; 4 | use std::io::Write; 5 | 6 | fn main() { 7 | let mut ops = dynasmrt::riscv::Assembler::new().unwrap(); 8 | let string = "Hello World!"; 9 | 10 | dynasm!(ops 11 | ; .arch riscv64 12 | ; ->hello: 13 | ; .bytes string.as_bytes() 14 | ; .align 8 15 | ; ->print: 16 | ; .u64 print as _ 17 | ); 18 | 19 | let hello = ops.offset(); 20 | dynasm!(ops 21 | ; .arch riscv64 22 | ; .feature IC 23 | ; c.addi16sp sp, -16 24 | ; c.sdsp ra, [sp, 0] 25 | ; ld t1, ->print 26 | ; la a0, ->hello 27 | ; li.12 a1, string.len() as i32 28 | ; c.jalr t1 29 | ; c.ldsp ra, [sp, 0] 30 | ; c.addi16sp sp, 16 31 | ; ret 32 | ); 33 | 34 | let buf = ops.finalize().unwrap(); 35 | 36 | let hello_fn: extern "C" fn() -> bool = unsafe { mem::transmute(buf.ptr(hello)) }; 37 | 38 | assert!(hello_fn()); 39 | } 40 | 41 | pub extern "C" fn print(buffer: *const u8, length: u64) -> bool { 42 | io::stdout() 43 | .write_all(unsafe { slice::from_raw_parts(buffer, length as usize) }) 44 | .is_ok() 45 | } 46 | -------------------------------------------------------------------------------- /doc/examples/hello-world/src/x64.rs: -------------------------------------------------------------------------------- 1 | use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; 2 | 3 | use std::{io, slice, mem}; 4 | use std::io::Write; 5 | 6 | fn main() { 7 | let mut ops = dynasmrt::x64::Assembler::new().unwrap(); 8 | let string = "Hello World!"; 9 | 10 | dynasm!(ops 11 | ; .arch x64 12 | ; ->hello: 13 | ; .bytes string.as_bytes() 14 | ); 15 | 16 | let hello = ops.offset(); 17 | dynasm!(ops 18 | ; .arch x64 19 | ; lea rcx, [->hello] 20 | ; xor edx, edx 21 | ; mov dl, BYTE string.len() as _ 22 | ; mov rax, QWORD print as _ 23 | ; sub rsp, BYTE 0x28 24 | ; call rax 25 | ; add rsp, BYTE 0x28 26 | ; ret 27 | ); 28 | 29 | let buf = ops.finalize().unwrap(); 30 | 31 | let hello_fn: extern "win64" fn() -> bool = unsafe { mem::transmute(buf.ptr(hello)) }; 32 | 33 | assert!(hello_fn()); 34 | } 35 | 36 | pub extern "win64" fn print(buffer: *const u8, length: u64) -> bool { 37 | io::stdout() 38 | .write_all(unsafe { slice::from_raw_parts(buffer, length as usize) }) 39 | .is_ok() 40 | } 41 | -------------------------------------------------------------------------------- /doc/formatting.css: -------------------------------------------------------------------------------- 1 | @import url('${RUSTDOC}'); 2 | @import url('${LIGHT}'); 3 | 4 | pre.language-diffnew code { 5 | background-color: rgb(203, 255, 203); 6 | } 7 | 8 | pre.language-diffold code { 9 | background-color: rgb(255, 203, 203); 10 | } 11 | 12 | .block h4 { 13 | text-align: center; 14 | } 15 | 16 | pre.collapse-old { 17 | margin-bottom: 0; 18 | padding-bottom: 0; 19 | } 20 | 21 | pre.collapse-new { 22 | margin-top: 0; 23 | padding-top: 0; 24 | } 25 | -------------------------------------------------------------------------------- /doc/hack.js: -------------------------------------------------------------------------------- 1 | // I'm sorry 2 | 3 | var path = document.currentScript.getAttribute("src"); 4 | var nest_count = (path.match(/\.\./g)||[]).length; 5 | 6 | var base_path = ""; 7 | for (var i = 0; i < nest_count; i++) { 8 | base_path += "../"; 9 | } 10 | 11 | var sidebar = document.getElementsByClassName("sidebar")[0]; 12 | 13 | var node = document.createElement("div"); 14 | node.innerHTML = '\ 15 |

\ 16 | dynasm-rs\ 17 |

\ 18 |

Components

\ 19 | '; 30 | 31 | node.setAttribute("class", "sidebar-elems") 32 | 33 | sidebar.insertBefore(node, sidebar.firstChild); 34 | 35 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | % Dynasm-rs 2 | 3 | In the search for faster interpreters, Just-In-Time compilation is often a useful tool. 4 | This compiler extension attempts to make the writing of such programs easier and faster. 5 | 6 | At its core, dynasm-rs is an assembler compiler. It reads assembly templates which it then 7 | compiles into code that when executed will result in the proper machine code being emitted. 8 | 9 | Dynasm is split up into two parts. The first is the compiler extension that performs the 10 | translation of assembly templates into rust code, the second part is a 11 | [small runtime](../runtime/dynasmrt/index.html) that handles the generation of the wanted 12 | machine code. 13 | 14 | Dynasm-rs supports the x86, x64, aarch64, riscv32 and riscv64 instruction set architectures. 15 | 16 | Dynasm-rs is inspired by the LuaJIT DynASM project for C and C++. 17 | 18 | # Documentation 19 | 20 | The documentation of dynasm-rs is split up into several parts. To get started, you're advised 21 | to read through the [tutorial](./tutorial.html). After this, you can read through the 22 | [language reference](./langref_common.html) to learn about the syntax used by dynasm-rs. You can 23 | also read through the [runtime documentation](../runtime/dynasmrt/index.html) to learn about the 24 | runtime API. The instruction references lists all assembly mnemnonics 25 | and formats supported by dynasm-rs. Finally, documentation on the 26 | [internals on dynasm-rs](../plugin/dynasm/index.html) can be browsed here. 27 | 28 | # Differences from LuaJit Dynasm 29 | 30 | The following list summarizes some of the larger differences between LuaJIT dynasm and dynasm-rs. 31 | 32 | ### general 33 | 34 | - LuaJIT dynasm uses full program analysis, allowing it to compile local and global labels down to 35 | enums. Dynasm-rs however uses HashMaps keyed by static strings, meaning label resolution in dynasm-rs 36 | can be a bit slower. 37 | - LuaJIT local labels are integer literals. Dynasm-rs local labels are identifiers. 38 | - Dynasm-rs does not (directly) support stand-alone files. 39 | - LuaJIT dynasm uses a special preprocessor which detects lines starting with pipes (`|`) as dynasm 40 | instructions, dynasm-rs uses the `dynasm!` procedural macro with lines starting with semicolons (`;`). 41 | - LuaJIT has macros in its invocations, dynasm-rs uses rust macros that expand to `dynasm!` invocations. 42 | - Dynasm-rs doesn't have typed aliases 43 | 44 | ### x64/x86 45 | 46 | - LuaJIT uses the `mov64` mnemnonic to encode 64-bit displacement mov. Dynasm-rs uses the `movabs` 47 | mnemnonic with a 64-bit immediate parameter to encode this. 48 | - Dynasm-rs is not sensitive to the order of parameters inside a memory reference. 49 | - The syntax used for type maps is significantly different. In LuaJit dynasm it is `Type:reg->attr` 50 | in dynasm-rs it is `reg => Type.attr`. 51 | 52 | ### aarch64 53 | 54 | - Unknown due to lack of documentation on LuaJIT's dynasm. 55 | 56 | ### RISC-V 57 | 58 | - Unknown due to lack of documentation on LuaJIT's dynasm. -------------------------------------------------------------------------------- /doc/insref/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "insref" 3 | version = "0.1.0" 4 | authors = ["Alexander Stocko ", "CensoredUsername "] 5 | default-run = "main" 6 | edition = "2021" 7 | 8 | [[bin]] 9 | name = "main" 10 | path = "src/main.rs" 11 | 12 | [[bin]] 13 | name = "export" 14 | path = "src/export.rs" 15 | 16 | [dependencies] 17 | 18 | [dependencies.dynasm] 19 | path = "../../plugin" 20 | features = ["dynasm_opmap", "dynasm_extract"] 21 | -------------------------------------------------------------------------------- /doc/insref/src/export.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | fn main() { 4 | let mut args = std::env::args(); 5 | args.next().unwrap(); 6 | 7 | let opmap = match args.next().expect("Architecture name").as_str() { 8 | "x64" => dynasm::dynasm_extract!(x64), 9 | "aarch64" => dynasm::dynasm_extract!(aarch64), 10 | "riscv" => dynasm::dynasm_extract!(riscv), 11 | x => panic!("Unknown opmap format '{}'", x) 12 | }; 13 | 14 | let stdout = io::stdout(); 15 | let mut stdout = stdout.lock(); 16 | stdout.write_all(opmap.as_bytes()).unwrap(); 17 | } 18 | -------------------------------------------------------------------------------- /doc/insref/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | fn main() { 4 | let mut args = std::env::args(); 5 | args.next().unwrap(); 6 | 7 | let opmap = match args.next().expect("Architecture name").as_str() { 8 | "x64" => dynasm::dynasm_opmap!(x64), 9 | "aarch64" => dynasm::dynasm_opmap!(aarch64), 10 | "riscv" => dynasm::dynasm_opmap!(riscv), 11 | x => panic!("Unknown opmap format '{}'", x) 12 | }; 13 | 14 | let stdout = io::stdout(); 15 | let mut stdout = stdout.lock(); 16 | stdout.write_all(opmap.as_bytes()).unwrap(); 17 | } 18 | -------------------------------------------------------------------------------- /doc/langref_common.md: -------------------------------------------------------------------------------- 1 | % Language Reference 2 | 3 | # Lexical structure definition 4 | 5 | ## Base units 6 | 7 | The following syntax units used in dynasm syntax are defined by the [Rust grammar](https://doc.rust-lang.org/grammar.html) itself: 8 | 9 | - `num_lit` 10 | - `ident` 11 | - `expr_path` 12 | - `expr` 13 | - `stmt` 14 | 15 | ## Entry point 16 | 17 | The entry point of dynasm-rs is the dynasm! macro. It is structured as following 18 | 19 | `dynasm : "dynasm" "!" "(" ident (";" line)* ")" ;` 20 | 21 | Where line can be one of the following: 22 | 23 | `line : (";" stmt) | directive | label | instruction ;` 24 | 25 | ## Directives 26 | 27 | Directives are special commands given to the assembler that do not correspond to instructions directly. 28 | They are executed at parse time, and each directive can have different parsing rules. 29 | 30 | `directive : "." ident directive_parsing_rule;` 31 | 32 | ## Labels 33 | 34 | `label : ident ":" | "->" ident ":" | "=>" expr ;` 35 | `offset : ("+" | "-") expr` 36 | `labelref : (">" ident offset? | "<" ident offset? | "->" ident offset? | "=>" expr offset? | "extern" expr) ;` 37 | 38 | ## Instructions 39 | 40 | The assembly dialect used by dynasm-rs and parsing rules for it differ based on the target architecture (configured using the `.arch` directive). 41 | Check the documentation for the specific architecture. 42 | 43 | # Reference 44 | 45 | ## Directives 46 | 47 | Dynasm-rs currently supports the following directives: 48 | 49 | 50 | Table 1: dynasm-rs directives 51 | 52 | Name | Argument format | Description 53 | ----------|-----------------|------------ 54 | `.arch` | A single identifier | Specifies the current architecture to assemble. Defaults to the current target architecture. Only `x64`, `x86` and `aarch64` are supported as of now. 55 | `.feature`| A comma-separated list of identifiers. | Set architectural features that are allowed to be used. 56 | `.alias` | An name followed by a register | Defines the name as an alias for the wanted register. 57 | `.align` | An expression of type usize | Pushes NOPs until the assembling head has reached the desired alignment. 58 | `.u8` | One or more expressions of the type `u8` | Pushes the values into the assembling buffer. 59 | `.u16` | One or more expressions of the type `u16` | Pushes the values into the assembling buffer. 60 | `.u32` | One or more expressions of the type `u32` | Pushes the values into the assembling buffer. 61 | `.u64` | One or more expressions of the type `u64` | Pushes the values into the assembling buffer. 62 | `.i8` | One or more expressions of the type `i8` | Pushes the values into the assembling buffer. 63 | `.i16` | One or more expressions of the type `i16` | Pushes the values into the assembling buffer. 64 | `.i32` | One or more expressions of the type `i32` | Pushes the values into the assembling buffer. 65 | `.i64` | One or more expressions of the type `i64` | Pushes the values into the assembling buffer. 66 | `.f32` | One or more expressions of the type `f32` | Pushes the values into the assembling buffer. 67 | `.f64` | One or more expressions of the type `f64` | Pushes the values into the assembling buffer. 68 | `.bytes` | An expression of that implements `IntoIterator` or `IntoIterator` | Extends the assembling buffer with the iterator. 69 | 70 | Directives are normally local to the current `dynasm!` invocation. However, if the `filelocal` feature is used they will be processed in lexical order over the whole file. This feature only works on a nightly compiler and might be removed in the future. 71 | 72 | ## Aliases 73 | 74 | Dynasm-rs allows the user to define aliases for registers using the `.alias name, register` directive. These aliases can then be used at places where registers are allowed to be used. Note that aliases are defined in lexical parsing order. 75 | 76 | ## Macros 77 | 78 | While this is technically not a feature of dynasm-rs, there are a few rules that must be taken into account when using normal Rust macros with dynasm-rs. 79 | 80 | First of all, it is not possible to have `dynasm!` parse the result of a Rust macro. This is a limitation of Rust itself. The proper way to use Rust macros with dynasm-rs is to have macros expand to a `dynasm!` call as can be seen in the following example: 81 | 82 | ``` 83 | macro_rules! fma { 84 | ($ops:ident, $accumulator:expr, $arg1:expr, $arg2:expr) => {dynasm!($ops 85 | ; imul $arg1, $arg2 86 | ; add $accumulator, $arg1 87 | )}; 88 | } 89 | ``` 90 | 91 | ## Statements 92 | 93 | To make code that uses a lot of macros less verbose, dynasm-rs allows bare Rust statements to be inserted inside `dynasm!` invocations. This can be done by using a double semicolon instead of a single semicolon at the start of the line as displayed in the following equivalent examples: 94 | 95 | ``` 96 | dynasm!(ops 97 | ; mov rcx, rax 98 | ); 99 | call_extern!(ops, extern_func); 100 | dynasm!(ops 101 | ; mov rcx, rax 102 | ); 103 | 104 | dynasm!(ops 105 | ; mov rcx, rax 106 | ;; call_extern!(ops, extern_func) 107 | ; mov rcx, rax 108 | ); 109 | ``` 110 | 111 | ## Labels 112 | 113 | In order to describe flow control effectively, dynasm-rs supports labels. However, since the assembly templates can be combined in a variety of ways at the mercy of the program using dynasm-rs, the semantics of these labels are somewhat different from how labels work in a static assembler. 114 | 115 | Dynasm-rs distinguishes between four different types of labels: global, local, dynamic and extern. Their syntax is as follows: 116 | 117 | Table 2: dynasm-rs label types 118 | 119 | Type | Kind | Definition | Reference 120 | --------|---------|--------------|----------- 121 | Local | static | `label:` | `>label` or `label:` | `->label` 123 | Dynamic | dynamic | `=>expr` | `=>expr` 124 | Extern | extern | `-` | `extern expr` 125 | 126 | All labels have their addresses resolved at `Assembler::commit()` time. 127 | 128 | Any valid Rust identifier is a valid label name. 129 | 130 | ### Local labels 131 | 132 | On first sight, local label definitions are similar to how labels are normally used in static assemblers. The trick with local labels is however in how they can be referenced. Local labels referenced with the `>label` syntax will be resolved to the first definition of this label after this piece of code, while local labels referenced with the ` 2 | -------------------------------------------------------------------------------- /doc/pre.html: -------------------------------------------------------------------------------- 1 | 63 |
-------------------------------------------------------------------------------- /plugin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dynasm" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | 7 | description = "A plugin for assembling code at runtime. Combined with the runtime crate dynasmrt it can be used to write JIT compilers easily." 8 | 9 | documentation.workspace = true 10 | repository.workspace = true 11 | 12 | readme.workspace = true 13 | keywords.workspace = true 14 | license.workspace = true 15 | 16 | [lib] 17 | name = "dynasm" 18 | proc-macro = true 19 | 20 | [dependencies] 21 | lazy_static = "1.5.0" 22 | bitflags = "2.6.0" 23 | byteorder = "1.5.0" 24 | quote = "1.0.37" 25 | proc-macro-error2 = "2.0.1" 26 | 27 | [dependencies.syn] 28 | version = "2.0.77" 29 | features = ["full", "extra-traits"] 30 | 31 | [dependencies.proc-macro2] 32 | version = "1.0.86" 33 | 34 | [features] 35 | dynasm_opmap = [] 36 | dynasm_extract = [] 37 | filelocal = [] 38 | 39 | default = [] 40 | -------------------------------------------------------------------------------- /plugin/src/arch/aarch64/encoding_helpers.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{bitmask, bitmask64}; 2 | 3 | pub fn encode_floating_point_immediate(value: f32) -> Option { 4 | // floating point ARM immediates are encoded as 5 | // abcdefgh => aBbbbbbc defgh000 00000000 00000000 6 | // where B = !b 7 | // which means we can just slice out "a" and "bcdefgh" and assume the rest was correct 8 | 9 | let bits = value.to_bits(); 10 | 11 | let check = (bits >> 25) & 0x3F; 12 | if (check == 0b10_0000 || check == 0b01_1111) && (bits & 0x7_FFFF) == 0 { 13 | Some((((bits >> 24) & 0x80) | ((bits >> 19) & 0x7F)) as u8) 14 | } else { 15 | None 16 | } 17 | } 18 | 19 | pub fn encode_logical_immediate_32bit(value: u32) -> Option { 20 | let transitions = value ^ value.rotate_right(1); 21 | let element_size = (64u32).checked_div(transitions.count_ones())?; 22 | 23 | // confirm that the elements are identical 24 | if value != value.rotate_left(element_size) { 25 | return None; 26 | } 27 | 28 | let element = value & bitmask(element_size as u8); 29 | let ones = element.count_ones(); 30 | let imms = (!((element_size << 1) - 1) & 0x3F) | (ones - 1); 31 | 32 | let immr = if (element & 1) != 0 { 33 | ones - (!element).trailing_zeros() 34 | } else { 35 | element_size - element.trailing_zeros() 36 | }; 37 | 38 | Some(((immr as u16) << 6) | (imms as u16)) 39 | } 40 | 41 | pub fn encode_logical_immediate_64bit(value: u64) -> Option { 42 | let transitions = value ^ value.rotate_right(1); 43 | let element_size = (128u32).checked_div(transitions.count_ones())?; 44 | 45 | // confirm that the elements are identical 46 | if value != value.rotate_left(element_size) { 47 | return None; 48 | } 49 | 50 | let element = value & bitmask64(element_size as u8); 51 | let ones = element.count_ones(); 52 | let imms = (!((element_size << 1) - 1) & 0x7F) | (ones - 1); 53 | 54 | let immr = if (element & 1) != 0 { 55 | ones - (!element).trailing_zeros() 56 | } else { 57 | element_size - element.trailing_zeros() 58 | }; 59 | 60 | let n = imms & 0x40 == 0; 61 | let imms = imms & 0x3F; 62 | 63 | Some(((n as u16) << 12) | ((immr as u16) << 6) | (imms as u16)) 64 | } 65 | 66 | pub fn encode_stretched_immediate(value: u64) -> Option { 67 | // ensure the number is formatted correctly 68 | let mut test = value & 0x0101_0101_0101_0101; 69 | test |= test << 1; 70 | test |= test << 2; 71 | test |= test << 4; 72 | if test != value { 73 | return None; 74 | } 75 | 76 | // do bitwise magic 77 | let mut masked = value & 0x8040_2010_0804_0201; 78 | masked |= masked >> 32; 79 | masked |= masked >> 16; 80 | masked |= masked >> 8; 81 | let masked = masked as u32; 82 | Some(masked & 0xFF) 83 | } 84 | 85 | pub fn encode_wide_immediate_64bit(value: u64) -> Option { 86 | let offset = value.trailing_zeros() & 0b11_0000; 87 | let masked = 0xFFFF & (value >> offset); 88 | if (masked << offset) == value { 89 | Some((masked as u32) | (offset << 12)) 90 | } else { 91 | None 92 | } 93 | } 94 | 95 | pub fn encode_wide_immediate_32bit(value: u32) -> Option { 96 | let offset = value.trailing_zeros() & 0b1_0000; 97 | let masked = 0xFFFF & (value >> offset); 98 | if (masked << offset) == value { 99 | Some(masked | (offset << 12)) 100 | } else { 101 | None 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /plugin/src/arch/aarch64/mod.rs: -------------------------------------------------------------------------------- 1 | use syn::parse; 2 | use proc_macro_error2::emit_error; 3 | 4 | mod ast; 5 | mod parser; 6 | mod matching; 7 | mod compiler; 8 | mod aarch64data; 9 | mod encoding_helpers; 10 | mod debug; 11 | 12 | use crate::State; 13 | use crate::common::{Size, Stmt, Jump}; 14 | use crate::arch::Arch; 15 | use self::aarch64data::Relocation; 16 | 17 | #[cfg(feature = "dynasm_opmap")] 18 | pub use debug::create_opmap; 19 | #[cfg(feature = "dynasm_extract")] 20 | pub use debug::extract_opmap; 21 | 22 | struct Context<'a, 'b: 'a> { 23 | pub state: &'a mut State<'b> 24 | } 25 | 26 | #[derive(Clone, Debug, Default)] 27 | pub struct ArchAarch64 { 28 | 29 | } 30 | 31 | impl Arch for ArchAarch64 { 32 | fn set_features(&mut self, features: &[syn::Ident]) { 33 | if let Some(feature) = features.first() { 34 | emit_error!(feature, "Arch aarch64 has no known features"); 35 | } 36 | } 37 | 38 | fn handle_static_reloc(&self, stmts: &mut Vec, reloc: Jump, size: Size) { 39 | let span = reloc.span(); 40 | 41 | let relocation = match size { 42 | Size::BYTE => Relocation::LITERAL8, 43 | Size::B_2 => Relocation::LITERAL16, 44 | Size::B_4 => Relocation::LITERAL32, 45 | Size::B_8 => Relocation::LITERAL64, 46 | _ => { 47 | emit_error!(span, "Relocation of unsupported size for the current target architecture"); 48 | return; 49 | } 50 | }; 51 | 52 | stmts.push(Stmt::Const(0, size)); 53 | stmts.push(reloc.encode(size.in_bytes(), size.in_bytes(), &[relocation.to_id()])); 54 | } 55 | 56 | fn default_align(&self) -> u8 { 57 | 0 58 | } 59 | 60 | fn compile_instruction(&self, state: &mut State, input: parse::ParseStream) -> parse::Result<()> { 61 | let mut ctx = Context { 62 | state 63 | }; 64 | 65 | let (instruction, args) = parser::parse_instruction(&mut ctx, input)?; 66 | let span = instruction.span; 67 | 68 | let match_data = match matching::match_instruction(&mut ctx, &instruction, args) { 69 | Err(None) => return Ok(()), 70 | Err(Some(e)) => { 71 | emit_error!(span, e); 72 | return Ok(()) 73 | } 74 | Ok(m) => m 75 | }; 76 | 77 | match compiler::compile_instruction(&mut ctx, match_data) { 78 | Err(None) => return Ok(()), 79 | Err(Some(e)) => { 80 | emit_error!(span, e); 81 | return Ok(()) 82 | } 83 | Ok(()) => () 84 | } 85 | 86 | Ok(()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /plugin/src/arch/mod.rs: -------------------------------------------------------------------------------- 1 | use syn::parse; 2 | use proc_macro_error2::emit_error; 3 | 4 | use crate::common::{Size, Stmt, Jump}; 5 | use crate::State; 6 | 7 | use std::fmt::Debug; 8 | 9 | pub mod x64; 10 | pub mod aarch64; 11 | pub mod riscv; 12 | 13 | pub(crate) trait Arch : Debug + Send { 14 | /// When the .features directive is used for an architecture, this architecture method will be 15 | /// called with the list of features as argument 16 | fn set_features(&mut self, features: &[syn::Ident]); 17 | /// When a data directive (.u32, .i64) is used with a jump in it, this needs to be emitted 18 | /// in a way that the target runtime understands it. This architecture method handles this. 19 | fn handle_static_reloc(&self, stmts: &mut Vec, reloc: Jump, size: Size); 20 | /// The default byte to pad with for alignment for this architecture. 21 | fn default_align(&self) -> u8; 22 | /// The core of the architecture. This function parses a single instruction, storing the to be 23 | /// emitted code in the passed `state` parameter. 24 | fn compile_instruction(&self, state: &mut State, input: parse::ParseStream) -> parse::Result<()>; 25 | } 26 | 27 | #[derive(Clone, Debug)] 28 | pub struct DummyArch {} 29 | 30 | impl DummyArch { 31 | fn new() -> DummyArch { 32 | DummyArch{} 33 | } 34 | } 35 | 36 | impl Arch for DummyArch { 37 | fn set_features(&mut self, features: &[syn::Ident]) { 38 | if let Some(feature) = features.first() { 39 | emit_error!(feature, "Cannot set features when the assembling architecture is undefined. Define it using a .arch directive"); 40 | } 41 | } 42 | 43 | fn handle_static_reloc(&self, _stmts: &mut Vec, reloc: Jump, _size: Size) { 44 | let span = reloc.span(); 45 | emit_error!(span, "Current assembling architecture is undefined. Define it using a .arch directive"); 46 | } 47 | 48 | fn default_align(&self) -> u8 { 49 | 0 50 | } 51 | 52 | fn compile_instruction(&self, _state: &mut State, input: parse::ParseStream) -> parse::Result<()> { 53 | emit_error!(input.cursor().span(), "Current assembling architecture is undefined. Define it using a .arch directive"); 54 | Ok(()) 55 | } 56 | } 57 | 58 | pub(crate) fn from_str(s: &str) -> Option> { 59 | match s { 60 | "x64" => Some(Box::new(x64::Archx64::default())), 61 | "x86" => Some(Box::new(x64::Archx86::default())), 62 | "aarch64" => Some(Box::new(aarch64::ArchAarch64::default())), 63 | "riscv64i" | "riscv64" => Some(Box::new(riscv::ArchRiscV64I::default())), 64 | "riscv64e" => Some(Box::new(riscv::ArchRiscV64E::default())), 65 | "riscv32i" | "riscv32" => Some(Box::new(riscv::ArchRiscV32I::default())), 66 | "riscv32e" => Some(Box::new(riscv::ArchRiscV32E::default())), 67 | "unknown" => Some(Box::new(DummyArch::new())), 68 | _ => None 69 | } 70 | } 71 | 72 | #[cfg(target_arch="x86_64")] 73 | pub const CURRENT_ARCH: &str = "x64"; 74 | #[cfg(target_arch="x86")] 75 | pub const CURRENT_ARCH: &str = "x86"; 76 | #[cfg(target_arch="aarch64")] 77 | pub const CURRENT_ARCH: &str = "aarch64"; 78 | // TODO: there seems to be no good way to detect riscv64i from riscv64e. You're probably not running 79 | // rustc on an embedded targets so assume the i variant. 80 | #[cfg(target_arch="riscv64")] 81 | pub const CURRENT_ARCH: &str = "riscv64i"; 82 | #[cfg(target_arch="riscv32")] 83 | pub const CURRENT_ARCH: &str = "riscv32i"; 84 | #[cfg(not(any( 85 | target_arch="x86", 86 | target_arch="x86_64", 87 | target_arch="aarch64", 88 | target_arch="riscv64", 89 | target_arch="riscv32" 90 | )))] 91 | pub const CURRENT_ARCH: &str = "unknown"; 92 | -------------------------------------------------------------------------------- /plugin/src/arch/riscv/ast.rs: -------------------------------------------------------------------------------- 1 | //! RISC-V registers are simple. The registers contain no size information, this is purely encoded 2 | //! in the target architecture and instruction 3 | //! we currently have three known register families. 4 | //! * General purpose registers, either denoted as x0-x31 or by specific names 5 | //! * floating point registers, denoted as f0-f31 or by specific names 6 | //! * vector registers, denoted as v0-v31 7 | use proc_macro2::Span; 8 | use crate::common::Jump; 9 | use super::riscvdata::Opdata; 10 | 11 | use std::fmt; 12 | 13 | 14 | /// A generic register reference. Can be either a static RegId or a dynamic register from a family 15 | #[derive(Debug, Clone)] 16 | pub enum Register { 17 | Static(RegId), 18 | Dynamic(RegFamily, syn::Expr) 19 | } 20 | 21 | /// Unique identifiers for a specific register 22 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 23 | pub enum RegId { 24 | // regular registers 25 | X0 = 0x00, X1 = 0x01, X2 = 0x02, X3 = 0x03, // zero, ra, sp, gp 26 | X4 = 0x04, X5 = 0x05, X6 = 0x06, X7 = 0x07, // tp, t0, t1, t2 27 | X8 = 0x08, X9 = 0x09, X10= 0x0A, X11= 0x0B, // s0, s1, a0, a1 28 | X12= 0x0C, X13= 0x0D, X14= 0x0E, X15= 0x0F, // a2, a3, a4, a5 29 | X16= 0x10, X17= 0x11, X18= 0x12, X19= 0x13, // a6, a7, s2, s3 30 | X20= 0x14, X21= 0x15, X22= 0x16, X23= 0x17, // s4, s5, s6, s7 31 | X24= 0x18, X25= 0x19, X26= 0x1A, X27= 0x1B, // s8, s9, s10,s11 32 | X28= 0x1C, X29= 0x1D, X30= 0x1E, X31= 0x1F, // t3, t4, t5, t6 33 | 34 | // floating point registers 35 | F0 = 0x20, F1 = 0x21, F2 = 0x22, F3 = 0x23, 36 | F4 = 0x24, F5 = 0x25, F6 = 0x26, F7 = 0x27, 37 | F8 = 0x28, F9 = 0x29, F10= 0x2A, F11= 0x2B, 38 | F12= 0x2C, F13= 0x2D, F14= 0x2E, F15= 0x2F, 39 | F16= 0x30, F17= 0x31, F18= 0x32, F19= 0x33, 40 | F20= 0x34, F21= 0x35, F22= 0x36, F23= 0x37, 41 | F24= 0x38, F25= 0x39, F26= 0x3A, F27= 0x3B, 42 | F28= 0x3C, F29= 0x3D, F30= 0x3E, F31= 0x3F, 43 | 44 | // vector registers 45 | V0 = 0x40, V1 = 0x41, V2 = 0x42, V3 = 0x43, 46 | V4 = 0x44, V5 = 0x45, V6 = 0x46, V7 = 0x47, 47 | V8 = 0x48, V9 = 0x49, V10= 0x4A, V11= 0x4B, 48 | V12= 0x4C, V13= 0x4D, V14= 0x4E, V15= 0x4F, 49 | V16= 0x50, V17= 0x51, V18= 0x52, V19= 0x53, 50 | V20= 0x54, V21= 0x55, V22= 0x56, V23= 0x57, 51 | V24= 0x58, V25= 0x59, V26= 0x5A, V27= 0x5B, 52 | V28= 0x5C, V29= 0x5D, V30= 0x5E, V31= 0x5F, 53 | } 54 | 55 | /// Register families 56 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 57 | pub enum RegFamily { 58 | INTEGER = 0, 59 | FP = 1, 60 | VECTOR = 2, 61 | } 62 | 63 | impl RegId { 64 | /// Encode this RegId in a 5-bit value 65 | pub fn code(self) -> u8 { 66 | self as u8 & 0x1F 67 | } 68 | 69 | /// Returns the family of this Regid 70 | pub fn family(self) -> RegFamily { 71 | match self as u8 >> 5 { 72 | 0 => RegFamily::INTEGER, 73 | 1 => RegFamily::FP, 74 | 2 => RegFamily::VECTOR, 75 | _ => unreachable!(), 76 | } 77 | } 78 | } 79 | 80 | impl fmt::Display for RegId { 81 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 82 | match self.family() { 83 | RegFamily::INTEGER => write!(f, "x{}", self.code()), 84 | RegFamily::FP => write!(f, "f{}", self.code()), 85 | RegFamily::VECTOR => write!(f, "v{}", self.code()), 86 | } 87 | } 88 | } 89 | 90 | impl Register { 91 | /// Get the 5-bit code for this Register, if statically known. 92 | pub fn code(&self) -> Option { 93 | match self { 94 | Register::Static(code) => Some(code.code()), 95 | Register::Dynamic(_, _) => None 96 | } 97 | } 98 | 99 | /// Returns the family that this Register is of 100 | pub fn family(&self) -> RegFamily { 101 | match self { 102 | Register::Static(code) => code.family(), 103 | Register::Dynamic(family, _) => *family 104 | } 105 | } 106 | 107 | /// Returns true if this Register is dynamic 108 | pub fn is_dynamic(&self) -> bool { 109 | match self { 110 | Register::Static(_) => false, 111 | Register::Dynamic(_, _) => true 112 | } 113 | } 114 | 115 | /// Returns Some(RegId) if this Register is static 116 | pub fn as_id(&self) -> Option { 117 | match self { 118 | Register::Static(id) => Some(*id), 119 | Register::Dynamic(_, _) => None 120 | } 121 | } 122 | } 123 | 124 | 125 | #[derive(Debug)] 126 | pub enum RegListCount { 127 | Static(u8), 128 | Dynamic(syn::Expr), 129 | Single(Register), 130 | Double(Register, Register) 131 | } 132 | 133 | 134 | /// A RISC-V parsed instruction. 135 | /// These are fairly simple. the format is "op" [ . "opext" ]* [ arg [ , arg ]* ] 136 | /// where arg is 137 | /// * an immediate (arbitrary expression) 138 | /// * a label (in normal dynasm-rs style) 139 | /// * a register (one of the above) 140 | /// * a memory reference `expr? ( intreg ) ` 141 | /// * a register list {ra [, s0 [- s_n]]} 142 | /// 143 | /// this last one is somewhat problematic, as just parsing the expr will normally swallow 144 | /// the register reference as a call expression. 145 | #[derive(Debug)] 146 | pub enum RawArg { 147 | // An immediate, or potentially an identifier 148 | Immediate { 149 | value: syn::Expr 150 | }, 151 | // A label 152 | JumpTarget { 153 | jump: Jump 154 | }, 155 | // A register 156 | Register { 157 | span: Span, 158 | reg: Register 159 | }, 160 | // A memory reference 161 | Reference { 162 | span: Span, 163 | offset: Option, 164 | base: Register, 165 | }, 166 | // A pc-relative reference 167 | LabelReference { 168 | span: Span, 169 | jump: Jump, 170 | base: Register 171 | }, 172 | // A register list. These only happen with a single family of instructions in the Zcmp extension 173 | RegisterList { 174 | span: Span, 175 | first: Register, // this should always be ra 176 | count: RegListCount 177 | }, 178 | } 179 | 180 | /// The result of parsing a single instruction 181 | #[derive(Debug)] 182 | pub struct ParsedInstruction { 183 | pub name: String, 184 | pub span: Span, 185 | pub args: Vec 186 | } 187 | 188 | #[derive(Debug)] 189 | pub enum RegListFlat { 190 | Static(u8), 191 | Dynamic(syn::Expr) 192 | } 193 | 194 | #[derive(Debug)] 195 | pub enum FlatArg { 196 | Immediate { 197 | value: syn::Expr 198 | }, 199 | JumpTarget { 200 | jump: Jump 201 | }, 202 | Register { 203 | span: Span, 204 | reg: Register 205 | }, 206 | RegisterList { 207 | span: Span, 208 | count: RegListFlat 209 | }, 210 | Default 211 | } 212 | 213 | /// The result of finding a match for an instruction 214 | #[derive(Debug)] 215 | pub struct MatchData { 216 | pub data: &'static Opdata, 217 | pub args: Vec 218 | } 219 | -------------------------------------------------------------------------------- /plugin/src/arch/x64/debug.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use super::compiler::{Opdata, FormatStringIterator}; 4 | use super::x64data::Flags; 5 | 6 | pub fn format_opdata_list(name: &str, data: &[Opdata]) -> String { 7 | let mut forms = Vec::new(); 8 | for data in data { 9 | forms.extend(format_opdata(name, data)); 10 | } 11 | forms.join("\n") 12 | } 13 | 14 | pub fn format_opdata(name: &str, data: &Opdata) -> Vec { 15 | let opsizes = if data.flags.contains(Flags::AUTO_SIZE) {&b"qwd"[..]} 16 | else if data.flags.contains(Flags::AUTO_NO32) {&b"qw"[..]} 17 | else if data.flags.contains(Flags::AUTO_REXW) {&b"qd"[..]} 18 | else if data.flags.contains(Flags::AUTO_VEXL) {&b"ho"[..]} 19 | else if name == "monitorx" {&b"qwd"[..]} 20 | else {&b"!"[..]}; 21 | 22 | let mut forms = Vec::new(); 23 | for opsize in opsizes.iter().cloned() { 24 | let mut buf = String::new(); 25 | buf.push_str(">>> "); 26 | buf.push_str(name); 27 | let mut first = true; 28 | for (ty, size) in FormatStringIterator::new(data.args) { 29 | if first { 30 | buf.push(' '); 31 | first = false; 32 | } else { 33 | buf.push_str(", "); 34 | } 35 | buf.push_str(&format_arg(ty, size, opsize)) 36 | } 37 | if data.flags.contains(Flags::X86_ONLY) { 38 | for _ in buf.len() .. 45 { 39 | buf.push(' '); 40 | } 41 | buf.push_str(" (x86 only)"); 42 | } 43 | if !data.features.is_empty() { 44 | for _ in buf.len() .. 45 { 45 | buf.push(' '); 46 | } 47 | buf.push_str(&format!(" ({})", data.features)); 48 | } 49 | forms.push(buf); 50 | } 51 | forms 52 | } 53 | 54 | static REGS: [&str; 16] = ["a", "c", "d", "b", "sp", "bp", "si", "di", 55 | "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"]; 56 | static SEGREGS: [&str; 6] = ["es", "cs", "ss", "ds", "fs", "gs"]; 57 | 58 | fn format_arg(ty: u8, mut size: u8, opsize: u8) -> Cow<'static, str> { 59 | if size == b'*' { 60 | size = if opsize == b'q' && (ty == b'i' || ty == b'o') { 61 | b'd' 62 | } else { 63 | opsize 64 | }; 65 | } 66 | 67 | fn format_size(size: u8) -> &'static str { 68 | match size { 69 | b'b' => "8", 70 | b'w' => "16", 71 | b'd' => "32", 72 | b'f' => "48", 73 | b'q' => "64", 74 | b'p' => "80", 75 | b'o' => "128", 76 | b'h' => "256", 77 | b't' => "512", 78 | _ => "" 79 | } 80 | } 81 | 82 | match ty { 83 | b'i' => format!("imm{}", format_size(size)).into(), 84 | b'o' => format!("rel{}off", format_size(size)).into(), 85 | b'm' => format!("mem{}", format_size(size)).into(), 86 | b'k' => format!("vm32addr{}", format_size(size)).into(), 87 | b'l' => format!("vm64addr{}", format_size(size)).into(), 88 | b'r' => format!("reg{}", format_size(size)).into(), 89 | b'f' => "st".into(), 90 | b'x' => "mm".into(), 91 | b'y' => (if size == b'h' {"ymm"} else {"xmm"}).into(), 92 | b's' => "segreg".into(), 93 | b'c' => "creg".into(), 94 | b'd' => "dreg".into(), 95 | b'b' => "bndreg".into(), 96 | b'v' => format!("reg/mem{}", format_size(size)).into(), 97 | b'u' => format!("mm/mem{}", format_size(size)).into(), 98 | b'w' => format!("{}mm/mem{}", if size == b'h' {"y"} else {"x"}, format_size(size)).into(), 99 | b'A'..=b'P' => { 100 | let i = ty as usize - 'A' as usize; 101 | match size { 102 | b'b' => if i < 4 { format!("{}l", REGS[i]).into() } 103 | else if i < 8 { REGS[i].into() } 104 | else { format!("{}b", REGS[i]).into() }, 105 | b'w' => if i < 4 { format!("{}x", REGS[i]).into() } 106 | else if i < 8 { REGS[i].into() } 107 | else { format!("{}w", REGS[i]).into() }, 108 | b'd' => if i < 4 { format!("e{}x",REGS[i]).into() } 109 | else if i < 8 { format!("e{}", REGS[i]).into() } 110 | else { format!("{}d", REGS[i]).into() }, 111 | b'q' => if i < 4 { format!("r{}x",REGS[i]).into() } 112 | else { format!("r{}", REGS[i]).into() }, 113 | _ => panic!("invalid formatting data") 114 | } 115 | }, 116 | b'Q'..=b'V' => SEGREGS[ty as usize - 'Q' as usize].into(), 117 | b'W' => "cr8".into(), 118 | b'X' => "st0".into(), 119 | _ => panic!("invalid formatting data") 120 | } 121 | } 122 | 123 | #[cfg(feature = "dynasm_opmap")] 124 | pub fn create_opmap() -> String { 125 | let mut s = String::new(); 126 | 127 | let mut mnemnonics: Vec<_> = super::x64data::mnemnonics().cloned().collect(); 128 | mnemnonics.sort(); 129 | 130 | for mnemnonic in mnemnonics { 131 | // get the data for this mnemnonic 132 | let data = super::x64data::get_mnemnonic_data(mnemnonic).unwrap(); 133 | // format the data for the opmap docs 134 | let mut formats = data.into_iter() 135 | .map(|x| format_opdata(mnemnonic, x)) 136 | .flat_map(|x| x) 137 | .map(|x| x.replace(">>> ", "")) 138 | .collect::>(); 139 | formats.sort(); 140 | 141 | // push mnemnonic name as title 142 | s.push_str("### "); 143 | s.push_str(mnemnonic); 144 | s.push_str("\n```insref\n"); 145 | 146 | // push the formats 147 | s.push_str(&formats.join("\n")); 148 | s.push_str("\n```\n"); 149 | } 150 | s 151 | } 152 | -------------------------------------------------------------------------------- /plugin/src/arch/x64/mod.rs: -------------------------------------------------------------------------------- 1 | use syn::parse; 2 | use proc_macro_error2::emit_error; 3 | 4 | mod ast; 5 | mod compiler; 6 | mod parser; 7 | mod debug; 8 | mod x64data; 9 | 10 | use crate::State; 11 | use crate::arch::Arch; 12 | use crate::common::{Size, Stmt, Jump}; 13 | 14 | #[cfg(feature = "dynasm_opmap")] 15 | pub use debug::create_opmap; 16 | 17 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 18 | pub enum X86Mode { 19 | Long, 20 | Protected 21 | } 22 | 23 | struct Context<'a, 'b: 'a> { 24 | pub state: &'a mut State<'b>, 25 | pub mode: X86Mode, 26 | pub features: x64data::Features 27 | } 28 | 29 | #[derive(Clone, Debug)] 30 | pub struct Archx64 { 31 | features: x64data::Features 32 | } 33 | 34 | impl Default for Archx64 { 35 | fn default() -> Archx64 { 36 | Archx64 { features: x64data::Features::all() } 37 | } 38 | } 39 | 40 | impl Arch for Archx64 { 41 | fn set_features(&mut self, features: &[syn::Ident]) { 42 | let mut new_features = x64data::Features::empty(); 43 | for ident in features { 44 | new_features |= match x64data::Features::from_str(&ident.to_string()) { 45 | Some(feature) => feature, 46 | None => { 47 | emit_error!(ident, "Architecture x64 does not support feature '{}'", ident); 48 | continue; 49 | } 50 | } 51 | } 52 | self.features = new_features; 53 | } 54 | 55 | fn handle_static_reloc(&self, stmts: &mut Vec, reloc: Jump, size: Size) { 56 | stmts.push(Stmt::Const(0, size)); 57 | // field_offset of size. Relative to the start of the field so matching ref_offset. Type is size and relative 58 | stmts.push(reloc.encode(size.in_bytes(), size.in_bytes(), &[size.in_bytes(), 0])); 59 | } 60 | 61 | fn default_align(&self) -> u8 { 62 | 0x90 63 | } 64 | 65 | fn compile_instruction(&self, state: &mut State, input: parse::ParseStream) -> parse::Result<()> { 66 | let mut ctx = Context { 67 | state, 68 | mode: X86Mode::Long, 69 | features: self.features 70 | }; 71 | let (instruction, args) = parser::parse_instruction(&mut ctx, input)?; 72 | let span = instruction.span; 73 | 74 | if let Err(Some(e)) = compiler::compile_instruction(&mut ctx, instruction, args) { 75 | emit_error!(span, e); 76 | } 77 | Ok(()) 78 | } 79 | } 80 | 81 | #[derive(Clone, Debug)] 82 | pub struct Archx86 { 83 | features: x64data::Features 84 | } 85 | 86 | impl Default for Archx86 { 87 | fn default() -> Archx86 { 88 | Archx86 { features: x64data::Features::all() } 89 | } 90 | } 91 | 92 | impl Arch for Archx86 { 93 | fn set_features(&mut self, features: &[syn::Ident]) { 94 | let mut new_features = x64data::Features::empty(); 95 | for ident in features { 96 | new_features |= match x64data::Features::from_str(&ident.to_string()) { 97 | Some(feature) => feature, 98 | None => { 99 | emit_error!(ident, "Architecture x86 does not support feature '{}'", ident); 100 | continue; 101 | } 102 | } 103 | } 104 | self.features = new_features; 105 | } 106 | 107 | fn handle_static_reloc(&self, stmts: &mut Vec, reloc: Jump, size: Size) { 108 | stmts.push(Stmt::Const(0, size)); 109 | // field_offset of size. Relative to the start of the field so matching ref_offset. Type is simply the size. 110 | stmts.push(reloc.encode(size.in_bytes(), size.in_bytes(), &[size.in_bytes()])); 111 | } 112 | 113 | fn default_align(&self) -> u8 { 114 | 0x90 115 | } 116 | 117 | fn compile_instruction(&self, state: &mut State, input: parse::ParseStream) -> parse::Result<()> { 118 | let mut ctx = Context { 119 | state, 120 | mode: X86Mode::Protected, 121 | features: self.features 122 | }; 123 | let (instruction, args) = parser::parse_instruction(&mut ctx, input)?; 124 | let span = instruction.span; 125 | 126 | if let Err(Some(e)) = compiler::compile_instruction(&mut ctx, instruction, args) { 127 | emit_error!(span, e); 128 | } 129 | Ok(()) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /plugin/src/directive.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | 3 | use syn::parse; 4 | use syn::Token; 5 | use syn::spanned::Spanned; 6 | use quote::quote_spanned; 7 | use proc_macro2::{TokenTree, Literal}; 8 | use proc_macro_error2::emit_error; 9 | 10 | use crate::common::{Stmt, Size, delimited}; 11 | use crate::arch; 12 | use crate::DynasmContext; 13 | use crate::parse_helpers::ParseOptExt; 14 | 15 | pub(crate) fn evaluate_directive(invocation_context: &mut DynasmContext, stmts: &mut Vec, input: parse::ParseStream) -> parse::Result<()> { 16 | let directive: syn::Ident = input.parse()?; 17 | 18 | match directive.to_string().as_str() { 19 | // TODO: oword, qword, float, double, long double 20 | 21 | "arch" => { 22 | // ; .arch ident 23 | let arch: syn::Ident = input.parse()?; 24 | if let Some(a) = arch::from_str(arch.to_string().as_str()) { 25 | invocation_context.current_arch = a; 26 | } else { 27 | emit_error!(arch, "Unknown architecture '{}'", arch); 28 | } 29 | }, 30 | "feature" => { 31 | // ; .feature ident ("," ident) * 32 | let mut features = Vec::new(); 33 | let ident: syn::Ident = input.parse()?; 34 | features.push(ident); 35 | 36 | while input.peek(Token![,]) { 37 | let _: Token![,] = input.parse()?; 38 | let ident: syn::Ident = input.parse()?; 39 | features.push(ident); 40 | } 41 | 42 | // ;.feature none cancels all features 43 | if features.len() == 1 && features[0] == "none" { 44 | features.pop(); 45 | } 46 | invocation_context.current_arch.set_features(&features); 47 | }, 48 | // ; .u8 (expr ("," expr)*)? 49 | "u8" => directive_unsigned(invocation_context, stmts, input, Size::BYTE)?, 50 | "u16" => directive_unsigned(invocation_context, stmts, input, Size::B_2)?, 51 | "u32" => directive_unsigned(invocation_context, stmts, input, Size::B_4)?, 52 | "u64" => directive_unsigned(invocation_context, stmts, input, Size::B_8)?, 53 | "i8" => directive_signed(invocation_context, stmts, input, Size::BYTE)?, 54 | "i16" => directive_signed(invocation_context, stmts, input, Size::B_2)?, 55 | "i32" => directive_signed(invocation_context, stmts, input, Size::B_4)?, 56 | "i64" => directive_signed(invocation_context, stmts, input, Size::B_8)?, 57 | "f32" => directive_float(stmts, input, Size::B_4)?, 58 | "f64" => directive_float(stmts, input, Size::B_8)?, 59 | "bytes" => { 60 | // ; .bytes expr 61 | let iterator: syn::Expr = input.parse()?; 62 | stmts.push(Stmt::ExprExtend(delimited(iterator))); 63 | }, 64 | "align" => { 65 | // ; .align expr ("," expr) 66 | // this might need to be architecture dependent 67 | let value: syn::Expr = input.parse()?; 68 | 69 | let with = if input.peek(Token![,]) { 70 | let _: Token![,] = input.parse()?; 71 | let with: syn::Expr = input.parse()?; 72 | delimited(with) 73 | } else { 74 | let with = invocation_context.current_arch.default_align(); 75 | TokenTree::Literal(Literal::u8_unsuffixed(with)) 76 | }; 77 | 78 | stmts.push(Stmt::Align(delimited(value), with)); 79 | }, 80 | "alias" => { 81 | // ; .alias ident, ident 82 | // consider changing this to ; .alias ident = ident next breaking change 83 | let alias = input.parse::()?; 84 | let _: Token![,] = input.parse()?; 85 | let reg = input.parse::()?; 86 | 87 | let alias_name = alias.to_string(); 88 | 89 | match invocation_context.aliases.entry(alias_name) { 90 | Entry::Occupied(_) => { 91 | emit_error!(alias, "Duplicate alias definition, alias '{}' was already defined", alias); 92 | }, 93 | Entry::Vacant(v) => { 94 | v.insert(reg.to_string()); 95 | } 96 | } 97 | }, 98 | // these are deprecated, but to prevent bad error messages handle them explicitly 99 | // I'd like to provide a warning instead, but proc-macro-error2 emit_warning! seems to not work. 100 | "byte" => emit_error!(directive.span(), "Directive .byte is deprecated, please use .i8 or .u8 instead."), 101 | "word" => emit_error!(directive.span(), "Directive .word is deprecated, please use .i16 or .u16 instead."), 102 | "dword" => emit_error!(directive.span(), "Directive .dword is deprecated, please use .i32 or .u32 instead."), 103 | "qword" => emit_error!(directive.span(), "Directive .qword is deprecated, please use .i64 or .u64 instead."), 104 | d => { 105 | // unknown directive. skip ahead until we hit a ; so the parser can recover 106 | emit_error!(directive, "unknown directive '{}'", d); 107 | skip_until_semicolon(input); 108 | } 109 | } 110 | 111 | Ok(()) 112 | } 113 | 114 | fn directive_signed(invocation_context: &mut DynasmContext, stmts: &mut Vec, input: parse::ParseStream, size: Size) -> parse::Result<()> { 115 | // FIXME: this could be replaced by a Punctuated parser? 116 | // parse (expr (, expr)*)? 117 | 118 | if input.is_empty() || input.peek(Token![;]) { 119 | return Ok(()) 120 | } 121 | 122 | if let Some(jump) = input.parse_opt()? { 123 | invocation_context.current_arch.handle_static_reloc(stmts, jump, size); 124 | } else { 125 | let expr: syn::Expr = input.parse()?; 126 | stmts.push(Stmt::ExprSigned(delimited(expr), size)); 127 | } 128 | 129 | 130 | while input.peek(Token![,]) { 131 | let _: Token![,] = input.parse()?; 132 | 133 | if let Some(jump) = input.parse_opt()? { 134 | invocation_context.current_arch.handle_static_reloc(stmts, jump, size); 135 | } else { 136 | let expr: syn::Expr = input.parse()?; 137 | stmts.push(Stmt::ExprSigned(delimited(expr), size)); 138 | } 139 | } 140 | 141 | Ok(()) 142 | } 143 | 144 | fn directive_unsigned(invocation_context: &mut DynasmContext, stmts: &mut Vec, input: parse::ParseStream, size: Size) -> parse::Result<()> { 145 | // FIXME: this could be replaced by a Punctuated parser? 146 | // parse (expr (, expr)*)? 147 | 148 | if input.is_empty() || input.peek(Token![;]) { 149 | return Ok(()) 150 | } 151 | 152 | if let Some(jump) = input.parse_opt()? { 153 | invocation_context.current_arch.handle_static_reloc(stmts, jump, size); 154 | } else { 155 | let expr: syn::Expr = input.parse()?; 156 | stmts.push(Stmt::ExprUnsigned(delimited(expr), size)); 157 | } 158 | 159 | 160 | while input.peek(Token![,]) { 161 | let _: Token![,] = input.parse()?; 162 | 163 | if let Some(jump) = input.parse_opt()? { 164 | invocation_context.current_arch.handle_static_reloc(stmts, jump, size); 165 | } else { 166 | let expr: syn::Expr = input.parse()?; 167 | stmts.push(Stmt::ExprUnsigned(delimited(expr), size)); 168 | } 169 | } 170 | 171 | Ok(()) 172 | } 173 | 174 | fn directive_float(stmts: &mut Vec, input: parse::ParseStream, size: Size) -> parse::Result<()> { 175 | // FIXME: this could be replaced by a Punctuated parser? 176 | // parse (expr (, expr)*)? 177 | 178 | if input.is_empty() || input.peek(Token![;]) { 179 | return Ok(()) 180 | } 181 | 182 | let expr: syn::Expr = input.parse()?; 183 | let expr = match size { 184 | Size::B_4 => quote_spanned! {expr.span() => f32::to_bits( #expr ) }, 185 | Size::B_8 => quote_spanned! {expr.span() => f64::to_bits( #expr ) }, 186 | _ => unreachable!() 187 | }; 188 | stmts.push(Stmt::ExprUnsigned(delimited(expr), size)); 189 | 190 | while input.peek(Token![,]) { 191 | let _: Token![,] = input.parse()?; 192 | 193 | let expr: syn::Expr = input.parse()?; 194 | let expr = match size { 195 | Size::B_4 => quote_spanned! {expr.span() => f32::to_bits( #expr ) }, 196 | Size::B_8 => quote_spanned! {expr.span() => f64::to_bits( #expr ) }, 197 | _ => unreachable!() 198 | }; 199 | stmts.push(Stmt::ExprUnsigned(delimited(expr), size)); 200 | } 201 | 202 | Ok(()) 203 | } 204 | 205 | /// In case a directive is unknown, try to skip up to the next ; and resume parsing. 206 | fn skip_until_semicolon(input: parse::ParseStream) { 207 | let _ = input.step(|cursor| { 208 | let mut rest = *cursor; 209 | while let Some((tt, next)) = rest.token_tree() { 210 | match tt { 211 | ::proc_macro2::TokenTree::Punct(ref punct) if punct.as_char() == ';' => { 212 | return Ok(((), rest)); 213 | } 214 | _ => rest = next, 215 | } 216 | } 217 | Ok(((), rest)) 218 | }); 219 | } 220 | -------------------------------------------------------------------------------- /plugin/src/parse_helpers.rs: -------------------------------------------------------------------------------- 1 | //! This file contains parsing helpers used by multiple parsing backends 2 | use syn::parse; 3 | use std::convert::TryInto; 4 | use syn::ext::IdentExt; 5 | 6 | /** 7 | * Jump types 8 | */ 9 | 10 | pub trait ParseOpt: Sized { 11 | fn parse(input: parse::ParseStream) -> parse::Result>; 12 | } 13 | 14 | pub trait ParseOptExt { 15 | /// Parses a syntax tree node of type `T`, advancing the position of our 16 | /// parse stream past it if it was found. 17 | fn parse_opt(&self) -> parse::Result>; 18 | } 19 | 20 | impl<'a> ParseOptExt for parse::ParseBuffer<'a> { 21 | fn parse_opt(&self) -> parse::Result> { 22 | T::parse(self) 23 | } 24 | } 25 | 26 | /// Tries to parse an ident that has a specific name as a keyword. Returns true if it worked. 27 | pub fn eat_pseudo_keyword(input: parse::ParseStream, kw: &str) -> bool { 28 | input.step(|cursor| { 29 | if let Some((ident, rest)) = cursor.ident() { 30 | if ident == kw { 31 | return Ok(((), rest)); 32 | } 33 | } 34 | Err(cursor.error("expected identifier")) 35 | }).is_ok() 36 | } 37 | 38 | /// parses an ident, but instead of syn's Parse impl it does also parse keywords as idents 39 | pub fn parse_ident_or_rust_keyword(input: parse::ParseStream) -> parse::Result { 40 | syn::Ident::parse_any(input) 41 | } 42 | 43 | /// checks if an expression is simply an ident, and if so, returns a clone of it. 44 | pub fn as_ident(expr: &syn::Expr) -> Option<&syn::Ident> { 45 | let path = match *expr { 46 | syn::Expr::Path(syn::ExprPath {ref path, qself: None, ..}) => path, 47 | _ => return None 48 | }; 49 | 50 | path.get_ident() 51 | } 52 | 53 | /// checks if an expression is a simple literal, allowing us to perform compile-time analysis of an expression 54 | pub fn as_lit(expr: &syn::Expr) -> Option<&syn::Lit> { 55 | // strip any wrapping Group nodes due to delimiting 56 | let mut inner = expr; 57 | while let syn::Expr::Group(syn::ExprGroup { expr, .. }) = inner { 58 | inner = expr; 59 | } 60 | 61 | match inner { 62 | syn::Expr::Lit(syn::ExprLit { ref lit, .. } ) => Some(lit), 63 | _ => None 64 | } 65 | } 66 | 67 | /// checks if an expression is a literal with possible negation 68 | pub fn as_lit_with_negation(expr: &syn::Expr) -> Option<(&syn::Lit, bool)> { 69 | // strip any wrapping Group nodes due to delimiting 70 | let mut inner = expr; 71 | while let syn::Expr::Group(syn::ExprGroup { expr, .. }) = inner { 72 | inner = expr; 73 | } 74 | 75 | match inner { 76 | syn::Expr::Lit(syn::ExprLit { ref lit, .. } ) => Some((lit, false)), 77 | syn::Expr::Unary(syn::ExprUnary { op: syn::UnOp::Neg(_), ref expr, .. } ) => { 78 | match &**expr { 79 | syn::Expr::Lit(syn::ExprLit { ref lit, .. } ) => Some((lit, true)), 80 | _ => None 81 | } 82 | } 83 | _ => None 84 | } 85 | } 86 | 87 | /// checks if an expression is a constant number literal 88 | pub fn as_unsigned_number(expr: &syn::Expr) -> Option { 89 | match as_lit(expr)? { 90 | syn::Lit::Int(i) => i.base10_parse().ok(), 91 | _ => None 92 | } 93 | } 94 | 95 | /// checks if an expression is a signed number literal 96 | pub fn as_signed_number(expr: &syn::Expr) -> Option { 97 | // FIXME: this possibly panics on --0x8000_0000_0000_0000 98 | let (expr, negated) = as_lit_with_negation(expr)?; 99 | match expr { 100 | syn::Lit::Int(i) => if let Ok(value) = i.base10_parse::() { 101 | let value: i64 = value.try_into().ok()?; 102 | Some (if negated {-value} else {value}) 103 | } else { 104 | None 105 | }, 106 | _ => None 107 | } 108 | } 109 | 110 | /// checks if an expression is a constant float literal 111 | pub fn as_float(expr: &syn::Expr) -> Option { 112 | let (expr, negated) = as_lit_with_negation(expr)?; 113 | match expr { 114 | syn::Lit::Float(i) => i.base10_parse::().ok().map(|i| if negated { -i } else { i } ), 115 | _ => None 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dynasmrt" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | 7 | description = "A simple runtime for assembling code at runtime. Combined with the plugin crate dynasm it can be used to write JIT compilers easily." 8 | 9 | documentation.workspace = true 10 | repository.workspace = true 11 | 12 | readme.workspace = true 13 | keywords.workspace = true 14 | license.workspace = true 15 | 16 | 17 | [dependencies] 18 | memmap2 = "0.9.5" 19 | byteorder = "1.5.0" 20 | fnv = "1.0.7" 21 | dynasm = { version = "=3.2.0", path = "../plugin" } 22 | -------------------------------------------------------------------------------- /runtime/src/cache_control.rs: -------------------------------------------------------------------------------- 1 | //! This module contains several utility functions to manage the state of the caches 2 | //! of the executing processor. On von Neumann architectures (like x86/AMD64), these are no-ops, 3 | //! as these processors ensure synchronization of the instruction and data caches internally. 4 | //! On modified Harvard architectures like ARMv8, these functions are needed to ensure that 5 | //! the data cache and instruction cache stay synchronized. 6 | 7 | /// This function should be called before any jit-compiled code is executed, on the thread that will 8 | /// execute this code. 9 | #[inline(always)] 10 | pub fn prepare_for_execution(slice: &[u8]) { 11 | #![allow(unused_variables)] 12 | #[cfg(target_arch="aarch64")] 13 | { 14 | aarch64::prepare_for_execution() 15 | } 16 | #[cfg(any(target_arch="riscv64", target_arch="riscv32"))] 17 | { 18 | riscv::enforce_ordering_dcache_icache(slice, true); 19 | } 20 | } 21 | 22 | /// This function should be called after modification of any data that could've been loaded into the 23 | /// instruction cache previously. It will ensure that these modifications will be propagated into 24 | /// the instruction caches 25 | #[inline(always)] 26 | #[allow(unused_variables)] 27 | pub fn synchronize_icache(slice: &[u8]) { 28 | #[cfg(target_arch="aarch64")] 29 | { 30 | aarch64::synchronize_icache(slice); 31 | } 32 | } 33 | 34 | #[cfg(target_arch="aarch64")] 35 | mod aarch64 { 36 | use std::arch::asm; 37 | 38 | /// return the cache line sizes as reported by the processor as a tuple of (dcache, icache) 39 | fn get_cacheline_sizes() -> (usize, usize) { 40 | let ctr_el0: usize; 41 | 42 | // safety: we're just reading a system register (ctr_cl0) that can always be read 43 | unsafe { 44 | asm!( 45 | "mrs {outreg}, ctr_el0", 46 | outreg = lateout(reg) ctr_el0, 47 | options(nomem, nostack, preserves_flags) 48 | ); 49 | } 50 | 51 | ( 52 | 4 << ((ctr_el0 >> 16) & 0xF), 53 | 4 << (ctr_el0 & 0xF) 54 | ) 55 | } 56 | 57 | /// waits for any previous cache operations to complete. According to the Aarch64 manuals 58 | /// `dsb ish` has bonus functionality where it will also wait for any previous cache maintenance 59 | /// operations to complete before allowing execution to continue. 60 | #[inline(always)] 61 | fn wait_for_cache_ops_complete() { 62 | // safety: this is purely a memory barrier. 63 | unsafe { 64 | asm!( 65 | "dsb ish", 66 | options(nostack, preserves_flags) 67 | ); 68 | } 69 | } 70 | 71 | /// inform the processor that the dache line containing `addr` should be synchronized back to 72 | /// the unified memory layer 73 | #[inline(always)] 74 | fn flush_dcache_line(addr: usize) { 75 | // safety: flushing caches is always safe 76 | unsafe { 77 | asm!( 78 | "dc cvau, {address}", 79 | address = in(reg)addr, 80 | options(nostack, preserves_flags) 81 | ); 82 | } 83 | } 84 | 85 | /// inform the processor that icache line containing `addr` is invalid, and that it should be 86 | /// re-fetched from unified memory 87 | #[inline(always)] 88 | fn invalidate_icache_line(addr: usize) { 89 | // safety: invalidating caches is always safe 90 | unsafe { 91 | asm!( 92 | "ic ivau, {address}", 93 | address = in(reg)addr, 94 | options(nostack, preserves_flags) 95 | ); 96 | } 97 | } 98 | 99 | /// inform the current core that the pipeline might contain stale data that should 100 | /// be re-fetched from the instruction cache 101 | #[inline(always)] 102 | fn invalidate_pipeline() { 103 | // safety: this is just a barrier. 104 | unsafe { 105 | asm!( 106 | "isb", 107 | options(nostack, preserves_flags) 108 | ); 109 | } 110 | } 111 | 112 | /// On Aarch64, after the data has been synchronized from the dcache to the icache 113 | /// it is necessary to flush the pipelines of the cores that will execute the modified data 114 | /// as some may already have been loaded into the pipeline. 115 | #[inline(always)] 116 | pub fn prepare_for_execution() { 117 | invalidate_pipeline(); 118 | } 119 | 120 | /// On Aarch64, we first need to flush data from the dcache to unified memory, and then 121 | /// inform the icache(s) that their current data might be invalid. This is a no-op if 122 | /// the slice is zero length. 123 | pub fn synchronize_icache(slice: &[u8]) { 124 | if slice.len() == 0 { 125 | return; 126 | } 127 | 128 | let start_addr = slice.as_ptr() as usize; 129 | let end_addr = start_addr + slice.len(); 130 | 131 | // query the cache line sizes 132 | let (dcache_line_size, icache_line_size) = get_cacheline_sizes(); 133 | 134 | // dcache cleaning loop 135 | let mut addr = start_addr & !(dcache_line_size - 1); 136 | while addr < end_addr { 137 | flush_dcache_line(addr); 138 | addr += dcache_line_size; 139 | } 140 | 141 | // need to wait for dcache cleaning to complete before invalidating the icache 142 | wait_for_cache_ops_complete(); 143 | 144 | // icache invalidation loop 145 | addr = start_addr & !(icache_line_size - 1); 146 | while addr < end_addr { 147 | invalidate_icache_line(addr); 148 | addr += icache_line_size; 149 | } 150 | 151 | // wait for that to complete as well 152 | wait_for_cache_ops_complete(); 153 | } 154 | } 155 | 156 | #[cfg(any(target_arch="riscv64", target_arch="riscv32"))] 157 | mod riscv { 158 | // On risc-v, the story about how we synchronize caches is confused. 159 | // The data sheet states that we ought to do the following. 160 | // 1: on the assembling hart, perform a data fence to ensure that 161 | // any stores will be visible to other harts 162 | // 2: on the executing hart, perform a fence.i instruction fence to 163 | // ensure that all the observed stores are visible to our instruction 164 | // fetches 165 | // 166 | // however, this doesn't solve all problems. Namely, the OS might just move our process 167 | // from the current hart to another hart after the fence.i instruction. So it basically 168 | // offers no guarantees. for this reason, linux has removed FENCE.I from the user ABI, and 169 | // instead offered a syscall for managing this. 170 | // this is `riscv_flush_icache()`, which has options to apply to a single thread, or all threads 171 | // and over a range of addresses. 172 | // as there are no other operating systems targetting risc-v right now, this is the only choice 173 | // we have. 174 | use std::ffi::{c_void, c_long, c_int}; 175 | 176 | #[cfg(unix)] 177 | extern "C" { 178 | #[link_name="__riscv_flush_icache"] 179 | fn riscv_flush_icache(start: *const c_void, end: *const c_void, flags: c_long) -> c_int; 180 | } 181 | 182 | pub fn enforce_ordering_dcache_icache(slice: &[u8], local: bool) { 183 | let range = slice.as_ptr_range(); 184 | let start = range.start as *const c_void; 185 | let end = range.end as *const c_void; 186 | let mut flags: c_long = 0; 187 | if local { 188 | flags |= 1; 189 | } 190 | let rv; 191 | unsafe { 192 | rv = riscv_flush_icache(start, end, flags); 193 | } 194 | assert!(rv == 0, "riscv_flush_icache failed, returned {rv}"); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /runtime/src/mmap.rs: -------------------------------------------------------------------------------- 1 | //! This module implements some wrappers around Mmap/MmapMut to also support a cheap "empty" variant. 2 | // Unfortunately Memmap itself doesn't support a cheap zero-length variant 3 | 4 | use std::ops::{Deref, DerefMut}; 5 | use std::io; 6 | 7 | use memmap2::{Mmap, MmapMut}; 8 | 9 | use crate::AssemblyOffset; 10 | 11 | /// A structure holding a buffer of executable memory. It also derefs to a `&[u8]`. 12 | /// This structure does not allocate when its size is 0. 13 | #[derive(Debug, Default)] 14 | pub struct ExecutableBuffer { 15 | // length of the buffer that has actually been written to 16 | length: usize, 17 | // backing buffer 18 | buffer: Option 19 | } 20 | 21 | /// ExecutableBuffer equivalent that holds a buffer of mutable memory instead of executable memory. It also derefs to a `&mut [u8]`. 22 | /// This structure does not allocate when its size is 0. 23 | #[derive(Debug, Default)] 24 | pub struct MutableBuffer { 25 | // length of the buffer that has actually been written to 26 | length: usize, 27 | // backing buffer 28 | buffer: Option 29 | } 30 | 31 | impl ExecutableBuffer { 32 | /// Obtain a pointer into the executable memory from an offset into it. 33 | /// When an offset returned from `DynasmLabelApi::offset` is used, the resulting pointer 34 | /// will point to the start of the first instruction after the offset call, 35 | /// which can then be jumped or called to divert control flow into the executable 36 | /// buffer. Note that if this buffer is accessed through an Executor, these pointers 37 | /// will only be valid as long as its lock is held. When no locks are held, 38 | /// the assembler is free to relocate the executable buffer when it requires 39 | /// more memory than available. 40 | /// 41 | /// The memory this pointer points to is owned by this `ExecutableBuffer`. 42 | /// The programmer is responsible for ensuring that pointers generated from this API do not 43 | /// outlive the `ExecutableBuffer`. 44 | pub fn ptr(&self, offset: AssemblyOffset) -> *const u8 { 45 | &self[offset.0] as *const u8 46 | } 47 | 48 | /// Create a new executable buffer, backed by a buffer of size `size`. 49 | /// It will start with an initialized length of 0. 50 | pub fn new(size: usize) -> io::Result { 51 | let buffer = if size == 0 { 52 | None 53 | } else { 54 | Some(MmapMut::map_anon(size)?.make_exec()?) 55 | }; 56 | 57 | Ok(ExecutableBuffer { 58 | length: 0, 59 | buffer 60 | }) 61 | } 62 | 63 | /// Query the backing size of this executable buffer 64 | pub fn size(&self) -> usize { 65 | self.buffer.as_ref().map(|b| b.len()).unwrap_or(0) 66 | } 67 | 68 | /// Change this executable buffer into a mutable buffer. 69 | pub fn make_mut(self) -> io::Result { 70 | let buffer = if let Some(map) = self.buffer { 71 | Some(map.make_mut()?) 72 | } else { 73 | None 74 | }; 75 | 76 | Ok(MutableBuffer { 77 | length: self.length, 78 | buffer 79 | }) 80 | } 81 | } 82 | 83 | impl MutableBuffer { 84 | /// Create a new mutable buffer, backed by a buffer of size `size`. 85 | /// It will start with an initialized length of 0. 86 | pub fn new(size: usize) -> io::Result { 87 | let buffer = if size == 0 { 88 | None 89 | } else { 90 | Some(MmapMut::map_anon(size)?) 91 | }; 92 | 93 | Ok(MutableBuffer { 94 | length: 0, 95 | buffer 96 | }) 97 | } 98 | 99 | /// Query the backing size of this mutable buffer 100 | pub fn size(&self) -> usize { 101 | self.buffer.as_ref().map(|b| b.len()).unwrap_or(0) 102 | } 103 | 104 | /// Set the length of the usable part of this mutable buffer. The length 105 | /// should not be set larger than the allocated size, otherwise methods can panic. 106 | pub fn set_len(&mut self, length: usize) { 107 | self.length = length 108 | } 109 | 110 | /// Change this mutable buffer into an executable buffer. 111 | pub fn make_exec(self) -> io::Result { 112 | let buffer = if let Some(map) = self.buffer { 113 | Some(map.make_exec()?) 114 | } else { 115 | None 116 | }; 117 | 118 | Ok(ExecutableBuffer { 119 | length: self.length, 120 | buffer 121 | }) 122 | } 123 | } 124 | 125 | impl Deref for ExecutableBuffer { 126 | type Target = [u8]; 127 | fn deref(&self) -> &[u8] { 128 | if let Some(map) = &self.buffer { 129 | &map[..self.length] 130 | } else { 131 | &[] 132 | } 133 | } 134 | } 135 | 136 | impl Deref for MutableBuffer { 137 | type Target = [u8]; 138 | fn deref(&self) -> &[u8] { 139 | if let Some(map) = &self.buffer { 140 | &map[..self.length] 141 | } else { 142 | &[] 143 | } 144 | } 145 | } 146 | 147 | impl DerefMut for MutableBuffer { 148 | fn deref_mut(&mut self) -> &mut [u8] { 149 | if let Some(map) = &mut self.buffer { 150 | &mut map[..self.length] 151 | } else { 152 | &mut [] 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /runtime/src/relocations.rs: -------------------------------------------------------------------------------- 1 | //! This module defines the `Relocation` trait and several utilities for implementing relocations. 2 | 3 | use byteorder::{ByteOrder, LittleEndian}; 4 | 5 | use std::convert::TryFrom; 6 | 7 | /// Error returned when encoding a relocation failed 8 | #[derive(Debug)] 9 | pub struct ImpossibleRelocation { } 10 | 11 | 12 | /// Used to inform assemblers on how to implement relocations for each architecture. 13 | /// When implementing a new architecture, one simply has to implement this trait for 14 | /// the architecture's relocation definition. 15 | pub trait Relocation { 16 | /// The encoded representation for this relocation that is emitted by the dynasm! macro. 17 | type Encoding; 18 | /// construct this relocation from an encoded representation. 19 | fn from_encoding(encoding: Self::Encoding) -> Self; 20 | /// construct this relocation from a simple size. This is used to implement relocations in directives and literal pools. 21 | fn from_size(size: RelocationSize) -> Self; 22 | /// The size of the slice of bytes affected by this relocation 23 | fn size(&self) -> usize; 24 | /// Write a value into a buffer of size `self.size()` in the format of this relocation. 25 | /// Any bits not part of the relocation should be preserved. 26 | fn write_value(&self, buf: &mut [u8], value: isize) -> Result<(), ImpossibleRelocation>; 27 | /// Read a value from a buffer of size `self.size()` in the format of this relocation. 28 | fn read_value(&self, buf: &[u8]) -> isize; 29 | /// Specifies what kind of relocation this relocation instance is. 30 | fn kind(&self) -> RelocationKind; 31 | /// Specifies the default page size on this platform. 32 | fn page_size() -> usize; 33 | } 34 | 35 | 36 | /// Specifies what kind of relocation a relocation is. 37 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 38 | pub enum RelocationKind { 39 | /// A simple, PC-relative relocation. These can be encoded once and do not need 40 | /// to be adjusted when the executable buffer is moved. 41 | Relative = 0, 42 | /// An absolute relocation to a relative address, 43 | /// i.e. trying to put the address of a dynasm x86 function in a register 44 | /// This means adjustment is necessary when the executable buffer is moved 45 | AbsToRel = 1, 46 | /// A relative relocation to an absolute address, 47 | /// i.e. trying to call a Rust function with a dynasm x86 call. 48 | /// This means adjustment is necessary when the executable buffer is moved 49 | RelToAbs = 2, 50 | } 51 | 52 | impl RelocationKind { 53 | /// Converts back from numeric value to RelocationKind 54 | pub fn from_encoding(encoding: u8) -> Self { 55 | match encoding { 56 | 0 => Self::Relative, 57 | 1 => Self::AbsToRel, 58 | 2 => Self::RelToAbs, 59 | x => panic!("Unsupported relocation kind {}", x) 60 | } 61 | } 62 | } 63 | 64 | 65 | /// A descriptor for the size of a relocation. This also doubles as a relocation itself 66 | /// for relocations in data directives. Can be converted to relocations of any kind of architecture 67 | /// using `Relocation::from_size`. 68 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 69 | pub enum RelocationSize { 70 | /// A byte-sized relocation 71 | Byte = 1, 72 | /// A two-byte relocation 73 | Word = 2, 74 | /// A four-byte sized relocation 75 | DWord = 4, 76 | /// An 8-byte sized relocation 77 | QWord = 8, 78 | } 79 | 80 | impl Relocation for RelocationSize { 81 | type Encoding = u8; 82 | fn from_encoding(encoding: Self::Encoding) -> Self { 83 | match encoding { 84 | 1 => RelocationSize::Byte, 85 | 2 => RelocationSize::Word, 86 | 4 => RelocationSize::DWord, 87 | 8 => RelocationSize::QWord, 88 | x => panic!("Unsupported relocation size {}", x) 89 | } 90 | } 91 | fn from_size(size: RelocationSize) -> Self { 92 | size 93 | } 94 | fn size(&self) -> usize { 95 | *self as usize 96 | } 97 | fn write_value(&self, buf: &mut [u8], value: isize) -> Result<(), ImpossibleRelocation> { 98 | match self { 99 | RelocationSize::Byte => buf[0] = 100 | i8::try_from(value).map_err(|_| ImpossibleRelocation { } )? 101 | as u8, 102 | RelocationSize::Word => LittleEndian::write_i16(buf, 103 | i16::try_from(value).map_err(|_| ImpossibleRelocation { } )? 104 | ), 105 | RelocationSize::DWord => LittleEndian::write_i32(buf, 106 | i32::try_from(value).map_err(|_| ImpossibleRelocation { } )? 107 | ), 108 | RelocationSize::QWord => LittleEndian::write_i64(buf, 109 | i64::try_from(value).map_err(|_| ImpossibleRelocation { } )? 110 | ), 111 | } 112 | Ok(()) 113 | } 114 | fn read_value(&self, buf: &[u8]) -> isize { 115 | match self { 116 | RelocationSize::Byte => buf[0] as i8 as isize, 117 | RelocationSize::Word => LittleEndian::read_i16(buf) as isize, 118 | RelocationSize::DWord => LittleEndian::read_i32(buf) as isize, 119 | RelocationSize::QWord => LittleEndian::read_i64(buf) as isize, 120 | } 121 | } 122 | fn kind(&self) -> RelocationKind { 123 | RelocationKind::Relative 124 | } 125 | fn page_size() -> usize { 126 | 4096 127 | } 128 | } 129 | 130 | pub(crate) fn fits_signed_bitfield(value: i64, bits: u8) -> bool { 131 | if bits >= 64 { 132 | return true; 133 | } 134 | 135 | let half = 1i64 << (bits - 1); 136 | value < half && value >= -half 137 | } 138 | -------------------------------------------------------------------------------- /runtime/src/x64.rs: -------------------------------------------------------------------------------- 1 | //! Runtime support for the x64 architecture assembling target. 2 | //! 3 | //! The x64 instruction set features variable-length instructions and 4 | //! relative relocations up to 32 bits in size. 5 | //! 6 | //! The core relocation behaviour for this architecture is provided by the [`X64Relocation`] type. 7 | //! 8 | //! Next to that, this module contains the following: 9 | //! 10 | //! ## Type aliases 11 | //! 12 | //! Several specialized type aliases of the generic [`Assembler`] are provided as these are by far the most common usecase. 13 | //! 14 | //! ## Enums 15 | //! 16 | //! There are enumerator of every logically distinct register family usable in x64. 17 | //! These enums implement the [`Register`] trait and their discriminant values match their numeric encoding in dynamic register literals. 18 | //! Some of these are re-exported from the x86 architecture. 19 | //! 20 | //! *Note: The presence of some registers listed here is purely what is encodable. Check the relevant architecture documentation to find what is architecturally valid.* 21 | 22 | use crate::relocations::{Relocation, RelocationSize, RelocationKind, ImpossibleRelocation}; 23 | use crate::Register; 24 | 25 | use std::hash::Hash; 26 | 27 | 28 | /// Relocation implementation for the x64 architecture. 29 | #[derive(Debug, Clone)] 30 | pub struct X64Relocation { 31 | size: RelocationSize, 32 | } 33 | 34 | impl Relocation for X64Relocation { 35 | type Encoding = (u8,); 36 | fn from_encoding(encoding: Self::Encoding) -> Self { 37 | Self { 38 | size: RelocationSize::from_encoding(encoding.0), 39 | } 40 | } 41 | fn from_size(size: RelocationSize) -> Self { 42 | Self { 43 | size, 44 | } 45 | } 46 | fn size(&self) -> usize { 47 | self.size.size() 48 | } 49 | fn write_value(&self, buf: &mut [u8], value: isize) -> Result<(), ImpossibleRelocation> { 50 | self.size.write_value(buf, value) 51 | } 52 | fn read_value(&self, buf: &[u8]) -> isize { 53 | self.size.read_value(buf) 54 | } 55 | fn kind(&self) -> RelocationKind { 56 | RelocationKind::Relative 57 | } 58 | fn page_size() -> usize { 59 | 4096 60 | } 61 | } 62 | 63 | /// An x64 Assembler. This is aliased here for backwards compatability. 64 | pub type Assembler = crate::Assembler; 65 | /// An x64 AssemblyModifier. This is aliased here for backwards compatability. 66 | pub type AssemblyModifier<'a> = crate::Modifier<'a, X64Relocation>; 67 | /// An x64 UncommittedModifier. This is aliased here for backwards compatability. 68 | pub type UncommittedModifier<'a> = crate::UncommittedModifier<'a>; 69 | 70 | 71 | /// 1, 2, 4 or 8-byte general purpose "quad-word" registers. 72 | /// 73 | /// RIP does not appear here as it cannot be addressed dynamically. 74 | #[allow(missing_docs)] 75 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 76 | pub enum Rq { 77 | RAX = 0x0, RCX = 0x1, RDX = 0x2, RBX = 0x3, 78 | RSP = 0x4, RBP = 0x5, RSI = 0x6, RDI = 0x7, 79 | R8 = 0x8, R9 = 0x9, R10 = 0xA, R11 = 0xB, 80 | R12 = 0xC, R13 = 0xD, R14 = 0xE, R15 = 0xF, 81 | } 82 | reg_impls!(Rq); 83 | 84 | /// 16 or 32-byte SSE registers. 85 | #[allow(missing_docs)] 86 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 87 | pub enum Rx { 88 | XMM0 = 0x0, XMM1 = 0x1, XMM2 = 0x2, XMM3 = 0x3, 89 | XMM4 = 0x4, XMM5 = 0x5, XMM6 = 0x6, XMM7 = 0x7, 90 | XMM8 = 0x8, XMM9 = 0x9, XMM10 = 0xA, XMM11 = 0xB, 91 | XMM12 = 0xC, XMM13 = 0xD, XMM14 = 0xE, XMM15 = 0xF, 92 | } 93 | reg_impls!(Rx); 94 | 95 | /// 8-byte control registers. 96 | #[allow(missing_docs)] 97 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 98 | pub enum RC { 99 | CR0 = 0x0, CR1 = 0x1, CR2 = 0x2, CR3 = 0x3, 100 | CR4 = 0x4, CR5 = 0x5, CR6 = 0x6, CR7 = 0x7, 101 | CR8 = 0x8, CR9 = 0x9, CR10 = 0xA, CR11 = 0xB, 102 | CR12 = 0xC, CR13 = 0xD, CR14 = 0xE, CR15 = 0xF, 103 | } 104 | reg_impls!(RC); 105 | 106 | // The other register families are the same as 32-bit X86. (Although access size for Debug regs is 8-byte) 107 | pub use crate::x86::{Rh, Rf, Rm, Rs, RD, RB}; 108 | 109 | #[cfg(test)] 110 | mod tests { 111 | use super::Rq::*; 112 | use crate::Register; 113 | 114 | #[test] 115 | fn reg_code() { 116 | assert_eq!(RAX.code(), 0); 117 | } 118 | 119 | #[test] 120 | fn reg_code_from() { 121 | assert_eq!(u8::from(R11), 11); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /runtime/src/x86.rs: -------------------------------------------------------------------------------- 1 | //! Runtime support for the x86 architecture assembling target. 2 | //! 3 | //! The x86 instruction set features variable-length instructions and 4 | //! relative relocations up to 16 bits in size, or absolute relocations of 32 bits in size. 5 | //! 6 | //! The core relocation behaviour for this architecture is provided by the [`X86Relocation`] type. 7 | //! 8 | //! Next to that, this module contains the following: 9 | //! 10 | //! ## Type aliases 11 | //! 12 | //! Several specialized type aliases of the generic [`Assembler`] are provided as these are by far the most common usecase. 13 | //! 14 | //! ## Enums 15 | //! 16 | //! There are enumerator of every logically distinct register family usable in x86. 17 | //! These enums implement the [`Register`] trait and their discriminant values match their numeric encoding in dynamic register literals. 18 | //! 19 | //! *Note: The presence of some registers listed here is purely what is encodable. Check the relevant architecture documentation to find what is architecturally valid.* 20 | 21 | 22 | use crate::Register; 23 | use crate::relocations::{Relocation, RelocationSize, RelocationKind, ImpossibleRelocation}; 24 | 25 | 26 | /// Relocation implementation for the x86 architecture. 27 | #[derive(Debug, Clone)] 28 | pub struct X86Relocation { 29 | size: RelocationSize, 30 | kind: RelocationKind, 31 | } 32 | 33 | impl Relocation for X86Relocation { 34 | type Encoding = (u8, u8); 35 | fn from_encoding(encoding: Self::Encoding) -> Self { 36 | Self { 37 | size: RelocationSize::from_encoding(encoding.0), 38 | kind: RelocationKind::from_encoding(encoding.1), 39 | } 40 | } 41 | fn from_size(size: RelocationSize) -> Self { 42 | Self { 43 | size, 44 | kind: RelocationKind::Relative, 45 | } 46 | } 47 | fn size(&self) -> usize { 48 | self.size.size() 49 | } 50 | fn write_value(&self, buf: &mut [u8], value: isize) -> Result<(), ImpossibleRelocation> { 51 | self.size.write_value(buf, value) 52 | } 53 | fn read_value(&self, buf: &[u8]) -> isize { 54 | self.size.read_value(buf) 55 | } 56 | fn kind(&self) -> RelocationKind { 57 | self.kind 58 | } 59 | fn page_size() -> usize { 60 | 4096 61 | } 62 | } 63 | 64 | 65 | /// An x86 Assembler. This is aliased here for backwards compatability. 66 | pub type Assembler = crate::Assembler; 67 | /// An x86 AssemblyModifier. This is aliased here for backwards compatability. 68 | pub type AssemblyModifier<'a> = crate::Modifier<'a, X86Relocation>; 69 | /// An x86 UncommittedModifier. This is aliased here for backwards compatability. 70 | pub type UncommittedModifier<'a> = crate::UncommittedModifier<'a>; 71 | 72 | 73 | /// 1, 2 or 4-byte general purpose "double-word" registers. 74 | /// 75 | /// EIP does not appear here as it cannot be addressed dynamically. 76 | #[allow(missing_docs)] 77 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 78 | pub enum Rd { 79 | EAX = 0x00, ECX = 0x01, EDX = 0x02, EBX = 0x03, 80 | ESP = 0x04, EBP = 0x05, ESI = 0x06, EDI = 0x07, 81 | } 82 | reg_impls!(Rd); 83 | 84 | /// High-byte general purpose registers. 85 | #[allow(missing_docs)] 86 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 87 | pub enum Rh { 88 | AH = 0x4, CH = 0x5, DH = 0x6, BH = 0x7, 89 | } 90 | reg_impls!(Rh); 91 | 92 | /// 10-byte floating point registers. 93 | #[allow(missing_docs)] 94 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 95 | pub enum Rf { 96 | ST0 = 0x0, ST1 = 0x1, ST2 = 0x2, ST3 = 0x3, 97 | ST4 = 0x4, ST5 = 0x5, ST6 = 0x6, ST7 = 0x7, 98 | } 99 | reg_impls!(Rf); 100 | 101 | /// 8-byte MMX registers. 102 | #[allow(missing_docs)] 103 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 104 | pub enum Rm { 105 | MMX0 = 0x0, MMX1 = 0x1, MMX2 = 0x2, MMX3 = 0x3, 106 | MMX4 = 0x4, MMX5 = 0x5, MMX6 = 0x6, MMX7 = 0x7, 107 | } 108 | reg_impls!(Rm); 109 | 110 | /// 16 or 32-byte SSE registers. 111 | #[allow(missing_docs)] 112 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 113 | pub enum Rx { 114 | XMM0 = 0x0, XMM1 = 0x1, XMM2 = 0x2, XMM3 = 0x3, 115 | XMM4 = 0x4, XMM5 = 0x5, XMM6 = 0x6, XMM7 = 0x7, 116 | } 117 | reg_impls!(Rx); 118 | 119 | /// 2-byte segment registers. 120 | #[allow(missing_docs)] 121 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 122 | pub enum Rs { 123 | ES = 0x0, CS = 0x1, SS = 0x2, DS = 0x3, 124 | FS = 0x4, GS = 0x5, 125 | } 126 | reg_impls!(Rs); 127 | 128 | /// 4-byte control registers. 129 | #[allow(missing_docs)] 130 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 131 | pub enum RC { 132 | CR0 = 0x0, CR1 = 0x1, CR2 = 0x2, CR3 = 0x3, 133 | CR4 = 0x4, CR5 = 0x5, CR6 = 0x6, CR7 = 0x7, 134 | } 135 | reg_impls!(RC); 136 | 137 | /// 4-byte debug registers. 138 | #[allow(missing_docs)] 139 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 140 | pub enum RD { 141 | DR0 = 0x0, DR1 = 0x1, DR2 = 0x2, DR3 = 0x3, 142 | DR4 = 0x4, DR5 = 0x5, DR6 = 0x6, DR7 = 0x7, 143 | DR8 = 0x8, DR9 = 0x9, DR10 = 0xA, DR11 = 0xB, 144 | DR12 = 0xC, DR13 = 0xD, DR14 = 0xE, DR15 = 0xF, 145 | } 146 | reg_impls!(RD); 147 | 148 | /// 16-byte bound registers. 149 | #[allow(missing_docs)] 150 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 151 | pub enum RB { 152 | BND0 = 0x0, BND1 = 0x1, BND2 = 0x2, BND3 = 0x3 153 | } 154 | reg_impls!(RB); 155 | 156 | #[cfg(test)] 157 | mod tests { 158 | use super::Rd::*; 159 | use crate::Register; 160 | 161 | #[test] 162 | fn reg_code() { 163 | assert_eq!(EAX.code(), 0); 164 | } 165 | 166 | #[test] 167 | fn reg_code_from() { 168 | assert_eq!(u8::from(ECX), 1); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /testing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "testing" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | 7 | [dependencies] 8 | itertools = "0.14.0" 9 | 10 | [dependencies.dynasmrt] 11 | path = "../runtime" 12 | -------------------------------------------------------------------------------- /testing/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | fn main() { 4 | println!("Please execute: cargo test --no-fail-fast") 5 | } 6 | -------------------------------------------------------------------------------- /testing/tests/aarch64_0.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_aarch64/aarch64_tests_0.rs.gen"); 7 | 8 | -------------------------------------------------------------------------------- /testing/tests/aarch64_1.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_aarch64/aarch64_tests_1.rs.gen"); 7 | -------------------------------------------------------------------------------- /testing/tests/aarch64_2.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_aarch64/aarch64_tests_2.rs.gen"); 7 | 8 | -------------------------------------------------------------------------------- /testing/tests/aarch64_3.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_aarch64/aarch64_tests_3.rs.gen"); 7 | 8 | -------------------------------------------------------------------------------- /testing/tests/aarch64_4.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_aarch64/aarch64_tests_4.rs.gen"); 7 | 8 | -------------------------------------------------------------------------------- /testing/tests/aarch64_5.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_aarch64/aarch64_tests_5.rs.gen"); 7 | 8 | -------------------------------------------------------------------------------- /testing/tests/aarch64_6.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_aarch64/aarch64_tests_6.rs.gen"); 7 | 8 | -------------------------------------------------------------------------------- /testing/tests/aarch64_7.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_aarch64/aarch64_tests_7.rs.gen"); 7 | 8 | -------------------------------------------------------------------------------- /testing/tests/aarch64_8.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_aarch64/aarch64_tests_8.rs.gen"); 7 | 8 | -------------------------------------------------------------------------------- /testing/tests/aarch64_cache_coherency.rs: -------------------------------------------------------------------------------- 1 | // This file contains test cases designed to validate proper assembler cache invalidation 2 | // this is needed because aarch64's modified harvard architecture has an incoherent instruction and 3 | // data cache. Therefore, it is needed to explicitly command the cache hierarchy to flush the dcache 4 | // to the coherent layers, invalidate the icache, and ensure no stale data is left in the 5 | // instruction pipeline. Testcases in this file are designed to break if this isn't handled properly 6 | #![allow(unused_imports)] 7 | 8 | extern crate dynasmrt; 9 | 10 | use dynasmrt::dynasm; 11 | use dynasmrt::{DynasmApi, DynasmLabelApi}; 12 | 13 | #[cfg(target_arch="aarch64")] 14 | #[test] 15 | fn test_cache_coherency_same_core() { 16 | let mut ops = dynasmrt::aarch64::Assembler::new().unwrap(); 17 | let reader = ops.reader(); 18 | 19 | // write some code 20 | let start = ops.offset(); 21 | dynasm!(ops 22 | ; .arch aarch64 23 | ; mov w0, 0xABCD 24 | ; ret 25 | ); 26 | let end = ops.offset(); 27 | 28 | ops.commit().unwrap(); 29 | 30 | // execute it once 31 | { 32 | let buf = reader.lock(); 33 | let callable: extern "C" fn() -> u32 = unsafe { std::mem::transmute(buf.ptr(start)) }; 34 | assert_eq!(callable(), 0xABCD); 35 | drop(buf); 36 | } 37 | 38 | // change the code back and forth to see if errors happen 39 | for _ in 0 .. 10000 { 40 | // change the code 41 | ops.alter(|modifier| { 42 | modifier.goto(start); 43 | 44 | dynasm!(modifier 45 | ; .arch aarch64 46 | ; mov w0, 0xCDEF 47 | ; ret 48 | ); 49 | modifier.check_exact(end).unwrap(); 50 | 51 | }).unwrap(); 52 | 53 | // execute it 54 | { 55 | let buf = reader.lock(); 56 | let callable: extern "C" fn() -> u32 = unsafe { std::mem::transmute(buf.ptr(start)) }; 57 | assert_eq!(callable(), 0xCDEF); 58 | drop(buf); 59 | } 60 | 61 | // change the code again 62 | ops.alter(|modifier| { 63 | modifier.goto(start); 64 | 65 | dynasm!(modifier 66 | ; .arch aarch64 67 | ; mov w0, 0xABCD 68 | ; ret 69 | ); 70 | modifier.check_exact(end).unwrap(); 71 | 72 | }).unwrap(); 73 | 74 | // execute it again 75 | { 76 | let buf = reader.lock(); 77 | let callable: extern "C" fn() -> u32 = unsafe { std::mem::transmute(buf.ptr(start)) }; 78 | assert_eq!(callable(), 0xABCD); 79 | drop(buf); 80 | } 81 | } 82 | } 83 | 84 | #[cfg(target_arch="aarch64")] 85 | #[test] 86 | fn test_cache_coherency_other_cores() { 87 | // spawn a bunch of threads, and have them all racing to execute some assembly 88 | // then modify the assembly, and see if we execute stale data 89 | let thread_count = 3; 90 | 91 | use std::sync::atomic::{AtomicU32, AtomicBool, Ordering}; 92 | 93 | // the code we'll generate tries to read one of these atomics with acquire ordering, 94 | // and always expects to read 0x12345678. At first it tries to read the first one, and 95 | // then we update it to read the second one, at which point we also change the second one 96 | // to hold the expected value, and invalidate the first one. If stale code is read 97 | // it will read the first value instead, which at that point should be updated to be invalid 98 | let first_value = AtomicU32::new(0x12345678); 99 | let second_value = AtomicU32::new(0xDEADC0DE); 100 | let rejoin_threads = AtomicBool::new(false); 101 | 102 | let mut ops = dynasmrt::aarch64::Assembler::new().unwrap(); 103 | 104 | // write some code; 105 | dynasm!(ops 106 | ; .arch aarch64 107 | ; .align 8 108 | ; -> first_addr: 109 | ; .u64 first_value.as_ptr() as *mut u8 as _ 110 | ; -> second_addr: 111 | ; .u64 second_value.as_ptr() as *mut u8 as _ 112 | ); 113 | let start = ops.offset(); 114 | dynasm!(ops 115 | ; .arch aarch64 116 | ; adr x1, ->first_addr 117 | ; adr x2, ->second_addr 118 | ); 119 | let edit = ops.offset(); 120 | dynasm!(ops 121 | ; .arch aarch64 122 | ; ldr x0, [x1] 123 | ; ldr w0, [x0] 124 | ; ret 125 | ); 126 | let end = ops.offset(); 127 | 128 | ops.commit().unwrap(); 129 | 130 | std::thread::scope(|scope| { 131 | 132 | // start our racing threads 133 | let mut handles = Vec::new(); 134 | for _ in 0 .. thread_count { 135 | 136 | // these get moved to each threads 137 | let reader = ops.reader(); 138 | let rejoin_threads_borrow = &rejoin_threads; 139 | 140 | handles.push(scope.spawn(move || { 141 | 142 | let mut bad_results = 0usize; 143 | while !rejoin_threads_borrow.load(Ordering::Acquire) { 144 | 145 | let buf = reader.lock(); 146 | let callable: extern "C" fn() -> u32 = unsafe { std::mem::transmute(buf.ptr(start)) }; 147 | 148 | let value = callable(); 149 | if value != 0x12345678 { 150 | bad_results += 1; 151 | } 152 | } 153 | 154 | bad_results 155 | })); 156 | } 157 | 158 | // wait a bit 159 | std::thread::sleep(std::time::Duration::from_millis(10)); 160 | 161 | // change the code back and forth to see if errors happen 162 | for _ in 0 .. 100 { 163 | ops.alter(|modifier| { 164 | modifier.goto(edit); 165 | 166 | dynasm!(modifier 167 | ; .arch aarch64 168 | ; ldr x0, [x2] 169 | ; ldr w0, [x0] 170 | ; ret 171 | ); 172 | modifier.check_exact(end).unwrap(); 173 | 174 | // also change the values. ordering is relaxed as the lock of the assembler 175 | // guarantees that these values will be visible. 176 | first_value.store(0xDEADBEEF, Ordering::Relaxed); 177 | second_value.store(0x12345678, Ordering::Relaxed); 178 | 179 | }).unwrap(); 180 | 181 | // wait a bit more 182 | std::thread::sleep(std::time::Duration::from_millis(10)); 183 | 184 | // change it back 185 | ops.alter(|modifier| { 186 | modifier.goto(edit); 187 | 188 | dynasm!(modifier 189 | ; .arch aarch64 190 | ; ldr x0, [x1] 191 | ; ldr w0, [x0] 192 | ; ret 193 | ); 194 | modifier.check_exact(end).unwrap(); 195 | 196 | // also change the values. ordering is relaxed as the lock of the assembler 197 | // guarantees that these values will be visible. 198 | first_value.store(0x12345678, Ordering::Relaxed); 199 | second_value.store(0xDEADBEEF, Ordering::Relaxed); 200 | 201 | }).unwrap(); 202 | 203 | // wait a bit more 204 | std::thread::sleep(std::time::Duration::from_millis(1)); 205 | } 206 | 207 | // join our threads 208 | rejoin_threads.store(true, Ordering::Release); 209 | 210 | let errors: usize = handles.into_iter().map(|handle| handle.join().unwrap()).sum(); 211 | 212 | assert_eq!(errors, 0, "racing threads read the wrong value"); 213 | 214 | }); 215 | } 216 | -------------------------------------------------------------------------------- /testing/tests/aarch64_complex.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | extern crate dynasmrt; 4 | 5 | use dynasmrt::{dynasm, MutPointer}; 6 | use dynasmrt::{DynasmApi, DynasmLabelApi}; 7 | use dynasmrt::components::LitPool; 8 | 9 | // aliases, and dynasm! in item position 10 | macro_rules! my_dynasm { 11 | ($ops:ident $($t:tt)*) => { 12 | dynasm!($ops 13 | ; .arch aarch64 14 | ; .alias test, x1 15 | $($t)* 16 | ) 17 | } 18 | } 19 | 20 | #[test] 21 | fn complex() { 22 | let mut ops = dynasmrt::aarch64::Assembler::new().unwrap(); 23 | let d = 3; 24 | let c = 4; 25 | 26 | let label = ops.new_dynamic_label(); 27 | let litpool_dyn = ops.new_dynamic_label(); 28 | 29 | let wide_integer = 0x5600000u64; 30 | let a_random_float = 3.625f32; 31 | let bitmask = 0x00FF00FFFF00FF00; 32 | let logical = 0x03F003F003F003F0; 33 | 34 | let mut litpool = LitPool::new(); 35 | 36 | // interesting testcases 37 | my_dynasm!(ops 38 | ; aligned: 39 | // no args 40 | ; nop 41 | // bare immediate 42 | ; hlt 0x324 43 | // arm immediate 44 | ; hlt #0x123 45 | // registers 46 | ; mov w1, w2 47 | // weird registers 48 | ; mov w1, wzr 49 | ; mov w1, wsp 50 | ; mov test, sp 51 | // dynamic registers 52 | ; mov w1, W(2) 53 | // memory references 54 | ; ldr x2, [x3] 55 | ; ldr x2, [x3, 8] 56 | ; ldr x2, [x3, #16] 57 | ; ldr x2, [x3, #16]! 58 | ; ldr x2, [x3], #5 59 | ; ldr x2, [x3, x4] 60 | ; ldr x2, [x5, x6, lsl 3] 61 | ; ldr x2, [x5, x6, lsl d] 62 | ; ldr x2, [x7, x8, lsl #3] 63 | ; ldr x2, [x9, w10, uxtw] 64 | ; ldr x2, [x11, w12, uxtw #3] 65 | // register lists 66 | ; ld1 {v1.b8}, [x3] 67 | ; ld1 {v1.b16, v2.b16}, [x3] 68 | ; ld1 {v1.h4, v2.h4, v3.h4}, [x3] 69 | ; ld1 {v1.h8, v2.h8, v3.h8, v4.h8}, [x3] 70 | ; ld1 {v1.s2 - v4.s2}, [x3] 71 | ; ld1 {v1.d2 * 4}, [x3] 72 | // vector lane specificers 73 | ; mla v1.h4, v2.h4, v2.h4[0] 74 | ; mla v1.h8, v2.h8, V(c).h[d] 75 | ; ld4 {v1.b * 4}[0], [x3] 76 | // "funny" immediates 77 | ; mov x29, 0x12340000 78 | ; mov x29, 0x123400000000 79 | ; mov x29, 0x1234000000000000 80 | ; mov x29, #wide_integer 81 | ; mov.logical x30, 0xff00ff00ff00ff00 82 | ; mov.logical x29, 0x5555555555555555 83 | ; mov.logical w28, 0xff00ff00 84 | ; mov.logical w27, 0x33333333 85 | ; mov.logical x5, #logical 86 | ; mov.inverted x26, 0xffff1234ffffffff 87 | ; mov.inverted x25, 0xffffffff5678ffff 88 | ; mov.inverted w24, 0x9012ffff 89 | ; movi v1.d2, 0xff00ff00ff00ff00 90 | ; movi d1, 0x00ff00ffff00ff00 91 | ; movi d1, #bitmask 92 | ; fmov s2, 0.1875 93 | ; fmov d3, 11.0 94 | ; fmov s4, a_random_float 95 | // ident args 96 | ; dsb sy 97 | // labels 98 | ; a: // local 99 | ; -> b: // global 100 | ; => label // dynamic. 101 | // jumps 102 | ; b b 104 | ; b => label 105 | ; b.lt b 107 | ; adrp x1, b 109 | ; ldr x2, litpool + litpool.push_u8(0xCC) 112 | ; ldrb w1, [x1] 113 | ; adr x1, >litpool + litpool.push_u8(0xEE) 114 | ; ldrb w1, [x1] 115 | ; adr x1, >litpool + litpool.push_u8(0xFF) 116 | ; ldrb w1, [x1] 117 | ; adr x1, =>(litpool_dyn) + litpool.push_u8(0x12) 118 | ; adr x1, =>(litpool_dyn) + litpool.push_u8(0x34) 119 | ; adr x1, =>(litpool_dyn) + litpool.push_u8(0x56) 120 | ; ldr w2, >litpool + litpool.push_u64(0x00ff00ffff00ff00) 121 | ; ldr w2, >litpool + litpool.push_u64(0x1111111111111111) 122 | ; ldr w2, >litpool + litpool.push_u64(0x2222222222222222) 123 | ;.align 8, 0xDD 124 | ;litpool: 125 | ;=>litpool_dyn 126 | ;;litpool.emit(&mut ops) 127 | ); 128 | 129 | 130 | let buf = ops.finalize().unwrap(); 131 | 132 | println!("Generated assembly:"); 133 | for i in buf.iter() { 134 | print!("{:02x }", i); 135 | } 136 | println!(""); 137 | } 138 | -------------------------------------------------------------------------------- /testing/tests/bugreports.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | // basic dynamic register usage 7 | #[test] 8 | fn bugreport_1() { 9 | let mut ops = dynasmrt::x64::Assembler::new().unwrap(); 10 | dynasm!(ops 11 | ; .arch x64 12 | ; int 3 13 | ; mov Rq(8), rdi 14 | ; add Rq(8), 1 15 | ; mov rax, Rq(8) 16 | ; ret 17 | ); 18 | let buf = ops.finalize().unwrap(); 19 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 20 | let hex: String = hex.join(", "); 21 | assert_eq!(hex, "0xCD, 0x03, 0x49, 0x89, 0xF8, 0x49, 0x83, 0xC0, 0x01, 0x4C, 0x89, 0xC0, 0xC3", "bugreport_1"); 22 | } 23 | 24 | // ensure RBP/RSP can be used as dynamic base register by always emitting the full SIB byte and a displacement 25 | #[test] 26 | fn bugreport_2() { 27 | let mut ops = dynasmrt::x64::Assembler::new().unwrap(); 28 | dynasm!(ops 29 | ; .arch x64 30 | ; inc [rsp] 31 | ; inc [Rq(4)] 32 | ; inc [Rq(4) + 1] 33 | ; inc [4 * rdx + Rq(4) + 1] 34 | ; inc [rbp] 35 | ; inc [Rq(5)] 36 | ; inc [Rq(5) + 1] 37 | ; inc [4 * rdx + Rq(5) + 1] 38 | ; inc [r12] 39 | ; inc [Rq(12)] 40 | ; inc [Rq(12) + 1] 41 | ; inc [4 * rdx + Rq(12) + 1] 42 | ; inc [r13] 43 | ; inc [Rq(13)] 44 | ; inc [Rq(13) + 1] 45 | ; inc [4 * rdx + Rq(13) + 1] 46 | ; inc [rcx] 47 | ; inc [Rq(1)] 48 | ; inc [Rq(1) + 1] 49 | ; inc [4 * rdx + Rq(1) + 1] 50 | ); 51 | let buf = ops.finalize().unwrap(); 52 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 53 | let hex: String = hex.join(", "); 54 | assert_eq!(hex, "0xFE, 0x04, 0x24, 0x40, 0xFE, 0x44, 0x24, 0x00, 0x40, 0xFE, 0x44, 0x24, 0x01, 0x40, 0xFE, 0x44, 0x94, 0x01, 0xFE, 0x45, 0x00, 0x40, 0xFE, 0x44, 0x25, 0x00, 0x40, 0xFE, 0x44, 0x25, 0x01, 0x40, 0xFE, 0x44, 0x95, 0x01, 0x41, 0xFE, 0x04, 0x24, 0x41, 0xFE, 0x44, 0x24, 0x00, 0x41, 0xFE, 0x44, 0x24, 0x01, 0x41, 0xFE, 0x44, 0x94, 0x01, 0x41, 0xFE, 0x45, 0x00, 0x41, 0xFE, 0x44, 0x25, 0x00, 0x41, 0xFE, 0x44, 0x25, 0x01, 0x41, 0xFE, 0x44, 0x95, 0x01, 0xFE, 0x01, 0x40, 0xFE, 0x44, 0x21, 0x00, 0x40, 0xFE, 0x44, 0x21, 0x01, 0x40, 0xFE, 0x44, 0x91, 0x01", "bugreport_2"); 55 | } 56 | 57 | // ensure dynamic registers work correctly with VEX ops 58 | #[test] 59 | fn bugreport_3() { 60 | let mut ops = dynasmrt::x64::Assembler::new().unwrap(); 61 | dynasm!(ops 62 | ; .arch x64 63 | ; vaddsd Rx(1), Rx(2), Rx(3) 64 | ; vaddsd Rx(10), Rx(9), Rx(11) 65 | ); 66 | let buf = ops.finalize().unwrap(); 67 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 68 | let hex: String = hex.join(", "); 69 | assert_eq!(hex, "0xC4, 0xE1, 0x6B, 0x58, 0xCB, 0xC4, 0x41, 0x33, 0x58, 0xD3", "bugreport_3"); 70 | } 71 | 72 | // overflow in logical immediate encoding 73 | #[test] 74 | fn bugreport_4() { 75 | let mut ops = dynasmrt::VecAssembler::::new(0); 76 | dynasm!(ops 77 | ; .arch aarch64 78 | ; and w0, w0, 255 79 | ); 80 | } 81 | 82 | // Precedence issue around typemapped operands due to proc_macro2::Delimiter::None being broken. 83 | #[test] 84 | fn bugreport_5() { 85 | #[allow(dead_code)] 86 | struct Test { 87 | a: u32, 88 | b: u32 89 | } 90 | 91 | let mut ops = dynasmrt::x64::Assembler::new().unwrap(); 92 | dynasm!(ops 93 | ; .arch x64 94 | ; mov rbx => Test[2 + 1].b, rax 95 | ); 96 | 97 | let buf = ops.finalize().unwrap(); 98 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 99 | let hex: String = hex.join(", "); 100 | assert_eq!(hex, "0x48, 0x89, 0x83, 0x1C, 0x00, 0x00, 0x00", "bugreport_5"); 101 | } 102 | 103 | // Bad sizing of constant immediates in x64 mode 104 | #[test] 105 | fn bugreport_6() { 106 | let mut ops = dynasmrt::x64::Assembler::new().unwrap(); 107 | dynasm!(ops 108 | ; .arch x64 109 | ; lea rax, [rbx + 1] 110 | ; lea rax, [rbx + 0x80] 111 | ; lea rax, [rbx - 1] 112 | ; lea rax, [rbx + -1] 113 | ; lea rax, [rbx - 0x81] 114 | ); 115 | 116 | let buf = ops.finalize().unwrap(); 117 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 118 | let hex: String = hex.join(", "); 119 | assert_eq!(hex, "0x48, 0x8D, 0x43, 0x01, 0x48, 0x8D, 0x83, 0x80, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x43, 0xFF, 0x48, 0x8D, 0x43, 0xFF, 0x48, 0x8D, 0x83, 0x7F, 0xFF, 0xFF, 0xFF", "bugreport_6"); 120 | } 121 | -------------------------------------------------------------------------------- /testing/tests/directives.rs: -------------------------------------------------------------------------------- 1 | // Testcases for dynasm-rs directives 2 | use dynasmrt::{dynasm, DynasmApi}; 3 | 4 | 5 | #[cfg(target_arch="x86_64")] 6 | #[test] 7 | fn test_default_arch() { 8 | let mut ops = dynasmrt::SimpleAssembler::new(); 9 | dynasm!(ops 10 | ; inc DWORD [rax*8 + rbx + 16] 11 | ); 12 | 13 | let buf = ops.finalize(); 14 | let hex: Vec = buf.iter().map(|x| format!("{:02X}", *x)).collect(); 15 | let hex = hex.join(", "); 16 | assert_eq!(hex, "FF, 44, C3, 10", "Default arch is x64"); 17 | } 18 | 19 | #[cfg(target_arch="x86")] 20 | #[test] 21 | fn test_default_arch() { 22 | let mut ops = dynasmrt::SimpleAssembler::new(); 23 | dynasm!(ops 24 | // this instruction is encoded differently in x86 and x64 25 | ; inc eax 26 | ); 27 | 28 | let buf = ops.finalize(); 29 | let hex: Vec = buf.iter().map(|x| format!("{:02X}", *x)).collect(); 30 | let hex = hex.join(", "); 31 | assert_eq!(hex, "40", "Default arch is x86"); 32 | } 33 | 34 | #[cfg(target_arch="aarch64")] 35 | #[test] 36 | fn test_default_arch() { 37 | let mut ops = dynasmrt::SimpleAssembler::new(); 38 | dynasm!(ops 39 | ; ldr x2, [x11, w12, uxtw #3] 40 | ); 41 | 42 | let buf = ops.finalize(); 43 | let hex: Vec = buf.iter().map(|x| format!("{:02X}", *x)).collect(); 44 | let hex = hex.join(", "); 45 | assert_eq!(hex, "62, 59, 6C, F8", "Default arch is aarch64"); 46 | } 47 | 48 | #[test] 49 | fn test_arch_switching() { 50 | let mut ops = dynasmrt::SimpleAssembler::new(); 51 | dynasm!(ops 52 | ; .arch x64 53 | ; inc DWORD [rax*8 + rbx + 16] 54 | ; .arch x86 55 | ; inc eax 56 | ; .arch aarch64 57 | ; ldr x2, [x11, w12, uxtw #3] 58 | ; .arch unknown 59 | ); 60 | 61 | let buf = ops.finalize(); 62 | let hex: Vec = buf.iter().map(|x| format!("{:02X}", *x)).collect(); 63 | let hex = hex.join(", "); 64 | assert_eq!(hex, "FF, 44, C3, 10, 40, 62, 59, 6C, F8", "Switching between architectures"); 65 | } 66 | 67 | #[test] 68 | fn test_x64_features() { 69 | dynasm!(() 70 | ; .arch x64 71 | ; .feature fpu 72 | ; .feature mmx 73 | ; .feature tdnow 74 | ; .feature sse 75 | ; .feature sse2 76 | ; .feature sse3 77 | ; .feature vmx 78 | ; .feature ssse3 79 | ; .feature sse4a 80 | ; .feature sse41 81 | ; .feature sse42 82 | ; .feature sse5 83 | ; .feature avx 84 | ; .feature avx2 85 | ; .feature fma 86 | ; .feature bmi1 87 | ; .feature bmi2 88 | ; .feature tbm 89 | ; .feature rtm 90 | ; .feature invpcid 91 | ; .feature mpx 92 | ; .feature sha 93 | ; .feature prefetchwt1 94 | ; .feature cyrix 95 | ; .feature amd 96 | ; .feature directstores 97 | // multiple 98 | ; .feature sse, sse2 99 | ); 100 | } 101 | 102 | #[test] 103 | fn test_aliases() { 104 | let mut ops = dynasmrt::SimpleAssembler::new(); 105 | dynasm!(ops 106 | ; .arch x64 107 | ; .alias first, rax 108 | ; .alias second, rbx 109 | ; inc DWORD [first*8 + second + 16] 110 | ; .arch x86 111 | ; .alias first_but_smaller, eax 112 | ; inc first_but_smaller 113 | ; .arch aarch64 114 | ; .alias one, x2 115 | ; .alias two, x11 116 | ; .alias three, w12 117 | ; ldr one, [two, three, uxtw #3] 118 | ); 119 | 120 | let buf = ops.finalize(); 121 | let hex: Vec = buf.iter().map(|x| format!("{:02X}", *x)).collect(); 122 | let hex = hex.join(", "); 123 | assert_eq!(hex, "FF, 44, C3, 10, 40, 62, 59, 6C, F8", "Register aliases"); 124 | } 125 | 126 | #[test] 127 | fn test_data_directives_unaligned_unsigned() { 128 | let mut ops = dynasmrt::SimpleAssembler::new(); 129 | dynasm!(ops 130 | ; .u8 0x88 131 | ; .u16 0x9999 132 | ; .u32 0xAAAAAAAA 133 | ; .u64 0xBBBBBBBBBBBBBBBB 134 | ); 135 | 136 | let buf = ops.finalize(); 137 | let hex: Vec = buf.iter().map(|x| format!("{:02X}", *x)).collect(); 138 | let hex = hex.join(", "); 139 | assert_eq!(hex, "88, 99, 99, AA, AA, AA, AA, BB, BB, BB, BB, BB, BB, BB, BB", "Data directives"); 140 | } 141 | 142 | #[test] 143 | fn test_data_directives_unaligned_signed() { 144 | let mut ops = dynasmrt::SimpleAssembler::new(); 145 | dynasm!(ops 146 | ; .i8 -0x44 147 | ; .i16 -0x1111 148 | ; .i32 -0x22222222 149 | ; .i64 -0x3333333333333333 150 | ); 151 | 152 | let buf = ops.finalize(); 153 | let hex: Vec = buf.iter().map(|x| format!("{:02X}", *x)).collect(); 154 | let hex = hex.join(", "); 155 | assert_eq!(hex, "BC, EF, EE, DE, DD, DD, DD, CD, CC, CC, CC, CC, CC, CC, CC", "Data directives"); 156 | } 157 | 158 | #[test] 159 | fn test_data_directives_aligned_unsigned() { 160 | let mut ops = dynasmrt::SimpleAssembler::new(); 161 | dynasm!(ops 162 | ; .arch x64 163 | ; .u8 0x88 164 | ; .align 2 165 | ; .u16 0x9999 166 | ; .align 8 167 | ; .u32 0xAAAAAAAA 168 | ; .align 8 169 | ; .u64 0xBBBBBBBBBBBBBBBB 170 | ); 171 | 172 | let buf = ops.finalize(); 173 | let hex: Vec = buf.iter().map(|x| format!("{:02X}", *x)).collect(); 174 | let hex = hex.join(", "); 175 | assert_eq!(hex, "88, 90, 99, 99, 90, 90, 90, 90, AA, AA, AA, AA, 90, 90, 90, 90, BB, BB, BB, BB, BB, BB, BB, BB", "Data directives"); 176 | } 177 | 178 | #[test] 179 | fn test_data_directives_aligned_signed() { 180 | let mut ops = dynasmrt::SimpleAssembler::new(); 181 | dynasm!(ops 182 | ; .arch x64 183 | ; .i8 -0x44 184 | ; .align 2 185 | ; .i16 -0x1111 186 | ; .align 8 187 | ; .i32 -0x22222222 188 | ; .align 8 189 | ; .i64 -0x3333333333333333 190 | ); 191 | 192 | let buf = ops.finalize(); 193 | let hex: Vec = buf.iter().map(|x| format!("{:02X}", *x)).collect(); 194 | let hex = hex.join(", "); 195 | assert_eq!(hex, "BC, 90, EF, EE, 90, 90, 90, 90, DE, DD, DD, DD, 90, 90, 90, 90, CD, CC, CC, CC, CC, CC, CC, CC", "Data directives"); 196 | } 197 | 198 | #[test] 199 | fn test_data_directives_unaligned_float() { 200 | let mut ops = dynasmrt::SimpleAssembler::new(); 201 | dynasm!(ops 202 | ; .arch x64 203 | ; .f32 3.14159265359 204 | ; .f64 3.14159265359 205 | ); 206 | 207 | let buf = ops.finalize(); 208 | let hex: Vec = buf.iter().map(|x| format!("{:02X}", *x)).collect(); 209 | let hex = hex.join(", "); 210 | assert_eq!(hex, "DB, 0F, 49, 40, EA, 2E, 44, 54, FB, 21, 09, 40", "Data directives"); 211 | } 212 | 213 | #[test] 214 | fn test_data_directives_aligned_float() { 215 | let mut ops = dynasmrt::SimpleAssembler::new(); 216 | dynasm!(ops 217 | ; .arch x64 218 | ; .f32 3.14159265359 219 | ; .align 8 220 | ; .f64 3.14159265359 221 | ); 222 | 223 | let buf = ops.finalize(); 224 | let hex: Vec = buf.iter().map(|x| format!("{:02X}", *x)).collect(); 225 | let hex = hex.join(", "); 226 | assert_eq!(hex, "DB, 0F, 49, 40, 90, 90, 90, 90, EA, 2E, 44, 54, FB, 21, 09, 40", "Data directives"); 227 | } 228 | 229 | #[test] 230 | fn test_bytes() { 231 | let mut ops = dynasmrt::SimpleAssembler::new(); 232 | let data: Vec = (0 .. 0xFF).collect(); 233 | dynasm!(ops 234 | ; .bytes &data 235 | ); 236 | 237 | let buf = ops.finalize(); 238 | assert_eq!(data, buf, "bytes directive"); 239 | } 240 | -------------------------------------------------------------------------------- /testing/tests/gen_x64/avx512.rs.gen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CensoredUsername/dynasm-rs/d3446db33c23c7da0ecdf59f119dd25264cdd886/testing/tests/gen_x64/avx512.rs.gen -------------------------------------------------------------------------------- /testing/tests/gen_x64/avx512bw.rs.gen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CensoredUsername/dynasm-rs/d3446db33c23c7da0ecdf59f119dd25264cdd886/testing/tests/gen_x64/avx512bw.rs.gen -------------------------------------------------------------------------------- /testing/tests/gen_x64/avx512cd.rs.gen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CensoredUsername/dynasm-rs/d3446db33c23c7da0ecdf59f119dd25264cdd886/testing/tests/gen_x64/avx512cd.rs.gen -------------------------------------------------------------------------------- /testing/tests/gen_x64/avx512dq.rs.gen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CensoredUsername/dynasm-rs/d3446db33c23c7da0ecdf59f119dd25264cdd886/testing/tests/gen_x64/avx512dq.rs.gen -------------------------------------------------------------------------------- /testing/tests/gen_x64/avx512er.rs.gen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CensoredUsername/dynasm-rs/d3446db33c23c7da0ecdf59f119dd25264cdd886/testing/tests/gen_x64/avx512er.rs.gen -------------------------------------------------------------------------------- /testing/tests/gen_x64/avx512ifma.rs.gen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CensoredUsername/dynasm-rs/d3446db33c23c7da0ecdf59f119dd25264cdd886/testing/tests/gen_x64/avx512ifma.rs.gen -------------------------------------------------------------------------------- /testing/tests/gen_x64/avx512pf.rs.gen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CensoredUsername/dynasm-rs/d3446db33c23c7da0ecdf59f119dd25264cdd886/testing/tests/gen_x64/avx512pf.rs.gen -------------------------------------------------------------------------------- /testing/tests/gen_x64/avx512vbmi.rs.gen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CensoredUsername/dynasm-rs/d3446db33c23c7da0ecdf59f119dd25264cdd886/testing/tests/gen_x64/avx512vbmi.rs.gen -------------------------------------------------------------------------------- /testing/tests/gen_x64/avx512vl.rs.gen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CensoredUsername/dynasm-rs/d3446db33c23c7da0ecdf59f119dd25264cdd886/testing/tests/gen_x64/avx512vl.rs.gen -------------------------------------------------------------------------------- /testing/tests/gen_x64/directstores.rs.gen: -------------------------------------------------------------------------------- 1 | 2 | #[test] 3 | fn enc_directstores_movdiri2154() { 4 | let mut ops = dynasmrt::SimpleAssembler::new(); 5 | dynasm!(ops 6 | ; .arch x64 7 | ; movdiri QWORD [rax * 2 + rdx], rcx 8 | ); 9 | let buf = ops.finalize(); 10 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 11 | let hex: String = hex.join(", "); 12 | assert_eq!(hex, "0x48, 0x0F, 0x38, 0xF9, 0x0C, 0x42", "movdiri QWORD [rax * 2 + rdx], rcx"); 13 | } 14 | 15 | 16 | 17 | #[test] 18 | fn enc_directstores_movdiri2155() { 19 | let mut ops = dynasmrt::SimpleAssembler::new(); 20 | dynasm!(ops 21 | ; .arch x64 22 | ; movdiri QWORD [rax], rcx 23 | ); 24 | let buf = ops.finalize(); 25 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 26 | let hex: String = hex.join(", "); 27 | assert_eq!(hex, "0x48, 0x0F, 0x38, 0xF9, 0x08", "movdiri QWORD [rax], rcx"); 28 | } 29 | 30 | 31 | 32 | #[test] 33 | fn enc_directstores_movdiri2156() { 34 | let mut ops = dynasmrt::SimpleAssembler::new(); 35 | dynasm!(ops 36 | ; .arch x64 37 | ; movdiri QWORD [rax], rax 38 | ); 39 | let buf = ops.finalize(); 40 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 41 | let hex: String = hex.join(", "); 42 | assert_eq!(hex, "0x48, 0x0F, 0x38, 0xF9, 0x00", "movdiri QWORD [rax], rax"); 43 | } 44 | 45 | 46 | 47 | #[test] 48 | fn enc_directstores_movdiri2157() { 49 | let mut ops = dynasmrt::SimpleAssembler::new(); 50 | dynasm!(ops 51 | ; .arch x64 52 | ; movdiri QWORD [rax + 16], rdx 53 | ); 54 | let buf = ops.finalize(); 55 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 56 | let hex: String = hex.join(", "); 57 | assert_eq!(hex, "0x48, 0x0F, 0x38, 0xF9, 0x50, 0x10", "movdiri QWORD [rax + 16], rdx"); 58 | } 59 | 60 | 61 | 62 | #[test] 63 | fn enc_directstores_movdiri2158() { 64 | let mut ops = dynasmrt::SimpleAssembler::new(); 65 | dynasm!(ops 66 | ; .arch x64 67 | ; movdiri DWORD [rax + 16], ecx 68 | ); 69 | let buf = ops.finalize(); 70 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 71 | let hex: String = hex.join(", "); 72 | assert_eq!(hex, "0x0F, 0x38, 0xF9, 0x48, 0x10", "movdiri DWORD [rax + 16], ecx"); 73 | } 74 | 75 | 76 | 77 | #[test] 78 | fn enc_directstores_movdiri2159() { 79 | let mut ops = dynasmrt::SimpleAssembler::new(); 80 | dynasm!(ops 81 | ; .arch x64 82 | ; movdiri DWORD [rax * 2 + rdx], edx 83 | ); 84 | let buf = ops.finalize(); 85 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 86 | let hex: String = hex.join(", "); 87 | assert_eq!(hex, "0x0F, 0x38, 0xF9, 0x14, 0x42", "movdiri DWORD [rax * 2 + rdx], edx"); 88 | } 89 | 90 | 91 | 92 | #[test] 93 | fn enc_directstores_movdiri2160() { 94 | let mut ops = dynasmrt::SimpleAssembler::new(); 95 | dynasm!(ops 96 | ; .arch x64 97 | ; movdiri DWORD [rax + 16], eax 98 | ); 99 | let buf = ops.finalize(); 100 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 101 | let hex: String = hex.join(", "); 102 | assert_eq!(hex, "0x0F, 0x38, 0xF9, 0x40, 0x10", "movdiri DWORD [rax + 16], eax"); 103 | } 104 | 105 | 106 | 107 | #[test] 108 | fn enc_directstores_movdiri2161() { 109 | let mut ops = dynasmrt::SimpleAssembler::new(); 110 | dynasm!(ops 111 | ; .arch x64 112 | ; movdiri DWORD [rax * 2 + rdx], ecx 113 | ); 114 | let buf = ops.finalize(); 115 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 116 | let hex: String = hex.join(", "); 117 | assert_eq!(hex, "0x0F, 0x38, 0xF9, 0x0C, 0x42", "movdiri DWORD [rax * 2 + rdx], ecx"); 118 | } 119 | 120 | 121 | 122 | #[test] 123 | fn enc_directstores_movdiri2162() { 124 | let mut ops = dynasmrt::SimpleAssembler::new(); 125 | dynasm!(ops 126 | ; .arch x64 127 | ; movdiri DWORD [rax * 2 + rdx], eax 128 | ); 129 | let buf = ops.finalize(); 130 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 131 | let hex: String = hex.join(", "); 132 | assert_eq!(hex, "0x0F, 0x38, 0xF9, 0x04, 0x42", "movdiri DWORD [rax * 2 + rdx], eax"); 133 | } 134 | 135 | 136 | -------------------------------------------------------------------------------- /testing/tests/gen_x64/invpcid.rs.gen: -------------------------------------------------------------------------------- 1 | 2 | #[test] 3 | fn enc_invpcid_invpcid5908() { 4 | let mut ops = dynasmrt::SimpleAssembler::new(); 5 | dynasm!(ops 6 | ; .arch x64 7 | ; invpcid rcx, OWORD [rax] 8 | ); 9 | let buf = ops.finalize(); 10 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 11 | let hex: String = hex.join(", "); 12 | assert_eq!(hex, "0x66, 0x0F, 0x38, 0x82, 0x08", "invpcid rcx, OWORD [rax]"); 13 | } 14 | 15 | 16 | 17 | #[test] 18 | fn enc_invpcid_invpcid5909() { 19 | let mut ops = dynasmrt::SimpleAssembler::new(); 20 | dynasm!(ops 21 | ; .arch x64 22 | ; invpcid rcx, OWORD [rax] 23 | ); 24 | let buf = ops.finalize(); 25 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 26 | let hex: String = hex.join(", "); 27 | assert_eq!(hex, "0x66, 0x0F, 0x38, 0x82, 0x08", "invpcid rcx, OWORD [rax]"); 28 | } 29 | 30 | 31 | 32 | #[test] 33 | fn enc_invpcid_invpcid5910() { 34 | let mut ops = dynasmrt::SimpleAssembler::new(); 35 | dynasm!(ops 36 | ; .arch x64 37 | ; invpcid rcx, OWORD [rax] 38 | ); 39 | let buf = ops.finalize(); 40 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 41 | let hex: String = hex.join(", "); 42 | assert_eq!(hex, "0x66, 0x0F, 0x38, 0x82, 0x08", "invpcid rcx, OWORD [rax]"); 43 | } 44 | 45 | 46 | 47 | #[test] 48 | fn enc_invpcid_invpcid5911() { 49 | let mut ops = dynasmrt::SimpleAssembler::new(); 50 | dynasm!(ops 51 | ; .arch x64 52 | ; invpcid rax, OWORD [rax] 53 | ); 54 | let buf = ops.finalize(); 55 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 56 | let hex: String = hex.join(", "); 57 | assert_eq!(hex, "0x66, 0x0F, 0x38, 0x82, 0x00", "invpcid rax, OWORD [rax]"); 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /testing/tests/gen_x64/prefetchwt1.rs.gen: -------------------------------------------------------------------------------- 1 | 2 | #[test] 3 | fn enc_prefetchwt1_prefetchwt13299() { 4 | let mut ops = dynasmrt::SimpleAssembler::new(); 5 | dynasm!(ops 6 | ; .arch x64 7 | ; prefetchwt1 BYTE [rax * 2 + rdx] 8 | ); 9 | let buf = ops.finalize(); 10 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 11 | let hex: String = hex.join(", "); 12 | assert_eq!(hex, "0x0F, 0x0D, 0x14, 0x42", "prefetchwt1 BYTE [rax * 2 + rdx]"); 13 | } 14 | 15 | 16 | 17 | #[test] 18 | fn enc_prefetchwt1_prefetchwt13300() { 19 | let mut ops = dynasmrt::SimpleAssembler::new(); 20 | dynasm!(ops 21 | ; .arch x64 22 | ; prefetchwt1 BYTE [rax + 16] 23 | ); 24 | let buf = ops.finalize(); 25 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 26 | let hex: String = hex.join(", "); 27 | assert_eq!(hex, "0x0F, 0x0D, 0x50, 0x10", "prefetchwt1 BYTE [rax + 16]"); 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /testing/tests/gen_x64/rtm.rs.gen: -------------------------------------------------------------------------------- 1 | 2 | #[test] 3 | fn enc_rtm_xabort5901() { 4 | let mut ops = dynasmrt::SimpleAssembler::new(); 5 | dynasm!(ops 6 | ; .arch x64 7 | ; xabort 30 8 | ); 9 | let buf = ops.finalize(); 10 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 11 | let hex: String = hex.join(", "); 12 | assert_eq!(hex, "0xC6, 0xF8, 0x1E", "xabort 30"); 13 | } 14 | 15 | 16 | 17 | #[test] 18 | fn enc_rtm_xabort5902() { 19 | let mut ops = dynasmrt::SimpleAssembler::new(); 20 | dynasm!(ops 21 | ; .arch x64 22 | ; xabort 118 23 | ); 24 | let buf = ops.finalize(); 25 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 26 | let hex: String = hex.join(", "); 27 | assert_eq!(hex, "0xC6, 0xF8, 0x76", "xabort 118"); 28 | } 29 | 30 | 31 | 32 | #[test] 33 | fn enc_rtm_xabort5903() { 34 | let mut ops = dynasmrt::SimpleAssembler::new(); 35 | dynasm!(ops 36 | ; .arch x64 37 | ; xabort 114 38 | ); 39 | let buf = ops.finalize(); 40 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 41 | let hex: String = hex.join(", "); 42 | assert_eq!(hex, "0xC6, 0xF8, 0x72", "xabort 114"); 43 | } 44 | 45 | 46 | 47 | #[test] 48 | fn enc_rtm_xabort5904() { 49 | let mut ops = dynasmrt::SimpleAssembler::new(); 50 | dynasm!(ops 51 | ; .arch x64 52 | ; xabort 29 53 | ); 54 | let buf = ops.finalize(); 55 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 56 | let hex: String = hex.join(", "); 57 | assert_eq!(hex, "0xC6, 0xF8, 0x1D", "xabort 29"); 58 | } 59 | 60 | 61 | 62 | #[test] 63 | fn enc_rtm_xabort5905() { 64 | let mut ops = dynasmrt::SimpleAssembler::new(); 65 | dynasm!(ops 66 | ; .arch x64 67 | ; xabort 48 68 | ); 69 | let buf = ops.finalize(); 70 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 71 | let hex: String = hex.join(", "); 72 | assert_eq!(hex, "0xC6, 0xF8, 0x30", "xabort 48"); 73 | } 74 | 75 | 76 | 77 | #[test] 78 | fn enc_rtm_xend5906() { 79 | let mut ops = dynasmrt::SimpleAssembler::new(); 80 | dynasm!(ops 81 | ; .arch x64 82 | ; xend 83 | ); 84 | let buf = ops.finalize(); 85 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 86 | let hex: String = hex.join(", "); 87 | assert_eq!(hex, "0x0F, 0x01, 0xD5", "xend"); 88 | } 89 | 90 | 91 | 92 | #[test] 93 | fn enc_rtm_xtest5907() { 94 | let mut ops = dynasmrt::SimpleAssembler::new(); 95 | dynasm!(ops 96 | ; .arch x64 97 | ; xtest 98 | ); 99 | let buf = ops.finalize(); 100 | let hex: Vec = buf.iter().map(|x| format!("0x{:02X}", *x)).collect(); 101 | let hex: String = hex.join(", "); 102 | assert_eq!(hex, "0x0F, 0x01, 0xD6", "xtest"); 103 | } 104 | 105 | 106 | -------------------------------------------------------------------------------- /testing/tests/gen_x64/sse42.rs.gen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CensoredUsername/dynasm-rs/d3446db33c23c7da0ecdf59f119dd25264cdd886/testing/tests/gen_x64/sse42.rs.gen -------------------------------------------------------------------------------- /testing/tests/riscv32_0.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_riscv32/riscv32_tests_0.rs.gen"); 7 | -------------------------------------------------------------------------------- /testing/tests/riscv32_1.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_riscv32/riscv32_tests_1.rs.gen"); 7 | -------------------------------------------------------------------------------- /testing/tests/riscv64_0.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_riscv64/riscv64_tests_0.rs.gen"); 7 | -------------------------------------------------------------------------------- /testing/tests/riscv64_1.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_riscv64/riscv64_tests_1.rs.gen"); 7 | -------------------------------------------------------------------------------- /testing/tests/riscv64_2.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::DynasmApi; 5 | 6 | include!("gen_riscv64/riscv64_tests_2.rs.gen"); 7 | -------------------------------------------------------------------------------- /testing/tests/riscv_cache_coherency.rs: -------------------------------------------------------------------------------- 1 | // This file contains test cases designed to validate proper assembler cache invalidation 2 | // this is needed because aarch64's modified harvard architecture has an incoherent instruction and 3 | // data cache. Therefore, it is needed to explicitly command the cache hierarchy to flush the dcache 4 | // to the coherent layers, invalidate the icache, and ensure no stale data is left in the 5 | // instruction pipeline. Testcases in this file are designed to break if this isn't handled properly 6 | #![allow(unused_imports)] 7 | 8 | extern crate dynasmrt; 9 | 10 | use dynasmrt::dynasm; 11 | use dynasmrt::{DynasmApi, DynasmLabelApi}; 12 | 13 | #[cfg(target_arch="riscv64")] 14 | #[test] 15 | fn test_cache_coherency_same_core() { 16 | let mut ops = dynasmrt::riscv::Assembler::new().unwrap(); 17 | let reader = ops.reader(); 18 | 19 | // write some code 20 | let start = ops.offset(); 21 | dynasm!(ops 22 | ; .arch riscv64 23 | ; li a0, 0xABCD 24 | ; ret 25 | ); 26 | let end = ops.offset(); 27 | 28 | ops.commit().unwrap(); 29 | 30 | // execute it once 31 | { 32 | let buf = reader.lock(); 33 | let callable: extern "C" fn() -> u32 = unsafe { std::mem::transmute(buf.ptr(start)) }; 34 | assert_eq!(callable(), 0xABCD); 35 | drop(buf); 36 | } 37 | 38 | // change the code back and forth to see if errors happen 39 | for _ in 0 .. 10000 { 40 | // change the code 41 | ops.alter(|modifier| { 42 | modifier.goto(start); 43 | 44 | dynasm!(modifier 45 | ; .arch riscv64 46 | ; li a0, 0xCDEF 47 | ; ret 48 | ); 49 | modifier.check_exact(end).unwrap(); 50 | 51 | }).unwrap(); 52 | 53 | // execute it 54 | { 55 | let buf = reader.lock(); 56 | let callable: extern "C" fn() -> u32 = unsafe { std::mem::transmute(buf.ptr(start)) }; 57 | assert_eq!(callable(), 0xCDEF); 58 | drop(buf); 59 | } 60 | 61 | // change the code again 62 | ops.alter(|modifier| { 63 | modifier.goto(start); 64 | 65 | dynasm!(modifier 66 | ; .arch riscv64 67 | ; li a0, 0xABCD 68 | ; ret 69 | ); 70 | modifier.check_exact(end).unwrap(); 71 | 72 | }).unwrap(); 73 | 74 | // execute it again 75 | { 76 | let buf = reader.lock(); 77 | let callable: extern "C" fn() -> u32 = unsafe { std::mem::transmute(buf.ptr(start)) }; 78 | assert_eq!(callable(), 0xABCD); 79 | drop(buf); 80 | } 81 | } 82 | } 83 | 84 | #[cfg(target_arch="riscv64")] 85 | #[test] 86 | fn test_cache_coherency_other_cores() { 87 | // spawn a bunch of threads, and have them all racing to execute some assembly 88 | // then modify the assembly, and see if we execute stale data 89 | let thread_count = 3; 90 | 91 | use std::sync::atomic::{AtomicU32, AtomicBool, Ordering}; 92 | 93 | // the code we'll generate tries to read one of these atomics with acquire ordering, 94 | // and always expects to read 0x12345678. At first it tries to read the first one, and 95 | // then we update it to read the second one, at which point we also change the second one 96 | // to hold the expected value, and invalidate the first one. If stale code is read 97 | // it will read the first value instead, which at that point should be updated to be invalid 98 | let first_value = AtomicU32::new(0x12345678); 99 | let second_value = AtomicU32::new(0xDEADC0DE); 100 | let rejoin_threads = AtomicBool::new(false); 101 | 102 | let mut ops = dynasmrt::riscv::Assembler::new().unwrap(); 103 | 104 | // write some code; 105 | dynasm!(ops 106 | ; .arch riscv64 107 | ; .align 8 108 | ; -> first_addr: 109 | ; .u64 first_value.as_ptr() as *mut u8 as _ 110 | ; -> second_addr: 111 | ; .u64 second_value.as_ptr() as *mut u8 as _ 112 | ); 113 | let start = ops.offset(); 114 | dynasm!(ops 115 | ; .arch riscv64 116 | ; la t0, ->first_addr 117 | ; la t1, ->second_addr 118 | ); 119 | let edit = ops.offset(); 120 | dynasm!(ops 121 | ; .arch riscv64 122 | ; ld t2, [t0] 123 | ; lwu a0, [t2] 124 | ; ret 125 | ); 126 | let end = ops.offset(); 127 | 128 | ops.commit().unwrap(); 129 | 130 | std::thread::scope(|scope| { 131 | 132 | // start our racing threads 133 | let mut handles = Vec::new(); 134 | for _ in 0 .. thread_count { 135 | 136 | // these get moved to each threads 137 | let reader = ops.reader(); 138 | let rejoin_threads_borrow = &rejoin_threads; 139 | 140 | handles.push(scope.spawn(move || { 141 | 142 | let mut bad_results = 0usize; 143 | while !rejoin_threads_borrow.load(Ordering::Acquire) { 144 | 145 | let buf = reader.lock(); 146 | let callable: extern "C" fn() -> u32 = unsafe { std::mem::transmute(buf.ptr(start)) }; 147 | 148 | let value = callable(); 149 | if value != 0x12345678 { 150 | assert_eq!(value, 0xDEADC0DE, "something worse is broken"); 151 | bad_results += 1; 152 | } 153 | } 154 | 155 | bad_results 156 | })); 157 | } 158 | 159 | // wait a bit 160 | std::thread::sleep(std::time::Duration::from_millis(10)); 161 | 162 | // change the code back and forth to see if errors happen 163 | for _ in 0 .. 100 { 164 | ops.alter(|modifier| { 165 | modifier.goto(edit); 166 | 167 | dynasm!(modifier 168 | ; .arch riscv64 169 | ; ld t2, [t1] 170 | ; lwu a0, [t2] 171 | ; ret 172 | ); 173 | modifier.check_exact(end).unwrap(); 174 | 175 | // also change the values. ordering is relaxed as the lock of the assembler 176 | // guarantees that these values will be visible. 177 | first_value.store(0xDEADC0DE, Ordering::Relaxed); 178 | second_value.store(0x12345678, Ordering::Relaxed); 179 | 180 | }).unwrap(); 181 | 182 | // wait a bit more 183 | std::thread::sleep(std::time::Duration::from_millis(10)); 184 | 185 | // change it back 186 | ops.alter(|modifier| { 187 | modifier.goto(edit); 188 | 189 | dynasm!(modifier 190 | ; .arch riscv64 191 | ; ld t2, [t0] 192 | ; lwu a0, [t2] 193 | ; ret 194 | ); 195 | modifier.check_exact(end).unwrap(); 196 | 197 | // also change the values. ordering is relaxed as the lock of the assembler 198 | // guarantees that these values will be visible. 199 | first_value.store(0x12345678, Ordering::Relaxed); 200 | second_value.store(0xDEADC0DE, Ordering::Relaxed); 201 | 202 | }).unwrap(); 203 | 204 | // wait a bit more 205 | std::thread::sleep(std::time::Duration::from_millis(1)); 206 | } 207 | 208 | // join our threads 209 | rejoin_threads.store(true, Ordering::Release); 210 | 211 | let errors: usize = handles.into_iter().map(|handle| handle.join().unwrap()).sum(); 212 | 213 | assert_eq!(errors, 0, "racing threads read the wrong value"); 214 | 215 | }); 216 | } 217 | -------------------------------------------------------------------------------- /testing/tests/riscv_complex.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | extern crate dynasmrt; 4 | 5 | use dynasmrt::{dynasm, MutPointer}; 6 | use dynasmrt::{DynasmApi, DynasmLabelApi}; 7 | use dynasmrt::components::LitPool; 8 | 9 | // aliases, and dynasm! in item position 10 | macro_rules! my_dynasm { 11 | ($ops:ident $($t:tt)*) => { 12 | dynasm!($ops 13 | ; .arch riscv64 14 | ; .feature GCQZcmt_Zcmp_Zacas_Zfa 15 | $($t)* 16 | ) 17 | } 18 | } 19 | 20 | #[test] 21 | fn complex() { 22 | let mut ops = dynasmrt::riscv::Assembler::new().unwrap(); 23 | // let d = 3i32; 24 | let c = 4u32; 25 | 26 | // interesting testcases 27 | my_dynasm!(ops 28 | ; test: 29 | // no args 30 | ; nop 31 | // short 32 | ; c.nop 33 | // bare immediate 34 | ; cm.jalt 99 35 | // expression 36 | ; cm.jt c 37 | // registers 38 | ; add x1, x2, x3 39 | ; add X(1), X(2), X(3) 40 | ; fadd.q f1, f2, f3 41 | // weird registers 42 | ; add x1, x2, x0 43 | ; c.add x1, x2 44 | ; c.addw x8, x9 45 | ; c.lui x1, 0x1F000 46 | ; amocas.d x2, x4, [x6] 47 | ; cm.mva01s s1, s7 48 | // register list 49 | ; cm.push {ra, s0 - s6}, -80 50 | ; cm.push {ra; 7}, -80 51 | // memory references 52 | ; ld x6, [x7] 53 | ; ld x6, [x7, 0] 54 | ; ld x6, [x7, 512] 55 | ; amocas.d x2, x4, [x6] 56 | ; amocas.d x2, x4, [x6, 0] 57 | // ident arguments 58 | ; fence iorw, iorw 59 | ; fence.tso 60 | ; fadd.q f1, f2, f3, rdn 61 | ; fli.q f1, min 62 | ; fli.q f2, inf 63 | ; fli.q f3, nan 64 | ; fli.q f4, 1.0 65 | ; csrrc x1, fflags, x2 66 | ; csrrc x1, 1, x2 67 | // all the branches 68 | ; beqz x31, (i: I) -> I { i } 9 | 10 | // chunk comparison function 11 | fn are_chunks_equal(data: &[u8], chunksize: usize) -> bool { 12 | assert!(data.len() >= chunksize * 2); 13 | assert!(((data.len() / chunksize) * chunksize) == data.len()); 14 | 15 | let mut iter = data.chunks_exact(chunksize); 16 | 17 | let first = iter.next().expect("data was empty"); 18 | 19 | for c in iter { 20 | if c != first { 21 | return false; 22 | } 23 | } 24 | true 25 | } 26 | 27 | #[test] 28 | fn register_lists_1() { 29 | let mut ops = dynasmrt::SimpleAssembler::new(); 30 | dynasm!(ops 31 | ; .arch riscv64 32 | ; .feature GCQZcmt_Zcmp_Zacas_Zfa 33 | ; cm.push {ra, s0 - s6}, -80 34 | ; cm.push {ra, s0 - s6}, i(-80) 35 | ; cm.push {ra; 7}, -80 36 | ; cm.push {ra; 7}, i(-80) 37 | ; cm.push {ra; i(7)}, -80 38 | ; cm.push {ra; i(7)}, i(-80) 39 | ); 40 | let buf = ops.finalize(); 41 | assert!(are_chunks_equal(&buf, 4), "register_lists"); 42 | } 43 | 44 | #[test] 45 | fn register_lists_2() { 46 | let mut ops = dynasmrt::SimpleAssembler::new(); 47 | dynasm!(ops 48 | ; .arch riscv64 49 | ; .feature GCQZcmt_Zcmp_Zacas_Zfa 50 | ; cm.push {ra, s0 - s11}, -112 51 | ; cm.push {ra, s0 - s11}, i(-112) 52 | ; cm.push {ra; 12}, -112 53 | ; cm.push {ra; 12}, i(-112) 54 | ; cm.push {ra; i(12)}, -112 55 | ; cm.push {ra; i(12)}, i(-112) 56 | ); 57 | let buf = ops.finalize(); 58 | assert!(are_chunks_equal(&buf, 2), "register_lists"); 59 | } 60 | 61 | #[test] 62 | fn csrs() { 63 | let mut ops = dynasmrt::SimpleAssembler::new(); 64 | dynasm!(ops 65 | ; .arch riscv64 66 | ; .feature GCQZicsr 67 | ; csrc cycle, x3 68 | ; csrc 0xC00, x3 69 | ; csrc i(0xC00), x3 70 | ); 71 | let buf = ops.finalize(); 72 | assert!(are_chunks_equal(&buf, 4), "csrs"); 73 | } 74 | 75 | #[test] 76 | fn uimm() { 77 | let mut ops = dynasmrt::SimpleAssembler::new(); 78 | dynasm!(ops 79 | ; .arch riscv64 80 | ; .feature GCZcmop_Zimop 81 | ; mop.r.27 x3, x4 82 | ; mop.r.N 27, x3, x4 83 | ; mop.r.N i(27), x3, x4 84 | ); 85 | let buf = ops.finalize(); 86 | assert!(are_chunks_equal(&buf, 4), "uimm"); 87 | } 88 | 89 | #[test] 90 | fn simm() { 91 | let mut ops = dynasmrt::SimpleAssembler::new(); 92 | dynasm!(ops 93 | ; .arch riscv64 94 | ; .feature G 95 | ; andi x1, x2, -0x45C 96 | ; andi x1, x2, i(-0x45C) 97 | ); 98 | let buf = ops.finalize(); 99 | assert!(are_chunks_equal(&buf, 4), "uimm"); 100 | } 101 | 102 | #[test] 103 | fn uimm_odd() { 104 | let mut ops = dynasmrt::SimpleAssembler::new(); 105 | dynasm!(ops 106 | ; .arch riscv64 107 | ; .feature GCZcmop_Zimop 108 | ; c.mop.11 109 | ; c.mop.N 11 110 | ; c.mop.N i(11) 111 | ); 112 | let buf = ops.finalize(); 113 | assert!(are_chunks_equal(&buf, 2), "uimm_odd"); 114 | } 115 | 116 | #[test] 117 | fn uimm_no0() { 118 | let mut ops = dynasmrt::SimpleAssembler::new(); 119 | dynasm!(ops 120 | ; .arch riscv64 121 | ; .feature GC 122 | ; c.addi4spn x8, x2, 0x3C4 123 | ; c.addi4spn x8, x2, i(0x3C4) 124 | ); 125 | let buf = ops.finalize(); 126 | assert!(are_chunks_equal(&buf, 2), "uimm_no0"); 127 | } 128 | 129 | #[test] 130 | fn simm_no0() { 131 | let mut ops = dynasmrt::SimpleAssembler::new(); 132 | dynasm!(ops 133 | ; .arch riscv64 134 | ; .feature GC 135 | ; c.addi16sp x2, 0x1C0 136 | ; c.addi16sp x2, i(0x1C0) 137 | ); 138 | let buf = ops.finalize(); 139 | assert!(are_chunks_equal(&buf, 2), "simm_no0"); 140 | } 141 | 142 | #[test] 143 | fn bigimm() { 144 | let mut ops = dynasmrt::SimpleAssembler::new(); 145 | dynasm!(ops 146 | ; .arch riscv64 147 | ; .feature G 148 | ; li x23, 0x1234_5678_9ABC_DEF0 149 | ; li x23, i(0x1234_5678_9ABC_DEF0) 150 | ); 151 | let buf = ops.finalize(); 152 | assert!(are_chunks_equal(&buf, 32), "bigimm"); 153 | } 154 | 155 | #[test] 156 | fn uimm_range() { 157 | let mut ops = dynasmrt::SimpleAssembler::new(); 158 | dynasm!(ops 159 | ; .arch riscv64 160 | ; .feature GZcmt 161 | ; cm.jalt 177 162 | ; cm.jalt i(177) 163 | ); 164 | let buf = ops.finalize(); 165 | assert!(are_chunks_equal(&buf, 2), "uimm_range"); 166 | } 167 | 168 | #[test] 169 | fn offsets_range() { 170 | let mut ops = dynasmrt::SimpleAssembler::new(); 171 | dynasm!(ops 172 | ; .arch riscv64 173 | ; .feature GZcmt 174 | ; la x5, 0x12345678 175 | ; la x5, i(0x12345678) 176 | ); 177 | let buf = ops.finalize(); 178 | assert!(are_chunks_equal(&buf, 8), "offsets_range"); 179 | } 180 | -------------------------------------------------------------------------------- /testing/tests/x64_0_complex.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | extern crate dynasmrt; 4 | 5 | use dynasmrt::{dynasm, MutPointer}; 6 | use dynasmrt::{DynasmApi, DynasmLabelApi}; 7 | 8 | // aliases, and dynasm! in item position 9 | macro_rules! my_dynasm { 10 | ($ops:ident $($t:tt)*) => { 11 | dynasm!($ops 12 | ; .arch x64 13 | ; .alias test, rax 14 | $($t)* 15 | ) 16 | } 17 | } 18 | 19 | #[test] 20 | fn complex() { 21 | let mut ops = dynasmrt::x64::Assembler::new().unwrap(); 22 | let d = 3; 23 | let c = 4; 24 | 25 | let label = ops.new_dynamic_label(); 26 | 27 | // interesting testcases 28 | my_dynasm!(ops 29 | // no args 30 | ; ret 31 | // reserved keyword 32 | ; loop 5 33 | // immediate 34 | ; ret 16 35 | // register 36 | ; inc rax 37 | // memory ref 38 | ; inc DWORD [16] 39 | ; inc DWORD [test] 40 | ; inc DWORD [rax*2] 41 | ; inc DWORD [rax*3] 42 | ; inc DWORD [rax*4] 43 | ; inc DWORD [rax*5] 44 | ; inc DWORD [rax*8] 45 | ; inc DWORD [rax*9] 46 | ; inc DWORD [rax + 16] 47 | ; inc DWORD [rax*8 + 16] 48 | ; inc DWORD [rax + rbx] 49 | ; inc DWORD [rax + rbx + 16] 50 | ; inc DWORD [rax*8 + rbx + 16] 51 | // special memoryref cases 52 | ; inc DWORD [rsp] 53 | ; inc DWORD [r12] 54 | ; inc DWORD [rsp + rax] 55 | ; inc DWORD [rax + rsp] 56 | ; inc DWORD [rbp] 57 | ; inc DWORD [r13] 58 | ; inc DWORD [rbp + 16] 59 | ; inc DWORD [rbp*8] 60 | ; inc DWORD [rip] 61 | ; inc DWORD [rip + 16] 62 | ; inc DWORD [1*r15] 63 | ; inc DWORD [NOSPLIT 1*r15] 64 | ; inc DWORD [2*r15] 65 | ; inc DWORD [NOSPLIT 2*r15] 66 | ; inc DWORD [1*r15 + r14] 67 | ; inc DWORD [rax + rax + rax + rax + rbx] 68 | // weird registers 69 | ; xchg al, ah 70 | ; xchg al, dil 71 | // register-specific forms 72 | ; adc rax, 5 73 | // multi arg forms 74 | ; mov rax, rbx 75 | ; mov rax, [rbx] 76 | ; mov [rbx], rax 77 | ; mov rax, 1 78 | ; mov BYTE [rax], 1 79 | ; imul rax, rbx, 1 80 | ; imul rax, [rbx], 1 81 | // prefixes 82 | ; fs inc DWORD [rax] 83 | ; lock fs inc DWORD [rax] 84 | ; rep stosq 85 | ; inc DWORD [eax] 86 | // really long instructions 87 | ; fs imul r9w, [r10d*8 + r11d + 0x66778899], 0x1122 88 | ; fs imul r9, [edi*8 + r11d + 0x66778899], 0x11223344 89 | ; fs mov r9, QWORD 0x1122334455667788 90 | ; fs movabs rax, 0x1122334455667788 91 | // funky syntax features 92 | ; inc BYTE [rax] 93 | ; inc WORD [rax] 94 | ; inc DWORD [rax] 95 | ; inc QWORD [rax] 96 | ; inc QWORD [BYTE rax + 0] 97 | ; inc QWORD [DWORD rax + 0] 98 | // very odd memoryrefs 99 | ; mov rax, [rbx + rbx * 3 + 2 + c + rax + d] 100 | ; mov rax, [rbx - 4] 101 | // labels 102 | ; a: // local 103 | ; -> b: // global 104 | ; => label // dynamic. note the lack of a trailing :. this is due to : being a valid symbol within expressions that does not occur in any other normal Rust expr contexts. 105 | // jumps 106 | ; jmp b 108 | ; jmp => label 109 | // rip relative stuff 110 | ; lea rax, [->b] 111 | // dynamic registers 112 | ; inc Rb(1) 113 | ; inc Rh(5) 114 | ; inc Rw(1) 115 | ; inc Rd(1) 116 | ; inc Rq(1) 117 | ; mov Rb(7), [Rq(3)*4 + rax] 118 | ; fsub Rf(5), st0 119 | // other register families 120 | ; mov cr1, rax 121 | ; mov dr1, rax 122 | ; mov rax, cr1 123 | ; mov rax, dr1 124 | ; pop fs 125 | ; movmskps eax, xmm7 126 | ; movd mm7, eax 127 | ; movd eax, mm7 128 | ; fcomp st0 129 | // VEX/XOP instructions 130 | ; andn rax, rcx, rdx 131 | ; andn r8, r9, r10 132 | ; bextr rax, rbx, 1 133 | ; vaddpd xmm0, xmm1, [rax] 134 | // VSIB addressing 135 | ; vgatherqpd ymm1, QWORD [ymm15 + rsi + 0x11112222], ymm8 136 | ; vgatherqpd ymm1, QWORD [NOSPLIT rsi + ymm15 + 0x11112222], ymm8 137 | ; vgatherqpd ymm1, QWORD [ymm15*8 + rsi + 0x11112222], ymm8 138 | // 4 argument instructions 139 | ; vfmaddss xmm0, xmm1, xmm2, xmm3 140 | // directives 141 | ; string: 142 | //; .bytes "Hello world!\0".bytes() 143 | ); 144 | 145 | // typemap support 146 | #[allow(dead_code)] 147 | struct Test { 148 | foo: i32, 149 | bar: u32 150 | } 151 | #[allow(dead_code)] 152 | struct SmallTest { 153 | foo: i8, 154 | bar: u8 155 | } 156 | let mut test_array = [Test {foo: 1, bar: 2}, Test {foo: 3, bar: 4}, Test {foo: 5, bar: 6}]; 157 | let test_array = &mut test_array; 158 | let mut test_single = Test {foo: 7, bar: 8}; 159 | let test_single = &mut test_single; 160 | my_dynasm!(ops 161 | ; mov rax, AWORD MutPointer!(test_array) 162 | ; mov ebx, 2 163 | ; vgatherqpd ymm13, QWORD rax => f64[ymm15], ymm14 164 | ; inc DWORD rax => Test[2].bar 165 | ; inc DWORD rax => Test[BYTE 2] 166 | ; inc DWORD rax => Test[BYTE 0].bar 167 | ; inc DWORD rax => Test[2 + rbx].bar 168 | ; inc DWORD rax => Test[rbx].bar 169 | ; inc DWORD rax => Test[rbx] 170 | ; inc DWORD rax => Test[rbx + 2] 171 | ; inc DWORD rax => SmallTest[4*rbx + 2].bar 172 | ; inc DWORD rax => SmallTest[BYTE 2*rbx + 2].bar 173 | ; mov rax, AWORD MutPointer!(test_single) 174 | ; inc DWORD rax => Test.bar 175 | ); 176 | 177 | // dynasm in expr position 178 | match 1 { 179 | 0 => (), 180 | _ => my_dynasm!(ops; inc rax) 181 | } 182 | 183 | // fixups 184 | let start = ops.offset(); 185 | my_dynasm!( ops 186 | ; inc rbx 187 | ); 188 | let end = ops.offset(); 189 | ops.alter(|ops| { 190 | ops.goto(start); 191 | my_dynasm!(ops 192 | ; inc r12 193 | ); 194 | ops.check(end).unwrap(); 195 | }).unwrap(); 196 | 197 | #[allow(unused_variables)] 198 | let index = ops.offset(); 199 | 200 | my_dynasm!(ops 201 | ; mov eax, 10203040 202 | ; ret 203 | ); 204 | 205 | let buf = ops.finalize().unwrap(); 206 | 207 | println!("Generated assembly:"); 208 | for i in buf.iter() { 209 | print!("{:02x }", i); 210 | } 211 | println!(""); 212 | 213 | #[cfg(target_arch="x86_64")] 214 | { 215 | let func: extern "C" fn() -> i64 = unsafe { std::mem::transmute(buf.ptr(index)) }; 216 | println!("assembled function result: {}", func() ); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /testing/tests/x64_0_relocations.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use dynasmrt::dynasm; 4 | use dynasmrt::{DynasmApi, DynasmLabelApi, DynasmError, LabelKind, DynamicLabel}; 5 | 6 | 7 | #[test] 8 | fn test_local_jumps() { 9 | let mut ops = dynasmrt::VecAssembler::::new(0); 10 | 11 | dynasm!(ops 12 | ; .arch x64 13 | ; jmp BYTE >foo 14 | ; inc rax 15 | ; foo: 16 | ; dec rax 17 | ; jmp BYTE bar 20 | ; inc rax 21 | ; bar: 22 | ; dec rax 23 | ; jmp bar 26 | ; jmp >foo 27 | ; jmp foo 31 | ; inc rax 32 | ; foo: 33 | ; dec rax 34 | ; jmp BYTE bar 37 | ; inc rax 38 | ; bar: 39 | ; dec rax 40 | ; jmp BYTE close 43 | ; close: 44 | ; jmp ::new(0); 67 | 68 | dynasm!(ops 69 | ; .arch x64 70 | ; jmp BYTE ->minusone 71 | ; jmp BYTE ->plustwo 72 | ; jmp BYTE ->minusone 73 | ;->start: 74 | ; inc rax 75 | ;->plusone: 76 | ; inc rbx 77 | ;->plustwo: 78 | ; jmp BYTE ->end 79 | ; jmp BYTE ->start 80 | ;->minustwo: 81 | ; inc rcx 82 | ;->minusone: 83 | ; inc rdx 84 | ;->end: 85 | ; jmp BYTE ->plusone 86 | ; jmp BYTE ->minustwo 87 | ; jmp BYTE ->plusone 88 | ); 89 | 90 | let output = ops.finalize().unwrap(); 91 | 92 | for i in &output { 93 | print!("\\x{:02x}", i); 94 | } 95 | println!(""); 96 | 97 | let expected: &[u8] = b"\ 98 | \xEB\x11\xEB\x08\xEB\x0D\x48\xFF\xC0\x48\xFF\xC3\xEB\x08\xEB\xF6\ 99 | \x48\xFF\xC1\x48\xFF\xC2\xEB\xF1\xEB\xF6\xEB\xED"; 100 | assert!(&output == expected); 101 | } 102 | 103 | 104 | #[test] 105 | fn test_dynamic_jumps() { 106 | let mut ops = dynasmrt::VecAssembler::::new(0); 107 | let minustwo = ops.new_dynamic_label(); 108 | let minusone = ops.new_dynamic_label(); 109 | let end = ops.new_dynamic_label(); 110 | let start = ops.new_dynamic_label(); 111 | let plusone = ops.new_dynamic_label(); 112 | let plustwo = ops.new_dynamic_label(); 113 | 114 | dynasm!(ops 115 | ; .arch x64 116 | ; jmp BYTE =>minusone 117 | ; jmp BYTE =>plustwo 118 | ; jmp BYTE =>minusone 119 | ;=>start 120 | ; inc rax 121 | ;=>plusone 122 | ; inc rbx 123 | ;=>plustwo 124 | ; jmp BYTE =>end 125 | ; jmp BYTE =>start 126 | ;=>minustwo 127 | ; inc rcx 128 | ;=>minusone 129 | ; inc rdx 130 | ;=>end 131 | ; jmp BYTE =>plusone 132 | ; jmp BYTE =>minustwo 133 | ; jmp BYTE =>plusone 134 | ); 135 | 136 | let output = ops.finalize().unwrap(); 137 | 138 | for i in &output { 139 | print!("\\x{:02x}", i); 140 | } 141 | println!(""); 142 | 143 | let expected: &[u8] = b"\ 144 | \xEB\x11\xEB\x08\xEB\x0D\x48\xFF\xC0\x48\xFF\xC3\xEB\x08\xEB\xF6\ 145 | \x48\xFF\xC1\x48\xFF\xC2\xEB\xF1\xEB\xF6\xEB\xED"; 146 | assert!(&output == expected); 147 | } 148 | 149 | 150 | #[test] 151 | fn test_all_jumps() { 152 | let mut ops = dynasmrt::VecAssembler::::new(0); 153 | 154 | let label = ops.new_dynamic_label(); 155 | 156 | // please never do this 157 | dynasm!(ops 158 | ; .arch x64 159 | ; jmp >label 160 | ; inc rax 161 | ; jmp ->label 162 | ; inc rbx 163 | ; jmp =>label 164 | ; inc rcx 165 | ; label: 166 | ; inc rdx 167 | ;->label: 168 | ; inc rbp 169 | ;=>label 170 | ; inc rsp 171 | ; jmp