/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 | 6.41M | uint64_t ReadLEB128Internal(bool is_signed, size_t size, string_view* data) { |
26 | 6.41M | uint64_t ret = 0; |
27 | 6.41M | int shift = 0; |
28 | 6.41M | int maxshift = 70; |
29 | 6.41M | const char* ptr = data->data(); |
30 | 6.41M | const char* limit = ptr + data->size(); |
31 | | |
32 | 6.45M | while (ptr < limit && shift < maxshift) { |
33 | 6.45M | char byte = *(ptr++); |
34 | 6.45M | ret |= static_cast<uint64_t>(byte & 0x7f) << shift; |
35 | 6.45M | shift += 7; |
36 | 6.45M | if ((byte & 0x80) == 0) { |
37 | 6.41M | data->remove_prefix(ptr - data->data()); |
38 | 6.41M | if (is_signed && shift < size && (byte & 0x40)) { |
39 | 0 | ret |= -(1ULL << shift); |
40 | 0 | } |
41 | 6.41M | return ret; |
42 | 6.41M | } |
43 | 6.45M | } |
44 | | |
45 | 6.41M | THROW("corrupt wasm data, unterminated LEB128"); |
46 | 6.41M | } |
47 | | |
48 | 2.40k | bool ReadVarUInt1(string_view* data) { |
49 | 2.40k | return static_cast<bool>(ReadLEB128Internal(false, 1, data)); |
50 | 2.40k | } |
51 | | |
52 | 3.06M | uint8_t ReadVarUInt7(string_view* data) { |
53 | 3.06M | return static_cast<char>(ReadLEB128Internal(false, 7, data)); |
54 | 3.06M | } |
55 | | |
56 | 3.34M | uint32_t ReadVarUInt32(string_view* data) { |
57 | 3.34M | return static_cast<uint32_t>(ReadLEB128Internal(false, 32, data)); |
58 | 3.34M | } |
59 | | |
60 | 1.77k | int8_t ReadVarint7(string_view* data) { |
61 | 1.77k | return static_cast<int8_t>(ReadLEB128Internal(true, 7, data)); |
62 | 1.77k | } |
63 | | |
64 | 6.37M | string_view ReadPiece(size_t bytes, string_view* data) { |
65 | 6.37M | if(data->size() < bytes) { |
66 | 28.0k | THROW("premature EOF reading variable-length DWARF data"); |
67 | 28.0k | } |
68 | 6.34M | string_view ret = data->substr(0, bytes); |
69 | 6.34M | data->remove_prefix(bytes); |
70 | 6.34M | return ret; |
71 | 6.37M | } |
72 | | |
73 | 21.9M | bool ReadMagic(string_view* data) { |
74 | 21.9M | const uint32_t wasm_magic = 0x6d736100; |
75 | 21.9M | auto magic = ReadFixed<uint32_t>(data); |
76 | | |
77 | 21.9M | if (magic != wasm_magic) { |
78 | 21.7M | return false; |
79 | 21.7M | } |
80 | | |
81 | | // TODO(haberman): do we need to fail if this is >1? |
82 | 193k | auto version = ReadFixed<uint32_t>(data); |
83 | 193k | (void)version; |
84 | | |
85 | 193k | return true; |
86 | 21.9M | } |
87 | | |
88 | | class Section { |
89 | | public: |
90 | | uint32_t id; |
91 | | std::string name; |
92 | | string_view data; |
93 | | string_view contents; |
94 | | |
95 | 3.06M | static Section Read(string_view* data_param) { |
96 | 3.06M | Section ret; |
97 | 3.06M | string_view data = *data_param; |
98 | 3.06M | string_view section_data = data; |
99 | | |
100 | 3.06M | ret.id = ReadVarUInt7(&data); |
101 | 3.06M | uint32_t size = ReadVarUInt32(&data); |
102 | 3.06M | ret.contents = ReadPiece(size, &data); |
103 | 3.06M | size_t header_size = ret.contents.data() - section_data.data(); |
104 | 3.06M | ret.data = ReadPiece(size + header_size, §ion_data); |
105 | | |
106 | 3.06M | if (ret.id == 0) { |
107 | 257k | uint32_t name_len = ReadVarUInt32(&ret.contents); |
108 | 257k | ret.name = std::string(ReadPiece(name_len, &ret.contents)); |
109 | 2.81M | } else if (ret.id <= 13) { |
110 | 2.78M | ret.name = names[ret.id]; |
111 | 2.78M | } else { |
112 | 29.3k | THROWF("Unknown section id: $0", ret.id); |
113 | 29.3k | } |
114 | | |
115 | 3.03M | *data_param = data; |
116 | 3.03M | return ret; |
117 | 3.06M | } |
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 | 92.0k | void ForEachSection(string_view file, Func&& section_func) { |
166 | 92.0k | string_view data = file; |
167 | 92.0k | ReadMagic(&data); |
168 | | |
169 | 3.16M | while (!data.empty()) { |
170 | 3.06M | Section section = Section::Read(&data); |
171 | 3.06M | section_func(section); |
172 | 3.06M | } |
173 | 92.0k | } 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 | 57.5k | void ForEachSection(string_view file, Func&& section_func) { | 166 | 57.5k | string_view data = file; | 167 | 57.5k | ReadMagic(&data); | 168 | | | 169 | 1.58M | while (!data.empty()) { | 170 | 1.52M | Section section = Section::Read(&data); | 171 | 1.52M | section_func(section); | 172 | 1.52M | } | 173 | 57.5k | } |
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.34k | void ForEachSection(string_view file, Func&& section_func) { | 166 | 3.34k | string_view data = file; | 167 | 3.34k | ReadMagic(&data); | 168 | | | 169 | 164k | while (!data.empty()) { | 170 | 160k | Section section = Section::Read(&data); | 171 | 160k | section_func(section); | 172 | 160k | } | 173 | 3.34k | } |
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.34k | void ForEachSection(string_view file, Func&& section_func) { | 166 | 3.34k | string_view data = file; | 167 | 3.34k | ReadMagic(&data); | 168 | | | 169 | 64.7k | while (!data.empty()) { | 170 | 61.4k | Section section = Section::Read(&data); | 171 | 61.4k | section_func(section); | 172 | 61.4k | } | 173 | 3.34k | } |
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 | 27.7k | void ForEachSection(string_view file, Func&& section_func) { | 166 | 27.7k | string_view data = file; | 167 | 27.7k | ReadMagic(&data); | 168 | | | 169 | 1.34M | while (!data.empty()) { | 170 | 1.32M | Section section = Section::Read(&data); | 171 | 1.32M | section_func(section); | 172 | 1.32M | } | 173 | 27.7k | } |
|
174 | | |
175 | 57.5k | void ParseSections(RangeSink* sink) { |
176 | 1.49M | ForEachSection(sink->input_file().data(), [sink](const Section& section) { |
177 | 1.49M | sink->AddFileRange("wasm_sections", section.name, section.data); |
178 | 1.49M | }); |
179 | 57.5k | } |
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(§ion); |
207 | 0 | for (uint32_t i = 0; i < count; i++) { |
208 | 0 | string_view entry = section; |
209 | 0 | uint32_t index = ReadVarUInt32(§ion); |
210 | 0 | uint32_t name_len = ReadVarUInt32(§ion); |
211 | 0 | string_view name = ReadPiece(name_len, §ion); |
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 | 530 | int ReadValueType(string_view* data) { |
222 | 530 | return ReadVarint7(data); |
223 | 530 | } |
224 | | |
225 | 1.24k | int ReadElemType(string_view* data) { |
226 | 1.24k | return ReadVarint7(data); |
227 | 1.24k | } |
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.41k | ReadVarUInt32(data); |
234 | 1.41k | } |
235 | 1.91k | } |
236 | | |
237 | 530 | void ReadGlobalType(string_view* data) { |
238 | 530 | ReadValueType(data); |
239 | 530 | ReadVarUInt1(data); |
240 | 530 | } |
241 | | |
242 | 1.24k | void ReadTableType(string_view* data) { |
243 | 1.24k | ReadElemType(data); |
244 | 1.24k | ReadResizableLimits(data); |
245 | 1.24k | } |
246 | | |
247 | 729 | void ReadMemoryType(string_view* data) { |
248 | 729 | ReadResizableLimits(data); |
249 | 729 | } |
250 | | |
251 | 3.25k | uint32_t GetNumFunctionImports(const Section& section) { |
252 | 3.25k | assert(section.id == Section::kImport); |
253 | 0 | string_view data = section.contents; |
254 | | |
255 | 3.25k | uint32_t count = ReadVarUInt32(&data); |
256 | 3.25k | uint32_t func_count = 0; |
257 | | |
258 | 7.02k | for (uint32_t i = 0; i < count; i++) { |
259 | 5.64k | uint32_t module_len = ReadVarUInt32(&data); |
260 | 5.64k | ReadPiece(module_len, &data); |
261 | 5.64k | uint32_t field_len = ReadVarUInt32(&data); |
262 | 5.64k | ReadPiece(field_len, &data); |
263 | 5.64k | auto kind = ReadFixed<uint8_t>(&data); |
264 | | |
265 | 5.64k | switch (kind) { |
266 | 1.82k | case ExternalKind::kFunction: |
267 | 1.82k | func_count++; |
268 | 1.82k | ReadVarUInt32(&data); |
269 | 1.82k | break; |
270 | 1.24k | case ExternalKind::kTable: |
271 | 1.24k | ReadTableType(&data); |
272 | 1.24k | break; |
273 | 729 | case ExternalKind::kMemory: |
274 | 729 | ReadMemoryType(&data); |
275 | 729 | break; |
276 | 530 | case ExternalKind::kGlobal: |
277 | 530 | ReadGlobalType(&data); |
278 | 530 | break; |
279 | 129 | default: |
280 | 129 | THROWF("Unrecognized import kind: $0", kind); |
281 | 5.64k | } |
282 | 5.64k | } |
283 | | |
284 | 1.38k | return func_count; |
285 | 3.25k | } |
286 | | |
287 | | void ReadCodeSection(const Section& section, const IndexedNames& names, |
288 | 358 | uint32_t num_imports, RangeSink* sink) { |
289 | 358 | string_view data = section.contents; |
290 | | |
291 | 358 | uint32_t count = ReadVarUInt32(&data); |
292 | | |
293 | 483 | for (uint32_t i = 0; i < count; i++) { |
294 | 125 | string_view func = data; |
295 | 125 | uint32_t size = ReadVarUInt32(&data); |
296 | 125 | uint32_t total_size = size + (data.data() - func.data()); |
297 | | |
298 | 125 | func = StrictSubstr(func, 0, total_size); |
299 | 125 | data = StrictSubstr(data, size); |
300 | | |
301 | 125 | auto iter = names.find(num_imports + i); |
302 | | |
303 | 125 | if (iter == names.end()) { |
304 | 82 | std::string name = "func[" + std::to_string(i) + "]"; |
305 | 82 | sink->AddFileRange("wasm_function", name, func); |
306 | 82 | } else { |
307 | 43 | sink->AddFileRange("wasm_function", ItaniumDemangle(iter->second, sink->data_source()), func); |
308 | 43 | } |
309 | 125 | } |
310 | 358 | } |
311 | | |
312 | | void ReadDataSection(const Section& section, const IndexedNames& names, |
313 | 945 | RangeSink* sink) { |
314 | 945 | string_view data = section.contents; |
315 | 945 | uint32_t count = ReadVarUInt32(&data); |
316 | | |
317 | 1.02k | for (uint32_t i = 0; i < count; i++) { |
318 | 142 | string_view segment = data; |
319 | 142 | uint8_t mode = ReadFixed<uint8_t>(&data); |
320 | 142 | if (mode > 1) THROW("multi-memory extension isn't supported"); |
321 | 82 | 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 | 48 | continue; |
327 | 48 | } |
328 | | // else, a passive segment |
329 | | |
330 | 34 | uint32_t segment_size = ReadVarUInt32(&data); |
331 | 34 | uint32_t total_size = segment_size + (data.data() - segment.data()); |
332 | | |
333 | 34 | segment = StrictSubstr(segment, 0, total_size); |
334 | 34 | data = StrictSubstr(data, segment_size); |
335 | | |
336 | 34 | auto iter = names.find(i); |
337 | 34 | if (iter == names.end()) { |
338 | 8 | std::string name = "data[" + std::to_string(i) + "]"; |
339 | 8 | sink->AddFileRange("wasm_data", name, segment); |
340 | 26 | } else { |
341 | 26 | sink->AddFileRange("wasm_data", iter->second, segment); |
342 | 26 | } |
343 | 34 | } |
344 | 945 | } |
345 | | |
346 | | |
347 | 3.34k | void ParseSymbols(RangeSink* sink) { |
348 | | // First pass: read the custom naming section to get function names. |
349 | 3.34k | std::unordered_map<int, std::string> func_names; |
350 | 3.34k | std::unordered_map<int, std::string> dataseg_names; |
351 | 3.34k | uint32_t num_imports = 0; |
352 | | |
353 | 3.34k | ForEachSection(sink->input_file().data(), |
354 | 160k | [&func_names, &dataseg_names, sink](const Section& section) { |
355 | 160k | if (section.name == "name") { |
356 | 0 | ReadNames(section, &func_names, &dataseg_names, sink); |
357 | 0 | } |
358 | 160k | }); |
359 | | |
360 | | // Second pass: read the function/code sections. |
361 | 3.34k | ForEachSection(sink->input_file().data(), |
362 | 61.4k | [&func_names, &dataseg_names, &num_imports, sink](const Section& section) { |
363 | 61.4k | if (section.id == Section::kImport) { |
364 | 3.25k | num_imports = GetNumFunctionImports(section); |
365 | 58.1k | } else if (section.id == Section::kCode) { |
366 | 358 | ReadCodeSection(section, func_names, num_imports, sink); |
367 | 57.7k | } else if (section.id == Section::kData) { |
368 | 945 | ReadDataSection(section, dataseg_names, sink); |
369 | 945 | } |
370 | 61.4k | }); |
371 | 3.34k | } |
372 | | |
373 | 27.7k | void AddWebAssemblyFallback(RangeSink* sink) { |
374 | 1.32M | ForEachSection(sink->input_file().data(), [sink](const Section& section) { |
375 | 1.32M | std::string name2 = |
376 | 1.32M | std::string("[section ") + std::string(section.name) + std::string("]"); |
377 | 1.32M | sink->AddFileRange("wasm_overhead", name2, section.data); |
378 | 1.32M | }); |
379 | 27.7k | sink->AddFileRange("wasm_overhead", "[WASM Header]", |
380 | 27.7k | StrictSubstr(sink->input_file().data(), 0, 8)); |
381 | 27.7k | } |
382 | | |
383 | | class WebAssemblyObjectFile : public ObjectFile { |
384 | | public: |
385 | | WebAssemblyObjectFile(std::unique_ptr<InputFile> file_data) |
386 | 101k | : ObjectFile(std::move(file_data)) {} |
387 | | |
388 | 101k | std::string GetBuildId() const override { |
389 | | // TODO(haberman): does WebAssembly support this? |
390 | 101k | return std::string(); |
391 | 101k | } |
392 | | |
393 | 50.8k | void ProcessFile(const std::vector<RangeSink*>& sinks) const override { |
394 | 70.9k | for (auto sink : sinks) { |
395 | 70.9k | switch (sink->data_source()) { |
396 | 54.2k | case DataSource::kSegments: |
397 | 57.5k | case DataSource::kSections: |
398 | 57.5k | ParseSections(sink); |
399 | 57.5k | break; |
400 | 0 | case DataSource::kSymbols: |
401 | 0 | case DataSource::kRawSymbols: |
402 | 3.34k | case DataSource::kShortSymbols: |
403 | 3.34k | case DataSource::kFullSymbols: |
404 | 3.34k | ParseSymbols(sink); |
405 | 3.34k | break; |
406 | 3.34k | case DataSource::kArchiveMembers: |
407 | 6.69k | case DataSource::kCompileUnits: |
408 | 10.0k | case DataSource::kInlines: |
409 | 10.0k | default: |
410 | 10.0k | THROW("WebAssembly doesn't support this data source"); |
411 | 70.9k | } |
412 | 27.7k | AddWebAssemblyFallback(sink); |
413 | 27.7k | } |
414 | 50.8k | } |
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 | 21.8M | std::unique_ptr<InputFile>& file) { |
428 | 21.8M | string_view data = file->data(); |
429 | 21.8M | if (wasm::ReadMagic(&data)) { |
430 | 101k | return std::unique_ptr<ObjectFile>( |
431 | 101k | new wasm::WebAssemblyObjectFile(std::move(file))); |
432 | 101k | } |
433 | | |
434 | 21.7M | return nullptr; |
435 | 21.8M | } |
436 | | |
437 | | } // namespace bloaty |