Skip to content

Instantly share code, notes, and snippets.

@kg
Last active April 2, 2024 00:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kg/76b420ff56c356dc4914dce5d7f8c9dc to your computer and use it in GitHub Desktop.
Save kg/76b420ff56c356dc4914dce5d7f8c9dc to your computer and use it in GitHub Desktop.
How to get native disassembly for a wasm module

How to get native disassembly for a wasm module out of Firefox's JS runtime

Build Spidermonkey from source

Follow steps 1 and 2 here: https://firefox-source-docs.mozilla.org/setup/linux_build.html#building-firefox-on-linux

Then set up a debug mozconfig and build using it as described here: https://firefox-source-docs.mozilla.org/js/build.html#developer-debug-build

Once you're done you should have a mozilla-unified/obj-debug-x86_64-pc-linux-gnu/dist/bin/js. Building from source is necessary to get the ability to disassemble native code.

Compile the code you care about into a module with the right flags

i.e. emcc virtcall-test.c -g -Os -msimd128. It's important to set the right compile flags, especially -g for debug information.

Disassemble the module

You can use the disasm.js script from this gist combined with the bin/js from your source build, like so:

/home/kate/Projects/firefox/mozilla-unified/obj-debug-x86_64-pc-linux-gnu/dist/bin/js --ion-eager -f disasm.js 2> disasm.out

Profit!

(module
(type (;0;) (func (param i32) (result i32)))
(type (;1;) (func (result i32)))
(type (;2;) (func))
(type (;3;) (func (param i32 i32) (result i32)))
(type (;4;) (func (param i32)))
(func $__wasm_call_ctors (type 2)
nop)
(func $main (type 3) (param i32 i32) (result i32)
local.get 0
i32.const 2
i32.shl
i32.const 1024
i32.add
i32.load
call $call_a_target)
(func $call_a_target (type 0) (param i32) (result i32)
i32.const 32
local.get 0
call_indirect (type 0))
(func $fn_a (type 0) (param i32) (result i32)
local.get 0
i32.const 1
i32.shl)
(func $fn_b (type 0) (param i32) (result i32)
local.get 0
i32.const 1
i32.add)
(func $__errno_location (type 1) (result i32)
i32.const 1036)
(func $stackSave (type 1) (result i32)
global.get $__stack_pointer)
(func $stackRestore (type 4) (param i32)
local.get 0
global.set $__stack_pointer)
(func $stackAlloc (type 0) (param i32) (result i32)
(local i32)
global.get $__stack_pointer
local.get 0
i32.sub
i32.const -16
i32.and
local.tee 1
global.set $__stack_pointer
local.get 1)
(table (;0;) 3 3 funcref)
(memory (;0;) 256 256)
(global $__stack_pointer (mut i32) (i32.const 66576))
(export "memory" (memory 0))
(export "__wasm_call_ctors" (func $__wasm_call_ctors))
(export "__main_argc_argv" (func $main))
(export "__indirect_function_table" (table 0))
(export "__errno_location" (func $__errno_location))
(export "stackSave" (func $stackSave))
(export "stackRestore" (func $stackRestore))
(export "stackAlloc" (func $stackAlloc))
(elem (;0;) (i32.const 1) func $fn_a $fn_b)
(data $.rodata (i32.const 1024) "\01\00\00\00\02"))
let wasmBytes = os.file.readFile("a.out.wasm", "binary");
let wasmModule = new WebAssembly.Module(wasmBytes);
wasmDis(wasmModule);
Sample output (truncated):
--------------------------------------------------
Kind = Function, index = 1, name = main:
00000000 55 push %rbp
00000001 48 89 e5 mov %rsp, %rbp
00000004 41 83 fa 43 cmp $0x43, %r10d
00000008 0f 84 06 00 00 00 jz 0x0000000000000014
0000000E 0f 0b ud2
00000010 55 push %rbp
00000011 48 89 e5 mov %rsp, %rbp
00000014 48 83 ec 10 sub $0x10, %rsp
00000018 49 39 66 38 cmpq %rsp, 0x38(%r14)
0000001C 0f 82 02 00 00 00 jb 0x0000000000000024
00000022 0f 0b ud2
00000024 c1 e7 02 shl $0x02, %edi
00000027 81 c7 00 04 00 00 add $0x400, %edi
0000002D 41 8b 3c 3f movl (%r15,%rdi,1), %edi
00000031 40 f6 c4 0f test $0x0F, %spl
00000035 0f 84 01 00 00 00 jz 0x000000000000003C
0000003B cc int3
0000003C e8 0f 00 00 00 call 0x0000000000000050
00000041 48 8d 65 f0 lea -0x10(%rbp), %rsp
00000045 8b c0 mov %eax, %eax
00000047 48 83 c4 10 add $0x10, %rsp
0000004B 5d pop %rbp
0000004C c3 ret
--------------------------------------------------
Kind = Function, index = 2, name = call_a_target:
00000000 55 push %rbp
00000001 48 89 e5 mov %rsp, %rbp
00000004 48 83 ec 20 sub $0x20, %rsp
00000008 49 39 66 38 cmpq %rsp, 0x38(%r14)
0000000C 0f 82 02 00 00 00 jb 0x0000000000000014
00000012 0f 0b ud2
00000014 8b c7 mov %edi, %eax
00000016 4c 89 75 f8 movq %r14, -0x08(%rbp)
0000001A bf 20 00 00 00 mov $0x20, %edi
0000001F 44 8b e0 mov %eax, %r12d
00000022 40 f6 c4 0f test $0x0F, %spl
00000026 0f 84 01 00 00 00 jz 0x000000000000002D
0000002C cc int3
0000002D 41 83 fc 03 cmp $0x03, %r12d
00000031 0f 83 7a 00 00 00 jnb 0x00000000000000B1
00000037 41 ba 23 00 00 00 mov $0x23, %r10d
0000003D 49 8b 86 18 03 00 00 movq 0x318(%r14), %rax
00000044 41 c1 e4 04 shl $0x04, %r12d
00000048 49 03 c4 add %r12, %rax
0000004B 48 8b 58 08 movq 0x08(%rax), %rbx
0000004F 4c 3b f3 cmp %rbx, %r14
00000052 0f 84 44 00 00 00 jz 0x000000000000009C
00000058 4c 89 74 24 08 movq %r14, 0x08(%rsp)
0000005D 49 89 de mov %rbx, %r14
00000060 4c 89 34 24 movq %r14, (%rsp)
00000064 4d 8b 3e movq (%r14), %r15
00000067 4d 8b 66 20 movq 0x20(%r14), %r12
0000006B 49 8b 5e 18 movq 0x18(%r14), %rbx
0000006F 49 89 9c 24 b0 00 00 00 movq %rbx, 0xB0(%r12)
00000077 48 8b 00 movq (%rax), %rax
0000007A ff d0 call %rax
0000007C 49 83 ce 00 or $0x00, %r14
00000080 4c 8b 74 24 08 movq 0x08(%rsp), %r14
00000085 4d 8b 3e movq (%r14), %r15
00000088 4d 8b 56 20 movq 0x20(%r14), %r10
0000008C 4d 8b 66 18 movq 0x18(%r14), %r12
00000090 4d 89 a2 b0 00 00 00 movq %r12, 0xB0(%r10)
00000097 e9 05 00 00 00 jmp 0x00000000000000A1
0000009C 48 8b 00 movq (%rax), %rax
0000009F ff d0 call %rax
000000A1 48 8d 65 e0 lea -0x20(%rbp), %rsp
000000A5 8b c0 mov %eax, %eax
000000A7 4c 8b 75 f8 movq -0x08(%rbp), %r14
000000AB 48 83 c4 20 add $0x20, %rsp
000000AF 5d pop %rbp
000000B0 c3 ret
000000B1 0f 0b ud2
typedef int (*func_t) (int);
static int fn_a (int i) {
return i * 2;
}
static int fn_b (int i) {
return i + 1;
}
static func_t targets[3] = {
fn_a,
fn_b,
0
};
static __attribute__((noinline)) int call_a_target (func_t func, int value) {
return func(value);
}
int main (int argc, const char *argv[]) {
func_t target = targets[argc];
return call_a_target(target, 32);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment