Skip to content

Commit 3938e0e

Browse files
committed
Release v0.3.0
1 parent 968fcfc commit 3938e0e

File tree

7 files changed

+98
-15
lines changed

7 files changed

+98
-15
lines changed

.github/workflows/release.yml

+1
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,5 @@ jobs:
6868
draft: true
6969
fail_on_unmatched_files: true
7070
generate_release_notes: true
71+
tag_name: ${{ env.TAG }}
7172
if: env.TAG

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ax-x86"
3-
version = "0.2.1"
3+
version = "0.3.0"
44
authors = ["xarantolus <[email protected]>"]
55
edition = "2018"
66
license = "GPL-3.0"

README.md

+84-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ The recommended approach is to just install the [NPM module](https://www.npmjs.c
1919
npm i ax-x86
2020
```
2121

22-
To actually emulate programs, you have to make sure the WASM binary has been downloaded using the default `init` function:
22+
Before using any functions, you have to make sure the WASM binary has been downloaded using the default `init` function:
2323

2424
```js
2525
import { default as init, version } from 'ax-x86';
@@ -29,7 +29,16 @@ await init();
2929
console.log("ax version:", version());
3030
```
3131

32-
Here is a simple example that executes a few instructions and prints the result:
32+
Two warnings/pitfalls when using this emulator:
33+
* 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)
34+
* 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 the [this Vue component](examples/web/src/components/Initial.vue) for an example.
35+
36+
37+
### Simple emulation of instruction
38+
The following is a simple example that executes a few instructions and prints the calculated result.
39+
40+
<details>
41+
<summary>Open for more info on the example</summary>
3342

3443
```js
3544
import { default as init, Axecutor, Mnemonic, Register, version } from 'ax-x86';
@@ -87,9 +96,80 @@ console.log("Final state:", ax.toString());
8796
console.log("RAX:", ax.reg_read_64(Register.RAX));
8897
```
8998

90-
Warning: Make sure that all numbers are passed as `bigint`, hence the `n` suffix!
99+
The emulator will just stop when reaching the end of the code.
100+
101+
</details>
102+
103+
### Emulate ELF binaries
104+
The emulator also has some convenience functions for handling Linux/ELF binaries. The [test site](https://ax.010.one) uses these convenience functions to emulate programs.
105+
106+
<details>
107+
<summary>Open for more info on how to emulate ELF files</summary>
108+
One thing to note is that binaries usually exit via the `exit` syscall, which is not implemented by default (same as any other syscall). You must handle it yourself, like in the following example:
109+
110+
```js
111+
let ax = Axecutor.from_binary(/* elf file content as Uint8Array */);
112+
113+
// Set up the stack according to the System V ABI.
114+
// This sets up memory locations for command-line arguments and environment variables
115+
// and writes the stack pointer to RSP
116+
ax.init_stack_program_start(
117+
8n * 1024n, // Stack size
118+
["/bin/my_binary", "arg1", "arg2"],
119+
[ /* environment variables */ ]
120+
);
121+
122+
123+
let syscallHandler = async function (ax: Axecutor) {
124+
let syscall_num = ax.reg_read_64(Register.RAX);
125+
let rdi = ax.reg_read_64(Register.RDI);
126+
let rsi = ax.reg_read_64(Register.RSI);
127+
let rdx = ax.reg_read_64(Register.RDX);
128+
129+
console.log(`Syscall ${syscall_num} with args ${rdi}, ${rsi}, ${rdx}`);
130+
131+
switch (syscall_num) {
132+
case 1n: {
133+
// WRITE syscall MUST write to stdout or stderr (stdin supported for compatibility)
134+
if (rdi != 0n && rdi != 1n && rdi != 2n) {
135+
throw new Error(`WRITE syscall: cannot write non-std{out,err} (!= 1,2) fds, but tried ${rdi}`);
136+
}
137+
138+
// Read whatever was
139+
let result_buf = ax.mem_read_bytes(rsi, rdx);
140+
141+
// Convert to string
142+
let result_str = new TextDecoder().decode(result_buf);
143+
144+
// Do something with the string
145+
console.log("WRITE syscall:", result_str);
146+
147+
// Return the number of bytes that were written in RAX,
148+
// that way the program knows it worked
149+
ax.reg_write_64(Register.RAX, rdx);
150+
151+
return ax.commit();
152+
}
153+
case 60n: {
154+
// EXIT syscall
155+
console.log("EXIT syscall: exiting with code " + rdi);
156+
return ax.stop();
157+
}
158+
}
159+
160+
throw `Unhandled syscall ${syscall_num}`;
161+
}
162+
163+
// Register the syscall handler
164+
ax.hook_before_mnemonic(Mnemonic.Syscall, syscallHandler);
165+
166+
// Execute the program
167+
await ax.execute();
168+
```
169+
170+
If your binaries need more syscalls, you can look at the [example site implementation](examples/web/src/components/Initial.vue) or get more details [here](https://syscalls.w3challs.com/?arch=x86_64).
91171

92-
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 the [this Vue component](examples/web/src/components/Initial.vue) for an example.
172+
</details>
93173

94174
## Contributing
95175
If 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 :)

examples/programs/argc/Makefile examples/programs/args/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
PROGRAM_NAME=argc
1+
PROGRAM_NAME=args
22

33
$(PROGRAM_NAME).bin: $(PROGRAM_NAME).S
44
gcc $^ -m64 -o $@ -nostdlib -static
File renamed without changes.

examples/web/package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/web/src/components/Initial.vue

+10-8
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,11 @@ export default defineComponent({
8989
source_name: "strlen/strlen.s",
9090
},
9191
{
92-
name: "argc",
93-
binary: "argc.bin",
94-
source_name: "argc/argc.s",
92+
name: "Print command-line arguments",
93+
binary: "args.bin",
94+
source_name: "args/args.S",
9595
},
96+
/*
9697
{
9798
name: "Exit Code (C)",
9899
binary: "exit_c.bin",
@@ -108,6 +109,7 @@ export default defineComponent({
108109
binary: "thread_local.bin",
109110
source_name: "thread_local_c/thread_local.c",
110111
}
112+
*/
111113
]
112114
113115
await init();
@@ -176,6 +178,11 @@ export default defineComponent({
176178
177179
return ax.unchanged();
178180
}
181+
case 60n: {
182+
console.log("EXIT syscall: exiting with code " + rdi.toString(16));
183+
// EXIT syscall
184+
return ax.stop();
185+
}
179186
case 12n: {
180187
// brk syscall
181188
@@ -215,11 +222,6 @@ export default defineComponent({
215222
216223
return ax.commit();
217224
}
218-
case 60n: {
219-
console.log("EXIT syscall: exiting with code " + rdi.toString(16));
220-
// EXIT syscall
221-
return ax.stop();
222-
}
223225
case 158n: { // arch_prctl
224226
console.log("FS arch_prctl: operation 0x" + rdi.toString(16) + ", addr 0x" + rsi.toString(16))
225227
if (rdi === 0x1002n) {

0 commit comments

Comments
 (0)