186 lines
5.1 KiB
JavaScript
Executable file
186 lines
5.1 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
// Read a wasm module in binary format from stdin, find table import entries, i.e.:
|
|
//
|
|
// (import "env" "table" (table (;0;) <initial> <maximum> anyfunc))
|
|
//
|
|
// Remove the <maximum> and write the patched wasm module to stdout.
|
|
|
|
process.on("unhandledRejection", exn => { throw exn; });
|
|
|
|
const assert = require("assert").strict;
|
|
const fs = require("fs");
|
|
|
|
const SECTION_IMPORT = 2;
|
|
|
|
const IMPORT_KIND_FUNCTION = 0;
|
|
const IMPORT_KIND_TABLE = 1;
|
|
const IMPORT_KIND_MEMORY = 2;
|
|
const IMPORT_KIND_GLOBAL = 3;
|
|
|
|
function main()
|
|
{
|
|
const wasm = fs.readFileSync("/dev/stdin");
|
|
const view = new DataView(wasm.buffer);
|
|
var ptr = 0;
|
|
|
|
// magic
|
|
assert(view.getUint32(ptr, true) === 0x6d736100);
|
|
ptr += 4;
|
|
|
|
// version
|
|
assert(view.getUint32(ptr, true) === 1);
|
|
ptr += 4;
|
|
|
|
while(ptr < view.byteLength)
|
|
{
|
|
const section_id = view.getUint8(ptr);
|
|
ptr++;
|
|
var { ptr, value: size } = read_leb_u32(ptr, view);
|
|
const section_end = ptr + size;
|
|
|
|
if(section_id === SECTION_IMPORT)
|
|
{
|
|
patch_import_section(ptr, view);
|
|
}
|
|
|
|
ptr = section_end;
|
|
}
|
|
|
|
// sanity check
|
|
const module = new WebAssembly.Module(view.buffer);
|
|
|
|
process.stdout.write(wasm);
|
|
}
|
|
|
|
function patch_import_section(ptr, view)
|
|
{
|
|
var { ptr, value: section_entry_count } = read_leb_u32(ptr, view);
|
|
|
|
for(let i = 0; i < section_entry_count; i++)
|
|
{
|
|
var { ptr, value: module_str_length } = read_leb_u32(ptr, view);
|
|
ptr += module_str_length;
|
|
var { ptr, value: field_str_length } = read_leb_u32(ptr, view);
|
|
ptr += field_str_length;
|
|
|
|
const kind = view.getUint8(ptr);
|
|
ptr++;
|
|
|
|
if(kind === IMPORT_KIND_FUNCTION)
|
|
{
|
|
var { ptr, value: function_signature_index } = read_leb_u32(ptr, view);
|
|
}
|
|
else if(kind === IMPORT_KIND_TABLE)
|
|
{
|
|
const table_offset = ptr;
|
|
var { ptr, value: table_element_type } = read_leb_u32(ptr, view);
|
|
assert(table_element_type === 0x70);
|
|
|
|
const maximum_present = new Uint8Array(view.buffer, ptr, 1);
|
|
assert(maximum_present[0] === 0 || maximum_present[0] === 1);
|
|
ptr++;
|
|
|
|
var { ptr, value: initial_table_size, leb_view: initial_table_size_view } = read_leb_u32(ptr, view);
|
|
|
|
if(maximum_present[0])
|
|
{
|
|
var { ptr, value: maximum_table_size, leb_view: maximum_table_size_view } = read_leb_u32(ptr, view);
|
|
}
|
|
else
|
|
{
|
|
maximum_table_size = -1;
|
|
}
|
|
|
|
console.error(`Found table import at offset` +
|
|
` ${table_offset}` +
|
|
` maximum_present=${maximum_present[0]}` +
|
|
` initial=${initial_table_size}` +
|
|
` maximum=${maximum_table_size}`);
|
|
|
|
if(maximum_present[0])
|
|
{
|
|
patch_maximum_limit(maximum_present, initial_table_size_view, maximum_table_size_view);
|
|
console.error("Patched!");
|
|
}
|
|
else
|
|
{
|
|
console.error("No maximum present, skipped");
|
|
}
|
|
}
|
|
else if(kind === IMPORT_KIND_MEMORY)
|
|
{
|
|
const maximum_present = view.getUint8(ptr);
|
|
assert(maximum_present === 0 || maximum_present === 1);
|
|
ptr++;
|
|
|
|
var { ptr, value: initial_memory_size } = read_leb_u32(ptr, view);
|
|
|
|
if(maximum_present)
|
|
{
|
|
var { ptr, value: maximum_memory_size } = read_leb_u32(ptr, view);
|
|
}
|
|
}
|
|
else if(kind === IMPORT_KIND_GLOBAL)
|
|
{
|
|
const content_type = view.getUint8(ptr);
|
|
ptr++;
|
|
const mutability = view.getUint8(ptr);
|
|
assert(mutability === 0 || mutability === 1);
|
|
ptr++;
|
|
}
|
|
else
|
|
{
|
|
assert(false, `Unexpected import kind: 0x${kind.toString(16)} at offset ${ptr - 1}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function patch_maximum_limit(maximum_present, initial_size, maximum_size)
|
|
{
|
|
// clear the maximum present bit
|
|
maximum_present[0] = 0;
|
|
|
|
// set the highest bit of the initial size, in order to use it to pad the existing maximum size bytes
|
|
const last_byte_initial_size = initial_size[initial_size.length - 1];
|
|
assert((last_byte_initial_size & 0x80) === 0);
|
|
initial_size[initial_size.length - 1] = last_byte_initial_size | 0x80;
|
|
|
|
for(let i = 0; i < maximum_size.length - 1; i++)
|
|
{
|
|
// pad maximum value with 0x80 bytes
|
|
maximum_size[i] = 0x80;
|
|
}
|
|
|
|
// pad the last byte of the maximum value with 0x00
|
|
maximum_size[maximum_size.length - 1] = 0x00;
|
|
}
|
|
|
|
function read_leb_u32(ptr, view)
|
|
{
|
|
let value = 0;
|
|
let byte_length = 0;
|
|
|
|
while(true)
|
|
{
|
|
let byte = view.getUint8(ptr++);
|
|
|
|
value |= (byte & 0x7f) << (byte_length * 7);
|
|
byte_length++;
|
|
|
|
if((byte & 0x80) === 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(byte_length <= 4);
|
|
|
|
const leb_view = new Uint8Array(view.buffer, ptr - byte_length, byte_length);
|
|
|
|
return { ptr, value, leb_view };
|
|
}
|
|
|
|
main();
|