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 }