blob: 1804f98d135f7a790cbe0a05785f2e4c84b4c309 [file] [log] [blame] [edit]
# Copyright 2011 The Emscripten Authors. All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License. Both these licenses can be
# found in the LICENSE file.
"""Utilties for manipulating WebAssembly binaries from python.
"""
import logging
from . import shared
logger = logging.getLogger('shared')
# For the Emscripten-specific WASM metadata section, follows semver, changes
# whenever metadata section changes structure.
# NB: major version 0 implies no compatibility
# NB: when changing the metadata format, we should only append new fields, not
# reorder, modify, or remove existing ones.
EMSCRIPTEN_METADATA_MAJOR, EMSCRIPTEN_METADATA_MINOR = (0, 3)
# For the JS/WASM ABI, specifies the minimum ABI version required of
# the WASM runtime implementation by the generated WASM binary. It follows
# semver and changes whenever C types change size/signedness or
# syscalls change signature. By semver, the maximum ABI version is
# implied to be less than (EMSCRIPTEN_ABI_MAJOR + 1, 0). On an ABI
# change, increment EMSCRIPTEN_ABI_MINOR if EMSCRIPTEN_ABI_MAJOR == 0
# or the ABI change is backwards compatible, otherwise increment
# EMSCRIPTEN_ABI_MAJOR and set EMSCRIPTEN_ABI_MINOR = 0.
EMSCRIPTEN_ABI_MAJOR, EMSCRIPTEN_ABI_MINOR = (0, 28)
def toLEB(x):
assert x >= 0, 'TODO: signed'
ret = []
while 1:
byte = x & 127
x >>= 7
more = x != 0
if more:
byte = byte | 128
ret.append(byte)
if not more:
break
return bytearray(ret)
def readLEB(buf, offset):
result = 0
shift = 0
while True:
byte = bytearray(buf[offset:offset + 1])[0]
offset += 1
result |= (byte & 0x7f) << shift
if not (byte & 0x80):
break
shift += 7
return (result, offset)
def add_emscripten_metadata(wasm_file):
WASM_PAGE_SIZE = 65536
mem_size = shared.Settings.INITIAL_MEMORY // WASM_PAGE_SIZE
global_base = shared.Settings.GLOBAL_BASE
dynamic_base = shared.Settings.LEGACY_DYNAMIC_BASE
logger.debug('creating wasm emscripten metadata section with mem size %d' % mem_size)
name = b'\x13emscripten_metadata' # section name, including prefixed size
contents = (
# metadata section version
toLEB(EMSCRIPTEN_METADATA_MAJOR) +
toLEB(EMSCRIPTEN_METADATA_MINOR) +
# NB: The structure of the following should only be changed
# if EMSCRIPTEN_METADATA_MAJOR is incremented
# Minimum ABI version
toLEB(EMSCRIPTEN_ABI_MAJOR) +
toLEB(EMSCRIPTEN_ABI_MINOR) +
# Wasm backend, always 1 now
toLEB(1) +
toLEB(mem_size) +
toLEB(0) +
toLEB(global_base) +
toLEB(dynamic_base) +
# dynamictopPtr, always 0 now
toLEB(0) +
# tempDoublePtr, always 0 in wasm backend
toLEB(0) +
toLEB(int(shared.Settings.STANDALONE_WASM))
# NB: more data can be appended here as long as you increase
# the EMSCRIPTEN_METADATA_MINOR
)
orig = open(wasm_file, 'rb').read()
with open(wasm_file, 'wb') as f:
f.write(orig[0:8]) # copy magic number and version
# write the special section
f.write(b'\0') # user section is code 0
# need to find the size of this section
size = len(name) + len(contents)
f.write(toLEB(size))
f.write(name)
f.write(contents)
f.write(orig[8:])
def add_dylink_section(wasm_file, needed_dynlibs):
# A wasm shared library has a special "dylink" section, see tools-conventions repo.
# This function adds this section to the beginning on the given file.
wasm = open(wasm_file, 'rb').read()
section_name = b'\06dylink' # section name, including prefixed size
file_header = wasm[:8]
# Read the existing section data
offset = 8
assert wasm[offset] == 0
offset += 1
size, offset = readLEB(wasm, offset)
section = wasm[offset:offset + size]
# section name
assert section.startswith(section_name)
offset = len(section_name)
mem_size, offset = readLEB(section, offset)
mem_align, offset = readLEB(section, offset)
table_size, offset = readLEB(section, offset)
table_align, offset = readLEB(section, offset)
logger.debug('creating wasm dynamic library with mem size %d, table size %d, align %d' % (mem_size, table_size, mem_align))
contents = (toLEB(mem_size) + toLEB(mem_align) +
toLEB(table_size) + toLEB(0))
# we extend "dylink" section with information about which shared libraries
# our shared library needs. This is similar to DT_NEEDED entries in ELF.
#
# In theory we could avoid doing this, since every import in wasm has
# "module" and "name" attributes, but currently emscripten almost always
# uses just "env" for "module". This way we have to embed information about
# required libraries for the dynamic linker somewhere, and "dylink" section
# seems to be the most relevant place.
#
# Binary format of the extension:
#
# needed_dynlibs_count varuint32 ; number of needed shared libraries
# needed_dynlibs_entries dynlib_entry* ; repeated dynamic library entries as described below
#
# dynlib_entry:
#
# dynlib_name_len varuint32 ; length of dynlib_name_str in bytes
# dynlib_name_str bytes ; name of a needed dynamic library: valid UTF-8 byte sequence
#
# a proposal has been filed to include the extension into "dylink" specification:
# https://github.com/WebAssembly/tool-conventions/pull/77
contents += toLEB(len(needed_dynlibs))
for dyn_needed in needed_dynlibs:
dyn_needed = bytes(shared.asbytes(dyn_needed))
contents += toLEB(len(dyn_needed))
contents += dyn_needed
section_size = len(section_name) + len(contents)
with open(wasm_file, 'wb') as f:
# copy magic number and version
f.write(file_header)
# write the special section
f.write(b'\0') # user section is code 0
f.write(toLEB(section_size))
f.write(section_name)
f.write(contents)
# copy rest of binary
f.write(wasm[8:])