├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── HACKING.md ├── LICENSE.md ├── Makefile ├── README.md ├── asmdot ├── __init__.py ├── arch │ ├── __init__.py │ ├── arm │ │ ├── __init__.py │ │ ├── arch.py │ │ ├── data.txt │ │ └── tests.py │ ├── mips │ │ ├── __init__.py │ │ ├── arch.py │ │ ├── data.txt │ │ └── tests.py │ ├── testsource.py │ └── x86 │ │ ├── __init__.py │ │ ├── arch.py │ │ ├── data.txt │ │ └── tests.py ├── ast.py ├── emit.py ├── helpers.py ├── metadata.ini ├── options.py ├── requirements.txt └── setup.py ├── languages ├── c │ ├── README.md │ ├── generate.py │ ├── include │ │ ├── arm.h │ │ ├── mips.h │ │ └── x86.h │ ├── src │ │ ├── arm.c │ │ ├── mips.c │ │ └── x86.c │ └── test │ │ ├── arm.c │ │ ├── greatest.h │ │ ├── mips.c │ │ └── x86.c ├── cpp │ ├── README.md │ ├── generate.py │ ├── src │ │ ├── arm.cpp │ │ ├── mips.cpp │ │ └── x86.cpp │ └── test │ │ ├── arm.cpp │ │ ├── catch.hpp │ │ ├── main.cpp │ │ ├── mips.cpp │ │ └── x86.cpp ├── csharp │ ├── Asm.Net.Tests │ │ ├── Arm.cs │ │ ├── Asm.Net.Tests.csproj │ │ ├── BufferWriter.cs │ │ ├── Mips.cs │ │ ├── Program.cs │ │ └── X86.cs │ ├── Asm.Net.sln │ ├── Asm.Net │ │ ├── Arm.cs │ │ ├── Arm.g.cs │ │ ├── Asm.Net.csproj │ │ ├── Helpers.cs │ │ ├── Mips.cs │ │ ├── Mips.g.cs │ │ ├── X86.cs │ │ └── X86.g.cs │ ├── Common.props │ ├── README.md │ └── generate.py ├── go │ ├── README.md │ ├── arm │ │ ├── arm.go │ │ └── arm_test.go │ ├── generate.py │ ├── go.mod │ ├── mips │ │ ├── mips.go │ │ └── mips_test.go │ └── x86 │ │ ├── x86.go │ │ └── x86_test.go ├── haskell │ ├── Asm.cabal │ ├── README.md │ ├── Setup.hs │ ├── generate.py │ ├── src │ │ └── Asm │ │ │ ├── Arm.hs │ │ │ ├── Internal │ │ │ ├── Arm.hs │ │ │ ├── Mips.hs │ │ │ └── X86.hs │ │ │ ├── Mips.hs │ │ │ └── X86.hs │ └── test │ │ ├── Asm │ │ ├── ArmSpec.hs │ │ ├── MipsSpec.hs │ │ └── X86Spec.hs │ │ └── Spec.hs ├── javascript │ ├── README.md │ ├── generate.py │ ├── package.json │ ├── src │ │ ├── arm.ts │ │ ├── index.ts │ │ ├── mips.ts │ │ └── x86.ts │ ├── test │ │ ├── arm.test.ts │ │ ├── helpers.ts │ │ ├── mips.test.ts │ │ └── x86.test.ts │ └── tsconfig.json ├── nim │ ├── README.md │ ├── asmdot.nimble │ ├── asmdot │ │ ├── arm.nim │ │ ├── mips.nim │ │ ├── private │ │ │ ├── arm.nim │ │ │ ├── helpers.nim │ │ │ ├── mips.nim │ │ │ └── x86.nim │ │ └── x86.nim │ ├── generate.py │ └── test │ │ ├── testall.nim │ │ ├── testarm.nim │ │ ├── testmips.nim │ │ └── testx86.nim ├── ocaml │ ├── README.md │ ├── dune-project │ ├── generate.py │ ├── src │ │ ├── arm.ml │ │ ├── dune │ │ ├── mips.ml │ │ └── x86.ml │ └── test │ │ ├── dune │ │ ├── testarm.ml │ │ ├── testmips.ml │ │ └── testx86.ml ├── python │ ├── README.md │ ├── asm │ │ ├── __init__.py │ │ ├── arm.py │ │ ├── mips.py │ │ └── x86.py │ ├── generate.py │ ├── pytest.ini │ ├── setup.py │ └── tests │ │ ├── __init__.py │ │ ├── test_arm.py │ │ ├── test_mips.py │ │ └── test_x86.py └── rust │ ├── Cargo.toml │ ├── README.md │ ├── generate.py │ ├── src │ ├── generated │ │ ├── arm.rs │ │ ├── mips.rs │ │ ├── mod.rs │ │ └── x86.rs │ └── lib.rs │ └── tests │ ├── arm.rs │ ├── mips.rs │ └── x86.rs └── templates ├── __init__.py ├── arch.py ├── lang.py └── testsource.py /.gitattributes: -------------------------------------------------------------------------------- 1 | catch.hpp linguist-vendored 2 | greatest.h linguist-vendored 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries without extensions. 2 | * 3 | !*/ 4 | !*.* 5 | !dune 6 | !dune-project 7 | 8 | # IDE artifacts. 9 | .merlin 10 | .vs/ 11 | .vscode/settings.json 12 | *cache*/ 13 | 14 | # Build artifacts. 15 | _build/ 16 | bin/ 17 | build/ 18 | dist/ 19 | node_modules/ 20 | obj/ 21 | target/ 22 | BenchmarkDotNet.Artifacts/ 23 | *.o 24 | *.out 25 | *.exe 26 | Cargo.lock 27 | package-lock.json 28 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: Run current file", 6 | "type": "python", 7 | "request": "launch", 8 | "program": "${file}", 9 | "env": { 10 | "PYTHONPATH": "${workspaceRoot}" 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.enabled": true, 3 | "python.linting.mypyEnabled": true, 4 | 5 | "files.exclude": { 6 | "**/.git": true, 7 | "**/.vs": true, 8 | "**/*cache*": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Generate all sources", 6 | "type": "shell", 7 | "command": "make emit" 8 | }, 9 | { 10 | "label": "Build all", 11 | "type": "shell", 12 | "command": "make build" 13 | }, 14 | { 15 | "label": "Test all", 16 | "type": "shell", 17 | "command": "make test" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | Hacking 2 | ======= 3 | 4 | Python 3.6 is required to run the scripts, since the scripts make heavy use of the new typing 5 | features added in this release. A type-aware linter such as [mypy](http://mypy-lang.org/) is 6 | thus recommended for editing. 7 | 8 | ## Structure 9 | - `asmdot/`: Sources of the library. 10 | * `arch/`: Definition of the supported architectures. 11 | - `__init__.py`: `Architecture` class, and utilities. 12 | - `testsource.py`: `TestSource` class, and utilities. 13 | 14 | - `arm/`: Definition of the ARM architecture. 15 | * `__init__.py`: 16 | * `arch.py`: Parsing `data.txt` to AST. 17 | * `data.txt`: Definition of all known ARM instructions. 18 | * `tests.py`: Definition of various ARM tests. 19 | - `...` 20 | 21 | * `__init__.py`: Utilities and useful exported symbols. 22 | * `ast.py`: Definition of the AST (functions, types, etc). 23 | * `emit.py`: Base definition of `Emitter`, which transforms the AST into source code. 24 | * `helpers.py`: Miscellaneous helpers. 25 | * `options.py`: `Options` class, which is inherited by other classes. 26 | 27 | * `setup.py`: Package definition. 28 | * `metadata.ini`: Metadata about the package. 29 | * `requirements.txt`: Requirements to run the source generator. 30 | 31 | - `languages/`: Supported languages and generated code. 32 | * `c/`: Generated C code, and C emitter. 33 | - `include/`, `src/`, `test/`: Generated code. 34 | - `generate.py`: C source and tests emitter. 35 | - `README.md`: C-specific documentation. 36 | * `...` 37 | 38 | - `templates/`: Beginner-friendly templates for creating your own... 39 | * `arch.py`: ... architecture parser. 40 | * `lang.py`: ... language emitter. 41 | * `testsource.py`: ... tests. 42 | 43 | - `HACKING.md`: Documentation on the code itself. 44 | 45 | 46 | ## Adding emitters 47 | Emitters are Python modules of the following form, and are used to extend 48 | the build process. They are used to automate the generation of native code 49 | in various languages. 50 | 51 | All they have to do is transform the simple AST into source code. 52 | 53 | Please see the [languages](./languages) directory for some example emitters. Additionally, a 54 | [beginner-friendly template is available](./templates/lang.py) to easily get 55 | started creating a custom emitter. 56 | 57 | The following rules shall be followed when emitting source code: 58 | 1. Conventions of the programming language shall be followed. 59 | 2. The code shall be readable by a human reader, and shall contain documentation comments. 60 | 3. Only the `\n` character shall be written at the end of each line. 61 | 62 | 63 | ## Using the AST 64 | The AST is defined in the [ast.py](./asmdot/ast.py) file, and mostly consists of 65 | the following elements. 66 | 67 | #### Function 68 | Every instruction is translated into a function, that itself has a `initname`, `fullname`, 69 | `body`, as well as parameters. `fullname` only changes when a single instruction can take 70 | multiple parameters, and should be used instead of `initname` when a language does not 71 | support overloading. For example, `mov` is the `initname` of both `mov_r32_r32` and `mov_r64_r64`. 72 | 73 | Additionally, the `name` property returns the value returned by `Emitter.get_function_name`, 74 | which can be used to transform what a function is named in the scope of the emitter. 75 | 76 | #### Declaration 77 | Declarations are top-level elements used by the functions generated for an architecture. 78 | 79 | Currently, the only existing declaration is the `Enumeration`, which contains enumeration 80 | members and can be translated to many languages. 81 | 82 | #### Statement 83 | Many kinds of statements exist, and they typically make up the whole body of a function. They 84 | contain other informations, such as variable names, or `Expression`s. 85 | 86 | #### Expression 87 | Once again, many kinds of expressions exist. For example, `Binary` expressions have an 88 | operator, as well as left and right operands. There are also `Ternary` expressions, 89 | `Call` expressions, etc. In most cases, a translation from an `Expression` tree to a string 90 | is extremely easy. 91 | 92 | 93 | #### Example 94 | Manipulation of the IR AST can be seen in the [Rust code generation script](./languages/rust/generate.py). 95 | 96 | ## Utilities 97 | Many utilities are provided to make scripts easier to create and reason about. 98 | 99 | If you see anything you don't know about, please file an issue. In the meantime, 100 | that section is a **TODO**. 101 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Grégoire Geis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CXX = g++ 3 | PY = python3.6 4 | 5 | BUILD_DIR = build 6 | CXX_FLAGS = -Wall -Wextra -Werror -Wno-unused-function -Wno-constant-conversion 7 | ADDITIONAL_FLAGS = 8 | 9 | export PYTHONPATH = . 10 | 11 | 12 | # MISC 13 | # 14 | main: build-c emit 15 | 16 | all: emit-all build test 17 | 18 | clean: 19 | rm -rf "$(BUILD_DIR)/" 20 | 21 | 22 | # EMITTING 23 | # 24 | emit-include: 25 | $(PY) languages/c/generate.py -o languages/c/include/ --no-prefix --as-header --no-tests $(ADDITIONAL_FLAGS) 26 | 27 | emit-c: 28 | $(PY) languages/c/generate.py -o languages/c/ $(ADDITIONAL_FLAGS) 29 | 30 | emit-cpp: 31 | $(PY) languages/cpp/generate.py -o languages/cpp/ $(ADDITIONAL_FLAGS) 32 | 33 | emit-csharp: 34 | $(PY) languages/csharp/generate.py -o languages/csharp/ $(ADDITIONAL_FLAGS) 35 | 36 | emit-go: 37 | $(PY) languages/go/generate.py -o languages/go/ $(ADDITIONAL_FLAGS) 38 | 39 | emit-haskell: 40 | $(PY) languages/haskell/generate.py -o languages/haskell/ $(ADDITIONAL_FLAGS) 41 | 42 | emit-javascript: 43 | $(PY) languages/javascript/generate.py -o languages/javascript/ $(ADDITIONAL_FLAGS) 44 | 45 | emit-nim: 46 | $(PY) languages/nim/generate.py -o languages/nim/ $(ADDITIONAL_FLAGS) 47 | 48 | emit-ocaml: 49 | $(PY) languages/ocaml/generate.py -o languages/ocaml/ $(ADDITIONAL_FLAGS) 50 | 51 | emit-python: 52 | $(PY) languages/python/generate.py -o languages/python/ $(ADDITIONAL_FLAGS) 53 | 54 | emit-rust: 55 | $(PY) languages/rust/generate.py -o languages/rust/ $(ADDITIONAL_FLAGS) 56 | 57 | emit: emit-include emit-c emit-cpp emit-csharp emit-go emit-haskell emit-javascript emit-nim emit-ocaml emit-python emit-rust 58 | 59 | 60 | # BUILDING 61 | # 62 | build-c: 63 | # Write C files 64 | $(PY) languages/c/generate.py --no-tests --as-header -o "$(BUILD_DIR)" 65 | 66 | # Build object files 67 | cd "$(BUILD_DIR)" && $(CC) -O3 -xc -c arm.h -c mips.h -c x86.h 68 | 69 | # Link the whole thing 70 | cd "$(BUILD_DIR)" && $(CC) -shared -o asmdot.a arm.o mips.o x86.o 71 | 72 | build-cpp: emit-cpp 73 | cd languages/cpp/src/ && $(CXX) $(CXX_FLAGS) -std=c++11 -c *.cpp 74 | 75 | build-csharp: emit-csharp 76 | cd languages/csharp/Asm.Net/ && dotnet build 77 | 78 | build-go: emit-go 79 | cd languages/go/ && go build 80 | 81 | build-haskell: emit-haskell 82 | cd languages/haskell/ && cabal build 83 | 84 | build-nim: emit-nim 85 | cd languages/nim/ && nimble debug 86 | 87 | build-ocaml: emit-ocaml 88 | cd languages/ocaml/ && opam build 89 | 90 | build-rust: emit-rust 91 | cd languages/rust/ && cargo build 92 | 93 | build: build-c build-cpp build-csharp build-go build-haskell build-nim build-ocaml build-rust 94 | 95 | 96 | # TESTING 97 | # 98 | test-c: emit-c 99 | for arch in arm mips x86 ; do \ 100 | $(CC) -g languages/c/test/$$arch.c -o languages/c/test/$$arch && languages/c/test/$$arch ; \ 101 | done 102 | 103 | test-cpp: emit-cpp 104 | $(CXX) $(CXX_FLAGS) -std=c++14 -g languages/cpp/test/main.cpp -o languages/cpp/test/main && languages/cpp/test/main 105 | 106 | test-csharp: emit-csharp 107 | cd languages/csharp/Asm.Net.Tests/ && dotnet test 108 | 109 | test-go: emit-go 110 | cd languages/go/ && go test ./... 111 | 112 | test-haskell: emit-haskell 113 | cd languages/haskell/ && cabal test 114 | 115 | test-javascript: emit-javascript 116 | cd languages/javascript/ && npm test 117 | 118 | test-nim: emit-nim 119 | cd languages/nim/ && nimble test 120 | 121 | test-ocaml: emit-ocaml 122 | cd languages/ocaml/ && opam test 123 | 124 | test-python: emit-python 125 | cd languages/python/ && $(PY) -m pytest 126 | 127 | test-rust: emit-rust 128 | cd languages/rust/ && cargo test 129 | 130 | test: test-c test-csharp test-go test-haskell test-javascript test-nim test-ocaml test-python test-rust 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ASM. 2 | ==== 3 | 4 | Providing an extensible Python framework for building a **fast, zero-copy** assembler. 5 | 6 | 7 | ## History and goals 8 | 9 | This project originally aimed to create a fast, minimalist and unopinionated assembler in C 10 | that could live in a single file, and support multiple architectures. 11 | 12 | Thus, a Python library was built to transform various instructions from different architectures 13 | into a simple, common AST that supports bitwise and logical expressions, basic flow control 14 | and variables into C code. 15 | Since code would be generated automatically, other options such as naming conventions and parameter 16 | types could be easily modified when generating it. 17 | 18 | However, I soon realized that, since a complete AST was built, it could be easily to extend this 19 | process to not only support C, but also other programming languages. 20 | At first, the goal was thus to produce bindings to the C API, which is *very* efficient; but since a 21 | complete AST was built anyway, and that a mechanism already existed to distinguish source files and 22 | include files, I decided to make the whole process available in different languages. 23 | 24 | As such, ASM. was born. **Parsers** transform data files that define instructions in various architectures 25 | to an AST, which is then transformed by **emitters** into source code in various programming languages. 26 | 27 | ### Goals and non-goals 28 | - **ASM. is a lightweight assembler library. It is designed to be as simple as possible.** 29 | - **ASM. has no support for labels or macros**: developers are expected to build their own 30 | interface on top of the provided functions. 31 | - **ASM. is not a binary, it's a library**. You cannot use it directly. 32 | - **ASM. has no built-in parser**: if you want an assembler that works with arbitrary strings, use 33 | [Keystone](https://www.keystone-engine.org). 34 | - **ASM. has different instructions for different architectures**: if you want a common 35 | interface for all architectures, use [GNU Lightning](https://www.gnu.org/software/lightning) 36 | or [libjit](https://www.gnu.org/software/libjit). 37 | 38 | 39 | ## Usage 40 | 41 | ### Using `make` 42 | A [Makefile](./Makefile) is provided to automate most tasks, including generating sources, 43 | as well as building and testing every generated library. 44 | 45 | The `emit`, `build` and `test` recipes are made available, and invoke all language-specific 46 | recipes that are defined. To execute tasks in a language-specific manner, the recipes 47 | `emit-lang`, `build-lang`, and `test-lang` are also available, where `lang` is either one 48 | of these values: 49 | - `c` (uses any C compiler). 50 | - `cpp` (uses any C++ compiler). 51 | - `csharp` (uses `dotnet`). 52 | - `go` (uses `go`). 53 | - `haskell` (uses `cabal`, **doesn't compile; help needed**). 54 | - `javascript` (uses `npm`). 55 | - `nim` (uses `nimble`). 56 | - `ocaml` (uses `dune`, **doesn't compile; help needed**). 57 | - `python` (uses `pytest`). 58 | - `rust` (uses `cargo`). 59 | 60 | ### Generating the sources 61 | Each language directory contains a `generate.py` file, which can be directly invoked 62 | from the command line. 63 | 64 | Here is an example output of the C generation script: 65 | ``` 66 | usage: generate.py [-h] [-ns] [-nt] [-o output-dir/] [-v] [-np] [-ah] 67 | [-cc CALLING-CONVENTION] 68 | 69 | Generate ASM. sources. 70 | 71 | optional arguments: 72 | -h, --help Show the help message. 73 | 74 | -ns, --no-sources Do not generate sources. 75 | -nt, --no-tests Do not generate tests. 76 | -be, --big-endian Use big-endian instead of little-endian. 77 | 78 | -o output-dir/, --output output-dir/ 79 | Change the output directory (default: directory of 80 | calling emitter). 81 | -v, --verbose Increase verbosity (can be given multiple times to 82 | increase it further). 83 | 84 | C: 85 | -np, --no-prefix Do not prefix function names by their architecture. 86 | -ah, --as-header Generate headers instead of regular files. 87 | 88 | -cc CALLING-CONVENTION, --calling-convention CALLING-CONVENTION 89 | Specify the calling convention of generated functions. 90 | ``` 91 | ### Using the C API 92 | ```c 93 | #include "./x86.h" 94 | 95 | void* buffer = malloc(0xff); 96 | void* origin = buffer; 97 | 98 | inc_r32(&buffer, eax); 99 | ret(&buffer); 100 | 101 | free(origin); 102 | ``` 103 | 104 | ### Using the Nim API 105 | ```nim 106 | # The Nim language goes very well with ASM., thanks to its UFCS support. 107 | import asmdot/x86 108 | 109 | var 110 | bytes = newSeqOfCap[byte](10) 111 | buf = addr bytes[0] 112 | 113 | buf.inc(eax) 114 | buf.ret() 115 | ``` 116 | 117 | ### Using the Python API 118 | ```python 119 | from asm.x86 import Reg32, X86Assembler 120 | 121 | asm = X86Assembler(10) 122 | 123 | asm.inc_r32(Reg32.eax) 124 | asm.ret() 125 | ``` 126 | 127 | ### Using the Rust API 128 | ```rust 129 | use asm::x86::{Register32, X86Assembler}; 130 | 131 | let mut buf = vec!(); 132 | 133 | buf.inc_r32(Register32::EAX)?; 134 | buf.ret()?; 135 | ``` 136 | 137 | 138 | ## Installing 139 | We're not there yet, but if you want to experiment with the project or contribute, 140 | you're welcome to clone it and play around. 141 | 142 | ```bash 143 | # Clone project 144 | git clone https://github.com/71/asmdot.git 145 | 146 | # Get dependencies 147 | python -m pip install -r asmdot/requirements.txt 148 | 149 | # Play around 150 | PYTHONPATH=. && python languages/c/generate.py --help 151 | ``` 152 | 153 | 154 | ## Status 155 | 156 | ### Architectures 157 | * [ARM](./asmdot/arch/arm): **WIP**. 158 | * [MIPS](./asmdot/arch/mips): **WIP**. 159 | * [X86](./asmdot/arch/x86): **WIP**. 160 | 161 | ### Sources 162 | * [C](./languages/c) 163 | * [C++](./languages/cpp) 164 | * [C#](./languages/csharp) 165 | * [Go](./languages/go) 166 | * [Haskell](./languages/haskell) 167 | * [JavaScript](./languages/javascript) 168 | * [Nim](./languages/nim) 169 | * [OCaml](./languages/ocaml) 170 | * [Python](./languages/python) 171 | * [Rust](./languages/rust) 172 | 173 | 174 | ## Docs 175 | The directory of each language (list available above) contains the documentation for 176 | said language. Furthermore, a [hacking](./HACKING.md) guide is available for those who want 177 | to extend or improve ASM. 178 | 179 | 180 | ## License 181 | All the content of the repository is [MIT-licensed](./LICENSE.md), except the [data](./src/data) 182 | directory which is [Unlicensed](http://unlicense.org). 183 | -------------------------------------------------------------------------------- /asmdot/__init__.py: -------------------------------------------------------------------------------- 1 | from logzero import logger 2 | from typing import Type 3 | 4 | from .emit import * 5 | 6 | def handle_command_line(force: bool = False): 7 | """Handles the provided command like arguments. 8 | 9 | If @force is `True`, this function will be executed regardless if whether 10 | the current module is the main module.""" 11 | 12 | def decorator(emitter_class: Type[Emitter]): 13 | from .arch.arm import ArmArchitecture 14 | from .arch.mips import MipsArchitecture 15 | from .arch.x86 import X86Architecture 16 | 17 | from .helpers import create_default_argument_parser, \ 18 | emitter_hooks, \ 19 | ensure_directory_exists, \ 20 | parent, debug, info, ASMLOGGER 21 | 22 | import inspect 23 | 24 | # Ensure we're supposed to handle command line 25 | caller_frame = inspect.stack()[1] 26 | 27 | if not force: 28 | caller_module_name = caller_frame[0].f_globals['__name__'] 29 | 30 | if caller_module_name != '__main__': 31 | return 32 | 33 | # Got this far, we can continue peacefully 34 | import logging, os.path 35 | 36 | architectures = [ ArmArchitecture(), MipsArchitecture(), X86Architecture() ] 37 | 38 | # Set up verbosity 39 | args, _ = create_default_argument_parser().parse_known_args() 40 | verbosity = args.verbose 41 | 42 | if verbosity == 0: 43 | ASMLOGGER.setLevel(logging.FATAL) 44 | elif verbosity == 1: 45 | ASMLOGGER.setLevel(logging.ERROR) 46 | elif verbosity == 2: 47 | ASMLOGGER.setLevel(logging.WARN) 48 | elif verbosity == 3: 49 | ASMLOGGER.setLevel(logging.INFO) 50 | else: 51 | ASMLOGGER.setLevel(logging.DEBUG) 52 | 53 | # Load all arguments 54 | parser = create_default_argument_parser() 55 | 56 | emitter_class.register(parser) 57 | 58 | for arch in architectures: 59 | arch.__class__.register(parser) # type: ignore 60 | 61 | args = parser.parse_args() 62 | 63 | if args.help: 64 | # Stop execution and show help message. 65 | # We only do this now so that the help message also contains usage of arguments 66 | # registered by loaded architectures / sources / languages. 67 | parser.print_help() 68 | quit(0) 69 | 70 | output_dir = args.output or \ 71 | parent(caller_frame.filename) or \ 72 | os.getcwd() 73 | 74 | 75 | # Translate architectures one by one 76 | for arch in architectures: 77 | # Initialize architecture and test source 78 | arch.initialize(args) 79 | 80 | declarations = list( arch.declarations ) 81 | functions = list( arch.functions ) 82 | 83 | declarations.sort(key=lambda x: x.name) 84 | functions.sort(key=lambda x: x.fullname) 85 | 86 | test_source = arch.tests 87 | 88 | emitter : Emitter = emitter_class(args, arch.name) 89 | emitter.functions = functions 90 | emitter.declarations = declarations 91 | 92 | debug('Translating', arch.name.upper(), '.') 93 | 94 | # Ready output files 95 | output_path = os.path.join(output_dir, emitter.filename) 96 | 97 | if not args.no_tests and emitter.test_filename: 98 | test_path = os.path.join(output_dir, emitter.test_filename) 99 | else: 100 | test_path = None 101 | 102 | # Translate source 103 | if not args.no_sources: 104 | ensure_directory_exists(output_path) 105 | 106 | with open(output_path, 'w', newline='\n') as output, emitter_hooks(emitter, output): 107 | emitter.write_header() 108 | 109 | for decl in arch.declarations: 110 | emitter.write_decl(decl) 111 | 112 | emitter.write_separator() 113 | 114 | for fun in arch.functions: 115 | emitter.write_function(fun) 116 | 117 | emitter.write_footer() 118 | 119 | info('Translated', arch.name.upper(), ' sources.') 120 | 121 | # Translate tests 122 | if test_path and test_source: 123 | ensure_directory_exists(test_path) 124 | 125 | test_source.declarations = declarations 126 | test_source.functions = functions 127 | 128 | with open(test_path, 'w', newline='\n') as output, emitter_hooks(emitter, output): 129 | emitter.write_test_header() 130 | 131 | for test_case in test_source.test_cases: 132 | emitter.write_test(test_case) 133 | 134 | emitter.write_test_footer() 135 | 136 | info('Translated', arch.name.upper(), ' tests.') 137 | 138 | return decorator 139 | -------------------------------------------------------------------------------- /asmdot/arch/__init__.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from argparse import ArgumentParser, Namespace 3 | from parsy import regex, eof, seq, Parser 4 | from typing import Callable, IO, Iterator, List 5 | 6 | from ..ast import Declaration, Function, TestCase, TestCaseCall 7 | from ..helpers import relative, parse, ws, end 8 | from ..options import Options 9 | from .testsource import TestCases, TestSource 10 | 11 | 12 | Declarations = Iterator[Declaration] 13 | Functions = Iterator[Function] 14 | 15 | class Architecture(ABC, Options): 16 | """An architecture parser.""" 17 | 18 | @property 19 | @abstractmethod 20 | def name(self) -> str: 21 | """Returns the name of the architecture.""" 22 | pass 23 | 24 | 25 | @staticmethod 26 | def register(parser: ArgumentParser) -> None: 27 | """Registers the architecture, allowing it to add command-line parameters.""" 28 | pass 29 | 30 | def initialize(self, args: Namespace) -> None: 31 | """Initializes the architecture using the provided command-line arguments.""" 32 | super().initialize_options(args, self.name) 33 | 34 | 35 | @property 36 | @abstractmethod 37 | def tests(self) -> TestSource: 38 | """Returns the tests for the architecture.""" 39 | pass 40 | 41 | @property 42 | def declarations(self) -> Declarations: 43 | """Returns an iterator over all non-instruction declarations for the architecture.""" 44 | pass 45 | 46 | @property 47 | @abstractmethod 48 | def functions(self) -> Functions: 49 | """Returns an iterator over all functions for the architecture.""" 50 | pass 51 | -------------------------------------------------------------------------------- /asmdot/arch/arm/__init__.py: -------------------------------------------------------------------------------- 1 | from .arch import * 2 | -------------------------------------------------------------------------------- /asmdot/arch/arm/data.txt: -------------------------------------------------------------------------------- 1 | adc cond 0 0 I 0 1 0 1 S Rn Rd shifter 2 | add cond 0 0 I 0 1 0 0 S Rn Rd shifter 3 | and cond 0 0 I 0 0 0 0 S Rn Rd shifter 4 | eor cond 0 0 I 0 0 0 1 S Rn Rd shifter 5 | orr cond 0 0 I 1 1 0 0 S Rn Rd shifter 6 | rsb cond 0 0 I 0 0 1 1 S Rn Rd shifter 7 | rsc cond 0 0 I 0 1 1 1 S Rn Rd shifter 8 | sbc cond 0 0 I 0 1 1 0 S Rn Rd shifter 9 | sub cond 0 0 I 0 0 1 0 S Rn Rd shifter 10 | 11 | bkpt 1 1 1 0 0 0 0 1 0 0 1 0 topimm 0 1 1 1 botimm 12 | 13 | b cond 1 0 1 L simm24 14 | bic cond 0 0 I 1 1 1 0 S Rn Rd shifter 15 | blx cond 0 0 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 Rm 16 | bx cond 0 0 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 Rm 17 | bxj cond 0 0 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 0 Rm 18 | 19 | blxun 1 1 1 1 1 0 1 H simm24 20 | 21 | clz cond 0 0 0 1 0 1 1 0 1 1 1 1 Rd 1 1 1 1 0 0 0 1 Rm 22 | cmn cond 0 0 I 1 0 1 1 1 Rn 0 0 0 0 shifter 23 | cmp cond 0 0 I 1 0 1 0 1 Rn 0 0 0 0 shifter 24 | cpy cond 0 0 0 1 1 0 1 0 0 0 0 0 Rd 0 0 0 0 0 0 0 0 Rm 25 | 26 | cps 1 1 1 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 mode 27 | cpsie 1 1 1 1 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 iflags 0 0 0 0 0 0 28 | cpsid 1 1 1 1 0 0 0 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 iflags 0 0 0 0 0 0 29 | cpsie_mode 1 1 1 1 0 0 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 iflags 0 mode 30 | cpsid_mode 1 1 1 1 0 0 0 1 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 iflags 0 mode 31 | 32 | ldc cond 1 1 0 P_U N W 1 Rn CRd cpnum ofs8 33 | ldm cond 1 0 0 P_U B_W 1 Rn reglist 34 | 35 | ldr cond 0 1 I P_U 0 W 1 Rn Rd addrmode 36 | ldrb cond 0 1 I P_U 1 W 1 Rn Rd addrmode 37 | ldrbt cond 0 1 I 0 U 1 1 1 Rn Rd addrmode 38 | ldrd cond 0 0 0 P_U I W 0 Rn Rd addrmode1 1 1 0 1 addrmode2 39 | ldrex cond 0 0 0 1 1 0 0 1 Rn Rd 1 1 1 1 1 0 0 1 1 1 1 1 40 | ldrh cond 0 0 0 P_U I W 1 Rn Rd addrmode1 1 0 1 1 addrmode2 41 | ldrsb cond 0 0 0 P_U I W 1 Rn Rd addrmode1 1 1 0 1 addrmode2 42 | ldrsh cond 0 0 0 P_U I W 1 Rn Rd addrmode1 1 1 1 1 addrmode2 43 | ldrt cond 0 1 I 0 U 0 1 1 Rn Rd addrmode 44 | 45 | cdp cond 1 1 1 0 cpopcode1 CRn CRd cpnum opcode2 0 CRm 46 | mcr cond 1 1 1 0 opcode1 0 CRn Rd cpnum opcode2 1 CRm 47 | mrc cond 1 1 1 0 opcode1 1 CRn Rd cpnum opcode2 1 CRm 48 | 49 | mcrr cond 1 1 0 0 0 1 0 0 Rn Rd cpnum opcode CRm 50 | mla cond 0 0 0 0 0 0 1 S Rd Rn Rs 1 0 0 1 Rm 51 | mov cond 0 0 I 1 1 0 1 S 0 0 0 0 Rd shifter 52 | mrrc cond 1 1 0 0 0 1 0 1 Rn Rd cpnum opcode CRm 53 | mrs cond 0 0 0 1 0 R 0 0 1 1 1 1 Rd 0 0 0 0 0 0 0 0 0 0 0 0 54 | mul cond 0 0 0 0 0 0 0 S Rd 0 0 0 0 Rs 1 0 0 1 Rm 55 | mvn cond 0 0 I 1 1 1 1 S 0 0 0 0 Rd shifter 56 | 57 | msr#_imm cond 0 0 1 1 0 R 1 0 fieldmask 1 1 1 1 rotateimm imm8 58 | msr#_reg cond 0 0 0 1 0 R 1 0 fieldmask 1 1 1 1 0 0 0 0 0 0 0 0 Rm 59 | 60 | pkhbt cond 0 1 1 0 1 0 0 0 Rn Rd shiftimm 0 0 1 Rm 61 | pkhtb cond 0 1 1 0 1 0 0 0 Rn Rd shiftimm 1 0 1 Rm 62 | 63 | pld 1 1 1 1 0 1 I 1 U 1 0 1 Rn 1 1 1 1 addrmode 64 | 65 | qadd cond 0 0 0 1 0 0 0 0 Rn Rd 0 0 0 0 0 1 0 1 Rm 66 | qadd16 cond 0 1 1 0 0 0 1 0 Rn Rd 1 1 1 1 0 0 0 1 Rm 67 | qadd8 cond 0 1 1 0 0 0 1 0 Rn Rd 1 1 1 1 1 0 0 1 Rm 68 | qaddsubx cond 0 1 1 0 0 0 1 0 Rn Rd 1 1 1 1 0 0 1 1 Rm 69 | qdadd cond 0 0 0 1 0 1 0 0 Rn Rd 0 0 0 0 0 1 0 1 Rm 70 | qdsub cond 0 0 0 1 0 1 1 0 Rn Rd 0 0 0 0 0 1 0 1 Rm 71 | qsub cond 0 0 0 1 0 0 1 0 Rn Rd 0 0 0 0 0 1 0 1 Rm 72 | qsub16 cond 0 1 1 0 0 0 1 0 Rn Rd 1 1 1 1 0 1 1 1 Rm 73 | qsub8 cond 0 1 1 0 0 0 1 0 Rn Rd 1 1 1 1 1 1 1 1 Rm 74 | qsubaddx cond 0 1 1 0 0 0 1 0 Rn Rd 1 1 1 1 0 1 0 1 Rm 75 | 76 | rev cond 0 1 1 0 1 0 1 1 1 1 1 1 Rd 1 1 1 1 0 0 1 1 Rm 77 | rev16 cond 0 1 1 0 1 0 1 1 1 1 1 1 Rd 1 1 1 1 1 0 1 1 Rm 78 | revsh cond 0 1 1 0 1 1 1 1 1 1 1 1 Rd 1 1 1 1 1 0 1 1 Rm 79 | 80 | rfe 1 1 1 1 1 0 0 P_U 0 W 1 Rn 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 81 | 82 | sadd16 cond 0 1 1 0 0 0 0 1 Rn Rd 1 1 1 1 0 0 0 1 Rm 83 | sadd8 cond 0 1 1 0 0 0 0 1 Rn Rd 1 1 1 1 1 0 0 1 Rm 84 | saddsubx cond 0 1 1 0 0 0 0 1 Rn Rd 1 1 1 1 0 0 1 1 Rm 85 | 86 | sel cond 0 1 1 0 1 0 0 0 Rn Rd 1 1 1 1 1 0 1 1 Rm 87 | 88 | setendbe 1 1 1 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 89 | setendle 1 1 1 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 90 | 91 | shadd16 cond 0 1 1 0 0 0 1 1 Rn Rd 1 1 1 1 0 0 0 1 Rm 92 | shadd8 cond 0 1 1 0 0 0 1 1 Rn Rd 1 1 1 1 1 0 0 1 Rm 93 | shaddsubx cond 0 1 1 0 0 0 1 1 Rn Rd 1 1 1 1 0 0 1 1 Rm 94 | shsub16 cond 0 1 1 0 0 0 1 1 Rn Rd 1 1 1 1 0 1 1 1 Rm 95 | shsub8 cond 0 1 1 0 0 0 1 1 Rn Rd 1 1 1 1 1 1 1 1 Rm 96 | shsubaddx cond 0 1 1 0 0 0 1 1 Rn Rd 1 1 1 1 0 1 0 1 Rm 97 | 98 | smlabb cond 0 0 0 1 0 0 0 0 Rd Rn Rs 1 0 0 0 Rm 99 | smlabt cond 0 0 0 1 0 0 0 0 Rd Rn Rs 1 0 1 0 Rm 100 | smlatb cond 0 0 0 1 0 0 0 0 Rd Rn Rs 1 1 0 0 Rm 101 | smlatt cond 0 0 0 1 0 0 0 0 Rd Rn Rs 1 1 1 0 Rm 102 | smlad cond 0 1 1 1 0 0 0 0 Rd Rn Rs 0 0 X 1 Rm 103 | 104 | smlal cond 0 0 0 0 1 1 1 S RdHi RdLo Rs 1 0 0 1 Rm 105 | smlalbb cond 0 0 0 1 0 1 0 0 RdHi RdLo Rs 1 0 0 0 Rm 106 | smlalbt cond 0 0 0 1 0 1 0 0 RdHi RdLo Rs 1 0 1 0 Rm 107 | smlaltb cond 0 0 0 1 0 1 0 0 RdHi RdLo Rs 1 1 0 0 Rm 108 | smlaltt cond 0 0 0 1 0 1 0 0 RdHi RdLo Rs 1 1 1 0 Rm 109 | smlald cond 0 1 1 1 0 1 0 0 RdHi RdLo Rs 0 0 X 1 Rm 110 | 111 | smlawb cond 0 0 0 1 0 0 1 0 Rd Rn Rs 1 0 0 0 Rm 112 | smlawt cond 0 0 0 1 0 0 1 0 Rd Rn Rs 1 1 0 0 Rm 113 | smlsd cond 0 1 1 1 0 0 0 0 Rd Rn Rs 0 1 X 1 Rm 114 | smlsld cond 0 1 1 1 0 1 0 0 RdHi RdLo Rs 0 1 X 1 Rm 115 | smmla cond 0 1 1 1 0 1 0 1 Rd Rn Rs 0 0 R 1 Rm 116 | smmls cond 0 1 1 1 0 1 0 1 Rd Rn Rs 1 1 R 1 Rm 117 | smmul cond 0 1 1 1 0 1 0 1 Rd 1 1 1 1 Rs 0 0 R 1 Rm 118 | smuad cond 0 1 1 1 0 0 0 0 Rd 1 1 1 1 Rs 0 0 X 1 Rm 119 | smulbb cond 0 0 0 1 0 1 1 0 Rd 0 0 0 0 Rs 1 0 0 0 Rm 120 | smulbt cond 0 0 0 1 0 1 1 0 Rd 0 0 0 0 Rs 1 0 1 0 Rm 121 | smultb cond 0 0 0 1 0 1 1 0 Rd 0 0 0 0 Rs 1 1 0 0 Rm 122 | smultt cond 0 0 0 1 0 1 1 0 Rd 0 0 0 0 Rs 1 1 1 0 Rm 123 | smull cond 0 0 0 0 1 1 0 S RdHi RdLo Rs 1 0 0 1 Rm 124 | smulwb cond 0 0 0 1 0 0 1 0 Rd 0 0 0 0 Rs 1 0 1 0 Rm 125 | smulwt cond 0 0 0 1 0 0 1 0 Rd 0 0 0 0 Rs 1 1 1 0 Rm 126 | smusd cond 0 1 1 1 0 0 0 0 Rd 1 1 1 1 Rs 0 1 X 1 Rm 127 | 128 | srs 1 1 1 1 1 0 0 P_U 1 W 0 1 1 0 1 0 0 0 0 0 1 0 1 0 0 0 mode 129 | 130 | ssat cond 0 1 1 0 0 1 0 1 satimm Rd shiftimm+sh 0 1 Rm 131 | ssat16 cond 0 1 1 0 1 0 1 0 satimm Rd 1 1 1 1 0 0 1 1 Rm 132 | ssub16 cond 0 1 1 0 0 0 0 1 Rn Rd 1 1 1 1 0 1 1 1 Rm 133 | ssub8 cond 0 1 1 0 0 0 0 1 Rn Rd 1 1 1 1 1 1 1 1 Rm 134 | ssubaddx cond 0 1 1 0 0 0 0 1 Rn Rd 1 1 1 1 0 1 0 1 Rm 135 | 136 | stc cond 1 1 0 P_U N W 0 Rn CRd cpnum ofs8 137 | stm cond 1 0 0 P_U G_W 0 Rn reglist 138 | str cond 0 1 I P_U 0 W 0 Rn Rd addrmode 139 | str#b cond 0 1 I P_U 1 W 0 Rn Rd addrmode 140 | str#bt cond 0 1 I 0 U 1 1 0 Rn Rd addrmode 141 | str#d cond 0 0 0 P_U I W 0 Rn Rd addrmode1 1 1 1 1 addrmode2 142 | strex cond 0 0 0 1 1 0 0 0 Rn Rd 1 1 1 1 1 0 0 1 Rm 143 | str#h cond 0 0 0 P_U I W 0 Rn Rd addrmode1 1 0 1 1 addrmode2 144 | str#t cond 0 1 I 0 U 0 1 0 Rn Rd addrmode 145 | 146 | swi cond 1 1 1 1 imm24 147 | 148 | swp cond 0 0 0 1 0 0 0 0 Rn Rd 0 0 0 0 1 0 0 1 Rm 149 | swpb cond 0 0 0 1 0 1 0 0 Rn Rd 0 0 0 0 1 0 0 1 Rm 150 | 151 | sxtab cond 0 1 1 0 1 0 1 0 Rn Rd rotate 0 0 0 1 1 1 Rm 152 | sxtab16 cond 0 1 1 0 1 0 0 0 Rn Rd rotate 0 0 0 1 1 1 Rm 153 | sxtah cond 0 1 1 0 1 0 1 1 Rn Rd rotate 0 0 0 1 1 1 Rm 154 | sxtb cond 0 1 1 0 1 0 1 0 1 1 1 1 Rd rotate 0 0 0 1 1 1 Rm 155 | sxtb16 cond 0 1 1 0 1 0 0 0 1 1 1 1 Rd rotate 0 0 0 1 1 1 Rm 156 | sxth cond 0 1 1 0 1 0 1 1 1 1 1 1 Rd rotate 0 0 0 1 1 1 Rm 157 | 158 | teq cond 0 0 I 1 0 0 1 1 Rn 0 0 0 0 shifter 159 | tst cond 0 0 I 1 0 0 0 1 Rn 0 0 0 0 shifter 160 | 161 | uadd16 cond 0 1 1 0 0 1 0 1 Rn Rd 1 1 1 1 0 0 0 1 Rm 162 | uadd8 cond 0 1 1 0 0 1 0 1 Rn Rd 1 1 1 1 1 0 0 1 Rm 163 | uaddsubx cond 0 1 1 0 0 1 0 1 Rn Rd 1 1 1 1 0 0 1 1 Rm 164 | uhadd16 cond 0 1 1 0 0 1 1 1 Rn Rd 1 1 1 1 0 0 0 1 Rm 165 | uhadd8 cond 0 1 1 0 0 1 1 1 Rn Rd 1 1 1 1 1 0 0 1 Rm 166 | uhaddsubx cond 0 1 1 0 0 1 1 1 Rn Rd 1 1 1 1 0 0 1 1 Rm 167 | uhsub16 cond 0 1 1 0 0 1 1 1 Rn Rd 1 1 1 1 0 1 1 1 Rm 168 | uhsub8 cond 0 1 1 0 0 1 1 1 Rn Rd 1 1 1 1 1 1 1 1 Rm 169 | uhsubaddx cond 0 1 1 0 0 1 1 1 Rn Rd 1 1 1 1 0 1 0 1 Rm 170 | 171 | umaal cond 0 0 0 0 0 1 0 0 RdHi RdLo Rs 1 0 0 1 Rm 172 | umlal cond 0 0 0 0 1 0 1 S RdHi RdLo Rs 1 0 0 1 Rm 173 | umull cond 0 0 0 0 1 0 0 S RdHi RdLo Rs 1 0 0 1 Rm 174 | 175 | uqadd16 cond 0 1 1 0 0 1 1 0 Rn Rd 1 1 1 1 0 0 0 1 Rm 176 | uqadd8 cond 0 1 1 0 0 1 1 0 Rn Rd 1 1 1 1 1 0 0 1 Rm 177 | uqaddsubx cond 0 1 1 0 0 1 1 0 Rn Rd 1 1 1 1 0 0 1 1 Rm 178 | uqsub16 cond 0 1 1 0 0 1 1 0 Rn Rd 1 1 1 1 0 1 1 1 Rm 179 | uqsub8 cond 0 1 1 0 0 1 1 0 Rn Rd 1 1 1 1 1 1 1 1 Rm 180 | uqsubaddx cond 0 1 1 0 0 1 1 0 Rn Rd 1 1 1 1 0 1 0 1 Rm 181 | 182 | usad8 cond 0 1 1 1 1 0 0 0 Rd 1 1 1 1 Rs 0 0 0 1 Rm 183 | usada8 cond 0 1 1 1 1 0 0 0 Rd Rn Rs 0 0 0 1 Rm 184 | usat cond 0 1 1 0 1 1 1 satimm5 Rd shiftimm+sh 0 1 Rm 185 | usat16 cond 0 1 1 0 1 1 1 0 satimm Rd 1 1 1 1 0 0 1 1 Rm 186 | usub16 cond 0 1 1 0 0 1 0 1 Rn Rd 1 1 1 1 0 1 1 1 Rm 187 | usub8 cond 0 1 1 0 0 1 0 1 Rn Rd 1 1 1 1 1 1 1 1 Rm 188 | usubaddx cond 0 1 1 0 0 1 0 1 Rn Rd 1 1 1 1 0 1 0 1 Rm 189 | 190 | uxtab cond 0 1 1 0 1 1 1 0 Rn Rd rotate 0 0 0 1 1 1 Rm 191 | uxtab16 cond 0 1 1 0 1 1 0 0 Rn Rd rotate 0 0 0 1 1 1 Rm 192 | uxtah cond 0 1 1 0 1 1 1 1 Rn Rd rotate 0 0 0 1 1 1 Rm 193 | uxtb cond 0 1 1 0 1 1 1 0 1 1 1 1 Rd rotate 0 0 0 1 1 1 Rm 194 | uxtb16 cond 0 1 1 0 1 1 0 0 1 1 1 1 Rd rotate 0 0 0 1 1 1 Rm 195 | uxth cond 0 1 1 0 1 1 1 1 1 1 1 1 Rd rotate 0 0 0 1 1 1 Rm 196 | -------------------------------------------------------------------------------- /asmdot/arch/arm/tests.py: -------------------------------------------------------------------------------- 1 | from ..testsource import * # pylint: disable=W0614 2 | 3 | class ArmTestSource(TestSource): 4 | 5 | @property 6 | def name(self) -> str: 7 | return 'arm' 8 | 9 | @property 10 | def test_cases(self) -> TestCases: 11 | yield TestCase('should encode single cps instruction', [ 12 | self.make_call('cps', 'Mode::USR') 13 | ], bytearray(b'\x10\x00\x02\xf1')) 14 | -------------------------------------------------------------------------------- /asmdot/arch/mips/__init__.py: -------------------------------------------------------------------------------- 1 | from .arch import * 2 | -------------------------------------------------------------------------------- /asmdot/arch/mips/arch.py: -------------------------------------------------------------------------------- 1 | from ...ast import * # pylint: disable=W0614 2 | from .. import * # pylint: disable=W0614 3 | 4 | from functools import reduce 5 | 6 | class MipsArchitecture(Architecture): 7 | 8 | @property 9 | def name(self) -> str: 10 | return 'mips' 11 | 12 | @property 13 | def tests(self): 14 | from .tests import MipsTestSource 15 | 16 | return MipsTestSource() 17 | 18 | @property 19 | def declarations(self) -> Declarations: 20 | mips_registers = [ 21 | 'zero', 'at', 'v0', 'v1', 22 | 'a0', 'a1', 'a2', 'a3', 23 | 't0', 't1', 't2', 't3', 24 | 't4', 't5', 't6', 't7', 25 | 's0', 's1', 's2', 's3', 26 | 's4', 's5', 's6', 's7', 27 | 't8', 't9', 'k0', 'k1', 28 | 'gp', 'sp', 'fp', 'ra' 29 | ] 30 | yield DistinctType(TYPE_MIPS_REG, 'A Mips register.', [ Constant(n, i) for i, n in enumerate(mips_registers) ]) 31 | 32 | @property 33 | def functions(self) -> Functions: 34 | def cast(var: Union[str, Expression], bits: int) -> Binary: 35 | """Casts a variable to a fixed number of bits.""" 36 | if isinstance(var, str): 37 | var = Var(var) 38 | return Binary(OP_BITWISE_AND, var, Literal((1 << bits) - 1, TYPE_U32)) 39 | 40 | with open(relative('data.txt'), 'r') as input: 41 | for line in input: 42 | line = line.strip() 43 | 44 | if not line.startswith('#') and len(line) > 0: 45 | chunks = line.split(' ') 46 | mode = chunks[0] 47 | fullname = chunks[1] 48 | u_idx = fullname.find('_') 49 | name = fullname if u_idx == -1 else fullname[:u_idx] 50 | 51 | if mode == 'R': 52 | # Type R 53 | # (opcode: 6b) (rs: 5b) (rt: 5b) (rd: 5b) (shift: 5b) (funct: 6b) 54 | # OP rd, rs, rt 55 | 56 | func = Function(name, [ param('rd', TYPE_MIPS_REG, TYPE_U32), 57 | param('rs', TYPE_MIPS_REG, TYPE_U32), 58 | param('rt', TYPE_MIPS_REG, TYPE_U32), 59 | param('shift', TYPE_U8, TYPE_U32) ], 60 | fullname=fullname) 61 | 62 | opcode = int(chunks[2], 16) 63 | fcnt = int(chunks[3], 16) 64 | 65 | vals = [ 66 | Literal((opcode << 26) | (fcnt & 0x3f), TYPE_U32), 67 | Binary(OP_SHL, cast(Var('rs'), 5), Literal(21, TYPE_U32)), 68 | Binary(OP_SHL, cast(Var('rt'), 5), Literal(16, TYPE_U32)), 69 | Binary(OP_SHL, cast(Var('rd'), 5), Literal(11, TYPE_U32)), 70 | Binary(OP_SHL, cast(Var('shift'), 5), Literal(6, TYPE_U32)) 71 | ] 72 | 73 | func += Set(TYPE_U32, reduce(lambda a, b: Binary(OP_BITWISE_OR, a, b), vals)) 74 | 75 | yield func 76 | 77 | elif mode == 'RI': 78 | # type RI 79 | # mode for branches 80 | # (opcode: 6b) (register source: 5b) (funct: 5b) (imm: 16b) 81 | func = Function(name, [ param('rs', TYPE_MIPS_REG, TYPE_U32), 82 | param('target', TYPE_U16, TYPE_U32) ], 83 | fullname=fullname) 84 | 85 | opcode = int(chunks[2], 16) 86 | fcnt = int(chunks[3], 16) 87 | 88 | vals = [ 89 | Literal(opcode << 26, TYPE_U32), 90 | Binary(OP_SHL, cast(Var('rs'), 5), Literal(16, TYPE_U32)), 91 | cast(Binary(OP_SHR, Var('target'), Literal(2, TYPE_U32)), 16) 92 | ] 93 | 94 | func += Set(TYPE_U32, reduce(lambda a, b: Binary(OP_BITWISE_OR, a, b), vals)) 95 | 96 | yield func 97 | 98 | elif mode == 'J': 99 | # Type J 100 | # (opcode: 6b) (addr: 26b) 101 | # OP address 102 | # address has least two bits truncated and 4 topmost bits truncated too 103 | 104 | func = Function(name, [ param('address', TYPE_U32) ], 105 | fullname=fullname) 106 | 107 | opcode = int(chunks[2], 16) 108 | 109 | code = Literal(opcode << 26, TYPE_U32) 110 | truncated = cast(Binary(OP_SHR, Var('address'), Literal(2, TYPE_U32)), 26) 111 | 112 | func += Set(TYPE_U32, Binary(OP_BITWISE_OR, code, truncated)) 113 | 114 | yield func 115 | 116 | else: 117 | # Type I 118 | # (opcode: 6b) (rs: 5b) (rt: 5b) (imm: 16b) 119 | # OP rt, IMM(rs) 120 | # OP rs, rt, IMM # for beq 121 | 122 | func = Function(name, [ param('rs', TYPE_MIPS_REG, TYPE_U32), 123 | param('rt', TYPE_MIPS_REG, TYPE_U32), 124 | param('imm', TYPE_U16, TYPE_U32)], 125 | fullname=fullname) 126 | 127 | opcode = int(chunks[2], 16) 128 | immediate = cast(Var('imm'), 16) 129 | 130 | if name in ['beq', 'bne', 'blez', 'bgtz']: 131 | immediate = Binary(OP_SHR, immediate, 2) 132 | 133 | vals = [ 134 | Literal(opcode << 26, TYPE_U32), 135 | Binary(OP_SHL, cast(Var('rs'), 5), Literal(21, TYPE_U32)), 136 | Binary(OP_SHL, cast(Var('rt'), 5), Literal(16, TYPE_U32)), 137 | immediate 138 | ] 139 | 140 | func += Set(TYPE_U32, reduce(lambda a, b: Binary(OP_BITWISE_OR, a, b), vals)) 141 | 142 | yield func 143 | -------------------------------------------------------------------------------- /asmdot/arch/mips/data.txt: -------------------------------------------------------------------------------- 1 | # 2 | 3 | # R instructions (opcode rs rt rd shift funct) 4 | # format: 5 | 6 | R sll 0x00 0x0 7 | R movci 0x00 0x1 8 | R srl 0x00 0x2 9 | R sra 0x00 0x3 10 | R sllv_r 0x00 0x4 11 | R srlv 0x00 0x6 12 | R srav 0x00 0x7 13 | R jr 0x00 0x8 14 | R jalr_r 0x00 0x9 15 | R movz 0x00 0xa 16 | R movn 0x00 0xb 17 | R syscall 0x00 0xc 18 | R breakpoint 0x00 0xd 19 | R sync 0x00 0xf 20 | R mfhi 0x00 0x10 21 | R mthi 0x00 0x11 22 | R mflo 0x00 0x12 23 | R dsllv_r 0x00 0x14 24 | R dsrlv 0x00 0x16 25 | R dsrav 0x00 0x17 26 | R mult 0x00 0x18 27 | R multu 0x00 0x19 28 | R div 0x00 0x1a 29 | R divu 0x00 0x1b 30 | R dmult 0x00 0x1c 31 | R dmultu 0x00 0x1d 32 | R ddiv 0x00 0x1e 33 | R ddivu 0x00 0x1f 34 | R add 0x00 0x20 35 | R addu 0x00 0x21 36 | R sub 0x00 0x22 37 | R subu 0x00 0x23 38 | R and 0x00 0x24 39 | R or 0x00 0x25 40 | R xor 0x00 0x26 41 | R nor 0x00 0x27 42 | R slt 0x00 0x2a 43 | R sltu 0x00 0x2b 44 | R dadd 0x00 0x2c 45 | R daddu 0x00 0x2d 46 | R dsub 0x00 0x2e 47 | R dsubu 0x00 0x2f 48 | R tge 0x00 0x30 49 | R tgeu 0x00 0x31 50 | R tlt 0x00 0x32 51 | R tltu 0x00 0x33 52 | R teq 0x00 0x34 53 | R tne 0x00 0x36 54 | R dsll 0x00 0x38 55 | R dslr 0x00 0x3a 56 | R dsra 0x00 0x3b 57 | R mhc0 0x10 0x00 58 | 59 | # RI instructions (opcode reg addr) 60 | # format: 61 | RI btlz 0x01 0x0 62 | RI bgez 0x01 0x1 63 | RI bltzl 0x01 0x2 64 | RI bgezl 0x01 0x3 65 | RI sllv_ri 0x01 0x4 66 | RI tgei 0x01 0x8 67 | RI jalr_ri 0x01 0x9 68 | RI tlti 0x01 0xa 69 | RI tltiu 0x01 0xb 70 | RI teqi 0x01 0xc 71 | RI tnei 0x01 0xe 72 | RI bltzal 0x01 0x10 73 | RI bgezal 0x01 0x11 74 | RI bltzall 0x01 0x12 75 | RI bgezall 0x01 0x13 76 | RI dsllv_ri 0x01 0x14 77 | RI synci 0x01 0x1f 78 | 79 | # I instructions (opcode rs rt IMM) 80 | # format: 81 | I addi 0x08 82 | I addiu 0x09 83 | I andi 0x0c 84 | I beq 0x04 85 | I blez 0x06 86 | I bne 0x05 87 | I lw 0x23 88 | I lbu 0x24 89 | I lhu 0x25 90 | I lui 0x0f 91 | I ori 0x0d 92 | I sb 0x28 93 | I sh 0x29 94 | I slti 0x0a 95 | I sltiu 0x0b 96 | I sw 0x2b 97 | 98 | # J instructions (opcode addr) 99 | # format: 100 | J j 0x02 101 | J jal 0x03 102 | -------------------------------------------------------------------------------- /asmdot/arch/mips/tests.py: -------------------------------------------------------------------------------- 1 | from ..testsource import * # pylint: disable=W0614 2 | 3 | class MipsTestSource(TestSource): 4 | 5 | @property 6 | def name(self) -> str: 7 | return 'mips' 8 | 9 | @property 10 | def test_cases(self) -> TestCases: 11 | yield TestCase('should assemble single addi instruction', [ 12 | self.make_call('addi', 'Reg::T1', 'Reg::T2', '0\'uint8') 13 | ], bytearray(b'\x00\x00\x49\x21')) 14 | -------------------------------------------------------------------------------- /asmdot/arch/testsource.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from argparse import ArgumentParser, Namespace 3 | from parsy import string 4 | from typing import Iterator, List 5 | 6 | from ..ast import Declaration, DistinctType, Enumeration, Function, IrType, all_types 7 | from ..ast import TestCase, TestCaseArgument, TestCaseCall, ArgConstant, ArgEnumMember, ArgInteger 8 | from ..helpers import parse 9 | from ..options import Options 10 | 11 | 12 | TestCases = Iterator[TestCase] 13 | 14 | class TestSource(ABC, Options): 15 | """A source of tests for an architecture.""" 16 | declarations: List[Declaration] 17 | functions: List[Function] 18 | 19 | 20 | @property 21 | @abstractmethod 22 | def test_cases(self) -> TestCases: 23 | """Returns an iterator over all test cases for the architecture.""" 24 | pass 25 | 26 | 27 | def make_argument(self, repr: str) -> TestCaseArgument: 28 | """Creates a `TestCaseArgument`, given its string representation. 29 | 30 | Supported syntax: 31 | Enum::Member 32 | Enum.Member 33 | 10'u8 34 | 0xff'u16 35 | 0b111'reg32 36 | """ 37 | 38 | @parse('\'', r'[\w\d]+') 39 | def literal_type(_, typ_: str) -> IrType: 40 | typ = typ_.lower() 41 | 42 | for ty in all_types: 43 | if ty.id.lower() == typ: 44 | return ty 45 | 46 | raise KeyError() 47 | 48 | @parse(r'\w+', r'(\.|\:\:)', r'\w+') 49 | def enum_member(enum_: str, _, member_: str) -> ArgEnumMember: 50 | enum = enum_.lower() 51 | member = member_.lower() 52 | 53 | for e in self.declarations: 54 | if isinstance(e, Enumeration) and e.type.id.lower() == enum: 55 | for m in e.members + e.additional_members: 56 | if m.name.lower() != member and m.fullname.lower() != member: 57 | continue 58 | 59 | return ArgEnumMember(e, m) 60 | elif isinstance(e, DistinctType) and e.type.id.lower() == enum: 61 | for c in e.constants: 62 | if c.name.lower() != member: 63 | continue 64 | 65 | return ArgConstant(e, c) 66 | 67 | raise KeyError() 68 | 69 | @parse(r'\d+') 70 | def dec_literal(n: str) -> int: 71 | return int(n) 72 | 73 | @parse('0b', r'[abcdefABCDEF0123456789]') 74 | def hex_literal(_, n: str) -> int: 75 | return int(n, base=16) 76 | 77 | @parse('0x', r'[01]') 78 | def bin_literal(_, n: str) -> int: 79 | return int(n, base=2) 80 | 81 | @parse(dec_literal | hex_literal | bin_literal, literal_type) 82 | def literal(n: int, typ: IrType) -> ArgInteger: 83 | return ArgInteger(typ, n) 84 | 85 | return (enum_member | literal).parse(repr) 86 | 87 | def make_call(self, name: str, *args) -> TestCaseCall: 88 | """Creates a `TestCaseCall`, given the full name of the function to invoke and its arguments.""" 89 | for fn in self.functions: 90 | if fn.fullname != name: 91 | continue 92 | 93 | return TestCaseCall(fn, [ self.make_argument(arg) for arg in args ]) 94 | 95 | raise KeyError() 96 | -------------------------------------------------------------------------------- /asmdot/arch/x86/__init__.py: -------------------------------------------------------------------------------- 1 | from .arch import * 2 | -------------------------------------------------------------------------------- /asmdot/arch/x86/arch.py: -------------------------------------------------------------------------------- 1 | from ...ast import * # pylint: disable=W0614 2 | from .. import * # pylint: disable=W0614 3 | 4 | from logzero import logger 5 | from parsy import regex, string, ParseError 6 | 7 | # The x86 parser works by recognizing specific instructions (for instance, 8 | # an instruction with no operand, or an instruction with one register operand), 9 | # and generating one or many (when multiple sizes can be accepted) functions 10 | # that correspond to that instruction. 11 | # 12 | # To make all this easier, we also have a set of parsers that do not return functions, 13 | # but instead return other values that can then be aggregated into a full instruction. 14 | 15 | 16 | # Helpers 17 | 18 | @no_type_check # mypy wants a return statement :( 19 | def regtype_for_size(size: int) -> IrType: 20 | assert size in (8, 16, 32, 64, 128) 21 | 22 | if size == 8: return TYPE_X86_R8 23 | if size == 16: return TYPE_X86_R16 24 | if size == 32: return TYPE_X86_R32 25 | if size == 64: return TYPE_X86_R64 26 | if size == 128: return TYPE_X86_R128 27 | 28 | @no_type_check # mypy wants a return statement :( 29 | def immtype_for_size(size: int) -> IrType: 30 | assert size in (8, 16, 32, 64) 31 | 32 | if size == 8: return TYPE_I8 33 | if size == 16: return TYPE_I16 34 | if size == 32: return TYPE_I32 35 | if size == 64: return TYPE_I64 36 | 37 | 38 | def emit_opcode(opcode: Union[int, List[int], Expression]) -> Iterator[Statement]: 39 | if isinstance(opcode, int): 40 | yield Set(TYPE_BYTE, Literal(opcode, TYPE_BYTE)) 41 | elif isinstance(opcode, tuple(expressionClasses)): 42 | yield Set(TYPE_BYTE, opcode) 43 | else: 44 | for opc in opcode: 45 | yield Set(TYPE_BYTE, Literal(opc, TYPE_BYTE)) 46 | 47 | def emit_prefix(sra: bool, bits: int) -> Iterator[Statement]: 48 | assert bits in (16, 64) 49 | 50 | v: Expression = Literal( 0x66 if bits == 16 else 0x48 , TYPE_BYTE) 51 | 52 | if sra: 53 | v = Binary(OP_ADD, v, Call(BUILTIN_X86_PREFIX, [ Var('operand') ])) 54 | 55 | return emit_opcode(v) 56 | 57 | def pregister(name: str, size: int) -> Parameter: 58 | return param(name, regtype_for_size(size)) 59 | 60 | def pimm(name: str, size: int) -> Parameter: 61 | return param(name, immtype_for_size(size)) 62 | 63 | 64 | # Parser 65 | 66 | def get_x86_parser(opts: Options): 67 | mnemo = regex(r'[a-zA-Z]+') 68 | mnemos = mnemo.sep_by(string('/')) 69 | 70 | opcode = regex(r'[0-9a-fA-F]{1,2}').map(lambda x: int(x, base=16)) 71 | hyphen = string('-') 72 | 73 | funcs = {} 74 | 75 | def parse_instr(*args): 76 | """Indicates a function that parses an instruction. 77 | Informations such as opcode and name will be added automatically.""" 78 | 79 | def decorator(func): 80 | funcs[func.__name__] = func 81 | 82 | @parse(opcodes, mnemos, ws, *args) 83 | def inner(opcodes: List[int], names: List[str], _, *args): 84 | for fun in func(opcodes, *args): 85 | for name in names: 86 | fullname = name + fun.fullname 87 | yield fun.with_name(name, fullname) 88 | 89 | return inner 90 | 91 | return decorator 92 | 93 | opcodes = opcode.sep_by(hyphen) << ws 94 | 95 | def get_size_parser(prefix: str): 96 | @parse(prefix + r'\d{1,3}(-\d{2,3})?') 97 | def inner(s: str) -> List[int]: 98 | b = len(s) - len(s.lstrip('abcdefghijklmnopqrstuvwxyz')) 99 | i = s.find('-') 100 | 101 | if i == -1: 102 | return [ int(s[b:]) ] 103 | else: 104 | min, max = int(s[b:i]), int(s[i+1:]) 105 | 106 | return [ n for n in [8, 16, 32, 64, 128] if min <= n <= max ] 107 | 108 | return inner 109 | 110 | rsize = get_size_parser('r') 111 | rmsize = get_size_parser('rm') 112 | immsize = get_size_parser('(imm|rel)') 113 | 114 | @parse(opcodes, mnemos) 115 | def instr_nop(opcodes: List[int], names: List[str]) -> Functions: 116 | opc = emit_opcode(opcodes) 117 | 118 | for name in names: 119 | f = Function(name, []) 120 | f.body.extend(opc) 121 | 122 | yield f 123 | 124 | @parse_instr(rsize) 125 | def instr_single_reg(opcodes: List[int], sizes: List[int]) -> Functions: 126 | sra = True 127 | 128 | for size in sizes: 129 | f = Function('', [ pregister('operand', size) ], fullname=f'_r{size}') 130 | 131 | if size == 16: 132 | f += emit_prefix(sra, 16) 133 | elif size == 64: 134 | f += emit_prefix(sra, 64) 135 | else: 136 | f += Conditional( 137 | Binary(OP_GT, Var('operand'), Literal(7, TYPE_BYTE)), 138 | Block(list(emit_opcode(0x41))) 139 | ) 140 | 141 | if len(opcodes) > 1: 142 | for i in range(len(opcodes) - 1): 143 | f += emit_opcode(opcodes[i]) 144 | 145 | opcode_lit: Expression = Literal(opcodes[len(opcodes) - 1], TYPE_U8) 146 | 147 | if sra: 148 | opcode_lit = Binary(OP_ADD, opcode_lit, Var('operand')) 149 | 150 | f += emit_opcode(opcode_lit) 151 | 152 | yield f 153 | 154 | @parse_instr(immsize) 155 | def instr_single_imm(opcodes: List[int], sizes: List[int]) -> Functions: 156 | for size in sizes: 157 | f = Function('', [ pimm('operand', size) ], fullname=f'_imm{size}') 158 | 159 | if size == 16: 160 | f += emit_prefix(False, 16) 161 | elif size == 64: 162 | f += emit_prefix(False, 64) 163 | 164 | f += emit_opcode(opcodes) 165 | f += Set(immtype_for_size(size), Var('operand')) 166 | 167 | yield f 168 | 169 | @parse_instr(rmsize, ws, immsize, ws, string('+'), opcode) 170 | def instr_reg_imm_plus(opcodes: List[int], rsizes: List[int], _, immsizes: List[int], 171 | __, ___, plus: int) -> Functions: 172 | for rsize in rsizes: 173 | for isize in immsizes: 174 | fullname = f'_rm{rsize}_imm{isize}' 175 | f = Function('', [ pregister('reg', rsize), pimm('value', isize) ], fullname) 176 | 177 | if rsize == 16: 178 | f += emit_prefix(False, 16) 179 | elif rsize == 64: 180 | f += emit_prefix(False, 64) 181 | 182 | f += emit_opcode(opcodes) 183 | f += Set(regtype_for_size(rsize), Binary(OP_ADD, Var('reg'), Literal(plus, TYPE_U8))) 184 | f += Set(immtype_for_size(isize), Var('value')) 185 | 186 | yield f 187 | 188 | return instr_reg_imm_plus | instr_single_reg | instr_single_imm | instr_nop 189 | 190 | 191 | # Architecture 192 | 193 | class X86Architecture(Architecture): 194 | 195 | @property 196 | def name(self) -> str: 197 | return 'x86' 198 | 199 | @property 200 | def tests(self): 201 | from .tests import X86TestSource 202 | 203 | return X86TestSource() 204 | 205 | @property 206 | def declarations(self) -> Declarations: 207 | yield DistinctType(TYPE_X86_R8, 'An x86 8-bits register.', 208 | [ Constant(n, i) for i, n in enumerate('al, cl, dl, bl, spl, bpl, sil, dil, r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b'.split(', ')) ]) 209 | yield DistinctType(TYPE_X86_R16, 'An x86 16-bits register.', 210 | [ Constant(n, i) for i, n in enumerate('ax, cx, dx, bx, sp, bp, si, di, r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w'.split(', ')) ]) 211 | yield DistinctType(TYPE_X86_R32, 'An x86 32-bits register.', 212 | [ Constant(n, i) for i, n in enumerate('eax, ecx, edx, ebx, esp, ebp, esi, edi, r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d'.split(', ')) ]) 213 | yield DistinctType(TYPE_X86_R64, 'An x86 64-bits register.', 214 | [ Constant(n, i) for i, n in enumerate('rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi, r8, r9, r10, r11, r12, r13, r14, r15'.split(', ')) ]) 215 | yield DistinctType(TYPE_X86_R128, 'An x86 128-bits register.', []) 216 | 217 | @property 218 | def functions(self) -> Functions: 219 | parser = get_x86_parser(self) 220 | 221 | with open(relative('data.txt'), 'r') as input: 222 | for line in input: 223 | if not len(line) or line[0] == '#': 224 | continue 225 | 226 | line = line.strip() 227 | 228 | if not len(line): 229 | continue 230 | 231 | try: 232 | for fun in parser.parse(line): 233 | yield fun 234 | 235 | except ParseError as err: 236 | stripped_line = line.strip('\n') 237 | 238 | logger.error(f'Invalid instruction: "{stripped_line}".') 239 | logger.exception(err) 240 | except Exception as err: 241 | stripped_line = line.strip('\n') 242 | 243 | logger.error(f'Invalid instruction: "{stripped_line}".') 244 | logger.exception(err) 245 | 246 | break 247 | -------------------------------------------------------------------------------- /asmdot/arch/x86/data.txt: -------------------------------------------------------------------------------- 1 | # No operand 2 | 9c pushf 3 | 9d popf 4 | c3 ret 5 | f8 clc 6 | f9 stc 7 | fa cli 8 | fb sti 9 | fc cld 10 | fd std 11 | 12 | # Jumps 13 | 70 jo rel8 14 | 71 jno rel8 15 | 72 jb/jnae/jc rel8 16 | 73 jnb/jae/jnc rel8 17 | 74 jz/je rel8 18 | 75 jnz/jne rel8 19 | 76 jbe/jna rel8 20 | 77 jnbe/ja rel8 21 | 78 js rel8 22 | 79 jns rel8 23 | 7a jp/jpe rel8 24 | 7b jnp/jpo rel8 25 | 7c jl/jnge rel8 26 | 7d jnl/jge rel8 27 | 7e jle/jng rel8 28 | 7f jnle/jg rel8 29 | 30 | # One register operand 31 | 40 inc r16-32 32 | 48 dec r16-32 33 | 50 push r16-32 34 | 58 pop r16-64 35 | 36 | # One register / one immediate operands 37 | 80 add rm8 imm8 +0 38 | 80 or rm8 imm8 +1 39 | 80 adc rm8 imm8 +2 40 | 80 sbb rm8 imm8 +3 41 | 80 and rm8 imm8 +4 42 | 80 sub rm8 imm8 +5 43 | 80 xor rm8 imm8 +6 44 | 80 cmp rm8 imm8 +7 45 | 46 | 81 add rm16-32 imm16-32 +0 47 | 81 or rm16-32 imm16-32 +1 48 | 81 adc rm16-32 imm16-32 +2 49 | 81 sbb rm16-32 imm16-32 +3 50 | 81 and rm16-32 imm16-32 +4 51 | 81 sub rm16-32 imm16-32 +5 52 | 81 xor rm16-32 imm16-32 +6 53 | 81 cmp rm16-32 imm16-32 +7 54 | 55 | 83 add rm16-32 imm8 +0 56 | 83 or rm16-32 imm8 +1 57 | 83 adc rm16-32 imm8 +2 58 | 83 sbb rm16-32 imm8 +3 59 | 83 and rm16-32 imm8 +4 60 | 83 sub rm16-32 imm8 +5 61 | 83 xor rm16-32 imm8 +6 62 | 83 cmp rm16-32 imm8 +7 63 | -------------------------------------------------------------------------------- /asmdot/arch/x86/tests.py: -------------------------------------------------------------------------------- 1 | from ..testsource import * # pylint: disable=W0614 2 | 3 | class X86TestSource(TestSource): 4 | 5 | @property 6 | def name(self) -> str: 7 | return 'x86' 8 | 9 | @property 10 | def test_cases(self) -> TestCases: 11 | yield TestCase('should assemble single ret instruction', [ 12 | self.make_call('ret') 13 | ], bytearray(b'\xC3')) 14 | -------------------------------------------------------------------------------- /asmdot/emit.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from argparse import ArgumentParser, Namespace 3 | from typing import Any, Callable, Dict, IO, no_type_check 4 | 5 | from .ast import * # pylint: disable=W0614 6 | from .options import Options 7 | 8 | 9 | class Emitter(ABC, Options): 10 | """An AST to source code emitter.""" 11 | functions : List[Function] = [] 12 | declarations : List[Declaration] = [] 13 | 14 | @property 15 | @abstractmethod 16 | def language(self) -> str: 17 | """Returns the identifier of the target language.""" 18 | pass 19 | 20 | @property 21 | @abstractmethod 22 | def filename(self) -> str: 23 | """Returns the name of the generated file for the current architecture.""" 24 | pass 25 | 26 | @property 27 | def test_filename(self) -> Optional[str]: 28 | """Returns the name of the generated file that tests the current architecture. 29 | If it is `None`, no tests will be generated.""" 30 | return None 31 | 32 | 33 | @staticmethod 34 | def register(parser: ArgumentParser) -> None: 35 | """Registers the emitter, allowing it to add command-line parameters.""" 36 | pass 37 | 38 | 39 | def get_type_name(self, ty: IrType) -> str: 40 | """Returns the name of the given type.""" 41 | return ty.id 42 | 43 | def get_operator(self, op: Operator) -> str: 44 | """Returns the string representation of the given operator.""" 45 | return op.op 46 | 47 | def get_builtin_name(self, builtin: Builtin) -> str: 48 | """Returns the name of the given builtin.""" 49 | return builtin.name 50 | 51 | def get_function_name(self, function: Function) -> str: 52 | """Returns the name of the given function.""" 53 | return function.initname 54 | 55 | 56 | def __init__(self, args: Namespace, arch: str) -> None: 57 | """Initializes the emitter for the given architecture. This constructor shall not be overriden.""" 58 | self.arch = arch 59 | self.indent = Indent() 60 | self.initialize_options(args, arch) 61 | 62 | self.output : Optional[IO[str]] = None 63 | 64 | 65 | def write_header(self) -> None: 66 | """Emits the header of the file to a stream.""" 67 | pass 68 | 69 | def write_footer(self) -> None: 70 | """Emits the footer of the file to a stream.""" 71 | pass 72 | 73 | def write_separator(self) -> None: 74 | """Emits the separator between the declarations (written previously) and the functions (that are about to be written).""" 75 | pass 76 | 77 | 78 | @abstractmethod 79 | def write_expr(self, expr: Expression) -> None: 80 | """Emits an expression to a stream. 81 | Additionally, the `str` function will be modified to use this method for every `Expression` class.""" 82 | raise NotImplementedError 83 | 84 | @abstractmethod 85 | def write_stmt(self, stmt: Statement) -> None: 86 | """Emits a statement to a stream. 87 | Additionally, the `str` function will be modified to use this method for every `Statement` class.""" 88 | raise NotImplementedError 89 | 90 | @abstractmethod 91 | def write_function(self, fun: Function) -> None: 92 | """Emits a function to a stream.""" 93 | raise NotImplementedError 94 | 95 | 96 | @abstractmethod 97 | def write_decl(self, decl: Declaration) -> None: 98 | """Emits a declaration to a stream.""" 99 | raise NotImplementedError 100 | 101 | 102 | def write_test_header(self) -> None: 103 | """Emits the header of the test file.""" 104 | pass 105 | 106 | def write_test_footer(self) -> None: 107 | """Emits the footer of the test file.""" 108 | pass 109 | 110 | def write_test(self, test: TestCase) -> None: 111 | """Emits a test to a stream.""" 112 | raise NotImplementedError 113 | 114 | 115 | def write(self, *args, indent: bool = False) -> None: 116 | """Writes the given arguments to the underlying stream.""" 117 | assert self.output is not None 118 | 119 | out = self.output 120 | 121 | if indent: 122 | out.write(str(self.indent)) 123 | 124 | for arg in args: 125 | if isinstance(arg, str): 126 | out.write(arg) 127 | elif any([ isinstance(arg, k) for k in expressionClasses ]): 128 | self.write_expr(arg) 129 | elif any([ isinstance(arg, k) for k in statementClasses ]): 130 | self.write_stmt(arg) 131 | else: 132 | out.write(str(arg)) 133 | 134 | def writeline(self, *args, indent: bool = False) -> None: 135 | """Writes the given arguments to the underlying stream followed by a new-line 136 | character.""" 137 | self.write(*args, indent=indent) 138 | self.output.write('\n') # type: ignore 139 | 140 | def writei(self, *args) -> None: 141 | """Writes the given arguments to the underlying stream after inserting indentation.""" 142 | self.write(*args, indent=True) 143 | 144 | def writelinei(self, *args) -> None: 145 | """Writes the given arguments to the underlying stream followed by a new-line 146 | character after inserting indentation.""" 147 | self.writeline(*args, indent=True) 148 | 149 | @no_type_check 150 | def replace_pattern(patterns: Dict[str, str], string: str) -> str: 151 | """Replaces the string by the first template matching the corresponding key.""" 152 | import re 153 | 154 | if not hasattr(replace_pattern, 'memoized'): 155 | replace_pattern.memoized = {} 156 | 157 | for pattern in patterns: 158 | if pattern in replace_pattern.memoized: 159 | pat = replace_pattern.memoized[pattern] 160 | else: 161 | pat = re.compile(f'^{pattern}$') 162 | replace_pattern.memoized[pattern] = pat 163 | 164 | newstring, n = pat.subn(patterns[pattern], string, count=1) 165 | 166 | if n == 1: 167 | return newstring 168 | 169 | return string 170 | 171 | def join_any(sep: str, args: Sequence[Any]) -> str: 172 | """Joins multiple values together in a string using their `str` operator.""" 173 | return sep.join([ str(arg) for arg in args ]) 174 | 175 | 176 | class Indent: 177 | """Helper class that manages the indentation when writing a string.""" 178 | 179 | def __init__(self, indent_str: str = ' ', lvl: int = 0) -> None: 180 | self._str = indent_str 181 | self._lvl = lvl 182 | 183 | def __iadd__(self, i: int) -> 'Indent': 184 | """Adds the given level of indentation to the current one.""" 185 | self._lvl += i 186 | 187 | return self 188 | 189 | def __isub__(self, i: int) -> 'Indent': 190 | """Removes the given level of indentation from the current one.""" 191 | self._lvl -= i 192 | 193 | return self 194 | 195 | def __add__(self, i: int): 196 | """Returns a new indentation object with a greater indentation level.""" 197 | return Indent(self._str, self._lvl + i) 198 | 199 | def __sub__(self, i: int): 200 | """Returns a new indentation object with a lower indentation level.""" 201 | return Indent(self._str, self._lvl - i) 202 | 203 | def further(self, i: int = 1): 204 | """Returns an object that can be __enter__'d and __exit__'d with the `with` syntax. 205 | Inside the created block, the indentation level is increased by the given integer.""" 206 | class FurtherWrapper: 207 | @classmethod 208 | def __enter__(s, *_): 209 | self._lvl += i 210 | @classmethod 211 | def __exit__(s, *_): 212 | self._lvl -= i 213 | 214 | return FurtherWrapper() 215 | 216 | def write(self, io: IO[str], *args) -> None: 217 | """Writes the indent to a text stream.""" 218 | io.write(self._str * self._lvl) 219 | 220 | for arg in args: 221 | io.write(str(arg)) 222 | 223 | def __call__(self, fmt: str, *args) -> str: 224 | """Returns the given string, indented and terminated with a line feed. Additionally, other arguments can be given in order to format the string.""" 225 | return str(self) + (fmt.format(*args) if len(args) else fmt) + '\n' 226 | 227 | def __str__(self) -> str: 228 | """Returns the string representation of the indentation level, which is `' ' * indent_level`.""" 229 | return self._str * self._lvl 230 | 231 | class UnsupportedArchitecture(Exception): 232 | arch: str 233 | 234 | def __str__(self): 235 | return f'Architecture {self.arch} is not supported.' 236 | 237 | class UnsupportedDeclaration(Exception): 238 | decl: Declaration 239 | 240 | def __str__(self): 241 | return f'Declaration of type {self.decl.__class__.__name__} is not supported.' 242 | 243 | class UnsupportedExpression(Exception): 244 | expr: Expression 245 | 246 | def __str__(self): 247 | return f'Expression of type {self.expr.__class__.__name__} is not supported.' 248 | 249 | class UnsupportedStatement(Exception): 250 | stmt: Statement 251 | 252 | def __str__(self): 253 | return f'Statement of type {self.stmt.__class__.__name__} is not supported.' 254 | 255 | class UnsupportedTestArgument(Exception): 256 | arg: TestCaseArgument 257 | 258 | def __str__(self): 259 | return f'Test case argument of type {self.arg.__class__.__name__} is not supported.' 260 | 261 | class UnsupportedOption(Exception): 262 | option: str 263 | explain: str 264 | 265 | def __str__(self): 266 | return f'Option "{self.option}" is not supported: {self.explain}' 267 | -------------------------------------------------------------------------------- /asmdot/helpers.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | from io import StringIO 3 | from importlib.util import spec_from_file_location, module_from_spec 4 | from typing import no_type_check, TextIO, IO, List, Tuple 5 | 6 | from .ast import expressionClasses, statementClasses 7 | from .ast import Expression, Function, Statement, IrType, Operator, Builtin, TestCase 8 | from .emit import Emitter 9 | 10 | def create_default_argument_parser(): 11 | """Creates the default argument parser.""" 12 | import argparse 13 | 14 | parser = argparse.ArgumentParser(description='Generate ASM. sources.', 15 | add_help=False) 16 | 17 | parser.add_argument('-h', '--help', action='store_true', 18 | help='Show the help message.') 19 | 20 | parser.add_argument('-ns', '--no-sources', action='store_true', help='Do not generate sources.') 21 | parser.add_argument('-nt', '--no-tests', action='store_true', help='Do not generate tests.') 22 | 23 | parser.add_argument('-be', '--big-endian', action='store_true', 24 | help='Emit integers in big-endian.') 25 | 26 | parser.add_argument('-o', '--output', default=None, metavar='output-dir/', 27 | help='Change the output directory ' + 28 | '(default: directory of calling emitter).') 29 | 30 | parser.add_argument('-v', '--verbose', action='count', default=0, 31 | help='Increase verbosity (can be given multiple times to increase it further).') 32 | 33 | return parser 34 | 35 | def emitter_hooks(emitter: Emitter, output: IO[str]): 36 | """Enters a block in which some global functions are managed by the architecture.""" 37 | saved_output = emitter.output 38 | saved = {} 39 | 40 | def get_stmt_str(x: Statement) -> str: 41 | s = StringIO(newline = '\n') 42 | 43 | emitter.output = s 44 | emitter.write_stmt(x) 45 | emitter.output = output 46 | 47 | return s.getvalue() 48 | 49 | def get_expr_str(x: Expression) -> str: 50 | s = StringIO(newline = '\n') 51 | 52 | emitter.output = s 53 | emitter.write_expr(x) 54 | emitter.output = output 55 | 56 | return s.getvalue() 57 | 58 | class Wrapper: 59 | def __enter__(self): 60 | emitter.output = output 61 | 62 | for stmtclass in statementClasses: 63 | saved[stmtclass] = stmtclass.__str__ 64 | stmtclass.__str__ = get_stmt_str 65 | 66 | for exprclass in expressionClasses: 67 | saved[exprclass] = exprclass.__str__ 68 | exprclass.__str__ = get_expr_str 69 | 70 | saved[IrType] = IrType.__str__ 71 | IrType.__str__ = lambda x: emitter.get_type_name(x) 72 | 73 | saved[Operator] = Operator.__str__ 74 | Operator.__str__ = lambda x: emitter.get_operator(x) 75 | 76 | saved[Builtin] = Builtin.__str__ 77 | Builtin.__str__ = lambda x: emitter.get_builtin_name(x) 78 | 79 | Function.name = property(lambda x: emitter.get_function_name(x)) 80 | 81 | def __exit__(self, *_): 82 | emitter.output = saved_output 83 | 84 | for k in saved: 85 | k.__str__ = saved[k] 86 | 87 | Function.name = lambda x: x.initname 88 | 89 | return Wrapper() 90 | 91 | def ensure_directory_exists(path): 92 | """Ensures that the given directory exists, creating it and its parents if necessary.""" 93 | import pathlib 94 | 95 | # pylint: disable=E1101 96 | pathlib.Path(path).parent.mkdir(parents=True, exist_ok=True) 97 | 98 | def parent(path: str): 99 | """Returns the parent `Path` of the given path.""" 100 | from pathlib import Path 101 | 102 | return Path(path).parent.resolve() 103 | 104 | def relative(*args, up: int = 1): 105 | """Returns the given path, relative to the current file.""" 106 | import inspect, pathlib 107 | 108 | caller_path = inspect.stack()[up].filename 109 | 110 | return pathlib.Path(caller_path).parent.joinpath(*args) 111 | 112 | 113 | # Lexer / parser built-ins 114 | 115 | from parsy import eof, regex, seq, Parser 116 | 117 | ws = regex(r'[ \t]+').desc('whitespace') 118 | ows = regex(r'[ \t]*').desc('whitespace') 119 | end = (regex(r'\n+') | eof).desc('end of line') 120 | 121 | def parse(*args): 122 | """Creates a parser that maps the given parse to the designated function.""" 123 | if len(args) == 0: 124 | raise ValueError('At least one parser required.') 125 | 126 | parsers = [] 127 | 128 | for arg in args: 129 | if isinstance(arg, str): 130 | parsers.append(regex(arg)) 131 | elif isinstance(arg, Parser): 132 | parsers.append(arg) 133 | else: 134 | raise ValueError('Invalid parser provided.') 135 | 136 | if len(args) == 1: 137 | return parsers[0].map 138 | else: 139 | return seq(*parsers).combine 140 | 141 | 142 | # Logging 143 | from colorama import init, Fore, Style 144 | import logging 145 | 146 | init() 147 | 148 | def create_logger(): 149 | logger : logging.Logger = logging.getLogger('asm') 150 | formatter = logging.Formatter('%(message)s') 151 | 152 | console = logging.StreamHandler() 153 | console.setFormatter(formatter) 154 | 155 | logger.addHandler(console) 156 | 157 | return logger 158 | 159 | ASMLOGGER = create_logger() 160 | JUSTWIDTH = 15 161 | 162 | def debug(title: str, *args, sep=''): 163 | ASMLOGGER.debug(Fore.BLUE + title.rjust(JUSTWIDTH) + Style.RESET_ALL + ' ' + 164 | sep.join([ str(arg) for arg in args ])) 165 | 166 | def info(title: str, *args, sep=''): 167 | ASMLOGGER.info(Fore.GREEN + title.rjust(JUSTWIDTH) + Style.RESET_ALL + ' ' + 168 | sep.join([ str(arg) for arg in args ])) 169 | 170 | def error(title: str, *args, sep=''): 171 | ASMLOGGER.error(Fore.RED + title.rjust(JUSTWIDTH) + Style.RESET_ALL + ' ' + 172 | sep.join([ str(arg) for arg in args ])) 173 | 174 | def warning(title: str, *args, sep=''): 175 | ASMLOGGER.warning(Fore.YELLOW + title.rjust(JUSTWIDTH) + Style.RESET_ALL + ' ' + 176 | sep.join([ str(arg) for arg in args ])) 177 | 178 | def exception(exc: Exception): 179 | ASMLOGGER.error(exc.__class__.__name__, exc) 180 | -------------------------------------------------------------------------------- /asmdot/metadata.ini: -------------------------------------------------------------------------------- 1 | 2 | [default] 3 | # Keep semantic versioning here 4 | # 5 | # Note: 6 | # Right now we're absolutely not ready for use, which is why this version stays at 0.0.1. 7 | version = 0.0.1 8 | 9 | name = asm 10 | url = https://github.com/6A/asmdot 11 | description = Fast, zero-copy, unopinionated and multi-architecture assembler 12 | author = Grégoire Geis 13 | author_email = git@gregoirege.is 14 | -------------------------------------------------------------------------------- /asmdot/options.py: -------------------------------------------------------------------------------- 1 | from argparse import Namespace 2 | 3 | class Options: 4 | 5 | def initialize_options(self, args: Namespace, arch: str): 6 | """Initializes the options, setting its attributes based on command-line parameters.""" 7 | self.arch : str = arch 8 | self.bigendian : bool = args.big_endian 9 | -------------------------------------------------------------------------------- /asmdot/requirements.txt: -------------------------------------------------------------------------------- 1 | parsy>=1.3 2 | colorama>=0.3 3 | logzero>=1.5 4 | -------------------------------------------------------------------------------- /asmdot/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | from configparser import ConfigParser 3 | 4 | config = ConfigParser() 5 | config.read('./metadata.ini') 6 | 7 | metadata = config['default'] 8 | 9 | 10 | setup( 11 | name = metadata.get('name'), 12 | version = metadata.get('version'), 13 | description = metadata.get('description'), 14 | url = metadata.get('url'), 15 | author = metadata.get('author'), 16 | author_email = metadata.get('author_email'), 17 | license = 'MIT', 18 | 19 | python_requires = '>=3.6.0', 20 | 21 | packages = find_packages(exclude=('tests',)), 22 | include_package_data = True, 23 | 24 | classifiers = [ 25 | 'Intended Audience :: Developers', 26 | 'License :: OSI Approved :: MIT License', 27 | 'Programming Language :: Python', 28 | 'Programming Language :: Python :: 3', 29 | 'Programming Language :: Python :: 3.6', 30 | 'Operating System :: OS Independent', 31 | 'Topic :: Software Development :: Assemblers' 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /languages/c/README.md: -------------------------------------------------------------------------------- 1 | C 2 | = 3 | 4 | TO-DO. 5 | -------------------------------------------------------------------------------- /languages/c/generate.py: -------------------------------------------------------------------------------- 1 | from asmdot import * # pylint: disable=W0614 2 | 3 | header = '''// Automatically generated file. 4 | 5 | #include 6 | #include 7 | 8 | #define byte uint8_t 9 | #define bool _Bool 10 | #define CALLCONV {} 11 | 12 | inline uint16_t asm_swap16(uint16_t value) 13 | {{ 14 | return (value << 8) | (value >> 8); 15 | }} 16 | 17 | inline uint32_t asm_swap32(uint32_t value) 18 | {{ 19 | value = ((value << 8) & 0xFF00FF00) | ((value >> 8) & 0xFF00FF); 20 | return (value << 16) | (value >> 16); 21 | }} 22 | 23 | inline uint64_t asm_swap64(uint64_t value) 24 | {{ 25 | value = ((value << 8) & 0xFF00FF00FF00FF00ULL) | ((value >> 8) & 0x00FF00FF00FF00FFULL); 26 | value = ((value << 16) & 0xFFFF0000FFFF0000ULL) | ((value >> 16) & 0x0000FFFF0000FFFFULL); 27 | return (value << 32) | (value >> 32); 28 | }} 29 | 30 | ''' 31 | 32 | @handle_command_line() 33 | class CEmitter(Emitter): 34 | 35 | @property 36 | def language(self): 37 | return 'c' 38 | 39 | @property 40 | def filename(self): 41 | if self.header: 42 | return f'{self.arch}.h' 43 | else: 44 | return f'src/{self.arch}.c' 45 | 46 | @property 47 | def test_filename(self): 48 | return f'test/{self.arch}.c' 49 | 50 | 51 | def get_type_name(self, ty: IrType) -> str: 52 | return replace_pattern({ 53 | r'u?int\d+': r'\g<0>_t' 54 | }, ty.id) 55 | 56 | def get_function_name(self, function: Function) -> str: 57 | if self.prefix: 58 | return f'{self.arch}_{function.fullname}' 59 | else: 60 | return function.fullname 61 | 62 | 63 | @staticmethod 64 | def register(parser: ArgumentParser): 65 | group = parser.add_argument_group('C') 66 | 67 | # Useful when overloading is not available, and files have no concept of modules or namespaces. 68 | group.add_argument('-np', '--no-prefix', action='store_true', 69 | help='Do not prefix function names by their architecture.') 70 | 71 | group.add_argument('-ah', '--as-header', action='store_true', 72 | help='Generate headers instead of regular files.') 73 | 74 | group.add_argument('-cc', '--calling-convention', default='', metavar='CALLING-CONVENTION', 75 | help='Specify the calling convention of generated functions.') 76 | 77 | def __init__(self, args: Namespace, arch: str) -> None: 78 | super().__init__(args, arch) 79 | 80 | self.indent = Indent(' ') 81 | self.cc : str = args.calling_convention 82 | self.header = args.as_header 83 | self.prefix : bool = not args.no_prefix 84 | self.tests : List[str] = [] 85 | 86 | 87 | def write_header(self): 88 | self.write(header.format(self.cc)) 89 | 90 | if self.arch == 'x86': 91 | self.write('#define get_prefix(r) (r > 7 && (r -= 8) == r)\n\n') 92 | 93 | def write_separator(self): 94 | self.writeline() 95 | 96 | 97 | def write_expr(self, expr: Expression): 98 | if isinstance(expr, Binary): 99 | self.write('(', expr.l, ' ', expr.op, ' ', expr.r, ')') 100 | 101 | elif isinstance(expr, Unary): 102 | self.write(expr.op, expr.v) 103 | 104 | elif isinstance(expr, Ternary): 105 | self.write('(', expr.condition, ' ? ', expr.consequence, ' : ', expr.alternative, ')') 106 | 107 | elif isinstance(expr, Var): 108 | self.write(expr.name) 109 | 110 | elif isinstance(expr, Call): 111 | self.write(expr.builtin, '(', join_any(', ', expr.args), ')') 112 | 113 | elif isinstance(expr, Literal): 114 | self.write(expr.value) 115 | 116 | else: 117 | raise UnsupportedExpression(expr) 118 | 119 | def write_stmt(self, stmt: Statement): 120 | if isinstance(stmt, Assign): 121 | self.writelinei(stmt.variable, ' = ', stmt.value, ';') 122 | 123 | elif isinstance(stmt, Conditional): 124 | self.writelinei('if (', stmt.condition, ')') 125 | 126 | with self.indent.further(): 127 | self.write_stmt(stmt.consequence) 128 | 129 | if stmt.alternative: 130 | self.writelinei('else') 131 | 132 | with self.indent.further(): 133 | self.write_stmt(stmt.alternative) 134 | 135 | elif isinstance(stmt, Block): 136 | with self.indent.further(-1): 137 | self.writelinei('{') 138 | 139 | for s in stmt.statements: 140 | self.write_stmt(s) 141 | 142 | with self.indent.further(-1): 143 | self.writelinei('}') 144 | 145 | elif isinstance(stmt, Set): 146 | if stmt.type.under in (TYPE_U8, TYPE_I8): 147 | self.writelinei('*(uint8_t*)buf = ', stmt.value, ';') 148 | else: 149 | swap = f'asm_swap{stmt.type.under.size * 8}' 150 | 151 | self.writeline('#if BIGENDIAN') 152 | 153 | if self.bigendian: 154 | self.writelinei(f'*({stmt.type}*)(*buf) = ', stmt.value, ';') 155 | self.writeline('#else') 156 | self.writelinei(f'*({stmt.type}*)(*buf) = ', swap, '(', stmt.value, ');') 157 | else: 158 | self.writelinei(f'*({stmt.type}*)(*buf) = ', swap, '(', stmt.value, ');') 159 | self.writeline('#else') 160 | self.writelinei(f'*({stmt.type}*)(*buf) = ', stmt.value, ';') 161 | 162 | self.writeline('#endif') 163 | 164 | self.writelinei(f'*(byte*)buf += {stmt.type.size};') 165 | 166 | elif isinstance(stmt, Define): 167 | self.writelinei(f'{stmt.type} {stmt.name} = ', stmt.value, ';') 168 | 169 | else: 170 | raise UnsupportedStatement(stmt) 171 | 172 | def write_function(self, fun: Function): 173 | self.write(f'void CALLCONV {fun.name}(void** buf') 174 | 175 | for name, ctype, _ in fun.params: 176 | self.write(f', {ctype} {name}') 177 | 178 | self.write(') {\n') 179 | 180 | self.indent += 1 181 | 182 | for condition in fun.conditions: 183 | self.writelinei('assert(', condition, ');') 184 | 185 | for stmt in fun.body: 186 | self.write_stmt(stmt) 187 | 188 | self.write('}\n\n') 189 | self.indent -= 1 190 | 191 | 192 | def write_decl(self, decl: Declaration): 193 | if isinstance(decl, Enumeration): 194 | self.write('///\n') 195 | self.write('/// ', decl.descr, '\n') 196 | self.write('typedef enum {\n') 197 | 198 | for _, value, descr, fullname in decl.members + decl.additional_members: 199 | self.write(' ///\n') 200 | self.write(' /// ', descr, '\n') 201 | self.write(' ', fullname, ' = ', value, ',\n') 202 | 203 | self.write('} ', decl.type, ';\n\n') 204 | 205 | elif isinstance(decl, DistinctType): 206 | self.write('#define ', decl.type, ' ', decl.type.underlying, '\n') 207 | 208 | for name, value in decl.constants: 209 | self.write('#define ', decl.type, '_', name.upper(), ' ', value, '\n') 210 | 211 | else: 212 | raise UnsupportedDeclaration(decl) 213 | 214 | 215 | def write_test_header(self): 216 | self.write( '#include "greatest.h"\n') 217 | self.write(f'#include "../src/{self.arch}.c"\n\n') 218 | 219 | def write_test_footer(self): 220 | self.write('GREATEST_MAIN_DEFS();\n\n') 221 | self.write('int main(int argc, char** argv) {\n') 222 | self.indent += 1 223 | 224 | self.writei('GREATEST_MAIN_BEGIN();\n\n') 225 | 226 | for test_name in self.tests: 227 | self.writei('RUN_TEST(', test_name, ');\n') 228 | 229 | self.writeline() 230 | self.writei('GREATEST_MAIN_END();\n') 231 | 232 | self.indent -= 1 233 | self.write('}\n') 234 | self.tests.clear() 235 | 236 | def write_test(self, test: TestCase): 237 | name = test.name.replace(' ', '_') 238 | 239 | self.tests.append(name) 240 | 241 | self.write('TEST ', name, '() {\n') 242 | self.indent += 1 243 | 244 | self.writei('void* buf = malloc(', len(test.expected), ');\n') 245 | self.writei('void* origin = buf;\n\n') 246 | 247 | def arg_str(arg: TestCaseArgument): 248 | if isinstance(arg, ArgConstant): 249 | return f'{arg.type.type}_{arg.const.name.upper()}' 250 | if isinstance(arg, ArgEnumMember): 251 | return arg.member.fullname 252 | elif isinstance(arg, ArgInteger): 253 | return str(arg.value) 254 | else: 255 | raise UnsupportedTestArgument(arg) 256 | 257 | for func, args in test.calls: 258 | self.writei(func.name, '(&buf') 259 | 260 | for arg in args: 261 | self.write(', ', arg_str(arg)) 262 | 263 | self.write(');\n') 264 | 265 | self.writeline() 266 | self.writei('ASSERT_EQ((char*)buf, (char*)origin + ', len(test.expected), ');\n') 267 | self.writei('ASSERT_MEM_EQ(origin, "', test.expected_string, '", ', len(test.expected), ');\n\n') 268 | self.writei('free(origin);\n') 269 | self.writei('PASS();\n') 270 | self.indent -= 1 271 | 272 | self.write('}\n\n') 273 | -------------------------------------------------------------------------------- /languages/c/test/arm.c: -------------------------------------------------------------------------------- 1 | #include "greatest.h" 2 | #include "../src/arm.c" 3 | 4 | TEST should_encode_single_cps_instruction() { 5 | void* buf = malloc(4); 6 | void* origin = buf; 7 | 8 | arm_cps(&buf, USRMode); 9 | 10 | ASSERT_EQ((char*)buf, (char*)origin + 4); 11 | ASSERT_MEM_EQ(origin, "\x10\x00\x02\xf1", 4); 12 | 13 | free(origin); 14 | PASS(); 15 | } 16 | 17 | GREATEST_MAIN_DEFS(); 18 | 19 | int main(int argc, char** argv) { 20 | GREATEST_MAIN_BEGIN(); 21 | 22 | RUN_TEST(should_encode_single_cps_instruction); 23 | 24 | GREATEST_MAIN_END(); 25 | } 26 | -------------------------------------------------------------------------------- /languages/c/test/mips.c: -------------------------------------------------------------------------------- 1 | #include "greatest.h" 2 | #include "../src/mips.c" 3 | 4 | TEST should_assemble_single_addi_instruction() { 5 | void* buf = malloc(4); 6 | void* origin = buf; 7 | 8 | mips_addi(&buf, Reg_T1, Reg_T2, 0); 9 | 10 | ASSERT_EQ((char*)buf, (char*)origin + 4); 11 | ASSERT_MEM_EQ(origin, "\x00\x00\x49\x21", 4); 12 | 13 | free(origin); 14 | PASS(); 15 | } 16 | 17 | GREATEST_MAIN_DEFS(); 18 | 19 | int main(int argc, char** argv) { 20 | GREATEST_MAIN_BEGIN(); 21 | 22 | RUN_TEST(should_assemble_single_addi_instruction); 23 | 24 | GREATEST_MAIN_END(); 25 | } 26 | -------------------------------------------------------------------------------- /languages/c/test/x86.c: -------------------------------------------------------------------------------- 1 | #include "greatest.h" 2 | #include "../src/x86.c" 3 | 4 | TEST should_assemble_single_ret_instruction() { 5 | void* buf = malloc(1); 6 | void* origin = buf; 7 | 8 | x86_ret(&buf); 9 | 10 | ASSERT_EQ((char*)buf, (char*)origin + 1); 11 | ASSERT_MEM_EQ(origin, "\xc3", 1); 12 | 13 | free(origin); 14 | PASS(); 15 | } 16 | 17 | GREATEST_MAIN_DEFS(); 18 | 19 | int main(int argc, char** argv) { 20 | GREATEST_MAIN_BEGIN(); 21 | 22 | RUN_TEST(should_assemble_single_ret_instruction); 23 | 24 | GREATEST_MAIN_END(); 25 | } 26 | -------------------------------------------------------------------------------- /languages/cpp/README.md: -------------------------------------------------------------------------------- 1 | C++ 2 | === 3 | 4 | To-do. 5 | -------------------------------------------------------------------------------- /languages/cpp/generate.py: -------------------------------------------------------------------------------- 1 | from asmdot import * # pylint: disable=W0614 2 | 3 | header = '''// Automatically generated file. 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace {} 10 | {{ 11 | namespace 12 | {{ 13 | template 14 | inline uint8_t get_prefix(T& r) 15 | {{ 16 | if (r.value < 8) 17 | return r.value; 18 | 19 | r.value -= 8; 20 | return 1; 21 | }} 22 | 23 | template 24 | inline void write_binary(std::ostream& os, T value, std::streamsize size) 25 | {{ 26 | os.write(reinterpret_cast(&value), size); 27 | }} 28 | 29 | inline uint16_t swap16(uint16_t value) 30 | {{ 31 | return (value << 8) | (value >> 8); 32 | }} 33 | 34 | inline uint32_t swap32(uint32_t value) 35 | {{ 36 | value = ((value << 8) & 0xFF00FF00) | ((value >> 8) & 0xFF00FF); 37 | return (value << 16) | (value >> 16); 38 | }} 39 | 40 | inline uint64_t swap64(uint64_t value) 41 | {{ 42 | value = ((value << 8) & 0xFF00FF00FF00FF00ULL) | ((value >> 8) & 0x00FF00FF00FF00FFULL); 43 | value = ((value << 16) & 0xFFFF0000FFFF0000ULL) | ((value >> 16) & 0x0000FFFF0000FFFFULL); 44 | return (value << 32) | (value >> 32); 45 | }} 46 | }} 47 | 48 | ''' 49 | 50 | @handle_command_line() 51 | class CppEmitter(Emitter): 52 | var_map: Dict[str, IrType] = {} 53 | 54 | @property 55 | def language(self): 56 | return 'cpp' 57 | 58 | @property 59 | def filename(self): 60 | if self.header: 61 | return f'{self.arch}.hpp' 62 | else: 63 | return f'src/{self.arch}.cpp' 64 | 65 | @property 66 | def test_filename(self): 67 | return f'test/{self.arch}.cpp' 68 | 69 | 70 | def get_type_name(self, ty: IrType) -> str: 71 | return replace_pattern({ 72 | r'u?int\d+': r'\g<0>_t' 73 | }, ty.id) 74 | 75 | def get_function_name(self, function: Function) -> str: 76 | if function.initname in ('and', 'not', 'or', 'xor'): 77 | return function.initname + '_' 78 | else: 79 | return function.initname 80 | 81 | 82 | @staticmethod 83 | def register(parser: ArgumentParser): 84 | group = parser.add_argument_group('C++') 85 | 86 | # Useful when overloading is not available, and files have no concept of modules or namespaces. 87 | group.add_argument('-ah', '--as-header', action='store_true', 88 | help='Generate headers instead of regular files.') 89 | 90 | def __init__(self, args: Namespace, arch: str) -> None: 91 | super().__init__(args, arch) 92 | 93 | self.indent = Indent(' ') 94 | self.header = args.as_header 95 | 96 | 97 | def write_header(self): 98 | self.write(header.format(self.arch)) 99 | self.indent += 1 100 | 101 | def write_separator(self): 102 | self.writeline() 103 | 104 | def write_footer(self): 105 | self.indent -= 1 106 | self.writeline('}') 107 | 108 | 109 | def write_expr(self, expr: Expression): 110 | if isinstance(expr, Binary): 111 | self.write('(', expr.l, ' ', expr.op, ' ', expr.r, ')') 112 | 113 | elif isinstance(expr, Unary): 114 | self.write(expr.op, expr.v) 115 | 116 | elif isinstance(expr, Ternary): 117 | self.write('(', expr.condition, ' ? ', expr.consequence, ' : ', expr.alternative, ')') 118 | 119 | elif isinstance(expr, Var): 120 | self.write('(', self.var_map[expr.name], ')', expr.name) 121 | 122 | elif isinstance(expr, Call): 123 | assert( all([ isinstance(arg, Var) for arg in expr.args ]) ) 124 | 125 | args_str = ', '.join([ arg.name for arg in expr.args ]) 126 | 127 | self.write(expr.builtin, '(', args_str, ')') 128 | 129 | elif isinstance(expr, Literal): 130 | self.write(expr.value) 131 | 132 | else: 133 | raise UnsupportedExpression(expr) 134 | 135 | def write_stmt(self, stmt: Statement): 136 | if isinstance(stmt, Assign): 137 | self.writelinei(stmt.variable, ' = ', stmt.value, ';') 138 | 139 | elif isinstance(stmt, Conditional): 140 | self.writelinei('if (', stmt.condition, ')') 141 | 142 | with self.indent.further(): 143 | self.write_stmt(stmt.consequence) 144 | 145 | if stmt.alternative: 146 | self.writelinei('else') 147 | 148 | with self.indent.further(): 149 | self.write_stmt(stmt.alternative) 150 | 151 | elif isinstance(stmt, Block): 152 | with self.indent.further(-1): 153 | self.writelinei('{') 154 | 155 | for s in stmt.statements: 156 | self.write_stmt(s) 157 | 158 | with self.indent.further(-1): 159 | self.writelinei('}') 160 | 161 | elif isinstance(stmt, Set): 162 | if stmt.type.under in (TYPE_U8, TYPE_I8): 163 | self.writelinei('os.put(', stmt.value, ');') 164 | else: 165 | size = stmt.type.under.size 166 | swap = f'swap{size * 8}' 167 | 168 | self.writelinei('#if BIGENDIAN') 169 | 170 | if self.bigendian: 171 | self.writelinei('write_binary(os, ', stmt.value, ', ', size, ');') 172 | self.writelinei('#else') 173 | self.writelinei('write_binary(os, ', swap, '(', stmt.value, '), ', size, ');') 174 | else: 175 | self.writelinei('write_binary(os, ', swap, '(', stmt.value, '), ', size, ');') 176 | self.writelinei('#else') 177 | self.writelinei('write_binary(os, ', stmt.value, ', ', size, ');') 178 | 179 | self.writelinei('#endif\n') 180 | 181 | elif isinstance(stmt, Define): 182 | self.writelinei(f'{stmt.type} {stmt.name} = ', stmt.value, ';') 183 | 184 | else: 185 | raise UnsupportedStatement(stmt) 186 | 187 | def write_function(self, fun: Function): 188 | self.writei(f'std::ostream& {fun.name}(std::ostream& os') 189 | 190 | for name, ctype, usagetyp in fun.params: 191 | self.write(f', {ctype} {name}') 192 | self.var_map[name] = usagetyp 193 | 194 | self.write(') {\n') 195 | 196 | self.indent += 1 197 | 198 | for condition in fun.conditions: 199 | self.writelinei('assert(', condition, ');') 200 | 201 | for stmt in fun.body: 202 | self.write_stmt(stmt) 203 | 204 | self.writelinei('return os;') 205 | self.indent -= 1 206 | self.writei('}\n\n') 207 | 208 | 209 | def write_decl(self, decl: Declaration): 210 | if isinstance(decl, Enumeration): 211 | self.writei('///\n') 212 | self.writei('/// ', decl.descr, '\n') 213 | self.writei('enum class ', decl.type, ' {\n') 214 | 215 | for name, value, descr, _ in decl.members + decl.additional_members: 216 | self.writei(' ///\n') 217 | self.writei(' /// ', descr, '\n') 218 | self.writei(' ', name, ' = ', value, ',\n') 219 | 220 | self.writei('};\n\n') 221 | 222 | elif isinstance(decl, DistinctType): 223 | self.writelinei('///') 224 | self.writelinei('/// ', decl.descr, '') 225 | self.writelinei('struct ', decl.type, ' {') 226 | 227 | with self.indent.further(): 228 | self.writelinei('/// Underlying value.') 229 | self.writelinei(decl.type.underlying, ' value;\n') 230 | self.writelinei('/// Creates a new ', decl.type, ', given its underlying value.') 231 | self.writelinei(decl.type, '(const ', decl.type.underlying, ' underlyingValue) : value(underlyingValue) {}\n') 232 | self.writelinei('/// Converts the wrapper to its underlying value.') 233 | self.writelinei('operator ', decl.type.underlying, '() { return value; }\n') 234 | 235 | for name, value in decl.constants: 236 | self.writei('static const ', decl.type, ' ', name.upper(), ';\n') 237 | 238 | self.writelinei('};\n') 239 | 240 | for name, value in decl.constants: 241 | self.writei('const ', decl.type, ' ', decl.type, '::', name.upper(), ' = ', value, ';\n') 242 | 243 | self.writeline() 244 | 245 | else: 246 | raise UnsupportedDeclaration(decl) 247 | 248 | 249 | def write_test_header(self): 250 | self.write( '#ifndef ASM_ALL_TESTS\n #define CATCH_CONFIG_MAIN\n#endif\n\n') 251 | self.write( '#include \n') 252 | self.write( '#include "catch.hpp"\n') 253 | self.write(f'#include "../src/{self.arch}.cpp"\n\n') 254 | self.write( 'using Catch::Matchers::Equals;\n\n') 255 | self.write( 'using namespace std::string_literals;\n\n') 256 | self.writei(f'TEST_CASE("{self.arch} tests", "[{self.arch}]") {{\n') 257 | self.indent += 1 258 | self.writei('std::ostringstream buf;\n') 259 | 260 | def write_test_footer(self): 261 | self.indent -= 1 262 | self.write('}\n') 263 | 264 | def write_test(self, test: TestCase): 265 | self.writeline() 266 | self.writei('SECTION("', test.name, '") {\n') 267 | self.indent += 1 268 | 269 | def arg_str(arg: TestCaseArgument): 270 | if isinstance(arg, ArgConstant): 271 | return f'{self.arch}::{arg.type.type}::{arg.const.name.upper()}' 272 | elif isinstance(arg, ArgEnumMember): 273 | return f'{self.arch}::{arg.enum.type}::{arg.member.name}' 274 | elif isinstance(arg, ArgInteger): 275 | return str(arg.value) 276 | else: 277 | raise UnsupportedTestArgument(arg) 278 | 279 | for func, args in test.calls: 280 | self.writei(self.arch, '::', func.name, '(buf') 281 | 282 | for arg in args: 283 | self.write(', ', arg_str(arg)) 284 | 285 | self.write(');\n') 286 | 287 | self.writeline() 288 | self.writei('REQUIRE( buf.tellp() == ', len(test.expected), ' );\n') 289 | self.writei('REQUIRE_THAT(buf.str(), Equals("', test.expected_string, '"s));\n') 290 | self.indent -= 1 291 | self.writei('}\n') 292 | -------------------------------------------------------------------------------- /languages/cpp/test/arm.cpp: -------------------------------------------------------------------------------- 1 | #ifndef ASM_ALL_TESTS 2 | #define CATCH_CONFIG_MAIN 3 | #endif 4 | 5 | #include 6 | #include "catch.hpp" 7 | #include "../src/arm.cpp" 8 | 9 | using Catch::Matchers::Equals; 10 | 11 | using namespace std::string_literals; 12 | 13 | TEST_CASE("arm tests", "[arm]") { 14 | std::ostringstream buf; 15 | 16 | SECTION("should encode single cps instruction") { 17 | arm::cps(buf, arm::Mode::USR); 18 | 19 | REQUIRE( buf.tellp() == 4 ); 20 | REQUIRE_THAT(buf.str(), Equals("\x10\x00\x02\xf1"s)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /languages/cpp/test/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | 3 | #include "catch.hpp" 4 | 5 | #include "arm.cpp" 6 | #include "mips.cpp" 7 | #include "x86.cpp" 8 | -------------------------------------------------------------------------------- /languages/cpp/test/mips.cpp: -------------------------------------------------------------------------------- 1 | #ifndef ASM_ALL_TESTS 2 | #define CATCH_CONFIG_MAIN 3 | #endif 4 | 5 | #include 6 | #include "catch.hpp" 7 | #include "../src/mips.cpp" 8 | 9 | using Catch::Matchers::Equals; 10 | 11 | using namespace std::string_literals; 12 | 13 | TEST_CASE("mips tests", "[mips]") { 14 | std::ostringstream buf; 15 | 16 | SECTION("should assemble single addi instruction") { 17 | mips::addi(buf, mips::Reg::T1, mips::Reg::T2, 0); 18 | 19 | REQUIRE( buf.tellp() == 4 ); 20 | REQUIRE_THAT(buf.str(), Equals("\x00\x00\x49\x21"s)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /languages/cpp/test/x86.cpp: -------------------------------------------------------------------------------- 1 | #ifndef ASM_ALL_TESTS 2 | #define CATCH_CONFIG_MAIN 3 | #endif 4 | 5 | #include 6 | #include "catch.hpp" 7 | #include "../src/x86.cpp" 8 | 9 | using Catch::Matchers::Equals; 10 | 11 | using namespace std::string_literals; 12 | 13 | TEST_CASE("x86 tests", "[x86]") { 14 | std::ostringstream buf; 15 | 16 | SECTION("should assemble single ret instruction") { 17 | x86::ret(buf); 18 | 19 | REQUIRE( buf.tellp() == 1 ); 20 | REQUIRE_THAT(buf.str(), Equals("\xc3"s)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /languages/csharp/Asm.Net.Tests/Arm.cs: -------------------------------------------------------------------------------- 1 | #if !USE_BUFFERS 2 | using System.IO; 3 | #endif 4 | using NUnit.Framework; 5 | using Asm.Net.Arm; 6 | 7 | namespace Asm.Net.Tests.Arm 8 | { 9 | [TestFixture] 10 | public class ArmTest 11 | { 12 | [Test(Description = "should encode single cps instruction")] 13 | public void should_encode_single_cps_instruction() 14 | { 15 | #if USE_BUFFERS 16 | BufferWriter stream = new BufferWriter(); 17 | #else 18 | using (MemoryStream stream = new MemoryStream()) 19 | #endif 20 | { 21 | stream.Cps(Mode.USR); 22 | 23 | Assert.AreEqual(stream.ToArray(), new byte[] { 16, 0, 2, 241 }); 24 | } 25 | } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /languages/csharp/Asm.Net.Tests/Asm.Net.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Exe 7 | netcoreapp2.0 8 | 9 | False 10 | False 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /languages/csharp/Asm.Net.Tests/BufferWriter.cs: -------------------------------------------------------------------------------- 1 | #if USE_BUFFERS 2 | using System; 3 | using System.Buffers; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Asm.Net.Tests 7 | { 8 | internal sealed class BufferWriter : IBufferWriter 9 | { 10 | private byte[] storage; 11 | private int index; 12 | 13 | public int Position 14 | { 15 | get => index; 16 | set => index = value; // Not checked, we only except 0 to be given. 17 | } 18 | 19 | public BufferWriter(int capacity = 16) 20 | { 21 | storage = new byte[capacity]; 22 | index = 0; 23 | } 24 | 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | public void Advance(int count) => index += count; 27 | 28 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 29 | public Memory GetMemory(int sizeHint = 0) 30 | { 31 | // Note: this method is never used anyway, but would most likely 32 | // perform worse. 33 | int needed = sizeHint > 0 ? index + sizeHint : index + 16; 34 | 35 | if (needed > storage.Length) 36 | Array.Resize(ref storage, needed); 37 | 38 | return new Memory(storage, index, storage.Length - index); 39 | } 40 | 41 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 42 | public Span GetSpan(int sizeHint = 0) 43 | { 44 | int needed = sizeHint > 0 ? index + sizeHint : index + 16; 45 | 46 | if (needed > storage.Length) 47 | Array.Resize(ref storage, needed); 48 | 49 | return new Span(storage, index, storage.Length - index); 50 | } 51 | 52 | public byte[] ToArray() 53 | { 54 | byte[] result = new byte[index]; 55 | 56 | Array.Copy(storage, result, index); 57 | 58 | return result; 59 | } 60 | } 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /languages/csharp/Asm.Net.Tests/Mips.cs: -------------------------------------------------------------------------------- 1 | #if !USE_BUFFERS 2 | using System.IO; 3 | #endif 4 | using NUnit.Framework; 5 | using Asm.Net.Mips; 6 | 7 | namespace Asm.Net.Tests.Mips 8 | { 9 | [TestFixture] 10 | public class MipsTest 11 | { 12 | [Test(Description = "should assemble single addi instruction")] 13 | public void should_assemble_single_addi_instruction() 14 | { 15 | #if USE_BUFFERS 16 | BufferWriter stream = new BufferWriter(); 17 | #else 18 | using (MemoryStream stream = new MemoryStream()) 19 | #endif 20 | { 21 | stream.Addi(Register.T1, Register.T2, 0); 22 | 23 | Assert.AreEqual(stream.ToArray(), new byte[] { 0, 0, 73, 33 }); 24 | } 25 | } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /languages/csharp/Asm.Net.Tests/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.IO; 3 | using BenchmarkDotNet.Attributes; 4 | using BenchmarkDotNet.Running; 5 | 6 | namespace Asm.Net.Tests 7 | { 8 | using Asm.Net.Arm; 9 | using Asm.Net.X86; 10 | 11 | [ShortRunJob, MemoryDiagnoser] 12 | public class Benchmarks 13 | { 14 | private const int N = 256; 15 | 16 | #if USE_BUFFERS 17 | private readonly BufferWriter buffer = new BufferWriter(N * 8); 18 | #else 19 | private readonly MemoryStream buffer = new MemoryStream(N * 8); 20 | #endif 21 | 22 | [Benchmark(OperationsPerInvoke = N)] 23 | public void X86Ret() 24 | { 25 | var buf = buffer; 26 | 27 | buf.Position = 0; 28 | 29 | for (int i = 0; i < N; i++) 30 | buf.Ret(); 31 | } 32 | 33 | [Benchmark(OperationsPerInvoke = N)] 34 | public void X86PopEax() 35 | { 36 | var buf = buffer; 37 | 38 | buf.Position = 0; 39 | 40 | for (int i = 0; i < N; i++) 41 | buf.Pop(Register32.EAX); 42 | } 43 | 44 | [Benchmark(OperationsPerInvoke = N)] 45 | public void X86PopR15() 46 | { 47 | var buf = buffer; 48 | 49 | buf.Position = 0; 50 | 51 | for (int i = 0; i < N; i++) 52 | buf.Pop(Register32.R15D); 53 | } 54 | 55 | [Benchmark(OperationsPerInvoke = N)] 56 | public void ArmCps() 57 | { 58 | var buf = buffer; 59 | 60 | buf.Position = 0; 61 | 62 | for (int i = 0; i < N; i++) 63 | buf.Cps(Mode.USR); 64 | } 65 | } 66 | 67 | public static class Program 68 | { 69 | public static void Main(string[] args) 70 | { 71 | BenchmarkRunner.Run(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /languages/csharp/Asm.Net.Tests/X86.cs: -------------------------------------------------------------------------------- 1 | #if !USE_BUFFERS 2 | using System.IO; 3 | #endif 4 | using NUnit.Framework; 5 | using Asm.Net.X86; 6 | 7 | namespace Asm.Net.Tests.X86 8 | { 9 | [TestFixture] 10 | public class X86Test 11 | { 12 | [Test(Description = "should assemble single ret instruction")] 13 | public void should_assemble_single_ret_instruction() 14 | { 15 | #if USE_BUFFERS 16 | BufferWriter stream = new BufferWriter(); 17 | #else 18 | using (MemoryStream stream = new MemoryStream()) 19 | #endif 20 | { 21 | stream.Ret(); 22 | 23 | Assert.AreEqual(stream.ToArray(), new byte[] { 195 }); 24 | } 25 | } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /languages/csharp/Asm.Net.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Asm.Net", "Asm.Net\Asm.Net.csproj", "{6DB5E7E6-5370-42C9-B11D-21EF11688491}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Asm.Net.Tests", "Asm.Net.Tests\Asm.Net.Tests.csproj", "{D12E702E-48DB-40C5-8AA4-A1B4E02747BD}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {6DB5E7E6-5370-42C9-B11D-21EF11688491}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {6DB5E7E6-5370-42C9-B11D-21EF11688491}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {6DB5E7E6-5370-42C9-B11D-21EF11688491}.Debug|x64.ActiveCfg = Debug|x64 26 | {6DB5E7E6-5370-42C9-B11D-21EF11688491}.Debug|x64.Build.0 = Debug|x64 27 | {6DB5E7E6-5370-42C9-B11D-21EF11688491}.Debug|x86.ActiveCfg = Debug|x86 28 | {6DB5E7E6-5370-42C9-B11D-21EF11688491}.Debug|x86.Build.0 = Debug|x86 29 | {6DB5E7E6-5370-42C9-B11D-21EF11688491}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {6DB5E7E6-5370-42C9-B11D-21EF11688491}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {6DB5E7E6-5370-42C9-B11D-21EF11688491}.Release|x64.ActiveCfg = Release|x64 32 | {6DB5E7E6-5370-42C9-B11D-21EF11688491}.Release|x64.Build.0 = Release|x64 33 | {6DB5E7E6-5370-42C9-B11D-21EF11688491}.Release|x86.ActiveCfg = Release|x86 34 | {6DB5E7E6-5370-42C9-B11D-21EF11688491}.Release|x86.Build.0 = Release|x86 35 | {D12E702E-48DB-40C5-8AA4-A1B4E02747BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {D12E702E-48DB-40C5-8AA4-A1B4E02747BD}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {D12E702E-48DB-40C5-8AA4-A1B4E02747BD}.Debug|x64.ActiveCfg = Debug|x64 38 | {D12E702E-48DB-40C5-8AA4-A1B4E02747BD}.Debug|x64.Build.0 = Debug|x64 39 | {D12E702E-48DB-40C5-8AA4-A1B4E02747BD}.Debug|x86.ActiveCfg = Debug|x86 40 | {D12E702E-48DB-40C5-8AA4-A1B4E02747BD}.Debug|x86.Build.0 = Debug|x86 41 | {D12E702E-48DB-40C5-8AA4-A1B4E02747BD}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {D12E702E-48DB-40C5-8AA4-A1B4E02747BD}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {D12E702E-48DB-40C5-8AA4-A1B4E02747BD}.Release|x64.ActiveCfg = Release|x64 44 | {D12E702E-48DB-40C5-8AA4-A1B4E02747BD}.Release|x64.Build.0 = Release|x64 45 | {D12E702E-48DB-40C5-8AA4-A1B4E02747BD}.Release|x86.ActiveCfg = Release|x86 46 | {D12E702E-48DB-40C5-8AA4-A1B4E02747BD}.Release|x86.Build.0 = Release|x86 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /languages/csharp/Asm.Net/Arm.cs: -------------------------------------------------------------------------------- 1 | namespace Asm.Net.Arm 2 | { 3 | /// 4 | /// Defines methods for emitting ARM instructions. 5 | /// 6 | public static partial class Arm 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /languages/csharp/Asm.Net/Asm.Net.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | netstandard1.3 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /languages/csharp/Asm.Net/Mips.cs: -------------------------------------------------------------------------------- 1 | namespace Asm.Net.Mips 2 | { 3 | /// 4 | /// Defines methods for emitting Mips instructions. 5 | /// 6 | public static partial class Mips 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /languages/csharp/Asm.Net/X86.cs: -------------------------------------------------------------------------------- 1 | namespace Asm.Net.X86 2 | { 3 | /// 4 | /// Defines methods for emitting x86 instructions. 5 | /// 6 | public static partial class X86 7 | { 8 | private static byte GetPrefix(ref Register16 r) 9 | { 10 | if (r.Value < 8) 11 | return r.Value; 12 | 13 | r = new Register16((byte)(r.Value - 8)); 14 | return 1; 15 | } 16 | 17 | private static byte GetPrefix(ref Register32 r) 18 | { 19 | if (r.Value < 8) 20 | return r.Value; 21 | 22 | r = new Register32((byte)(r.Value - 8)); 23 | return 1; 24 | } 25 | 26 | private static byte GetPrefix(ref Register64 r) 27 | { 28 | if (r.Value < 8) 29 | return r.Value; 30 | 31 | r = new Register64((byte)(r.Value - 8)); 32 | return 1; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /languages/csharp/Common.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | False 15 | 16 | 17 | 18 | Latest 19 | $(DefineConstants);USE_BUFFERS 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /languages/csharp/README.md: -------------------------------------------------------------------------------- 1 | C# 2 | == 3 | 4 | The C# version of ASM. can be used with any `Stream`, and is therefore 5 | compatible with many backends (`MemoryStream` for in-memory assembly, 6 | `FileStream` for assembling directly to a file, etc). 7 | 8 | Furthermore, it is intended to be fast, and is guaranteed to make **no 9 | allocation**, whether you use `IBufferWriter` or `Stream`. 10 | 11 | ## `UseBuffers` 12 | The `UseBuffers` MSBuild property instructs .NET to use `IBufferWriter` 13 | as first argument instead of `Stream` in all methods. 14 | 15 | Performance-wise, this seems equivalent to using `Stream`. 16 | 17 | ### Benchmarks 18 | Benchmarks are performed using [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet), and yield the following results. 19 | 20 | #### Setup 21 | ``` ini 22 | 23 | BenchmarkDotNet=v0.11.0, OS=Windows 10.0.17134.137 (1803/April2018Update/Redstone4) 24 | Intel Core i7-6700K CPU 4.00GHz (Max: 4.01GHz) (Skylake), 1 CPU, 8 logical and 4 physical cores 25 | Frequency=3914061 Hz, Resolution=255.4891 ns, Timer=TSC 26 | .NET Core SDK=2.1.302 27 | [Host] : .NET Core 2.1.2 (CoreCLR 4.6.26628.05, CoreFX 4.6.26629.01), 64bit RyuJIT 28 | ShortRun : .NET Core 2.1.2 (CoreCLR 4.6.26628.05, CoreFX 4.6.26629.01), 64bit RyuJIT 29 | 30 | Job=ShortRun IterationCount=3 LaunchCount=1 31 | WarmupCount=3 32 | 33 | ``` 34 | 35 | The following intructions are encoded: 36 | - `ret` (x86): Extremely simple, only one call to `WriteByte` with a constant value. 37 | - `pop eax` (x86): Less simple, but it still only performs one call to `WriteByte`. 38 | - `pop r15d` (x86): Even less simple, with one branch and two separate calls to `WriteByte`. 39 | - `cps #USR` (Arm): More computationaly-heavy, and performs a single call to `WriteLE`. 40 | 41 | #### [`BufferWriter`](Asm.Net.Tests/BufferWriter.cs) 42 | | Method | Mean | Error | StdDev | Allocated | 43 | |---------- |---------:|----------:|----------:|----------:| 44 | | X86Ret | 3.305 ns | 0.2265 ns | 0.0128 ns | 0 B | 45 | | X86PopEax | 3.895 ns | 0.0357 ns | 0.0020 ns | 0 B | 46 | | X86PopR15 | 7.298 ns | 1.7638 ns | 0.0997 ns | 0 B | 47 | | ArmCps | 4.132 ns | 0.9399 ns | 0.0531 ns | 0 B | 48 | 49 | Note however that these results highly depend on the backing `IBufferWriter`. For example, 50 | when `[MethodImpl(MethodImplOptions.AggressiveInlining)]` is added to the `GetSpan()` method, the 51 | benchmarks are **very** different: 52 | 53 | | Method | Mean | Error | StdDev | Allocated | 54 | |---------- |---------:|----------:|----------:|----------:| 55 | | X86Ret | 1.671 ns | 0.0773 ns | 0.0044 ns | 0 B | 56 | | X86PopEax | 2.160 ns | 0.4197 ns | 0.0237 ns | 0 B | 57 | | X86PopR15 | 3.907 ns | 0.4779 ns | 0.0270 ns | 0 B | 58 | | ArmCps | 2.052 ns | 0.1341 ns | 0.0076 ns | 0 B | 59 | 60 | #### `MemoryStream` 61 | | Method | Mean | Error | StdDev | Allocated | 62 | |---------- |----------:|----------:|----------:|----------:| 63 | | X86Ret | 3.356 ns | 1.4595 ns | 0.0825 ns | 0 B | 64 | | X86PopEax | 3.872 ns | 0.5465 ns | 0.0309 ns | 0 B | 65 | | X86PopR15 | 6.130 ns | 0.1557 ns | 0.0088 ns | 0 B | 66 | | ArmCps | 11.176 ns | 0.9019 ns | 0.0510 ns | 0 B | 67 | -------------------------------------------------------------------------------- /languages/go/README.md: -------------------------------------------------------------------------------- 1 | Go 2 | == 3 | 4 | To-do. 5 | -------------------------------------------------------------------------------- /languages/go/arm/arm_test.go: -------------------------------------------------------------------------------- 1 | package arm 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestShouldEncodeSingleCpsInstruction(t *testing.T) { 9 | buf := new(bytes.Buffer) 10 | 11 | Cps(buf, USRMode) 12 | 13 | if buf.Len() != 4 { 14 | t.Errorf("buf.Len() = %d; want 4", buf.Len()) 15 | } 16 | if !bytes.Equal(buf.Bytes(), []byte{0x10, 0x00, 0x02, 0xf1}) { 17 | t.Errorf("buf.Bytes() is not valid") 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /languages/go/generate.py: -------------------------------------------------------------------------------- 1 | from asmdot import * # pylint: disable=W0614 2 | from typing import Tuple 3 | 4 | header = '''// Automatically generated file. 5 | package {} 6 | 7 | import ( 8 | \t"bytes" 9 | \t"encoding/binary" 10 | \t"errors" 11 | \t"io" 12 | ) 13 | 14 | // Bypass unused module error if we don't have assertions. 15 | var _ = errors.New 16 | 17 | var ( 18 | \tinterbuf = [8]byte{{}} 19 | \tbyteOrder = binary.LittleEndian 20 | \tswappedByteOrder = binary.BigEndian 21 | ) 22 | 23 | func write16(w io.Writer, x uint16) error {{ 24 | \tbyteOrder.PutUint16(interbuf[:], x) 25 | \t_, err := w.Write(interbuf[:2]) 26 | \treturn err 27 | }} 28 | 29 | func writeSwapped16(w io.Writer, x uint16) error {{ 30 | \tswappedByteOrder.PutUint16(interbuf[:], x) 31 | \t_, err := w.Write(interbuf[:2]) 32 | \treturn err 33 | }} 34 | 35 | func write32(w io.Writer, x uint32) error {{ 36 | \tbyteOrder.PutUint32(interbuf[:], x) 37 | \t_, err := w.Write(interbuf[:4]) 38 | \treturn err 39 | }} 40 | 41 | func writeSwapped32(w io.Writer, x uint32) error {{ 42 | \tswappedByteOrder.PutUint32(interbuf[:], x) 43 | \t_, err := w.Write(interbuf[:4]) 44 | \treturn err 45 | }} 46 | 47 | func write64(w io.Writer, x uint64) error {{ 48 | \tbyteOrder.PutUint64(interbuf[:], x) 49 | \t_, err := w.Write(interbuf[:]) 50 | \treturn err 51 | }} 52 | 53 | func writeSwapped64(w io.Writer, x uint64) error {{ 54 | \tswappedByteOrder.PutUint64(interbuf[:], x) 55 | \t_, err := w.Write(interbuf[:]) 56 | \treturn err 57 | }} 58 | 59 | ''' 60 | 61 | header_x86 = ''' 62 | func getPrefix16(r *Reg16) byte { 63 | if uint8(*r) < 8 { 64 | return byte(*r) 65 | } 66 | 67 | *r = Reg16(uint8(*r) - 8) 68 | return 1 69 | } 70 | 71 | func getPrefix32(r *Reg32) byte { 72 | if uint8(*r) < 8 { 73 | return byte(*r) 74 | } 75 | 76 | *r = Reg32(uint8(*r) - 8) 77 | return 1 78 | } 79 | 80 | func getPrefix64(r *Reg64) byte { 81 | if uint8(*r) < 8 { 82 | return byte(*r) 83 | } 84 | 85 | *r = Reg64(uint8(*r) - 8) 86 | return 1 87 | } 88 | ''' 89 | 90 | def _camel_case(s: str) -> str: 91 | return s[0] + s.title().replace('_', '')[1:] 92 | 93 | def _pascal_case(s: str) -> str: 94 | return s.title().replace('_', '') 95 | 96 | @handle_command_line() 97 | class GoEmitter(Emitter): 98 | modified_list: List[str] = [] 99 | var_map: Dict[str, Tuple[IrType, IrType]] = {} 100 | 101 | @property 102 | def language(self): 103 | return 'go' 104 | 105 | @property 106 | def filename(self): 107 | return f'{self.arch}/{self.arch}.go' 108 | 109 | @property 110 | def test_filename(self): 111 | return f'{self.arch}/{self.arch}_test.go' 112 | 113 | 114 | def get_function_name(self, function: Function) -> str: 115 | return _pascal_case(function.fullname) 116 | 117 | def get_operator(self, op: Operator) -> str: 118 | if op == OP_BITWISE_XOR: 119 | return '!=' 120 | else: 121 | return op.op 122 | 123 | def get_builtin_name(self, builtin: Builtin) -> str: 124 | if builtin == BUILTIN_X86_PREFIX: 125 | return 'getPrefix' 126 | else: 127 | return builtin.name 128 | 129 | 130 | def __init__(self, args: Namespace, arch: str) -> None: 131 | super().__init__(args, arch) 132 | 133 | self.indent = Indent('\t') 134 | 135 | 136 | def write_header(self): 137 | self.write(header.format(self.arch)) 138 | 139 | if self.arch == 'x86': 140 | self.write(header_x86) 141 | 142 | def write_separator(self): 143 | self.writeline() 144 | 145 | 146 | def write_expr(self, expr: Expression): 147 | if isinstance(expr, Binary): 148 | self.write('(', expr.l, ' ', expr.op, ' ', expr.r, ')') 149 | 150 | elif isinstance(expr, Unary): 151 | self.write(expr.op, expr.v) 152 | 153 | elif isinstance(expr, Ternary): 154 | self.write('(func() { if ', expr.condition, ' { return ', expr.consequence, ' } else { return ', expr.alternative, ' })()') 155 | 156 | elif isinstance(expr, Var): 157 | name = _camel_case(expr.name) 158 | 159 | if name in self.modified_list: 160 | name = name + '_' 161 | else: 162 | name = f'{self.var_map[expr.name][1]}({name})' 163 | 164 | self.write(name) 165 | 166 | elif isinstance(expr, Call): 167 | if self.var_map[expr.args[0].name][0].id == 'Reg16': 168 | self.write(expr.builtin, '16(&', expr.args[0].name, ')') 169 | elif self.var_map[expr.args[0].name][0].id == 'Reg32': 170 | self.write(expr.builtin, '32(&', expr.args[0].name, ')') 171 | elif self.var_map[expr.args[0].name][0].id == 'Reg64': 172 | self.write(expr.builtin, '64(&', expr.args[0].name, ')') 173 | 174 | elif isinstance(expr, Literal): 175 | self.write(expr.value) 176 | 177 | else: 178 | raise UnsupportedExpression(expr) 179 | 180 | def write_stmt(self, stmt: Statement): 181 | if isinstance(stmt, Assign): 182 | self.writelinei(stmt.variable, ' = ', stmt.value) 183 | 184 | elif isinstance(stmt, Conditional): 185 | self.writelinei('if ', stmt.condition, ' {') 186 | 187 | with self.indent.further(): 188 | self.write_stmt(stmt.consequence) 189 | 190 | if stmt.alternative: 191 | self.writelinei('} else {') 192 | 193 | with self.indent.further(): 194 | self.write_stmt(stmt.alternative) 195 | 196 | self.writelinei('}') 197 | 198 | elif isinstance(stmt, Block): 199 | for s in stmt.statements: 200 | self.write_stmt(s) 201 | 202 | elif isinstance(stmt, Set): 203 | if stmt.type.under in (TYPE_U8, TYPE_I8): 204 | self.writelinei('if err := w.WriteByte(byte(', stmt.value, ')); err != nil {') 205 | else: 206 | if self.bigendian: 207 | write = f'writeSwapped{stmt.type.under.size * 8}' 208 | else: 209 | write = f'write{stmt.type.under.size * 8}' 210 | 211 | self.writelinei('if err := ', write, '(w, uint', stmt.type.under.size * 8, '(', stmt.value, ')); err != nil {') 212 | 213 | self.writelinei('\treturn err') 214 | self.writelinei('}') 215 | 216 | elif isinstance(stmt, Define): 217 | self.writelinei(f'{stmt.name} := ', stmt.value) 218 | 219 | else: 220 | raise UnsupportedStatement(stmt) 221 | 222 | def write_function(self, fun: Function): 223 | self.modified_list.clear() 224 | self.write(f'func {fun.name}(w *bytes.Buffer') 225 | 226 | for name, typ, usagetyp in fun.params: 227 | self.write(f', {_camel_case(name)} {typ}') 228 | self.var_map[name] = typ, usagetyp 229 | 230 | self.write(') error {\n') 231 | 232 | self.indent += 1 233 | 234 | for name, typ, usagetyp in fun.params: 235 | if typ is TYPE_BOOL and usagetyp is not TYPE_BOOL: 236 | name = _camel_case(name) 237 | 238 | self.writelinei(f'var {name}_ {usagetyp} = 0') 239 | self.writelinei(f'if {name} {{') 240 | self.writelinei(f'\t{name}_ = 1') 241 | self.writelinei( '}') 242 | self.modified_list.append(name) 243 | 244 | for condition in fun.conditions: 245 | self.writelinei('if !', condition, ' {') 246 | self.writelinei('\treturn errors.New("Failed precondition: ', condition, '.")') 247 | self.writelinei('}') 248 | 249 | for stmt in fun.body: 250 | self.write_stmt(stmt) 251 | 252 | self.writelinei('return nil') 253 | self.write('}\n\n') 254 | self.indent -= 1 255 | 256 | 257 | def write_decl(self, decl: Declaration): 258 | if isinstance(decl, Enumeration): 259 | self.writeline('// ', decl.descr) 260 | self.writeline('type ', decl.type, ' ', decl.type.underlying, '\n') 261 | self.writeline('const (') 262 | 263 | for _, value, descr, fullname in decl.members + decl.additional_members: 264 | self.writeline('\t// ', descr) 265 | self.writeline('\t', fullname, ' ', decl.type, ' = ', value) 266 | 267 | self.writeline(')') 268 | 269 | elif isinstance(decl, DistinctType): 270 | self.writeline('// ', decl.descr) 271 | self.writeline('type ', decl.type, ' ', decl.type.underlying, '\n') 272 | self.writeline('const (') 273 | 274 | for name, value in decl.constants: 275 | self.writeline('\t', name.upper(), ' ', decl.type, ' = ', value) 276 | 277 | self.writeline(')') 278 | 279 | else: 280 | raise UnsupportedDeclaration(decl) 281 | 282 | self.writeline() 283 | 284 | 285 | def write_test_header(self): 286 | self.writeline('package ', self.arch, '\n') 287 | self.writeline('import (\n\t"bytes"\n\t"testing"\n)\n') 288 | 289 | def write_test(self, test: TestCase): 290 | self.write('func Test', _pascal_case(test.name.replace(' ', '_')), '(t *testing.T) {\n') 291 | self.indent += 1 292 | 293 | self.writelinei('buf := new(bytes.Buffer)\n') 294 | 295 | def arg_str(arg: TestCaseArgument): 296 | if isinstance(arg, ArgConstant): 297 | return f'{arg.const.name.upper()}' 298 | if isinstance(arg, ArgEnumMember): 299 | return arg.member.fullname 300 | elif isinstance(arg, ArgInteger): 301 | return str(arg.value) 302 | else: 303 | raise UnsupportedTestArgument(arg) 304 | 305 | for func, args in test.calls: 306 | self.writei(func.name, '(buf') 307 | 308 | for arg in args: 309 | self.write(', ', arg_str(arg)) 310 | 311 | self.write(')\n') 312 | 313 | self.writeline() 314 | self.writelinei('if buf.Len() != ', len(test.expected), ' {') 315 | self.writelinei('\tt.Errorf("buf.Len() = %d; want ', len(test.expected), '", buf.Len())') 316 | self.writelinei('}') 317 | self.writelinei('if !bytes.Equal(buf.Bytes(), []byte{', test.expected_bytes, '}) {') 318 | self.writelinei('\tt.Errorf("buf.Bytes() is not valid")') 319 | self.writelinei('}') 320 | self.indent -= 1 321 | 322 | self.write('}\n\n') 323 | -------------------------------------------------------------------------------- /languages/go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/71/asmdot 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /languages/go/mips/mips_test.go: -------------------------------------------------------------------------------- 1 | package mips 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestShouldAssembleSingleAddiInstruction(t *testing.T) { 9 | buf := new(bytes.Buffer) 10 | 11 | Addi(buf, T1, T2, 0) 12 | 13 | if buf.Len() != 4 { 14 | t.Errorf("buf.Len() = %d; want 4", buf.Len()) 15 | } 16 | if !bytes.Equal(buf.Bytes(), []byte{0x00, 0x00, 0x49, 0x21}) { 17 | t.Errorf("buf.Bytes() is not valid") 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /languages/go/x86/x86_test.go: -------------------------------------------------------------------------------- 1 | package x86 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestShouldAssembleSingleRetInstruction(t *testing.T) { 9 | buf := new(bytes.Buffer) 10 | 11 | Ret(buf) 12 | 13 | if buf.Len() != 1 { 14 | t.Errorf("buf.Len() = %d; want 1", buf.Len()) 15 | } 16 | if !bytes.Equal(buf.Bytes(), []byte{0xc3}) { 17 | t.Errorf("buf.Bytes() is not valid") 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /languages/haskell/Asm.cabal: -------------------------------------------------------------------------------- 1 | -- Initial Asm.cabal generated by cabal init. For further documentation, 2 | -- see http://haskell.org/cabal/users-guide/ 3 | 4 | name: asm 5 | version: 0.1.0.0 6 | synopsis: Lightweight assembler for ARM and x86. 7 | homepage: https://github.com/6A/asmdot 8 | license: MIT 9 | author: Grégoire Geis 10 | maintainer: git@gregoirege.is 11 | copyright: 2018 (C) Grégoire Geis 12 | bug-reports: https://github.com/6A/issues 13 | category: System 14 | build-type: Simple 15 | cabal-version: >=1.10 16 | 17 | library 18 | exposed-modules: Asm.Arm, Asm.Mips, Asm.X86 19 | other-modules: Asm.Internal.Arm, Asm.Internal.Mips, Asm.Internal.X86 20 | build-depends: base == 4.*, bytestring == 0.10.* 21 | hs-source-dirs: src 22 | default-language: Haskell2010 23 | ghc-options: -Wno-overlapping-patterns 24 | 25 | test-suite spec 26 | type: exitcode-stdio-1.0 27 | main-is: Spec.hs 28 | build-depends: base == 4.*, asm, hspec == 2.* 29 | hs-source-dirs: test 30 | default-language: Haskell2010 31 | -------------------------------------------------------------------------------- /languages/haskell/README.md: -------------------------------------------------------------------------------- 1 | Haskell 2 | ======= 3 | 4 | To-do. 5 | -------------------------------------------------------------------------------- /languages/haskell/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /languages/haskell/generate.py: -------------------------------------------------------------------------------- 1 | from asmdot import * # pylint: disable=W0614 2 | 3 | @handle_command_line() 4 | class HaskellEmitter(Emitter): 5 | is_first_statement: bool = False 6 | 7 | @property 8 | def language(self): 9 | return 'haskell' 10 | 11 | @property 12 | def filename(self): 13 | return f'src/Asm/Internal/{self.arch.capitalize()}.hs' 14 | 15 | @property 16 | def test_filename(self): 17 | return f'test/Asm/{self.arch.capitalize()}Spec.hs' 18 | 19 | 20 | def __init__(self, args: Namespace, arch: str) -> None: 21 | super().__init__(args, arch) 22 | 23 | self.indent = Indent(' ') 24 | 25 | 26 | def get_type_name(self, ty: IrType) -> str: 27 | return replace_pattern({ 28 | r'bool': r'Bool', 29 | r'uint(\d+)': r'Word\1', 30 | r'int(\d+)': r'Int\1', 31 | r'Reg(\d*)': r'Register\1' 32 | }, ty.id) 33 | 34 | def get_operator(self, op: Operator) -> str: 35 | dic = { 36 | OP_BITWISE_AND: '.&.', 37 | OP_BITWISE_OR : '.|.', 38 | OP_BITWISE_XOR: '`xor`', 39 | OP_SHL: '`shiftL`', 40 | OP_SHR: '`shiftR`' 41 | } 42 | 43 | if op in dic: 44 | return dic[op] 45 | else: 46 | return op.op 47 | 48 | def get_function_name(self, function: Function) -> str: 49 | if function.fullname in ('div'): 50 | return function.fullname + '_' 51 | else: 52 | return function.fullname 53 | 54 | 55 | def write_header(self): 56 | self.write('module Asm.Internal.', self.arch.capitalize(), ' where\n\n') 57 | self.indent += 1 58 | 59 | self.writei('import Control.Exception (assert)\n') 60 | self.writei('import Data.Bits\n') 61 | self.writei('import Data.ByteString.Builder\n') 62 | self.writei('import Data.Int\n') 63 | self.writei('import Data.Semigroup (Semigroup((<>)))\n') 64 | self.writei('import Data.Word\n\n') 65 | 66 | def write_footer(self): 67 | self.indent -= 1 68 | 69 | 70 | def write_expr(self, expr: Expression): 71 | if isinstance(expr, Binary): 72 | self.write('(', expr.l, ' ', expr.op, ' ', expr.r, ')') 73 | 74 | elif isinstance(expr, Unary): 75 | self.write(expr.op, expr.v) 76 | 77 | elif isinstance(expr, Ternary): 78 | self.write('(if ', expr.condition, ' then ', expr.consequence, ' else ', expr.alternative, ')') 79 | 80 | elif isinstance(expr, Var): 81 | self.write(expr.name) 82 | 83 | elif isinstance(expr, Call): 84 | self.write(expr.builtin, ' ', join_any(' ', expr.args)) 85 | 86 | elif isinstance(expr, Literal): 87 | self.write(expr.value) 88 | 89 | else: 90 | raise UnsupportedExpression(expr) 91 | 92 | def write_stmt(self, stmt: Statement): 93 | deindent = True 94 | 95 | if self.is_first_statement: 96 | self.is_first_statement = False 97 | deindent = False 98 | else: 99 | self.writelinei('<>') 100 | self.indent += 1 101 | 102 | if isinstance(stmt, Assign): 103 | self.writelinei(stmt.variable, ' = ', stmt.value) 104 | 105 | elif isinstance(stmt, Conditional): 106 | self.writelinei('if ', stmt.condition, ' then') 107 | 108 | with self.indent.further(): 109 | self.is_first_statement = True 110 | self.write_stmt(stmt.consequence) 111 | self.is_first_statement = False 112 | 113 | self.writelinei('else') 114 | 115 | with self.indent.further(): 116 | self.is_first_statement = True 117 | 118 | if stmt.alternative: 119 | self.write_stmt(stmt.alternative) 120 | else: 121 | self.writelinei('mempty') 122 | 123 | self.is_first_statement = False 124 | 125 | 126 | elif isinstance(stmt, Block): 127 | self.is_first_statement = True 128 | 129 | for s in stmt.statements: 130 | self.write_stmt(s) 131 | 132 | self.is_first_statement = False 133 | 134 | elif isinstance(stmt, Set): 135 | typ = stmt.type.under 136 | endian = 'BE ' if self.bigendian else 'LE ' 137 | 138 | if typ is TYPE_I8: self.writei('int8 ') 139 | elif typ is TYPE_U8: self.writei('word8 ') 140 | elif typ.id.startswith('u'): self.writei('word', typ.size * 8, endian) 141 | else: self.writei('int', typ.size * 8, endian) 142 | 143 | self.writeline(stmt.value) 144 | 145 | elif isinstance(stmt, Define): 146 | self.writelinei('let ', stmt.name, ' = ', stmt.value, ' in') 147 | 148 | else: 149 | raise UnsupportedStatement(stmt) 150 | 151 | if deindent: 152 | self.indent -= 1 153 | 154 | def write_function(self, fun: Function): 155 | self.is_first_statement = True 156 | self.writei(fun.name, ' :: ') 157 | 158 | for _, typ, _ in fun.params: 159 | self.write(f'{typ} -> ') 160 | 161 | self.write('Builder\n') 162 | self.writei(fun.name, ' ', ' '.join([ name for name, _, _ in fun.params ]), ' =\n') 163 | self.indent += 1 164 | 165 | for name, typ, _ in fun.params: 166 | # Deconstruct distinct types. 167 | if typ.underlying is not None: 168 | self.writelinei(f'let {name} = fromIntegral {name} in') 169 | else: 170 | self.writelinei(f'let {name} = fromIntegral {name} in') 171 | 172 | for condition in fun.conditions: 173 | self.writei('assert ', condition, '\n') 174 | 175 | for stmt in fun.body: 176 | self.write_stmt(stmt) 177 | 178 | self.write('\n\n') 179 | self.indent -= 1 180 | 181 | 182 | def write_decl(self, decl: Declaration): 183 | if isinstance(decl, Enumeration): 184 | self.writei('-- | ', decl.descr, '\n') 185 | self.writei('data ', decl.type, ' =\n') 186 | self.indent += 1 187 | 188 | prefix = ' ' 189 | 190 | for _, _, descr, fullname in decl.members + decl.additional_members: 191 | self.writei(prefix, fullname, ' -- ^ ', descr, '\n') 192 | 193 | if prefix == ' ': 194 | prefix = '| ' 195 | 196 | self.writei(' deriving (Eq, Show)\n\n') 197 | self.indent -= 1 198 | self.writei('instance Enum ', decl.type, ' where\n') 199 | 200 | for _, value, _, fullname in decl.members + decl.additional_members: 201 | self.writei(' fromEnum ', fullname, ' = ', value, '\n') 202 | 203 | self.write('\n') 204 | 205 | for _, value, _, fullname in decl.members + decl.additional_members: 206 | self.writei(' toEnum ', value, ' = ', fullname, '\n') 207 | 208 | self.write('\n\n') 209 | 210 | elif isinstance(decl, DistinctType): 211 | self.writei('-- | ', decl.descr, '\n') 212 | self.writei('newtype ', decl.type, ' = ', decl.type, ' ', decl.type.underlying, '\n\n') 213 | 214 | if decl.constants: 215 | self.writei(', '.join([ name for name, _ in decl.constants ]), ' :: ', decl.type, '\n') 216 | 217 | for name, value in decl.constants: 218 | self.writei(name, ' = ', decl.type, ' ', value, '\n') 219 | 220 | self.write('\n\n') 221 | 222 | else: 223 | raise UnsupportedDeclaration(decl) 224 | 225 | 226 | def write_test_header(self): 227 | self.write(f'import Asm.{self.arch.capitalize()}\nimport Test.Hspec\n\n') 228 | self.write(f'{self.arch}Spec = do\n') 229 | self.indent += 1 230 | 231 | def write_test_footer(self): 232 | self.indent -= 1 233 | 234 | def write_test(self, test: TestCase): 235 | self.writei('it "', test.name, '" $\n') 236 | self.indent += 1 237 | 238 | self.writelinei('pending') 239 | self.writeline() 240 | 241 | self.indent -= 1 242 | -------------------------------------------------------------------------------- /languages/haskell/src/Asm/Arm.hs: -------------------------------------------------------------------------------- 1 | module Asm.Arm ( module Asm.Internal.Arm ) where 2 | 3 | import Asm.Internal.Arm 4 | -------------------------------------------------------------------------------- /languages/haskell/src/Asm/Mips.hs: -------------------------------------------------------------------------------- 1 | module Asm.Mips ( module Asm.Internal.Mips ) where 2 | 3 | import Asm.Internal.Mips 4 | -------------------------------------------------------------------------------- /languages/haskell/src/Asm/X86.hs: -------------------------------------------------------------------------------- 1 | module Asm.X86 ( module Asm.Internal.X86 ) where 2 | 3 | import Asm.Internal.X86 4 | -------------------------------------------------------------------------------- /languages/haskell/test/Asm/ArmSpec.hs: -------------------------------------------------------------------------------- 1 | import Asm.Arm 2 | import Test.Hspec 3 | 4 | armSpec = do 5 | it "should encode single cps instruction" $ 6 | pending 7 | 8 | -------------------------------------------------------------------------------- /languages/haskell/test/Asm/MipsSpec.hs: -------------------------------------------------------------------------------- 1 | import Asm.Mips 2 | import Test.Hspec 3 | 4 | mipsSpec = do 5 | it "should assemble single addi instruction" $ 6 | pending 7 | 8 | -------------------------------------------------------------------------------- /languages/haskell/test/Asm/X86Spec.hs: -------------------------------------------------------------------------------- 1 | import Asm.X86 2 | import Test.Hspec 3 | 4 | x86Spec = do 5 | it "should assemble single ret instruction" $ 6 | pending 7 | 8 | -------------------------------------------------------------------------------- /languages/haskell/test/Spec.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -F -pgmF hspec-discover #-} 2 | -------------------------------------------------------------------------------- /languages/javascript/README.md: -------------------------------------------------------------------------------- 1 | JavaScript 2 | ========== 3 | 4 | To-do. 5 | -------------------------------------------------------------------------------- /languages/javascript/generate.py: -------------------------------------------------------------------------------- 1 | from asmdot import * # pylint: disable=W0614 2 | 3 | separator = ''' 4 | 5 | export class {}Assembler {{ 6 | private ofs: number = 0; 7 | 8 | public constructor(readonly buffer: DataView) {{}} 9 | 10 | public get offset(): number {{ return this.ofs; }} 11 | public set offset(ofs: number) {{ 12 | if (ofs < 0 || ofs > this.buffer.byteLength) 13 | throw RangeError(); 14 | 15 | this.ofs = ofs; 16 | }} 17 | 18 | ''' 19 | 20 | @handle_command_line() 21 | class JavaScriptEmitter(Emitter): 22 | cast_params: List[str] = [] 23 | declaration_names: List[str] = [] 24 | 25 | 26 | @property 27 | def language(self): 28 | return 'javascript' 29 | 30 | @property 31 | def filename(self): 32 | return f'src/{self.arch}.ts' 33 | 34 | @property 35 | def test_filename(self): 36 | return f'test/{self.arch}.test.ts' 37 | 38 | 39 | def __init__(self, args: Namespace, arch: str) -> None: 40 | super().__init__(args, arch) 41 | 42 | self.indent = Indent(' ') 43 | 44 | 45 | def get_function_name(self, function: Function) -> str: 46 | return function.fullname 47 | 48 | def get_type_name(self, ty: IrType) -> str: 49 | return replace_pattern({ 50 | 'bool': 'boolean', 51 | r'u?int\d+': 'number' 52 | }, ty.id) 53 | 54 | def get_builtin_name(self, builtin: Builtin) -> str: 55 | if builtin is BUILTIN_X86_PREFIX: 56 | return 'getPrefix' 57 | else: 58 | return builtin.name 59 | 60 | 61 | def write_header(self): 62 | self.declaration_names.append(f'{self.arch.capitalize()}Assembler') 63 | 64 | def write_separator(self): 65 | self.write(separator.format(self.arch.capitalize())) 66 | self.indent += 1 67 | 68 | def write_footer(self): 69 | self.indent -= 1 70 | self.write('}\n') 71 | 72 | 73 | def write_expr(self, expr: Expression): 74 | if isinstance(expr, Binary): 75 | self.write('(', expr.l, ' ', expr.op, ' ', expr.r, ')') 76 | 77 | elif isinstance(expr, Unary): 78 | self.write(expr.op, expr.v) 79 | 80 | elif isinstance(expr, Ternary): 81 | self.write('(', expr.condition, ' ? ', expr.consequence, ' : ', expr.alternative, ')') 82 | 83 | elif isinstance(expr, Var): 84 | if expr.name in self.cast_params: 85 | self.write('(', expr.name, ' ? 1 : 0)') 86 | else: 87 | self.write(expr.name) 88 | 89 | elif isinstance(expr, Call): 90 | self.write(expr.builtin, '(', join_any(', ', expr.args), ')') 91 | 92 | elif isinstance(expr, Literal): 93 | self.write(expr.value) 94 | 95 | else: 96 | raise UnsupportedExpression(expr) 97 | 98 | def write_stmt(self, stmt: Statement): 99 | if isinstance(stmt, Assign): 100 | self.writelinei(stmt.variable, ' = ', stmt.value) 101 | 102 | elif isinstance(stmt, Conditional): 103 | self.writelinei('if (', stmt.condition, ') {') 104 | 105 | with self.indent.further(): 106 | self.write_stmt(stmt.consequence) 107 | 108 | if stmt.alternative: 109 | self.writelinei('} else {') 110 | 111 | with self.indent.further(): 112 | self.write_stmt(stmt.alternative) 113 | else: 114 | self.writelinei('}') 115 | 116 | elif isinstance(stmt, Block): 117 | for s in stmt.statements: 118 | self.write_stmt(s) 119 | 120 | elif isinstance(stmt, Set): 121 | call = 'this.buffer.set' 122 | 123 | if stmt.type.under in (TYPE_U8, TYPE_U16, TYPE_U32, TYPE_U64): 124 | call += 'Uint' 125 | else: 126 | call += 'Int' 127 | 128 | call += str(stmt.type.under.size * 8) 129 | 130 | if stmt.type.under in (TYPE_U8, TYPE_I8): 131 | self.writelinei(call, '(this.ofs, ', stmt.value, ');') 132 | else: 133 | endian = 'false' if self.bigendian else 'true' 134 | 135 | self.writelinei(call, '(this.ofs, ', stmt.value, ', ', endian, ');') 136 | 137 | self.writelinei('this.ofs += ', stmt.type.size, ';') 138 | 139 | elif isinstance(stmt, Define): 140 | self.writelinei(f'let {stmt.name} = ', stmt.value, ';') 141 | 142 | else: 143 | raise UnsupportedStatement(stmt) 144 | 145 | def write_function(self, fun: Function): 146 | self.writelinei('// ', fun.descr) 147 | self.writei('public ', fun.name, '(') 148 | self.write(', '.join([ f'{name}: {typ}' for name, typ, _ in fun.params ])) 149 | self.write(') {\n') 150 | self.indent += 1 151 | 152 | for name, typ, usagetyp in fun.params: 153 | if typ is TYPE_BOOL and usagetyp is not TYPE_BOOL: 154 | self.cast_params.append(name) 155 | 156 | for condition in fun.conditions: 157 | self.writelinei('if (!', condition, ') throw Error();') 158 | 159 | for stmt in fun.body: 160 | self.write_stmt(stmt) 161 | 162 | self.indent -= 1 163 | self.writei('}\n\n') 164 | 165 | 166 | def write_decl(self, decl: Declaration): 167 | if isinstance(decl, Enumeration): 168 | self.declaration_names.append(str(decl.type)) 169 | 170 | self.write('// ', decl.descr, '\n') 171 | self.write('export const enum ', decl.type, ' {\n') 172 | self.indent += 1 173 | 174 | for name, value, descr, _ in decl.members + decl.additional_members: 175 | self.writei('// ', descr, '\n') 176 | self.writei(name, ' = ', value, ',\n') 177 | 178 | self.indent -= 1 179 | self.write('}\n\n') 180 | 181 | elif isinstance(decl, DistinctType): 182 | self.declaration_names.append(str(decl.type)) 183 | self.write('// ', decl.descr, '\n') 184 | 185 | if decl.constants: 186 | self.write('export const enum ', decl.type, ' {\n') 187 | self.indent += 1 188 | 189 | for name, value in decl.constants: 190 | self.writei(name.upper(), ' = ', value, ',\n') 191 | 192 | self.indent -= 1 193 | self.write('}\n') 194 | else: 195 | self.write('export type ', decl.type, ' = ', decl.type.underlying, ';\n') 196 | 197 | self.write('\n') 198 | 199 | else: 200 | raise UnsupportedDeclaration(decl) 201 | 202 | 203 | def write_test_header(self): 204 | imports = ', '.join(self.declaration_names) 205 | 206 | self.write( 'import { arrayBufferToArray } from "./helpers";\n') 207 | self.write(f'import {{ {imports} }} from "../src/{self.arch}";\n\n') 208 | 209 | def write_test(self, test: TestCase): 210 | self.writelinei('test("', test.name, '", () => {') 211 | self.indent += 1 212 | 213 | self.writelinei('const arrayBuffer = new ArrayBuffer(', len(test.expected), ');') 214 | self.writelinei('const dataView = new DataView(arrayBuffer);\n') 215 | self.writelinei('const buffer = new ', self.arch.capitalize(), 'Assembler(dataView);\n') 216 | 217 | def arg_str(arg: TestCaseArgument): 218 | if isinstance(arg, ArgConstant): 219 | return f'{arg.type.type}.{arg.const.name.upper()}' 220 | if isinstance(arg, ArgEnumMember): 221 | return f'{arg.enum.type}.{arg.member.name}' 222 | elif isinstance(arg, ArgInteger): 223 | return str(arg.value) 224 | else: 225 | raise UnsupportedTestArgument(arg) 226 | 227 | for func, args in test.calls: 228 | args_str = ', '.join([ arg_str(arg) for arg in args ]) 229 | 230 | self.writelinei('buffer.', func.name, '(', args_str, ');') 231 | 232 | self.writeline() 233 | self.writelinei('expect(arrayBufferToArray(arrayBuffer)).toEqual([ ', join_any(', ', test.expected), ' ]);') 234 | self.indent -= 1 235 | 236 | self.writeline('});\n') 237 | -------------------------------------------------------------------------------- /languages/javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asmdot", 3 | "version": "0.1.0", 4 | "description": "Lightweight assembler.", 5 | "main": "src/index.ts", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/6A/asmdot.git" 12 | }, 13 | "keywords": [ 14 | "assembler", 15 | "arm", 16 | "mips", 17 | "x86" 18 | ], 19 | "author": "Grégoire Geis", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/6A/asmdot/issues" 23 | }, 24 | "homepage": "https://github.com/6A/asmdot#readme", 25 | "devDependencies": { 26 | "@types/jest": "^23.1.3", 27 | "jest": "^23.2.0", 28 | "ts-jest": "^22.4.6" 29 | }, 30 | "jest": { 31 | "moduleFileExtensions": [ 32 | "ts", 33 | "js" 34 | ], 35 | "transform": { 36 | "^.+\\.ts$": "ts-jest" 37 | }, 38 | "globals": { 39 | "ts-jest": { 40 | "tsConfigFile": "tsconfig.json" 41 | } 42 | }, 43 | "testEnvironment": "node", 44 | "testRegex": "test/.+\\.test\\.ts$" 45 | }, 46 | "dependencies": { 47 | "typescript": "^2.9.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /languages/javascript/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as Arm from "./arm"; 2 | import * as Mips from "./mips"; 3 | import * as X86 from "./x86"; 4 | 5 | export { Arm, Mips, X86 }; 6 | -------------------------------------------------------------------------------- /languages/javascript/test/arm.test.ts: -------------------------------------------------------------------------------- 1 | import { arrayBufferToArray } from "./helpers"; 2 | import { ArmAssembler, Reg, RegList, Coprocessor, Condition, Mode, Shift, Rotation, FieldMask, InterruptFlags, Addressing, OffsetMode } from "../src/arm"; 3 | 4 | test("should encode single cps instruction", () => { 5 | const arrayBuffer = new ArrayBuffer(4); 6 | const dataView = new DataView(arrayBuffer); 7 | 8 | const buffer = new ArmAssembler(dataView); 9 | 10 | buffer.cps(Mode.USR); 11 | 12 | expect(arrayBufferToArray(arrayBuffer)).toEqual([ 16, 0, 2, 241 ]); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /languages/javascript/test/helpers.ts: -------------------------------------------------------------------------------- 1 | 2 | export function arrayBufferToArray(buffer: ArrayBuffer): Array { 3 | return Array.prototype.slice.call(new Uint8Array(buffer)); 4 | } 5 | -------------------------------------------------------------------------------- /languages/javascript/test/mips.test.ts: -------------------------------------------------------------------------------- 1 | import { arrayBufferToArray } from "./helpers"; 2 | import { ArmAssembler, Reg, RegList, Coprocessor, Condition, Mode, Shift, Rotation, FieldMask, InterruptFlags, Addressing, OffsetMode, MipsAssembler, Reg } from "../src/mips"; 3 | 4 | test("should assemble single addi instruction", () => { 5 | const arrayBuffer = new ArrayBuffer(4); 6 | const dataView = new DataView(arrayBuffer); 7 | 8 | const buffer = new MipsAssembler(dataView); 9 | 10 | buffer.addi(Reg.T1, Reg.T2, 0); 11 | 12 | expect(arrayBufferToArray(arrayBuffer)).toEqual([ 0, 0, 73, 33 ]); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /languages/javascript/test/x86.test.ts: -------------------------------------------------------------------------------- 1 | import { arrayBufferToArray } from "./helpers"; 2 | import { ArmAssembler, Reg, RegList, Coprocessor, Condition, Mode, Shift, Rotation, FieldMask, InterruptFlags, Addressing, OffsetMode, MipsAssembler, Reg, X86Assembler, Reg8, Reg16, Reg32, Reg64, Reg128 } from "../src/x86"; 3 | 4 | test("should assemble single ret instruction", () => { 5 | const arrayBuffer = new ArrayBuffer(1); 6 | const dataView = new DataView(arrayBuffer); 7 | 8 | const buffer = new X86Assembler(dataView); 9 | 10 | buffer.ret(); 11 | 12 | expect(arrayBufferToArray(arrayBuffer)).toEqual([ 195 ]); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /languages/javascript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | // "outDir": "./", /* Redirect output structure to the directory. */ 15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true, /* Enable all strict type-checking options. */ 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | 32 | /* Additional Checks */ 33 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 37 | 38 | /* Module Resolution Options */ 39 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | // "types": [], /* Type declaration files to be included in compilation. */ 45 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 46 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | 49 | /* Source Map Options */ 50 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | 55 | /* Experimental Options */ 56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 58 | } 59 | } -------------------------------------------------------------------------------- /languages/nim/README.md: -------------------------------------------------------------------------------- 1 | Nim 2 | === 3 | 4 | TO-DO. 5 | -------------------------------------------------------------------------------- /languages/nim/asmdot.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.1.0" 4 | author = "Grégoire Geis" 5 | description = "Lightweight and fast assembler for ARM and x86." 6 | license = "MIT" 7 | skipDirs = @[ "test" ] 8 | 9 | # Dependencies 10 | 11 | requires "nim >= 0.17.0" 12 | 13 | # Tasks 14 | 15 | task debug, "Compiles the project in debug mode.": 16 | exec "nim c -d:debug asmdot/arm.nim" 17 | exec "nim c -d:debug asmdot/mips.nim" 18 | exec "nim c -d:debug asmdot/x86.nim" 19 | 20 | task release, "Compiles the project in release mode.": 21 | exec "nim c -d:release asmdot/arm.nim" 22 | exec "nim c -d:release asmdot/mips.nim" 23 | exec "nim c -d:release asmdot/x86.nim" 24 | 25 | task test, "Run tests in debug mode.": 26 | exec "nim c -d:debug -r test/testall.nim" 27 | -------------------------------------------------------------------------------- /languages/nim/asmdot/arm.nim: -------------------------------------------------------------------------------- 1 | import typeinfo, private/helpers 2 | 3 | template `shl`(a: bool, b: untyped): untyped = 4 | cast[type b](a) shl b 5 | 6 | include private/arm 7 | -------------------------------------------------------------------------------- /languages/nim/asmdot/mips.nim: -------------------------------------------------------------------------------- 1 | import typeinfo, private/helpers 2 | 3 | include private/mips 4 | -------------------------------------------------------------------------------- /languages/nim/asmdot/private/helpers.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | 3 | macro makeWrite(name: untyped, ty: typedesc, size: static[int], inverse: static[bool]): untyped = 4 | let 5 | buf = newIdentNode("buf") 6 | value = newIdentNode("value") 7 | var 8 | stmts = newNimNode(nnkStmtList, name) 9 | 10 | if inverse: 11 | for shift in countdown(0, size - 8, 8): 12 | let shiftNode = newIntLitNode(shift) 13 | 14 | stmts.add quote do: 15 | `buf`.add (byte)(`value` shr `shiftNode`) 16 | else: 17 | for shift in countup(0, size - 8, 8): 18 | let shiftNode = newIntLitNode(shift) 19 | 20 | stmts.add quote do: 21 | `buf`.add (byte)(`value` shr `shiftNode`) 22 | 23 | result = quote do: 24 | proc `name`*(`buf`: var seq[byte], `value`: `ty`) {. inline .} = 25 | `stmts` 26 | 27 | 28 | makeWrite writeBE, int16, 16, cpuEndian != bigEndian 29 | makeWrite writeBE, int32, 32, cpuEndian != bigEndian 30 | makeWrite writeBE, int64, 64, cpuEndian != bigEndian 31 | makeWrite writeBE, uint16, 16, cpuEndian != bigEndian 32 | makeWrite writeBE, uint32, 32, cpuEndian != bigEndian 33 | makeWrite writeBE, uint64, 64, cpuEndian != bigEndian 34 | 35 | makeWrite writeLE, int16, 16, cpuEndian != littleEndian 36 | makeWrite writeLE, int32, 32, cpuEndian != littleEndian 37 | makeWrite writeLE, int64, 64, cpuEndian != littleEndian 38 | makeWrite writeLE, uint16, 16, cpuEndian != littleEndian 39 | makeWrite writeLE, uint32, 32, cpuEndian != littleEndian 40 | makeWrite writeLE, uint64, 64, cpuEndian != littleEndian 41 | 42 | proc add*(buf: var seq[byte], value: int8) {.inline.} = 43 | buf.add cast[uint8](value) 44 | -------------------------------------------------------------------------------- /languages/nim/asmdot/x86.nim: -------------------------------------------------------------------------------- 1 | import typeinfo, private/helpers, macros 2 | 3 | # Built-ins 4 | 5 | template getPrefix(r: untyped): byte = 6 | if byte(r) > byte(7): 7 | r = r - type(r)(8) 8 | 1 9 | else: 10 | 0 11 | 12 | 13 | # Import generated code + add operators to registers. 14 | 15 | include private/x86 16 | 17 | template borrowProc(name: untyped): untyped = 18 | proc name*(a, b: Reg8): Reg8 {.borrow.} 19 | proc name*(a, b: Reg16): Reg16 {.borrow.} 20 | proc name*(a, b: Reg32): Reg32 {.borrow.} 21 | proc name*(a, b: Reg64): Reg64 {.borrow.} 22 | proc name*(a, b: Reg128): Reg128 {.borrow.} 23 | 24 | borrowProc `+` 25 | borrowProc `-` 26 | borrowProc `*` 27 | borrowProc `and` 28 | borrowProc `or` 29 | borrowProc `xor` 30 | -------------------------------------------------------------------------------- /languages/nim/generate.py: -------------------------------------------------------------------------------- 1 | from asmdot import * # pylint: disable=W0614 2 | 3 | @handle_command_line() 4 | class NimEmitter(Emitter): 5 | 6 | @property 7 | def language(self): 8 | return 'nim' 9 | 10 | @property 11 | def filename(self): 12 | return f'asmdot/private/{self.arch}.nim' 13 | 14 | @property 15 | def test_filename(self): 16 | return f'test/test{self.arch}.nim' 17 | 18 | 19 | def get_operator(self, op: Operator) -> str: 20 | dic = { 21 | OP_BITWISE_AND: 'and', 22 | OP_BITWISE_OR : 'or', 23 | OP_BITWISE_XOR: 'xor', 24 | OP_AND: 'and', 25 | OP_OR : 'or', 26 | OP_XOR: 'xor', 27 | OP_SHL: 'shl', 28 | OP_SHR: 'shr' 29 | } 30 | 31 | if op in dic: 32 | return dic[op] 33 | else: 34 | return op.op 35 | 36 | def get_function_name(self, function: Function) -> str: 37 | if function.initname in ('and', 'div', 'or', 'xor'): 38 | return function.initname.capitalize() 39 | else: 40 | return function.initname 41 | 42 | def get_builtin_name(self, builtin: Builtin) -> str: 43 | if builtin is BUILTIN_X86_PREFIX: 44 | return 'getPrefix' 45 | else: 46 | return builtin.name 47 | 48 | 49 | def write_footer(self): 50 | self.writeline('proc assemble*(buf: var seq[byte], opcode: string, params: varargs[Any]): bool =') 51 | self.indent += 1 52 | 53 | self.writelinei('return false') 54 | # for fun in self.functions: 55 | # args = ', '.join([ f'' for name, ]) 56 | 57 | self.indent -= 1 58 | 59 | def write_expr(self, expr: Expression): 60 | if isinstance(expr, Binary): 61 | self.write('(', expr.l, ' ', expr.op, ' ', expr.r, ')') 62 | 63 | elif isinstance(expr, Unary): 64 | self.write(expr.op, expr.v) 65 | 66 | elif isinstance(expr, Ternary): 67 | self.write('(if ', expr.condition, ': ', expr.consequence, ' else: ', expr.alternative, ')') 68 | 69 | elif isinstance(expr, Var): 70 | self.write(expr.name) 71 | 72 | elif isinstance(expr, Call): 73 | self.write(expr.builtin, '(', join_any(', ', expr.args), ')') 74 | 75 | elif isinstance(expr, Literal): 76 | t = replace_pattern({ r'uint(\d+)': r'u\1', r'int(\d+)': r'i\1', r'.+': 'nop' }, 77 | expr.type.under.id) 78 | 79 | if t == 'nop': 80 | self.write(expr.value) 81 | else: 82 | self.write(expr.value, '\'', t) 83 | 84 | else: 85 | raise UnsupportedExpression(expr) 86 | 87 | def write_stmt(self, stmt: Statement): 88 | if isinstance(stmt, Assign): 89 | self.writelinei(stmt.variable, ' = ', stmt.value) 90 | 91 | elif isinstance(stmt, Conditional): 92 | self.writelinei('if ', stmt.condition, ':') 93 | 94 | with self.indent.further(): 95 | self.write_stmt(stmt.consequence) 96 | 97 | if stmt.alternative: 98 | self.writelinei('else:') 99 | 100 | with self.indent.further(): 101 | self.write_stmt(stmt.alternative) 102 | 103 | elif isinstance(stmt, Block): 104 | for s in stmt.statements: 105 | self.write_stmt(s) 106 | 107 | elif isinstance(stmt, Set): 108 | if stmt.type.under in (TYPE_U8, TYPE_I8): 109 | self.writelinei('buf.add ', stmt.value) 110 | else: 111 | endian = 'writeBE' if self.bigendian else 'writeLE' 112 | 113 | self.writelinei('buf.', endian, ' cast[', stmt.type.under, '](', stmt.value, ')') 114 | 115 | elif isinstance(stmt, Define): 116 | self.writelinei(f'var {stmt.name} = ', stmt.value) 117 | 118 | else: 119 | raise UnsupportedStatement(stmt) 120 | 121 | def write_function(self, fun: Function): 122 | name = fun.name 123 | 124 | self.write(f'proc {name}*(buf: var seq[byte]') 125 | 126 | needs_underlying = False 127 | 128 | for name, typ, _ in fun.params: 129 | self.write(f', {name}: {typ}') 130 | 131 | if typ.underlying: 132 | needs_underlying = True 133 | 134 | self.write(') = \n') 135 | self.indent += 1 136 | 137 | if needs_underlying: 138 | self.write('var\n', indent=True) 139 | 140 | with self.indent.further(): 141 | for name, _, usagetyp in fun.params: 142 | self.write(f'{name} = {usagetyp} {name}\n', indent=True) 143 | 144 | self.write('\n') 145 | 146 | for condition in fun.conditions: 147 | self.write('assert ', condition, '\n', indent=True) 148 | 149 | for stmt in fun.body: 150 | self.write_stmt(stmt) 151 | 152 | self.write('\n\n') 153 | self.indent -= 1 154 | 155 | 156 | def write_decl(self, decl: Declaration): 157 | if isinstance(decl, Enumeration): 158 | self.write('type ', decl.type, '* {.pure.} = enum ## ', decl.descr, '\n') 159 | 160 | for name, value, descr, _ in decl.members: 161 | self.write(' ', name, ' = ', value, ' ## ', descr, '\n') 162 | 163 | self.write('\n\n') 164 | 165 | for name, value, descr, _ in decl.additional_members: 166 | self.write('template ', name, '*(typ: type ', decl.type, '): ', decl.type, ' =\n') 167 | self.write(' ## ', descr, '\n') 168 | self.write(' ', value, '\n\n') 169 | 170 | if decl.flags: 171 | self.write('proc `+`*(a, b: ', decl.type, '): ', decl.type, ' =\n') 172 | self.write(' ', decl.type, '(byte(a) + byte(b))\n') 173 | self.write('proc `and`*(a, b: ', decl.type, '): ', decl.type, ' =\n') 174 | self.write(' ', decl.type, '(byte(a) and byte(b))\n') 175 | self.write('proc `or`*(a, b: ', decl.type, '): ', decl.type, ' =\n') 176 | self.write(' ', decl.type, '(byte(a) or byte(b))\n\n') 177 | 178 | elif isinstance(decl, DistinctType): 179 | self.write('type ', decl.type, '* = distinct ', decl.type.underlying, ' ## ', decl.descr, '\n\n') 180 | 181 | if decl.constants: 182 | self.write('const\n') 183 | 184 | for name, value in decl.constants: 185 | self.write(' ', name, '* = ', decl.type, ' ', value, '\n') 186 | 187 | self.write('\n\n') 188 | 189 | else: 190 | raise UnsupportedDeclaration(decl) 191 | 192 | 193 | def write_test_header(self): 194 | self.write(f'import sequtils, unittest, ../asmdot/{self.arch}\n\n') 195 | self.write(f'suite "test {self.arch} assembler":\n') 196 | self.indent += 1 197 | 198 | self.writelinei('setup:') 199 | 200 | with self.indent.further(): 201 | self.writelinei('var') 202 | 203 | with self.indent.further(): 204 | self.writelinei('buf = newSeqOfCap[byte](100)') 205 | 206 | self.writeline() 207 | 208 | def write_test(self, test: TestCase): 209 | self.writelinei(f'test "{test.name}":') 210 | self.indent += 1 211 | 212 | def arg_str(arg: TestCaseArgument): 213 | if isinstance(arg, ArgConstant): 214 | return arg.const.name 215 | if isinstance(arg, ArgEnumMember): 216 | return arg.member.name 217 | elif isinstance(arg, ArgInteger): 218 | return str(arg.value) 219 | else: 220 | raise UnsupportedTestArgument(arg) 221 | 222 | for func, args in test.calls: 223 | args_str = ', '.join([ arg_str(arg) for arg in args ]) 224 | 225 | self.writelinei('buf.', func.name, '(', args_str, ')') 226 | 227 | self.writeline() 228 | self.writelinei('check cast[seq[char]](buf) == toSeq("', test.expected_string, '".items)') 229 | self.writeline() 230 | 231 | self.indent -= 1 232 | -------------------------------------------------------------------------------- /languages/nim/test/testall.nim: -------------------------------------------------------------------------------- 1 | import testarm, testmips, testx86 2 | -------------------------------------------------------------------------------- /languages/nim/test/testarm.nim: -------------------------------------------------------------------------------- 1 | import sequtils, unittest, ../asmdot/arm 2 | 3 | suite "test arm assembler": 4 | setup: 5 | var 6 | buf = newSeqOfCap[byte](100) 7 | 8 | test "should encode single cps instruction": 9 | buf.cps(USR) 10 | 11 | check cast[seq[char]](buf) == toSeq("\x10\x00\x02\xf1".items) 12 | 13 | -------------------------------------------------------------------------------- /languages/nim/test/testmips.nim: -------------------------------------------------------------------------------- 1 | import sequtils, unittest, ../asmdot/mips 2 | 3 | suite "test mips assembler": 4 | setup: 5 | var 6 | buf = newSeqOfCap[byte](100) 7 | 8 | test "should assemble single addi instruction": 9 | buf.addi(t1, t2, 0) 10 | 11 | check cast[seq[char]](buf) == toSeq("\x00\x00\x49\x21".items) 12 | 13 | -------------------------------------------------------------------------------- /languages/nim/test/testx86.nim: -------------------------------------------------------------------------------- 1 | import sequtils, unittest, ../asmdot/x86 2 | 3 | suite "test x86 assembler": 4 | setup: 5 | var 6 | buf = newSeqOfCap[byte](100) 7 | 8 | test "should assemble single ret instruction": 9 | buf.ret() 10 | 11 | check cast[seq[char]](buf) == toSeq("\xc3".items) 12 | 13 | -------------------------------------------------------------------------------- /languages/ocaml/README.md: -------------------------------------------------------------------------------- 1 | OCaml 2 | ===== 3 | 4 | To-do. 5 | -------------------------------------------------------------------------------- /languages/ocaml/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | (name asmdot) 3 | -------------------------------------------------------------------------------- /languages/ocaml/generate.py: -------------------------------------------------------------------------------- 1 | from asmdot import * # pylint: disable=W0614 2 | 3 | @handle_command_line() 4 | class OCamlEmitter(Emitter): 5 | 6 | @property 7 | def language(self): 8 | return 'ocaml' 9 | 10 | @property 11 | def filename(self): 12 | return f'src/{self.arch}.ml' 13 | 14 | @property 15 | def test_filename(self): 16 | return f'test/test{self.arch}.ml' 17 | 18 | 19 | def get_function_name(self, function: Function) -> str: 20 | return function.fullname 21 | 22 | 23 | def write_header(self): 24 | self.write('open Core\n\n') 25 | 26 | def write_separator(self): 27 | self.writeline() 28 | 29 | 30 | def write_expr(self, expr: Expression): 31 | if isinstance(expr, Binary): 32 | self.write('(', expr.l, ' ', expr.op, ' ', expr.r, ')') 33 | 34 | elif isinstance(expr, Unary): 35 | self.write(expr.op, expr.v) 36 | 37 | elif isinstance(expr, Ternary): 38 | self.write('(if ', expr.condition, ' then ', expr.consequence, ' else ', expr.alternative, ')') 39 | 40 | elif isinstance(expr, Var): 41 | self.write(expr.name) 42 | 43 | elif isinstance(expr, Call): 44 | self.write(expr.builtin, ' ', join_any(' ', expr.args)) 45 | 46 | elif isinstance(expr, Literal): 47 | self.write(expr.value) 48 | 49 | else: 50 | raise UnsupportedExpression(expr) 51 | 52 | def write_stmt(self, stmt: Statement): 53 | if isinstance(stmt, Assign): 54 | self.writelinei(stmt.variable, ' <- ', stmt.value) 55 | elif isinstance(stmt, Conditional): 56 | self.writelinei('if ', stmt.condition, ' then') 57 | 58 | with self.indent.further(): 59 | self.write_stmt(stmt.consequence) 60 | 61 | if stmt.alternative: 62 | self.writelinei('else') 63 | 64 | with self.indent.further(): 65 | self.write_stmt(stmt.alternative) 66 | 67 | elif isinstance(stmt, Block): 68 | for s in stmt.statements: 69 | self.write_stmt(s) 70 | 71 | elif isinstance(stmt, Set): 72 | call = 'Iobuf.Poke.' 73 | 74 | if stmt.type.under in (TYPE_U8, TYPE_U16, TYPE_U32, TYPE_U64): 75 | call += 'u' 76 | 77 | call += f'int{stmt.type.size * 8}' 78 | 79 | if stmt.type.under not in (TYPE_U8, TYPE_I8): 80 | call += '_be' if self.bigendian else '_le' 81 | 82 | self.writelinei(call, ' buf ', stmt.value, ';') 83 | self.writelinei('Iobuf.advance buf ', stmt.type.size) 84 | 85 | elif isinstance(stmt, Define): 86 | self.writelinei('let mutable ', stmt.name, ' = ', stmt.value, ' in') 87 | 88 | else: 89 | raise UnsupportedStatement(stmt) 90 | 91 | def write_function(self, fun: Function): 92 | names = 'buf ' 93 | 94 | self.writelinei('(** ', fun.descr, ' *)') 95 | self.writei('val ', fun.name, ' : (_, _) t') 96 | 97 | for name, typ, _ in fun.params: 98 | names += name + ' ' 99 | 100 | self.write(f' -> {typ}') 101 | 102 | self.writeline(' -> unit') 103 | 104 | self.writelinei(f'let {fun.name} {names}=') 105 | self.indent += 1 106 | 107 | for condition in fun.conditions: 108 | self.writelinei('assert ', condition, ';') 109 | 110 | for stmt in fun.body: 111 | self.write_stmt(stmt) 112 | 113 | self.indent -= 1 114 | self.writei(';;\n\n') 115 | 116 | 117 | def write_decl(self, decl: Declaration): 118 | if isinstance(decl, Enumeration): 119 | self.writelinei('(** ', decl.descr, ' *)') 120 | self.writelinei('type ', decl.type, ' =') 121 | self.indent += 1 122 | 123 | for name, value, _, _ in decl.members + decl.additional_members: 124 | self.writelinei('| ', name) 125 | 126 | self.writeline() 127 | self.indent -= 1 128 | 129 | elif isinstance(decl, DistinctType): 130 | self.writelinei('(** ', decl.descr, ' *)') 131 | self.write('type ', decl.type, ' = ', decl.type.underlying, '\n') 132 | 133 | if decl.constants: 134 | self.writelinei('module ', decl.type) 135 | self.indent += 1 136 | 137 | for name, value in decl.constants: 138 | self.writelinei('let ', name, ' = ', decl.type, ' ', value, ' ;;') 139 | 140 | self.indent -= 1 141 | self.writelinei(';;') 142 | 143 | self.writeline() 144 | 145 | else: 146 | raise UnsupportedDeclaration(decl) 147 | 148 | 149 | def write_test_header(self): 150 | self.writei(f'open OUnit2\n\nlet suite = "{self.arch} suite" >::: [\n') 151 | self.indent += 1 152 | 153 | def write_test_footer(self): 154 | self.indent -= 1 155 | self.write(f'];;\n\nlet () = run_test_tt_main suite ;;\n') 156 | 157 | def write_test(self, test: TestCase): 158 | self.writelinei('"', test.name, '" >:: (fun ctx ->') 159 | self.indent += 1 160 | 161 | self.writelinei('let buf = Iobuf.create ', len(test.expected), ' in') 162 | self.writeline() 163 | 164 | arch_module = self.arch.capitalize() 165 | 166 | for func, args in test.calls: 167 | self.writei(arch_module, '.', func.name, ' buf ') 168 | 169 | for arg in args: 170 | if isinstance(arg, ArgConstant): 171 | self.write(f'{arg.type.type}.{arg.const.name} ') 172 | elif isinstance(arg, ArgEnumMember): 173 | self.write(f'{arg.enum.type}.{arg.member.name} ') 174 | elif isinstance(arg, ArgInteger): 175 | self.write(arg.value) 176 | else: 177 | raise UnsupportedTestArgument(arg) 178 | 179 | self.writeline(';') 180 | 181 | self.writeline() 182 | self.writelinei('assert_equal ctx (Iobuf.to_string buf) "', test.expected_string, '"') 183 | self.indent -= 1 184 | self.writelinei(');') 185 | -------------------------------------------------------------------------------- /languages/ocaml/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name asmdot) 3 | (synopsis "Lightweight assembler.") 4 | (modules arm mips x86) 5 | (libraries core)) 6 | -------------------------------------------------------------------------------- /languages/ocaml/test/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name testarm) 3 | (modules testarm) 4 | (libraries base oUnit)) 5 | (executable 6 | (name testmips) 7 | (modules testmips) 8 | (libraries base oUnit)) 9 | (executable 10 | (name testx86) 11 | (modules testx86) 12 | (libraries base oUnit)) 13 | 14 | (alias 15 | (name runtest) 16 | (deps testarm.exe testmips.exe testx86.exe) 17 | (action 18 | (run testarm.exe))) 19 | -------------------------------------------------------------------------------- /languages/ocaml/test/testarm.ml: -------------------------------------------------------------------------------- 1 | open OUnit2 2 | 3 | let suite = "arm suite" >::: [ 4 | "should encode single cps instruction" >:: (fun ctx -> 5 | let buf = Iobuf.create 4 in 6 | 7 | Arm.cps buf Mode.USR ; 8 | 9 | assert_equal ctx (Iobuf.to_string buf) "\x10\x00\x02\xf1" 10 | ); 11 | ];; 12 | 13 | let () = run_test_tt_main suite ;; 14 | -------------------------------------------------------------------------------- /languages/ocaml/test/testmips.ml: -------------------------------------------------------------------------------- 1 | open OUnit2 2 | 3 | let suite = "mips suite" >::: [ 4 | "should assemble single addi instruction" >:: (fun ctx -> 5 | let buf = Iobuf.create 4 in 6 | 7 | Mips.addi buf Reg.t1 Reg.t2 0; 8 | 9 | assert_equal ctx (Iobuf.to_string buf) "\x00\x00\x49\x21" 10 | ); 11 | ];; 12 | 13 | let () = run_test_tt_main suite ;; 14 | -------------------------------------------------------------------------------- /languages/ocaml/test/testx86.ml: -------------------------------------------------------------------------------- 1 | open OUnit2 2 | 3 | let suite = "x86 suite" >::: [ 4 | "should assemble single ret instruction" >:: (fun ctx -> 5 | let buf = Iobuf.create 1 in 6 | 7 | X86.ret buf ; 8 | 9 | assert_equal ctx (Iobuf.to_string buf) "\xc3" 10 | ); 11 | ];; 12 | 13 | let () = run_test_tt_main suite ;; 14 | -------------------------------------------------------------------------------- /languages/python/README.md: -------------------------------------------------------------------------------- 1 | Python 2 | ====== 3 | 4 | # Performances 5 | On May 19th, 2018, it was decided that Python would no longer use bindings 6 | to the C library, but instead directly generate code by itself. 7 | 8 | This choice was taken because it appears that in most cases, the overhead added 9 | by performing FFI outweights the performance improvements of using C instead of Python. 10 | 11 | For reference, the following code was used to benchmark both alternatives. 12 | 13 | ```python 14 | from bindings.python import voidptr 15 | from bindings.python.arm import load_arm, Mode 16 | from bindings.python.x86 import load_x86, Reg32 17 | 18 | import struct 19 | 20 | # Raw translation of the Nim source code (right now, only bindings are generated for Python). 21 | class Assembler: 22 | def __init__(self, size: int) -> None: 23 | self.size = size 24 | self.buf = bytearray(size) 25 | self.pos = 0 26 | 27 | def cps(self, mode: Mode) -> None: 28 | mode = mode.value 29 | 30 | struct.pack_into(' None: 34 | operand = operand.value 35 | 36 | if operand > 7: 37 | self.buf[self.pos] = 65 38 | self.pos += 1 39 | 40 | self.buf[self.pos] = 64 + operand 41 | self.pos += 1 42 | 43 | if __name__ == '__main__': 44 | # ARM benchmark: 45 | # Encode a single instruction into a buffer 50,000 times. 46 | # Every 50 iteration, reset the pointer to the start of the buffer. 47 | # 48 | # x86 benchmark: 49 | # Encode a single instruction into a buffer 100,000 times. 50 | # Every 100 iteration, reset the pointer to the start of the buffer. 51 | 52 | import ctypes, time 53 | 54 | arm = load_arm() 55 | x86 = load_x86() 56 | eax = Reg32(0x0) 57 | usr = Mode.USR 58 | 59 | # FFI time 60 | char_buffer = ctypes.create_string_buffer(256) 61 | void_pointer = ctypes.cast(char_buffer, voidptr) 62 | 63 | buf = ctypes.pointer(void_pointer) 64 | 65 | start_time = time.perf_counter() 66 | 67 | for i in range(1000): 68 | for j in range(100): 69 | x86.inc_r32(buf, eax) 70 | 71 | buf = ctypes.pointer(void_pointer) 72 | 73 | print('FFI x86 time:', time.perf_counter() - start_time) 74 | 75 | buf = ctypes.pointer(void_pointer) 76 | start_time = time.perf_counter() 77 | 78 | for i in range(1000): 79 | for j in range(50): 80 | arm.cps(buf, usr) 81 | 82 | buf = ctypes.pointer(void_pointer) 83 | 84 | print('FFI ARM time:', time.perf_counter() - start_time) 85 | 86 | # Pure Python time 87 | assembler = Assembler(256) 88 | start_time = time.perf_counter() 89 | 90 | for i in range(1000): 91 | for j in range(100): 92 | assembler.inc_r32(eax) 93 | 94 | assembler.pos = 0 95 | 96 | print('Python x86 time:', time.perf_counter() - start_time) 97 | 98 | start_time = time.perf_counter() 99 | 100 | for i in range(1000): 101 | for j in range(50): 102 | assembler.cps(usr) 103 | 104 | assembler.pos = 0 105 | 106 | print('Python ARM time:', time.perf_counter() - start_time) 107 | ``` 108 | 109 | Results: 110 | 111 | | Implementation | Architecture | Time | 112 | | -------------- | ------------ | ------------------- | 113 | | FFI | ARM | 0.04635770555265187 | 114 | | Pure Python | ARM | 0.05121554772057621 | 115 | | FFI | x86 | 0.0714547106012631 | 116 | | Pure Python | x86 | 0.0478309351130789 | 117 | -------------------------------------------------------------------------------- /languages/python/asm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/71/asmdot/bcece68e6fee2091225da8ef3e7c874900a55b53/languages/python/asm/__init__.py -------------------------------------------------------------------------------- /languages/python/generate.py: -------------------------------------------------------------------------------- 1 | from asmdot import * # pylint: disable=W0614 2 | 3 | @handle_command_line() 4 | class PythonEmitter(Emitter): 5 | 6 | @property 7 | def language(self): 8 | return 'python' 9 | 10 | @property 11 | def filename(self): 12 | return f'asm/{self.arch}.py' 13 | 14 | @property 15 | def test_filename(self): 16 | return f'tests/test_{self.arch}.py' 17 | 18 | 19 | def get_type_name(self, ty: IrType) -> str: 20 | return replace_pattern({ 21 | r'u?int\d+': 'int' 22 | }, ty.id) 23 | 24 | def get_function_name(self, function: Function) -> str: 25 | if function.fullname in ('and', 'or'): 26 | return function.fullname + '_' 27 | else: 28 | return function.fullname 29 | 30 | def get_operator(self, op: Operator) -> str: 31 | dic = { 32 | OP_BITWISE_AND: '&', 33 | OP_BITWISE_OR : '|', 34 | OP_AND: 'and', 35 | OP_OR : 'or', 36 | } 37 | 38 | if op in dic: 39 | return dic[op] 40 | else: 41 | return op.op 42 | 43 | 44 | def __init__(self, args: Namespace, arch: str) -> None: 45 | super().__init__(args, arch) 46 | 47 | self.indent = Indent(' ') 48 | 49 | 50 | def write_header(self): 51 | self.write('import struct\nfrom enum import Enum, Flag\nfrom typing import NewType\n\n') 52 | 53 | def write_separator(self): 54 | self.write(f''' 55 | class {self.arch.capitalize()}Assembler: 56 | """Assembler that targets the {self.arch} architecture.""" 57 | def __init__(self, size: int) -> None: 58 | assert size > 0 59 | 60 | self.size = size 61 | self.buf = bytearray(size) 62 | self.pos = 0 63 | 64 | ''') 65 | self.indent += 1 66 | 67 | def write_footer(self): 68 | self.indent -= 1 69 | 70 | 71 | def write_expr(self, expr: Expression): 72 | if isinstance(expr, Binary): 73 | self.write('(', expr.l, ' ', expr.op, ' ', expr.r, ')') 74 | 75 | elif isinstance(expr, Unary): 76 | self.write(expr.op, expr.v) 77 | 78 | elif isinstance(expr, Ternary): 79 | self.write('(if ', expr.condition, ': ', expr.consequence, ' else: ', expr.alternative, ')') 80 | 81 | elif isinstance(expr, Var): 82 | self.write(expr.name) 83 | 84 | elif isinstance(expr, Call): 85 | self.write(expr.builtin, '(', join_any(', ', expr.args), ')') 86 | 87 | elif isinstance(expr, Literal): 88 | self.write(expr.value) 89 | 90 | else: 91 | raise UnsupportedExpression(expr) 92 | 93 | def write_stmt(self, stmt: Statement): 94 | if isinstance(stmt, Assign): 95 | self.writelinei(stmt.variable, ' = ', stmt.value) 96 | elif isinstance(stmt, Conditional): 97 | self.writelinei('if ', stmt.condition, ':') 98 | 99 | with self.indent.further(): 100 | self.write_stmt(stmt.consequence) 101 | 102 | if stmt.alternative: 103 | self.writelinei('else:') 104 | 105 | with self.indent.further(): 106 | self.write_stmt(stmt.alternative) 107 | 108 | elif isinstance(stmt, Block): 109 | for s in stmt.statements: 110 | self.write_stmt(s) 111 | 112 | elif isinstance(stmt, Set): 113 | if stmt.type.under in [TYPE_U8, TYPE_I8]: 114 | self.writelinei('self.buf[self.pos] = ', stmt.value) 115 | else: 116 | endian = '>' if self.bigendian else '<' 117 | 118 | self.writelinei('struct.pack_into("', endian, 'I", self.buf, self.pos, ', 119 | stmt.value, ')') 120 | 121 | self.writelinei('self.pos += ', stmt.type.size) 122 | 123 | elif isinstance(stmt, Define): 124 | self.writelinei(stmt.name, ' = ', stmt.value) 125 | 126 | else: 127 | raise UnsupportedStatement(stmt) 128 | 129 | def write_function(self, fun: Function): 130 | self.writei(f'def {fun.name}(self') 131 | 132 | for name, typ, _ in fun.params: 133 | self.write(f', {name}: {typ}') 134 | 135 | self.writeline(') -> None:') 136 | self.indent += 1 137 | self.writelinei('"""', fun.descr, '"""') 138 | 139 | for condition in fun.conditions: 140 | self.writelinei('assert ', condition, '\n') 141 | 142 | for stmt in fun.body: 143 | self.write_stmt(stmt) 144 | 145 | self.indent -= 1 146 | self.writeline() 147 | 148 | 149 | def write_decl(self, decl: Declaration): 150 | if isinstance(decl, Enumeration): 151 | sub = 'Flag' if decl.flags else 'Enum' 152 | 153 | self.write('class ', decl.type, f'(int, {sub}):\n') 154 | self.indent += 1 155 | self.write('"""', decl.descr, '"""\n', indent=True) 156 | 157 | for name, value, _, _ in decl.members + decl.additional_members: 158 | self.write(name, ' = ', value, '\n', indent=True) 159 | 160 | self.write('\n') 161 | self.indent -= 1 162 | 163 | elif isinstance(decl, DistinctType): 164 | self.write(decl.type, ' = NewType("', decl.type, '", ', decl.type.underlying, ')\n') 165 | 166 | for name, value in decl.constants: 167 | self.write('setattr(', decl.type, ', "', name.upper(), '", ', decl.type, '(', value, '))\n') 168 | 169 | self.write('\n') 170 | 171 | else: 172 | raise UnsupportedDeclaration(decl) 173 | 174 | 175 | def write_test_header(self): 176 | self.write(f'from asm.{self.arch} import * # pylint: disable=W0614\n\n') 177 | 178 | def write_test(self, test: TestCase): 179 | self.write('def ', test.name.replace(' ', '_'), '():\n') 180 | self.indent += 1 181 | 182 | self.writelinei('asm = ', self.arch.capitalize(), 'Assembler(', len(test.expected), ')') 183 | self.writeline() 184 | 185 | def arg_str(arg: TestCaseArgument): 186 | if isinstance(arg, ArgConstant): 187 | return f'{arg.type.type}.{arg.const.name.upper()}' 188 | if isinstance(arg, ArgEnumMember): 189 | return f'{arg.enum.type}.{arg.member.name}' 190 | elif isinstance(arg, ArgInteger): 191 | return str(arg.value) 192 | else: 193 | raise UnsupportedTestArgument(arg) 194 | 195 | for func, args in test.calls: 196 | args_str = ', '.join([ arg_str(arg) for arg in args ]) 197 | 198 | self.writelinei('asm.', func.name, '(', args_str, ')') 199 | 200 | self.writeline() 201 | self.writelinei('assert asm.buf == b"', test.expected_string, '"') 202 | self.writeline() 203 | 204 | self.indent -= 1 205 | -------------------------------------------------------------------------------- /languages/python/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files=test_*.py 3 | python_functions=should_* 4 | -------------------------------------------------------------------------------- /languages/python/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | from configparser import ConfigParser 3 | 4 | config = ConfigParser() 5 | config.read('../../src/metadata.ini') 6 | 7 | metadata = config['default'] 8 | 9 | 10 | setup( 11 | name = metadata.get('name'), 12 | version = metadata.get('version'), 13 | description = metadata.get('description'), 14 | url = metadata.get('url'), 15 | author = metadata.get('author'), 16 | author_email = metadata.get('author_email'), 17 | license = 'MIT', 18 | 19 | python_requires = '>=3.6.0', 20 | 21 | packages = find_packages(exclude=('tests',)), 22 | include_package_data = True, 23 | 24 | classifiers = [ 25 | 'Intended Audience :: Developers', 26 | 'License :: OSI Approved :: MIT License', 27 | 'Programming Language :: Python', 28 | 'Programming Language :: Python :: 3', 29 | 'Programming Language :: Python :: 3.6', 30 | 'Operating System :: OS Independent', 31 | 'Topic :: Software Development :: Assemblers' 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /languages/python/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/71/asmdot/bcece68e6fee2091225da8ef3e7c874900a55b53/languages/python/tests/__init__.py -------------------------------------------------------------------------------- /languages/python/tests/test_arm.py: -------------------------------------------------------------------------------- 1 | from asm.arm import * # pylint: disable=W0614 2 | 3 | def should_encode_single_cps_instruction(): 4 | asm = ArmAssembler(4) 5 | 6 | asm.cps(Mode.USR) 7 | 8 | assert asm.buf == b"\x10\x00\x02\xf1" 9 | 10 | -------------------------------------------------------------------------------- /languages/python/tests/test_mips.py: -------------------------------------------------------------------------------- 1 | from asm.mips import * # pylint: disable=W0614 2 | 3 | def should_assemble_single_addi_instruction(): 4 | asm = MipsAssembler(4) 5 | 6 | asm.addi(Reg.T1, Reg.T2, 0) 7 | 8 | assert asm.buf == b"\x00\x00\x49\x21" 9 | 10 | -------------------------------------------------------------------------------- /languages/python/tests/test_x86.py: -------------------------------------------------------------------------------- 1 | from asm.x86 import * # pylint: disable=W0614 2 | 3 | def should_assemble_single_ret_instruction(): 4 | asm = X86Assembler(1) 5 | 6 | asm.ret() 7 | 8 | assert asm.buf == b"\xc3" 9 | 10 | -------------------------------------------------------------------------------- /languages/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "asm" 3 | version = "0.1.0" 4 | authors = ["Grégoire Geis "] 5 | homepage = "https://github.com/6A/asmdot" 6 | repository = "https://github.com/6A/asmdot" 7 | license = "MIT" 8 | categories = ["encoding"] 9 | 10 | [lib] 11 | name = "asm" 12 | path = "src/lib.rs" 13 | test = false 14 | 15 | [dependencies] 16 | bitflags = "^1.0" 17 | byteorder = "^1.2" 18 | -------------------------------------------------------------------------------- /languages/rust/README.md: -------------------------------------------------------------------------------- 1 | Rust 2 | ==== 3 | 4 | ## Installation 5 | Simply add this line to your `Cargo.toml` dependencies: 6 | 7 | ```toml 8 | [dependencies] 9 | asm = { git = "https://github.com/6A/asmdot" } 10 | ``` 11 | 12 | ## Usage 13 | ```rust 14 | extern crate asm; 15 | 16 | use asm::x86::{Register32, X86Assembler}; 17 | 18 | use std::io; 19 | 20 | fn emit_example(buf: &mut io::Write) -> io::Result<()> { 21 | buf.inc_r32(Register32::EAX)?; 22 | buf.ret()?; 23 | 24 | Ok(()) 25 | } 26 | 27 | let mut buf = vec!(); 28 | 29 | assert!(emit_example(&mut buf) == Ok(())); 30 | assert!(buf.len() == 2); 31 | ``` 32 | -------------------------------------------------------------------------------- /languages/rust/src/generated/mod.rs: -------------------------------------------------------------------------------- 1 | macro_rules! prefix_adder { 2 | ( $value: expr ) => (if $value > 7 { 3 | $value -= 8; 1 4 | } else { 5 | 0 6 | }) 7 | } 8 | 9 | /// Trick to transmute a value at compile-time. 10 | /// 11 | /// # See also 12 | /// https://github.com/rust-lang/rust/issues/49450 13 | pub(crate) union Transmute { 14 | from: T, 15 | to: U 16 | } 17 | 18 | macro_rules! transmute_const { 19 | ( $value: expr ) => { 20 | unsafe { super::Transmute { from: $value }.to } 21 | }; 22 | } 23 | 24 | pub(crate) mod arm; 25 | pub(crate) mod mips; 26 | pub(crate) mod x86; 27 | -------------------------------------------------------------------------------- /languages/rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Fast, minimal and zero-copy assembler for ARM and x86. 2 | #![feature(pattern_parentheses)] 3 | 4 | #[macro_use] 5 | extern crate bitflags; 6 | extern crate byteorder; 7 | 8 | mod generated; 9 | 10 | /// Provides ARM-specific types and the `ArmAssembler` trait, which 11 | /// allows any `Write` struct to assemble ARM instructions. 12 | /// 13 | /// # Example 14 | /// ```rust 15 | /// use asm::arm::{ArmAssembler, Mode}; 16 | /// 17 | /// let mut buf = Vec::new(); 18 | /// 19 | /// assert!( buf.cps(Mode::USR).is_ok() ); 20 | /// assert!( buf.cps(Mode::USR).is_ok() ); 21 | /// 22 | /// assert_eq!(buf, b"\x10\x00\x02\xf1\x10\x00\x02\xf1"); 23 | /// ``` 24 | pub mod arm { 25 | pub use generated::arm::*; 26 | } 27 | 28 | /// Provides MIPS-specific types and the `MipsAssembler` trait, which 29 | /// allows any `Write` struct to assemble MIPS instructions. 30 | /// 31 | /// # Example 32 | /// ```rust 33 | /// use asm::mips::{MipsAssembler, Register}; 34 | /// 35 | /// let mut buf = Vec::new(); 36 | /// 37 | /// assert!( buf.li(Register::T1).is_ok() ); 38 | /// assert!( buf.li(Register::T2).is_ok() ); 39 | /// assert!( buf.addi(Register::T1, Register::T2, 0).is_ok() ); 40 | /// 41 | /// assert_eq!(buf, b"\x05\x00\x0d\x24\x0a\x00\x0e\x24\x00\x00\xcd\x21"); 42 | /// ``` 43 | pub mod mips { 44 | pub use generated::mips::*; 45 | } 46 | 47 | /// Provides x86-specific types and the `X86Assembler` trait, which 48 | /// allows any `Write` struct to assemble x86 instructions. 49 | /// 50 | /// # Example 51 | /// ```rust 52 | /// use asm::x86::{X86Assembler, Register32}; 53 | /// 54 | /// let mut buf = Vec::new(); 55 | /// 56 | /// assert!( buf.inc_r32(Register32::EAX).is_ok() ); 57 | /// assert!( buf.ret().is_ok() ); 58 | /// 59 | /// assert_eq!( buf, b"\x40\xc3" ); 60 | /// ``` 61 | pub mod x86 { 62 | pub use generated::x86::*; 63 | } 64 | -------------------------------------------------------------------------------- /languages/rust/tests/arm.rs: -------------------------------------------------------------------------------- 1 | extern crate asm; 2 | 3 | use asm::arm::*; 4 | 5 | #[test] 6 | fn should_encode_single_cps_instruction() { 7 | let mut buf = Vec::new(); 8 | 9 | assert!(buf.cps(Mode::USR).is_ok()); 10 | 11 | assert_eq!(buf, b"\x10\x00\x02\xf1"); 12 | } 13 | -------------------------------------------------------------------------------- /languages/rust/tests/mips.rs: -------------------------------------------------------------------------------- 1 | extern crate asm; 2 | 3 | use asm::mips::*; 4 | 5 | #[test] 6 | fn should_assemble_single_addi_instruction() { 7 | let mut buf = Vec::new(); 8 | 9 | assert!(buf.addi(Register::T1, Register::T2, 0).is_ok()); 10 | 11 | assert_eq!(buf, b"\x00\x00\x49\x21"); 12 | } 13 | -------------------------------------------------------------------------------- /languages/rust/tests/x86.rs: -------------------------------------------------------------------------------- 1 | extern crate asm; 2 | 3 | use asm::x86::*; 4 | 5 | #[test] 6 | fn should_assemble_single_ret_instruction() { 7 | let mut buf = Vec::new(); 8 | 9 | assert!(buf.ret().is_ok()); 10 | 11 | assert_eq!(buf, b"\xc3"); 12 | } 13 | -------------------------------------------------------------------------------- /templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/71/asmdot/bcece68e6fee2091225da8ef3e7c874900a55b53/templates/__init__.py -------------------------------------------------------------------------------- /templates/arch.py: -------------------------------------------------------------------------------- 1 | from asmdot.ast import * # pylint: disable=W0614 2 | from asmdot.arch import * # pylint: disable=W0614 3 | from asmdot.helpers import error, exception 4 | 5 | from .testsource import ExampleTestSource 6 | 7 | class ExampleArchitecture(Architecture): 8 | """Example `Architecture` that can be used to easily get started creating a new 9 | architecture parser.""" 10 | 11 | @property 12 | def name(self) -> str: 13 | # Return the identifier of the architecture, 14 | # which will also be used to lookup the instructions file. 15 | 16 | return 'example' # File 'data/example.txt' will be loaded. 17 | 18 | @property 19 | def tests(self) -> TestSource: 20 | # Return the test source defined for this architecture. 21 | return ExampleTestSource() 22 | 23 | @property 24 | def declarations(self) -> Iterator[Declaration]: 25 | # Yield declarations (DistinctType or Enumeration) that will be used by the 26 | # generated functions. 27 | 28 | TYPE_EXAMPLE = IrType('ExampleName', TYPE_BYTE) 29 | 30 | yield DistinctType(TYPE_EXAMPLE, 'Documentation of the distinct type.', [ 31 | Constant('CONSTANT_NAME', 0), 32 | ]) 33 | 34 | 35 | # Flags enum (can be OR'd, AND'd, XOR'd, etc). 36 | yield Enumeration(TYPE_EXAMPLE, True, 'Documentation...', [], []) 37 | 38 | # Non-flags enum (cannot be OR'd, AND'd, XOR'd, etc). 39 | yield Enumeration(TYPE_EXAMPLE, False, 'Documentation...', [], []) 40 | 41 | yield Enumeration(TYPE_EXAMPLE, False, 'Documentation...', [ 42 | # First array argument defines all the core enum members in increasing value. 43 | EnumerationMember('EQ', 0x0, 'Documentation of the first member...'), 44 | EnumerationMember('NE', 0x1, 'Documentation of the second member...'), 45 | 46 | # Some languages do not support multiple enum values that have the same name, 47 | # even if the enum they belong to is different, and likewise with constants. 48 | # Thus, ASM. encourages you to provide a unique, longer name that can be chosen 49 | # by such languages by specifying a 4th argument. 50 | EnumerationMember('AL', 0xE, 'Documentation...', '*Always') 51 | # Will either have the name 'AL' or 'ExampleNameAlways' depending on the language. 52 | 53 | ], [ 54 | # Second array argument defines all additional members that have previously given 55 | # values, but that are still good to have around. 56 | EnumerationMember('Equal', 0x0, 'Documentation...'), 57 | EnumerationMember('NotEqual', 0x1, 'Documentation...') 58 | 59 | ]) 60 | 61 | 62 | def translate(self, input: IO[str]): 63 | # Parse the input however you like! 64 | # 65 | # Parsy (https://github.com/python-parsy/parsy) is recommended for parsing. 66 | # Furthermore, some utilities are provided to make parsing easier. 67 | from parsy import string, ParseError 68 | 69 | @parse(r'\d*\.\d+') 70 | def floating_point(n: str) -> float: 71 | return float(n) 72 | 73 | @parse(floating_point, '=', floating_point) 74 | def comparison(a: float, _, b: float) -> bool: 75 | return a == b 76 | 77 | plus = string('+') 78 | 79 | @parse(floating_point.sep_by(plus) << ws) 80 | def floats_sum(floats: List[float]) -> float: 81 | return sum(floats) 82 | 83 | for line in input: 84 | line = line.strip() 85 | 86 | if not len(line): 87 | continue 88 | 89 | try: 90 | parsed = (floats_sum | comparison).parse(line) 91 | 92 | yield Function('Function name', [], 'FullName (when overloading is not supported)', []) 93 | 94 | except ParseError as err: 95 | error(f'Invalid instruction: "{line}".') 96 | exception(err) 97 | -------------------------------------------------------------------------------- /templates/lang.py: -------------------------------------------------------------------------------- 1 | from asmdot import * # pylint: disable=W0614 2 | 3 | # Uncomment the following line to ensure the language 4 | # can be directly used through the command line. 5 | # @handle_command_line() 6 | class ExampleEmitter(Emitter): 7 | """Example `Emitter` that can be used to easily get started creating new bindings. 8 | 9 | This example is a simplified and commented version of the C emitter available 10 | in the src/lang/c.py file.""" 11 | 12 | @property 13 | def language(self): 14 | # Returns the identifier of the language. 15 | # 16 | # This identifier will be used to name the directory in which 17 | # all files are put. 18 | return 'example' 19 | 20 | @property 21 | def filename(self): 22 | # Relative path to the file that is to be generated. 23 | # 24 | # In this case, a file '$OUTPUT_DIR/subdir/arch.ext' would be generated. 25 | return f'subdir/{self.arch}.ext' 26 | 27 | @property 28 | def test_filename(self): 29 | # (Optional) 30 | # Relative path to the test file that is to be generated. 31 | return f'test/{self.arch.ext}' 32 | 33 | 34 | def get_type_name(self, ty: IrType) -> str: 35 | # (Optional) 36 | # Return another name for the given type for this language. 37 | # 38 | # This can be used to customize the built-in types or coding conventions 39 | # specific to each language. This method is used by `IrType.__str__()` when 40 | # it is overriden. 41 | # 42 | # The methods `get_operator(self, Operator)`, `get_function_name(self, Function)` 43 | # and # `get_builtin_name(self, Builtin)` serve the same purpose, but for 44 | # `Operator`, `Function` and `Builtin` respectively. 45 | # 46 | # Furthermore, the utility function `` is provided to easily replace 47 | # common patterns. If no pattern matches the given type, its initial name is 48 | # returned. 49 | return replace_pattern({ 50 | r'uint(\d+)': r'u\1' 51 | }, ty.id) 52 | 53 | 54 | @staticmethod 55 | def register(parser: ArgumentParser): 56 | # (Optional) 57 | # Register custom command line parameters for additional options. 58 | group = parser.add_argument_group('Example language') 59 | group.add_argument('-s', '--long-name', action='store_true', help='Help text.') 60 | 61 | def __init__(self, args: Namespace, arch: str): 62 | # (Optional) 63 | # Initialize the emitter, giving it the possibility to access its registered 64 | # arguments. 65 | super().__init__(args) 66 | 67 | self.example_arg : bool = args.long_name 68 | 69 | # You can also override `self.indent` depending on your needs 70 | self.indent = Indent(' ') # Default is two spaces 71 | 72 | 73 | def write_header(self): 74 | # (Optional) 75 | self.writeline('# Header...') 76 | self.indent += 1 77 | 78 | # Other utilities are provided for writing. 79 | 80 | self.write('# Not only ', type(str), 'ings.\n', indent=True) 81 | self.writeline('# Also, ablility to automatically add \\n.', indent=True) 82 | 83 | self.writei('# If you don\'t like writing `indent=True`, just add `i` to the\n') 84 | self.writelinei('# function name!') 85 | 86 | # Oh, and writing expressions or statements via `self.write` is optimized, 87 | # and just as fast as using `self.write_expr` and `self.write_stmt`. 88 | 89 | with self.indent.further(): 90 | # Here, all indent is greater by a single unit. 91 | # Additionally, an integer can be given to control how much the indent 92 | # changes. 93 | self.writelinei('# Indented further...') 94 | 95 | def write_separator(self): 96 | # (Optional) 97 | self.writeline('# Write text that comes after custom declarations, but before\n', 98 | '# function definitions.') 99 | 100 | def write_footer(self): 101 | # (Optional) 102 | self.write('# Write whatever goes at the end of the file.') 103 | self.indent -= 1 104 | 105 | 106 | def write_expr(self, expr: Expression): 107 | # Here, expressions should be written to the output stream based on their type. 108 | # 109 | # Also, please note that every call made to methods in the `Emitter` class 110 | # modify the Expression.__str__() and Statement.__str__() 111 | if isinstance(expr, Binary): 112 | self.write('(', expr.l, ' ', expr.op, ' ', expr.r, ')') 113 | 114 | elif isinstance(expr, Unary): 115 | self.write(expr.op, expr.v) 116 | 117 | elif isinstance(expr, Ternary): 118 | self.write('(', expr.condition, ' ? ', expr.consequence, ' : ', expr.alternative, ')') 119 | 120 | elif isinstance(expr, Var): 121 | self.write(expr.name) 122 | 123 | elif isinstance(expr, Call): 124 | self.write(expr.builtin, '(', join_any(', ', expr.args), ')') 125 | 126 | elif isinstance(expr, Literal): 127 | self.write(expr.value) 128 | 129 | else: 130 | raise UnsupportedExpression(expr) 131 | 132 | def write_stmt(self, stmt: Statement): 133 | # Same but with statements... 134 | if isinstance(stmt, Assign): 135 | self.writelinei(stmt.variable, ' = ', stmt.value, ';') 136 | 137 | elif isinstance(stmt, Conditional): 138 | self.writelinei('if (', stmt.condition, ')') 139 | 140 | with self.indent.further(): 141 | self.write_stmt(stmt.consequence) 142 | 143 | if stmt.alternative: 144 | self.writelinei('else') 145 | 146 | with self.indent.further(): 147 | self.write_stmt(stmt.alternative) 148 | 149 | elif isinstance(stmt, Block): 150 | with self.indent.further(-1): 151 | self.writelinei('{') 152 | 153 | for s in stmt.statements: 154 | self.write_stmt(s) 155 | 156 | with self.indent.further(-1): 157 | self.writelinei('}') 158 | 159 | elif isinstance(stmt, Set): 160 | self.writelinei(f'*({stmt.type}*)(*buf) = ', stmt.value, ';') 161 | self.writelinei(f'(*buf) += ', stmt.type.size, ';') 162 | 163 | elif isinstance(stmt, Define): 164 | self.writelinei(f'{stmt.type} {stmt.name} = ', stmt.value, ';') 165 | 166 | else: 167 | raise UnsupportedStatement(stmt) 168 | 169 | def write_function(self, fun: Function): 170 | # Emit full function bodies, including their signature. 171 | # 172 | # Here is a simplified example of the C emitter. 173 | self.write(f'void {fun.name}(void** buf') 174 | 175 | for name, ctype, _ in fun.params: 176 | self.write(f', {ctype} {name}') # Here, `ctype` will use `get_type_name` 177 | # defined above. 178 | 179 | # Note the third tuple element, which is usually named 'usage type'. 180 | # It defines how the value will be used within the function. 181 | # 182 | # For example, in ARM, many instructions have switches that can be enabled. 183 | # In practice, those switches are encoded by shifting them to the 184 | # left by a constant value. In most languages, though, shifting a bool by 185 | # an integer is illegal. 186 | # 187 | # Thus, for a switch boolean, `ctype` would be TYPE_BOOL, but `usagetype` 188 | # would be TYPE_U32. Most languages actually convert these values at the 189 | # start of each function, but you may implement this as you like. 190 | 191 | self.write(') {\n') 192 | 193 | self.indent += 1 194 | 195 | for condition in fun.conditions: 196 | # Some assertions are made, and can be implemented as you wish. 197 | self.writelinei('assert(', condition, ');') 198 | 199 | for stmt in fun.body: 200 | # Finally, write the body of the function! 201 | self.write_stmt(stmt) 202 | 203 | self.write('}\n\n') 204 | self.indent -= 1 205 | 206 | 207 | def write_decl(self, decl: Declaration): 208 | # Emit declarations (either `Enumeration`s or `DistincType`s). 209 | # 210 | # They can have very different behaviors depending on whether 211 | # they are flags, so watch out for that! 212 | 213 | if isinstance(decl, Enumeration): 214 | self.write('/// ', decl.descr, '\n') 215 | self.write('typedef enum {\n') 216 | 217 | for _, value, descr, fullname in decl.members + decl.additional_members: 218 | self.write(' ///\n') 219 | self.write(' /// ', descr, '\n') 220 | self.write(' ', fullname, ' = ', value, ',\n') 221 | 222 | self.write('} ', decl.type, ';\n\n') 223 | 224 | elif isinstance(decl, DistinctType): 225 | self.write('#define ', decl.type, ' ', decl.type.underlying, '\n') 226 | 227 | for name, value in decl.constants: 228 | self.write('#define ', decl.type, '_', name, ' ', value, '\n') 229 | 230 | else: 231 | raise UnsupportedDeclaration(decl) 232 | 233 | 234 | def write_test_header(self): 235 | # (Optional) 236 | # Write the header of the test file. 237 | # 238 | # If @test_filename is None, this function will never be called. 239 | self.writelinei('...') 240 | self.indent += 1 241 | 242 | def write_test_footer(self): 243 | # (Optional) 244 | # Write the footer of the test file. 245 | # 246 | # If @test_filename is None, this function will never be called. 247 | self.writei('...') 248 | self.indent -= 1 249 | 250 | def write_test(self, test: TestCase): 251 | # (Optional) 252 | # Write the given test case to the test file. 253 | # 254 | # If @test_filename is None, this function will never be called. 255 | self.writelinei(f'void ', test.name.replace(' ', '_'), '() {') 256 | self.indent += 1 257 | 258 | for func, args in test.calls: 259 | # Each call is made up of one Function defined in the architecture, 260 | # and a list of arguments (which are either constants, enum members 261 | # or literals). 262 | self.writelinei('buf.', func.name, '(', '...', ')') 263 | 264 | self.writelinei('assert bytes == b"', test.expected_string, '"') 265 | self.writeline() 266 | 267 | self.indent -= 1 268 | -------------------------------------------------------------------------------- /templates/testsource.py: -------------------------------------------------------------------------------- 1 | from asmdot.arch import * # pylint: disable=W0614 2 | 3 | class ExampleTestSource(TestSource): 4 | """Example `TestSource` that can be used to easily get started creating tests for all 5 | languages.""" 6 | 7 | @property 8 | def name(self) -> str: 9 | # Return the name of the architecture for which the tests are provided. 10 | # It /must/ match the name provided by the `Architecture`. 11 | return 'example' 12 | 13 | @property 14 | def test_cases(self) -> TestCases: 15 | # Return test cases that can be used in all languages to compare 16 | # calls, and the generated machine code. 17 | 18 | yield TestCase('short description of the test (spaces are allowed)', [ 19 | self.make_call('full_instr_name', "42'int8", "0xff'int32", "0b1111'reg32"), 20 | self.make_call('other_full_instr_name', "Enum::Member", "Enum.OtherMember") 21 | ], bytearray(b'\x04\x02')) 22 | --------------------------------------------------------------------------------