Coverage Report

Created: 2023-10-07 03:36

/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
1.35M
uint64_t ReadLEB128Internal(bool is_signed, size_t size, string_view* data) {
26
1.35M
  uint64_t ret = 0;
27
1.35M
  int shift = 0;
28
1.35M
  int maxshift = 70;
29
1.35M
  const char* ptr = data->data();
30
1.35M
  const char* limit = ptr + data->size();
31
32
1.40M
  while (ptr < limit && shift < maxshift) {
33
1.40M
    char byte = *(ptr++);
34
1.40M
    ret |= static_cast<uint64_t>(byte & 0x7f) << shift;
35
1.40M
    shift += 7;
36
1.40M
    if ((byte & 0x80) == 0) {
37
1.35M
      data->remove_prefix(ptr - data->data());
38
1.35M
      if (is_signed && shift < size && (byte & 0x40)) {
39
0
        ret |= -(1ULL << shift);
40
0
      }
41
1.35M
      return ret;
42
1.35M
    }
43
1.40M
  }
44
45
1.35M
  THROW("corrupt wasm data, unterminated LEB128");
46
1.35M
}
47
48
2.27k
bool ReadVarUInt1(string_view* data) {
49
2.27k
  return static_cast<bool>(ReadLEB128Internal(false, 1, data));
50
2.27k
}
51
52
590k
uint8_t ReadVarUInt7(string_view* data) {
53
590k
  return static_cast<char>(ReadLEB128Internal(false, 7, data));
54
590k
}
55
56
758k
uint32_t ReadVarUInt32(string_view* data) {
57
758k
  return static_cast<uint32_t>(ReadLEB128Internal(false, 32, data));
58
758k
}
59
60
1.90k
int8_t ReadVarint7(string_view* data) {
61
1.90k
  return static_cast<int8_t>(ReadLEB128Internal(true, 7, data));
62
1.90k
}
63
64
1.33M
string_view ReadPiece(size_t bytes, string_view* data) {
65
1.33M
  if(data->size() < bytes) {
66
1.62k
    THROW("premature EOF reading variable-length DWARF data");
67
1.62k
  }
68
1.32M
  string_view ret = data->substr(0, bytes);
69
1.32M
  data->remove_prefix(bytes);
70
1.32M
  return ret;
71
1.33M
}
72
73
453k
bool ReadMagic(string_view* data) {
74
453k
  const uint32_t wasm_magic = 0x6d736100;
75
453k
  auto magic = ReadFixed<uint32_t>(data);
76
77
453k
  if (magic != wasm_magic) {
78
346k
    return false;
79
346k
  }
80
81
  // TODO(haberman): do we need to fail if this is >1?
82
107k
  auto version = ReadFixed<uint32_t>(data);
83
107k
  (void)version;
84
85
107k
  return true;
86
453k
}
87
88
class Section {
89
 public:
90
  uint32_t id;
91
  std::string name;
92
  string_view data;
93
  string_view contents;
94
95
590k
  static Section Read(string_view* data_param) {
96
590k
    Section ret;
97
590k
    string_view data = *data_param;
98
590k
    string_view section_data = data;
99
100
590k
    ret.id = ReadVarUInt7(&data);
101
590k
    uint32_t size = ReadVarUInt32(&data);
102
590k
    ret.contents = ReadPiece(size, &data);
103
590k
    size_t header_size = ret.contents.data() - section_data.data();
104
590k
    ret.data = ReadPiece(size + header_size, &section_data);
105
106
590k
    if (ret.id == 0) {
107
141k
      uint32_t name_len = ReadVarUInt32(&ret.contents);
108
141k
      ret.name = std::string(ReadPiece(name_len, &ret.contents));
109
448k
    } else if (ret.id <= 13) {
110
446k
      ret.name = names[ret.id];
111
446k
    } else {
112
1.88k
      THROWF("Unknown section id: $0", ret.id);
113
1.88k
    }
114
115
588k
    *data_param = data;
116
588k
    return ret;
117
590k
  }
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
62.3k
void ForEachSection(string_view file, Func&& section_func) {
166
62.3k
  string_view data = file;
167
62.3k
  ReadMagic(&data);
168
169
652k
  while (!data.empty()) {
170
590k
    Section section = Section::Read(&data);
171
590k
    section_func(section);
172
590k
  }
173
62.3k
}
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
29.0k
void ForEachSection(string_view file, Func&& section_func) {
166
29.0k
  string_view data = file;
167
29.0k
  ReadMagic(&data);
168
169
319k
  while (!data.empty()) {
170
290k
    Section section = Section::Read(&data);
171
290k
    section_func(section);
172
290k
  }
173
29.0k
}
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
3.27k
void ForEachSection(string_view file, Func&& section_func) {
166
3.27k
  string_view data = file;
167
3.27k
  ReadMagic(&data);
168
169
33.2k
  while (!data.empty()) {
170
29.9k
    Section section = Section::Read(&data);
171
29.9k
    section_func(section);
172
29.9k
  }
173
3.27k
}
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
3.06k
void ForEachSection(string_view file, Func&& section_func) {
166
3.06k
  string_view data = file;
167
3.06k
  ReadMagic(&data);
168
169
21.0k
  while (!data.empty()) {
170
18.0k
    Section section = Section::Read(&data);
171
18.0k
    section_func(section);
172
18.0k
  }
173
3.06k
}
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
26.9k
void ForEachSection(string_view file, Func&& section_func) {
166
26.9k
  string_view data = file;
167
26.9k
  ReadMagic(&data);
168
169
278k
  while (!data.empty()) {
170
251k
    Section section = Section::Read(&data);
171
251k
    section_func(section);
172
251k
  }
173
26.9k
}
174
175
29.0k
void ParseSections(RangeSink* sink) {
176
287k
  ForEachSection(sink->input_file().data(), [sink](const Section& section) {
177
287k
    sink->AddFileRange("wasm_sections", section.name, section.data);
178
287k
  });
179
29.0k
}
180
181
typedef std::unordered_map<int, std::string> IndexedNames;
182
183
void ReadNames(const Section& section, IndexedNames* func_names,
184
359
               IndexedNames* dataseg_names, RangeSink* sink) {
185
359
  enum class NameType {
186
359
    kModule = 0,
187
359
    kFunction = 1,
188
359
    kLocal = 2,
189
359
    kLabel = 3,
190
359
    kType = 4,
191
359
    kTable = 5,
192
359
    kMemory = 6,
193
359
    kGlobal = 7,
194
359
    kElemSegment = 8,
195
359
    kDataSegment = 9
196
359
  };
197
198
359
  string_view data = section.contents;
199
200
919
  while (!data.empty()) {
201
560
    NameType type = static_cast<NameType>(ReadVarUInt7(&data));
202
560
    uint32_t size = ReadVarUInt32(&data);
203
560
    string_view section = ReadPiece(size, &data);
204
205
560
    if (type == NameType::kFunction || type == NameType::kDataSegment) {
206
265
      uint32_t count = ReadVarUInt32(&section);
207
1.06k
      for (uint32_t i = 0; i < count; i++) {
208
800
        string_view entry = section;
209
800
        uint32_t index = ReadVarUInt32(&section);
210
800
        uint32_t name_len = ReadVarUInt32(&section);
211
800
        string_view name = ReadPiece(name_len, &section);
212
800
        entry = StrictSubstr(entry, 0, name.data() - entry.data() + name.size());
213
800
        sink->AddFileRange("wasm_funcname", name, entry);
214
800
        IndexedNames *names = (type == NameType::kFunction ? func_names : dataseg_names);
215
800
        (*names)[index] = std::string(name);
216
800
      }
217
265
    }
218
560
  }
219
359
}
220
221
411
int ReadValueType(string_view* data) {
222
411
  return ReadVarint7(data);
223
411
}
224
225
1.49k
int ReadElemType(string_view* data) {
226
1.49k
  return ReadVarint7(data);
227
1.49k
}
228
229
1.91k
void ReadResizableLimits(string_view* data) {
230
1.91k
  auto flags = ReadVarUInt1(data);
231
1.91k
  ReadVarUInt32(data);
232
1.91k
  if (flags) {
233
1.27k
    ReadVarUInt32(data);
234
1.27k
  }
235
1.91k
}
236
237
411
void ReadGlobalType(string_view* data) {
238
411
  ReadValueType(data);
239
411
  ReadVarUInt1(data);
240
411
}
241
242
1.49k
void ReadTableType(string_view* data) {
243
1.49k
  ReadElemType(data);
244
1.49k
  ReadResizableLimits(data);
245
1.49k
}
246
247
475
void ReadMemoryType(string_view* data) {
248
475
  ReadResizableLimits(data);
249
475
}
250
251
2.37k
uint32_t GetNumFunctionImports(const Section& section) {
252
2.37k
  assert(section.id == Section::kImport);
253
0
  string_view data = section.contents;
254
255
2.37k
  uint32_t count = ReadVarUInt32(&data);
256
2.37k
  uint32_t func_count = 0;
257
258
6.46k
  for (uint32_t i = 0; i < count; i++) {
259
5.46k
    uint32_t module_len = ReadVarUInt32(&data);
260
5.46k
    ReadPiece(module_len, &data);
261
5.46k
    uint32_t field_len = ReadVarUInt32(&data);
262
5.46k
    ReadPiece(field_len, &data);
263
5.46k
    auto kind = ReadFixed<uint8_t>(&data);
264
265
5.46k
    switch (kind) {
266
2.11k
      case ExternalKind::kFunction:
267
2.11k
        func_count++;
268
2.11k
        ReadVarUInt32(&data);
269
2.11k
        break;
270
1.49k
      case ExternalKind::kTable:
271
1.49k
        ReadTableType(&data);
272
1.49k
        break;
273
475
      case ExternalKind::kMemory:
274
475
        ReadMemoryType(&data);
275
475
        break;
276
411
      case ExternalKind::kGlobal:
277
411
        ReadGlobalType(&data);
278
411
        break;
279
153
      default:
280
153
        THROWF("Unrecognized import kind: $0", kind);
281
5.46k
    }
282
5.46k
  }
283
284
999
  return func_count;
285
2.37k
}
286
287
void ReadCodeSection(const Section& section, const IndexedNames& names,
288
986
                     uint32_t num_imports, RangeSink* sink) {
289
986
  string_view data = section.contents;
290
291
986
  uint32_t count = ReadVarUInt32(&data);
292
293
4.03k
  for (uint32_t i = 0; i < count; i++) {
294
3.04k
    string_view func = data;
295
3.04k
    uint32_t size = ReadVarUInt32(&data);
296
3.04k
    uint32_t total_size = size + (data.data() - func.data());
297
298
3.04k
    func = StrictSubstr(func, 0, total_size);
299
3.04k
    data = StrictSubstr(data, size);
300
301
3.04k
    auto iter = names.find(num_imports + i);
302
303
3.04k
    if (iter == names.end()) {
304
2.65k
      std::string name = "func[" + std::to_string(i) + "]";
305
2.65k
      sink->AddFileRange("wasm_function", name, func);
306
2.65k
    } else {
307
393
      sink->AddFileRange("wasm_function", ItaniumDemangle(iter->second, sink->data_source()), func);
308
393
    }
309
3.04k
  }
310
986
}
311
312
void ReadDataSection(const Section& section, const IndexedNames& names,
313
1.34k
                     RangeSink* sink) {
314
1.34k
  string_view data = section.contents;
315
1.34k
  uint32_t count = ReadVarUInt32(&data);
316
317
4.45k
  for (uint32_t i = 0; i < count; i++) {
318
3.24k
    string_view segment = data;
319
3.24k
    uint8_t mode = ReadFixed<uint8_t>(&data);
320
3.24k
    if (mode > 1) THROW("multi-memory extension isn't supported");
321
3.10k
    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
2.06k
      continue;
327
2.06k
    }
328
    // else, a passive segment
329
330
1.04k
    uint32_t segment_size = ReadVarUInt32(&data);
331
1.04k
    uint32_t total_size = segment_size + (data.data() - segment.data());
332
333
1.04k
    segment = StrictSubstr(segment, 0, total_size);
334
1.04k
    data = StrictSubstr(data, segment_size);
335
336
1.04k
    auto iter = names.find(i);
337
1.04k
    if (iter == names.end()) {
338
825
      std::string name = "data[" + std::to_string(i) + "]";
339
825
      sink->AddFileRange("wasm_data", name, segment);
340
825
    } else {
341
217
      sink->AddFileRange("wasm_data", iter->second, segment);
342
217
    }
343
1.04k
  }
344
1.34k
}
345
346
347
3.27k
void ParseSymbols(RangeSink* sink) {
348
  // First pass: read the custom naming section to get function names.
349
3.27k
  std::unordered_map<int, std::string> func_names;
350
3.27k
  std::unordered_map<int, std::string> dataseg_names;
351
3.27k
  uint32_t num_imports = 0;
352
353
3.27k
  ForEachSection(sink->input_file().data(),
354
29.9k
                 [&func_names, &dataseg_names, sink](const Section& section) {
355
29.9k
                   if (section.name == "name") {
356
359
                     ReadNames(section, &func_names, &dataseg_names, sink);
357
359
                   }
358
29.9k
                 });
359
360
  // Second pass: read the function/code sections.
361
3.27k
  ForEachSection(sink->input_file().data(),
362
18.0k
                 [&func_names, &dataseg_names, &num_imports, sink](const Section& section) {
363
18.0k
                   if (section.id == Section::kImport) {
364
2.37k
                     num_imports = GetNumFunctionImports(section);
365
15.6k
                   } else if (section.id == Section::kCode) {
366
986
                     ReadCodeSection(section, func_names, num_imports, sink);
367
14.6k
                   } else if (section.id == Section::kData) {
368
1.34k
                     ReadDataSection(section, dataseg_names, sink);
369
1.34k
                   }
370
18.0k
                 });
371
3.27k
}
372
373
26.9k
void AddWebAssemblyFallback(RangeSink* sink) {
374
251k
  ForEachSection(sink->input_file().data(), [sink](const Section& section) {
375
251k
    std::string name2 =
376
251k
        std::string("[section ") + std::string(section.name) + std::string("]");
377
251k
    sink->AddFileRange("wasm_overhead", name2, section.data);
378
251k
  });
379
26.9k
  sink->AddFileRange("wasm_overhead", "[WASM Header]",
380
26.9k
                     StrictSubstr(sink->input_file().data(), 0, 8));
381
26.9k
}
382
383
class WebAssemblyObjectFile : public ObjectFile {
384
 public:
385
  WebAssemblyObjectFile(std::unique_ptr<InputFile> file_data)
386
44.9k
      : ObjectFile(std::move(file_data)) {}
387
388
44.9k
  std::string GetBuildId() const override {
389
    // TODO(haberman): does WebAssembly support this?
390
44.9k
    return std::string();
391
44.9k
  }
392
393
22.4k
  void ProcessFile(const std::vector<RangeSink*>& sinks) const override {
394
42.1k
    for (auto sink : sinks) {
395
42.1k
      switch (sink->data_source()) {
396
25.7k
        case DataSource::kSegments:
397
29.0k
        case DataSource::kSections:
398
29.0k
          ParseSections(sink);
399
29.0k
          break;
400
0
        case DataSource::kSymbols:
401
0
        case DataSource::kRawSymbols:
402
3.27k
        case DataSource::kShortSymbols:
403
3.27k
        case DataSource::kFullSymbols:
404
3.27k
          ParseSymbols(sink);
405
3.27k
          break;
406
3.27k
        case DataSource::kArchiveMembers:
407
6.55k
        case DataSource::kCompileUnits:
408
9.82k
        case DataSource::kInlines:
409
9.82k
        default:
410
9.82k
          THROW("WebAssembly doesn't support this data source");
411
42.1k
      }
412
26.9k
      AddWebAssemblyFallback(sink);
413
26.9k
    }
414
22.4k
  }
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
391k
    std::unique_ptr<InputFile>& file) {
428
391k
  string_view data = file->data();
429
391k
  if (wasm::ReadMagic(&data)) {
430
44.9k
    return std::unique_ptr<ObjectFile>(
431
44.9k
        new wasm::WebAssemblyObjectFile(std::move(file)));
432
44.9k
  }
433
434
346k
  return nullptr;
435
391k
}
436
437
}  // namespace bloaty