Skip to content

Helper Functions

Erik McClure edited this page May 26, 2019 · 4 revisions

The inNative Default Environment comes with several helper functions intended to make debugging native WebAssembly easier. All these functions are considered raw C functions with a default calling convention, and should be imported like any other C function. There are also some intrinsic functions provided by the compiler itself, which act like helper functions from WebAssembly's perspective. Some of these functions are not safe, and all of them must be whitelisted like other C functions if the whitelist is enabled.


(import "" "_innative_internal_env_print" (func $print (param i64)))

Prints out the hexadecimal representation of a single 64-bit integer to standard out, plus a newline. This is intended for investigating the values of WebAssembly stack values or local variables. It is written in such a way that most C++ compilers optimize all stack values out, allowing it to function even when the program state is corrupted.

Example:

(module  
  (import "" "_innative_internal_env_print" (func $print (param $i i64)))
  
  (func $caller
    i64.const 14
    call $print
    )
  (start $caller)
)

(import "" "_innative_internal_write_out" (func $write (param $p cref) (param $i i64)))

This writes a string of bytes from a given location $p in memory using the provided length $i to the standard output. This is essentially fwrite($p, 1, $i, stdout); and can be used as a primitive to re-implement standard output functions. The cref parameter relies on the inNative cref Extension, but can be replaced with i64 if the _innative_to_c helper function is used (see below).

Example:

(module  
  (import "" "_innative_internal_write_out" (func $write (param $p cref) (param $i i64)))
  
  (memory (;0;) 1 1)
  
  (func $caller
    i32.const 0
    i64.const 14
    call $write
    )
  (start $caller)
  (data (i32.const 0)
    "Hello, world!\n\00"
  )
)

(import "" "_innative_internal_env_memdump" (func $memdump (param $p cref) (param $i i64)))

This is a memory dump helper function, intended to inspect large blocks of memory for errors. It dumps a 2-character hexadecimal representation of each byte starting from $p and printing $i bytes. $p uses the inNative cref Extension, but can also be changed to an i64 and paired with _innative_to_c. Calling this function is identical to calling _innative_internal_write_out, only the result is different.


(import "" "_innative_to_c" (func $toc (param i64) (result i64)))

This function takes a 64-bit integer index into the default linear memory and transforms it into a valid C pointer. This is done by adding the offset to the linear memory pointer. The resulting integer will have no meaning inside WebAssembly, but when sent to a C function expecting a pointer, it will point to that part of memory in the WebAssembly module. It takes and returns an i64 value, so you should call it right after pushing the index to the stack when calling a function. Here is an example that uses the _innative_to_c function instead of cref to call _innative_internal_env_memdump:

(module  
  (import "" "_innative_internal_env_memdump" (func $dump (param $p i64) (param $i i64)))
  (import "" "_innative_to_c" (func $toc (param i64) (result i64)))

  (memory (;0;) 1 1)
  
  (func $caller
    i64.const 0
    call $toc
    i64.const 14
    call $dump
    )
  (start $caller)
  (data (i32.const 0)
    "Hello, world!\n\00"
  )
)

One advantage of using this function instead of cref is that you can declare void* _innative_to_c(void*); and then convince a C compiler to treat it as a normal function, which prevents having to write WebAssembly by hand, or having to adjust compiled WebAssembly modules by hand after the fact.


(import "" "_innative_from_c" (func $fromc (param i64) (result i64)))

This function takes a C pointer and subtracts the webassembly default linear memory location from that pointer. If this was originally a WebAssembly index, this will recover the original index and it can be used normally. However, it can also be used on C pointers pointing elsewhere. If the memory range checks are enabled, this will simply crash, but if memory range checks are disabled, it will enable your WebAssembly module to manipulate arbitrary C memory. This is obviously extremely dangerous and should only be done by trusted modules, but it is an option. Care must be taken if working with 64-bit pointers, as they are likely to be out of the range of the 32-bit integer that webassembly requires for the memory load instructions.

This function is particularly special because cref does not convert back into an integer for you, so the only way to get this functionality is by using _innative_from_c (mostly because it breaks the sandbox and allows the module to inspect arbitrary memory pointers).

Example:

(module  
  (import "" "unsafe_function" (func $unsafe (param f32) (result i64)))
  (import "" "_innative_from_c" (func $fromc (param i64) (result i64)))

  (memory (;0;) 1 1)
  
  (func $caller
    f32.const 0
    call $unsafe
    call $fromc
    i32.wrap/i64
    i64.load
    drop
    )
  (start $caller)
  (data (i32.const 0)
    "Hello, world!\n\00"
  )
)

(import "" "_innative_funcptr" (func $funcptr (param i32) (result i64)))

This function returns a raw C function pointer to the given function index. It's important to remember that WebAssembly function indexes include imported functions. The returned pointer is returned as a 64-bit integer that can be passed to a C function that expects a function pointer. The calling convention of the function is preserved. The function index is a 32-bit integer (since WebAssembly function indexes are themselves 32-bit). Because this works with imported functions, it can be used to get the function pointer to a C function.


(import "" "_innative_trap" (func $trap))

This function simply forces a trap instruction to be generated, in the same way a WebAssembly trap would generate the instruction (which is usually an illegal instruction that triggers SIGILL on x86).