doc/wasm.md
radare2 can be compiled to WebAssembly (WASM) using the WASI (WebAssembly System Interface) standard, allowing it to run in web browsers or other WASM-compatible environments. This enables radare2 to be used in client-side applications without server-side execution.
To build radare2 for WASI, you need the WASI SDK installed. The build process is automated in sys/wasi.sh.
WASI_SDK environment variable to the installation pathRun the WASI build script:
./sys/wasi.sh
This script configures radare2 with the following options:
./configure \
--with-static-themes \
--without-gperf \
--with-compiler=wasi \
--disable-debugger \
--without-fork \
--with-ostype=wasi \
--with-checks-level=0 \
--disable-threads \
--without-dylink \
--with-libr \
--without-gpl
Then compiles and packages the WASM binaries for tools like radare2, rabin2, etc., into a radare2-<version>-wasi.zip file.
To use the generated .wasm files in a web application, instantiate them using the WebAssembly API.
const response = await fetch('radare2.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer);
// Define imports
const imports = {
r2: {
is_tty: () => 1, // Assume TTY available
set_raw_mode: (raw) => { /* set terminal mode */ },
key_next: () => { /* return next key code */ },
http_get: (url, headers_str, code_ptr, rlen_ptr) => {
// Implement HTTP GET using fetch
// Allocate memory for response, set code and rlen
return response_ptr;
},
http_post: (url, headers_str, data, code_ptr, rlen_ptr) => {
// Implement HTTP POST using fetch
return response_ptr;
}
}
};
const instance = await WebAssembly.instantiate(module, imports);
The WASM module requires the following imports in the r2 module:
is_tty(): Returns 1 if running in a TTY environment, 0 otherwiseset_raw_mode(raw): Set terminal to raw mode (raw=1) or cooked mode (raw=0)key_next(): Return the next key code from inputhttp_get(url, headers_str, code_ptr, rlen_ptr): Perform HTTP GET requesthttp_post(url, headers_str, data, code_ptr, rlen_ptr): Perform HTTP POST requestFor HTTP functions:
url: Null-terminated stringheaders_str: Headers as "Header: value\nHeader2: value\n"data: POST data (for POST only)code_ptr: Pointer to int for HTTP status coderlen_ptr: Pointer to int for response lengthxterm.js is a terminal emulator for the web. To integrate radare2 WASM with xterm.js:
<div id="terminal"></div>
<script src="https://cdn.jsdelivr.net/npm/xterm@5/lib/xterm.min.js"></script>
const term = new Terminal();
term.open(document.getElementById('terminal'));
// WASM memory buffer
let wasmMemory;
// Instantiate WASM with imports
const imports = {
r2: {
is_tty: () => 1,
set_raw_mode: (raw) => {
// Configure xterm.js for raw/cooked mode
term.setOption('cursorBlink', !raw);
},
key_next: () => {
// This is tricky - xterm.js input is event-driven
// You might need to buffer input or use a promise-based approach
return term.buffer.active.getLine(term.buffer.active.cursorY).translateToString(
term.buffer.active.cursorX
).charCodeAt(0) || 0;
},
http_get: async (urlPtr, headersPtr, codePtr, rlenPtr) => {
const url = getStringFromWasm(urlPtr);
const headers = getStringFromWasm(headersPtr);
try {
const response = await fetch(url, {
headers: parseHeaders(headers)
});
const data = await response.text();
const dataPtr = allocateStringInWasm(data);
setInt32(codePtr, response.status);
setInt32(rlenPtr, data.length);
return dataPtr;
} catch (error) {
setInt32(codePtr, 500);
setInt32(rlenPtr, 0);
return 0;
}
},
http_post: async (urlPtr, headersPtr, dataPtr, codePtr, rlenPtr) => {
const url = getStringFromWasm(urlPtr);
const headers = getStringFromWasm(headersPtr);
const data = getStringFromWasm(dataPtr);
try {
const response = await fetch(url, {
method: 'POST',
headers: parseHeaders(headers),
body: data
});
const responseData = await response.text();
const responsePtr = allocateStringInWasm(responseData);
setInt32(codePtr, response.status);
setInt32(rlenPtr, responseData.length);
return responsePtr;
} catch (error) {
setInt32(codePtr, 500);
setInt32(rlenPtr, 0);
return 0;
}
}
}
};
WebAssembly.instantiateStreaming(fetch('radare2.wasm'), imports)
.then(({ instance }) => {
wasmMemory = instance.exports.memory;
// Connect WASM stdout to xterm.js
// This requires hooking the WASM write functions or using WASI stdout
// For input, you may need to handle xterm.js 'data' events
term.onData(data => {
// Send data to WASM input buffer
});
});
function getStringFromWasm(ptr) {
const view = new Uint8Array(wasmMemory.buffer);
let str = '';
let i = ptr;
while (view[i]) {
str += String.fromCharCode(view[i]);
i++;
}
return str;
}
function allocateStringInWasm(str) {
const bytes = new TextEncoder().encode(str + '\0');
const ptr = instance.exports.malloc(bytes.length);
const view = new Uint8Array(wasmMemory.buffer);
view.set(bytes, ptr);
return ptr;
}
function setInt32(ptr, value) {
const view = new DataView(wasmMemory.buffer);
view.setInt32(ptr, value, true);
}
function parseHeaders(headersStr) {
const headers = {};
headersStr.split('\n').forEach(line => {
const [key, ...valueParts] = line.split(':');
if (key && valueParts.length) {
headers[key.trim()] = valueParts.join(':').trim();
}
});
return headers;
}
See the radare2 web interface at shlr/www/ for examples of WASM integration.