Merge branch 'upstream/master' into HEAD Change-Id: I9b2399c8303dd09947308a841b7e5efaaa930c85
diff --git a/CMakeLists.txt b/CMakeLists.txt index 41989ce..680b958 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -28,7 +28,7 @@ "${CMAKE_EXE_LINKER_FLAGS} ${CLANG_CIPD_TOOLCHAIN_DIR}/../lib/libc++.a") endif(APPLE) -if(UNIX) +if(UNIX OR MINGW) find_package(PkgConfig) find_package(ZLIB) if(BLOATY_ENABLE_RE2) @@ -105,7 +105,7 @@ set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads REQUIRED) -if(UNIX) +if(UNIX OR MINGW) if(BLOATY_ENABLE_RE2) if(RE2_FOUND) include_directories(${RE2_INCLUDE_DIRS}) @@ -355,7 +355,7 @@ ) add_dependencies(libbloaty rustc-demangle-lib) -if(UNIX) +if(UNIX OR MINGW) set(LIBBLOATY_LIBS libbloaty) if(PROTOBUF_FOUND) list(APPEND LIBBLOATY_LIBS ${PROTOBUF_LIBRARIES}) @@ -390,7 +390,7 @@ list(APPEND LIBBLOATY_LIBS zlibstatic) endif() -if(UNIX) +if(UNIX OR MINGW) if(BLOATY_ENABLE_RE2) if(RE2_FOUND) link_directories(${RE2_LIBRARY_DIRS})
diff --git a/src/bloaty.cc b/src/bloaty.cc index f178b96..8bc4c08 100644 --- a/src/bloaty.cc +++ b/src/bloaty.cc
@@ -18,6 +18,12 @@ // It's very hard to figure out why. For the moment this seems to fix it, // but ideally we'd have a better solution here. typedef size_t z_size_t; +#include <assert.h> +#include <fcntl.h> +#include <limits.h> +#include <math.h> +#include <signal.h> +#include <stdlib.h> #include <zlib.h> #include <atomic> @@ -35,19 +41,12 @@ #include <thread> #include <unordered_map> #include <vector> - -#include <assert.h> -#include <fcntl.h> -#include <limits.h> -#include <math.h> -#include <signal.h> -#include <stdlib.h> -#if !defined(_MSC_VER) +#if !defined(_WIN32) #include <sys/mman.h> #include <sys/wait.h> #include <unistd.h> #else -#include <Windows.h> +#include <windows.h> #endif #include <sys/stat.h> #include <sys/types.h> @@ -55,16 +54,15 @@ #include "absl/debugging/internal/demangle.h" #include "absl/memory/memory.h" #include "absl/strings/numbers.h" -#include "absl/strings/string_view.h" #include "absl/strings/str_join.h" +#include "absl/strings/string_view.h" #include "absl/strings/substitute.h" -#include "google/protobuf/io/zero_copy_stream_impl.h" -#include "google/protobuf/text_format.h" - #include "bloaty.h" #include "bloaty.pb.h" #include "demangle.h" #include "rustc_demangle.h" +#include "google/protobuf/io/zero_copy_stream_impl.h" +#include "google/protobuf/text_format.h" #include "re.h" using absl::string_view; @@ -245,8 +243,7 @@ return std::string(symbol); } } else if (source == DataSource::kFullSymbols) { - char* demangled = - __cxa_demangle(demangle_from.data(), NULL, NULL, NULL); + char* demangled = __cxa_demangle(demangle_from.data(), NULL, NULL, NULL); if (demangled) { std::string ret(demangled); free(demangled); @@ -274,10 +271,10 @@ } } - // NameMunger ////////////////////////////////////////////////////////////////// -void NameMunger::AddRegex(const std::string& regex, const std::string& replacement) { +void NameMunger::AddRegex(const std::string& regex, + const std::string& replacement) { auto reg = absl::make_unique<ReImpl>(regex); regexes_.push_back(std::make_pair(std::move(reg), replacement)); } @@ -295,7 +292,6 @@ return name_str; } - // Rollup ////////////////////////////////////////////////////////////////////// // A Rollup is a hierarchical tally of sizes. Its graphical representation is @@ -331,8 +327,8 @@ Rollup(Rollup&& other) = default; Rollup& operator=(Rollup&& other) = default; - void AddSizes(const std::vector<std::string>& names, - uint64_t size, bool is_vmsize) { + void AddSizes(const std::vector<std::string>& names, uint64_t size, + bool is_vmsize) { // We start at 1 to exclude the base map (see base_map_). AddInternal(names, 1, size, is_vmsize); } @@ -346,33 +342,17 @@ void CreateDiffModeRollupOutput(Rollup* base, const Options& options, RollupOutput* output) const { RollupRow* row = &output->toplevel_row_; - row->vmsize = vm_total_; - row->filesize = file_total_; - row->filtered_vmsize = filtered_vm_total_; - row->filtered_filesize = filtered_file_total_; + row->size.vm = vm_total_; + row->size.file = file_total_; + row->filtered_size.vm = filtered_vm_total_; + row->filtered_size.file = filtered_file_total_; row->vmpercent = 100; row->filepercent = 100; output->diff_mode_ = true; CreateRows(row, base, options, true); } - void SetFilterRegex(const ReImpl* regex) { - filter_regex_ = regex; - } - - // Subtract the values in "other" from this. - void Subtract(const Rollup& other) { - vm_total_ -= other.vm_total_; - file_total_ -= other.file_total_; - - for (const auto& other_child : other.children_) { - auto& child = children_[other_child.first]; - if (child.get() == NULL) { - child.reset(new Rollup()); - } - child->Subtract(*other_child.second); - } - } + void SetFilterRegex(const ReImpl* regex) { filter_regex_ = regex; } // Add the values in "other" from this. void Add(const Rollup& other) { @@ -388,6 +368,17 @@ } } + // Create entries for all children which exist in "other" but not in this. + void AddEntriesFrom(const Rollup& other) { + for (const auto& other_child : other.children_) { + auto& child = children_[other_child.first]; + if (child.get() == NULL) { + child.reset(new Rollup()); + } + child->AddEntriesFrom(*other_child.second); + } + } + int64_t file_total() const { return file_total_; } int64_t filtered_file_total() const { return filtered_file_total_; } @@ -485,11 +476,33 @@ } for (const auto& value : children_) { - if (value.second->vm_total_ != 0 || value.second->file_total_ != 0) { + int64_t vm_total = value.second->vm_total_; + int64_t file_total = value.second->file_total_; + Rollup* base_child = nullptr; + + if (base) { + // Reassign sizes to base during a diff to compare to target sizes. + auto it = base->children_.find(value.first); + if (it != base->children_.end()) { + base_child = it->second.get(); + vm_total -= base_child->vm_total_; + file_total -= base_child->file_total_; + } + } + + if (vm_total != 0 || file_total != 0) { row->sorted_children.emplace_back(value.first); RollupRow& child_row = row->sorted_children.back(); - child_row.vmsize = value.second->vm_total_; - child_row.filesize = value.second->file_total_; + child_row.size.vm = vm_total; + child_row.size.file = file_total; + + // Preserve the old size for this label in the RollupRow output. + // If there is a diff base, the old sizes come from the size of the label + // in that base. Otherwise, the old size stays 0. + if (base_child) { + child_row.old_size.vm = base_child->vm_total_; + child_row.old_size.file = base_child->file_total_; + } } } @@ -524,14 +537,14 @@ for (auto& child : child_rows) { switch (options.sort_by()) { case Options::SORTBY_VMSIZE: - child.sortkey = std::abs(child.vmsize); + child.sortkey = std::abs(child.size.vm); break; case Options::SORTBY_FILESIZE: - child.sortkey = std::abs(child.filesize); + child.sortkey = std::abs(child.size.file); break; case Options::SORTBY_BOTH: child.sortkey = - std::max(std::abs(child.vmsize), std::abs(child.filesize)); + std::max(std::abs(child.size.vm), std::abs(child.size.file)); break; default: BLOATY_UNREACHABLE(); @@ -550,8 +563,8 @@ // out to "others_row". size_t i = child_rows.size() - 1; while (i >= options.max_rows_per_level()) { - CheckedAdd(&others_row.vmsize, child_rows[i].vmsize); - CheckedAdd(&others_row.filesize, child_rows[i].filesize); + CheckedAdd(&others_row.size.vm, child_rows[i].size.vm); + CheckedAdd(&others_row.size.file, child_rows[i].size.file); if (base) { auto it = base->children_.find(child_rows[i].name); if (it != base->children_.end()) { @@ -564,26 +577,26 @@ i--; } - if (std::abs(others_row.vmsize) > 0 || std::abs(others_row.filesize) > 0) { + if (std::abs(others_row.size.vm) > 0 || std::abs(others_row.size.file) > 0) { child_rows.push_back(others_row); - CheckedAdd(&others_rollup.vm_total_, others_row.vmsize); - CheckedAdd(&others_rollup.file_total_, others_row.filesize); + CheckedAdd(&others_rollup.vm_total_, others_row.size.vm); + CheckedAdd(&others_rollup.file_total_, others_row.size.file); } // Now sort by actual value (positive or negative). for (auto& child : child_rows) { switch (options.sort_by()) { case Options::SORTBY_VMSIZE: - child.sortkey = child.vmsize; + child.sortkey = child.size.vm; break; case Options::SORTBY_FILESIZE: - child.sortkey = child.filesize; + child.sortkey = child.size.file; break; case Options::SORTBY_BOTH: - if (std::abs(child.vmsize) > std::abs(child.filesize)) { - child.sortkey = child.vmsize; + if (std::abs(child.size.vm) > std::abs(child.size.file)) { + child.sortkey = child.size.vm; } else { - child.sortkey = child.filesize; + child.sortkey = child.size.file; } break; default: @@ -596,8 +609,8 @@ // For a non-diff, the percentage is compared to the total size of the parent. if (!base) { for (auto& child_row : child_rows) { - child_row.vmpercent = Percent(child_row.vmsize, row->vmsize); - child_row.filepercent = Percent(child_row.filesize, row->filesize); + child_row.vmpercent = Percent(child_row.size.vm, row->size.vm); + child_row.filepercent = Percent(child_row.size.file, row->size.file); } } @@ -633,7 +646,6 @@ } } - // RollupOutput //////////////////////////////////////////////////////////////// // RollupOutput represents rollup data after we have applied output massaging @@ -672,14 +684,14 @@ return ret; } -std::string DoubleStringPrintf(const char *fmt, double d) { +std::string DoubleStringPrintf(const char* fmt, double d) { char buf[1024]; snprintf(buf, sizeof(buf), fmt, d); return std::string(buf); } std::string SiPrint(int64_t size, bool force_sign) { - const char *prefixes[] = {"", "Ki", "Mi", "Gi", "Ti"}; + const char* prefixes[] = {"", "Ki", "Mi", "Gi", "Ti"}; size_t num_prefixes = 5; size_t n = 0; double size_d = size; @@ -742,14 +754,39 @@ } // namespace +void RollupOutput::Print(const OutputOptions& options, std::ostream* out) { + if (!source_names_.empty()) { + switch (options.output_format) { + case bloaty::OutputFormat::kPrettyPrint: + PrettyPrint(options, out); + break; + case bloaty::OutputFormat::kCSV: + PrintToCSV(out, /*tabs=*/false, options.showAllSizesCSV); + break; + case bloaty::OutputFormat::kTSV: + PrintToCSV(out, /*tabs=*/true, options.showAllSizesCSV); + break; + case bloaty::OutputFormat::kProtobuf: + PrintToProtobuf(out); + break; + default: + BLOATY_UNREACHABLE(); + } + } + + if (!disassembly_.empty()) { + *out << disassembly_; + } +} + void RollupOutput::PrettyPrintRow(const RollupRow& row, size_t indent, const OutputOptions& options, std::ostream* out) const { if (&row != &toplevel_row_) { // Avoid printing this row if it is only zero. // This can happen when using --domain if the row is zero for this domain. - if ((!ShowFile(options) && row.vmsize == 0) || - (!ShowVM(options) && row.filesize == 0)) { + if ((!ShowFile(options) && row.size.vm == 0) || + (!ShowVM(options) && row.size.file == 0)) { return; } } @@ -758,12 +795,12 @@ if (ShowFile(options)) { *out << PercentString(row.filepercent, diff_mode_) << " " - << SiPrint(row.filesize, diff_mode_) << " "; + << SiPrint(row.size.file, diff_mode_) << " "; } if (ShowVM(options)) { *out << PercentString(row.vmpercent, diff_mode_) << " " - << SiPrint(row.vmsize, diff_mode_) << " "; + << SiPrint(row.size.vm, diff_mode_) << " "; } *out << " " << row.name << "\n"; @@ -787,7 +824,7 @@ // Rows are printed before their sub-rows. PrettyPrintRow(row, indent, options, out); - if (!row.vmsize && !row.filesize) { + if (!row.size.vm && !row.size.file) { return; } @@ -833,11 +870,12 @@ uint64_t file_filtered = 0; uint64_t vm_filtered = 0; + uint64_t filtered = 0; if (ShowFile(options)) { - file_filtered = toplevel_row_.filtered_filesize; + filtered += toplevel_row_.filtered_size.file; } if (ShowVM(options)) { - vm_filtered = toplevel_row_.filtered_vmsize; + filtered += toplevel_row_.filtered_size.vm; } if (vm_filtered == 0 && file_filtered == 0) { @@ -855,19 +893,28 @@ *out << SiPrint(vm_filtered, /*force_sign=*/false); } - *out << " of entries\n"; + *out << " of entries\n"; } void RollupOutput::PrintRowToCSV(const RollupRow& row, std::vector<std::string> parent_labels, - std::ostream* out, bool tabs) const { + std::ostream* out, bool tabs, bool csvDiff) const { while (parent_labels.size() < source_names_.size()) { // If this label had no data at this level, append an empty string. parent_labels.push_back(""); } - parent_labels.push_back(std::to_string(row.vmsize)); - parent_labels.push_back(std::to_string(row.filesize)); + parent_labels.push_back(std::to_string(row.size.vm)); + parent_labels.push_back(std::to_string(row.size.file)); + + // If in diff where both old size are 0, get new size by adding diff size to + // old size. + if (csvDiff) { + parent_labels.push_back(std::to_string(row.old_size.vm)); + parent_labels.push_back(std::to_string(row.old_size.file)); + parent_labels.push_back(std::to_string(row.old_size.vm + (row.size.vm))); + parent_labels.push_back( + std::to_string(row.old_size.file + (row.size.file)));} std::string sep = tabs ? "\t" : ","; *out << absl::StrJoin(parent_labels, sep) << "\n"; @@ -875,7 +922,7 @@ void RollupOutput::PrintTreeToCSV(const RollupRow& row, std::vector<std::string> parent_labels, - std::ostream* out, bool tabs) const { + std::ostream* out, bool tabs, bool csvDiff) const { if (tabs) { parent_labels.push_back(row.name); } else { @@ -884,21 +931,28 @@ if (row.sorted_children.size() > 0) { for (const auto& child_row : row.sorted_children) { - PrintTreeToCSV(child_row, parent_labels, out, tabs); + PrintTreeToCSV(child_row, parent_labels, out, tabs, csvDiff); } } else { - PrintRowToCSV(row, parent_labels, out, tabs); + PrintRowToCSV(row, parent_labels, out, tabs, csvDiff); } } -void RollupOutput::PrintToCSV(std::ostream* out, bool tabs) const { +void RollupOutput::PrintToCSV(std::ostream* out, bool tabs, + bool csvDiff) const { std::vector<std::string> names(source_names_); names.push_back("vmsize"); names.push_back("filesize"); + if (csvDiff) { + names.push_back("original_vmsize"); + names.push_back("original_filesize"); + names.push_back("current_vmsize"); + names.push_back("current_filesize"); + } std::string sep = tabs ? "\t" : ","; *out << absl::StrJoin(names, sep) << "\n"; for (const auto& child_row : toplevel_row_.sorted_children) { - PrintTreeToCSV(child_row, std::vector<std::string>(), out, tabs); + PrintTreeToCSV(child_row, std::vector<std::string>(), out, tabs, csvDiff); } } @@ -907,18 +961,23 @@ constexpr uint64_t RangeSink::kUnknownSize; - // MmapInputFile /////////////////////////////////////////////////////////////// -#if !defined(_MSC_VER) +#if !defined(_WIN32) class MmapInputFile : public InputFile { public: - MmapInputFile(const std::string& filename); + MmapInputFile(string_view filename, string_view data); MmapInputFile(const MmapInputFile&) = delete; MmapInputFile& operator=(const MmapInputFile&) = delete; ~MmapInputFile() override; -}; + bool TryOpen(absl::string_view filename, + std::unique_ptr<InputFile>& file) override { + return DoTryOpen(filename, file); + } + static bool DoTryOpen(absl::string_view filename, + std::unique_ptr<InputFile>& file); +}; class FileDescriptor { public: @@ -936,28 +995,41 @@ int fd_; }; -MmapInputFile::MmapInputFile(const std::string& filename) - : InputFile(filename) { - FileDescriptor fd(open(filename.c_str(), O_RDONLY)); +bool MmapInputFile::DoTryOpen(absl::string_view filename, + std::unique_ptr<InputFile>& file) { + std::string str(filename); + FileDescriptor fd(open(str.c_str(), O_RDONLY)); struct stat buf; - const char *map; + const char* map; if (fd.fd() < 0) { - THROWF("couldn't open file '$0': $1", filename, strerror(errno)); + std::cerr << absl::Substitute("couldn't open file '$0': $1\n", filename, + strerror(errno)); + return false; } if (fstat(fd.fd(), &buf) < 0) { - THROWF("couldn't stat file '$0': $1", filename, strerror(errno)); + std::cerr << absl::Substitute("couldn't stat file '$0': $1\n", filename, + strerror(errno)); + return false; } map = static_cast<char*>( mmap(nullptr, buf.st_size, PROT_READ, MAP_SHARED, fd.fd(), 0)); if (map == MAP_FAILED) { - THROWF("couldn't mmap file '$0': $1", filename, strerror(errno)); + std::cerr << absl::Substitute("couldn't mmap file '$0': $1", filename, + strerror(errno)); + return false; } - data_ = string_view(map, buf.st_size); + file.reset(new MmapInputFile(filename, string_view(map, buf.st_size))); + return true; +} + +MmapInputFile::MmapInputFile(string_view filename, string_view data) + : InputFile(filename) { + data_ = data; } MmapInputFile::~MmapInputFile() { @@ -969,19 +1041,30 @@ std::unique_ptr<InputFile> MmapInputFileFactory::OpenFile( const std::string& filename) const { - return absl::make_unique<MmapInputFile>(filename); + std::unique_ptr<InputFile> ret; + if (!MmapInputFile::DoTryOpen(filename, ret)) { + THROW("Failed to open file."); + } + return ret; } -#else // !_MSC_VER +#else // !_WIN32 // MmapInputFile /////////////////////////////////////////////////////////////// class Win32MMapInputFile : public InputFile { public: - Win32MMapInputFile(const std::string& filename); + Win32MMapInputFile(string_view filename, string_view data); Win32MMapInputFile(const Win32MMapInputFile&) = delete; Win32MMapInputFile& operator=(const Win32MMapInputFile&) = delete; ~Win32MMapInputFile() override; + + bool TryOpen(absl::string_view filename, + std::unique_ptr<InputFile>& file) override { + return DoTryOpen(filename, file); + } + static bool DoTryOpen(absl::string_view filename, + std::unique_ptr<InputFile>& file); }; class Win32Handle { @@ -1001,34 +1084,49 @@ HANDLE h_; }; -Win32MMapInputFile::Win32MMapInputFile(const std::string& filename) +Win32MMapInputFile::Win32MMapInputFile(string_view filename, string_view data) : InputFile(filename) { - Win32Handle fd(::CreateFileA(filename.c_str(), FILE_GENERIC_READ, - FILE_SHARE_READ, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL)); + data_ = data; +} + +bool Win32MMapInputFile::DoTryOpen(absl::string_view filename, + std::unique_ptr<InputFile>& file) { + std::string str(filename); + Win32Handle fd(::CreateFileA(str.c_str(), FILE_GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + NULL)); LARGE_INTEGER li = {}; const char* map; if (fd.h() == INVALID_HANDLE_VALUE) { - THROWF("couldn't open file '$0': $1", filename, ::GetLastError()); + std::cerr << absl::Substitute("couldn't open file '$0': $1", filename, + ::GetLastError()); + return false; } if (!::GetFileSizeEx(fd.h(), &li)) { - THROWF("couldn't stat file '$0': $1", filename, ::GetLastError()); + std::cerr << absl::Substitute("couldn't stat file '$0': $1", filename, + ::GetLastError()); + return false; } Win32Handle mapfd( ::CreateFileMappingA(fd.h(), NULL, PAGE_READONLY, 0, 0, nullptr)); if (!mapfd.h()) { - THROWF("couldn't create file mapping '$0': $1", filename, ::GetLastError()); + std::cerr << absl::Substitute("couldn't create file mapping '$0': $1", + filename, ::GetLastError()); + return false; } map = static_cast<char*>(::MapViewOfFile(mapfd.h(), FILE_MAP_READ, 0, 0, 0)); if (!map) { - THROWF("couldn't MapViewOfFile file '$0': $1", filename, ::GetLastError()); + std::cerr << absl::Substitute("couldn't MapViewOfFile file '$0': $1", + filename, ::GetLastError()); + return false; } - data_ = string_view(map, li.QuadPart); + file.reset(new Win32MMapInputFile(filename, string_view(map, li.QuadPart))); + return true; } Win32MMapInputFile::~Win32MMapInputFile() { @@ -1040,18 +1138,25 @@ std::unique_ptr<InputFile> MmapInputFileFactory::OpenFile( const std::string& filename) const { - return absl::make_unique<Win32MMapInputFile>(filename); + std::unique_ptr<InputFile> ret; + if (!Win32MMapInputFile::DoTryOpen(filename, ret)) { + THROW("Failed to open file."); + } + return ret; } #endif // RangeSink /////////////////////////////////////////////////////////////////// -RangeSink::RangeSink(const InputFile *file, const Options &options, - DataSource data_source, const DualMap *translator, - google::protobuf::Arena *arena) - : file_(file), options_(options), data_source_(data_source), - translator_(translator), arena_(arena) {} +RangeSink::RangeSink(const InputFile* file, const Options& options, + DataSource data_source, const DualMap* translator, + google::protobuf::Arena* arena) + : file_(file), + options_(options), + data_source_(data_source), + translator_(translator), + arena_(arena) {} RangeSink::~RangeSink() {} @@ -1347,21 +1452,23 @@ } uint64_t mb = 1 << 20; // Limit for uncompressed size is 30x the compressed size + 128MB. - if (uncompressed_size > static_cast<uint64_t>(data.size()) * 30 + (128 * mb)) { + if (uncompressed_size > + static_cast<uint64_t>(data.size()) * 30 + (128 * mb)) { fprintf(stderr, "warning: ignoring compressed debug data, implausible uncompressed " "size (compressed: %zu, uncompressed: %" PRIu64 ")\n", data.size(), uncompressed_size); return absl::string_view(); } - unsigned char *dbuf = + unsigned char* dbuf = arena_->google::protobuf::Arena::CreateArray<unsigned char>( arena_, uncompressed_size); uLongf zliblen = uncompressed_size; - if (uncompress(dbuf, &zliblen, (unsigned char*)(data.data()), data.size()) != Z_OK) { + if (uncompress(dbuf, &zliblen, (unsigned char*)(data.data()), data.size()) != + Z_OK) { THROW("Error decompressing debug info"); } - string_view sv(reinterpret_cast<char *>(dbuf), zliblen); + string_view sv(reinterpret_cast<char*>(dbuf), zliblen); return sv; } @@ -1404,7 +1511,6 @@ const int max_; }; - // Bloaty ////////////////////////////////////////////////////////////////////// // Represents a program execution and associated state. @@ -1506,8 +1612,9 @@ std::unique_ptr<google::protobuf::Arena> arena_; }; -Bloaty::Bloaty(const InputFileFactory &factory, const Options &options) - : file_factory_(factory), options_(options), +Bloaty::Bloaty(const InputFileFactory& factory, const Options& options) + : file_factory_(factory), + options_(options), arena_(std::make_unique<google::protobuf::Arena>()) { AddBuiltInSources(data_sources, options); } @@ -1590,8 +1697,10 @@ auto iter = all_known_sources_.find(source.base_data_source()); if (iter == all_known_sources_.end()) { - THROWF("custom data source '$0': no such base source '$1'.\nTry --list-sources to see valid sources.", source.name(), - source.base_data_source()); + THROWF( + "custom data source '$0': no such base source '$1'.\nTry " + "--list-sources to see valid sources.", + source.name(), source.base_data_source()); } else if (!iter->second->munger->IsEmpty()) { THROWF("custom data source '$0' tries to depend on custom data source '$1'", source.name(), source.base_data_source()); @@ -1609,7 +1718,8 @@ source_names_.emplace_back(name); auto it = all_known_sources_.find(name); if (it == all_known_sources_.end()) { - THROWF("no such data source: $0.\nTry --list-sources to see valid sources.", name); + THROWF("no such data source: $0.\nTry --list-sources to see valid sources.", + name); } sources_.emplace_back(it->second.get()); @@ -1676,7 +1786,8 @@ return ret; } - void PrintMapRow(string_view str, uint64_t start, uint64_t end, int hex_digits) { + void PrintMapRow(string_view str, uint64_t start, uint64_t end, + int hex_digits) { printf("%.*" PRIx64 "-%.*" PRIx64 "\t %s\t\t%.*s\n", hex_digits, start, hex_digits, end, LeftPad(std::to_string(end - start), 10).c_str(), (int)str.size(), str.data()); @@ -1704,7 +1815,7 @@ std::vector<std::unique_ptr<DualMap>> maps_; }; -void Bloaty::ScanAndRollupFile(const std::string &filename, Rollup* rollup, +void Bloaty::ScanAndRollupFile(const std::string& filename, Rollup* rollup, std::vector<std::string>* out_build_ids) const { auto file = GetObjectFile(filename); @@ -1721,9 +1832,9 @@ sink_ptrs.push_back(sinks.back().get()); for (auto source : sources_) { - sinks.push_back(absl::make_unique<RangeSink>(&file->file_data(), options_, - source->effective_source, - maps.base_map(), arena_.get())); + sinks.push_back(absl::make_unique<RangeSink>( + &file->file_data(), options_, source->effective_source, maps.base_map(), + arena_.get())); sinks.back()->AddOutput(maps.AppendMap(), source->munger.get()); // We handle the kInputFiles data source internally, without handing it off // to the file format implementation. This seems slightly simpler, since @@ -1746,8 +1857,8 @@ } } - int64_t filesize_before = rollup->file_total() + - rollup->filtered_file_total(); + int64_t filesize_before = + rollup->file_total() + rollup->filtered_file_total(); file->ProcessFile(sink_ptrs); // kInputFile source: Copy the base map to the filename sink(s). @@ -1767,8 +1878,8 @@ maps.ComputeRollup(rollup); // The ObjectFile implementation must guarantee this. - int64_t filesize = rollup->file_total() + - rollup->filtered_file_total() - filesize_before; + int64_t filesize = + rollup->file_total() + rollup->filtered_file_total() - filesize_before; (void)filesize; assert(filesize == file->file_data().data().size()); @@ -1785,10 +1896,9 @@ } } -void Bloaty::ScanAndRollupFiles( - const std::vector<std::string>& filenames, - std::vector<std::string>* build_ids, - Rollup * rollup) const { +void Bloaty::ScanAndRollupFiles(const std::vector<std::string>& filenames, + std::vector<std::string>* build_ids, + Rollup* rollup) const { int num_cpus = std::thread::hardware_concurrency(); int num_threads = std::min(num_cpus, static_cast<int>(filenames.size())); @@ -1809,16 +1919,18 @@ for (int i = 0; i < num_threads; i++) { thread_data[i].rollup.SetFilterRegex(regex.get()); - threads[i] = std::thread([this, &index, &filenames](PerThreadData* data) { - try { - int j; - while (index.TryGetNext(&j)) { - ScanAndRollupFile(filenames[j], &data->rollup, &data->build_ids); - } - } catch (const bloaty::Error& e) { - index.Abort(e.what()); - } - }, &thread_data[i]); + threads[i] = std::thread( + [this, &index, &filenames](PerThreadData* data) { + try { + int j; + while (index.TryGetNext(&j)) { + ScanAndRollupFile(filenames[j], &data->rollup, &data->build_ids); + } + } catch (const bloaty::Error& e) { + index.Abort(e.what()); + } + }, + &thread_data[i]); } for (int i = 0; i < num_threads; i++) { @@ -1830,8 +1942,7 @@ rollup->Add(data->rollup); } - build_ids->insert(build_ids->end(), - data->build_ids.begin(), + build_ids->insert(build_ids->end(), data->build_ids.begin(), data->build_ids.end()); } @@ -1865,7 +1976,7 @@ base_filenames.push_back(file_info.filename_); } ScanAndRollupFiles(base_filenames, &build_ids, &base); - rollup.Subtract(base); + rollup.AddEntriesFrom(base); rollup.CreateDiffModeRollupOutput(&base, options, output); } else { rollup.CreateRollupOutput(options, output); @@ -1881,8 +1992,7 @@ std::string unused_debug; for (const auto& pair : debug_files_) { unused_debug += absl::Substitute( - "$0 $1\n", - absl::BytesToHexString(pair.first).c_str(), + "$0 $1\n", absl::BytesToHexString(pair.first).c_str(), pair.second.c_str()); } @@ -1896,9 +2006,8 @@ "$0 $1\n", absl::BytesToHexString(file_info.build_id_).c_str(), file_info.filename_.c_str()); } - THROWF( - "Debug file(s) did not match any input file:\n$0\nInput Files:\n$1", - unused_debug.c_str(), input_files.c_str()); + THROWF("Debug file(s) did not match any input file:\n$0\nInput Files:\n$1", + unused_debug.c_str(), input_files.c_str()); } } @@ -1992,9 +2101,7 @@ return ret; } - void ConsumeAndSaveArg() { - (*out_argv_)[(*out_argc_)++] = argv_[index_++]; - } + void ConsumeAndSaveArg() { (*out_argv_)[(*out_argc_)++] = argv_[index_++]; } // Singular flag like --csv or -v. bool TryParseFlag(string_view flag) { @@ -2193,6 +2300,7 @@ } } else { if (saw_separator) { + output_options->showAllSizesCSV = true; options->add_base_filename(std::string(args.ConsumeArg())); } else { options->add_filename(std::string(args.ConsumeArg()));
diff --git a/src/bloaty.h b/src/bloaty.h index d3e7949..482c9e6 100644 --- a/src/bloaty.h +++ b/src/bloaty.h
@@ -71,9 +71,11 @@ class InputFile { public: - InputFile(const std::string& filename) : filename_(filename) {} + InputFile(absl::string_view filename) : filename_(filename) {} InputFile(const InputFile&) = delete; InputFile& operator=(const InputFile&) = delete; + virtual bool TryOpen(absl::string_view filename, + std::unique_ptr<InputFile>& file) = 0; virtual ~InputFile() {} const std::string& filename() const { return filename_; } @@ -303,7 +305,11 @@ // Provided by dwarf.cc. To use these, a module should fill in a dwarf::File // and then call these functions. void ReadDWARFCompileUnits(const dwarf::File& file, const DualMap& map, - RangeSink* sink); + const dwarf::CU* skeleton, RangeSink* sink); +inline void ReadDWARFCompileUnits(const dwarf::File& file, const DualMap& map, + RangeSink* sink) { + return ReadDWARFCompileUnits(file, map, nullptr, sink); +} void ReadDWARFInlines(const dwarf::File& file, RangeSink* sink, bool include_line); void ReadEhFrame(absl::string_view contents, RangeSink* sink); @@ -356,18 +362,26 @@ class Rollup; +struct DomainSizes { + int64_t vm; + int64_t file; +}; + struct RollupRow { RollupRow(const std::string& name_) : name(name_) {} std::string name; - int64_t vmsize = 0; - int64_t filesize = 0; - int64_t filtered_vmsize = 0; - int64_t filtered_filesize = 0; + DomainSizes size = {0, 0}; + DomainSizes filtered_size = {0, 0}; + int64_t other_count = 0; int64_t sortkey; double vmpercent; double filepercent; + + // The size of the base in a diff mode. Otherwise stay 0. + DomainSizes old_size = {0, 0}; + std::vector<RollupRow> sorted_children; static bool Compare(const RollupRow& a, const RollupRow& b) { @@ -397,6 +411,7 @@ OutputFormat output_format = OutputFormat::kPrettyPrint; size_t max_label_len = 80; ShowDomain show = ShowDomain::kShowBoth; + bool showAllSizesCSV = false; }; struct RollupOutput { @@ -411,29 +426,7 @@ const std::vector<std::string>& source_names() const { return source_names_; } - void Print(const OutputOptions& options, std::ostream* out) { - if (!source_names_.empty()) { - switch (options.output_format) { - case bloaty::OutputFormat::kPrettyPrint: - PrettyPrint(options, out); - break; - case bloaty::OutputFormat::kCSV: - PrintToCSV(out, /*tabs=*/false); - break; - case bloaty::OutputFormat::kTSV: - PrintToCSV(out, /*tabs=*/true); - break; - case bloaty::OutputFormat::kProtobuf: - PrintToProtobuf(out); - break; - default: - BLOATY_UNREACHABLE(); - } - } - if (!disassembly_.empty()) { - *out << disassembly_; - } - } + void Print(const OutputOptions& options, std::ostream* out); void SetDisassembly(absl::string_view disassembly) { disassembly_ = std::string(disassembly); @@ -457,7 +450,7 @@ static bool IsSame(const std::string& a, const std::string& b); void PrettyPrint(const OutputOptions& options, std::ostream* out) const; - void PrintToCSV(std::ostream* out, bool tabs) const; + void PrintToCSV(std::ostream* out, bool tabs, bool csvDiff) const; void PrintToProtobuf(std::ostream* out) const; void PrettyPrintRow(const RollupRow& row, size_t indent, const OutputOptions& options, std::ostream* out) const; @@ -465,10 +458,10 @@ const OutputOptions& options, std::ostream* out) const; void PrintRowToCSV(const RollupRow& row, std::vector<std::string> parent_labels, - std::ostream* out, bool tabs) const; + std::ostream* out, bool tabs, bool csvDiff) const; void PrintTreeToCSV(const RollupRow& row, std::vector<std::string> parent_labels, - std::ostream* out, bool tabs) const; + std::ostream* out, bool tabs, bool csvDiff) const; }; // Shim for `std::filesystem::path(filename).stem()`.
diff --git a/src/dwarf.cc b/src/dwarf.cc index d062c55..5099a31 100644 --- a/src/dwarf.cc +++ b/src/dwarf.cc
@@ -573,6 +573,11 @@ sink->AddFileRange("dwarf_stmtlistrange", cu.unit_name(), data); } +struct DwoFilePointer { + std::string comp_dir; + std::string dwo_name; +}; + // The DWARF debug info can help us get compileunits info. DIEs for compilation // units, functions, and global variables often have attributes that will // resolve to addresses. @@ -588,13 +593,33 @@ while (iter.NextCU(reader, &cu)) { dwarf::DIEReader die_reader = cu.GetDIEReader(); GeneralDIE compileunit_die; + DwoFilePointer dwo_info; auto* abbrev = die_reader.ReadCode(cu); die_reader.ReadAttributes( cu, abbrev, - [&cu, &compileunit_die](uint16_t tag, dwarf::AttrValue value) { + [&](uint16_t tag, dwarf::AttrValue value) { ReadGeneralDIEAttr(tag, value, cu, &compileunit_die); + switch (tag) { + case DW_AT_comp_dir: + if (value.IsString()) { + dwo_info.comp_dir = value.GetString(cu); + } + break; + case DW_AT_GNU_dwo_name: + if (value.IsString()) { + dwo_info.dwo_name = value.GetString(cu); + } + break; + } }); + if (!dwo_info.comp_dir.empty() && !dwo_info.dwo_name.empty()) { + auto file = MmapInputFileFactory().OpenFile(dwo_info.comp_dir + "/" + dwo_info.dwo_name); + dwarf::File dwo_dwarf; + cu.dwarf().open(*file, &dwo_dwarf, sink); + ReadDWARFCompileUnits(dwo_dwarf, symbol_map, &cu, sink); + } + if (cu.unit_name().empty()) { continue; } @@ -628,7 +653,7 @@ } void ReadDWARFCompileUnits(const dwarf::File& file, const DualMap& symbol_map, - RangeSink* sink) { + const dwarf::CU* skeleton, RangeSink* sink) { if (!file.debug_info.size()) { THROW("missing debug info"); } @@ -638,7 +663,7 @@ } // Share a reader to avoid re-parsing debug abbreviations. - dwarf::InfoReader reader(file); + dwarf::InfoReader reader(file, skeleton); ReadDWARFDebugInfo(reader, dwarf::InfoReader::Section::kDebugInfo, symbol_map, sink);
diff --git a/src/dwarf/attr.cc b/src/dwarf/attr.cc index b794fd1..a540341 100644 --- a/src/dwarf/attr.cc +++ b/src/dwarf/attr.cc
@@ -26,6 +26,12 @@ namespace dwarf { absl::optional<uint64_t> AttrValue::ToUint(const CU& cu) const { + if (form_ == DW_FORM_implicit_const) { + // DW_FORM_implicit_const value is stored in AbbrevTable, but + // we don't keep it in AttrValue (discarded in AbbrevTable::ReadAbbrevs()). + // return absl::nullopt to make sure the value is not available. + return absl::nullopt; + } if (IsUint()) return GetUint(cu); string_view str = GetString(cu); switch (str.size()) { @@ -46,6 +52,10 @@ return ResolveIndirectAddress(cu); } else { assert(type_ == Type::kUint); + // DW_FORM_implicit_const value is stored in AbbrevTable, but + // we don't keep it in AttrValue (discarded in AbbrevTable::ReadAbbrevs()). + // Assertion makes sure that nobody is trying to read a fake value. + assert(form_ != DW_FORM_implicit_const); return uint_; } } @@ -102,7 +112,7 @@ return ReadIndirectAddress(cu, uint_); } -AttrValue AttrValue::ParseAttr(const CU& cu, uint8_t form, string_view* data) { +AttrValue AttrValue::ParseAttr(const CU& cu, uint16_t form, string_view* data) { switch (form) { case DW_FORM_indirect: { uint16_t indirect_form = ReadLEB128<uint16_t>(data); @@ -130,6 +140,7 @@ case DW_FORM_strx4: return AttrValue::UnresolvedString(form, ReadFixed<uint32_t>(data)); case DW_FORM_strx: + case DW_FORM_GNU_str_index: return AttrValue::UnresolvedString(form, ReadLEB128<uint64_t>(data)); case DW_FORM_addrx1: return AttrValue::UnresolvedUint(form, ReadFixed<uint8_t>(data)); @@ -140,6 +151,7 @@ case DW_FORM_addrx4: return AttrValue::UnresolvedUint(form, ReadFixed<uint32_t>(data)); case DW_FORM_addrx: + case DW_FORM_GNU_addr_index: return AttrValue::UnresolvedUint(form, ReadLEB128<uint64_t>(data)); case DW_FORM_addr: address_size: @@ -176,6 +188,7 @@ case DW_FORM_string: return AttrValue(form, ReadNullTerminated(data)); case DW_FORM_strp: + case DW_FORM_line_strp: if (cu.unit_sizes().dwarf64()) { return AttrValue(form, ReadIndirectString<uint64_t>(cu, data)); } else { @@ -201,6 +214,8 @@ return AttrValue(form, ReadFixed<uint8_t>(data)); case DW_FORM_sdata: return AttrValue(form, ReadLEB128<uint64_t>(data)); + case DW_FORM_implicit_const: + return AttrValue(form, 1); default: THROWF("Don't know how to parse DWARF form: $0", form); }
diff --git a/src/dwarf/attr.h b/src/dwarf/attr.h index 9261d26..a6964d2 100644 --- a/src/dwarf/attr.h +++ b/src/dwarf/attr.h
@@ -27,7 +27,7 @@ class AttrValue { public: - static AttrValue ParseAttr(const CU& cu, uint8_t form, absl::string_view* data); + static AttrValue ParseAttr(const CU& cu, uint16_t form, absl::string_view* data); AttrValue(const AttrValue &) = default; AttrValue &operator=(const AttrValue &) = default; @@ -45,7 +45,7 @@ // Attempts to coerce to uint, returning nullopt if this is not possible. absl::optional<uint64_t> ToUint(const CU& cu) const; - // REQUIRES: IsUint(). + // REQUIRES: IsUint() && form() != DW_FORM_implicit_const. uint64_t GetUint(const CU& cu) const; // REQUIRES: IsString().
diff --git a/src/dwarf/debug_info.cc b/src/dwarf/debug_info.cc index e7f488c..cd9ce16 100644 --- a/src/dwarf/debug_info.cc +++ b/src/dwarf/debug_info.cc
@@ -60,12 +60,15 @@ while (true) { Attribute attr; attr.name = ReadLEB128<uint16_t>(&data); - attr.form = ReadLEB128<uint8_t>(&data); + attr.form = ReadLEB128<uint16_t>(&data); if (attr.name == 0 && attr.form == 0) { break; // End of this abbrev } - + if (attr.form == DW_FORM_implicit_const) { + // We don't use the constant value, just discard it. + ReadLEB128<int64_t>(&data); + } abbrev.attr.push_back(attr); } } @@ -125,6 +128,7 @@ InfoReader::Section section, InfoReader& reader) { entire_unit_ = entire_unit; dwarf_ = &reader.dwarf_; + dwo_id_ = 0; unit_sizes_.ReadDWARFVersion(&data); if (unit_sizes_.dwarf_version() > 5) { @@ -179,6 +183,12 @@ data_ = data; ReadTopLevelDIE(reader); + + if (reader.skeleton_ && reader.skeleton_->dwo_id_ == dwo_id_) { + skeleton_ = reader.skeleton_; + } else { + skeleton_ = this; + } } // Read the root-level DIE in order to populate some member variables on which @@ -203,6 +213,7 @@ } break; case DW_AT_addr_base: + case DW_AT_GNU_addr_base: if (value.form() == DW_FORM_sec_offset) { addr_base_ = value.GetUint(*this); } @@ -217,6 +228,10 @@ range_lists_base_ = value.GetUint(*this); } break; + case DW_AT_GNU_dwo_id: + if (value.IsUint()) { + dwo_id_ = value.GetUint(*this); + } } });
diff --git a/src/dwarf/debug_info.h b/src/dwarf/debug_info.h index d1b0f79..a18b0b4 100644 --- a/src/dwarf/debug_info.h +++ b/src/dwarf/debug_info.h
@@ -59,8 +59,16 @@ #include "util.h" namespace bloaty { + +class InputFile; +class RangeSink; + namespace dwarf { +struct File; + +typedef void OpenDwarf(const InputFile &file, File *dwarf, RangeSink *sink); + struct File { absl::string_view debug_abbrev; absl::string_view debug_addr; @@ -75,6 +83,8 @@ absl::string_view debug_str; absl::string_view debug_str_offsets; absl::string_view debug_types; + const InputFile* file; + OpenDwarf* open; absl::string_view* GetFieldByName(absl::string_view name); void SetFieldByName(absl::string_view name, absl::string_view contents) { @@ -156,7 +166,7 @@ // In a DWARF abbreviation, each attribute has a name and a form. struct Attribute { uint16_t name; - uint8_t form; + uint16_t form; }; // The representation of a single abbreviation. @@ -198,6 +208,8 @@ class InfoReader { public: InfoReader(const File& file) : dwarf_(file) {} + InfoReader(const File& file, const CU* skeleton) + : dwarf_(file), skeleton_(skeleton) {} InfoReader(const InfoReader&) = delete; InfoReader& operator=(const InfoReader&) = delete; @@ -214,6 +226,7 @@ private: friend class CU; const File& dwarf_; + const CU* skeleton_ = nullptr; std::unordered_map<uint64_t, std::string> stmt_list_map_; @@ -243,6 +256,7 @@ DIEReader GetDIEReader(); const File& dwarf() const { return *dwarf_; } + const CU& skeleton() const { return *skeleton_; } const CompilationUnitSizes& unit_sizes() const { return unit_sizes_; } const std::string& unit_name() const { return unit_name_; } absl::string_view entire_unit() const { return entire_unit_; } @@ -285,6 +299,7 @@ // Only for skeleton and split CUs. uint8_t unit_type_; uint64_t dwo_id_; + const CU* skeleton_; // Only for .debug_types uint64_t unit_type_signature_; @@ -324,13 +339,14 @@ }; inline uint64_t ReadIndirectAddress(const CU& cu, uint64_t val) { - absl::string_view addrs = cu.dwarf().debug_addr; + absl::string_view addrs = cu.skeleton().dwarf().debug_addr; + uint64_t base = cu.skeleton().addr_base(); switch (cu.unit_sizes().address_size()) { case 4: - SkipBytes((val * 4) + cu.addr_base(), &addrs); + SkipBytes((val * 4) + base, &addrs); return ReadFixed<uint32_t>(&addrs); case 8: - SkipBytes((val * 8) + cu.addr_base(), &addrs); + SkipBytes((val * 8) + base, &addrs); return ReadFixed<uint64_t>(&addrs); default: BLOATY_UNREACHABLE();
diff --git a/src/elf.cc b/src/elf.cc index 67c8893..a8d7f42 100644 --- a/src/elf.cc +++ b/src/elf.cc
@@ -1192,10 +1192,12 @@ // reader directly on them. At the moment we don't attempt to make these // work with object files. -static void ReadDWARFSections(const InputFile &file, dwarf::File *dwarf, - RangeSink *sink) { +void ReadDWARFSections(const InputFile &file, dwarf::File *dwarf, + RangeSink *sink) { ElfFile elf(file.data()); assert(elf.IsOpen()); + dwarf->file = &file; + dwarf->open = &ReadDWARFSections; for (Elf64_Xword i = 1; i < elf.section_count(); i++) { ElfFile::Section section; elf.ReadSection(i, §ion); @@ -1229,6 +1231,12 @@ uncompressed_size = ReadBigEndian<uint64_t>(&contents); } + static constexpr string_view dwo_str(".dwo"); + if (name.size() >= dwo_str.size() && + name.rfind(".dwo") == name.size() - dwo_str.size()) { + name.remove_suffix(dwo_str.size()); + } + if (string_view* member = dwarf->GetFieldByName(name)) { if (uncompressed_size) { *member = sink->ZlibDecompress(contents, uncompressed_size);
diff --git a/src/macho.cc b/src/macho.cc index f399853..18a9474 100644 --- a/src/macho.cc +++ b/src/macho.cc
@@ -522,6 +522,8 @@ static void ReadDebugSectionsFromMachO(const InputFile &file, dwarf::File *dwarf, RangeSink *sink) { + dwarf->file = &file; + dwarf->open = &ReadDebugSectionsFromMachO; ForEachLoadCommand( file.data(), nullptr, [dwarf, sink](const LoadCommand &cmd) { switch (cmd.cmd) {
diff --git a/src/webassembly.cc b/src/webassembly.cc index 9d1ccc0..5e2ab65 100644 --- a/src/webassembly.cc +++ b/src/webassembly.cc
@@ -178,24 +178,31 @@ }); } -typedef std::unordered_map<int, std::string> FuncNames; +typedef std::unordered_map<int, std::string> IndexedNames; -void ReadFunctionNames(const Section& section, FuncNames* names, - RangeSink* sink) { +void ReadNames(const Section& section, IndexedNames* func_names, + IndexedNames* dataseg_names, RangeSink* sink) { enum class NameType { kModule = 0, kFunction = 1, kLocal = 2, + kLabel = 3, + kType = 4, + kTable = 5, + kMemory = 6, + kGlobal = 7, + kElemSegment = 8, + kDataSegment = 9 }; string_view data = section.contents; while (!data.empty()) { - char type = ReadVarUInt7(&data); + NameType type = static_cast<NameType>(ReadVarUInt7(&data)); uint32_t size = ReadVarUInt32(&data); string_view section = ReadPiece(size, &data); - if (static_cast<NameType>(type) == NameType::kFunction) { + if (type == NameType::kFunction || type == NameType::kDataSegment) { uint32_t count = ReadVarUInt32(§ion); for (uint32_t i = 0; i < count; i++) { string_view entry = section; @@ -204,6 +211,7 @@ string_view name = ReadPiece(name_len, §ion); entry = StrictSubstr(entry, 0, name.data() - entry.data() + name.size()); sink->AddFileRange("wasm_funcname", name, entry); + IndexedNames *names = (type == NameType::kFunction ? func_names : dataseg_names); (*names)[index] = std::string(name); } } @@ -276,7 +284,7 @@ return func_count; } -void ReadCodeSection(const Section& section, const FuncNames& names, +void ReadCodeSection(const Section& section, const IndexedNames& names, uint32_t num_imports, RangeSink* sink) { string_view data = section.contents; @@ -301,25 +309,63 @@ } } +void ReadDataSection(const Section& section, const IndexedNames& names, + RangeSink* sink) { + string_view data = section.contents; + uint32_t count = ReadVarUInt32(&data); + + for (uint32_t i = 0; i < count; i++) { + string_view segment = data; + uint8_t mode = ReadFixed<uint8_t>(&data); + if (mode > 1) THROW("multi-memory extension isn't supported"); + if (mode == 0) { // Active segment + // We will need to read the init expr. + // For the extended const proposal, read instructions until end is reached + // Otherwise, just read a constexpr inst (t.const or global.get) + // For now, we just need to support passive segments. + continue; + } + // else, a passive segment + + uint32_t segment_size = ReadVarUInt32(&data); + uint32_t total_size = segment_size + (data.data() - segment.data()); + + segment = StrictSubstr(segment, 0, total_size); + data = StrictSubstr(data, segment_size); + + auto iter = names.find(i); + if (iter == names.end()) { + std::string name = "data[" + std::to_string(i) + "]"; + sink->AddFileRange("wasm_data", name, segment); + } else { + sink->AddFileRange("wasm_data", iter->second, segment); + } + } +} + + void ParseSymbols(RangeSink* sink) { // First pass: read the custom naming section to get function names. std::unordered_map<int, std::string> func_names; + std::unordered_map<int, std::string> dataseg_names; uint32_t num_imports = 0; ForEachSection(sink->input_file().data(), - [&func_names, sink](const Section& section) { + [&func_names, &dataseg_names, sink](const Section& section) { if (section.name == "name") { - ReadFunctionNames(section, &func_names, sink); + ReadNames(section, &func_names, &dataseg_names, sink); } }); // Second pass: read the function/code sections. ForEachSection(sink->input_file().data(), - [&func_names, &num_imports, sink](const Section& section) { + [&func_names, &dataseg_names, &num_imports, sink](const Section& section) { if (section.id == Section::kImport) { num_imports = GetNumFunctionImports(section); } else if (section.id == Section::kCode) { ReadCodeSection(section, func_names, num_imports, sink); + } else if (section.id == Section::kData) { + ReadDataSection(section, dataseg_names, sink); } }); }
diff --git a/src/write_bloaty_report.cc b/src/write_bloaty_report.cc index a22fbad..593eff6 100644 --- a/src/write_bloaty_report.cc +++ b/src/write_bloaty_report.cc
@@ -30,14 +30,15 @@ google::protobuf::Arena arena; Report* report = google::protobuf::Arena::CreateMessage<Report>(&arena); - report->set_file_total(toplevel_row_.filesize); - report->set_vm_total(toplevel_row_.vmsize); + report->set_file_total(toplevel_row_.size.file); + report->set_vm_total(toplevel_row_.size.vm); ([&] { // If 2 levels, assume compileunits,symbols. // If 3 levels, assume accesspattern,compileunits,symbols; and pick the cold accesses. auto* compile_unit_row = &toplevel_row_; - if (toplevel_row_.sorted_children.front().sorted_children.front().sorted_children.size() > 0) { + if (toplevel_row_.sorted_children.front().sorted_children.size() > 0 && + toplevel_row_.sorted_children.front().sorted_children.front().sorted_children.size() > 0) { bool found_hot = false; for (const auto& access : toplevel_row_.sorted_children) { if (access.name == "Hot") { @@ -68,8 +69,8 @@ CompileUnit* compile_unit = report->add_compile_units(); for (const auto& symbol_row : child_row.sorted_children) { SizeInfo* info = google::protobuf::Arena::CreateMessage<SizeInfo>(&arena); - info->set_file_actual(symbol_row.filesize); - info->set_vm_actual(symbol_row.vmsize); + info->set_file_actual(symbol_row.size.file); + info->set_vm_actual(symbol_row.size.vm); Symbol* symbol = compile_unit->add_symbols(); symbol->set_allocated_sizes(info); auto decoded_symbol = DecodeSymbolWithCrateId(symbol_row.name); @@ -80,16 +81,16 @@ // Add a "fake symbol" that is the same as the compile unit, // to make sizes add up. SizeInfo* info = google::protobuf::Arena::CreateMessage<SizeInfo>(&arena); - info->set_file_actual(child_row.filesize); - info->set_vm_actual(child_row.vmsize); + info->set_file_actual(child_row.size.file); + info->set_vm_actual(child_row.size.vm); Symbol* symbol = compile_unit->add_symbols(); symbol->set_allocated_sizes(info); symbol->set_name(child_row.name); } SizeInfo* info = google::protobuf::Arena::CreateMessage<SizeInfo>(&arena); - info->set_file_actual(child_row.filesize); - info->set_vm_actual(child_row.vmsize); + info->set_file_actual(child_row.size.file); + info->set_vm_actual(child_row.size.vm); compile_unit->set_allocated_sizes(info); compile_unit->set_name(child_row.name); }
diff --git a/tests/bloaty_misc_test.cc b/tests/bloaty_misc_test.cc index 8bee15a..da794d8 100644 --- a/tests/bloaty_misc_test.cc +++ b/tests/bloaty_misc_test.cc
@@ -27,7 +27,7 @@ {"bloaty", "-d", "compileunits", "03-small-binary-that-crashed-inlines.bin"}); RunBloaty( {"bloaty", "-d", "inlines", "03-small-binary-that-crashed-inlines.bin"}); - EXPECT_EQ(top_row_->vmsize, 2340); + EXPECT_EQ(top_row_->size.vm, 2340); } TEST_F(BloatyTest, GoBinary) { @@ -37,9 +37,16 @@ {"bloaty", "-d", "inlines", "04-go-binary-with-ref-addr.bin"}); } +TEST_F(BloatyTest, ImplicitConstAndLineStrp) { + RunBloaty( + {"bloaty", "-d", "compileunits", "05-implicit-const-and-line-strp.bin"}); + RunBloaty( + {"bloaty", "-d", "inlines", "05-implicit-const-and-line-strp.bin"}); +} + TEST_F(BloatyTest, MultiThreaded) { RunBloaty({"bloaty", "02-section-count-overflow.o"}); - size_t file_size = top_row_->filesize; + size_t file_size = top_row_->size.file; // Bloaty doesn't know or care that you are passing the same file multiple // times. @@ -49,7 +56,7 @@ args.push_back("02-section-count-overflow.o"); } RunBloaty(args); // Heavily multithreaded test. - EXPECT_EQ(top_row_->filesize, file_size * 100); + EXPECT_EQ(top_row_->size.file, file_size * 100); } TEST(GetPathStem, Normal) {
diff --git a/tests/bloaty_test.cc b/tests/bloaty_test.cc index 8187aca..68eb9df 100644 --- a/tests/bloaty_test.cc +++ b/tests/bloaty_test.cc
@@ -21,20 +21,20 @@ // Empty .c file should result in a .o file with no vmsize. RunBloaty({"bloaty", file}); - EXPECT_EQ(top_row_->vmsize, 0); - EXPECT_EQ(top_row_->filesize, size); + EXPECT_EQ(top_row_->size.vm, 0); + EXPECT_EQ(top_row_->size.file, size); EXPECT_GT(top_row_->sorted_children.size(), 1); // Same with segments (we fake segments on .o files). RunBloaty({"bloaty", "-d", "segments", file}); - EXPECT_EQ(top_row_->vmsize, 0); - EXPECT_EQ(top_row_->filesize, size); + EXPECT_EQ(top_row_->size.vm, 0); + EXPECT_EQ(top_row_->size.file, size); EXPECT_GT(top_row_->sorted_children.size(), 1); // Same with symbols. RunBloaty({"bloaty", "-d", "symbols", file}); - EXPECT_EQ(top_row_->vmsize, 0); - EXPECT_EQ(top_row_->filesize, size); + EXPECT_EQ(top_row_->size.vm, 0); + EXPECT_EQ(top_row_->size.file, size); EXPECT_GT(top_row_->sorted_children.size(), 1); // We can't run any of these targets against object files. @@ -50,16 +50,16 @@ // Test "-n 0" which should return an unlimited number of rows. RunBloaty({"bloaty", "-n", "0", file}); - EXPECT_GT(top_row_->vmsize, 64); - EXPECT_LT(top_row_->vmsize, 300); - EXPECT_EQ(top_row_->filesize, size); + EXPECT_GT(top_row_->size.vm, 64); + EXPECT_LT(top_row_->size.vm, 300); + EXPECT_EQ(top_row_->size.file, size); EXPECT_GT(top_row_->sorted_children.size(), 1); // Same with segments (we fake segments on .o files). RunBloaty({"bloaty", "-d", "segments", file}); - EXPECT_GT(top_row_->vmsize, 64); - EXPECT_LT(top_row_->vmsize, 300); - EXPECT_EQ(top_row_->filesize, size); + EXPECT_GT(top_row_->size.vm, 64); + EXPECT_LT(top_row_->size.vm, 300); + EXPECT_EQ(top_row_->size.file, size); EXPECT_GT(top_row_->sorted_children.size(), 1); // For inputfiles we should get everything attributed to the input file. @@ -111,15 +111,15 @@ ASSERT_TRUE(GetFileSize(file, &size)); RunBloaty({"bloaty", file}); - EXPECT_GT(top_row_->vmsize, 8000); - EXPECT_LT(top_row_->vmsize, 12000); - //EXPECT_EQ(top_row_->filesize, size); + EXPECT_GT(top_row_->size.vm, 8000); + EXPECT_LT(top_row_->size.vm, 12000); + //EXPECT_EQ(top_row_->size.file, size); EXPECT_GT(top_row_->sorted_children.size(), 3); RunBloaty({"bloaty", "-d", "segments", file}); - EXPECT_GT(top_row_->vmsize, 8000); - EXPECT_LT(top_row_->vmsize, 12000); - //EXPECT_EQ(top_row_->filesize, size); + EXPECT_GT(top_row_->size.vm, 8000); + EXPECT_LT(top_row_->size.vm, 12000); + //EXPECT_EQ(top_row_->size.file, size); RunBloaty({"bloaty", "-d", "symbols", "-n", "40", "-s", "vm", file}); AssertChildren(*top_row_, { @@ -174,15 +174,15 @@ ASSERT_TRUE(GetFileSize(file, &size)); RunBloaty({"bloaty", file}); - EXPECT_GT(top_row_->vmsize, 8000); - EXPECT_LT(top_row_->vmsize, 12000); - EXPECT_EQ(top_row_->filesize, size); + EXPECT_GT(top_row_->size.vm, 8000); + EXPECT_LT(top_row_->size.vm, 12000); + EXPECT_EQ(top_row_->size.file, size); EXPECT_GT(top_row_->sorted_children.size(), 3); RunBloaty({"bloaty", "-d", "segments", file}); - EXPECT_GT(top_row_->vmsize, 8000); - EXPECT_LT(top_row_->vmsize, 12000); - EXPECT_EQ(top_row_->filesize, size); + EXPECT_GT(top_row_->size.vm, 8000); + EXPECT_LT(top_row_->size.vm, 12000); + EXPECT_EQ(top_row_->size.file, size); RunBloaty({"bloaty", "-d", "symbols", "-n", "50", file}); AssertChildren(*top_row_, { @@ -199,15 +199,15 @@ ASSERT_TRUE(GetFileSize(file, &size)); RunBloaty({"bloaty", file}); - EXPECT_GT(top_row_->vmsize, 8000); - EXPECT_LT(top_row_->vmsize, 12000); - EXPECT_EQ(top_row_->filesize, size); + EXPECT_GT(top_row_->size.vm, 8000); + EXPECT_LT(top_row_->size.vm, 12000); + EXPECT_EQ(top_row_->size.file, size); EXPECT_GT(top_row_->sorted_children.size(), 3); RunBloaty({"bloaty", "-d", "segments", file}); - EXPECT_GT(top_row_->vmsize, 8000); - EXPECT_LT(top_row_->vmsize, 12000); - EXPECT_EQ(top_row_->filesize, size); + EXPECT_GT(top_row_->size.vm, 8000); + EXPECT_LT(top_row_->size.vm, 12000); + EXPECT_EQ(top_row_->size.file, size); RunBloaty({"bloaty", "-d", "symbols", "-n", "50", "-s", "vm", file}); AssertChildren(*top_row_, {
diff --git a/tests/dwarf/debug_info/gnu-split-dwarf.test b/tests/dwarf/debug_info/gnu-split-dwarf.test new file mode 100644 index 0000000..bbe38dc --- /dev/null +++ b/tests/dwarf/debug_info/gnu-split-dwarf.test
@@ -0,0 +1,166 @@ +# Tests that we can find .dwo files based on the DW_AT_GNU_dwo_name attribute. +# Output of this sort will be produced by using -gsplit-dwarf with gcc or clang. + +# RUN: mkdir -p %/t +# RUN: cat %s | sed "s|__PWD__|%/t|" | %yaml2obj --docnum=1 -o %t.obj +# RUN: %yaml2obj %s --docnum=2 -o %/t/foo.dwo +# RUN: %yaml2obj %s --docnum=3 -o %/t/bar.dwo +# RUN: %bloaty %t.obj -d compileunits --raw-map --domain=vm | %FileCheck %s + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 + Entry: 0x1040 +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_X, PF_R ] + FirstSec: .text + LastSec: .text + VAddr: 0x400000 + Align: 0x1000 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x400000 + AddressAlign: 0x10 + Size: 0x1100 + - Name: .debug_addr + Type: SHT_PROGBITS + AddressAlign: 0x1 + Content: '00004000000000000001400000000000' +DWARF: + debug_str: + # We don't expect DW_AT_comp_dir to contain '.' in real life, + # but using it here lets our test data work from any filesystem + # location + - foo.dwo + - bar.dwo + - __PWD__ + debug_abbrev: + - ID: 0 + Table: + - Code: 0x1 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_comp_dir + Form: DW_FORM_strp + - Attribute: DW_AT_GNU_dwo_name + Form: DW_FORM_strp + - Attribute: DW_AT_GNU_dwo_id + Form: DW_FORM_data8 + - Attribute: DW_AT_GNU_addr_base + Form: DW_FORM_sec_offset + debug_info: + - Version: 4 + AbbrevTableID: 0 + AbbrOffset: 0x0 + AddrSize: 8 + Entries: + - AbbrCode: 0x1 + Values: + - Value: 0x10 + - Value: 0x0 + - Value: 0xdeaddeaddeadbeef + - Value: 0x0 + - Version: 4 + AbbrevTableID: 0 + AbbrOffset: 0x0 + AddrSize: 8 + Entries: + - AbbrCode: 0x1 + Values: + - Value: 0x10 + - Value: 0x8 + - Value: 0xbeefbeefbeefbeef + - Value: 0x8 +... + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 +DWARF: + debug_abbrev: + - ID: 0 + Table: + - Code: 0x1 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_GNU_dwo_id + Form: DW_FORM_data8 + - Attribute: DW_AT_low_pc + Form: DW_FORM_GNU_addr_index + - Attribute: DW_AT_high_pc + Form: DW_FORM_data4 + debug_str: + - foo.c + debug_info: + - Version: 4 + AbbrevTableID: 0 + AbbrOffset: 0x0 + AddrSize: 8 + Entries: + - AbbrCode: 0x1 + Values: + - Value: 0x0 + - Value: 0xdeaddeaddeadbeef + - Value: 0x0 + - Value: 0x100 + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 +DWARF: + debug_abbrev: + - ID: 0 + Table: + - Code: 0x1 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_GNU_dwo_id + Form: DW_FORM_data8 + - Attribute: DW_AT_low_pc + Form: DW_FORM_GNU_addr_index + - Attribute: DW_AT_high_pc + Form: DW_FORM_data4 + debug_str: + - bar.c + # XXX: In a real file this section would be named debug_info.dwo. + # But currently obj2yaml doesn't have any way of specifying a section name + # other than debug_info. This means we don't currently have testing of + # the code that strips .dwo from the end of section names. + debug_info: + - Version: 4 + AbbrevTableID: 0 + AbbrOffset: 0x0 + AddrSize: 8 + Entries: + - AbbrCode: 0x1 + Values: + - Value: 0x0 + - Value: 0xbeefbeefbeefbeef + - Value: 0x0 + - Value: 0x1000 + +... + +# CHECK: VM MAP: +# CHECK: 000000-400000 4194304 [-- Nothing mapped --] +# CHECK: 400000-400100 256 foo.c +# CHECK: 400100-401100 4096 bar.c
diff --git a/tests/fuzz_target.cc b/tests/fuzz_target.cc index 037b414..99e13f5 100644 --- a/tests/fuzz_target.cc +++ b/tests/fuzz_target.cc
@@ -28,6 +28,12 @@ : InputFile("fake_StringPieceInputFile_file") { data_ = data; } + + bool TryOpen(absl::string_view /* filename */, + std::unique_ptr<InputFile>& file) override { + file.reset(new StringPieceInputFile(data_)); + return true; + } }; class StringPieceInputFileFactory : public InputFileFactory {
diff --git a/tests/test.h b/tests/test.h index dc864f0..99517d7 100644 --- a/tests/test.h +++ b/tests/test.h
@@ -81,16 +81,16 @@ uint64_t vmtotal = 0; uint64_t filetotal = 0; for (const auto& child : row.sorted_children) { - vmtotal += child.vmsize; - filetotal += child.filesize; + vmtotal += child.size.vm; + filetotal += child.size.file; CheckConsistencyForRow(child, false, diff_mode, count); ASSERT_TRUE(names.insert(child.name).second); - ASSERT_FALSE(child.vmsize == 0 && child.filesize == 0); + ASSERT_FALSE(child.size.vm == 0 && child.size.file == 0); } if (!diff_mode) { - ASSERT_EQ(vmtotal, row.vmsize); - ASSERT_EQ(filetotal, row.filesize); + ASSERT_EQ(vmtotal, row.size.vm); + ASSERT_EQ(filetotal, row.size.file); } } else { // Count leaf rows. @@ -147,7 +147,7 @@ ASSERT_TRUE(GetFileSize(filename, &size)); total_input_size += size; } - ASSERT_EQ(top_row_->filesize, total_input_size); + ASSERT_EQ(top_row_->size.file, total_input_size); } int rows = 0; @@ -248,21 +248,21 @@ if (expected_vm == kUnknown) { // Always pass. } else if (expected_vm > 0) { - EXPECT_GE(child.vmsize, expected_vm); + EXPECT_GE(child.size.vm, expected_vm); // Allow some overhead. - EXPECT_LE(child.vmsize, (expected_vm * 1.1) + 100); + EXPECT_LE(child.size.vm, (expected_vm * 1.1) + 100); } else { ASSERT_TRUE(false); } if (expected_file == kSameAsVM) { - expected_file = child.vmsize; + expected_file = child.size.vm; } if (expected_file != kUnknown) { - EXPECT_GE(child.filesize, expected_file); + EXPECT_GE(child.size.file, expected_file); // Allow some overhead. - EXPECT_LE(child.filesize, (expected_file * 1.2) + 180); + EXPECT_LE(child.size.file, (expected_file * 1.2) + 180); } if (++i == children.size()) {
diff --git a/tests/testdata/make_all_msvc_test_files.bat b/tests/testdata/make_all_msvc_test_files.bat old mode 100644 new mode 100755
diff --git a/tests/testdata/misc/05-implicit-const-and-line-strp.bin b/tests/testdata/misc/05-implicit-const-and-line-strp.bin new file mode 100755 index 0000000..4f84668 --- /dev/null +++ b/tests/testdata/misc/05-implicit-const-and-line-strp.bin Binary files differ
diff --git a/tests/wasm/sections.test b/tests/wasm/sections.test new file mode 100644 index 0000000..eff9979 --- /dev/null +++ b/tests/wasm/sections.test
@@ -0,0 +1,221 @@ +# RUN: %yaml2obj %s -o %t.obj +# RUN: %bloaty --raw-map %t.obj | %FileCheck %s + +--- !WASM +FileHeader: + Version: 0x1 +Sections: + - Type: TYPE + Signatures: + - Index: 0 + ParamTypes: [] + ReturnTypes: + - I32 + - Index: 1 + ParamTypes: + - I32 + - I32 + ReturnTypes: + - I32 + - Type: IMPORT + Imports: + - Module: env + Field: __linear_memory + Kind: MEMORY + Memory: + Minimum: 0x1 + - Module: env + Field: __stack_pointer + Kind: GLOBAL + GlobalType: I32 + GlobalMutable: true + - Module: env + Field: __indirect_function_table + Kind: TABLE + Table: + Index: 0 + ElemType: FUNCREF + Limits: + Minimum: 0x1 + - Type: FUNCTION + FunctionTypes: [ 0, 0, 0, 1 ] + - Type: ELEM + Segments: + - Offset: + Opcode: I32_CONST + Value: 1 + Functions: [ 0 ] + - Type: DATACOUNT + Count: 4 + - Type: CODE + Relocations: + - Type: R_WASM_MEMORY_ADDR_LEB + Index: 1 + Offset: 0xD + - Type: R_WASM_MEMORY_ADDR_LEB + Index: 2 + Offset: 0x27 + - Type: R_WASM_MEMORY_ADDR_LEB + Index: 3 + Offset: 0x3D + - Type: R_WASM_MEMORY_ADDR_LEB + Index: 3 + Offset: 0x55 + - Type: R_WASM_MEMORY_ADDR_SLEB + Index: 4 + Offset: 0x5B + - Type: R_WASM_TABLE_INDEX_SLEB + Index: 0 + Offset: 0x6F + - Type: R_WASM_GLOBAL_INDEX_LEB + Index: 7 + Offset: 0x83 + - Type: R_WASM_GLOBAL_INDEX_LEB + Index: 7 + Offset: 0x98 + - Type: R_WASM_FUNCTION_INDEX_LEB + Index: 0 + Offset: 0xB4 + - Type: R_WASM_GLOBAL_INDEX_LEB + Index: 7 + Offset: 0xCC + - Type: R_WASM_FUNCTION_INDEX_LEB + Index: 6 + Offset: 0xDA + Functions: + - Index: 0 + Locals: + - Type: I32 + Count: 13 + Body: 41002100200028028880808000210141052102200120026A210341002104200428028C808080002105200320056A2106410021072007280280808080002108200820066A21094100210A200A200936028080808000418480808000210B200B210C200C0F0B + - Index: 1 + Locals: + - Type: I32 + Count: 2 + Body: 41818080800021002000210120010F0B + - Index: 2 + Locals: + - Type: I32 + Count: 8 + Body: 238080808000210041102101200020016B21022002248080808000410021032002200336020C41022104200220043602081080808080001A4103210541102106200220066A2107200724808080800020050F0B + - Index: 3 + Locals: + - Type: I32 + Count: 1 + Body: 108280808000210220020F0B + - Type: DATA + Segments: + - SectionOffset: 6 + InitFlags: 0 + Offset: + Opcode: I32_CONST + Value: 0 + Content: '00000000' + - SectionOffset: 15 + InitFlags: 0 + Offset: + Opcode: I32_CONST + Value: 4 + Content: '03000000' + - SectionOffset: 24 + InitFlags: 0 + Offset: + Opcode: I32_CONST + Value: 8 + Content: EFBEADDE + - SectionOffset: 33 + InitFlags: 0 + Offset: + Opcode: I32_CONST + Value: 12 + Content: '05000000' + - Type: CUSTOM + Name: linking + Version: 2 + SymbolTable: + - Index: 0 + Kind: FUNCTION + Name: func1 + Flags: [ ] + Function: 0 + - Index: 1 + Kind: DATA + Name: global3 + Flags: [ ] + Segment: 2 + Size: 4 + - Index: 2 + Kind: DATA + Name: global4 + Flags: [ ] + Segment: 3 + Size: 4 + - Index: 3 + Kind: DATA + Name: global1 + Flags: [ ] + Segment: 0 + Size: 4 + - Index: 4 + Kind: DATA + Name: global2 + Flags: [ ] + Segment: 1 + Size: 4 + - Index: 5 + Kind: FUNCTION + Name: func2 + Flags: [ ] + Function: 1 + - Index: 6 + Kind: FUNCTION + Name: __original_main + Flags: [ ] + Function: 2 + - Index: 7 + Kind: GLOBAL + Name: __stack_pointer + Flags: [ UNDEFINED ] + Global: 0 + - Index: 8 + Kind: FUNCTION + Name: main + Flags: [ ] + Function: 3 + SegmentInfo: + - Index: 0 + Name: .bss.global1 + Alignment: 2 + Flags: [ ] + - Index: 1 + Name: .data.global2 + Alignment: 2 + Flags: [ ] + - Index: 2 + Name: .data.global3 + Alignment: 2 + Flags: [ ] + - Index: 3 + Name: .data.global4 + Alignment: 2 + Flags: [ ] + - Type: CUSTOM + Name: producers + Tools: + - Name: clang + Version: '14.0.0 (https://github.com/llvm/llvm-project f71c553a30cc52c0b4a6abbaa82ce97c30c13979)' +... + +# CHECK: FILE MAP: +# CHECK: 000-008 8 [WASM Header] +# CHECK: 008-015 13 Type +# CHECK: 015-068 83 Import +# CHECK: 068-06f 7 Function +# CHECK: 06f-078 9 Element +# CHECK: 078-07b 3 DataCount +# CHECK: 07b-163 232 Code +# CHECK: 163-18a 39 Data +# CHECK: 18a-23f 181 linking +# CHECK: 23f-2b7 120 producers +# CHECK: 2b7-2f1 58 reloc.CODE +
diff --git a/tests/wasm/symbol_test.test b/tests/wasm/symbol_test.test new file mode 100644 index 0000000..60194f8 --- /dev/null +++ b/tests/wasm/symbol_test.test
@@ -0,0 +1,209 @@ +# RUN: %yaml2obj %s -o %t.obj +# RUN: %bloaty -d symbols --raw-map %t.obj | %FileCheck %s + +--- !WASM +FileHeader: + Version: 0x1 +Sections: + - Type: TYPE + Signatures: + - Index: 0 + ParamTypes: [] + ReturnTypes: [] + - Index: 1 + ParamTypes: + - I32 + ReturnTypes: [] + - Index: 2 + ParamTypes: [] + ReturnTypes: + - I32 + - Index: 3 + ParamTypes: + - I32 + - I32 + ReturnTypes: + - I32 + - Type: FUNCTION + FunctionTypes: [ 0, 1, 0, 2, 2, 2, 3 ] + - Type: TABLE + Tables: + - Index: 0 + ElemType: FUNCREF + Limits: + Flags: [ HAS_MAX ] + Minimum: 0x2 + Maximum: 0x2 + - Type: MEMORY + Memories: + - Flags: [ HAS_MAX, IS_SHARED ] + Minimum: 0x2 + Maximum: 0x2 + - Type: GLOBAL + Globals: + - Index: 0 + Type: I32 + Mutable: true + InitExpr: + Opcode: I32_CONST + Value: 66592 + - Index: 1 + Type: I32 + Mutable: true + InitExpr: + Opcode: I32_CONST + Value: 0 + - Index: 2 + Type: I32 + Mutable: false + InitExpr: + Opcode: I32_CONST + Value: 0 + - Index: 3 + Type: I32 + Mutable: false + InitExpr: + Opcode: I32_CONST + Value: 0 + - Type: EXPORT + Exports: + - Name: memory + Kind: MEMORY + Index: 0 + - Type: START + StartFunction: 2 + - Type: ELEM + Segments: + - Offset: + Opcode: I32_CONST + Value: 1 + Functions: [ 3 ] + - Type: DATACOUNT + Count: 3 + - Type: CODE + Functions: + - Index: 0 + Locals: [] + Body: 0B + - Index: 1 + Locals: [] + Body: 0B + - Index: 2 + Locals: [] + Body: 02400240024041900841004101FE4802000E020001020B41800841004104FC08000041840841004104FC08010041880841004104FC080200418C0841004104FC0B004190084102FE170200419008417FFE0002001A0C010B4190084101427FFE0102001A0BFC0900FC0901FC09020B + - Index: 3 + Locals: + - Type: I32 + Count: 13 + Body: 41002100200028028488808000210141052102200120026A2103410021042004280288888080002105200320056A210641002107200728028C888080002108200820066A21094100210A200A200936028C88808000418088808000210B200B210C200C0F0B + - Index: 4 + Locals: + - Type: I32 + Count: 2 + Body: 41818080800021002000210120010F0B + - Index: 5 + Locals: + - Type: I32 + Count: 8 + Body: 238080808000210041102101200020016B21022002248080808000410021032002200336020C41022104200220043602081083808080001A4103210541102106200220066A2107200724808080800020050F0B + - Index: 6 + Locals: + - Type: I32 + Count: 1 + Body: 108580808000210220020F0B + - Type: DATA + Segments: + - SectionOffset: 3 + InitFlags: 1 + Content: '03000000' + - SectionOffset: 9 + InitFlags: 1 + Content: EFBEADDE + - SectionOffset: 15 + InitFlags: 1 + Content: '05000000' + - Type: CUSTOM + Name: name + FunctionNames: + - Index: 0 + Name: __wasm_call_ctors + - Index: 1 + Name: __wasm_init_tls + - Index: 2 + Name: __wasm_init_memory + - Index: 3 + Name: func1 + - Index: 4 + Name: func2 + - Index: 5 + Name: __original_main + - Index: 6 + Name: main + GlobalNames: + - Index: 0 + Name: __stack_pointer + - Index: 1 + Name: __tls_base + - Index: 2 + Name: __tls_size + - Index: 3 + Name: __tls_align + DataSegmentNames: + - Index: 0 + Name: .data.global2 + - Index: 1 + Name: .data.global3 + - Index: 2 + Name: .data.global4 + - Type: CUSTOM + Name: producers + Languages: + - Name: C99 + Version: '' + Tools: + - Name: clang + Version: '14.0.0 (https://github.com/llvm/llvm-project f71c553a30cc52c0b4a6abbaa82ce97c30c13979)' + - Type: CUSTOM + Name: target_features + Features: + - Prefix: USED + Name: atomics + - Prefix: USED + Name: bulk-memory +... + +# Check output for function and passive data segments + +# Code section +# CHECK: 06b-06e 3 __wasm_call_ctors +# CHECK: 06e-071 3 __wasm_init_tls +# CHECK: 071-0e2 113 __wasm_init_memory +# CHECK: 0e2-14b 105 func1 +# CHECK: 14b-15f 20 func2 +# CHECK: 15f-1b6 87 __original_main +# CHECK: 1b6-1c6 16 main + +# Data section +# FIXME: This is wrong, should be the data section header +# BUG: 1c6-1c9 3 main +# CHECK: 1c9-1cf 6 .data.global2 +# CHECK: 1cf-1d5 6 .data.global3 +# CHECK: 1d5-1db 6 .data.global4 + +# Name section +# FIXME: Name section header and subsection header is 1db-1e6 +# BUG: 1db-1e6 11 .data.global4 +# Function names +# CHECK: 1e6-1f9 19 __wasm_call_ctors +# CHECK: 1f9-20a 17 __wasm_init_tls +# CHECK: 20a-21e 20 __wasm_init_memory +# CHECK: 21e-225 7 func1 +# CHECK: 225-22c 7 func2 +# CHECK: 22c-23d 17 __original_main +# FIXME: Subsection headers and global names +# BUG: 23d-243 6 main +# BUG: 243-27f 60 [section name] +# Data names +# CHECK: 27f-28e 15 .data.global2 +# CHECK: 28e-29d 15 .data.global3 +# CHECK: 29d-2ac 15 .data.global4 \ No newline at end of file