github.com/stealthrocket/wzprof@v0.2.1-0.20230830205924-5fa86be5e5b3/wasmbin.go (about)

     1  package wzprof
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  )
     7  
     8  // Returns true if the wasm module binary b contains a custom section with this
     9  // name.
    10  func wasmHasCustomSection(b []byte, name string) bool {
    11  	return wasmCustomSection(b, name) != nil
    12  }
    13  
    14  // Returns the byte content of a custom section with name, or nil.
    15  func wasmCustomSection(b []byte, name string) []byte {
    16  	const customSectionId = 0
    17  	if len(b) < 8 {
    18  		return nil
    19  	}
    20  	b = b[8:] // skip magic+version
    21  	for len(b) > 2 {
    22  		id := b[0]
    23  		b = b[1:]
    24  		length, n := binary.Uvarint(b)
    25  		b = b[n:]
    26  
    27  		if id == customSectionId {
    28  			nameLen, n := binary.Uvarint(b)
    29  			b = b[n:]
    30  			m := string(b[:nameLen])
    31  			if m == name {
    32  				return b[nameLen : length-uint64(n)]
    33  			}
    34  			b = b[length-uint64(n):]
    35  		} else {
    36  			b = b[length:]
    37  		}
    38  	}
    39  	return nil
    40  }
    41  
    42  // The functions in this file inspect the contents of a well-formed wasm-binary.
    43  // They are very weak parsers: they should be called on a valid module, or may
    44  // panic. Eventually this code should be replaced by exposing the right APIs
    45  // from wazero to access data and segments.
    46  
    47  // wasmdataSection parses a WASM binary and returns the bytes of the WASM "Data"
    48  // section. Returns nil if the sections do not exist.
    49  func wasmdataSection(b []byte) []byte {
    50  	const dataSectionId = 11
    51  
    52  	b = b[8:] // skip magic+version
    53  	for len(b) > 2 {
    54  		id := b[0]
    55  		b = b[1:]
    56  		length, n := binary.Uvarint(b)
    57  		b = b[n:]
    58  
    59  		if id == dataSectionId {
    60  			return b[:length]
    61  		}
    62  		b = b[length:]
    63  	}
    64  	return nil
    65  }
    66  
    67  // dataIterator iterates over the segments contained in a wasm Data section.
    68  // Only support mode 0 (memory 0 + offset) segments.
    69  type dataIterator struct {
    70  	b []byte // remaining bytes in the Data section
    71  	n uint64 // number of segments
    72  
    73  	offset int // offset of b in the Data section.
    74  }
    75  
    76  // newDataIterator prepares an iterator using the bytes of a well-formed data
    77  // section.
    78  func newDataIterator(b []byte) dataIterator {
    79  	segments, r := binary.Uvarint(b)
    80  	return dataIterator{
    81  		b:      b[r:],
    82  		n:      segments,
    83  		offset: r,
    84  	}
    85  }
    86  
    87  func (d *dataIterator) read(n int) (b []byte) {
    88  	b, d.b = d.b[:n], d.b[n:]
    89  	d.offset += n
    90  	return b
    91  }
    92  
    93  func (d *dataIterator) skip(n int) {
    94  	d.b = d.b[n:]
    95  	d.offset += n
    96  }
    97  
    98  func (d *dataIterator) byte() byte {
    99  	b := d.b[0]
   100  	d.skip(1)
   101  	return b
   102  }
   103  
   104  func (d *dataIterator) varint() int64 {
   105  	x, n := sleb128(64, d.b)
   106  	d.skip(n)
   107  	return x
   108  }
   109  
   110  func sleb128(size int, b []byte) (result int64, read int) {
   111  	// The difference between sleb128 and protobuf's binary.Varint is that
   112  	// the latter puts the sign at the least significant bit.
   113  	shift := 0
   114  
   115  	var byte byte
   116  	for {
   117  		byte = b[0]
   118  		read++
   119  		b = b[1:]
   120  
   121  		result |= (int64(0b01111111&byte) << shift)
   122  		shift += 7
   123  		if 0b10000000&byte == 0 {
   124  			break
   125  		}
   126  	}
   127  	if (shift < size) && (0x40&byte > 0) {
   128  		result |= (^0 << shift)
   129  	}
   130  	return result, read
   131  }
   132  
   133  func (d *dataIterator) uvarint() uint64 {
   134  	x, n := binary.Uvarint(d.b)
   135  	d.skip(n)
   136  	return x
   137  }
   138  
   139  // Next returns the bytes of the following segment, and its address in virtual
   140  // memory, or a nil slice if there are no more segment.
   141  func (d *dataIterator) Next() (vaddr int64, seg []byte) {
   142  	if d.n == 0 {
   143  		return 0, nil
   144  	}
   145  
   146  	// Format of mode 0 segment:
   147  	//
   148  	// varuint32 - mode (1 byte, 0)
   149  	// byte      - i32.const (0x41)
   150  	// varint64  - virtual address
   151  	// byte      - end of expression (0x0B)
   152  	// varuint64 - length
   153  	// bytes     - raw bytes of the segment
   154  
   155  	mode := d.uvarint()
   156  	if mode != 0x0 {
   157  		panic(fmt.Errorf("unsupported mode %#x", mode))
   158  	}
   159  
   160  	v := d.byte()
   161  	if v != 0x41 {
   162  		panic(fmt.Errorf("expected constant i32.const (0x41); got %#x", v))
   163  	}
   164  
   165  	vaddr = d.varint()
   166  
   167  	v = d.byte()
   168  	if v != 0x0B {
   169  		panic(fmt.Errorf("expected end of expr (0x0B); got %#x", v))
   170  	}
   171  
   172  	length := d.uvarint()
   173  	seg = d.read(int(length))
   174  	d.n--
   175  
   176  	return vaddr, seg
   177  }
   178  
   179  // SkipToDataOffset iterates over segments to return the bytes at a given data
   180  // offset, until the end of the segment that contains the offset, and the
   181  // virtual address of the byte at that offset.
   182  //
   183  // Panics if offset was already passed or the offset is out of bounds.
   184  func (d *dataIterator) SkipToDataOffset(offset int) (int64, []byte) {
   185  	if offset < d.offset {
   186  		panic(fmt.Errorf("offset %d requested by already at %d", offset, d.offset))
   187  	}
   188  	end := d.offset + len(d.b)
   189  	if offset >= d.offset+len(d.b) {
   190  		panic(fmt.Errorf("offset %d requested past data section %d", offset, end))
   191  	}
   192  
   193  	for d.offset <= offset {
   194  		vaddr, seg := d.Next()
   195  		if d.offset < offset {
   196  			continue
   197  		}
   198  		o := len(seg) + offset - d.offset
   199  		return vaddr + int64(o), seg[o:]
   200  	}
   201  
   202  	return 0, nil
   203  }
   204  
   205  // vmemb is a helper to rebuild virtual memory from data segments.
   206  type vmemb struct {
   207  	// Virtual address of the first byte of memory.
   208  	Start int64
   209  	// Reconstructed memory buffer.
   210  	b []byte
   211  }
   212  
   213  func (m *vmemb) Has(addr int) bool {
   214  	return addr < len(m.b)
   215  }
   216  
   217  func (m *vmemb) CopyAtAddress(addr int64, b []byte) {
   218  	end := int64(len(m.b)) + m.Start
   219  	if addr < end {
   220  		panic(fmt.Errorf("address %d already mapped (end=%d)", addr, end))
   221  	}
   222  	size := len(m.b)
   223  	zeroes := int(addr - end)
   224  	total := zeroes + len(b) + size
   225  	if cap(m.b) < total {
   226  		new := make([]byte, total)
   227  		copy(new, m.b)
   228  		m.b = new
   229  	} else {
   230  		m.b = m.b[:total]
   231  	}
   232  	copy(m.b[size+zeroes:], b)
   233  
   234  	if m.Start+int64(len(m.b)) != addr+int64(len(b)) {
   235  		panic("invalid copy")
   236  	}
   237  }