{"id":25129535,"url":"https://github.com/xarantolus/ax","last_synced_at":"2025-04-23T16:27:06.758Z","repository":{"id":64551218,"uuid":"536060850","full_name":"xarantolus/ax","owner":"xarantolus","description":"Minimal x86-64 emulator for WebAssembly - run ELF binaries in your browser","archived":false,"fork":false,"pushed_at":"2024-03-25T22:25:26.000Z","size":1728,"stargazers_count":51,"open_issues_count":3,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-19T22:07:20.992Z","etag":null,"topics":["ax-x86","elf","npm-package","rust","webassembly","x86","x86-64","x86-64-emulator","x86-emulator"],"latest_commit_sha":null,"homepage":"https://ax.010.one","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xarantolus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-09-13T09:52:55.000Z","updated_at":"2025-04-10T01:51:17.000Z","dependencies_parsed_at":"2024-10-12T00:16:33.828Z","dependency_job_id":null,"html_url":"https://github.com/xarantolus/ax","commit_stats":{"total_commits":361,"total_committers":3,"mean_commits":"120.33333333333333","dds":0.03324099722991691,"last_synced_commit":"c4c21e50d4866f4fefb2557e1b0f0161905ccf1f"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xarantolus%2Fax","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xarantolus%2Fax/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xarantolus%2Fax/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xarantolus%2Fax/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xarantolus","download_url":"https://codeload.github.com/xarantolus/ax/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250469935,"owners_count":21435731,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ax-x86","elf","npm-package","rust","webassembly","x86","x86-64","x86-64-emulator","x86-emulator"],"created_at":"2025-02-08T12:17:55.885Z","updated_at":"2025-04-23T16:27:06.739Z","avatar_url":"https://github.com/xarantolus.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [ax](https://ax.010.one)\nThis is a minimal x86-64 emulator for WebAssembly. It executes real machine code and can be used to emulate x86-64 user-space programs in the browser.\n\nCurrently implemented are \u003c!-- stats-count-marker --\u003e315 opcodes for 65 mnemonics (37 complete, 28 partial)\u003c!-- stats-count-marker --\u003e, which is only a very small subset of the more than 981 available mnemonics with at least 3684 variants \u003csup\u003e[Source](https://dl.acm.org/doi/pdf/10.1145/2908080.2908121)\u003c/sup\u003e. More detailed stats can be found via the [`stats.py`](stats.py) script.\n\nNote that not all implemented instructions work exactly the same way as on real hardware, but the goal is to be as close as possible while staying reasonable. Notable exceptions are instructions that interact with the operating system (interrupts, syscalls) and the omission of all flags that are not used by jump instructions.\n\nIn addition to the emulator itself, this repository contains scripts that should be interesting for anyone who wants to write an x86-64 emulator. The most important one, [`t.py`](t.py), automatically generates test cases for an instruction by trying out different inputs and thus finding interesting inputs, outputs and flag combinations. See [automatically generate test cases](#automatically-generate-test-cases) for more information.\n\n## Try it out!\nYou can try out the emulator right now by visiting [the website](https://ax.010.one), selecting a suitable ELF binary and clicking \"Run\". The emulator will then execute the binary and show the output. Note that currently support for ELF binaries is limited/buggy (there are some problems getting libc to work), you can however use binaries from the [`examples/programs`](examples/programs) directory. The source code for this site is in the [`examples/web`](examples/web) directory.\n\n\n### Usage on the web\n* The [demo site](https://ax.010.one) uses `ax` to run ELF binaries in the browser\n* [MemeAssembly](https://kammt.github.io/MemeAssembly/) is a meme programming language that compiles to x86-64 instructions. My [MemeAssembly Playground](https://memeasm.010.one) uses `ax` to run this language right in the browser, emulating some syscalls like `read` and `write` to make the programs work.\n* Maybe you? If you use `ax` in a project, please let me know and I'll add it here! :)\n\n## How to use\nThe emulator is compiled to WebAssembly and can be used as a JavaScript Module. This works in modern browsers.\n\nThe recommended approach is to just install the [NPM module](https://www.npmjs.com/package/ax-x86):\n\n```sh\nnpm i ax-x86\n```\n\nBefore using any functions, you have to make sure the WASM binary has been downloaded using the default `init` function:\n\n```js\nimport init, { version } from 'ax-x86';\n// This will download the WASM binary and initialize the module\nawait init();\n// Now you can use the module\nconsole.log(\"ax version:\", version());\n```\n\nThis readme contains two examples that show typical use cases, for a more detailed API reference, see [the documentation](https://ax.010.one/docs/).\n\nTwo warnings/pitfalls when using this emulator:\n* Make sure that all numbers are passed as `bigint`, which can be done using an `n` suffix. `0x1000n` is a `bigint` literal (which is what we want), `0x1000` is a `number` (which will *not* work)\n* When using frontend frameworks, it is recommended to await the `init` function before your components are mounted, e.g. in a `setup` function. This will make sure the WASM binary is downloaded before the component is rendered. You can look at [this Vue component](examples/web/src/components/Demo.vue) for an example.\n\n\n### Simple emulation of instructions\nThe following is a simple example that executes a few instructions and logs the calculated result.\n\n\u003cdetails\u003e\n\u003csummary\u003eOpen for more info on the example\u003c/summary\u003e\n\n```js\nimport init, { Axecutor, Mnemonic, Register, version } from 'ax-x86';\nawait init();\n\n// Define bytes for x86 instructions:\nlet code = new Uint8Array([\n    // mov rax, 0xff\n    0x48, 0xc7, 0xc0, 0xff, 0, 0, 0,\n    // mov rbx, 0xf\n    0x48, 0xc7, 0xc3, 0xf, 0, 0, 0,\n    // and rax, rbx\n    0x48, 0x21, 0xd8\n]);\n\n// Create a new emulator instance\n// You could also create an instance from an ELF/Linux binary using `Axecutor.from_binary` instead\nlet ax = new Axecutor(\n    code,\n    // Code start address, this is where the first instruction byte is located\n    0x1000n,\n    // Entrypoint address, this is where execution starts. It is usually, but not always, the same as the code start address\n    0x1000n\n);\nconsole.log(\"Initial state:\", ax.toString());\n\n// One could set up a stack of size 0x1000 here, but it's not necessary for this example\n// This automatically writes the stack pointer to RSP\n// let stack_addr = ax.init_stack(0x1000n);\n\n// This function will be called before any \"Mov\" instruction is executed. There's also a hook_after_mnemonic function.\n// It can be used to e.g. implement custom syscall handlers\nax.hook_before_mnemonic(Mnemonic.Mov, (instance) =\u003e {\n    console.log(\"Executing a mov instruction\");\n\n    // Here you can e.g. modify registers, memory etc.\n    instance.reg_write_64(Register.RCX, 0xabn);\n\n    // this function *MUST* return one of\n    // - instance.commit(): keep the changes we made in this handler and continue execution\n    // - instance.stop(): keep changes, stop execution and return from the ax.execute() function\n    // - instance.unchanged(): don't keep changes, continue execution\n\n    // this will reset RCX to its previous value\n    return instance.unchanged();\n});\n\n// Execute all instructions\nawait ax.execute();\n\n// Log the final state of the emulator\nconsole.log(\"Final state:\", ax.toString());\n\n// Outputs \"15\"\nconsole.log(\"RAX:\", ax.reg_read_64(Register.RAX));\n```\n\nThe emulator will just stop when reaching the end of the code.\n\n\u003c/details\u003e\n\n### Emulate ELF binaries\nThe emulator also has some convenience functions for handling Linux/ELF binaries. The [demo site](https://ax.010.one) uses these convenience functions to emulate programs.\n\n\u003cdetails\u003e\n\u003csummary\u003eOpen for more info on how to emulate ELF files\u003c/summary\u003e\n\n\nOne thing to note is that binaries usually exit via the `exit` syscall, which is not implemented by default (same as any other syscall).\nYou can either implement your own syscall handler that handles the `exit` syscall, or you can use the [`handle_syscalls` method](https://ax.010.one/docs/classes/Axecutor.html#handle_syscalls) to register predefined handlers for a small set of syscalls.\n\n\n\n```js\nlet ax = Axecutor.from_binary(/* elf file content as Uint8Array */);\n\n// Set up the stack according to the System V ABI.\n// This sets up memory locations for command-line arguments and environment variables\n// and writes the stack pointer to RSP\nax.init_stack_program_start(\n  8n * 1024n, // Stack size\n  [\"/bin/my_binary\", \"arg1\", \"arg2\"], // argv\n  [\"COLORTERM=truecolor\", \"TERM=xterm-256color\" ] // environment variables\n);\n\n// Use a predefined handler for the `exit` syscall, which just stops execution\nax.handle_syscalls(Syscall.Exit);\n\n// Register a syscall handler for the `write` syscall (1)\nlet syscallHandler = async function (ax: Axecutor) {\n  let syscall_num = ax.reg_read_64(Register.RAX);\n  let rdi = ax.reg_read_64(Register.RDI);\n  let rsi = ax.reg_read_64(Register.RSI);\n  let rdx = ax.reg_read_64(Register.RDX);\n\n  console.log(`Syscall ${syscall_num} with args ${rdi}, ${rsi}, ${rdx}`);\n\n  switch (syscall_num) {\n    case 1n: {\n      // WRITE syscall MUST write to stdout or stderr (stdin supported for compatibility)\n      if (rdi != 0n \u0026\u0026 rdi != 1n \u0026\u0026 rdi != 2n) {\n        throw new Error(`WRITE syscall: cannot write non-std{out,err} (!= 1,2) fds, but tried ${rdi}`);\n      }\n\n      // Read data we should write from memory\n      let result_buf = ax.mem_read_bytes(rsi, rdx);\n\n      // Decode to string\n      let result_str = new TextDecoder().decode(result_buf);\n\n      // Do something with the string\n      console.log(\"WRITE syscall:\", result_str);\n\n      // Return the number of bytes that were written in RAX,\n      // that way the program knows it worked\n      ax.reg_write_64(Register.RAX, rdx);\n\n      return ax.commit();\n    }\n  }\n\n  throw `Unhandled syscall ${syscall_num}`;\n}\n\n// Register the write syscall handler\nax.hook_before_mnemonic(Mnemonic.Syscall, syscallHandler);\n\n// Log function calls\nax.hook_after_mnemonic(Mnemonic.Call, function (ax: Axecutor) {\n  // After a call instruction, RIP points to the first instruction of the called function\n  let func_addr = ax.reg_read_64(Register.RIP);\n  // Resolve a name for that function - it might be undefined if no symbol is available\n  // Make sure to compile your binary with -g to include symbols\n  let name = ax.resolve_symbol(func_addr);\n\n  console.log(\"Calling function \" + (name || \"\u003cunknown\u003e\") + \" at \" + func_addr.toString(16) + \"\\n\");\n  return ax.unchanged();\n});\n\n// Execute the program\nawait ax.execute();\n```\n\nIf your binaries need more system calls, you can look at the [example site implementation](examples/web/src/components/Demo.vue), see [the docs for pre-existing handler functions](https://ax.010.one/docs/enums/Syscall.html) or get more details for your own implementation [here](https://syscalls.w3challs.com/?arch=x86_64).\n\n\u003c/details\u003e\n\n## Contributing\nIf you want to contribute to this project, that's great! A good way to involved is using the emulator and finding things that could be improved :)\nYou could e.g. get started by adding support for a new instruction mnemonic. There's a tutorial on how to do that below.\nIf you run into problems setting up the development tools or have any other questions, feel free to open an issue.\n\nThe [`Makefile`](Makefile) has a lot of targets that can be useful for development. The `test` target runs tests both on your native machine and in WASM via NodeJS, making sure implemented instructions behave as expected in the target environment. The `test-js` target makes sure the public-facing JS API works as expected.\n\nFor any changes you want to make, you can branch off from the `develop` branch. Please format the code using `make fmt` before submitting a pull request and make sure that `make precommit` passes.\n\nIf you want to work on something, I recommend having two terminals opened: one job for automatically running tests on changes (`make watch-tests`) and one for automatically rebuilding the module, serving the web server and rebuilding the example programs (`make watch`). This configuration is already included in the [`tasks.json`](.vscode/tasks.json) file, VSCode should offer to run them automatically.\n\n### Development setup\nIf you want to develop in a Docker container, there's a [Dev Container](https://containers.dev) configuration provided in the repository. I would however recommend setting up the development tools on your native machine:\n\n1. Make sure you have installed Rust/Cargo, [`wasm-pack`](https://rustwasm.github.io/wasm-pack/installer/), Node.js, NPM, Python, PIP, Make, GCC and the GNU Assembler\n   - You can optionally install [mold](https://github.com/rui314/mold) to speed up link times (mostly for tests); the Makefile will automatically use it if it's installed\n2. You should now be able to build the WebAssembly module with `make` (this will also build a native binary `ax`)\n3. You can run `make dependencies` to install `cargo-watch`, `cargo-tarpaulin` (for generating test coverage info files) and python script dependencies\n4. Try out running `make test` or `make watch-tests` to run tests\n5. Run `make watch-debug` in one terminal to rebuild the WebAssembly module on changes, then run `make web` in another terminal to start the development server. You can also just run `make watch`, which runs both in the same terminal\n6. Open the local example site and make changes! (link should be in the `make web`/`make watch` output)\n\n#### How to implement a new mnemonic\nThe [`generate.py`](generate.py) script is used for generating instruction implementation stubs. You can e.g. run `python3 generate.py push` to generate a file for all instruction mnemonics that start with `push`; if you only want more exact matches use `push_` as argument. Note that you must have run a build for the WebAssembly package, as otherwise the script won't be able to find the files from the [`iced-x86` crate](https://crates.io/crates/iced-x86) that are used for generating the stubs.\n\nAfterwards, run `make switch` to regenerate the instruction mnemonic switch statement (in [`src/instructions/generated.rs`](src/instructions/generated.rs)). Now your new stub functions are reachable.\n\nAfterwards, it is recommended to automatically generate test cases, then implement the instruction. You can also add new [integration tests](src/instructions/integration_tests.rs) that use the instruction, this is especially important for instructions that operate on flags.\n\n#### Automatically generate test cases\nThe repository comes with scripts for generating test cases for x86-64 instructions. They basically try out instructions on your real x86-64 CPU and extract the processor state change.\n\nThe test cases are generated by running e.g. `python3 t.py add al, [rbx]`, which tries around 6000 different inputs for the instruction (the extreme mode with `-e` tries around 100k inputs).\nThe generated test cases are deduplicated, resulting in only one test case per unique combination of flags that are set and cleared. Note that not necessarily all combinations will be discovered.\nWhen generating tests, make sure that only the flags that are defined in the manual are tested for, e.g. for `imul` where only `CF` and `OF` are defined, you would pass `-f CF,OF` to the script. If an instruction needs to be tested with different flag values (e.g. `adc`), you can pass e.g. `-s cf` to permutate one or multiple flags.\nFor some instructions it makes more sense go by *result* instead of flags, you can do this by passing `-r` to the script.\nFor instructions with implicit arguments (e.g. `imul rax` also modifies `rdx`), you can pass `-i rdx` to the script to also test the implicit arguments (any comma separated list of operands should work).\n\nHere is one of 19 test cases that was automatically discovered for `add al, [rbx]` (without `-e`):\n```rust\n// ax_test is macro that sets up the emulator, then runs setup and a post-execution assertion function\n// add al, byte ptr [rbx]\nax_test![add_al_byte_ptr_rbx_cf;\n    // The encoded instruction bytes:\n    0x2, 0x3;\n    // A setup function that is called before the instruction is executed:\n    |a: \u0026mut Axecutor| {\n        write_reg_value!(b; a; AL; 0x8);\n        // This sets up a memory area with a size of one byte, containing 0xff\n        init_mem_value!(b; a; 0x1000; 0xff); // -1\n        // RBX points to that memory area\n        write_reg_value!(q; a; RBX; 0x1000);\n    };\n    // This function is called after the instruction ran and checks the result:\n    |a: Axecutor| {\n        assert_reg_value!(b; a; AL; 0x7);\n        // Also make sure the source operand is unchanged\n        assert_reg_value!(q; a; RBX; 0x1000);\n        assert_mem_value!(b; a; 0x1000; 0xff);\n    };\n    // On the left side of `;` are the flags that must be set after the instruction ran,\n    // on the right are flags that must not be set\n    (FLAG_CF; FLAG_PF | FLAG_ZF | FLAG_SF | FLAG_OF)\n];\n```\n\nThe test case generation script [`t.py`](t.py) supports register operands (both general purpose and SSE 128-bit), as well as a subset of memory and immediate operands. It requires that the GNU Assembler `as` and `gcc` are installed and must be run on x86-64 Linux. It places thousands of generated assembly files and binaries in `/dev/shm/ax_*`, so in case you run out of RAM that is the place to check.\n\nAnother script for testing jumps ([`j.py`](j.py)) is also available, but it's not as automated. Some other convenience copy-paste texts can be generated with [`a.py`](a.py), e.g. with `python3 a.py add al, [rbx]` and then selecting `u` you'll get a code snippet for a JavaScript `Uint8Array` containing the bytes of the instruction.\n\nIf you want to adjust `t.py` for testing your own emulator, you should adjust the `__str__` method of the `TestCase` class to generate different syntax with the same information.\n\n### Interesting Documentation\nHere are some useful links for more information about x86-64/AMD64, the System V ABI and ELF files:\n\n* [Intel x64 Manuals](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html)\n* [AMD64 Developer Guides](https://developer.amd.com/resources/developer-guides-manuals/)\n* [System V ABI](https://gitlab.com/x86-psABIs/x86-64-ABI) ([direct link](https://gitlab.com/x86-psABIs/x86-64-ABI/-/jobs/artifacts/master/raw/x86-64-ABI/abi.pdf?job=build))\n* [Linux ELF Specification](https://refspecs.linuxfoundation.org/elf/elf.pdf), [OSDev ELF with info on loading and relocations](https://wiki.osdev.org/ELF)\n* Man pages: `man 8 ld.so`\n\n### Limitations\nHere are some limitations that could be inspiration for future features:\n\n* Only the Signed, Carry, Overflow, Zero and Parity status flags are supported\n* Most instructions aren't implemented, especially\n  * Anything I found too legacy\n  * Many instructions\n* Syscall and Interrupts are not implemented to spec.\n  * If you have registered hooks using `hook_before_mnemonic` or `hook_after_mnemonic`) they are essentially a no-op with your handler executing\n  * If no hooks are registered and a syscall/interrupt is executed, an exception is thrown\n* The memory implementation is quite weird and needs an overhaul\n  * Access restrictions (partially implemented), page management (maybe better to leave to the user) etc. is missing\n* ELF file parsing is currently really basic\n  * Binaries with libc don't work due to relocations and more not being implemented\n  * Basically only very basic binaries work\n* Segments are not really implemented to spec, but kind of work OK\n\n### Ideas\nSee [issue 1](https://github.com/xarantolus/ax/issues/1) for some ideas for future features. Also feel free to open an issue if you have an idea :)\n\n\n## [License](LICENSE)\nAGPL v3\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxarantolus%2Fax","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxarantolus%2Fax","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxarantolus%2Fax/lists"}