Added experimental support for WebAssembly.
Currently, only sections and symbols are supported.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5ae4569..63c0df2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -89,6 +89,7 @@
src/elf.cc
src/macho.cc
src/range_map.cc
+ src/webassembly.cc
# Until Abseil has a proper CMake build system
third_party/abseil-cpp/absl/base/internal/raw_logging.cc # Grrrr...
third_party/abseil-cpp/absl/base/internal/throw_delegate.cc
diff --git a/README.md b/README.md
index 1f4fa3c..da8e515 100644
--- a/README.md
+++ b/README.md
@@ -3,15 +3,24 @@
[![Build Status](https://travis-ci.org/google/bloaty.svg?branch=master)](https://travis-ci.org/google/bloaty)
-Ever wondered what's making your ELF or Mach-O binary big?
-Bloaty McBloatface will show you a size profile of the binary
-so you can understand what's taking up space inside.
+Ever wondered what's making your binary big? Bloaty
+McBloatface will show you a size profile of the binary so
+you can understand what's taking up space inside.
Bloaty works on binaries, shared objects, object files, and
-static libraries (`.a` files). It supports ELF/DWARF and
-Mach-O, though the Mach-O support is much more preliminary
-(it shells out to `otool`/`symbols` instead of parsing the
-file directly).
+static libraries (`.a` files). The following file formats
+are supported:
+
+* ELF
+* Mach-O
+* WebAssembly (experimental)
+
+These formats are NOT supported, but I am very interested
+in adding support for them (I may implement these myself but
+would also be happy to get contributions!)
+
+* PE/COFF (not supported)
+* Android APK (not supported, might be tricky due to compression)
This is not an official Google product.
diff --git a/src/bloaty.cc b/src/bloaty.cc
index 8834c0c..acb1606 100644
--- a/src/bloaty.cc
+++ b/src/bloaty.cc
@@ -706,16 +706,36 @@
<< PercentString(row.filepercent, diff_mode_) << "\n";
}
+bool RollupOutput::IsSame(const std::string& a, const std::string& b) {
+ if (a == b) {
+ return true;
+ }
+
+ if (absl::EndsWith(b, a + "]") || absl::EndsWith(a, b + "]")) {
+ return true;
+ }
+
+ return false;
+}
+
void RollupOutput::PrettyPrintTree(const RollupRow& row, size_t indent,
size_t longest_label,
std::ostream* out) const {
// Rows are printed before their sub-rows.
PrettyPrintRow(row, indent, longest_label, out);
- if (row.vmsize || row.filesize) {
- for (const auto& child : row.sorted_children) {
- PrettyPrintTree(child, indent + 4, longest_label, out);
- }
+ if (!row.vmsize && !row.filesize) {
+ return;
+ }
+
+ if (row.sorted_children.size() == 1 &&
+ row.sorted_children[0].sorted_children.size() == 0 &&
+ IsSame(row.name, row.sorted_children[0].name)) {
+ return;
+ }
+
+ for (const auto& child : row.sorted_children) {
+ PrettyPrintTree(child, indent + 4, longest_label, out);
}
}
@@ -1267,6 +1287,10 @@
}
if (!object_file.get()) {
+ object_file = TryOpenWebAssemblyFile(file);
+ }
+
+ if (!object_file.get()) {
THROWF("unknown file type for file '$0'", filename.c_str());
}
diff --git a/src/bloaty.h b/src/bloaty.h
index 8f6f8e3..780b1c8 100644
--- a/src/bloaty.h
+++ b/src/bloaty.h
@@ -302,6 +302,7 @@
std::unique_ptr<ObjectFile> TryOpenELFFile(std::unique_ptr<InputFile>& file);
std::unique_ptr<ObjectFile> TryOpenMachOFile(std::unique_ptr<InputFile>& file);
+std::unique_ptr<ObjectFile> TryOpenWebAssemblyFile(std::unique_ptr<InputFile>& file);
namespace dwarf {
@@ -499,6 +500,7 @@
// When we are in diff mode, rollup sizes are relative to the baseline.
bool diff_mode_ = false;
+ static bool IsSame(const std::string& a, const std::string& b);
void PrettyPrint(size_t max_label_len, std::ostream* out) const;
void PrintToCSV(std::ostream* out) const;
size_t CalculateLongestLabel(const RollupRow& row, int indent) const;
diff --git a/src/webassembly.cc b/src/webassembly.cc
new file mode 100644
index 0000000..c358a34
--- /dev/null
+++ b/src/webassembly.cc
@@ -0,0 +1,327 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "bloaty.h"
+
+#include "absl/strings/substitute.h"
+
+ABSL_ATTRIBUTE_NORETURN
+static void Throw(const char *str, int line) {
+ throw bloaty::Error(str, __FILE__, line);
+}
+
+#define THROW(msg) Throw(msg, __LINE__)
+#define THROWF(...) Throw(absl::Substitute(__VA_ARGS__).c_str(), __LINE__)
+#define WARN(x) fprintf(stderr, "bloaty: %s\n", x);
+
+using absl::string_view;
+
+namespace bloaty {
+namespace wasm {
+
+template <class T>
+T ReadMemcpy(string_view* data) {
+ T ret;
+ if (data->size() < sizeof(T)) {
+ THROW("premature EOF reading fixed-length wasm data");
+ }
+ memcpy(&ret, data->data(), sizeof(T));
+ data->remove_prefix(sizeof(T));
+ return ret;
+}
+
+uint64_t ReadLEB128Internal(bool is_signed, size_t size, string_view* data) {
+ uint64_t ret = 0;
+ int shift = 0;
+ int maxshift = 70;
+ const char* ptr = data->data();
+ const char* limit = ptr + data->size();
+
+ while (ptr < limit && shift < maxshift) {
+ char byte = *(ptr++);
+ ret |= static_cast<uint64_t>(byte & 0x7f) << shift;
+ shift += 7;
+ if ((byte & 0x80) == 0) {
+ data->remove_prefix(ptr - data->data());
+ if (is_signed && shift < size && (byte & 0x40)) {
+ ret |= -(1ULL << shift);
+ }
+ return ret;
+ }
+ }
+
+ THROW("corrupt wasm data, unterminated LEB128");
+}
+
+bool ReadVarUInt1(string_view* data) {
+ return static_cast<bool>(ReadLEB128Internal(false, 1, data));
+}
+
+char ReadVarUInt7(string_view* data) {
+ return static_cast<char>(ReadLEB128Internal(false, 7, data));
+}
+
+uint32_t ReadVarUInt32(string_view* data) {
+ return static_cast<uint32_t>(ReadLEB128Internal(false, 32, data));
+}
+
+string_view ReadPiece(size_t bytes, string_view* data) {
+ if(data->size() < bytes) {
+ THROW("premature EOF reading variable-length DWARF data");
+ }
+ string_view ret = data->substr(0, bytes);
+ data->remove_prefix(bytes);
+ return ret;
+}
+
+bool ReadMagic(string_view* data) {
+ const uint32_t wasm_magic = 0x6d736100;
+ uint32_t magic = ReadMemcpy<uint32_t>(data);
+
+ if (magic != wasm_magic) {
+ return false;
+ }
+
+ // TODO(haberman): do we need to fail if this is >1?
+ uint32_t version = ReadMemcpy<uint32_t>(data);
+ (void)version;
+
+ return true;
+}
+
+class Section {
+ public:
+ int id;
+ std::string name;
+ string_view data;
+ string_view contents;
+
+ static Section Read(string_view* data_param) {
+ Section ret;
+ string_view data = *data_param;
+ string_view section_data = data;
+
+ ret.id = ReadVarUInt7(&data);
+ uint32_t size = ReadVarUInt32(&data);
+ string_view next_section = data.substr(size);
+ data = data.substr(0, size);
+ size_t header_size = data.data() - section_data.data();
+ ret.data = section_data.substr(0, size + header_size);
+
+ if (ret.id == 0) {
+ uint32_t name_len = ReadVarUInt32(&data);
+ ret.name = std::string(ReadPiece(name_len, &data));
+ } else if (ret.id <= 11) {
+ ret.name = names[ret.id];
+ } else {
+ THROWF("Unknown section id: $0", ret.id);
+ }
+
+ ret.contents = data;
+ *data_param = next_section;
+ return ret;
+ }
+
+ enum Name {
+ kType = 1,
+ kImport = 2,
+ kFunction = 3,
+ kTable = 4,
+ kMemory = 5,
+ kGlobal = 6,
+ kExport = 7,
+ kStart = 8,
+ kElement = 9,
+ kCode = 10,
+ kData = 11,
+ };
+
+ static const char* names[];
+};
+
+const char* Section::names[] = {
+ "<none>", // 0
+ "Type", // 1
+ "Import", // 2
+ "Function", // 3
+ "Table", // 4
+ "Memory", // 5
+ "Global", // 6
+ "Export", // 7
+ "Start", // 8
+ "Element", // 9
+ "Code", // 10
+ "Data", // 11
+};
+
+template <class Func>
+void ForEachSection(string_view file, Func&& section_func) {
+ string_view data = file;
+ ReadMagic(&data);
+
+ while (!data.empty()) {
+ Section section = Section::Read(&data);
+ section_func(section);
+ }
+}
+
+void ParseSections(RangeSink* sink) {
+ ForEachSection(sink->input_file().data(), [sink](const Section& section) {
+ sink->AddFileRange("wasm_sections", section.name, section.data);
+ });
+}
+
+typedef std::unordered_map<int, std::string> FuncNames;
+
+void ReadFunctionNames(const Section& section, FuncNames* names,
+ RangeSink* sink) {
+ enum class NameType {
+ kModule = 0,
+ kFunction = 1,
+ kLocal = 2,
+ };
+
+ string_view data = section.contents;
+
+ while (!data.empty()) {
+ char type = ReadVarUInt7(&data);
+ uint32_t size = ReadVarUInt32(&data);
+ string_view section = data.substr(0, size);
+ data = data.substr(size);
+
+ if (static_cast<NameType>(type) == NameType::kFunction) {
+ uint32_t count = ReadVarUInt32(§ion);
+ for (uint32_t i = 0; i < count; i++) {
+ string_view entry = section;
+ uint32_t index = ReadVarUInt32(§ion);
+ uint32_t name_len = ReadVarUInt32(§ion);
+ string_view name = ReadPiece(name_len, §ion);
+ entry = entry.substr(0, name.data() - entry.data() + name.size());
+ sink->AddFileRange("wasm_funcname", name, entry);
+ (*names)[index] = std::string(name);
+ }
+ }
+ }
+}
+
+void ReadCodeSection(const Section& section, const FuncNames& names,
+ RangeSink* sink) {
+ string_view data = section.contents;
+
+ uint32_t count = ReadVarUInt32(&data);
+
+ for (uint32_t i = 0; i < count; i++) {
+ string_view func = data;
+ uint32_t size = ReadVarUInt32(&data);
+ uint32_t total_size = size + (data.data() - func.data());
+
+ func = func.substr(0, total_size);
+ data = data.substr(size);
+
+ auto iter = names.find(i);
+
+ if (iter == names.end()) {
+ std::string name = "func[" + std::to_string(i) + "]";
+ sink->AddFileRange("wasm_function", name, func);
+ } else {
+ sink->AddFileRange("wasm_function", iter->second, func);
+ }
+ }
+}
+
+void ParseSymbols(RangeSink* sink) {
+ // First pass: read the custom naming section to get function names.
+ std::unordered_map<int, std::string> func_names;
+
+ ForEachSection(sink->input_file().data(),
+ [&func_names, sink](const Section& section) {
+ if (section.name == "name") {
+ ReadFunctionNames(section, &func_names, sink);
+ }
+ });
+
+ // Second pass: read the function/code sections.
+ ForEachSection(sink->input_file().data(),
+ [&func_names, sink](const Section& section) {
+ if (section.id == Section::kCode) {
+ ReadCodeSection(section, func_names, sink);
+ }
+ });
+}
+
+void AddWebAssemblyFallback(RangeSink* sink) {
+ ForEachSection(sink->input_file().data(), [sink](const Section& section) {
+ std::string name2 =
+ std::string("[section ") + std::string(section.name) + std::string("]");
+ sink->AddFileRange("wasm_overhead", name2, section.data);
+ });
+ sink->AddFileRange("wasm_overhead", "[WASM Header]",
+ sink->input_file().data().substr(0, 8));
+}
+
+class WebAssemblyObjectFile : public ObjectFile {
+ public:
+ WebAssemblyObjectFile(std::unique_ptr<InputFile> file_data)
+ : ObjectFile(std::move(file_data)) {}
+
+ std::string GetBuildId() const override {
+ // TODO(haberman): does WebAssembly support this?
+ return std::string();
+ }
+
+ void ProcessFile(const std::vector<RangeSink*>& sinks) const override {
+ for (auto sink : sinks) {
+ switch (sink->data_source()) {
+ case DataSource::kSegments:
+ case DataSource::kSections:
+ ParseSections(sink);
+ break;
+ case DataSource::kSymbols:
+ case DataSource::kRawSymbols:
+ case DataSource::kShortSymbols:
+ case DataSource::kFullSymbols:
+ ParseSymbols(sink);
+ break;
+ case DataSource::kArchiveMembers:
+ case DataSource::kCompileUnits:
+ case DataSource::kInlines:
+ default:
+ THROW("WebAssembly doesn't support this data source");
+ }
+ AddWebAssemblyFallback(sink);
+ }
+ }
+
+ bool GetDisassemblyInfo(absl::string_view /*symbol*/,
+ DataSource /*symbol_source*/,
+ DisassemblyInfo* /*info*/) const override {
+ WARN("WebAssembly files do not support disassembly yet");
+ return false;
+ }
+};
+
+} // namespace wasm
+
+std::unique_ptr<ObjectFile> TryOpenWebAssemblyFile(
+ std::unique_ptr<InputFile>& file) {
+ string_view data = file->data();
+ if (wasm::ReadMagic(&data)) {
+ return std::unique_ptr<ObjectFile>(
+ new wasm::WebAssemblyObjectFile(std::move(file)));
+ }
+
+ return nullptr;
+}
+
+} // namespace bloaty