v86/tools/wasm-patch-indirect-function-table.js
2020-12-31 19:14:30 -06:00

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();