Coverage Report

Created: 2023-07-31 08:09

/src/bloaty/src/webassembly.cc
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2018 Google Inc. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include "bloaty.h"
16
#include "util.h"
17
18
#include "absl/strings/substitute.h"
19
20
using absl::string_view;
21
22
namespace bloaty {
23
namespace wasm {
24
25
10.2M
uint64_t ReadLEB128Internal(bool is_signed, size_t size, string_view* data) {
26
10.2M
  uint64_t ret = 0;
27
10.2M
  int shift = 0;
28
10.2M
  int maxshift = 70;
29
10.2M
  const char* ptr = data->data();
30
10.2M
  const char* limit = ptr + data->size();
31
32
10.3M
  while (ptr < limit && shift < maxshift) {
33
10.3M
    char byte = *(ptr++);
34
10.3M
    ret |= static_cast<uint64_t>(byte & 0x7f) << shift;
35
10.3M
    shift += 7;
36
10.3M
    if ((byte & 0x80) == 0) {
37
10.2M
      data->remove_prefix(ptr - data->data());
38
10.2M
      if (is_signed && shift < size && (byte & 0x40)) {
39
0
        ret |= -(1ULL << shift);
40
0
      }
41
10.2M
      return ret;
42
10.2M
    }
43
10.3M
  }
44
45
10.2M
  THROW("corrupt wasm data, unterminated LEB128");
46
10.2M
}
47
48
2.99k
bool ReadVarUInt1(string_view* data) {
49
2.99k
  return static_cast<bool>(ReadLEB128Internal(false, 1, data));
50
2.99k
}
51
52
5.01M
uint8_t ReadVarUInt7(string_view* data) {
53
5.01M
  return static_cast<char>(ReadLEB128Internal(false, 7, data));
54
5.01M
}
55
56
5.27M
uint32_t ReadVarUInt32(string_view* data) {
57
5.27M
  return static_cast<uint32_t>(ReadLEB128Internal(false, 32, data));
58
5.27M
}
59
60
2.13k
int8_t ReadVarint7(string_view* data) {
61
2.13k
  return static_cast<int8_t>(ReadLEB128Internal(true, 7, data));
62
2.13k
}
63
64
10.2M
string_view ReadPiece(size_t bytes, string_view* data) {
65
10.2M
  if(data->size() < bytes) {
66
2.98k
    THROW("premature EOF reading variable-length DWARF data");
67
2.98k
  }
68
10.2M
  string_view ret = data->substr(0, bytes);
69
10.2M
  data->remove_prefix(bytes);
70
10.2M
  return ret;
71
10.2M
}
72
73
18.2M
bool ReadMagic(string_view* data) {
74
18.2M
  const uint32_t wasm_magic = 0x6d736100;
75
18.2M
  auto magic = ReadFixed<uint32_t>(data);
76
77
18.2M
  if (magic != wasm_magic) {
78
18.0M
    return false;
79
18.0M
  }
80
81
  // TODO(haberman): do we need to fail if this is >1?
82
152k
  auto version = ReadFixed<uint32_t>(data);
83
152k
  (void)version;
84
85
152k
  return true;
86
18.2M
}
87
88
class Section {
89
 public:
90
  uint32_t id;
91
  std::string name;
92
  string_view data;
93
  string_view contents;
94
95
5.01M
  static Section Read(string_view* data_param) {
96
5.01M
    Section ret;
97
5.01M
    string_view data = *data_param;
98
5.01M
    string_view section_data = data;
99
100
5.01M
    ret.id = ReadVarUInt7(&data);
101
5.01M
    uint32_t size = ReadVarUInt32(&data);
102
5.01M
    ret.contents = ReadPiece(size, &data);
103
5.01M
    size_t header_size = ret.contents.data() - section_data.data();
104
5.01M
    ret.data = ReadPiece(size + header_size, &section_data);
105
106
5.01M
    if (ret.id == 0) {
107
229k
      uint32_t name_len = ReadVarUInt32(&ret.contents);
108
229k
      ret.name = std::string(ReadPiece(name_len, &ret.contents));
109
4.78M
    } else if (ret.id <= 13) {
110
4.78M
      ret.name = names[ret.id];
111
4.78M
    } else {
112
3.18k
      THROWF("Unknown section id: $0", ret.id);
113
3.18k
    }
114
115
5.01M
    *data_param = data;
116
5.01M
    return ret;
117
5.01M
  }
118
119
  enum Name {
120
    kType      = 1,
121
    kImport    = 2,
122
    kFunction  = 3,
123
    kTable     = 4,
124
    kMemory    = 5,
125
    kGlobal    = 6,
126
    kExport    = 7,
127
    kStart     = 8,
128
    kElement   = 9,
129
    kCode      = 10,
130
    kData      = 11,
131
    kDataCount = 12,
132
    kEvent     = 13,
133
  };
134
135
  static const char* names[];
136
};
137
138
const char* Section::names[] = {
139
  "<none>",    // 0
140
  "Type",      // 1
141
  "Import",    // 2
142
  "Function",  // 3
143
  "Table",     // 4
144
  "Memory",    // 5
145
  "Global",    // 6
146
  "Export",    // 7
147
  "Start",     // 8
148
  "Element",   // 9
149
  "Code",      // 10
150
  "Data",      // 11
151
  "DataCount", // 12
152
  "Event",     // 13
153
};
154
155
struct ExternalKind {
156
  enum Kind {
157
    kFunction = 0,
158
    kTable = 1,
159
    kMemory = 2,
160
    kGlobal = 3,
161
  };
162
};
163
164
template <class Func>
165
88.6k
void ForEachSection(string_view file, Func&& section_func) {
166
88.6k
  string_view data = file;
167
88.6k
  ReadMagic(&data);
168
169
5.10M
  while (!data.empty()) {
170
5.01M
    Section section = Section::Read(&data);
171
5.01M
    section_func(section);
172
5.01M
  }
173
88.6k
}
webassembly.cc:void bloaty::wasm::ForEachSection<bloaty::wasm::ParseSections(bloaty::RangeSink*)::$_0>(std::__1::basic_string_view<char, std::__1::char_traits<char> >, bloaty::wasm::ParseSections(bloaty::RangeSink*)::$_0&&)
Line
Count
Source
165
41.3k
void ForEachSection(string_view file, Func&& section_func) {
166
41.3k
  string_view data = file;
167
41.3k
  ReadMagic(&data);
168
169
2.36M
  while (!data.empty()) {
170
2.31M
    Section section = Section::Read(&data);
171
2.31M
    section_func(section);
172
2.31M
  }
173
41.3k
}
webassembly.cc:void bloaty::wasm::ForEachSection<bloaty::wasm::ParseSymbols(bloaty::RangeSink*)::$_1>(std::__1::basic_string_view<char, std::__1::char_traits<char> >, bloaty::wasm::ParseSymbols(bloaty::RangeSink*)::$_1&&)
Line
Count
Source
165
4.62k
void ForEachSection(string_view file, Func&& section_func) {
166
4.62k
  string_view data = file;
167
4.62k
  ReadMagic(&data);
168
169
280k
  while (!data.empty()) {
170
275k
    Section section = Section::Read(&data);
171
275k
    section_func(section);
172
275k
  }
173
4.62k
}
webassembly.cc:void bloaty::wasm::ForEachSection<bloaty::wasm::ParseSymbols(bloaty::RangeSink*)::$_2>(std::__1::basic_string_view<char, std::__1::char_traits<char> >, bloaty::wasm::ParseSymbols(bloaty::RangeSink*)::$_2&&)
Line
Count
Source
165
4.62k
void ForEachSection(string_view file, Func&& section_func) {
166
4.62k
  string_view data = file;
167
4.62k
  ReadMagic(&data);
168
169
130k
  while (!data.empty()) {
170
125k
    Section section = Section::Read(&data);
171
125k
    section_func(section);
172
125k
  }
173
4.62k
}
webassembly.cc:void bloaty::wasm::ForEachSection<bloaty::wasm::AddWebAssemblyFallback(bloaty::RangeSink*)::$_3>(std::__1::basic_string_view<char, std::__1::char_traits<char> >, bloaty::wasm::AddWebAssemblyFallback(bloaty::RangeSink*)::$_3&&)
Line
Count
Source
165
38.0k
void ForEachSection(string_view file, Func&& section_func) {
166
38.0k
  string_view data = file;
167
38.0k
  ReadMagic(&data);
168
169
2.33M
  while (!data.empty()) {
170
2.29M
    Section section = Section::Read(&data);
171
2.29M
    section_func(section);
172
2.29M
  }
173
38.0k
}
174
175
41.3k
void ParseSections(RangeSink* sink) {
176
2.31M
  ForEachSection(sink->input_file().data(), [sink](const Section& section) {
177
2.31M
    sink->AddFileRange("wasm_sections", section.name, section.data);
178
2.31M
  });
179
41.3k
}
180
181
typedef std::unordered_map<int, std::string> IndexedNames;
182
183
void ReadNames(const Section& section, IndexedNames* func_names,
184
0
               IndexedNames* dataseg_names, RangeSink* sink) {
185
0
  enum class NameType {
186
0
    kModule = 0,
187
0
    kFunction = 1,
188
0
    kLocal = 2,
189
0
    kLabel = 3,
190
0
    kType = 4,
191
0
    kTable = 5,
192
0
    kMemory = 6,
193
0
    kGlobal = 7,
194
0
    kElemSegment = 8,
195
0
    kDataSegment = 9
196
0
  };
197
198
0
  string_view data = section.contents;
199
200
0
  while (!data.empty()) {
201
0
    NameType type = static_cast<NameType>(ReadVarUInt7(&data));
202
0
    uint32_t size = ReadVarUInt32(&data);
203
0
    string_view section = ReadPiece(size, &data);
204
205
0
    if (type == NameType::kFunction || type == NameType::kDataSegment) {
206
0
      uint32_t count = ReadVarUInt32(&section);
207
0
      for (uint32_t i = 0; i < count; i++) {
208
0
        string_view entry = section;
209
0
        uint32_t index = ReadVarUInt32(&section);
210
0
        uint32_t name_len = ReadVarUInt32(&section);
211
0
        string_view name = ReadPiece(name_len, &section);
212
0
        entry = StrictSubstr(entry, 0, name.data() - entry.data() + name.size());
213
0
        sink->AddFileRange("wasm_funcname", name, entry);
214
0
        IndexedNames *names = (type == NameType::kFunction ? func_names : dataseg_names);
215
0
        (*names)[index] = std::string(name);
216
0
      }
217
0
    }
218
0
  }
219
0
}
220
221
693
int ReadValueType(string_view* data) {
222
693
  return ReadVarint7(data);
223
693
}
224
225
1.44k
int ReadElemType(string_view* data) {
226
1.44k
  return ReadVarint7(data);
227
1.44k
}
228
229
2.35k
void ReadResizableLimits(string_view* data) {
230
2.35k
  auto flags = ReadVarUInt1(data);
231
2.35k
  ReadVarUInt32(data);
232
2.35k
  if (flags) {
233
1.87k
    ReadVarUInt32(data);
234
1.87k
  }
235
2.35k
}
236
237
693
void ReadGlobalType(string_view* data) {
238
693
  ReadValueType(data);
239
693
  ReadVarUInt1(data);
240
693
}
241
242
1.44k
void ReadTableType(string_view* data) {
243
1.44k
  ReadElemType(data);
244
1.44k
  ReadResizableLimits(data);
245
1.44k
}
246
247
972
void ReadMemoryType(string_view* data) {
248
972
  ReadResizableLimits(data);
249
972
}
250
251
3.86k
uint32_t GetNumFunctionImports(const Section& section) {
252
3.86k
  assert(section.id == Section::kImport);
253
0
  string_view data = section.contents;
254
255
3.86k
  uint32_t count = ReadVarUInt32(&data);
256
3.86k
  uint32_t func_count = 0;
257
258
7.98k
  for (uint32_t i = 0; i < count; i++) {
259
6.15k
    uint32_t module_len = ReadVarUInt32(&data);
260
6.15k
    ReadPiece(module_len, &data);
261
6.15k
    uint32_t field_len = ReadVarUInt32(&data);
262
6.15k
    ReadPiece(field_len, &data);
263
6.15k
    auto kind = ReadFixed<uint8_t>(&data);
264
265
6.15k
    switch (kind) {
266
1.52k
      case ExternalKind::kFunction:
267
1.52k
        func_count++;
268
1.52k
        ReadVarUInt32(&data);
269
1.52k
        break;
270
1.44k
      case ExternalKind::kTable:
271
1.44k
        ReadTableType(&data);
272
1.44k
        break;
273
972
      case ExternalKind::kMemory:
274
972
        ReadMemoryType(&data);
275
972
        break;
276
693
      case ExternalKind::kGlobal:
277
693
        ReadGlobalType(&data);
278
693
        break;
279
139
      default:
280
139
        THROWF("Unrecognized import kind: $0", kind);
281
6.15k
    }
282
6.15k
  }
283
284
1.83k
  return func_count;
285
3.86k
}
286
287
void ReadCodeSection(const Section& section, const IndexedNames& names,
288
1.77k
                     uint32_t num_imports, RangeSink* sink) {
289
1.77k
  string_view data = section.contents;
290
291
1.77k
  uint32_t count = ReadVarUInt32(&data);
292
293
5.22k
  for (uint32_t i = 0; i < count; i++) {
294
3.45k
    string_view func = data;
295
3.45k
    uint32_t size = ReadVarUInt32(&data);
296
3.45k
    uint32_t total_size = size + (data.data() - func.data());
297
298
3.45k
    func = StrictSubstr(func, 0, total_size);
299
3.45k
    data = StrictSubstr(data, size);
300
301
3.45k
    auto iter = names.find(num_imports + i);
302
303
3.45k
    if (iter == names.end()) {
304
2.97k
      std::string name = "func[" + std::to_string(i) + "]";
305
2.97k
      sink->AddFileRange("wasm_function", name, func);
306
2.97k
    } else {
307
473
      sink->AddFileRange("wasm_function", ItaniumDemangle(iter->second, sink->data_source()), func);
308
473
    }
309
3.45k
  }
310
1.77k
}
311
312
void ReadDataSection(const Section& section, const IndexedNames& names,
313
2.26k
                     RangeSink* sink) {
314
2.26k
  string_view data = section.contents;
315
2.26k
  uint32_t count = ReadVarUInt32(&data);
316
317
5.23k
  for (uint32_t i = 0; i < count; i++) {
318
3.22k
    string_view segment = data;
319
3.22k
    uint8_t mode = ReadFixed<uint8_t>(&data);
320
3.22k
    if (mode > 1) THROW("multi-memory extension isn't supported");
321
2.96k
    if (mode == 0) { // Active segment
322
      // We will need to read the init expr.
323
      // For the extended const proposal, read instructions until end is reached
324
      // Otherwise, just read a constexpr inst (t.const or global.get)
325
      // For now, we just need to support passive segments.
326
1.90k
      continue;
327
1.90k
    }
328
    // else, a passive segment
329
330
1.06k
    uint32_t segment_size = ReadVarUInt32(&data);
331
1.06k
    uint32_t total_size = segment_size + (data.data() - segment.data());
332
333
1.06k
    segment = StrictSubstr(segment, 0, total_size);
334
1.06k
    data = StrictSubstr(data, segment_size);
335
336
1.06k
    auto iter = names.find(i);
337
1.06k
    if (iter == names.end()) {
338
748
      std::string name = "data[" + std::to_string(i) + "]";
339
748
      sink->AddFileRange("wasm_data", name, segment);
340
748
    } else {
341
312
      sink->AddFileRange("wasm_data", iter->second, segment);
342
312
    }
343
1.06k
  }
344
2.26k
}
345
346
347
4.62k
void ParseSymbols(RangeSink* sink) {
348
  // First pass: read the custom naming section to get function names.
349
4.62k
  std::unordered_map<int, std::string> func_names;
350
4.62k
  std::unordered_map<int, std::string> dataseg_names;
351
4.62k
  uint32_t num_imports = 0;
352
353
4.62k
  ForEachSection(sink->input_file().data(),
354
275k
                 [&func_names, &dataseg_names, sink](const Section& section) {
355
275k
                   if (section.name == "name") {
356
0
                     ReadNames(section, &func_names, &dataseg_names, sink);
357
0
                   }
358
275k
                 });
359
360
  // Second pass: read the function/code sections.
361
4.62k
  ForEachSection(sink->input_file().data(),
362
125k
                 [&func_names, &dataseg_names, &num_imports, sink](const Section& section) {
363
125k
                   if (section.id == Section::kImport) {
364
3.86k
                     num_imports = GetNumFunctionImports(section);
365
121k
                   } else if (section.id == Section::kCode) {
366
1.77k
                     ReadCodeSection(section, func_names, num_imports, sink);
367
120k
                   } else if (section.id == Section::kData) {
368
2.26k
                     ReadDataSection(section, dataseg_names, sink);
369
2.26k
                   }
370
125k
                 });
371
4.62k
}
372
373
38.0k
void AddWebAssemblyFallback(RangeSink* sink) {
374
2.29M
  ForEachSection(sink->input_file().data(), [sink](const Section& section) {
375
2.29M
    std::string name2 =
376
2.29M
        std::string("[section ") + std::string(section.name) + std::string("]");
377
2.29M
    sink->AddFileRange("wasm_overhead", name2, section.data);
378
2.29M
  });
379
38.0k
  sink->AddFileRange("wasm_overhead", "[WASM Header]",
380
38.0k
                     StrictSubstr(sink->input_file().data(), 0, 8));
381
38.0k
}
382
383
class WebAssemblyObjectFile : public ObjectFile {
384
 public:
385
  WebAssemblyObjectFile(std::unique_ptr<InputFile> file_data)
386
64.2k
      : ObjectFile(std::move(file_data)) {}
387
388
64.2k
  std::string GetBuildId() const override {
389
    // TODO(haberman): does WebAssembly support this?
390
64.2k
    return std::string();
391
64.2k
  }
392
393
32.1k
  void ProcessFile(const std::vector<RangeSink*>& sinks) const override {
394
59.8k
    for (auto sink : sinks) {
395
59.8k
      switch (sink->data_source()) {
396
36.7k
        case DataSource::kSegments:
397
41.3k
        case DataSource::kSections:
398
41.3k
          ParseSections(sink);
399
41.3k
          break;
400
0
        case DataSource::kSymbols:
401
0
        case DataSource::kRawSymbols:
402
4.62k
        case DataSource::kShortSymbols:
403
4.62k
        case DataSource::kFullSymbols:
404
4.62k
          ParseSymbols(sink);
405
4.62k
          break;
406
4.62k
        case DataSource::kArchiveMembers:
407
9.24k
        case DataSource::kCompileUnits:
408
13.8k
        case DataSource::kInlines:
409
13.8k
        default:
410
13.8k
          THROW("WebAssembly doesn't support this data source");
411
59.8k
      }
412
38.0k
      AddWebAssemblyFallback(sink);
413
38.0k
    }
414
32.1k
  }
415
416
  bool GetDisassemblyInfo(absl::string_view /*symbol*/,
417
                          DataSource /*symbol_source*/,
418
0
                          DisassemblyInfo* /*info*/) const override {
419
0
    WARN("WebAssembly files do not support disassembly yet");
420
0
    return false;
421
0
  }
422
};
423
424
}  // namespace wasm
425
426
std::unique_ptr<ObjectFile> TryOpenWebAssemblyFile(
427
18.1M
    std::unique_ptr<InputFile>& file) {
428
18.1M
  string_view data = file->data();
429
18.1M
  if (wasm::ReadMagic(&data)) {
430
64.2k
    return std::unique_ptr<ObjectFile>(
431
64.2k
        new wasm::WebAssemblyObjectFile(std::move(file)));
432
64.2k
  }
433
434
18.0M
  return nullptr;
435
18.1M
}
436
437
}  // namespace bloaty