Merge remote-tracking branch 'haberman/master'
Change-Id: I84d71ec0817993e8040bf0be6b1bed8bc537e4f3
diff --git a/.gitignore b/.gitignore
index 2075731..67d9094 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,17 @@
/bloaty
!tests/testdata/**
*.dSYM
+.vscode
+.clangd
+.cache
+/build
+/CMakeFiles
+/Testing
+CMakeCache.txt
+CTestTestfile.cmake
+DartConfiguration.tcl
+Makefile
+bloaty_package.bloaty
+capstone.pc
+cmake_install.cmake
+compile_commands.json
diff --git a/.gitmodules b/.gitmodules
index 712299f..4d4ec30 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -16,6 +16,9 @@
[submodule "third_party/demumble"]
path = third_party/demumble
url = https://github.com/nico/demumble.git
+[submodule "third_party/rustc-demangle"]
+ path = third_party/rustc-demangle
+ url = https://github.com/alexcrichton/rustc-demangle.git
[submodule "third_party/zlib"]
path = third_party/zlib
url = https://github.com/madler/zlib
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4e73f60..41989ce 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,11 +1,12 @@
-cmake_minimum_required(VERSION 3.5)
+cmake_minimum_required(VERSION 3.9)
cmake_policy(SET CMP0048 NEW)
if(POLICY CMP0091)
cmake_policy(SET CMP0091 NEW)
endif()
project (Bloaty VERSION 1.1)
-include(CTest)
set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_GENERATOR "Ninja")
+include(CTest)
set_property(GLOBAL PROPERTY USE_FOLDERS ON) # Group projects in visual studio
# Options we define for users.
@@ -15,6 +16,18 @@
option(BLOATY_ENABLE_BUILDID "Enable build id." ON)
option(BLOATY_ENABLE_RE2 "Enable the support for regular expression functions." ON)
+if(APPLE)
+# When building bloaty on macOS infra builders,
+# need to add the libc++.a from the CIPD toolchain, otherwise there are linker errors
+# e.g. https://ci.chromium.org/p/fuchsia/builders/try/bloaty-x64-mac/b8874842054253378656?
+#
+# To achieve this, find the libc++.a location from the location of the clang binary.
+# CMAKE_CXX_COMPILER
+get_filename_component(CLANG_CIPD_TOOLCHAIN_DIR ${CMAKE_CXX_COMPILER} DIRECTORY CACHE)
+set(CMAKE_EXE_LINKER_FLAGS
+ "${CMAKE_EXE_LINKER_FLAGS} ${CLANG_CIPD_TOOLCHAIN_DIR}/../lib/libc++.a")
+endif(APPLE)
+
if(UNIX)
find_package(PkgConfig)
find_package(ZLIB)
@@ -22,7 +35,9 @@
pkg_search_module(RE2 re2)
endif()
pkg_search_module(CAPSTONE capstone)
-pkg_search_module(PROTOBUF protobuf)
+# Always use bundled protobuf, to accommodate building for other systems
+# without protobuf installed.
+# pkg_search_module(PROTOBUF protobuf)
if(BLOATY_ENABLE_RE2)
if(RE2_FOUND)
MESSAGE(STATUS "System re2 found, using")
@@ -130,6 +145,7 @@
set(CAPSTONE_BUILD_SHARED OFF CACHE BOOL "Build shared library" FORCE)
set(CAPSTONE_BUILD_TESTS OFF CACHE BOOL "Build tests" FORCE)
+
add_subdirectory(third_party/capstone)
include_directories(third_party/capstone/include)
set_property(TARGET capstone-static PROPERTY FOLDER "third_party")
@@ -155,19 +171,29 @@
include_directories(.)
include_directories(src)
include_directories(third_party/abseil-cpp)
+include_directories(third_party/rustc-demangle/crates/capi/include)
include_directories("${CMAKE_CURRENT_BINARY_DIR}/src")
# Baseline build flags.
if(MSVC)
set(CMAKE_CXX_FLAGS "/EHsc /wd4018 /D_CRT_SECURE_NO_WARNINGS /DNOMINMAX")
else()
- set(CMAKE_CXX_FLAGS "-W -Wall -Wno-sign-compare")
+ set(CMAKE_CXX_FLAGS "-std=c++17 -W -Wall -Wno-sign-compare")
set(CMAKE_CXX_FLAGS_DEBUG "-g1")
- set(CMAKE_CXX_FLAGS_RELEASE "-O2")
+ set(CMAKE_CXX_FLAGS_RELEASE "-O3")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g1")
set_source_files_properties(third_party/demumble/third_party/libcxxabi/cxa_demangle.cpp PROPERTIES COMPILE_FLAGS -Wno-implicit-fallthrough)
endif()
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fexceptions -ffunction-sections -fdata-sections")
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions -ffunction-sections -fdata-sections")
+
+if (APPLE)
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip")
+else()
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
+endif()
+
if(APPLE)
elseif(UNIX)
if(BLOATY_ENABLE_BUILDID)
@@ -206,6 +232,13 @@
--cpp_out=${CMAKE_CURRENT_BINARY_DIR}/src
-I${CMAKE_CURRENT_SOURCE_DIR}/src
)
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/src/report.pb.cc
+ DEPENDS protoc ${CMAKE_CURRENT_SOURCE_DIR}/src/report.proto
+ COMMAND protoc ${CMAKE_CURRENT_SOURCE_DIR}/src/report.proto
+ --cpp_out=${CMAKE_CURRENT_BINARY_DIR}/src
+ -I${CMAKE_CURRENT_SOURCE_DIR}/src
+)
else()
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/src/bloaty.pb.cc
@@ -213,6 +246,12 @@
--cpp_out=${CMAKE_CURRENT_BINARY_DIR}/src
-I${CMAKE_CURRENT_SOURCE_DIR}/src
)
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/src/report.pb.cc
+ COMMAND protoc ${CMAKE_CURRENT_SOURCE_DIR}/src/report.proto
+ --cpp_out=${CMAKE_CURRENT_BINARY_DIR}/src
+ -I${CMAKE_CURRENT_SOURCE_DIR}/src
+)
endif()
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/bloaty_package.bloaty
@@ -223,6 +262,7 @@
src/bloaty.h
src/disassemble.cc
${CMAKE_CURRENT_BINARY_DIR}/src/bloaty.pb.cc
+ ${CMAKE_CURRENT_BINARY_DIR}/src/report.pb.cc
src/dwarf/attr.h
src/dwarf/attr.cc
src/dwarf/dwarf_util.cc
@@ -232,6 +272,7 @@
src/dwarf_constants.h
src/eh_frame.cc
src/elf.cc
+ src/link_map.cc
src/macho.cc
src/pe.cc
third_party/lief_pe/pe_structures.h
@@ -241,6 +282,7 @@
src/util.cc
src/util.h
src/webassembly.cc
+ src/write_bloaty_report.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
@@ -266,6 +308,53 @@
)
set_property(TARGET libbloaty PROPERTY FOLDER "bloaty")
+# Teach CMake how to build rustc-demangle
+# RUST_TOOLCHAIN_PREFIX is the directory containing the `cargo` executable.
+if(NOT RUST_TOOLCHAIN_PREFIX)
+ find_program(CARGO_PATH cargo)
+ if(CARGO_PATH)
+ message(STATUS "Found cargo: ${CARGO_PATH}")
+ else()
+ set(HAVE_GMSH NO)
+ message(FATAL_ERROR "cargo not found. Please specify RUST_TOOLCHAIN_PREFIX")
+ endif()
+else()
+ set(CARGO_PATH ${RUST_TOOLCHAIN_PREFIX}/cargo CACHE FILEPATH "Path to the cargo executable")
+endif()
+file(
+ GLOB_RECURSE
+ RUSTC_DEMANGLE_SRC_FILES
+ "${CMAKE_CURRENT_SOURCE_DIR}/third_party/rustc-demangle/*.rs"
+ "${CMAKE_CURRENT_SOURCE_DIR}/third_party/rustc-demangle/*.c"
+ "${CMAKE_CURRENT_SOURCE_DIR}/third_party/rustc-demangle/*.h"
+ "${CMAKE_CURRENT_SOURCE_DIR}/third_party/rustc-demangle/*.toml"
+ )
+if(NOT APPLE)
+ # Rust uses libunwind, and embeds the libunwind runtime inside any static
+ # library produced from the build.
+ # Bloaty uses C++ exceptions to trigger failure. Use of C++ exceptions also
+ # requires libunwind, but specifically the version coming with GCC/Clang.
+ # Their interaction causes strange failures when compiled using GCC 9.3.
+ # To fix this, take out the unwind-related members from the Rust static lib.
+ set(RUSTC_DEMANGLE_FIXUP_ARCHIVE ar d target/release/librustc_demangle.a Unwind-EHABI.o Unwind-seh.o Unwind-sjlj.o UnwindLevel1-gcc-ext.o UnwindLevel1.o UnwindRegistersRestore.o UnwindRegistersSave.o libunwind.o)
+else()
+ # On macOS, there doesn't appear to be such problem.
+ set(RUSTC_DEMANGLE_FIXUP_ARCHIVE "")
+endif()
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/third_party/rustc-demangle/target/release/librustc_demangle.a
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/third_party/rustc-demangle
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/rustc-demangle/Cargo.toml
+ DEPENDS ${RUSTC_DEMANGLE_SRC_FILES}
+ COMMAND ${CARGO_PATH} build -p rustc-demangle-capi --release
+ COMMAND ${RUSTC_DEMANGLE_FIXUP_ARCHIVE}
+)
+add_custom_target(
+ rustc-demangle-lib
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/rustc-demangle/target/release/librustc_demangle.a
+)
+add_dependencies(libbloaty rustc-demangle-lib)
+
if(UNIX)
set(LIBBLOATY_LIBS libbloaty)
if(PROTOBUF_FOUND)
@@ -290,8 +379,11 @@
else()
list(APPEND LIBBLOATY_LIBS zlibstatic)
endif()
+
+ set(LIBBLOATY_LIBS ${LIBBLOATY_LIBS} "${CMAKE_CURRENT_SOURCE_DIR}/third_party/rustc-demangle/target/release/librustc_demangle.a" dl)
+
else()
- set(LIBBLOATY_LIBS libbloaty libprotoc capstone-static)
+ set(LIBBLOATY_LIBS libbloaty libprotoc capstone-static "${CMAKE_CURRENT_SOURCE_DIR}/third_party/rustc-demangle/target/release/librustc_demangle.a" dl)
if(BLOATY_ENABLE_RE2)
list(APPEND LIBBLOATY_LIBS re2)
endif()
@@ -319,9 +411,29 @@
add_executable(fuzz_target tests/fuzz_target.cc)
target_link_libraries(fuzz_target ${LIBBLOATY_LIBS} $ENV{LIB_FUZZING_ENGINE})
else()
+ include(CheckIPOSupported)
+ check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
+
add_executable(bloaty src/main.cc)
target_link_libraries(bloaty ${LIBBLOATY_LIBS})
+ if(ipo_supported)
+ message(STATUS "IPO / LTO enabled")
+ set_property(TARGET bloaty PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
+ else()
+ message(STATUS "IPO / LTO not supported: <${ipo_error}>")
+ endif()
+
+ # All of this is to add -pthread, which is required by re2 (not us).
+ find_package(Threads REQUIRED)
+ if(THREADS_HAVE_PTHREAD_ARG)
+ set_property(TARGET bloaty PROPERTY COMPILE_OPTIONS "-pthread")
+ set_property(TARGET bloaty PROPERTY INTERFACE_COMPILE_OPTIONS "-pthread")
+ endif()
+ if(CMAKE_THREAD_LIBS_INIT)
+ target_link_libraries(bloaty "${CMAKE_THREAD_LIBS_INIT}")
+ endif()
+
set_property(TARGET bloaty PROPERTY FOLDER "bloaty")
if(BLOATY_ENABLE_CMAKETARGETS)
@@ -368,8 +480,10 @@
set(TEST_TARGETS
bloaty_test
+ bloaty_report_test
bloaty_test_pe
bloaty_misc_test
+ link_map_test
range_map_test
)
@@ -389,9 +503,11 @@
file(GLOB fuzz_corpus tests/testdata/fuzz_corpus/*)
+ add_test(NAME link_map_test COMMAND link_map_test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata/link_map)
add_test(NAME range_map_test COMMAND range_map_test)
add_test(NAME bloaty_test_x86-64 COMMAND bloaty_test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata/linux-x86_64)
add_test(NAME bloaty_test_x86 COMMAND bloaty_test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata/linux-x86)
+ add_test(NAME bloaty_report_test_x86-64 COMMAND bloaty_report_test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata/linux-x86_64)
add_test(NAME bloaty_test_pe_x64 COMMAND bloaty_test_pe WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata/PE/x64)
add_test(NAME bloaty_test_pe_x86 COMMAND bloaty_test_pe WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata/PE/x86)
add_test(NAME bloaty_misc_test COMMAND bloaty_misc_test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata/misc)
diff --git a/build_all.sh b/build_all.sh
new file mode 100755
index 0000000..7321781
--- /dev/null
+++ b/build_all.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+set -xeuf -o pipefail
+
+pushd "${0%/*}"
+
+rm -rf build
+# Mimic the LUCI environment defined in
+# https://fuchsia.googlesource.com/infra/recipes/+/refs/heads/master/recipes/bloaty.py#59
+export CC=`which clang`
+export CXX=`which clang++`
+# On LUCI, we should set the `RUST_TOOLCHAIN_PREFIX` variable.
+# For local builds, cmake will try to detect its location automatically.
+cmake . -DCMAKE_GENERATOR=Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXE_LINKER_FLAGS='-static-libstdc++ -ldl -lpthread' -Bbuild
+ninja -C build
+ninja -C build test
+
+popd
diff --git a/src/bloaty.cc b/src/bloaty.cc
index 889005d..f178b96 100644
--- a/src/bloaty.cc
+++ b/src/bloaty.cc
@@ -22,12 +22,14 @@
#include <atomic>
#include <cmath>
+#include <filesystem>
#include <fstream>
#include <iostream>
#include <limits>
#include <map>
#include <memory>
#include <mutex>
+#include <regex>
#include <sstream>
#include <string>
#include <thread>
@@ -61,8 +63,9 @@
#include "bloaty.h"
#include "bloaty.pb.h"
+#include "demangle.h"
+#include "rustc_demangle.h"
#include "re.h"
-#include "util.h"
using absl::string_view;
@@ -82,6 +85,8 @@
constexpr DataSourceDefinition data_sources[] = {
{DataSource::kArchiveMembers, "armembers", "the .o files in a .a file"},
+ {DataSource::kAccessPattern, "accesspattern", "which regions in the file"
+ "is accessed at run-time"},
{DataSource::kCompileUnits, "compileunits",
"source file for the .o file (translation unit). requires debug info."},
{DataSource::kInputFiles, "inputfiles",
@@ -163,6 +168,23 @@
}
}
+namespace {
+
+std::string DemangleRustSymbol(std::string_view mangled) {
+ constexpr size_t kBufferSize = 8192;
+ std::unique_ptr<std::array<char, kBufferSize>> buffer =
+ std::make_unique<std::array<char, kBufferSize>>();
+ int result = rustc_demangle(mangled.data(), buffer->data(), kBufferSize);
+ if (result == 1) {
+ return std::string(buffer->data());
+ } else {
+ return "";
+ }
+}
+
+} // namespace
+
+
extern "C" char* __cxa_demangle(const char* mangled_name, char* buf, size_t* n,
int* status);
@@ -178,12 +200,48 @@
demangle_from.remove_prefix(1);
}
+ if (absl::StartsWith(demangle_from, "_R")) {
+ // Demangle Rust symbols
+ std::string ret = DemangleRustSymbol(demangle_from);
+ if (!ret.empty()) {
+ return ret;
+ }
+ }
+
+ if (absl::StartsWith(demangle_from, "switch.table._R")) {
+ // Demangle Rust symbols for switch tables
+ demangle_from.remove_prefix(13);
+ std::string ret = DemangleRustSymbol(demangle_from);
+ if (!ret.empty()) {
+ return "switch.table." + ret;
+ }
+ }
+
+ if (absl::StartsWith(demangle_from, ".Lswitch.table._R")) {
+ // Demangle Rust symbols for switch tables, with ".L" prefix.
+ demangle_from.remove_prefix(15);
+ std::string ret = DemangleRustSymbol(demangle_from);
+ if (!ret.empty()) {
+ return "switch.table." + ret;
+ }
+ }
+
if (source == DataSource::kShortSymbols) {
- char demangled[1024];
+ char demangled[4096];
if (absl::debugging_internal::Demangle(demangle_from.data(), demangled,
sizeof(demangled))) {
return std::string(demangled);
} else {
+ // TODO(yifeit): Certain symbols have dots (".") in them. Those are not allowed.
+ // Find and remove the last "." and anything after.
+ auto pos = demangle_from.find(".");
+ if (pos != absl::string_view::npos) {
+ demangle_from.remove_suffix(demangle_from.length() - pos);
+ std::string shortened(demangle_from);
+ if (absl::debugging_internal::Demangle(shortened.c_str(), demangled, sizeof(demangled))) {
+ return std::string(demangled);
+ }
+ }
return std::string(symbol);
}
} else if (source == DataSource::kFullSymbols) {
@@ -194,6 +252,20 @@
free(demangled);
return ret;
} else {
+ // TODO(yifeit): Certain symbols have dots (".") in them. Those are not allowed.
+ // Find and remove the last "." and anything after.
+ auto pos = demangle_from.find(".");
+ if (pos != absl::string_view::npos) {
+ demangle_from.remove_suffix(demangle_from.length() - pos);
+ std::string shortened(demangle_from);
+ char* demangled =
+ __cxa_demangle(shortened.c_str(), NULL, NULL, NULL);
+ if (demangled) {
+ std::string ret(demangled);
+ free(demangled);
+ return ret;
+ }
+ }
return std::string(symbol);
}
} else {
@@ -670,28 +742,6 @@
} // 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);
- break;
- case bloaty::OutputFormat::kTSV:
- PrintToCSV(out, /*tabs=*/true);
- 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 {
@@ -852,6 +902,7 @@
}
}
+
// RangeMap ////////////////////////////////////////////////////////////////////
constexpr uint64_t RangeSink::kUnknownSize;
@@ -1379,6 +1430,8 @@
void AddFilename(const std::string& filename, bool base_file);
void AddDebugFilename(const std::string& filename);
+ void AddLinkMapFilename(const std::string& filename);
+ void SetColdBytesFilter(const std::string& frequencies);
size_t GetSourceCount() const { return sources_.size(); }
@@ -1447,6 +1500,8 @@
std::vector<InputFileInfo> base_files_;
std::map<std::string, std::string> debug_files_;
+ // "foo" -> "some/path/foo.map"
+ std::map<std::string, std::string> link_map_files_;
// For allocating memory, like to decompress compressed sections.
std::unique_ptr<google::protobuf::Arena> arena_;
};
@@ -1457,10 +1512,27 @@
AddBuiltInSources(data_sources, options);
}
+std::string GetPathStem(const std::string& filename) {
+ std::regex filename_regex(R"([^\\\/]+(?=\.[\w]+$)|[^\\\/]+$)");
+ std::smatch m;
+ if (!std::regex_search(filename, m, filename_regex)) {
+ THROWF("Could not extract stem from $0", filename);
+ }
+ std::string stem = m.str(0);
+ return stem;
+}
+
std::unique_ptr<ObjectFile> Bloaty::GetObjectFile(
const std::string& filename) const {
std::unique_ptr<InputFile> file(file_factory_.OpenFile(filename));
- auto object_file = TryOpenELFFile(file);
+
+ std::string stem = GetPathStem(filename);
+ std::optional<std::string> link_map_file;
+ if (link_map_files_.find(stem) != link_map_files_.end()) {
+ link_map_file = link_map_files_.at(stem);
+ }
+
+ auto object_file = TryOpenELFFile(file, link_map_file);
if (!object_file.get()) {
object_file = TryOpenMachOFile(file);
@@ -1502,6 +1574,11 @@
debug_files_[build_id] = filename;
}
+void Bloaty::AddLinkMapFilename(const std::string& filename) {
+ std::string stem = GetPathStem(filename);
+ link_map_files_[stem] = filename;
+}
+
void Bloaty::DefineCustomDataSource(const CustomDataSource& source) {
if (source.base_data_source() == "symbols") {
THROW(
@@ -1851,6 +1928,10 @@
-c FILE Load configuration from <file>.
-d SOURCE,SOURCE Comma-separated list of sources to scan.
--debug-file=FILE Use this file for debug symbols and/or symbol table.
+ --link-map-file=FILE
+ Use this file for identifying a link map associated with
+ a binary. The link map and the binary must share the same
+ base name (e.g. `foo.map` and `foo`)
-C MODE How to demangle symbols. Possible values are:
--demangle=MODE --demangle=none no demangling, print raw symbols
--demangle=short demangle, but omit arg/return types
@@ -2006,6 +2087,8 @@
output_options->output_format = OutputFormat::kCSV;
} else if (args.TryParseFlag("--tsv")) {
output_options->output_format = OutputFormat::kTSV;
+ } else if (args.TryParseFlag("--pb")) {
+ output_options->output_format = OutputFormat::kProtobuf;
} else if (args.TryParseFlag("--raw-map")) {
options->set_dump_raw_map(true);
} else if (args.TryParseOption("-c", &option)) {
@@ -2035,6 +2118,12 @@
}
} else if (args.TryParseOption("--debug-file", &option)) {
options->add_debug_filename(std::string(option));
+ } else if (args.TryParseOption("--link-map-file", &option)) {
+ options->add_link_map_filename(std::string(option));
+ } else if (args.TryParseOption("--cold-bytes-filter", &option)) {
+ options->set_cold_bytes_filter(std::string(option));
+ } else if (args.TryParseUint64Option("--access-pattern-frame-size", &uint64_option)) {
+ options->set_access_pattern_frame_size(uint64_option);
} else if (args.TryParseUint64Option("--debug-fileoff", &uint64_option)) {
if (options->has_debug_fileoff()) {
THROW("currently we only support a single debug fileoff");
@@ -2132,6 +2221,20 @@
}
}
+ if (output_options->output_format == OutputFormat::kProtobuf) {
+ if ((options->data_source_size() != 2 ||
+ options->data_source()[0] != "compileunits" ||
+ options->data_source()[1] != "symbols") &&
+ (options->data_source_size() != 3 ||
+ options->data_source()[0] != "accesspattern" ||
+ options->data_source()[1] != "compileunits" ||
+ options->data_source()[2] != "symbols"))
+ {
+ THROW("Protobuf output only supports '-d compileunits,symbols' "
+ "or '-d accesspattern,compileunits,symbols' for now");
+ }
+ }
+
return true;
}
@@ -2169,6 +2272,10 @@
bloaty.AddDebugFilename(debug_filename);
}
+ for (auto& link_map_filename : options.link_map_filename()) {
+ bloaty.AddLinkMapFilename(link_map_filename);
+ }
+
for (const auto& custom_data_source : options.custom_data_source()) {
bloaty.DefineCustomDataSource(custom_data_source);
}
diff --git a/src/bloaty.h b/src/bloaty.h
index 9ed2be0..d3e7949 100644
--- a/src/bloaty.h
+++ b/src/bloaty.h
@@ -39,6 +39,7 @@
#include "bloaty.pb.h"
#include "range_map.h"
#include "re.h"
+#include "util.h"
namespace bloaty {
@@ -51,6 +52,7 @@
enum class DataSource {
kArchiveMembers,
+ kAccessPattern,
kCompileUnits,
kInlines,
kInputFiles,
@@ -260,6 +262,9 @@
: file_data_(std::move(file_data)), debug_file_(this) {}
virtual ~ObjectFile() {}
+ // Searches for a binary string representing the identity of this object file.
+ // It is typically a hash of the unstripped object during the build.
+ // If not found, returns an empty string.
virtual std::string GetBuildId() const = 0;
// Process this file, pushing data to |sinks| as appropriate for each data
@@ -271,6 +276,10 @@
DataSource symbol_source,
DisassemblyInfo* info) const = 0;
+ virtual std::optional<std::unordered_map<std::string, std::string>> TakeSymbolToCrateMap() {
+ return std::nullopt;
+ }
+
const InputFile& file_data() const { return *file_data_; }
// Sets the debug file for |this|. |file| must outlive this instance.
@@ -286,7 +295,7 @@
const ObjectFile* debug_file_;
};
-std::unique_ptr<ObjectFile> TryOpenELFFile(std::unique_ptr<InputFile>& file);
+std::unique_ptr<ObjectFile> TryOpenELFFile(std::unique_ptr<InputFile>& file, std::optional<std::string> link_map_file);
std::unique_ptr<ObjectFile> TryOpenMachOFile(std::unique_ptr<InputFile>& file);
std::unique_ptr<ObjectFile> TryOpenWebAssemblyFile(std::unique_ptr<InputFile>& file);
std::unique_ptr<ObjectFile> TryOpenPEFile(std::unique_ptr<InputFile>& file);
@@ -304,6 +313,22 @@
// controls what demangling mode we are using.
std::string ItaniumDemangle(absl::string_view symbol, DataSource source);
+// Encode |symbol| and |crate| together into a single string.
+std::string EncodeSymbolWithCrateId(absl::string_view symbol, absl::string_view crate);
+
+struct DecodeCrateIdResult {
+ std::string symbol;
+ std::string crate;
+};
+
+inline bool operator==(const DecodeCrateIdResult& lhs, const DecodeCrateIdResult& rhs) {
+ return lhs.symbol == rhs.symbol && lhs.crate == rhs.crate;
+}
+
+// Parse out |symbol| and |crate| from a string encoded with |EncodeSymbolWithCrateId|.
+// If the string was not encoded, |crate| will be the empty string.
+DecodeCrateIdResult DecodeSymbolWithCrateId(absl::string_view symbol);
+
// DualMap /////////////////////////////////////////////////////////////////////
@@ -359,6 +384,7 @@
kPrettyPrint,
kCSV,
kTSV,
+ kProtobuf,
};
enum class ShowDomain {
@@ -384,7 +410,31 @@
}
const std::vector<std::string>& source_names() const { return source_names_; }
- void Print(const OutputOptions& options, std::ostream* out);
+
+ 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 SetDisassembly(absl::string_view disassembly) {
disassembly_ = std::string(disassembly);
}
@@ -408,6 +458,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 PrintToProtobuf(std::ostream* out) const;
void PrettyPrintRow(const RollupRow& row, size_t indent,
const OutputOptions& options, std::ostream* out) const;
void PrettyPrintTree(const RollupRow& row, size_t indent,
@@ -420,6 +471,9 @@
std::ostream* out, bool tabs) const;
};
+// Shim for `std::filesystem::path(filename).stem()`.
+std::string GetPathStem(const std::string& filename);
+
bool ParseOptions(bool skip_unknown, int* argc, char** argv[], Options* options,
OutputOptions* output_options, std::string* error);
bool BloatyMain(const Options& options, const InputFileFactory& file_factory,
diff --git a/src/bloaty.proto b/src/bloaty.proto
index 1679328..f8bc306 100644
--- a/src/bloaty.proto
+++ b/src/bloaty.proto
@@ -29,6 +29,27 @@
// debug_filename will *not* have their file size counted.
repeated string debug_filename = 10;
+ // Link map files to assist symbol and compile unit parsing.
+ // We will match these to files with the same base name / stem.
+ // E.g. "foo.map" will be used to analyze the executable "foo".
+ repeated string link_map_filename = 30;
+
+ // If set, reads how frequently a range of bytes is accessed in CSV format.
+ // The filter is in a sparse pattern format: assuming the frame size (see
+ // access_pattern_frame_size) is 8 KiB, given a 24 KiB file and a `0:1,2:3`
+ // pattern, it means that the first 8 KiB frame is accessed once, and the
+ // third 8 KiB frame is accessed 3 times. The second 8 KiB frame is left
+ // out, meaning it was never accessed. The frame indices in the pattern is not
+ // guaranteed to be sorted. This option works together with the
+ // `accesspattern` data source. When the `accesspattern` data source is the
+ // first data source, the protobuf output format will only output items whose
+ // region overlaps with a never-accessed frame.
+ optional string cold_bytes_filter = 31;
+
+ // If set, customizes how many bytes is in a frame as used by the pattern
+ // in cold_bytes_filter.
+ optional uint64 access_pattern_frame_size = 32 [default = 8192];
+
// The data sources to scan in each file. At least one data source must be
// specified. If more than one source is specified, the output is
// hierarchical.
diff --git a/src/demangle.cc b/src/demangle.cc
new file mode 100644
index 0000000..284782e
--- /dev/null
+++ b/src/demangle.cc
@@ -0,0 +1,1901 @@
+// Copyright 2018 The Abseil Authors.
+//
+// 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
+//
+// https://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.
+
+// For reference check out:
+// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling
+//
+// Note that we only have partial C++11 support yet.
+
+#include "demangle.h"
+
+#include <cstdint>
+#include <cstdio>
+#include <limits>
+
+namespace absl {
+namespace debugging_internal {
+
+typedef struct {
+ const char *abbrev;
+ const char *real_name;
+ // Number of arguments in <expression> context, or 0 if disallowed.
+ int arity;
+} AbbrevPair;
+
+// List of operators from Itanium C++ ABI.
+static const AbbrevPair kOperatorList[] = {
+ // New has special syntax (not currently supported).
+ {"nw", "new", 0},
+ {"na", "new[]", 0},
+
+ // Works except that the 'gs' prefix is not supported.
+ {"dl", "delete", 1},
+ {"da", "delete[]", 1},
+
+ {"ps", "+", 1}, // "positive"
+ {"ng", "-", 1}, // "negative"
+ {"ad", "&", 1}, // "address-of"
+ {"de", "*", 1}, // "dereference"
+ {"co", "~", 1},
+
+ {"pl", "+", 2},
+ {"mi", "-", 2},
+ {"ml", "*", 2},
+ {"dv", "/", 2},
+ {"rm", "%", 2},
+ {"an", "&", 2},
+ {"or", "|", 2},
+ {"eo", "^", 2},
+ {"aS", "=", 2},
+ {"pL", "+=", 2},
+ {"mI", "-=", 2},
+ {"mL", "*=", 2},
+ {"dV", "/=", 2},
+ {"rM", "%=", 2},
+ {"aN", "&=", 2},
+ {"oR", "|=", 2},
+ {"eO", "^=", 2},
+ {"ls", "<<", 2},
+ {"rs", ">>", 2},
+ {"lS", "<<=", 2},
+ {"rS", ">>=", 2},
+ {"eq", "==", 2},
+ {"ne", "!=", 2},
+ {"lt", "<", 2},
+ {"gt", ">", 2},
+ {"le", "<=", 2},
+ {"ge", ">=", 2},
+ {"nt", "!", 1},
+ {"aa", "&&", 2},
+ {"oo", "||", 2},
+ {"pp", "++", 1},
+ {"mm", "--", 1},
+ {"cm", ",", 2},
+ {"pm", "->*", 2},
+ {"pt", "->", 0}, // Special syntax
+ {"cl", "()", 0}, // Special syntax
+ {"ix", "[]", 2},
+ {"qu", "?", 3},
+ {"st", "sizeof", 0}, // Special syntax
+ {"sz", "sizeof", 1}, // Not a real operator name, but used in expressions.
+ {nullptr, nullptr, 0},
+};
+
+// List of builtin types from Itanium C++ ABI.
+//
+// Invariant: only one- or two-character type abbreviations here.
+static const AbbrevPair kBuiltinTypeList[] = {
+ {"v", "void", 0},
+ {"w", "wchar_t", 0},
+ {"b", "bool", 0},
+ {"c", "char", 0},
+ {"a", "signed char", 0},
+ {"h", "unsigned char", 0},
+ {"s", "short", 0},
+ {"t", "unsigned short", 0},
+ {"i", "int", 0},
+ {"j", "unsigned int", 0},
+ {"l", "long", 0},
+ {"m", "unsigned long", 0},
+ {"x", "long long", 0},
+ {"y", "unsigned long long", 0},
+ {"n", "__int128", 0},
+ {"o", "unsigned __int128", 0},
+ {"f", "float", 0},
+ {"d", "double", 0},
+ {"e", "long double", 0},
+ {"g", "__float128", 0},
+ {"z", "ellipsis", 0},
+
+ {"De", "decimal128", 0}, // IEEE 754r decimal floating point (128 bits)
+ {"Dd", "decimal64", 0}, // IEEE 754r decimal floating point (64 bits)
+ {"Dc", "decltype(auto)", 0},
+ {"Da", "auto", 0},
+ {"Dn", "std::nullptr_t", 0}, // i.e., decltype(nullptr)
+ {"Df", "decimal32", 0}, // IEEE 754r decimal floating point (32 bits)
+ {"Di", "char32_t", 0},
+ {"Ds", "char16_t", 0},
+ {"Dh", "float16", 0}, // IEEE 754r half-precision float (16 bits)
+ {nullptr, nullptr, 0},
+};
+
+// List of substitutions Itanium C++ ABI.
+static const AbbrevPair kSubstitutionList[] = {
+ {"St", "", 0},
+ {"Sa", "allocator", 0},
+ {"Sb", "basic_string", 0},
+ // std::basic_string<char, std::char_traits<char>,std::allocator<char> >
+ {"Ss", "string", 0},
+ // std::basic_istream<char, std::char_traits<char> >
+ {"Si", "istream", 0},
+ // std::basic_ostream<char, std::char_traits<char> >
+ {"So", "ostream", 0},
+ // std::basic_iostream<char, std::char_traits<char> >
+ {"Sd", "iostream", 0},
+ {nullptr, nullptr, 0},
+};
+
+// State needed for demangling. This struct is copied in almost every stack
+// frame, so every byte counts.
+typedef struct {
+ int mangled_idx; // Cursor of mangled name.
+ int out_cur_idx; // Cursor of output string.
+ int prev_name_idx; // For constructors/destructors.
+ signed int prev_name_length : 16; // For constructors/destructors.
+ signed int nest_level : 15; // For nested names.
+ unsigned int append : 1; // Append flag.
+ // Note: for some reason MSVC can't pack "bool append : 1" into the same int
+ // with the above two fields, so we use an int instead. Amusingly it can pack
+ // "signed bool" as expected, but relying on that to continue to be a legal
+ // type seems ill-advised (as it's illegal in at least clang).
+} ParseState;
+
+static_assert(sizeof(ParseState) == 4 * sizeof(int),
+ "unexpected size of ParseState");
+
+// One-off state for demangling that's not subject to backtracking -- either
+// constant data, data that's intentionally immune to backtracking (steps), or
+// data that would never be changed by backtracking anyway (recursion_depth).
+//
+// Only one copy of this exists for each call to Demangle, so the size of this
+// struct is nearly inconsequential.
+typedef struct {
+ const char *mangled_begin; // Beginning of input string.
+ char *out; // Beginning of output string.
+ int out_end_idx; // One past last allowed output character.
+ int recursion_depth; // For stack exhaustion prevention.
+ int steps; // Cap how much work we'll do, regardless of depth.
+ ParseState parse_state; // Backtrackable state copied for most frames.
+} State;
+
+namespace {
+// Prevent deep recursion / stack exhaustion.
+// Also prevent unbounded handling of complex inputs.
+// absl:google3-begin(Buganizer links)
+// See also http://b/34269257 and http://b/37793125
+// absl:google3-end
+class ComplexityGuard {
+ public:
+ explicit ComplexityGuard(State *state) : state_(state) {
+ ++state->recursion_depth;
+ ++state->steps;
+ }
+ ~ComplexityGuard() { --state_->recursion_depth; }
+
+ // 256 levels of recursion seems like a reasonable upper limit on depth.
+ // 128 is not enough to demagle synthetic tests from demangle_unittest.txt:
+ // "_ZaaZZZZ..." and "_ZaaZcvZcvZ..."
+ static constexpr int kRecursionDepthLimit = 256;
+
+ // We're trying to pick a charitable upper-limit on how many parse steps are
+ // necessary to handle something that a human could actually make use of.
+ // This is mostly in place as a bound on how much work we'll do if we are
+ // asked to demangle an mangled name from an untrusted source, so it should be
+ // much larger than the largest expected symbol, but much smaller than the
+ // amount of work we can do in, e.g., a second.
+ //
+ // Some real-world symbols from an arbitrary binary started failing between
+ // 2^12 and 2^13, so we multiply the latter by an extra factor of 16 to set
+ // the limit.
+ //
+ // Spending one second on 2^17 parse steps would require each step to take
+ // 7.6us, or ~30000 clock cycles, so it's safe to say this can be done in
+ // under a second.
+ static constexpr int kParseStepsLimit = 1 << 17;
+
+ bool IsTooComplex() const {
+ return state_->recursion_depth > kRecursionDepthLimit ||
+ state_->steps > kParseStepsLimit;
+ }
+
+ private:
+ State *state_;
+};
+} // namespace
+
+// We don't use strlen() in libc since it's not guaranteed to be async
+// signal safe.
+static size_t StrLen(const char *str) {
+ size_t len = 0;
+ while (*str != '\0') {
+ ++str;
+ ++len;
+ }
+ return len;
+}
+
+// Returns true if "str" has at least "n" characters remaining.
+static bool AtLeastNumCharsRemaining(const char *str, int n) {
+ for (int i = 0; i < n; ++i) {
+ if (str[i] == '\0') {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Returns true if "str" has "prefix" as a prefix.
+static bool StrPrefix(const char *str, const char *prefix) {
+ size_t i = 0;
+ while (str[i] != '\0' && prefix[i] != '\0' && str[i] == prefix[i]) {
+ ++i;
+ }
+ return prefix[i] == '\0'; // Consumed everything in "prefix".
+}
+
+static void InitState(State *state, const char *mangled, char *out,
+ int out_size) {
+ state->mangled_begin = mangled;
+ state->out = out;
+ state->out_end_idx = out_size;
+ state->recursion_depth = 0;
+ state->steps = 0;
+
+ state->parse_state.mangled_idx = 0;
+ state->parse_state.out_cur_idx = 0;
+ state->parse_state.prev_name_idx = 0;
+ state->parse_state.prev_name_length = -1;
+ state->parse_state.nest_level = -1;
+ state->parse_state.append = true;
+}
+
+static inline const char *RemainingInput(State *state) {
+ return &state->mangled_begin[state->parse_state.mangled_idx];
+}
+
+// Returns true and advances "mangled_idx" if we find "one_char_token"
+// at "mangled_idx" position. It is assumed that "one_char_token" does
+// not contain '\0'.
+static bool ParseOneCharToken(State *state, const char one_char_token) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ if (RemainingInput(state)[0] == one_char_token) {
+ ++state->parse_state.mangled_idx;
+ return true;
+ }
+ return false;
+}
+
+// Returns true and advances "mangled_cur" if we find "two_char_token"
+// at "mangled_cur" position. It is assumed that "two_char_token" does
+// not contain '\0'.
+static bool ParseTwoCharToken(State *state, const char *two_char_token) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ if (RemainingInput(state)[0] == two_char_token[0] &&
+ RemainingInput(state)[1] == two_char_token[1]) {
+ state->parse_state.mangled_idx += 2;
+ return true;
+ }
+ return false;
+}
+
+// Returns true and advances "mangled_cur" if we find any character in
+// "char_class" at "mangled_cur" position.
+static bool ParseCharClass(State *state, const char *char_class) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ if (RemainingInput(state)[0] == '\0') {
+ return false;
+ }
+ const char *p = char_class;
+ for (; *p != '\0'; ++p) {
+ if (RemainingInput(state)[0] == *p) {
+ ++state->parse_state.mangled_idx;
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool ParseDigit(State *state, int *digit) {
+ char c = RemainingInput(state)[0];
+ if (ParseCharClass(state, "0123456789")) {
+ if (digit != nullptr) {
+ *digit = c - '0';
+ }
+ return true;
+ }
+ return false;
+}
+
+// This function is used for handling an optional non-terminal.
+static bool Optional(bool /*status*/) { return true; }
+
+// This function is used for handling <non-terminal>+ syntax.
+typedef bool (*ParseFunc)(State *);
+static bool OneOrMore(ParseFunc parse_func, State *state) {
+ if (parse_func(state)) {
+ while (parse_func(state)) {
+ }
+ return true;
+ }
+ return false;
+}
+
+// This function is used for handling <non-terminal>* syntax. The function
+// always returns true and must be followed by a termination token or a
+// terminating sequence not handled by parse_func (e.g.
+// ParseOneCharToken(state, 'E')).
+static bool ZeroOrMore(ParseFunc parse_func, State *state) {
+ while (parse_func(state)) {
+ }
+ return true;
+}
+
+// Append "str" at "out_cur_idx". If there is an overflow, out_cur_idx is
+// set to out_end_idx+1. The output string is ensured to
+// always terminate with '\0' as long as there is no overflow.
+static void Append(State *state, const char *const str, const int length) {
+ for (int i = 0; i < length; ++i) {
+ if (state->parse_state.out_cur_idx + 1 <
+ state->out_end_idx) { // +1 for '\0'
+ state->out[state->parse_state.out_cur_idx++] = str[i];
+ } else {
+ // signal overflow
+ state->parse_state.out_cur_idx = state->out_end_idx + 1;
+ break;
+ }
+ }
+ if (state->parse_state.out_cur_idx < state->out_end_idx) {
+ state->out[state->parse_state.out_cur_idx] =
+ '\0'; // Terminate it with '\0'
+ }
+}
+
+// We don't use equivalents in libc to avoid locale issues.
+static bool IsLower(char c) { return c >= 'a' && c <= 'z'; }
+
+static bool IsAlpha(char c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+static bool IsDigit(char c) { return c >= '0' && c <= '9'; }
+
+// Returns true if "str" is a function clone suffix. These suffixes are used
+// by GCC 4.5.x and later versions (and our locally-modified version of GCC
+// 4.4.x) to indicate functions which have been cloned during optimization.
+// We treat any sequence (.<alpha>+.<digit>+)+ as a function clone suffix.
+static bool IsFunctionCloneSuffix(const char *str) {
+ size_t i = 0;
+ while (str[i] != '\0') {
+ // Consume a single .<alpha>+.<digit>+ sequence.
+ if (str[i] != '.' || !IsAlpha(str[i + 1])) {
+ return false;
+ }
+ i += 2;
+ while (IsAlpha(str[i])) {
+ ++i;
+ }
+ if (str[i] != '.' || !IsDigit(str[i + 1])) {
+ return false;
+ }
+ i += 2;
+ while (IsDigit(str[i])) {
+ ++i;
+ }
+ }
+ return true; // Consumed everything in "str".
+}
+
+static bool EndsWith(State *state, const char chr) {
+ return state->parse_state.out_cur_idx > 0 &&
+ chr == state->out[state->parse_state.out_cur_idx - 1];
+}
+
+// Append "str" with some tweaks, iff "append" state is true.
+static void MaybeAppendWithLength(State *state, const char *const str,
+ const int length) {
+ if (state->parse_state.append && length > 0) {
+ // Append a space if the output buffer ends with '<' and "str"
+ // starts with '<' to avoid <<<.
+ if (str[0] == '<' && EndsWith(state, '<')) {
+ Append(state, " ", 1);
+ }
+ // Remember the last identifier name for ctors/dtors.
+ if (IsAlpha(str[0]) || str[0] == '_') {
+ state->parse_state.prev_name_idx = state->parse_state.out_cur_idx;
+ state->parse_state.prev_name_length = length;
+ }
+ Append(state, str, length);
+ }
+}
+
+// Appends a positive decimal number to the output if appending is enabled.
+static bool MaybeAppendDecimal(State *state, unsigned int val) {
+ // Max {32-64}-bit unsigned int is 20 digits.
+ constexpr size_t kMaxLength = 20;
+ char buf[kMaxLength];
+
+ // We can't use itoa or sprintf as neither is specified to be
+ // async-signal-safe.
+ if (state->parse_state.append) {
+ // We can't have a one-before-the-beginning pointer, so instead start with
+ // one-past-the-end and manipulate one character before the pointer.
+ char *p = &buf[kMaxLength];
+ do { // val=0 is the only input that should write a leading zero digit.
+ *--p = (val % 10) + '0';
+ val /= 10;
+ } while (p > buf && val != 0);
+
+ // 'p' landed on the last character we set. How convenient.
+ Append(state, p, kMaxLength - (p - buf));
+ }
+
+ return true;
+}
+
+// A convenient wrapper around MaybeAppendWithLength().
+// Returns true so that it can be placed in "if" conditions.
+static bool MaybeAppend(State *state, const char *const str) {
+ if (state->parse_state.append) {
+ int length = StrLen(str);
+ MaybeAppendWithLength(state, str, length);
+ }
+ return true;
+}
+
+// This function is used for handling nested names.
+static bool EnterNestedName(State *state) {
+ state->parse_state.nest_level = 0;
+ return true;
+}
+
+// This function is used for handling nested names.
+static bool LeaveNestedName(State *state, int16_t prev_value) {
+ state->parse_state.nest_level = prev_value;
+ return true;
+}
+
+// Disable the append mode not to print function parameters, etc.
+static bool DisableAppend(State *state) {
+ state->parse_state.append = false;
+ return true;
+}
+
+// Restore the append mode to the previous state.
+static bool RestoreAppend(State *state, bool prev_value) {
+ state->parse_state.append = prev_value;
+ return true;
+}
+
+// Increase the nest level for nested names.
+static void MaybeIncreaseNestLevel(State *state) {
+ if (state->parse_state.nest_level > -1) {
+ ++state->parse_state.nest_level;
+ }
+}
+
+// Appends :: for nested names if necessary.
+static void MaybeAppendSeparator(State *state) {
+ if (state->parse_state.nest_level >= 1) {
+ MaybeAppend(state, "::");
+ }
+}
+
+// Cancel the last separator if necessary.
+static void MaybeCancelLastSeparator(State *state) {
+ if (state->parse_state.nest_level >= 1 && state->parse_state.append &&
+ state->parse_state.out_cur_idx >= 2) {
+ state->parse_state.out_cur_idx -= 2;
+ state->out[state->parse_state.out_cur_idx] = '\0';
+ }
+}
+
+// Returns true if the identifier of the given length pointed to by
+// "mangled_cur" is anonymous namespace.
+static bool IdentifierIsAnonymousNamespace(State *state, int length) {
+ // Returns true if "anon_prefix" is a proper prefix of "mangled_cur".
+ static const char anon_prefix[] = "_GLOBAL__N_";
+ return (length > static_cast<int>(sizeof(anon_prefix) - 1) &&
+ StrPrefix(RemainingInput(state), anon_prefix));
+}
+
+// Forward declarations of our parsing functions.
+static bool ParseMangledName(State *state);
+static bool ParseEncoding(State *state);
+static bool ParseName(State *state);
+static bool ParseUnscopedName(State *state);
+static bool ParseNestedName(State *state);
+static bool ParsePrefix(State *state);
+static bool ParseUnqualifiedName(State *state);
+static bool ParseSourceName(State *state);
+static bool ParseLocalSourceName(State *state);
+static bool ParseUnnamedTypeName(State *state);
+static bool ParseNumber(State *state, int *number_out);
+static bool ParseFloatNumber(State *state);
+static bool ParseSeqId(State *state);
+static bool ParseIdentifier(State *state, int length);
+static bool ParseOperatorName(State *state, int *arity);
+static bool ParseSpecialName(State *state);
+static bool ParseCallOffset(State *state);
+static bool ParseNVOffset(State *state);
+static bool ParseVOffset(State *state);
+static bool ParseCtorDtorName(State *state);
+static bool ParseDecltype(State *state);
+static bool ParseType(State *state);
+static bool ParseCVQualifiers(State *state);
+static bool ParseBuiltinType(State *state);
+static bool ParseFunctionType(State *state);
+static bool ParseBareFunctionType(State *state);
+static bool ParseClassEnumType(State *state);
+static bool ParseArrayType(State *state);
+static bool ParsePointerToMemberType(State *state);
+static bool ParseTemplateParam(State *state);
+static bool ParseTemplateTemplateParam(State *state);
+static bool ParseTemplateArgs(State *state);
+static bool ParseTemplateArg(State *state);
+static bool ParseBaseUnresolvedName(State *state);
+static bool ParseUnresolvedName(State *state);
+static bool ParseExpression(State *state);
+static bool ParseExprPrimary(State *state);
+static bool ParseExprCastValue(State *state);
+static bool ParseLocalName(State *state);
+static bool ParseLocalNameSuffix(State *state);
+static bool ParseDiscriminator(State *state);
+static bool ParseSubstitution(State *state, bool accept_std);
+
+// Implementation note: the following code is a straightforward
+// translation of the Itanium C++ ABI defined in BNF with a couple of
+// exceptions.
+//
+// - Support GNU extensions not defined in the Itanium C++ ABI
+// - <prefix> and <template-prefix> are combined to avoid infinite loop
+// - Reorder patterns to shorten the code
+// - Reorder patterns to give greedier functions precedence
+// We'll mark "Less greedy than" for these cases in the code
+//
+// Each parsing function changes the parse state and returns true on
+// success, or returns false and doesn't change the parse state (note:
+// the parse-steps counter increases regardless of success or failure).
+// To ensure that the parse state isn't changed in the latter case, we
+// save the original state before we call multiple parsing functions
+// consecutively with &&, and restore it if unsuccessful. See
+// ParseEncoding() as an example of this convention. We follow the
+// convention throughout the code.
+//
+// Originally we tried to do demangling without following the full ABI
+// syntax but it turned out we needed to follow the full syntax to
+// parse complicated cases like nested template arguments. Note that
+// implementing a full-fledged demangler isn't trivial (libiberty's
+// cp-demangle.c has +4300 lines).
+//
+// Note that (foo) in <(foo) ...> is a modifier to be ignored.
+//
+// Reference:
+// - Itanium C++ ABI
+// <https://mentorembedded.github.io/cxx-abi/abi.html#mangling>
+
+// <mangled-name> ::= _Z <encoding>
+static bool ParseMangledName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ return ParseTwoCharToken(state, "_Z") && ParseEncoding(state);
+}
+
+// <encoding> ::= <(function) name> <bare-function-type>
+// ::= <(data) name>
+// ::= <special-name>
+static bool ParseEncoding(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ // Implementing the first two productions together as <name>
+ // [<bare-function-type>] avoids exponential blowup of backtracking.
+ //
+ // Since Optional(...) can't fail, there's no need to copy the state for
+ // backtracking.
+ if (ParseName(state) && Optional(ParseBareFunctionType(state))) {
+ return true;
+ }
+
+ if (ParseSpecialName(state)) {
+ return true;
+ }
+ return false;
+}
+
+// <name> ::= <nested-name>
+// ::= <unscoped-template-name> <template-args>
+// ::= <unscoped-name>
+// ::= <local-name>
+static bool ParseName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ if (ParseNestedName(state) || ParseLocalName(state)) {
+ return true;
+ }
+
+ // We reorganize the productions to avoid re-parsing unscoped names.
+ // - Inline <unscoped-template-name> productions:
+ // <name> ::= <substitution> <template-args>
+ // ::= <unscoped-name> <template-args>
+ // ::= <unscoped-name>
+ // - Merge the two productions that start with unscoped-name:
+ // <name> ::= <unscoped-name> [<template-args>]
+
+ ParseState copy = state->parse_state;
+ // "std<...>" isn't a valid name.
+ if (ParseSubstitution(state, /*accept_std=*/false) &&
+ ParseTemplateArgs(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ // Note there's no need to restore state after this since only the first
+ // subparser can fail.
+ return ParseUnscopedName(state) && Optional(ParseTemplateArgs(state));
+}
+
+// <unscoped-name> ::= <unqualified-name>
+// ::= St <unqualified-name>
+static bool ParseUnscopedName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ if (ParseUnqualifiedName(state)) {
+ return true;
+ }
+
+ ParseState copy = state->parse_state;
+ if (ParseTwoCharToken(state, "St") && MaybeAppend(state, "std::") &&
+ ParseUnqualifiedName(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <ref-qualifer> ::= R // lvalue method reference qualifier
+// ::= O // rvalue method reference qualifier
+static inline bool ParseRefQualifier(State *state) {
+ return ParseCharClass(state, "OR");
+}
+
+// <nested-name> ::= N [<CV-qualifiers>] [<ref-qualifier>] <prefix>
+// <unqualified-name> E
+// ::= N [<CV-qualifiers>] [<ref-qualifier>] <template-prefix>
+// <template-args> E
+static bool ParseNestedName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'N') && EnterNestedName(state) &&
+ Optional(ParseCVQualifiers(state)) &&
+ Optional(ParseRefQualifier(state)) && ParsePrefix(state) &&
+ LeaveNestedName(state, copy.nest_level) &&
+ ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// This part is tricky. If we literally translate them to code, we'll
+// end up infinite loop. Hence we merge them to avoid the case.
+//
+// <prefix> ::= <prefix> <unqualified-name>
+// ::= <template-prefix> <template-args>
+// ::= <template-param>
+// ::= <substitution>
+// ::= # empty
+// <template-prefix> ::= <prefix> <(template) unqualified-name>
+// ::= <template-param>
+// ::= <substitution>
+static bool ParsePrefix(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ bool has_something = false;
+ while (true) {
+ MaybeAppendSeparator(state);
+ if (ParseTemplateParam(state) ||
+ ParseSubstitution(state, /*accept_std=*/true) ||
+ ParseUnscopedName(state) ||
+ // absl:google3-begin(Internal link)
+ // Lambda initializer scope:
+ // http://google3/third_party/binutils/binutils/libiberty/cp-demangle.c?l=1566&rcl=181793049
+ // absl:google3-end
+ (ParseOneCharToken(state, 'M') && ParseUnnamedTypeName(state))) {
+ has_something = true;
+ MaybeIncreaseNestLevel(state);
+ continue;
+ }
+ MaybeCancelLastSeparator(state);
+ if (has_something && ParseTemplateArgs(state)) {
+ return ParsePrefix(state);
+ } else {
+ break;
+ }
+ }
+ return true;
+}
+
+// <unqualified-name> ::= <operator-name>
+// ::= <ctor-dtor-name>
+// ::= <source-name>
+// ::= <local-source-name> // GCC extension; see below.
+// ::= <unnamed-type-name>
+static bool ParseUnqualifiedName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ return (ParseOperatorName(state, nullptr) || ParseCtorDtorName(state) ||
+ ParseSourceName(state) || ParseLocalSourceName(state) ||
+ ParseUnnamedTypeName(state));
+}
+
+// <source-name> ::= <positive length number> <identifier>
+static bool ParseSourceName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ int length = -1;
+ if (ParseNumber(state, &length) && ParseIdentifier(state, length)) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <local-source-name> ::= L <source-name> [<discriminator>]
+//
+// References:
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=31775
+// https://gcc.gnu.org/viewcvs?view=rev&revision=124467
+static bool ParseLocalSourceName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'L') && ParseSourceName(state) &&
+ Optional(ParseDiscriminator(state))) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <unnamed-type-name> ::= Ut [<(nonnegative) number>] _
+// ::= <closure-type-name>
+// <closure-type-name> ::= Ul <lambda-sig> E [<(nonnegative) number>] _
+// <lambda-sig> ::= <(parameter) type>+
+static bool ParseUnnamedTypeName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ // Type's 1-based index n is encoded as { "", n == 1; itoa(n-2), otherwise }.
+ // Optionally parse the encoded value into 'which' and add 2 to get the index.
+ int which = -1;
+
+ // Unnamed type local to function or class.
+ if (ParseTwoCharToken(state, "Ut") && Optional(ParseNumber(state, &which)) &&
+ which <= std::numeric_limits<int>::max() - 2 && // Don't overflow.
+ ParseOneCharToken(state, '_')) {
+ MaybeAppend(state, "{unnamed type#");
+ MaybeAppendDecimal(state, 2 + which);
+ MaybeAppend(state, "}");
+ return true;
+ }
+ state->parse_state = copy;
+
+ // Closure type.
+ which = -1;
+ if (ParseTwoCharToken(state, "Ul") && DisableAppend(state) &&
+ OneOrMore(ParseType, state) && RestoreAppend(state, copy.append) &&
+ ParseOneCharToken(state, 'E') && Optional(ParseNumber(state, &which)) &&
+ which <= std::numeric_limits<int>::max() - 2 && // Don't overflow.
+ ParseOneCharToken(state, '_')) {
+ MaybeAppend(state, "{lambda()#");
+ MaybeAppendDecimal(state, 2 + which);
+ MaybeAppend(state, "}");
+ return true;
+ }
+ state->parse_state = copy;
+
+ return false;
+}
+
+// <number> ::= [n] <non-negative decimal integer>
+// If "number_out" is non-null, then *number_out is set to the value of the
+// parsed number on success.
+static bool ParseNumber(State *state, int *number_out) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ bool negative = false;
+ if (ParseOneCharToken(state, 'n')) {
+ negative = true;
+ }
+ const char *p = RemainingInput(state);
+ uint64_t number = 0;
+ for (; *p != '\0'; ++p) {
+ if (IsDigit(*p)) {
+ number = number * 10 + (*p - '0');
+ } else {
+ break;
+ }
+ }
+ // Apply the sign with uint64 arithmetic so overflows aren't UB. Gives
+ // "incorrect" results for out-of-range inputs, but negative values only
+ // appear for literals, which aren't printed.
+ if (negative) {
+ number = ~number + 1;
+ }
+ if (p != RemainingInput(state)) { // Conversion succeeded.
+ state->parse_state.mangled_idx += p - RemainingInput(state);
+ if (number_out != nullptr) {
+ // Note: possibly truncate "number".
+ *number_out = number;
+ }
+ return true;
+ }
+ return false;
+}
+
+// Floating-point literals are encoded using a fixed-length lowercase
+// hexadecimal string.
+static bool ParseFloatNumber(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ const char *p = RemainingInput(state);
+ for (; *p != '\0'; ++p) {
+ if (!IsDigit(*p) && !(*p >= 'a' && *p <= 'f')) {
+ break;
+ }
+ }
+ if (p != RemainingInput(state)) { // Conversion succeeded.
+ state->parse_state.mangled_idx += p - RemainingInput(state);
+ return true;
+ }
+ return false;
+}
+
+// The <seq-id> is a sequence number in base 36,
+// using digits and upper case letters
+static bool ParseSeqId(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ const char *p = RemainingInput(state);
+ for (; *p != '\0'; ++p) {
+ if (!IsDigit(*p) && !(*p >= 'A' && *p <= 'Z')) {
+ break;
+ }
+ }
+ if (p != RemainingInput(state)) { // Conversion succeeded.
+ state->parse_state.mangled_idx += p - RemainingInput(state);
+ return true;
+ }
+ return false;
+}
+
+// <identifier> ::= <unqualified source code identifier> (of given length)
+static bool ParseIdentifier(State *state, int length) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ if (length < 0 || !AtLeastNumCharsRemaining(RemainingInput(state), length)) {
+ return false;
+ }
+ if (IdentifierIsAnonymousNamespace(state, length)) {
+ MaybeAppend(state, "(anonymous namespace)");
+ } else {
+ MaybeAppendWithLength(state, RemainingInput(state), length);
+ }
+ state->parse_state.mangled_idx += length;
+ return true;
+}
+
+// <operator-name> ::= nw, and other two letters cases
+// ::= cv <type> # (cast)
+// ::= v <digit> <source-name> # vendor extended operator
+static bool ParseOperatorName(State *state, int *arity) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ if (!AtLeastNumCharsRemaining(RemainingInput(state), 2)) {
+ return false;
+ }
+ // First check with "cv" (cast) case.
+ ParseState copy = state->parse_state;
+ if (ParseTwoCharToken(state, "cv") && MaybeAppend(state, "operator ") &&
+ EnterNestedName(state) && ParseType(state) &&
+ LeaveNestedName(state, copy.nest_level)) {
+ if (arity != nullptr) {
+ *arity = 1;
+ }
+ return true;
+ }
+ state->parse_state = copy;
+
+ // Then vendor extended operators.
+ if (ParseOneCharToken(state, 'v') && ParseDigit(state, arity) &&
+ ParseSourceName(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ // Other operator names should start with a lower alphabet followed
+ // by a lower/upper alphabet.
+ if (!(IsLower(RemainingInput(state)[0]) &&
+ IsAlpha(RemainingInput(state)[1]))) {
+ return false;
+ }
+ // We may want to perform a binary search if we really need speed.
+ const AbbrevPair *p;
+ for (p = kOperatorList; p->abbrev != nullptr; ++p) {
+ if (RemainingInput(state)[0] == p->abbrev[0] &&
+ RemainingInput(state)[1] == p->abbrev[1]) {
+ if (arity != nullptr) {
+ *arity = p->arity;
+ }
+ MaybeAppend(state, "operator");
+ if (IsLower(*p->real_name)) { // new, delete, etc.
+ MaybeAppend(state, " ");
+ }
+ MaybeAppend(state, p->real_name);
+ state->parse_state.mangled_idx += 2;
+ return true;
+ }
+ }
+ return false;
+}
+
+// <special-name> ::= TV <type>
+// ::= TT <type>
+// ::= TI <type>
+// ::= TS <type>
+// ::= Tc <call-offset> <call-offset> <(base) encoding>
+// ::= GV <(object) name>
+// ::= T <call-offset> <(base) encoding>
+// G++ extensions:
+// ::= TC <type> <(offset) number> _ <(base) type>
+// ::= TF <type>
+// ::= TJ <type>
+// ::= GR <name>
+// ::= GA <encoding>
+// ::= Th <call-offset> <(base) encoding>
+// ::= Tv <call-offset> <(base) encoding>
+//
+// Note: we don't care much about them since they don't appear in
+// stack traces. The are special data.
+static bool ParseSpecialName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "VTIS") &&
+ ParseType(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "Tc") && ParseCallOffset(state) &&
+ ParseCallOffset(state) && ParseEncoding(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "GV") && ParseName(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseOneCharToken(state, 'T') && ParseCallOffset(state) &&
+ ParseEncoding(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ // G++ extensions
+ if (ParseTwoCharToken(state, "TC") && ParseType(state) &&
+ ParseNumber(state, nullptr) && ParseOneCharToken(state, '_') &&
+ DisableAppend(state) && ParseType(state)) {
+ RestoreAppend(state, copy.append);
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "FJ") &&
+ ParseType(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "GR") && ParseName(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "GA") && ParseEncoding(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "hv") &&
+ ParseCallOffset(state) && ParseEncoding(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <call-offset> ::= h <nv-offset> _
+// ::= v <v-offset> _
+static bool ParseCallOffset(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'h') && ParseNVOffset(state) &&
+ ParseOneCharToken(state, '_')) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseOneCharToken(state, 'v') && ParseVOffset(state) &&
+ ParseOneCharToken(state, '_')) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ return false;
+}
+
+// <nv-offset> ::= <(offset) number>
+static bool ParseNVOffset(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ return ParseNumber(state, nullptr);
+}
+
+// <v-offset> ::= <(offset) number> _ <(virtual offset) number>
+static bool ParseVOffset(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ if (ParseNumber(state, nullptr) && ParseOneCharToken(state, '_') &&
+ ParseNumber(state, nullptr)) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <ctor-dtor-name> ::= C1 | C2 | C3
+// ::= D0 | D1 | D2
+// # GCC extensions: "unified" constructor/destructor. See
+// # https://github.com/gcc-mirror/gcc/blob/7ad17b583c3643bd4557f29b8391ca7ef08391f5/gcc/cp/mangle.c#L1847
+// ::= C4 | D4
+static bool ParseCtorDtorName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'C') && ParseCharClass(state, "1234")) {
+ const char *const prev_name = state->out + state->parse_state.prev_name_idx;
+ MaybeAppendWithLength(state, prev_name,
+ state->parse_state.prev_name_length);
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "0124")) {
+ const char *const prev_name = state->out + state->parse_state.prev_name_idx;
+ MaybeAppend(state, "~");
+ MaybeAppendWithLength(state, prev_name,
+ state->parse_state.prev_name_length);
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <decltype> ::= Dt <expression> E # decltype of an id-expression or class
+// # member access (C++0x)
+// ::= DT <expression> E # decltype of an expression (C++0x)
+static bool ParseDecltype(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "tT") &&
+ ParseExpression(state) && ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ return false;
+}
+
+// <type> ::= <CV-qualifiers> <type>
+// ::= P <type> # pointer-to
+// ::= R <type> # reference-to
+// ::= O <type> # rvalue reference-to (C++0x)
+// ::= C <type> # complex pair (C 2000)
+// ::= G <type> # imaginary (C 2000)
+// ::= U <source-name> <type> # vendor extended type qualifier
+// ::= <builtin-type>
+// ::= <function-type>
+// ::= <class-enum-type> # note: just an alias for <name>
+// ::= <array-type>
+// ::= <pointer-to-member-type>
+// ::= <template-template-param> <template-args>
+// ::= <template-param>
+// ::= <decltype>
+// ::= <substitution>
+// ::= Dp <type> # pack expansion of (C++0x)
+//
+static bool ParseType(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+
+ // We should check CV-qualifers, and PRGC things first.
+ //
+ // CV-qualifiers overlap with some operator names, but an operator name is not
+ // valid as a type. To avoid an ambiguity that can lead to exponential time
+ // complexity, refuse to backtrack the CV-qualifiers.
+ //
+ // _Z4aoeuIrMvvE
+ // => _Z 4aoeuI rM v v E
+ // aoeu<operator%=, void, void>
+ // => _Z 4aoeuI r Mv v E
+ // aoeu<void void::* restrict>
+ //
+ // By consuming the CV-qualifiers first, the former parse is disabled.
+ if (ParseCVQualifiers(state)) {
+ const bool result = ParseType(state);
+ if (!result) state->parse_state = copy;
+ return result;
+ }
+ state->parse_state = copy;
+
+ // Similarly, these tag characters can overlap with other <name>s resulting in
+ // two different parse prefixes that land on <template-args> in the same
+ // place, such as "C3r1xI...". So, disable the "ctor-name = C3" parse by
+ // refusing to backtrack the tag characters.
+ if (ParseCharClass(state, "OPRCG")) {
+ const bool result = ParseType(state);
+ if (!result) state->parse_state = copy;
+ return result;
+ }
+ state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "Dp") && ParseType(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseOneCharToken(state, 'U') && ParseSourceName(state) &&
+ ParseType(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseBuiltinType(state) || ParseFunctionType(state) ||
+ ParseClassEnumType(state) || ParseArrayType(state) ||
+ ParsePointerToMemberType(state) || ParseDecltype(state) ||
+ // "std" on its own isn't a type.
+ ParseSubstitution(state, /*accept_std=*/false)) {
+ return true;
+ }
+
+ if (ParseTemplateTemplateParam(state) && ParseTemplateArgs(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ // Less greedy than <template-template-param> <template-args>.
+ if (ParseTemplateParam(state)) {
+ return true;
+ }
+
+ return false;
+}
+
+// <CV-qualifiers> ::= [r] [V] [K]
+// We don't allow empty <CV-qualifiers> to avoid infinite loop in
+// ParseType().
+static bool ParseCVQualifiers(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ int num_cv_qualifiers = 0;
+ num_cv_qualifiers += ParseOneCharToken(state, 'r');
+ num_cv_qualifiers += ParseOneCharToken(state, 'V');
+ num_cv_qualifiers += ParseOneCharToken(state, 'K');
+ return num_cv_qualifiers > 0;
+}
+
+// <builtin-type> ::= v, etc. # single-character builtin types
+// ::= u <source-name>
+// ::= Dd, etc. # two-character builtin types
+//
+// Not supported:
+// ::= DF <number> _ # _FloatN (N bits)
+//
+static bool ParseBuiltinType(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ const AbbrevPair *p;
+ for (p = kBuiltinTypeList; p->abbrev != nullptr; ++p) {
+ // Guaranteed only 1- or 2-character strings in kBuiltinTypeList.
+ if (p->abbrev[1] == '\0') {
+ if (ParseOneCharToken(state, p->abbrev[0])) {
+ MaybeAppend(state, p->real_name);
+ return true;
+ }
+ } else if (p->abbrev[2] == '\0' && ParseTwoCharToken(state, p->abbrev)) {
+ MaybeAppend(state, p->real_name);
+ return true;
+ }
+ }
+
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'u') && ParseSourceName(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <function-type> ::= F [Y] <bare-function-type> E
+static bool ParseFunctionType(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'F') &&
+ Optional(ParseOneCharToken(state, 'Y')) && ParseBareFunctionType(state) &&
+ ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <bare-function-type> ::= <(signature) type>+
+static bool ParseBareFunctionType(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ DisableAppend(state);
+ if (OneOrMore(ParseType, state)) {
+ RestoreAppend(state, copy.append);
+ MaybeAppend(state, "()");
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <class-enum-type> ::= <name>
+static bool ParseClassEnumType(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ return ParseName(state);
+}
+
+// <array-type> ::= A <(positive dimension) number> _ <(element) type>
+// ::= A [<(dimension) expression>] _ <(element) type>
+static bool ParseArrayType(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'A') && ParseNumber(state, nullptr) &&
+ ParseOneCharToken(state, '_') && ParseType(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseOneCharToken(state, 'A') && Optional(ParseExpression(state)) &&
+ ParseOneCharToken(state, '_') && ParseType(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <pointer-to-member-type> ::= M <(class) type> <(member) type>
+static bool ParsePointerToMemberType(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'M') && ParseType(state) && ParseType(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <template-param> ::= T_
+// ::= T <parameter-2 non-negative number> _
+static bool ParseTemplateParam(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ if (ParseTwoCharToken(state, "T_")) {
+ MaybeAppend(state, "?"); // We don't support template substitutions.
+ return true;
+ }
+
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'T') && ParseNumber(state, nullptr) &&
+ ParseOneCharToken(state, '_')) {
+ MaybeAppend(state, "?"); // We don't support template substitutions.
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <template-template-param> ::= <template-param>
+// ::= <substitution>
+static bool ParseTemplateTemplateParam(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ return (ParseTemplateParam(state) ||
+ // "std" on its own isn't a template.
+ ParseSubstitution(state, /*accept_std=*/false));
+}
+
+// <template-args> ::= I <template-arg>+ E
+static bool ParseTemplateArgs(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ DisableAppend(state);
+ if (ParseOneCharToken(state, 'I') && OneOrMore(ParseTemplateArg, state) &&
+ ParseOneCharToken(state, 'E')) {
+ RestoreAppend(state, copy.append);
+ MaybeAppend(state, "<>");
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <template-arg> ::= <type>
+// ::= <expr-primary>
+// ::= J <template-arg>* E # argument pack
+// ::= X <expression> E
+static bool ParseTemplateArg(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'J') && ZeroOrMore(ParseTemplateArg, state) &&
+ ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ // There can be significant overlap between the following leading to
+ // exponential backtracking:
+ //
+ // <expr-primary> ::= L <type> <expr-cast-value> E
+ // e.g. L 2xxIvE 1 E
+ // <type> ==> <local-source-name> <template-args>
+ // e.g. L 2xx IvE
+ //
+ // This means parsing an entire <type> twice, and <type> can contain
+ // <template-arg>, so this can generate exponential backtracking. There is
+ // only overlap when the remaining input starts with "L <source-name>", so
+ // parse all cases that can start this way jointly to share the common prefix.
+ //
+ // We have:
+ //
+ // <template-arg> ::= <type>
+ // ::= <expr-primary>
+ //
+ // First, drop all the productions of <type> that must start with something
+ // other than 'L'. All that's left is <class-enum-type>; inline it.
+ //
+ // <type> ::= <nested-name> # starts with 'N'
+ // ::= <unscoped-name>
+ // ::= <unscoped-template-name> <template-args>
+ // ::= <local-name> # starts with 'Z'
+ //
+ // Drop and inline again:
+ //
+ // <type> ::= <unscoped-name>
+ // ::= <unscoped-name> <template-args>
+ // ::= <substitution> <template-args> # starts with 'S'
+ //
+ // Merge the first two, inline <unscoped-name>, drop last:
+ //
+ // <type> ::= <unqualified-name> [<template-args>]
+ // ::= St <unqualified-name> [<template-args>] # starts with 'S'
+ //
+ // Drop and inline:
+ //
+ // <type> ::= <operator-name> [<template-args>] # starts with lowercase
+ // ::= <ctor-dtor-name> [<template-args>] # starts with 'C' or 'D'
+ // ::= <source-name> [<template-args>] # starts with digit
+ // ::= <local-source-name> [<template-args>]
+ // ::= <unnamed-type-name> [<template-args>] # starts with 'U'
+ //
+ // One more time:
+ //
+ // <type> ::= L <source-name> [<template-args>]
+ //
+ // Likewise with <expr-primary>:
+ //
+ // <expr-primary> ::= L <type> <expr-cast-value> E
+ // ::= LZ <encoding> E # cannot overlap; drop
+ // ::= L <mangled_name> E # cannot overlap; drop
+ //
+ // By similar reasoning as shown above, the only <type>s starting with
+ // <source-name> are "<source-name> [<template-args>]". Inline this.
+ //
+ // <expr-primary> ::= L <source-name> [<template-args>] <expr-cast-value> E
+ //
+ // Now inline both of these into <template-arg>:
+ //
+ // <template-arg> ::= L <source-name> [<template-args>]
+ // ::= L <source-name> [<template-args>] <expr-cast-value> E
+ //
+ // Merge them and we're done:
+ // <template-arg>
+ // ::= L <source-name> [<template-args>] [<expr-cast-value> E]
+ if (ParseLocalSourceName(state) && Optional(ParseTemplateArgs(state))) {
+ copy = state->parse_state;
+ if (ParseExprCastValue(state) && ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy;
+ return true;
+ }
+
+ // Now that the overlapping cases can't reach this code, we can safely call
+ // both of these.
+ if (ParseType(state) || ParseExprPrimary(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseOneCharToken(state, 'X') && ParseExpression(state) &&
+ ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <unresolved-type> ::= <template-param> [<template-args>]
+// ::= <decltype>
+// ::= <substitution>
+static inline bool ParseUnresolvedType(State *state) {
+ // No ComplexityGuard because we don't copy the state in this stack frame.
+ return (ParseTemplateParam(state) && Optional(ParseTemplateArgs(state))) ||
+ ParseDecltype(state) || ParseSubstitution(state, /*accept_std=*/false);
+}
+
+// <simple-id> ::= <source-name> [<template-args>]
+static inline bool ParseSimpleId(State *state) {
+ // No ComplexityGuard because we don't copy the state in this stack frame.
+
+ // Note: <simple-id> cannot be followed by a parameter pack; see comment in
+ // ParseUnresolvedType.
+ return ParseSourceName(state) && Optional(ParseTemplateArgs(state));
+}
+
+// <base-unresolved-name> ::= <source-name> [<template-args>]
+// ::= on <operator-name> [<template-args>]
+// ::= dn <destructor-name>
+static bool ParseBaseUnresolvedName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+
+ if (ParseSimpleId(state)) {
+ return true;
+ }
+
+ ParseState copy = state->parse_state;
+ if (ParseTwoCharToken(state, "on") && ParseOperatorName(state, nullptr) &&
+ Optional(ParseTemplateArgs(state))) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "dn") &&
+ (ParseUnresolvedType(state) || ParseSimpleId(state))) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ return false;
+}
+
+// <unresolved-name> ::= [gs] <base-unresolved-name>
+// ::= sr <unresolved-type> <base-unresolved-name>
+// ::= srN <unresolved-type> <unresolved-qualifier-level>+ E
+// <base-unresolved-name>
+// ::= [gs] sr <unresolved-qualifier-level>+ E
+// <base-unresolved-name>
+static bool ParseUnresolvedName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+
+ ParseState copy = state->parse_state;
+ if (Optional(ParseTwoCharToken(state, "gs")) &&
+ ParseBaseUnresolvedName(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "sr") && ParseUnresolvedType(state) &&
+ ParseBaseUnresolvedName(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "sr") && ParseOneCharToken(state, 'N') &&
+ ParseUnresolvedType(state) &&
+ OneOrMore(/* <unresolved-qualifier-level> ::= */ ParseSimpleId, state) &&
+ ParseOneCharToken(state, 'E') && ParseBaseUnresolvedName(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (Optional(ParseTwoCharToken(state, "gs")) &&
+ ParseTwoCharToken(state, "sr") &&
+ OneOrMore(/* <unresolved-qualifier-level> ::= */ ParseSimpleId, state) &&
+ ParseOneCharToken(state, 'E') && ParseBaseUnresolvedName(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ return false;
+}
+
+// <expression> ::= <1-ary operator-name> <expression>
+// ::= <2-ary operator-name> <expression> <expression>
+// ::= <3-ary operator-name> <expression> <expression> <expression>
+// ::= cl <expression>+ E
+// ::= cv <type> <expression> # type (expression)
+// ::= cv <type> _ <expression>* E # type (expr-list)
+// ::= st <type>
+// ::= <template-param>
+// ::= <function-param>
+// ::= <expr-primary>
+// ::= dt <expression> <unresolved-name> # expr.name
+// ::= pt <expression> <unresolved-name> # expr->name
+// ::= sp <expression> # argument pack expansion
+// ::= sr <type> <unqualified-name> <template-args>
+// ::= sr <type> <unqualified-name>
+// <function-param> ::= fp <(top-level) CV-qualifiers> _
+// ::= fp <(top-level) CV-qualifiers> <number> _
+// ::= fL <number> p <(top-level) CV-qualifiers> _
+// ::= fL <number> p <(top-level) CV-qualifiers> <number> _
+static bool ParseExpression(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ if (ParseTemplateParam(state) || ParseExprPrimary(state)) {
+ return true;
+ }
+
+ // Object/function call expression.
+ ParseState copy = state->parse_state;
+ if (ParseTwoCharToken(state, "cl") && OneOrMore(ParseExpression, state) &&
+ ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ // Function-param expression (level 0).
+ if (ParseTwoCharToken(state, "fp") && Optional(ParseCVQualifiers(state)) &&
+ Optional(ParseNumber(state, nullptr)) && ParseOneCharToken(state, '_')) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ // Function-param expression (level 1+).
+ if (ParseTwoCharToken(state, "fL") && Optional(ParseNumber(state, nullptr)) &&
+ ParseOneCharToken(state, 'p') && Optional(ParseCVQualifiers(state)) &&
+ Optional(ParseNumber(state, nullptr)) && ParseOneCharToken(state, '_')) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ // Parse the conversion expressions jointly to avoid re-parsing the <type> in
+ // their common prefix. Parsed as:
+ // <expression> ::= cv <type> <conversion-args>
+ // <conversion-args> ::= _ <expression>* E
+ // ::= <expression>
+ //
+ // Also don't try ParseOperatorName after seeing "cv", since ParseOperatorName
+ // also needs to accept "cv <type>" in other contexts.
+ if (ParseTwoCharToken(state, "cv")) {
+ if (ParseType(state)) {
+ ParseState copy2 = state->parse_state;
+ if (ParseOneCharToken(state, '_') && ZeroOrMore(ParseExpression, state) &&
+ ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy2;
+ if (ParseExpression(state)) {
+ return true;
+ }
+ }
+ } else {
+ // Parse unary, binary, and ternary operator expressions jointly, taking
+ // care not to re-parse subexpressions repeatedly. Parse like:
+ // <expression> ::= <operator-name> <expression>
+ // [<one-to-two-expressions>]
+ // <one-to-two-expressions> ::= <expression> [<expression>]
+ int arity = -1;
+ if (ParseOperatorName(state, &arity) &&
+ arity > 0 && // 0 arity => disabled.
+ (arity < 3 || ParseExpression(state)) &&
+ (arity < 2 || ParseExpression(state)) &&
+ (arity < 1 || ParseExpression(state))) {
+ return true;
+ }
+ }
+ state->parse_state = copy;
+
+ // sizeof type
+ if (ParseTwoCharToken(state, "st") && ParseType(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ // Object and pointer member access expressions.
+ if ((ParseTwoCharToken(state, "dt") || ParseTwoCharToken(state, "pt")) &&
+ ParseExpression(state) && ParseType(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ // Pointer-to-member access expressions. This parses the same as a binary
+ // operator, but it's implemented separately because "ds" shouldn't be
+ // accepted in other contexts that parse an operator name.
+ if (ParseTwoCharToken(state, "ds") && ParseExpression(state) &&
+ ParseExpression(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ // Parameter pack expansion
+ if (ParseTwoCharToken(state, "sp") && ParseExpression(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ return ParseUnresolvedName(state);
+}
+
+// <expr-primary> ::= L <type> <(value) number> E
+// ::= L <type> <(value) float> E
+// ::= L <mangled-name> E
+// // A bug in g++'s C++ ABI version 2 (-fabi-version=2).
+// ::= LZ <encoding> E
+//
+// Warning, subtle: the "bug" LZ production above is ambiguous with the first
+// production where <type> starts with <local-name>, which can lead to
+// exponential backtracking in two scenarios:
+//
+// - When whatever follows the E in the <local-name> in the first production is
+// not a name, we backtrack the whole <encoding> and re-parse the whole thing.
+//
+// - When whatever follows the <local-name> in the first production is not a
+// number and this <expr-primary> may be followed by a name, we backtrack the
+// <name> and re-parse it.
+//
+// Moreover this ambiguity isn't always resolved -- for example, the following
+// has two different parses:
+//
+// _ZaaILZ4aoeuE1x1EvE
+// => operator&&<aoeu, x, E, void>
+// => operator&&<(aoeu::x)(1), void>
+//
+// To resolve this, we just do what GCC's demangler does, and refuse to parse
+// casts to <local-name> types.
+static bool ParseExprPrimary(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+
+ // The "LZ" special case: if we see LZ, we commit to accept "LZ <encoding> E"
+ // or fail, no backtracking.
+ if (ParseTwoCharToken(state, "LZ")) {
+ if (ParseEncoding(state) && ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+
+ state->parse_state = copy;
+ return false;
+ }
+
+ // The merged cast production.
+ if (ParseOneCharToken(state, 'L') && ParseType(state) &&
+ ParseExprCastValue(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseOneCharToken(state, 'L') && ParseMangledName(state) &&
+ ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ return false;
+}
+
+// <number> or <float>, followed by 'E', as described above ParseExprPrimary.
+static bool ParseExprCastValue(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ // We have to be able to backtrack after accepting a number because we could
+ // have e.g. "7fffE", which will accept "7" as a number but then fail to find
+ // the 'E'.
+ ParseState copy = state->parse_state;
+ if (ParseNumber(state, nullptr) && ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseFloatNumber(state) && ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ return false;
+}
+
+// <local-name> ::= Z <(function) encoding> E <(entity) name> [<discriminator>]
+// ::= Z <(function) encoding> E s [<discriminator>]
+//
+// Parsing a common prefix of these two productions together avoids an
+// exponential blowup of backtracking. Parse like:
+// <local-name> := Z <encoding> E <local-name-suffix>
+// <local-name-suffix> ::= s [<discriminator>]
+// ::= <name> [<discriminator>]
+
+static bool ParseLocalNameSuffix(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+
+ if (MaybeAppend(state, "::") && ParseName(state) &&
+ Optional(ParseDiscriminator(state))) {
+ return true;
+ }
+
+ // Since we're not going to overwrite the above "::" by re-parsing the
+ // <encoding> (whose trailing '\0' byte was in the byte now holding the
+ // first ':'), we have to rollback the "::" if the <name> parse failed.
+ if (state->parse_state.append) {
+ state->out[state->parse_state.out_cur_idx - 2] = '\0';
+ }
+
+ return ParseOneCharToken(state, 's') && Optional(ParseDiscriminator(state));
+}
+
+static bool ParseLocalName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) &&
+ ParseOneCharToken(state, 'E') && ParseLocalNameSuffix(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <discriminator> := _ <(non-negative) number>
+static bool ParseDiscriminator(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, '_') && ParseNumber(state, nullptr)) {
+ return true;
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <substitution> ::= S_
+// ::= S <seq-id> _
+// ::= St, etc.
+//
+// "St" is special in that it's not valid as a standalone name, and it *is*
+// allowed to precede a name without being wrapped in "N...E". This means that
+// if we accept it on its own, we can accept "St1a" and try to parse
+// template-args, then fail and backtrack, accept "St" on its own, then "1a" as
+// an unqualified name and re-parse the same template-args. To block this
+// exponential backtracking, we disable it with 'accept_std=false' in
+// problematic contexts.
+static bool ParseSubstitution(State *state, bool accept_std) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ if (ParseTwoCharToken(state, "S_")) {
+ MaybeAppend(state, "?"); // We don't support substitutions.
+ return true;
+ }
+
+ ParseState copy = state->parse_state;
+ if (ParseOneCharToken(state, 'S') && ParseSeqId(state) &&
+ ParseOneCharToken(state, '_')) {
+ MaybeAppend(state, "?"); // We don't support substitutions.
+ return true;
+ }
+ state->parse_state = copy;
+
+ // Expand abbreviations like "St" => "std".
+ if (ParseOneCharToken(state, 'S')) {
+ const AbbrevPair *p;
+ for (p = kSubstitutionList; p->abbrev != nullptr; ++p) {
+ if (RemainingInput(state)[0] == p->abbrev[1] &&
+ (accept_std || p->abbrev[1] != 't')) {
+ MaybeAppend(state, "std");
+ if (p->real_name[0] != '\0') {
+ MaybeAppend(state, "::");
+ MaybeAppend(state, p->real_name);
+ }
+ ++state->parse_state.mangled_idx;
+ return true;
+ }
+ }
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// Parse <mangled-name>, optionally followed by either a function-clone suffix
+// or version suffix. Returns true only if all of "mangled_cur" was consumed.
+static bool ParseTopLevelMangledName(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ if (ParseMangledName(state)) {
+ if (RemainingInput(state)[0] != '\0') {
+ // Drop trailing function clone suffix, if any.
+ if (IsFunctionCloneSuffix(RemainingInput(state))) {
+ return true;
+ }
+ // Append trailing version suffix if any.
+ // ex. _Z3foo@@GLIBCXX_3.4
+ if (RemainingInput(state)[0] == '@') {
+ MaybeAppend(state, RemainingInput(state));
+ return true;
+ }
+ return false; // Unconsumed suffix.
+ }
+ return true;
+ }
+ return false;
+}
+
+static bool Overflowed(const State *state) {
+ return state->parse_state.out_cur_idx >= state->out_end_idx;
+}
+
+} // namespace debugging_internal
+} // namespace absl
+
+// The demangler entry point.
+bool Demangle(const char *mangled, char *out, int out_size) {
+ using namespace absl::debugging_internal;
+ State state;
+ InitState(&state, mangled, out, out_size);
+ return ParseTopLevelMangledName(&state) && !Overflowed(&state);
+}
diff --git a/src/demangle.h b/src/demangle.h
new file mode 100644
index 0000000..823b0da
--- /dev/null
+++ b/src/demangle.h
@@ -0,0 +1,58 @@
+// Copyright 2017 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.
+// Author: satorux@google.com (Satoru Takabayashi)
+//
+// An async-signal-safe and thread-safe demangler for Itanium C++ ABI
+// (aka G++ V3 ABI).
+// The demangler is implemented to be used in async signal handlers to
+// symbolize stack traces. We cannot use libstdc++'s
+// abi::__cxa_demangle() in such signal handlers since it's not async
+// signal safe (it uses malloc() internally).
+//
+// Note that this demangler doesn't support full demangling. More
+// specifically, it doesn't print types of function parameters and
+// types of template arguments. It just skips them. However, it's
+// still very useful to extract basic information such as class,
+// function, constructor, destructor, and operator names.
+//
+// See the implementation note in demangle.cc if you are interested.
+//
+// Example:
+//
+// | Mangled Name | The Demangler | abi::__cxa_demangle()
+// |---------------|---------------|-----------------------
+// | _Z1fv | f() | f()
+// | _Z1fi | f() | f(int)
+// | _Z3foo3bar | foo() | foo(bar)
+// | _Z1fIiEvi | f<>() | void f<int>(int)
+// | _ZN1N1fE | N::f | N::f
+// | _ZN3Foo3BarEv | Foo::Bar() | Foo::Bar()
+// | _Zrm1XS_" | operator%() | operator%(X, X)
+// | _ZN3FooC1Ev | Foo::Foo() | Foo::Foo()
+// | _Z1fSs | f() | f(std::basic_string<char,
+// | | | std::char_traits<char>,
+// | | | std::allocator<char> >)
+//
+// See the unit test for more examples.
+//
+// Note: we might want to write demanglers for ABIs other than Itanium
+// C++ ABI in the future.
+//
+#ifndef BASE_DEMANGLE_H_
+#define BASE_DEMANGLE_H_
+// Demangle "mangled". On success, return true and write the
+// demangled symbol name to "out". Otherwise, return false.
+// "out" is modified even if demangling is unsuccessful.
+bool Demangle(const char *mangled, char *out, int out_size);
+#endif // BASE_DEMANGLE_H_
diff --git a/src/elf.cc b/src/elf.cc
index 58b1b38..dc00d21 100644
--- a/src/elf.cc
+++ b/src/elf.cc
@@ -15,13 +15,15 @@
#include <algorithm>
#include <string>
#include <iostream>
+#include <fstream>
+#include <sstream>
#include "absl/numeric/int128.h"
#include "absl/strings/escaping.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "third_party/freebsd_elf/elf.h"
#include "bloaty.h"
-#include "util.h"
+#include "link_map.h"
#include <assert.h>
#include <limits.h>
@@ -140,6 +142,10 @@
: elf_(§ion.elf()), remaining_(section.contents()) {
Next();
}
+ NoteIter(const Segment& segment, const ElfFile* elf)
+ : elf_(elf), remaining_(segment.contents()) {
+ Next();
+ }
bool IsDone() const { return done_; }
uint32_t type() const { return type_; }
@@ -1251,8 +1257,27 @@
class ElfObjectFile : public ObjectFile {
public:
- ElfObjectFile(std::unique_ptr<InputFile> file)
- : ObjectFile(std::move(file)) {}
+ ElfObjectFile(std::unique_ptr<InputFile> file, std::optional<std::string> link_map_file)
+ : ObjectFile(std::move(file)) {
+ if (link_map_file.has_value()) {
+ std::ifstream infile(*link_map_file);
+ std::string link_map;
+
+ // Strip comments and empty lines.
+ for (std::string line; getline(infile, line);) {
+ if (line.empty()) continue;
+ if (line[0] == '#') continue;
+ link_map += line;
+ link_map += '\n';
+ }
+
+ absl::StripLeadingAsciiWhitespace(&link_map);
+ absl::StripTrailingAsciiWhitespace(&link_map);
+
+ link_map_symbols_ = bloaty_link_map::ParseLldLinkMap(link_map);
+ link_map_sections_ = bloaty_link_map::ParseLldLinkMapSections(link_map);
+ }
+ }
std::string GetBuildId() const override {
if (IsObjectFile(file_data().data())) {
@@ -1262,6 +1287,7 @@
ElfFile elf(file_data().data());
assert(elf.IsOpen());
+ // Search for a build-id section.
for (Elf64_Xword i = 1; i < elf.section_count(); i++) {
ElfFile::Section section;
elf.ReadSection(i, §ion);
@@ -1275,11 +1301,123 @@
}
}
}
+ // Search for a build-id segment.
+ for (Elf64_Xword i = 0; i < elf.header().e_phnum; i++) {
+ ElfFile::Segment segment;
+ elf.ReadSegment(i, &segment);
+ const auto &header = segment.header();
+ if (header.p_type != PT_NOTE) {
+ continue;
+ }
+ for (ElfFile::NoteIter notes(segment, &elf); !notes.IsDone(); notes.Next()) {
+ if (notes.name() == "GNU" && notes.type() == NT_GNU_BUILD_ID) {
+ return std::string(notes.descriptor());
+ }
+ }
+ }
// No build id section found.
return std::string();
}
+ void ReadAccessPattern(RangeSink* sink) const {
+ if (!sink->options().has_cold_bytes_filter()) {
+ THROW("need to specify cold bytes filter");
+ }
+ // Each element corresponds to |kAccessPatternFrameSize| bytes.
+ std::vector<bool> access_pattern;
+ uint64_t kAccessPatternFrameSize = sink->options().access_pattern_frame_size();
+ auto frequencies = sink->options().cold_bytes_filter();
+ size_t file_size = sink->input_file().data().size();
+ size_t num_frames = (file_size + kAccessPatternFrameSize - 1) / kAccessPatternFrameSize;
+ access_pattern.reserve(num_frames);
+ for (size_t i = 0; i < num_frames; i ++) {
+ access_pattern.push_back(false);
+ }
+ std::vector<std::string> frequencies_vec = absl::StrSplit(frequencies, ',');
+ for (const auto& part : frequencies_vec) {
+ std::vector<std::string> frame_and_count = absl::StrSplit(part, ':');
+ if (frame_and_count.size() != 2) {
+ THROWF("Invalid format in cold bytes filter: $0", part);
+ }
+ size_t frame = std::stoi(frame_and_count[0]);
+ size_t count = std::stoi(frame_and_count[1]);
+ if (frame >= access_pattern.size()) {
+ THROW("access pattern exceeded end of file");
+ }
+ if (count > 0) {
+ access_pattern[frame] = true;
+ }
+ }
+ for (size_t i = 0; i < access_pattern.size(); i++) {
+ std::string label = access_pattern[i] ? "Hot" : "Cold";
+ size_t length;
+ size_t file_size = sink->input_file().data().size();
+ if (i * kAccessPatternFrameSize > file_size) {
+ THROW("access pattern exceeded end of file");
+ }
+ if (i * kAccessPatternFrameSize + kAccessPatternFrameSize > file_size) {
+ // We're at the last frame in the ELF, and it is not fully 32 KiB.
+ length = file_size % kAccessPatternFrameSize;
+ } else {
+ length = kAccessPatternFrameSize;
+ }
+ sink->AddFileRange("access_pattern", label,
+ i * kAccessPatternFrameSize, length);
+ }
+ }
+
+ void ReadLinkMapSymbols(RangeSink* sink) const {
+ if (!link_map_symbols_.has_value()) return;
+ const auto& symbols = *link_map_symbols_;
+ for (const auto& symbol : symbols) {
+ auto maybe_transformed_compile_unit =
+ bloaty_link_map::TransformCompileUnitForFuchsia(symbol.compile_unit);
+ auto demangled = ItaniumDemangle(symbol.name, sink->data_source());
+ if (maybe_transformed_compile_unit.has_value()) {
+ auto [transformed_compile_unit, maybe_rust_crate] =
+ *maybe_transformed_compile_unit;
+ if (maybe_rust_crate.has_value()) {
+ auto symbol_with_crate_id =
+ EncodeSymbolWithCrateId(demangled, *maybe_rust_crate);
+ sink->AddVMRange("link_map", symbol.addr, symbol.size,
+ symbol_with_crate_id);
+ continue;
+ }
+ }
+ sink->AddVMRange("link_map", symbol.addr, symbol.size, demangled);
+ }
+
+ if (!link_map_sections_.has_value()) return;
+ const auto& sections = *link_map_sections_;
+ for (const auto& section : sections) {
+ sink->AddVMRange("link_map", section.addr, section.size,
+ "[section " + section.name + "]");
+ }
+ }
+
+ void ReadLinkMapCompileUnits(RangeSink* sink) const {
+ if (!link_map_symbols_.has_value()) return;
+ const auto& symbols = *link_map_symbols_;
+ for (const auto& symbol : symbols) {
+ auto maybe_transformed_compile_unit =
+ bloaty_link_map::TransformCompileUnitForFuchsia(symbol.compile_unit);
+ if (maybe_transformed_compile_unit.has_value()) {
+ auto [transformed_compile_unit, maybe_rust_crate] =
+ *maybe_transformed_compile_unit;
+ sink->AddVMRange("link_map", symbol.addr, symbol.size,
+ transformed_compile_unit);
+ }
+ }
+
+ if (!link_map_sections_.has_value()) return;
+ const auto& sections = *link_map_sections_;
+ for (const auto& section : sections) {
+ sink->AddVMRange("link_map", section.addr, section.size,
+ "[section " + section.name + "]");
+ }
+ }
+
void ProcessFile(const std::vector<RangeSink*>& sinks) const override {
for (auto sink : sinks) {
if (verbose_level > 1) {
@@ -1295,11 +1433,16 @@
case DataSource::kRawSymbols:
case DataSource::kShortSymbols:
case DataSource::kFullSymbols:
+ ReadLinkMapSymbols(sink);
ReadELFSymbols(debug_file().file_data(), sink, nullptr, false);
break;
case DataSource::kArchiveMembers:
DoReadELFSections(sink, kReportByArchiveMember);
break;
+ case DataSource::kAccessPattern: {
+ ReadAccessPattern(sink);
+ break;
+ }
case DataSource::kCompileUnits: {
CheckNotObject("compileunits", sink);
SymbolTable symtab;
@@ -1315,6 +1458,7 @@
dwarf::File dwarf;
ReadDWARFSections(debug_file().file_data(), &dwarf, sink);
ReadDWARFCompileUnits(dwarf, symbol_map, sink);
+ ReadLinkMapCompileUnits(sink);
break;
}
case DataSource::kInlines: {
@@ -1333,6 +1477,7 @@
case DataSource::kSegments:
case DataSource::kSections:
case DataSource::kArchiveMembers:
+ case DataSource::kAccessPattern:
break;
default:
// Add these *after* processing all other data sources.
@@ -1396,15 +1541,23 @@
return ReadElfArchMode(file_data(), &info->arch, &info->mode);
}
+
+ private:
+ std::optional<std::vector<bloaty_link_map::Symbol>> link_map_symbols_ = std::nullopt;
+ std::optional<std::vector<bloaty_link_map::Section>> link_map_sections_ = std::nullopt;
};
} // namespace
-std::unique_ptr<ObjectFile> TryOpenELFFile(std::unique_ptr<InputFile>& file) {
+std::unique_ptr<ObjectFile> TryOpenELFFile(std::unique_ptr<InputFile>& file,
+ std::optional<std::string> link_map_file) {
ElfFile elf(file->data());
ArFile ar(file->data());
if (elf.IsOpen() || ar.IsOpen()) {
- return std::unique_ptr<ObjectFile>(new ElfObjectFile(std::move(file)));
+ if (link_map_file.has_value()) {
+ std::cerr << "Using link map: " << *link_map_file << std::endl;
+ }
+ return std::unique_ptr<ObjectFile>(new ElfObjectFile(std::move(file), link_map_file));
} else {
return nullptr;
}
diff --git a/src/link_map.cc b/src/link_map.cc
new file mode 100644
index 0000000..9eca7f8
--- /dev/null
+++ b/src/link_map.cc
@@ -0,0 +1,626 @@
+// Copyright 2020 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 "link_map.h"
+
+#include <algorithm>
+#include <functional>
+#include <iostream>
+#include <optional>
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <unordered_set>
+
+#include "bloaty.h"
+
+ABSL_ATTRIBUTE_NORETURN
+static void Throw(const char* str, int line) {
+ std::cerr << __FILE__ << ":" << line << ", " << str << std::endl;
+ throw bloaty::Error(str, __FILE__, line);
+}
+#define THROW(msg) Throw(msg, __LINE__)
+
+#define likely(x) __builtin_expect((x), 1)
+#define unlikely(x) __builtin_expect((x), 0)
+
+#define GUARD(x) if (unlikely(!(x)))
+
+namespace bloaty_link_map {
+
+namespace {
+
+// VMA LMA Size Align Out In Symbol
+re2::RE2 header_regex(R"(^\s*VMA\s*LMA\s*Size\s*Align\s*Out\s*In\s*Symbol)");
+
+// 194 194 13 1 .interp
+re2::RE2 line_regex(R"(\s*[0-9a-f]+\s+([0-9a-f]+)\s+([0-9a-f]+)\s+(\d+) ( *)(.*))");
+
+// obj/zircon/system/uapp/blobfs/blobfs.main.cc.o:(.rodata.main)
+re2::RE2 level2_regex(R"(^(.*):\((.*)\))");
+
+enum class Level { k1 = 1, k2 = 2, k3 = 3 };
+
+struct Token {
+ std::string_view line;
+ uint64_t address;
+ uint64_t size;
+ Level level;
+ std::optional<uint64_t> span;
+ std::string_view tok;
+};
+
+std::string_view into_string_view(const re2::StringPiece& s) {
+ return std::string_view(s.data(), s.size());
+}
+
+re2::RE2 PROMOTED_GLOBAL_NAME_DEMANGLED_PATTERN(R"( \((\.\d+)?\.llvm\.\d+\)$)");
+re2::RE2 PROMOTED_GLOBAL_NAME_RAW_PATTERN(R"((\.\d+)?\.llvm\.\d+$)");
+
+std::string StripLlvmPromotedGlobalNames(std::string_view name) {
+ auto llvm_pos = name.find(".llvm.");
+ if (llvm_pos == std::string_view::npos) {
+ return std::string(name);
+ }
+ if (absl::EndsWith(name, ")")) {
+ std::string name_rep(name);
+ RE2::Replace(&name_rep, PROMOTED_GLOBAL_NAME_DEMANGLED_PATTERN, "");
+ return name_rep;
+ }
+ std::string name_rep(name);
+ RE2::Replace(&name_rep, PROMOTED_GLOBAL_NAME_RAW_PATTERN, "");
+ return name_rep;
+}
+
+const std::string STRING_LITERAL_NAME = "string literal";
+
+std::string_view NormalizeName(std::string_view name) {
+ if (absl::StartsWith(name, ".L.str")) {
+ return STRING_LITERAL_NAME;
+ }
+ if (absl::EndsWith(name, " (.cfi)")) {
+ return name.substr(0, name.size() - 7);
+ }
+ return name;
+}
+
+// Decides whether a Level 3 token is an annotation.
+//
+// Returns:
+// A 2-tuple (is_annotation, next_thumb2_mode):
+// is_annotation: Whether |tok| is an annotation.
+// next_thumb2_mode: New |thumb2_mode| value, or None if keep old value.
+std::tuple<bool, std::optional<bool>> ParseArmAnnotations(std::string_view tok) {
+ // Annotations for ARM match '$t', '$d.1', but not '$_21::invoke'.
+ if (absl::StartsWith(tok, "$") && (tok.size() == 2 || (tok.size() >= 3 && tok[2] == '.'))) {
+ if (absl::StartsWith(tok, "$t")) {
+ // Is annotation, enter Thumb2 mode.
+ return std::tuple<bool, std::optional<bool>>{true, std::optional<bool>{true}};
+ }
+ if (absl::StartsWith(tok, "$a")) {
+ // Is annotation, enter ARM32 mode.
+ return std::tuple<bool, std::optional<bool>>{true, std::optional<bool>{false}};
+ }
+ // Is annotation, keep old |thumb2_mode| value.
+ return std::tuple<bool, std::optional<bool>>{true, std::nullopt};
+ }
+ // Not annotation, keep old |thumb2_mode| value.
+ return std::tuple<bool, std::optional<bool>>{false, std::nullopt};
+}
+
+void Tokenize(std::istringstream& lines, std::function<void(const Token&)> cb) {
+ std::string one_line;
+ // A Level 3 symbol can have |size == 0| in some situations (e.g., assembly
+ // code symbols). To provided better size estimates in this case, the "span"
+ // of a Level 3 symbol is computed as:
+ // (A) The |address| difference compared to the next Level 3 symbol.
+ // (B) If the Level 3 symbol is the last among Level 3 lines nested under a
+ // Level 2 line: The difference between the Level 3 symbol's |address|
+ // and the containing Level 2 line's end address.
+ // To handle (A), |lines| is visited using a one-step lookahead, using
+ // |sentinel| to handle the last line. To handle (B), |level2_end_address| is
+ // computed for each Level 2 line.
+ const std::string sentinel = "0 0 0 0 THE_END";
+ GUARD(RE2::FullMatch(sentinel, line_regex)) { THROW("sentinel must match regex"); }
+ uint64_t level2_end_address = 0;
+ bool thumb2_mode = false;
+
+ std::string line;
+ std::optional<uint64_t> address = std::nullopt;
+ uint64_t size = 0;
+ Level level = Level::k1;
+ std::string tok;
+
+ auto process_line = [&](const std::string& next_line) {
+ re2::StringPiece str_next_address;
+ re2::StringPiece str_next_size;
+ re2::StringPiece str_next_level;
+ re2::StringPiece next_tok;
+ GUARD(RE2::FullMatch(next_line, line_regex, &str_next_address, &str_next_size, (void*)NULL,
+ &str_next_level, &next_tok)) {
+ return;
+ }
+ uint64_t next_address = std::stoul(str_next_address.as_string(), nullptr, 16);
+ uint64_t next_size = std::stoul(str_next_size.as_string(), nullptr, 16);
+ int int_level = (str_next_level.size() / 8) + 1;
+ GUARD(int_level >= 1 && int_level <= 3) { THROW("invalid level"); }
+ Level next_level = static_cast<Level>(int_level);
+
+ if (next_level == Level::k3) {
+ GUARD(level == Level::k2 || level == Level::k3) {
+ THROW("Cannot jump from Level 1 to Level l3");
+ }
+ // Detect annotations. If found, maybe update |thumb2_mode|, then skip.
+ auto [is_annotation, next_thumb2_mode] = ParseArmAnnotations(into_string_view(next_tok));
+ if (is_annotation) {
+ if (next_thumb2_mode.has_value()) {
+ thumb2_mode = next_thumb2_mode.value();
+ }
+ // Skip annotations.
+ return;
+ }
+ if (thumb2_mode) {
+ // Adjust odd address to even. Alignment is not guanteed for all
+ // symbols (e.g., data, or x86), so this is judiciously applied.
+ next_address &= ~(1ULL);
+ }
+ } else {
+ // Resets on leaving Level 3.
+ thumb2_mode = false;
+ }
+
+ if (address.has_value()) {
+ std::optional<uint64_t> span = std::nullopt;
+ if (level == Level::k3) {
+ span = next_level == Level::k3 ? next_address : level2_end_address;
+ *span -= *address;
+ } else if (level == Level::k2) {
+ level2_end_address = *address + size;
+ }
+ cb(Token{
+ .line = line,
+ .address = *address,
+ .size = size,
+ .level = level,
+ .span = span,
+ .tok = tok,
+ });
+ }
+
+ line = next_line;
+ address = next_address;
+ size = next_size;
+ level = next_level;
+ tok = next_tok.as_string();
+ };
+ while (getline(lines, one_line)) {
+ process_line(one_line);
+ }
+ process_line(sentinel);
+}
+
+const std::string SECTION_BSS = ".bss";
+const std::string SECTION_BSS_REL_RO = ".bss.rel.ro";
+const std::string SECTION_DATA = ".data";
+const std::string SECTION_DATA_REL_RO = ".data.rel.ro";
+const std::string SECTION_DATA_REL_RO_LOCAL = ".data.rel.ro.local";
+const std::string SECTION_DEX = ".dex";
+const std::string SECTION_DEX_METHOD = ".dex.method";
+const std::string SECTION_OTHER = ".other";
+const std::string SECTION_PAK_NONTRANSLATED = ".pak.nontranslated";
+const std::string SECTION_PAK_TRANSLATIONS = ".pak.translations";
+const std::string SECTION_PART_END = ".part.end";
+const std::string SECTION_RODATA = ".rodata";
+const std::string SECTION_TEXT = ".text";
+// Used by SymbolGroup when they contain a mix of sections.
+const std::string SECTION_MULTIPLE = ".*";
+
+const std::unordered_set<std::string> BSS_SECTIONS = {
+ SECTION_BSS,
+ SECTION_BSS_REL_RO,
+ SECTION_PART_END,
+};
+
+} // namespace
+
+std::vector<Symbol> ParseLldLinkMap(const std::string& content) {
+ std::vector<Symbol> syms;
+ std::istringstream lines(content);
+ std::string one_line;
+
+ GUARD(getline(lines, one_line)) { THROW("must have at least the header line"); }
+ GUARD(header_regex.ok()) { THROW("can't compile header regex"); }
+ GUARD(RE2::FullMatch(one_line, header_regex)) { THROW("link map is not in lld v1 format"); }
+
+ // Example Format:
+ // VMA LMA Size Align Out In Symbol
+ // 194 194 13 1 .interp
+ // 194 194 13 1 <internal>:(.interp)
+ // 1a8 1a8 22d8 4 .ARM.exidx
+ // 1b0 1b0 8 4 obj/sandbox/syscall.o:(.ARM.exidx)
+ // 400 400 123400 64 .text
+ // 600 600 14 4 ...:(.text.OUTLINED_FUNCTION_0)
+ // 600 600 0 1 $x.3
+ // 600 600 14 1 OUTLINED_FUNCTION_0
+ // 123800 123800 20000 256 .rodata
+ // 123800 123800 4 4 ...:o:(.rodata._ZN3fooE.llvm.1234)
+ // 123800 123800 4 1 foo (.llvm.1234)
+ // 123804 123804 4 4 ...:o:(.rodata.bar.llvm.1234)
+ // 123804 123804 4 1 bar.llvm.1234
+ // ^ ^ ^
+ // Level 1 ^ ^
+ // Level 2 ^
+ // Level 3
+ //
+ // Level 1 (Out) specify sections or PROVIDE_HIDDEN lines.
+ // Level 2 (In) specify object paths and section names within objects, or '<internal>:...'.
+ // Level 3 (Symbol) specify symbol names or special names such as
+ // '.L_MergeGlobals'. Annotations such as '$d', $t.42' also appear at Level 3,
+ // but they are consumed by |tokenizer|, so don't appear hear.
+ GUARD(line_regex.ok()) { THROW("can't compile line regex"); }
+ GUARD(level2_regex.ok()) { THROW("can't compile level2 regex"); }
+
+ std::optional<std::string> cur_section;
+
+ bool cur_section_is_useful = false;
+
+ uint64_t promoted_name_count = 0;
+
+ // |is_partial| indicates that an eligible Level 3 line should be used to
+ // update |syms[-1].full_name| instead of creating a new symbol.
+ bool is_partial = false;
+
+ // Assembly code can create consecutive Level 3 lines with |size == 0|. These
+ // lines can represent
+ // (1) assembly functions (should form symbol), or
+ // (2) assembly labels (should NOT form symbol).
+ // It seems (2) correlates with the presence of a leading Level 3 line with
+ // |size > 0|. This gives rise to the following strategy: Each symbol S from
+ // a Level 3 line suppresses Level 3 lines with |address| less than
+ // |next_usable_address := S.address + S.size|.
+ uint64_t next_usable_address = 0;
+
+ bool in_partitions = false;
+ bool in_jump_table = false;
+ uint64_t jump_tables_count = 0;
+ uint64_t jump_entries_count = 0;
+ uint64_t mangled_start_idx = 0;
+
+ std::string cur_obj;
+ // Assembly code can create consecutive Level 3 lines with |size == 0|. These
+ // lines can represent
+ // (1) assembly functions (should form symbol), or
+ // (2) assembly labels (should NOT form symbol).
+ // It seems (2) correlates with the presence of a leading Level 3 line with
+ // |size > 0|. This gives rise to the following strategy: Each symbol S from
+ // a Level 3 line suppresses Level 3 lines with |address| less than
+ // |next_usable_address := S.address + S.size|.
+ Tokenize(lines, [&](const Token& token) {
+ // Level 1 data match the "Out" column. They specify sections or
+ // PROVIDE_HIDDEN lines.
+ if (token.level == Level::k1) {
+ // Ignore sections that belong to feature library partitions. Seeing a
+ // partition name is an indicator that we've entered a list of feature
+ // partitions. After these, a single .part.end section will follow to
+ // reserve memory at runtime. Seeing the .part.end section also marks the
+ // end of partition sections in the map file.
+ if (absl::EndsWith(token.tok, "_partition")) {
+ in_partitions = true;
+ } else if (token.tok == ".part.end") {
+ // Note that we want to retain .part.end section, so it's fine to
+ // restart processing on this section, rather than the next one.
+ in_partitions = false;
+ }
+
+ if (in_partitions) {
+ // For now, completely ignore feature partitions.
+ cur_section = std::nullopt;
+ cur_section_is_useful = false;
+ } else {
+ cur_section = token.tok;
+ // E.g., Want to convert "(.text._name)" -> "_name" later.
+ mangled_start_idx = cur_section->size() + 1;
+ cur_section_is_useful = ((BSS_SECTIONS.find(*cur_section) != BSS_SECTIONS.end()) ||
+ SECTION_RODATA == *cur_section || SECTION_TEXT == *cur_section ||
+ absl::StartsWith(*cur_section, SECTION_DATA));
+ }
+ } else if (cur_section_is_useful) {
+ // Level 2 data match the "In" column. They specify object paths and
+ // section names within objects, or '<internal>:...'.
+ if (token.level == Level::k2) {
+ std::string mangled_name;
+ re2::StringPiece paren_value;
+ GUARD(RE2::FullMatch(token.tok, level2_regex, &cur_obj, &paren_value)) {
+ THROW("Level 2 regex did not match");
+ }
+ bool in_jump_table = absl::StrContains(into_string_view(paren_value), ".L.cfi.jumptable");
+ if (in_jump_table) {
+ // Store each CFI jump table as a Level 2 symbol, whose Level 3
+ // details are discarded.
+ jump_tables_count++;
+ // Replaces 'lto.tmp' to prevent problem later.
+ cur_obj = "";
+ mangled_name = "** CFI jump table";
+ } else {
+ // E.g., '(.text.unlikely._name)' -> '_name'.
+ mangled_name = into_string_view(paren_value.substr(mangled_start_idx));
+ is_partial = true;
+ // As of 2017/11 LLD does not distinguish merged strings from other
+ // merged data. Feature request is filed under:
+ // https://bugs.llvm.org/show_bug.cgi?id=35248
+ if (cur_obj == "<internal>") {
+ if (cur_section.has_value() && *cur_section == ".rodata" && mangled_name == "") {
+ // Treat all <internal> sections within .rodata as as string
+ // literals. Some may hold numeric constants or other data, but
+ // there is currently no way to distinguish them.
+ mangled_name = "** lld merge strings";
+ } else {
+ // e.g. <internal>:(.text.thunk)
+ mangled_name = "** " + mangled_name;
+ }
+
+ is_partial = false;
+ cur_obj = "";
+ } else if (cur_obj == "lto.tmp" ||
+ absl::StrContains(into_string_view(cur_obj), "thinlto-cache")) {
+ cur_obj = "";
+ }
+ }
+
+ // Create a symbol here since there may be no ensuing Level 3 lines.
+ // But if there are, then the symbol can be modified later as sym[-1].
+ syms.push_back(Symbol{
+ .name = std::move(mangled_name),
+ .compile_unit = cur_obj,
+ .section = *cur_section,
+ .addr = token.address,
+ .size = token.size,
+ });
+ // Level 3 |address| is nested under Level 2, don't add |size|.
+ next_usable_address = token.address;
+ } else if (token.level == Level::k3) {
+ // Level 3 data match the "Symbol" column. They specify symbol names or
+ // special names such as '.L_MergeGlobals'. Annotations such as '$d',
+ // '$t.42' also appear at Level 3, but they are consumed by |tokenizer|,
+ // so don't appear hear.
+
+ // Handle .L.cfi.jumptable.
+ if (in_jump_table) {
+ // Level 3 entries in CFI jump tables are thunks with mangled names.
+ // Extracting them as symbols is not worthwhile; we only store the
+ // Level 2 symbol, and print the count for verbose output. For
+ // counting, '__typeid_' entries are excluded since they're likely
+ // just annotations.
+ if (!absl::StartsWith(token.tok, "__typeid_")) {
+ jump_entries_count++;
+ }
+ return;
+ }
+
+ // Ignore anything with '.L_MergedGlobals' prefix. This seems to only
+ // happen for ARM (32-bit) builds.
+ if (absl::StartsWith(token.tok, ".L_MergedGlobals")) {
+ return;
+ }
+
+ // Use |span| to decide whether to use a Level 3 line for Symbols. This
+ // is useful for two purposes:
+ // * This is a better indicator than |size|, which can be 0 for
+ // assembly functions.
+ // * If multiple Level 3 lines have the same starting address, this
+ // cause all but the last line to have |span > 0|. This dedups lines
+ // with identical symbol names (why do they exist?). Note that this
+ // also skips legitimate aliases, but that's desired because nm.py
+ // (downstream) assumes no aliases already exist.
+ if (*token.span > 0) {
+ std::string stripped_tok = StripLlvmPromotedGlobalNames(token.tok);
+ std::string tok;
+ if (token.tok.size() != stripped_tok.size()) {
+ promoted_name_count++;
+ tok = stripped_tok;
+ } else {
+ tok = token.tok;
+ }
+ tok = NormalizeName(tok);
+
+ // Handle special case where a partial symbol consumes bytes before
+ // the first Level 3 symbol.
+ if (is_partial && syms.back().addr < token.address) {
+ // Truncate the partial symbol and leave it without |full_name|.
+ // The data from the current line will form a new symbol.
+ syms.back().size = token.address - syms.back().addr;
+ next_usable_address = token.address;
+ is_partial = false;
+ }
+
+ if (is_partial) {
+ syms.back().name = tok;
+ syms.back().size =
+ token.size > 0 ? token.size : std::min(syms.back().size, *token.span);
+ next_usable_address = token.address + syms.back().size;
+ is_partial = false;
+ } else if (token.address >= next_usable_address) {
+ uint64_t size_to_use;
+ if (absl::StartsWith(tok, "__typeid_")) {
+ GUARD(token.size == 1) { THROW("Token size"); }
+ if (absl::EndsWith(tok, "_byte_array")) {
+ // CFI byte array table: |size| is inaccurate, so use |span|.
+ size_to_use = *token.span;
+ } else {
+ // Likely '_global_addr' or '_unique_member'. These should be:
+ // * Skipped since they're in CFI tables.
+ // * Suppressed (via |next_usable_address|) by another Level 3
+ // symbol.
+ // Anything that makes it here would be an anomaly worthy of
+ // investigation, so print warnings.
+ std::cerr << "Unrecognized __typeid_ symbol at " << token.address << std::endl;
+ return;
+ }
+ } else {
+ size_to_use = token.size > 0 ? token.size : *token.span;
+ }
+ syms.push_back(Symbol{
+ .name = std::move(tok),
+ .compile_unit = cur_obj,
+ .section = *cur_section,
+ .addr = token.address,
+ .size = size_to_use,
+ });
+
+ // Suppress symbols with overlapping |address|. This eliminates
+ // labels from assembly sources.
+ next_usable_address = token.address + size_to_use;
+ if (!cur_obj.empty()) {
+ syms.back().compile_unit = cur_obj;
+ }
+ }
+ }
+ }
+ }
+ });
+
+ if (promoted_name_count > 0) {
+ std::cerr << "Found " << promoted_name_count << " promoted global names" << std::endl;
+ }
+ if (jump_tables_count > 0) {
+ std::cerr << "Found " << jump_tables_count << " CFI jump tables with " << jump_entries_count
+ << "total entries" << std::endl;
+ }
+
+ return syms;
+}
+
+std::vector<Section> ParseLldLinkMapSections(const std::string& content) {
+ std::vector<Section> sections;
+
+ std::istringstream lines(content);
+ std::string one_line;
+
+ Tokenize(lines, [&](const Token& token) {
+ // Level 1 data match the "Out" column. They specify sections or
+ // PROVIDE_HIDDEN lines.
+ if (token.level == Level::k1) {
+ sections.push_back(Section{
+ .name = std::string(token.tok),
+ .addr = token.address,
+ .size = token.size,
+ });
+ }
+ });
+
+ return sections;
+}
+
+namespace {
+
+// ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o
+re2::RE2 library_crate_regex(
+ R"(\/[a-zA-Z0-9_]+\.[a-zA-Z0-9_-]+\.([a-zA-Z0-9_]+)\.[a-zA-Z0-9-]+.*\.rcgu\.o$)");
+
+// ./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.0.rcgu.o
+re2::RE2 bin_crate_regex(R"(\/[a-zA-Z0-9_-]+\.([a-zA-Z0-9_]+)\.[a-zA-Z0-9-]+.*\.rcgu\.o$)");
+
+// foobar.rlib(libregex_syntax-579ced0738b0164d-579ced0738b0164d.regex_syntax.c02sfxfu-cgu.13.rcgu.o)
+re2::RE2 rlib_crate_regex(R"(rlib\([a-zA-Z_\-0-9]+\.([a-zA-Z0-9_]+)\.[a-zA-Z0-9-]+.*\.rcgu\.o\)$)");
+
+// /usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/c/crt1.Scrt1.cc.o
+re2::RE2 zircon_lib_regex(R"(\/out\/[a-zA-Z0-9_-]+\.zircon\/.*\/obj\/system\/ulib\/(.*)\.o$)");
+
+// obj/out/default/fidling/gen/sdk/fidl/fuchsia.hardware.block/fuchsia.hardware.block_tables.fuchsia.hardware.block.fidl.tables.c.o
+re2::RE2 fidling_regex(R"(^obj\/out\/.*\/fidling\/gen\/(.*)\.o$)");
+
+// obj/zircon/public/lib/fidl_base/libfidl_base.a(libfidl_base.decoding.cc.o)
+re2::RE2 zircon_fidl_lib_regex(
+ R"(^obj\/zircon\/public\/lib\/fidl_base\/libfidl_base\.a\(libfidl_base\.(.*)\.cc\.o\)$)");
+
+// obj/zircon/system/uapp/blobfs/blobfs.main.cc.o
+re2::RE2 zircon_lib_regex2(R"(^obj\/zircon\/system\/(.*)\.o$)");
+
+} // namespace
+
+std::optional<std::tuple<std::string, std::optional<std::string>>> TransformCompileUnitForFuchsia(
+ const std::string& compile_unit) {
+ GUARD(library_crate_regex.ok()) { THROW("can't compile library crate regex"); }
+ {
+ std::string crate_name;
+ if (RE2::PartialMatch(compile_unit, library_crate_regex, &crate_name)) {
+ return std::tuple{"[crate: " + crate_name + "]", crate_name};
+ }
+ }
+
+ GUARD(bin_crate_regex.ok()) { THROW("can't compile bin crate regex"); }
+ {
+ std::string crate_name;
+ if (RE2::PartialMatch(compile_unit, bin_crate_regex, &crate_name)) {
+ return std::tuple{"[crate: " + crate_name + "]", crate_name};
+ }
+ }
+
+ GUARD(rlib_crate_regex.ok()) { THROW("can't compile rlib crate regex"); }
+ {
+ std::string crate_name;
+ if (RE2::PartialMatch(compile_unit, rlib_crate_regex, &crate_name)) {
+ return std::tuple{"[crate: " + crate_name + "]", crate_name};
+ }
+ }
+
+ GUARD(zircon_lib_regex.ok()) { THROW("can't compile zircon lib regex"); }
+ {
+ std::string cc_path;
+ if (RE2::PartialMatch(compile_unit, zircon_lib_regex, &cc_path)) {
+ // Remove the prefix in last path component
+ // c/crt1.Scrt1.cc -> c/Scrt1.cc
+ static re2::RE2 prefix_regex(R"(\/[a-zA-Z0-9\-_]+\.([a-zA-Z0-9\-_]+\.(cc|c))$)");
+ if (RE2::Replace(&cc_path, prefix_regex, R"(/\1)")) {
+ return std::tuple{"../../zircon/system/ulib/" + cc_path, std::nullopt};
+ }
+ }
+ }
+
+ GUARD(fidling_regex.ok()) { THROW("can't compile fidling regex"); }
+ {
+ std::string cc_path;
+ if (RE2::PartialMatch(compile_unit, fidling_regex, &cc_path)) {
+ // A: sdk/fidl/fuchsia.hardware.block/fuchsia.hardware.block_tables.fuchsia.hardware.block.fidl.tables.c
+ // B: sdk/fidl/fuchsia.hardware.block/fuchsia.hardware.block.fidl.tables.c
+ // A: sdk/fidl/fuchsia.hardware.block/fuchsia/hardware/block/c/fuchsia.hardware.block_c_client.fidl.client.c
+ // B: sdk/fidl/fuchsia.hardware.block/fuchsia/hardware/block/c/fidl.client.c
+ // A: sdk/fidl/fuchsia.io/fuchsia/io/llcpp/fuchsia.io_llcpp.fidl.cc
+ // B: sdk/fidl/fuchsia.io/fuchsia/io/llcpp/fidl.cc
+ static re2::RE2 prefix_regex(
+ R"([a-zA-Z0-9\-\.]+_[a-zA-Z0-9\-_]+\.([a-zA-Z0-9\-\.]+\.(c|cc))$)");
+ if (RE2::Replace(&cc_path, prefix_regex, R"(\1)")) {
+ return std::tuple{"fidling/gen/" + cc_path, std::nullopt};
+ }
+ }
+ }
+
+ GUARD(zircon_fidl_lib_regex.ok()) { THROW("can't compile zircon fidl lib regex"); }
+ {
+ std::string cc_name;
+ if (RE2::PartialMatch(compile_unit, zircon_fidl_lib_regex, &cc_name)) {
+ return std::tuple{"../../zircon/system/ulib/fidl/" + cc_name + ".cc", std::nullopt};
+ }
+ }
+
+ // Rust-specific special casing...
+ if (absl::StartsWith(compile_unit, "obj/third_party/rust_crates/compat/ring/libring-core.a")) {
+ return std::tuple<std::string, std::optional<std::string>>{
+ "[crate: ring]", std::optional<std::string>{"ring"}};
+ }
+
+ return std::nullopt;
+}
+
+} // namespace bloaty_link_map
diff --git a/src/link_map.h b/src/link_map.h
new file mode 100644
index 0000000..38d9b8f
--- /dev/null
+++ b/src/link_map.h
@@ -0,0 +1,128 @@
+// Copyright 2020 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.
+
+#ifndef BLOATY_LINK_MAP_H_
+#define BLOATY_LINK_MAP_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace bloaty_link_map {
+
+struct Symbol {
+ std::string name;
+ std::string compile_unit;
+ std::string section;
+ uint64_t addr;
+ uint64_t size;
+};
+
+// Parses a linker map file in lld v1 format.
+//
+// This function is adapted from
+// https://chromium.googlesource.com/chromium/src.git/+/master/tools/binary_size/libsupersize/linker_map_parser.py
+//
+// Args:
+// content: Contents of the link map, the first line of which is the header.
+//
+// Returns:
+// A vector of symbols.
+//
+// Example format:
+// VMA LMA Size Align Out In Symbol
+// 194 194 13 1 .interp
+// 194 194 13 1 <internal>:(.interp)
+// 1a8 1a8 22d8 4 .ARM.exidx
+// 1b0 1b0 8 4 obj/sandbox/syscall.o:(.ARM.exidx)
+// 400 400 123400 64 .text
+// 600 600 14 4 ...:(.text.OUTLINED_FUNCTION_0)
+// 600 600 0 1 $x.3
+// 600 600 14 1 OUTLINED_FUNCTION_0
+// 123800 123800 20000 256 .rodata
+// 123800 123800 4 4 ...:o:(.rodata._ZN3fooE.llvm.1234)
+// 123800 123800 4 1 foo (.llvm.1234)
+// 123804 123804 4 4 ...:o:(.rodata.bar.llvm.1234)
+// 123804 123804 4 1 bar.llvm.1234
+//
+// Notes:
+// - `VMA` and `LMA` are mostly identical.
+// - In a position-independent executable, the base address is zero.
+// Hence the minimum value of the addresses is quite small.
+// - Stripping of the binary will not relocate symbols, hence the addresses
+// will be the same between a stripped and unstripped binary.
+std::vector<Symbol> ParseLldLinkMap(const std::string& content);
+
+struct Section {
+ std::string name;
+ uint64_t addr;
+ uint64_t size;
+};
+
+std::vector<Section> ParseLldLinkMapSections(const std::string& content);
+
+// Transform the compile unit path parsed from link maps to a format
+// easier to organize, specialized to a Fuchsia build.
+// If the compile unit could not be understood by the transformer, it will
+// return `std::nullopt`. Since bloaty can still recover some compile unit
+// information from DWARF, we do not have to cover 100% here. For Rust however,
+// this allows setting a reasonable fallback compile unit at the crate level.
+// Therefore, the output of this transform should be inserted after the
+// usual DWARF-based compile unit data source.
+//
+// Examples:
+//
+// `./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o`
+// becomes `[crate: alloc]`.
+//
+// `./exe.unstripped/component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.2.rcgu.o.rcgu.o`
+// becomes `[crate: component_manager_lib]`.
+//
+// `./exe.unstripped/component_manager.libcm_fidl_translator.cm_fidl_translator.3a1fbbbh-cgu.0.rcgu.o.rcgu.o`
+// becomes `[crate: cm_fidl_translator]`.
+//
+// `./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.0.rcgu.o`
+// becomes `[crate: component_manager]`.
+//
+// `./exe.unstripped/component_manager.libfidl_fuchsia_io.fidl_fuchsia_io.3a1fbbbh-cgu.0.rcgu.o.rcgu.o`
+// becomes `[crate: fidl_fuchsia_io]`.
+//
+// `/usr/local/google/home/yifeit/vg/out/default/obj/third_party/rust_crates/libregex_syntax-579ced0738b0164d.rlib(libregex_syntax-579ced0738b0164d-579ced0738b0164d.regex_syntax.c02sfxfu-cgu.13.rcgu.o)`
+// becomes `[crate: regex_syntax]`.
+//
+// `/usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/c/crt1.Scrt1.cc.o`
+// becomes `../../zircon/system/ulib/c/Scrt1.cc`.
+//
+// `obj/out/default/fidling/gen/sdk/fidl/fuchsia.hardware.block/fuchsia.hardware.block_tables.fuchsia.hardware.block.fidl.tables.c.o`
+// becomes `fidling/gen/sdk/fidl/fuchsia.hardware.block/fuchsia.hardware.block.fidl.tables.c`.
+//
+// `obj/zircon/public/lib/fidl_base/libfidl_base.a(libfidl_base.decoding.cc.o)`
+// becomes `../../zircon/system/ulib/fidl/decoding.cc`
+//
+// `obj/zircon/system/uapp/blobfs/blobfs.main.cc.o`
+// becomes `../../zircon/system/uapp/blobfs/main.cc`
+//
+// `obj/zircon/system/ulib/trace-provider/libtrace-provider-with-fdio.a(libtrace-provider-with-fdio.fdio_connect.cc.o)`
+// is not suppported.
+//
+// `obj/zircon/public/lib/lz4/liblz4.a(liblz4.lz4hc.c.o)` is not supported.
+std::optional<std::tuple<std::string, std::optional<std::string>>> TransformCompileUnitForFuchsia(
+ const std::string& compile_unit);
+
+} // namespace bloaty_link_map
+
+#endif // BLOATY_LINK_MAP_H_
diff --git a/src/report.proto b/src/report.proto
new file mode 100644
index 0000000..e4cab01
--- /dev/null
+++ b/src/report.proto
@@ -0,0 +1,41 @@
+// Copyright 2020 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.
+
+syntax = "proto3";
+option cc_enable_arenas = true;
+
+package bloaty_report;
+
+message SizeInfo {
+ uint32 file_actual = 1;
+ uint32 vm_actual = 2;
+}
+
+message Symbol {
+ SizeInfo sizes = 1;
+ string name = 2;
+ string maybe_rust_crate = 3;
+}
+
+message CompileUnit {
+ SizeInfo sizes = 1;
+ repeated Symbol symbols = 2;
+ string name = 3;
+}
+
+message Report {
+ repeated CompileUnit compile_units = 1;
+ uint32 file_total = 2;
+ uint32 vm_total = 3;
+}
diff --git a/src/write_bloaty_report.cc b/src/write_bloaty_report.cc
new file mode 100644
index 0000000..a22fbad
--- /dev/null
+++ b/src/write_bloaty_report.cc
@@ -0,0 +1,131 @@
+// Copyright 2020 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 <stdexcept>
+
+#include "absl/strings/match.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/substitute.h"
+
+#include "google/protobuf/arena.h"
+
+#include "bloaty.h"
+#include "report.pb.h"
+
+namespace bloaty {
+
+void RollupOutput::PrintToProtobuf(std::ostream* out) const {
+ using namespace ::bloaty_report;
+
+ 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);
+
+ ([&] {
+ // 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) {
+ bool found_hot = false;
+ for (const auto& access : toplevel_row_.sorted_children) {
+ if (access.name == "Hot") {
+ found_hot = true;
+ }
+ if (access.name == "Cold") {
+ compile_unit_row = &access;
+ break;
+ }
+ }
+ if (compile_unit_row == &toplevel_row_) {
+ if (!found_hot) {
+ THROWF("Could not find hot/cold regions in the binary, top-level row has $0 children "
+ "([$1, ...]), "
+ "next level row has $2 children ([$3, ...])",
+ toplevel_row_.sorted_children.size(),
+ toplevel_row_.sorted_children.front().name,
+ toplevel_row_.sorted_children.front().sorted_children.size(),
+ toplevel_row_.sorted_children.front().sorted_children.front().name);
+ } else {
+ // No cold regions, hence nothing to output.
+ return;
+ }
+ }
+ }
+
+ for (const auto& child_row : compile_unit_row->sorted_children) {
+ 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);
+ Symbol* symbol = compile_unit->add_symbols();
+ symbol->set_allocated_sizes(info);
+ auto decoded_symbol = DecodeSymbolWithCrateId(symbol_row.name);
+ symbol->set_name(decoded_symbol.symbol);
+ symbol->set_maybe_rust_crate(decoded_symbol.crate);
+ }
+ if (child_row.sorted_children.empty()) {
+ // 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);
+ 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);
+ compile_unit->set_allocated_sizes(info);
+ compile_unit->set_name(child_row.name);
+ }
+ })();
+
+ bool good = report->SerializeToOstream(out);
+ if (!good) {
+ throw std::runtime_error("Failed to serialize report");
+ }
+ out->flush();
+}
+
+namespace {
+
+const std::string kSeparator = ", in crate ";
+
+} // namespace
+
+std::string EncodeSymbolWithCrateId(absl::string_view symbol, absl::string_view crate) {
+ // The encoding format is "$symbol, in crate $crate". Since regular C++ and
+ // Rust code is extremely unlikely to produce the word "in crate", This format
+ // is unlikely to cause ambiguities, while staying readable if bloaty is
+ // invoked from the command line.
+ return std::string(symbol) + kSeparator + std::string(crate);
+}
+
+DecodeCrateIdResult DecodeSymbolWithCrateId(absl::string_view symbol) {
+ if (absl::StrContains(symbol, kSeparator)) {
+ std::vector<std::string> parts = absl::StrSplit(symbol, kSeparator);
+ if (parts.size() != 2) {
+ throw std::runtime_error("Unexpected symbol when decoding: " + std::string(symbol));
+ }
+ return DecodeCrateIdResult{.symbol = parts[0], .crate = parts[1]};
+ } else {
+ return DecodeCrateIdResult{.symbol = std::string(symbol)};
+ }
+}
+
+} // namespace bloaty
diff --git a/tests/bloaty_misc_test.cc b/tests/bloaty_misc_test.cc
index ef23d5a..8bee15a 100644
--- a/tests/bloaty_misc_test.cc
+++ b/tests/bloaty_misc_test.cc
@@ -51,3 +51,24 @@
RunBloaty(args); // Heavily multithreaded test.
EXPECT_EQ(top_row_->filesize, file_size * 100);
}
+
+TEST(GetPathStem, Normal) {
+ EXPECT_EQ(bloaty::GetPathStem("foo"), "foo");
+ EXPECT_EQ(bloaty::GetPathStem("foo/bar/baz"), "baz");
+ EXPECT_EQ(bloaty::GetPathStem("/foo/bar/baz"), "baz");
+ EXPECT_EQ(bloaty::GetPathStem("/foo/../bar/baz"), "baz");
+}
+
+TEST(GetPathStem, DotSuffix) {
+ EXPECT_EQ(bloaty::GetPathStem("foo.b"), "foo");
+ EXPECT_EQ(bloaty::GetPathStem("foo/bar/baz.b"), "baz");
+ EXPECT_EQ(bloaty::GetPathStem("/foo/bar/baz.b"), "baz");
+ EXPECT_EQ(bloaty::GetPathStem("/foo/../bar/baz.b"), "baz");
+}
+
+TEST(GetPathStem, DotPrefix) {
+ EXPECT_EQ(bloaty::GetPathStem(".foo"), ".foo");
+ EXPECT_EQ(bloaty::GetPathStem("foo/bar/.baz"), ".baz");
+ EXPECT_EQ(bloaty::GetPathStem("/foo/bar/.baz"), ".baz");
+ EXPECT_EQ(bloaty::GetPathStem("/foo/../bar/.baz"), ".baz");
+}
diff --git a/tests/bloaty_report_test.cc b/tests/bloaty_report_test.cc
new file mode 100644
index 0000000..aad4e47
--- /dev/null
+++ b/tests/bloaty_report_test.cc
@@ -0,0 +1,208 @@
+// Copyright 2020 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 "test.h"
+#include "report.pb.h"
+
+TEST_F(BloatyTest, ProtobufOutput) {
+ std::string file = "05-binary.bin";
+ uint64_t size;
+ ASSERT_TRUE(GetFileSize(file, &size));
+
+ RunBloaty({"bloaty", "-d", "compileunits,symbols", file});
+
+ // Select protobuf output.
+ std::ostringstream stream;
+ bloaty::OutputOptions options;
+ options.output_format = bloaty::OutputFormat::kProtobuf;
+ output_->Print(options, &stream);
+
+ // Check Protobuf output.
+ bloaty_report::Report report;
+ ASSERT_TRUE(report.ParseFromString(stream.str()));
+
+ // These are the internal structure of `05-binary.bin`.
+ // They shouldn't change if the test data don't change.
+ // See below for a CSV version of the same output; here we ensure that
+ // the Protobuf output matches the CSV version.
+ // See also `BloatyTest.SimpleBinary` in `bloaty_test.cc`.
+ ASSERT_EQ(21, report.compile_units().size());
+ auto& bar_o = report.compile_units()[0];
+ ASSERT_EQ("bar.o.c", bar_o.name());
+ EXPECT_NEAR(4.50, bar_o.sizes().file_actual() / 1024.0, .01);
+ EXPECT_NEAR(3.99, bar_o.sizes().vm_actual() / 1024.0, .01);
+
+ ASSERT_GT(bar_o.symbols().size(), 0);
+ auto& bar_x = bar_o.symbols()[0];
+ ASSERT_EQ("bar_x", bar_x.name());
+ EXPECT_NEAR(3.94, bar_x.sizes().file_actual() / 1024.0, .01);
+ EXPECT_NEAR(3.91, bar_x.sizes().vm_actual() / 1024.0, .01);
+
+ ASSERT_GT(bar_o.symbols().size(), 1);
+ auto& debug_info = bar_o.symbols()[1];
+ ASSERT_EQ("[section .debug_info]", debug_info.name());
+ EXPECT_EQ(169, debug_info.sizes().file_actual());
+ EXPECT_EQ(0, debug_info.sizes().vm_actual());
+
+ EXPECT_NEAR(14.1, report.file_total() / 1024.0, .1);
+ EXPECT_NEAR(10.3, report.vm_total() / 1024.0, .1);
+}
+
+TEST_F(BloatyTest, ProtobufOutputFilterHotSymbolsAllHot) {
+ std::string file = "05-binary.bin";
+ uint64_t size;
+ ASSERT_TRUE(GetFileSize(file, &size));
+
+ RunBloaty({"bloaty", "-d", "accesspattern,compileunits,symbols", file,
+ // The first 32 KiB frame is accessed 100 times.
+ "--cold-bytes-filter", "0:100",
+ "--access-pattern-frame-size", "32768"});
+
+ // Select protobuf output.
+ std::ostringstream stream;
+ bloaty::OutputOptions options;
+ options.output_format = bloaty::OutputFormat::kProtobuf;
+ output_->Print(options, &stream);
+
+ // Check Protobuf output.
+ bloaty_report::Report report;
+ ASSERT_TRUE(report.ParseFromString(stream.str()));
+
+ // Since the test binary `05-binary.bin` is smaller than 32 KiB,
+ // every symbol (except zero-filled segments synthesized at run-time)
+ // inside the file will be hot, as designated by our command line
+ // arguments. So the list of compile units should be empty.
+ ASSERT_EQ(0, report.compile_units().size());
+
+ EXPECT_NEAR(14.1, report.file_total() / 1024.0, .1);
+ EXPECT_NEAR(10.3, report.vm_total() / 1024.0, .1);
+}
+
+TEST_F(BloatyTest, ProtobufOutputFilterHotSymbols) {
+ std::string file = "05-binary.bin";
+ uint64_t size;
+ ASSERT_TRUE(GetFileSize(file, &size));
+
+ RunBloaty({"bloaty", "-d", "accesspattern,compileunits,symbols", file,
+ // Default frame size is 8 KiB.
+ // The first 8 KiB frame is accessed 100 times.
+ "--cold-bytes-filter", "0:100"});
+
+ // Select protobuf output.
+ std::ostringstream stream;
+ bloaty::OutputOptions options;
+ options.output_format = bloaty::OutputFormat::kProtobuf;
+ output_->Print(options, &stream);
+
+ // Check Protobuf output.
+ bloaty_report::Report report;
+ ASSERT_TRUE(report.ParseFromString(stream.str()));
+
+ // This test is only run on x86_64.
+ // The test binary `05-binary.bin` is around 14 KiB.
+ // We would expect to see some compile units.
+ EXPECT_NEAR(12, report.compile_units().size(), 1);
+
+ EXPECT_NEAR(14.1, static_cast<double>(size) / 1024.0, .1);
+ EXPECT_NEAR(14.1, report.file_total() / 1024.0, .1);
+ EXPECT_NEAR(10.3, report.vm_total() / 1024.0, .1);
+}
+
+TEST_F(BloatyTest, ProtobufOutputFilterHotSymbolsPatternTooLarge) {
+ std::string file = "05-binary.bin";
+ std::string errmsg = "access pattern exceeded end of file";
+ AssertBloatyFails({"bloaty", "-d", "accesspattern,compileunits,symbols", file,
+ // Default frame size is 8 KiB.
+ // The file is 14 KiB, so specifying the non-existent
+ // third frame should fail.
+ "--cold-bytes-filter", "0:100,1:100,2:100"}, errmsg);
+}
+
+// Here is a regular bloaty print-out on the same file for reference:
+//
+// FILE SIZE VM SIZE
+// -------------- --------------
+// 30.9% 4.36Ki 38.8% 3.99Ki bar.o.c
+// 90.3% 3.94Ki 97.9% 3.91Ki bar_x
+// 3.8% 169 0.0% 0 [section .debug_info]
+// 2.4% 109 1.9% 76 bar_func
+// 1.3% 58 0.0% 0 [section .debug_line]
+// 0.8% 34 0.1% 4 bar_y
+// 0.7% 31 0.0% 0 [section .debug_str]
+// 0.7% 30 0.1% 4 bar_z
+// 3.9% 564 38.8% 3.98Ki foo.o.c
+// 5.3% 30 98.0% 3.91Ki foo_x
+// 27.7% 156 0.0% 0 [section .debug_abbrev]
+// 26.2% 148 0.0% 0 [section .debug_info]
+// 19.3% 109 1.9% 76 foo_func
+// 10.3% 58 0.0% 0 [section .debug_line]
+// 5.9% 33 0.0% 0 [section .debug_str]
+// 5.3% 30 0.1% 4 foo_y
+// 15.5% 2.19Ki 0.0% 0 [ELF Headers]
+// 91.4% 2.00Ki NAN% 0 [ELF Headers]
+// 5.7% 128 NAN% 0 __libc_csu_init
+// 2.9% 64 NAN% 0 _IO_stdin_used
+// 12.1% 1.71Ki 0.0% 0 [Unmapped]
+// 11.6% 1.64Ki 0.0% 0 [section .symtab]
+// 78.6% 1.29Ki NAN% 0 [section .symtab]
+// 17.1% 288 NAN% 0 __libc_csu_init
+// 1.4% 24 NAN% 0 _IO_stdin_used
+// 1.4% 24 NAN% 0 __libc_csu_fini
+// 1.4% 24 NAN% 0 completed.6973
+// 4.1% 593 5.6% 593 [LOAD #2 [RX]]
+// 3.9% 561 0.0% 0 [section .strtab]
+// 53.5% 300 NAN% 0 [section .strtab]
+// 38.3% 215 NAN% 0 __libc_csu_init
+// 2.9% 16 NAN% 0 __libc_csu_fini
+// 2.7% 15 NAN% 0 _IO_stdin_used
+// 2.7% 15 NAN% 0 completed.6973
+// 3.2% 464 4.4% 464 [section .dynamic]
+// 3.0% 430 3.9% 405 [20 Others]
+// 2.5% 356 3.4% 356 [section .text]
+// 71.1% 253 71.1% 253 [section .text]
+// 28.4% 101 28.4% 101 __libc_csu_init
+// 0.6% 2 0.6% 2 __libc_csu_fini
+// 2.3% 328 0.0% 0 [section .shstrtab]
+// 1.5% 220 0.4% 46 main.o.c
+// 37.3% 82 0.0% 0 [section .debug_info]
+// 34.1% 75 100.0% 46 main
+// 26.8% 59 0.0% 0 [section .debug_line]
+// 1.8% 4 0.0% 0 [section .debug_str]
+// 1.5% 212 2.0% 212 [section .eh_frame]
+// 54.7% 116 54.7% 116 [section .eh_frame]
+// 34.0% 72 34.0% 72 __libc_csu_init
+// 11.3% 24 11.3% 24 __libc_csu_fini
+// 1.0% 144 0.0% 0 [section .debug_aranges]
+// 0.7% 101 0.0% 0 [section .debug_abbrev]
+// 0.6% 93 0.0% 0
+// 0.5% 72 0.7% 72 [section .dynsym]
+// 0.4% 56 0.5% 56 [section .dynstr]
+// 0.2% 24 0.5% 52 [LOAD #3 [RW]]
+// 0.3% 48 0.5% 48 [section .plt]
+// 0.3% 48 0.5% 48 [section .rela.plt]
+// 50.0% 24 50.0% 24 [section .rela.plt]
+// 50.0% 24 50.0% 24 __libc_csu_init
+// 100.0% 14.1Ki 100.0% 10.3Ki TOTAL
+
+TEST_F(BloatyTest, EncodeSymbolWithCrate) {
+ EXPECT_EQ(bloaty::EncodeSymbolWithCrateId("foo", "bar"), "foo, in crate bar");
+}
+
+TEST_F(BloatyTest, DecodeSymbolWithCrate) {
+ auto expected1 = bloaty::DecodeCrateIdResult{.symbol = "foo", .crate = ""};
+ EXPECT_EQ(bloaty::DecodeSymbolWithCrateId("foo"), expected1);
+ auto expected2 = bloaty::DecodeCrateIdResult{.symbol = "foo", .crate = "bar"};
+ EXPECT_EQ(bloaty::DecodeSymbolWithCrateId("foo, in crate bar"), expected2);
+}
diff --git a/tests/link_map_test.cc b/tests/link_map_test.cc
new file mode 100644
index 0000000..e729fcf
--- /dev/null
+++ b/tests/link_map_test.cc
@@ -0,0 +1,971 @@
+// Copyright 2020 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 "link_map.h"
+
+#include <fstream>
+#include <tuple>
+
+#include "bloaty.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+TEST(TransformCompileUnitForFuchsiaTest, RustLibraryCrate1) {
+ auto result = bloaty_link_map::TransformCompileUnitForFuchsia(
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o");
+ ASSERT_TRUE(result.has_value());
+ ASSERT_EQ(std::get<0>(*result), "[crate: alloc]");
+ ASSERT_EQ(*std::get<1>(*result), "alloc");
+}
+
+TEST(TransformCompileUnitForFuchsiaTest, RustLibraryCrate2) {
+ auto result = bloaty_link_map::TransformCompileUnitForFuchsia(
+ "./exe.unstripped/"
+ "component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.2.rcgu.o.rcgu."
+ "o");
+ ASSERT_TRUE(result.has_value());
+ ASSERT_EQ(std::get<0>(*result), "[crate: component_manager_lib]");
+ ASSERT_EQ(*std::get<1>(*result), "component_manager_lib");
+}
+
+TEST(TransformCompileUnitForFuchsiaTest, RustLibraryCrate3) {
+ auto result = bloaty_link_map::TransformCompileUnitForFuchsia(
+ "./exe.unstripped/"
+ "component_manager.libcm_fidl_translator.cm_fidl_translator.3a1fbbbh-cgu.0.rcgu.o.rcgu.o");
+ ASSERT_TRUE(result.has_value());
+ ASSERT_EQ(std::get<0>(*result), "[crate: cm_fidl_translator]");
+ ASSERT_EQ(*std::get<1>(*result), "cm_fidl_translator");
+}
+
+TEST(TransformCompileUnitForFuchsiaTest, RustLibraryCrate4) {
+ auto result = bloaty_link_map::TransformCompileUnitForFuchsia(
+ "./exe.unstripped/"
+ "component_manager.libfidl_fuchsia_io.fidl_fuchsia_io.3a1fbbbh-cgu.0.rcgu.o.rcgu.o");
+ ASSERT_TRUE(result.has_value());
+ ASSERT_EQ(std::get<0>(*result), "[crate: fidl_fuchsia_io]");
+ ASSERT_EQ(*std::get<1>(*result), "fidl_fuchsia_io");
+}
+
+TEST(TransformCompileUnitForFuchsiaTest, RustBinaryCrate) {
+ auto result = bloaty_link_map::TransformCompileUnitForFuchsia(
+ "./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.0.rcgu.o");
+ ASSERT_TRUE(result.has_value());
+ ASSERT_EQ(std::get<0>(*result), "[crate: component_manager]");
+ ASSERT_EQ(*std::get<1>(*result), "component_manager");
+}
+
+TEST(TransformCompileUnitForFuchsiaTest, RustRLibCrate) {
+ auto result = bloaty_link_map::TransformCompileUnitForFuchsia(
+ "/usr/local/google/home/yifeit/vg/out/default/obj/third_party/rust_crates/"
+ "libregex_syntax-579ced0738b0164d.rlib(libregex_syntax-579ced0738b0164d-579ced0738b0164d."
+ "regex_syntax.c02sfxfu-cgu.13.rcgu.o)");
+ ASSERT_TRUE(result.has_value());
+ ASSERT_EQ(std::get<0>(*result), "[crate: regex_syntax]");
+ ASSERT_EQ(*std::get<1>(*result), "regex_syntax");
+}
+
+TEST(TransformCompileUnitForFuchsiaTest, ZirconLib) {
+ auto result = bloaty_link_map::TransformCompileUnitForFuchsia(
+ "/usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/"
+ "c/crt1.Scrt1.cc.o");
+ ASSERT_TRUE(result.has_value());
+ ASSERT_EQ(std::get<0>(*result), "../../zircon/system/ulib/c/Scrt1.cc");
+ ASSERT_EQ(std::get<1>(*result), std::nullopt);
+}
+
+TEST(TransformCompileUnitForFuchsiaTest, Fidling1) {
+ auto result = bloaty_link_map::TransformCompileUnitForFuchsia(
+ "obj/out/default/fidling/gen/sdk/fidl/fuchsia.hardware.block/"
+ "fuchsia.hardware.block_tables.fuchsia.hardware.block.fidl.tables.c.o");
+ ASSERT_TRUE(result.has_value());
+ ASSERT_EQ(std::get<0>(*result),
+ "fidling/gen/sdk/fidl/fuchsia.hardware.block/fuchsia.hardware.block.fidl.tables.c");
+ ASSERT_EQ(std::get<1>(*result), std::nullopt);
+}
+
+TEST(TransformCompileUnitForFuchsiaTest, Fidling2) {
+ auto result = bloaty_link_map::TransformCompileUnitForFuchsia(
+ "obj/out/default/fidling/gen/sdk/fidl/fuchsia.hardware.block/fuchsia/hardware/block/c/"
+ "fuchsia.hardware.block_c_client.fidl.client.c.o");
+ ASSERT_TRUE(result.has_value());
+ ASSERT_EQ(std::get<0>(*result),
+ "fidling/gen/sdk/fidl/fuchsia.hardware.block/fuchsia/hardware/block/c/fidl.client.c");
+ ASSERT_EQ(std::get<1>(*result), std::nullopt);
+}
+
+TEST(TransformCompileUnitForFuchsiaTest, Fidling3) {
+ auto result = bloaty_link_map::TransformCompileUnitForFuchsia(
+ "obj/out/default/fidling/gen/sdk/fidl/fuchsia.security.resource/fuchsia/security/resource/"
+ "llcpp/fuchsia.security.resource_llcpp.fidl.cc.o");
+ ASSERT_TRUE(result.has_value());
+ ASSERT_EQ(
+ std::get<0>(*result),
+ "fidling/gen/sdk/fidl/fuchsia.security.resource/fuchsia/security/resource/llcpp/fidl.cc");
+ ASSERT_EQ(std::get<1>(*result), std::nullopt);
+}
+
+TEST(TransformCompileUnitForFuchsiaTest, ZirconFidlLib) {
+ auto result = bloaty_link_map::TransformCompileUnitForFuchsia(
+ "obj/zircon/public/lib/fidl_base/libfidl_base.a(libfidl_base.decoding.cc.o)");
+ ASSERT_TRUE(result.has_value());
+ ASSERT_EQ(std::get<0>(*result), "../../zircon/system/ulib/fidl/decoding.cc");
+ ASSERT_EQ(std::get<1>(*result), std::nullopt);
+}
+
+TEST(TransformCompileUnitForFuchsiaTest, PassthroughUnknown) {
+ auto result = bloaty_link_map::TransformCompileUnitForFuchsia("foobar");
+ ASSERT_FALSE(result.has_value());
+}
+
+std::string LoadLinkMapFile(const std::string& name) {
+ std::ifstream infile(name);
+ std::string link_map;
+
+ // Strip comments and empty lines.
+ for (std::string line; getline(infile, line);) {
+ if (line.empty()) continue;
+ if (line[0] == '#') continue;
+ link_map += line;
+ link_map += '\n';
+ }
+
+ absl::StripLeadingAsciiWhitespace(&link_map);
+ absl::StripTrailingAsciiWhitespace(&link_map);
+
+ return link_map;
+}
+
+TEST(LinkMapTest, Empty) {
+ const std::string link_map = " VMA LMA Size Align Out In Symbol\n";
+ auto symbols = bloaty_link_map::ParseLldLinkMap(link_map);
+ ASSERT_EQ(symbols.size(), 0);
+}
+
+TEST(LinkMapTest, ExampleCpp) {
+ std::string link_map = LoadLinkMapFile("example_cpp.map");
+ absl::StripLeadingAsciiWhitespace(&link_map);
+ absl::StripTrailingAsciiWhitespace(&link_map);
+
+ auto symbols = bloaty_link_map::ParseLldLinkMap(link_map);
+
+ struct Golden {
+ uint64_t addr;
+ uint64_t size;
+ std::string name;
+ std::string compile_unit;
+ std::string section;
+ };
+ std::vector<Golden> goldens = {
+ {0x4380, 0x14, "main", "obj/zircon/system/uapp/blobfs/blobfs.main.cc.o", ".rodata"},
+ {0x4394, 0x8a18, "** lld merge strings", "", ".rodata"},
+ {0xcdb0, 0xa8, "** lld merge strings", "", ".rodata"},
+ {0xce58, 0xc, "HandlefifonullableTable",
+ "obj/out/default/fidling/gen/sdk/fidl/fuchsia.hardware.block/"
+ "fuchsia.hardware.block_tables.fuchsia.hardware.block.fidl.tables.c.o",
+ ".rodata"},
+ {0xce64, 0xc, "HandlevmononnullableTable",
+ "obj/out/default/fidling/gen/sdk/fidl/fuchsia.hardware.block/"
+ "fuchsia.hardware.block_tables.fuchsia.hardware.block.fidl.tables.c.o",
+ ".rodata"},
+ {0xce70, 0xc, "HandleresourcenonnullableTable",
+ "obj/out/default/fidling/gen/sdk/fidl/fuchsia.security.resource/"
+ "fuchsia.security.resource_tables.fuchsia.security.resource.fidl.tables.c.o",
+ ".rodata"},
+ {0xce7c, 0xa, "_ZN5llcpp7fuchsia2io8NodeInfo9reset_ptrEON4fidl12tracking_ptrIvEE",
+ "obj/out/default/fidling/gen/sdk/fidl/fuchsia.io/fuchsia/io/llcpp/"
+ "fuchsia.io_llcpp.fidl.cc.o",
+ ".rodata"},
+ {0xce86, 0x2b, "fidl::kErrorWriteFailed",
+ "obj/out/default/fidling/gen/sdk/fidl/fuchsia.io/fuchsia/io/llcpp/"
+ "fuchsia.io_llcpp.fidl.cc.o",
+ ".rodata"},
+ {0xceb4, 0xc, "Request15fuchsia_io_NodenonnullableTable",
+ "obj/out/default/fidling/gen/sdk/fidl/fuchsia.io/"
+ "fuchsia.io_tables.fuchsia.io.fidl.tables.c.o",
+ ".rodata"},
+ {0xcec0, 0x8, "String4096nonnullableTable",
+ "obj/out/default/fidling/gen/sdk/fidl/fuchsia.io/"
+ "fuchsia.io_tables.fuchsia.io.fidl.tables.c.o",
+ ".rodata"},
+ {0xd030, 0x100, "** lld merge strings", "", ".rodata"},
+ {0xd130, 0xc, "HandleeventnonnullableTable",
+ "obj/out/default/fidling/gen/sdk/fidl/fuchsia.fs/"
+ "fuchsia.fs_tables.fuchsia.fs.fidl.tables.c.o",
+ ".rodata"},
+ {0xd13c, 0x8, "String32nonnullableTable",
+ "obj/out/default/fidling/gen/sdk/fidl/fuchsia.fs/"
+ "fuchsia.fs_tables.fuchsia.fs.fidl.tables.c.o",
+ ".rodata"},
+ {0xfb38, 0x9c, "LZ4HC_compress_generic_internal.clTable",
+ "obj/zircon/public/lib/lz4/liblz4.a(liblz4.lz4hc.c.o)", ".rodata"},
+ {0xfbd4, 0x10, "XXH32_finalize", "obj/zircon/public/lib/lz4/liblz4.a(liblz4.xxhash.c.o)",
+ ".rodata"},
+ {0xfbe4, 0x27, "kServicePath",
+ "obj/zircon/system/ulib/trace-provider/"
+ "libtrace-provider-with-fdio.a(libtrace-provider-with-fdio.fdio_connect.cc.o)",
+ ".rodata"},
+ {0x21000, 0x14, "_start",
+ "/usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/"
+ "c/crt1.Scrt1.cc.o",
+ ".text"},
+ {0x21014, 0x494, "main", "obj/zircon/system/uapp/blobfs/blobfs.main.cc.o", ".text"},
+ {0x3a04c, 0x8, "non-virtual thunk to blobfs::TransactionManager::~TransactionManager()",
+ "obj/zircon/system/ulib/blobfs/libblobfs.a(libblobfs.blobfs.cc.o)", ".text"},
+ {0x108120, 0xdc, "trace::internal::Session::~Session()",
+ "obj/zircon/system/ulib/trace-provider/"
+ "libtrace-provider-with-fdio.a(libtrace-provider-with-fdio.session.cc.o)",
+ ".text"},
+
+ };
+
+ // for (const auto& sym : symbols) {
+ // std::cout << std::hex << "{0x" << sym.addr << ", 0x" << sym.size << ", \"" << sym.name
+ // << "\", \"" << sym.compile_unit << "\", \"" << sym.section << "\"}," << std::endl;
+ // }
+
+ ASSERT_EQ(symbols.size(), goldens.size());
+ for (size_t i = 0; i < symbols.size(); i++) {
+ ASSERT_EQ(symbols[i].addr, goldens[i].addr);
+ ASSERT_EQ(symbols[i].size, goldens[i].size);
+ ASSERT_EQ(symbols[i].name, goldens[i].name);
+ ASSERT_EQ(symbols[i].compile_unit, goldens[i].compile_unit);
+ ASSERT_EQ(symbols[i].section, goldens[i].section);
+ }
+}
+
+TEST(LinkMapTest, ExampleRust) {
+ std::string link_map = LoadLinkMapFile("example_rust.map");
+ absl::StripLeadingAsciiWhitespace(&link_map);
+ absl::StripTrailingAsciiWhitespace(&link_map);
+
+ auto symbols = bloaty_link_map::ParseLldLinkMap(link_map);
+
+ struct Golden {
+ uint64_t addr;
+ uint64_t size;
+ std::string name;
+ std::string compile_unit;
+ std::string section;
+ };
+ std::vector<Golden> goldens = {
+ {0x2800, 0x27, ".Lanon.745d157954bc19b57d1f47812cead630.3",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x2828, 0x0, ".Lanon.745d157954bc19b57d1f47812cead630.5",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x2828, 0x33, ".Lanon.745d157954bc19b57d1f47812cead630.13",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x2885, 0x11, "anon.745d157954bc19b57d1f47812cead630",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x2896, 0x3, ".Lanon.745d157954bc19b57d1f47812cead630.23",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x2899, 0x16, ".Lanon.745d157954bc19b57d1f47812cead630.26",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x292a, 0xd, "anon.745d157954bc19b57d1f47812cead630",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x2937, 0x5, "anon.745d157954bc19b57d1f47812cead630",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x293c, 0x5, "anon.745d157954bc19b57d1f47812cead630",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x2950, 0xa90, "** lld merge strings", "", ".rodata"},
+ {0x33e0, 0x29, ".Lanon.256afffd819e3254289014381fd650e5.2",
+ "./exe.unstripped/"
+ "component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x346d, 0x0, ".Lanon.256afffd819e3254289014381fd650e5.14",
+ "./exe.unstripped/"
+ "component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x3470, 0x38, ".Lanon.256afffd819e3254289014381fd650e5.16",
+ "./exe.unstripped/"
+ "component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x34a8, 0x1b08, "** lld merge strings", "", ".rodata"},
+ {0x4fb0, 0x3, ".Lanon.256afffd819e3254289014381fd650e5.18",
+ "./exe.unstripped/"
+ "component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x5070, 0x2780, "** lld merge strings", "", ".rodata"},
+ {0x77f0, 0x6, ".Lanon.256afffd819e3254289014381fd650e5.26",
+ "./exe.unstripped/"
+ "component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x77f8, 0x118, ".Lanon.256afffd819e3254289014381fd650e5.28",
+ "./exe.unstripped/"
+ "component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x7910, 0xc, "anon.256afffd819e3254289014381fd650e5",
+ "./exe.unstripped/"
+ "component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x791c, 0x6, "anon.256afffd819e3254289014381fd650e5",
+ "./exe.unstripped/"
+ "component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x7922, 0x10,
+ "_RINvNtCs6QHBvYLDz7W_4core3ptr13drop_in_placeNtCsbDqzXfLQacH_15fidl_fuchsia_"
+ "io11FileRequestECs4fqI2P2rA04_17component_manager",
+ "./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.0.rcgu.o", ".rodata"},
+ {0x7950, 0xb1,
+ "_RNvXsc_NtCsbDqzXfLQacH_17fuchsia_component6serverINtB5_9ServiceFsINtNtB5_"
+ "7service10ServiceObjuEENtNtCseXumDWplyBO_12futures_core6stream6Stream9poll_"
+ "nextCs4fqI2P2rA04_17component_manager",
+ "./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.0.rcgu.o", ".rodata"},
+ {0x7a08, 0x380, "** lld merge strings", "", ".rodata"},
+ {0x7d88, 0x2b, ".Lanon.dda71605b8fec89c031783be146b7025.1",
+ "./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.0.rcgu.o", ".rodata"},
+ {0x8cf1, 0x4e, ".Lanon.8db4583f006f833985ea18d2ab89bab7.1",
+ "./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.7.rcgu.o", ".rodata"},
+ {0x8d3f, 0x1f, "anon.8db4583f006f833985ea18d2ab89bab7",
+ "./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.7.rcgu.o", ".rodata"},
+ {0x8d5e, 0x25, ".Lanon.a1c52c190a58d1713a23e7a7bdb0f9c0.2",
+ "./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.8.rcgu.o", ".rodata"},
+ {0x8d83, 0x7,
+ "_RINvNtCs6QHBvYLDz7W_4core3ptr13drop_in_placeINtNvNtB4_6future14from_"
+ "generator9GenFutureNCNvMNtNtNtCsbDqzXfLQacH_21component_manager_"
+ "lib5model6events6sourceNtB1s_11EventSource3new0EECs4fqI2P2rA04_17component_manager",
+ "./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.9.rcgu.o", ".rodata"},
+ {0x8d8a, 0x8,
+ "_RINvNtCs6QHBvYLDz7W_4core3ptr13drop_in_placeINtNvNtB4_6future14from_"
+ "generator9GenFutureNCNvMs2_NtNtCsbDqzXfLQacH_21component_manager_lib5model5realmNtB1v_"
+ "5Realm19lock_resolved_state0EECs4fqI2P2rA04_17component_manager",
+ "./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.9.rcgu.o", ".rodata"},
+ {0x8d92, 0x16,
+ "_RINvNtCs6QHBvYLDz7W_4core3ptr13drop_in_placeINtNvNtB4_6future14from_"
+ "generator9GenFutureNCNvMs_NtCsbDqzXfLQacH_21component_manager_lib19builtin_"
+ "environmentNtB1u_18BuiltinEnvironment3new0EECs4fqI2P2rA04_17component_manager",
+ "./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.9.rcgu.o", ".rodata"},
+ {0x8da8, 0x4,
+ "_RINvNtCs6QHBvYLDz7W_4core3ptr13drop_in_placeNtCsbDqzXfLQacH_7cm_"
+ "rust10ExposeDeclECs4fqI2P2rA04_17component_manager",
+ "./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.9.rcgu.o", ".rodata"},
+ {0x8dac, 0x6,
+ "_RINvNtCs6QHBvYLDz7W_4core3ptr13drop_in_placeNtCsbDqzXfLQacH_7cm_"
+ "rust7UseDeclECs4fqI2P2rA04_17component_manager",
+ "./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.9.rcgu.o", ".rodata"},
+ {0x91e3, 0x4, "_ZN4core3fmt9Formatter3pad17h9c055cdb18c92cc1E",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x91e7, 0x4, "_ZN4core3fmt9Formatter19pad_formatted_parts17heb4f002989281314E",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x9214, 0x29, "_ZN41_$LT$char$u20$as$u20$core..fmt..Debug$GT$3fmt17hb14158a7386f6b1aE",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x925c, 0x6f,
+ "_ZN61_$LT$core..str..EscapeDebug$u20$as$u20$core..fmt..Display$GT$3fmt17hf1238c3db5de3de5E",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x92d0, 0x0, "anon.6741f024a682c8cb8e59ecab4fc7e9ed.12.llvm.9325873315546439775",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x92d0, 0x1, ".Lanon.6741f024a682c8cb8e59ecab4fc7e9ed.14",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x92d1, 0x1d, ".Lanon.6741f024a682c8cb8e59ecab4fc7e9ed.27",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x9344, 0x28, "core::num::flt2dec::strategy::dragon::POW10::he881f1848ad745b6",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x936c, 0x28, "core::num::flt2dec::strategy::dragon::TWOPOW10::h8ee033ba72605fde",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x9394, 0x8, "core::num::flt2dec::strategy::dragon::POW10TO16::hd008c9b35d67c443",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x939c, 0x10, "core::num::flt2dec::strategy::dragon::POW10TO32::h94bb3ddee75ca692",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x93ac, 0x1c, "core::num::flt2dec::strategy::dragon::POW10TO64::h2607b29da45ca92b",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0xebf8, 0x28, ".Lanon.31de557278eced41e2a6dce0e33ed7ec.9",
+ "./exe.unstripped/"
+ "component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.2.rcgu.o."
+ "rcgu.o",
+ ".rodata"},
+ {0xec20, 0x72, ".Lanon.31de557278eced41e2a6dce0e33ed7ec.15",
+ "./exe.unstripped/"
+ "component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.2.rcgu.o."
+ "rcgu.o",
+ ".rodata"},
+ {0xec92, 0x2e, "anon.31de557278eced41e2a6dce0e33ed7ec",
+ "./exe.unstripped/"
+ "component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.2.rcgu.o."
+ "rcgu.o",
+ ".rodata"},
+ {0xecc0, 0x13, "anon.31de557278eced41e2a6dce0e33ed7ec",
+ "./exe.unstripped/"
+ "component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.2.rcgu.o."
+ "rcgu.o",
+ ".rodata"},
+ {0xecd3, 0x4a, ".Lanon.31de557278eced41e2a6dce0e33ed7ec.34",
+ "./exe.unstripped/"
+ "component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.2.rcgu.o."
+ "rcgu.o",
+ ".rodata"},
+ {0x2e20c, 0xc, ".Lanon.93519e6636017643a07f78e678f4f11e.3501",
+ "./exe.unstripped/"
+ "component_manager.libidna-5c3c4f537fb8313f-5c3c4f537fb8313f.idna.axcknxfw-cgu.15.rcgu.o."
+ "rcgu.o",
+ ".rodata"},
+ {0x2e218, 0xc, ".Lanon.93519e6636017643a07f78e678f4f11e.3502",
+ "./exe.unstripped/"
+ "component_manager.libidna-5c3c4f537fb8313f-5c3c4f537fb8313f.idna.axcknxfw-cgu.15.rcgu.o."
+ "rcgu.o",
+ ".rodata"},
+ {0x2e224, 0xc, ".Lanon.93519e6636017643a07f78e678f4f11e.3503",
+ "./exe.unstripped/"
+ "component_manager.libidna-5c3c4f537fb8313f-5c3c4f537fb8313f.idna.axcknxfw-cgu.15.rcgu.o."
+ "rcgu.o",
+ ".rodata"},
+ {0x2f4ca, 0x1b, "anon.7cbed09a5c2e2961b1298b053de6d71a",
+ "./exe.unstripped/component_manager.libio_util.io_util.3a1fbbbh-cgu.3.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x2f4e5, 0x28, ".Lanon.2dc76abe0f971e71acd588990ffc8513.0",
+ "./exe.unstripped/component_manager.libio_util.io_util.3a1fbbbh-cgu.8.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x2f50d, 0x2b, ".Lanon.6f9bafb4adb671d7ac9f8408e86ff31a.0",
+ "./exe.unstripped/"
+ "component_manager.liblibrary_loader.library_loader.3a1fbbbh-cgu.1.rcgu.o.rcgu.o",
+ ".rodata"},
+ {0x3aacc, 0x134,
+ "_ZN9libunwind17DwarfInstructionsINS_17LocalAddressSpaceENS_15Registers_"
+ "arm64EE18evaluateExpressionEmRS1_RKS2_m",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".rodata"},
+ {0x3ac00, 0x39,
+ "_ZN9libunwind10CFI_ParserINS_17LocalAddressSpaceEE8parseCIEERS1_mPNS2_8CIE_InfoE",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".rodata"},
+ {0x3ac39, 0xc, "_ZN9libunwind14EHHeaderParserINS_17LocalAddressSpaceEE17getTableEntrySizeEh",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".rodata"},
+ {0xb2640, 0x14, "_start",
+ "/usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/"
+ "c/crt1.Scrt1.cc.o",
+ ".text"},
+ {0xb2654, 0x1c, "_$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$::fmt::h12b31caec1d188c4",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".text"},
+ {0xb2670, 0xb4, "_$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$::fmt::h268a568299531efb",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".text"},
+ {0xcd52c, 0x98,
+ "core::num::_$LT$impl$u20$core..str..FromStr$u20$for$u20$usize$GT$::from_str::"
+ "h4398f35a1c0b4be7",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".text"},
+ {0xcd5c4, 0xe4,
+ "_$LT$core..num..TryFromIntError$u20$as$u20$core..fmt..Debug$GT$::fmt::h10047a113409f418",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".text"},
+ {0xcd6a8, 0x34,
+ "core::fmt::float::_$LT$impl$u20$core..fmt..Display$u20$for$u20$f64$GT$::fmt::"
+ "hac033855e28d5eb4",
+ "./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o",
+ ".text"},
+ {0xe2554, 0x10, "OUTLINED_FUNCTION_359",
+ "./exe.unstripped/"
+ "component_manager.libcm_fidl_translator.cm_fidl_translator.3a1fbbbh-cgu.0.rcgu.o.rcgu.o",
+ ".text"},
+ {0xe2564, 0x10, "OUTLINED_FUNCTION_360",
+ "./exe.unstripped/"
+ "component_manager.libcm_fidl_translator.cm_fidl_translator.3a1fbbbh-cgu.0.rcgu.o.rcgu.o",
+ ".text"},
+ {0xeb888, 0x40,
+ "_RINvNtNtCs6QHBvYLDz7W_4core4iter8adapters15process_resultsINtB2_3MapINtNtCsiEMmsMe2HZG_"
+ "5alloc3vec8IntoIterNtNtCsbDqzXfLQacH_7cm_json2cm8ResolverENCNvXs_CsbDqzXfLQacH_18cm_fidl_"
+ "translatorINtB17_3VecB1G_EINtB2q_6CmIntoIB2Z_NtCsbDqzXfLQacH_17fidl_fuchsia_"
+ "sys212ResolverDeclEE7cm_into0EB3x_NtB1K_5ErrorNCINvXsx_NtB6_6resultINtB4V_6ResultB3s_B4A_"
+ "EINtNtNtB4_6traits7collect12FromIteratorIB58_B3x_B4A_EE9from_iterBU_E0B3s_EB2q_",
+ "./exe.unstripped/"
+ "component_manager.libcm_fidl_translator.cm_fidl_translator.3a1fbbbh-cgu.4.rcgu.o.rcgu.o",
+ ".text"},
+ {0x197340, 0x70,
+ "_RINvNtCsevZhND7KDbE_9hashbrown3raw16calculate_layoutTjINtNtCsiEMmsMe2HZG_"
+ "5alloc4sync3ArcDNtNtCsbDqzXfLQacH_13fuchsia_async8executor14PacketReceiverEL_EEEB1t_",
+ "./exe.unstripped/"
+ "component_manager.libfuchsia_async.fuchsia_async.3a1fbbbh-cgu.1.rcgu.o.rcgu.o",
+ ".text"},
+ {0x200800, 0x38,
+ "std::sys_common::thread_info::THREAD_INFO::__getit::__KEY::h836843d10e59cada",
+ "./exe.unstripped/component_manager.std-7a72ffd2f3a7a812.std.a0uwjzoe-cgu.0.rcgu.o.rcgu.o",
+ ".text"},
+ {0x200840, 0x18,
+ "std::panicking::update_panic_count::PANIC_COUNT::__getit::__KEY::h69785e8fa31a63e6",
+ "./exe.unstripped/component_manager.std-7a72ffd2f3a7a812.std.a0uwjzoe-cgu.0.rcgu.o.rcgu.o",
+ ".text"},
+ {0x1ff610, 0x30, ".Lanon.745d157954bc19b57d1f47812cead630.1",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".data.rel.ro"},
+ {0x1ff640, 0x20, ".Lanon.745d157954bc19b57d1f47812cead630.2",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".data.rel.ro"},
+ {0x1ff660, 0x18, ".Lanon.745d157954bc19b57d1f47812cead630.4",
+ "./exe.unstripped/"
+ "component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o",
+ ".data.rel.ro"},
+ {0x202a88, 0x20, "anon.e57b3f2ae8b5861992cfe341e01c75d6",
+ "./exe.unstripped/"
+ "component_manager.libcm_fidl_validator.cm_fidl_validator.3a1fbbbh-cgu.0.rcgu.o.rcgu.o",
+ ".data.rel.ro"},
+ {0x2110a8, 0x18, ".Lanon.6f9bafb4adb671d7ac9f8408e86ff31a.3",
+ "./exe.unstripped/"
+ "component_manager.liblibrary_loader.library_loader.3a1fbbbh-cgu.1.rcgu.o.rcgu.o",
+ ".data.rel.ro"},
+ {0x211108, 0x10, "anon.6f9bafb4adb671d7ac9f8408e86ff31a",
+ "./exe.unstripped/"
+ "component_manager.liblibrary_loader.library_loader.3a1fbbbh-cgu.1.rcgu.o.rcgu.o",
+ ".data.rel.ro"},
+ {0x211118, 0x38, "anon.6f9bafb4adb671d7ac9f8408e86ff31a",
+ "./exe.unstripped/"
+ "component_manager.liblibrary_loader.library_loader.3a1fbbbh-cgu.1.rcgu.o.rcgu.o",
+ ".data.rel.ro"},
+ {0x211150, 0x20, "anon.54c490f3ebdb496a1657b64fb6de7217",
+ "./exe.unstripped/"
+ "component_manager.liblibrary_loader.library_loader.3a1fbbbh-cgu.10.rcgu.o.rcgu.o",
+ ".data.rel.ro"},
+ {0x211170, 0x18, ".Lanon.39cf32991562f2df00749835baa6fdc6.1",
+ "./exe.unstripped/"
+ "component_manager.liblibrary_loader.library_loader.3a1fbbbh-cgu.11.rcgu.o.rcgu.o",
+ ".data.rel.ro"},
+ {0x215018, 0x18, "anon.d8e73c4ab2af7c41d0ca8802142f134e",
+ "./exe.unstripped/component_manager.std-7a72ffd2f3a7a812.std.a0uwjzoe-cgu.0.rcgu.o.rcgu.o",
+ ".data.rel.ro"},
+ {0x215030, 0x88,
+ "vtable for libunwind::UnwindCursor<libunwind::LocalAddressSpace, "
+ "libunwind::Registers_arm64>",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".data.rel.ro"},
+ {0x2150b8, 0x310, ".Lswitch.table._ZN9libunwind15Registers_arm6415getRegisterNameEi",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".data.rel.ro"},
+ {0x225848, 0x10, "_RNvCsi1CgODtnl2B_3log6LOGGER",
+ "./exe.unstripped/"
+ "component_manager.liblog-51a8548556518bd3-51a8548556518bd3.log.7ncv9mgr-cgu.0.rcgu.o.rcgu."
+ "o",
+ ".data"},
+ {0x225858, 0x8, "std::thread::ThreadId::new::COUNTER::hb3a413a29c0f81f6",
+ "./exe.unstripped/component_manager.std-7a72ffd2f3a7a812.std.a0uwjzoe-cgu.0.rcgu.o.rcgu.o",
+ ".data"},
+ {0x225860, 0x38, "std::io::stdio::stderr::INSTANCE::hb24941292040ef06",
+ "./exe.unstripped/component_manager.std-7a72ffd2f3a7a812.std.a0uwjzoe-cgu.0.rcgu.o.rcgu.o",
+ ".data"},
+ {0x2258c8, 0x8, "libunwind::DwarfFDECache<libunwind::LocalAddressSpace>::_bufferEnd",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".data"},
+ {0x225c68, 0x20,
+ "_RNvNvNvXs15_CsbDqzXfLQacH_7cm_rustNtBa_13DATA_TYPENAMENtNtNtCs6QHBvYLDz7W_"
+ "4core3ops5deref5Deref5deref11___stability4LAZY",
+ "./exe.unstripped/component_manager.libcm_rust.cm_rust.3a1fbbbh-cgu.0.rcgu.o.rcgu.o",
+ ".bss"},
+ {0x225c88, 0x20,
+ "_RNvNvNvXs17_CsbDqzXfLQacH_7cm_rustNtBa_14CACHE_TYPENAMENtNtNtCs6QHBvYLDz7W_"
+ "4core3ops5deref5Deref5deref11___stability4LAZY",
+ "./exe.unstripped/component_manager.libcm_rust.cm_rust.3a1fbbbh-cgu.0.rcgu.o.rcgu.o",
+ ".bss"},
+ {0x225ca8, 0x20,
+ "_RNvNvNvXs19_CsbDqzXfLQacH_7cm_rustNtBa_13META_TYPENAMENtNtNtCs6QHBvYLDz7W_"
+ "4core3ops5deref5Deref5deref11___stability4LAZY",
+ "./exe.unstripped/component_manager.libcm_rust.cm_rust.3a1fbbbh-cgu.0.rcgu.o.rcgu.o",
+ ".bss"},
+ {0x225cc8, 0x38,
+ "_RNvNvNvXs0_NtNtNtCsbDqzXfLQacH_21component_manager_lib5model6events14source_factoryNtB9_"
+ "25EVENT_SOURCE_SERVICE_PATHNtNtNtCs6QHBvYLDz7W_4core3ops5deref5Deref5deref11___"
+ "stability4LAZY",
+ "./exe.unstripped/"
+ "component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.12.rcgu.o."
+ "rcgu.o",
+ ".bss"},
+ {0x225d00, 0x38,
+ "_RNvNvNvXs2_NtNtNtCsbDqzXfLQacH_21component_manager_lib5model6events14source_factoryNtB9_"
+ "30EVENT_SOURCE_SYNC_SERVICE_PATHNtNtNtCs6QHBvYLDz7W_4core3ops5deref5Deref5deref11___"
+ "stability4LAZY",
+ "./exe.unstripped/"
+ "component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.12.rcgu.o."
+ "rcgu.o",
+ ".bss"},
+ {0x2262ac, 0x8, "libunwind::DwarfFDECache<libunwind::LocalAddressSpace>::_lock",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".bss"},
+ {0x2262b8, 0x800, "libunwind::DwarfFDECache<libunwind::LocalAddressSpace>::_initialBuffer",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".bss"},
+ {0x226ab8, 0x1, "logAPIs::checked",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".bss"},
+ {0x226abc, 0x1, "logAPIs::log",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".bss"},
+ {0x226abd, 0x1, "logUnwinding::checked",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".bss"},
+ {0x226ac0, 0x1, "logUnwinding::log",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".bss"},
+ {0x226ac1, 0x1, "logDWARF::checked",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".bss"},
+ {0x226ac4, 0x1, "logDWARF::log",
+ "/tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o)", ".bss"},
+ };
+
+ // for (const auto& sym : symbols) {
+ // std::cout << std::hex << "{0x" << sym.addr << ", 0x" << sym.size << ", \"" << sym.name
+ // << "\", \"" << sym.compile_unit << "\", \"" << sym.section << "\"}," << std::endl;
+ // }
+
+ ASSERT_EQ(symbols.size(), goldens.size());
+ for (size_t i = 0; i < symbols.size(); i++) {
+ ASSERT_EQ(symbols[i].addr, goldens[i].addr);
+ ASSERT_EQ(symbols[i].size, goldens[i].size);
+ ASSERT_EQ(symbols[i].name, goldens[i].name);
+ ASSERT_EQ(symbols[i].compile_unit, goldens[i].compile_unit);
+ ASSERT_EQ(symbols[i].section, goldens[i].section);
+ }
+}
+
+TEST(LinkMapTest, SuperSizeGoldens) {
+ std::ifstream infile("test_lld-lto_v1.map");
+ std::string link_map;
+ for (std::string line; getline(infile, line);) {
+ if (line.empty()) continue;
+ if (line[0] == '#') continue;
+ link_map += line;
+ link_map += '\n';
+ }
+
+ absl::StripLeadingAsciiWhitespace(&link_map);
+ absl::StripTrailingAsciiWhitespace(&link_map);
+
+ struct Golden {
+ uint64_t addr;
+ uint64_t size;
+ std::string name;
+ std::string compile_unit;
+ std::string section;
+ };
+ std::vector<Golden> goldens = {
+ {0x213200, 0x4, "v8_Default_embedded_blob_size_", "obj/v8/v8_external_snapshot/embedded.o",
+ ".rodata"},
+ {0x213210, 0x10, "pmmp",
+ "obj/third_party/ffmpeg/libffmpeg_internal.a(ffmpeg_internal/fft_neon.o)", ".rodata"},
+ {0x213220, 0x10, "mppm",
+ "obj/third_party/ffmpeg/libffmpeg_internal.a(ffmpeg_internal/fft_neon.o)", ".rodata"},
+ {0x213230, 0xc0, "std::__ndk1::(anonymous namespace)::small_primes",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(hash.o)",
+ ".rodata"},
+ {0x2132f0, 0xc0, "std::__ndk1::(anonymous namespace)::indices",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(hash.o)",
+ ".rodata"},
+ {0x2133b0, 0x4, "std::__ndk1::ios_base::boolalpha",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133b4, 0x4, "std::__ndk1::ios_base::dec",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133b8, 0x4, "std::__ndk1::ios_base::fixed",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133bc, 0x4, "std::__ndk1::ios_base::hex",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133c0, 0x4, "std::__ndk1::ios_base::internal",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133c4, 0x4, "std::__ndk1::ios_base::left",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133c8, 0x4, "std::__ndk1::ios_base::oct",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133cc, 0x4, "std::__ndk1::ios_base::right",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133d0, 0x4, "std::__ndk1::ios_base::scientific",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133d4, 0x4, "std::__ndk1::ios_base::showbase",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133d8, 0x4, "std::__ndk1::ios_base::showpoint",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133dc, 0x4, "std::__ndk1::ios_base::showpos",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133e0, 0x4, "std::__ndk1::ios_base::skipws",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133e4, 0x4, "std::__ndk1::ios_base::unitbuf",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133e8, 0x4, "std::__ndk1::ios_base::uppercase",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133ec, 0x4, "std::__ndk1::ios_base::adjustfield",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133f0, 0x4, "std::__ndk1::ios_base::basefield",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133f4, 0x4, "std::__ndk1::ios_base::floatfield",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133f8, 0x4, "std::__ndk1::ios_base::badbit",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x2133fc, 0x4, "std::__ndk1::ios_base::eofbit",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x213400, 0x4, "std::__ndk1::ios_base::failbit",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x213404, 0x4, "std::__ndk1::ios_base::goodbit",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x213408, 0x4, "std::__ndk1::ios_base::app",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x21340c, 0x4, "std::__ndk1::ios_base::ate",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x213410, 0x4, "std::__ndk1::ios_base::binary",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x213414, 0x4, "std::__ndk1::ios_base::in",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x213418, 0x4, "std::__ndk1::ios_base::out",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x21341c, 0x4, "std::__ndk1::ios_base::trunc",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x213420, 0x1d, "typeinfo name for std::__ndk1::ios_base::failure",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x213440, 0x15, "typeinfo name for std::__ndk1::ios_base",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x213460, 0x21, "typeinfo name for std::__ndk1::__iostream_category",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x213490, 0x2d, "_ZTSNSt6__ndk19basic_iosIcNS_11char_traitsIcEEEE",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".rodata"},
+ {0x21368b, 0x2c378e, "** lld merge strings", "", ".rodata"},
+ {0x4d6e20, 0x1b, "typeinfo name for std::__ndk1::__stdinbuf<wchar_t>",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(iostream.o)",
+ ".rodata"},
+ {0x4d7920, 0x266c, "** lld merge strings", "", ".rodata"},
+ {0x4d9f90, 0x18, "typeinfo name for std::__ndk1::ctype_base",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(locale.o)",
+ ".rodata"},
+ {0x4d9fb0, 0x1a, "typeinfo name for std::__ndk1::codecvt_base",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(locale.o)",
+ ".rodata"},
+ {0x503eb3, 0x1, "network::mojom::CookieManagerProxy_SetCanonicalCookie_Message::kMessageTag",
+ "", ".rodata"},
+ {0x503eb4, 0x1,
+ "network::mojom::CookieManagerProxy_DeleteCanonicalCookie_Message::kMessageTag", "",
+ ".rodata"},
+ {0x82f000, 0x40, "", "obj/third_party/boringssl/boringssl_asm/chacha-armv4.o", ".text"},
+ {0x82f040, 0x3f0, "ChaCha20_ctr32", "obj/third_party/boringssl/boringssl_asm/chacha-armv4.o",
+ ".text"},
+ {0x82f440, 0x89c, "ChaCha20_neon", "obj/third_party/boringssl/boringssl_asm/chacha-armv4.o",
+ ".text"},
+ {0x82fce0, 0x540, "AES_Te", "obj/third_party/boringssl/boringssl_asm/aes-armv4.o", ".text"},
+ {0x830220, 0x60, "aes_nohw_encrypt", "obj/third_party/boringssl/boringssl_asm/aes-armv4.o",
+ ".text"},
+ {0x830280, 0x1d8, "_armv4_AES_encrypt", "obj/third_party/boringssl/boringssl_asm/aes-armv4.o",
+ ".text"},
+ {0x830460, 0x2a0, "aes_nohw_set_encrypt_key",
+ "obj/third_party/boringssl/boringssl_asm/aes-armv4.o", ".text"},
+ {0x830700, 0x20, "aes_nohw_set_decrypt_key",
+ "obj/third_party/boringssl/boringssl_asm/aes-armv4.o", ".text"},
+ {0x830720, 0x124, "AES_set_enc2dec_key",
+ "obj/third_party/boringssl/boringssl_asm/aes-armv4.o", ".text"},
+ {0x830860, 0x500, "AES_Td", "obj/third_party/boringssl/boringssl_asm/aes-armv4.o", ".text"},
+ {0x830d60, 0x60, "aes_nohw_decrypt", "obj/third_party/boringssl/boringssl_asm/aes-armv4.o",
+ ".text"},
+ {0x830dc0, 0x1f8, "_armv4_AES_decrypt", "obj/third_party/boringssl/boringssl_asm/aes-armv4.o",
+ ".text"},
+ {0x831000, 0x40, "", "obj/third_party/boringssl/boringssl_asm/aesv8-armx32.o", ".text"},
+ {0x831040, 0x218, "aes_hw_set_encrypt_key",
+ "obj/third_party/boringssl/boringssl_asm/aesv8-armx32.o", ".text"},
+ {0x831260, 0x60, "aes_hw_set_decrypt_key",
+ "obj/third_party/boringssl/boringssl_asm/aesv8-armx32.o", ".text"},
+ {0x8312c0, 0x50, "aes_hw_encrypt", "obj/third_party/boringssl/boringssl_asm/aesv8-armx32.o",
+ ".text"},
+ {0x831320, 0x50, "aes_hw_decrypt", "obj/third_party/boringssl/boringssl_asm/aesv8-armx32.o",
+ ".text"},
+ {0x83c640, 0x2e60, "v8_Default_embedded_blob_data_", "obj/v8/v8_external_snapshot/embedded.o",
+ ".text"},
+ {0x83f4a0, 0x320, "Builtins_RecordWrite", "obj/v8/v8_external_snapshot/embedded.o", ".text"},
+ {0x83f7c0, 0x40, "Builtins_AdaptorWithExitFrame", "obj/v8/v8_external_snapshot/embedded.o",
+ ".text"},
+ {0x83f800, 0x40, "Builtins_AdaptorWithBuiltinExitFrame",
+ "obj/v8/v8_external_snapshot/embedded.o", ".text"},
+ {0x83f840, 0x100, "Builtins_ArgumentsAdaptorTrampoline",
+ "obj/v8/v8_external_snapshot/embedded.o", ".text"},
+ {0x83f940, 0x100, "Builtins_CallFunction_ReceiverIsNullOrUndefined",
+ "obj/v8/v8_external_snapshot/embedded.o", ".text"},
+ {0x83fa40, 0x140, "Builtins_CallFunction_ReceiverIsNotNullOrUndefined",
+ "obj/v8/v8_external_snapshot/embedded.o", ".text"},
+ {0x83fb80, 0x180, "Builtins_CallFunction_ReceiverIsAny",
+ "obj/v8/v8_external_snapshot/embedded.o", ".text"},
+ {0x83fd00, 0x137820, "Builtins_CallBoundFunction", "obj/v8/v8_external_snapshot/embedded.o",
+ ".text"},
+ {0x977520, 0x18, "PushAllRegisters",
+ "obj/third_party/blink/renderer/platform/heap/asm/asm/SaveRegisters_arm.o", ".text"},
+ {0x97c834, 0x4dc, "vpx_convolve8_avg_horiz_filter_type1_neon",
+ "obj/third_party/libvpx/libvpx_assembly_arm.a(libvpx_assembly_arm/"
+ "vpx_convolve8_avg_horiz_filter_type1_neon.asm.o)",
+ ".text"},
+ {0x99cad8, 0xfc, "sinh",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libandroid_support.a(e_sinh.o)",
+ ".text"},
+ {0x1401788, 0xa4, "$_21::operator()(FamilyData*, char const*, char const**) const", "",
+ ".text"},
+ {0x1401820, 0x10, "UnlikelyFunc", "", ".text"},
+ {0x1401850, 0x10, "StartUpFunc", "", ".text"},
+ {0x2d4c000, 0x4, "v8_Default_embedded_blob_", "obj/v8/v8_external_snapshot/embedded.o",
+ ".data"},
+ {0x2d4c004, 0x4, "__dso_handle",
+ "../../third_party/android_ndk/platforms/android-16/arch-arm/usr/lib/crtbegin_so.o",
+ ".data"},
+ {0x2d69000, 0x3c, "fft_tab_vfp",
+ "obj/third_party/ffmpeg/libffmpeg_internal.a(ffmpeg_internal/fft_vfp.o)", ".data.rel.ro"},
+ {0x2d6903c, 0x3c, "fft_tab_neon",
+ "obj/third_party/ffmpeg/libffmpeg_internal.a(ffmpeg_internal/fft_neon.o)", ".data.rel.ro"},
+ {0x2d69078, 0x40,
+ "vtable for std::__ndk1::basic_streambuf<wchar_t, std::__ndk1::char_traits<wchar_t> >",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".data.rel.ro"},
+ {0x2d690b8, 0x28,
+ "vtable for std::__ndk1::basic_istream<char, std::__ndk1::char_traits<char> >",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".data.rel.ro"},
+ {0x2d690e0, 0x28,
+ "vtable for std::__ndk1::basic_istream<wchar_t, std::__ndk1::char_traits<wchar_t> >",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".data.rel.ro"},
+ {0x2d69108, 0x28,
+ "vtable for std::__ndk1::basic_ostream<char, std::__ndk1::char_traits<char> >",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".data.rel.ro"},
+ {0x2d69130, 0x28,
+ "vtable for std::__ndk1::basic_ostream<wchar_t, std::__ndk1::char_traits<wchar_t> >",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".data.rel.ro"},
+ {0x2d69158, 0x24, "vtable for std::__ndk1::__iostream_category",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".data.rel.ro"},
+ {0x2d6917c, 0x14, "vtable for std::__ndk1::ios_base::failure",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".data.rel.ro"},
+ {0x2d69190, 0x10, "vtable for std::__ndk1::ios_base",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".data.rel.ro"},
+ {0x2d691a0, 0xc, "typeinfo for std::__ndk1::ios_base::failure",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".data.rel.ro"},
+ {0x2d691ac, 0x8, "typeinfo for std::__ndk1::ios_base",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".data.rel.ro"},
+ {0x2d691b4, 0xc, "typeinfo for std::__ndk1::__iostream_category",
+ "../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/"
+ "libc++_static.a(ios.o)",
+ ".data.rel.ro"},
+ {0x2f56000, 0x4, "WebRtcSpl_CrossCorrelation", "", ".bss"},
+ {0x2f56004, 0x4, "WebRtcSpl_DownsampleFast", "", ".bss"},
+ {0x2f56008, 0x4, "WebRtcSpl_MaxAbsValueW16", "", ".bss"},
+ {0x0, 0x1000, "** ", "", ".part.end"},
+ };
+
+ auto symbols = bloaty_link_map::ParseLldLinkMap(link_map);
+ // for (const auto& sym : symbols) {
+ // std::cout << std::hex << "{0x" << sym.addr << ", 0x" << sym.size << ", \"" << sym.name
+ // << "\", \"" << sym.compile_unit << "\", \"" << sym.section << "\"}," << std::endl;
+ // }
+
+ ASSERT_EQ(symbols.size(), goldens.size());
+ for (size_t i = 0; i < symbols.size(); i++) {
+ ASSERT_EQ(symbols[i].addr, goldens[i].addr);
+ ASSERT_EQ(symbols[i].size, goldens[i].size);
+ ASSERT_EQ(symbols[i].name, goldens[i].name);
+ ASSERT_EQ(symbols[i].compile_unit, goldens[i].compile_unit);
+ ASSERT_EQ(symbols[i].section, goldens[i].section);
+ }
+}
diff --git a/tests/testdata/link_map/example_cpp.map b/tests/testdata/link_map/example_cpp.map
new file mode 100644
index 0000000..ff80ed1
--- /dev/null
+++ b/tests/testdata/link_map/example_cpp.map
@@ -0,0 +1,103 @@
+ VMA LMA Size Align Out In Symbol
+ 2a8 2a8 8 1 .interp
+ 2a8 2a8 8 1 <internal>:(.interp)
+ 2b0 2b0 18 4 .note.gnu.build-id
+ 2b0 2b0 18 4 <internal>:(.note.gnu.build-id)
+ 2c8 2c8 12f0 8 .dynsym
+ 2c8 2c8 12f0 8 <internal>:(.dynsym)
+ 15b8 15b8 1c 8 .gnu.hash
+ 15b8 15b8 1c 8 <internal>:(.gnu.hash)
+ 15d8 15d8 1e0 8 .dynamic
+ 15d8 15d8 1e0 8 <internal>:(.dynamic)
+ 17b8 17b8 143c 1 .dynstr
+ 17b8 17b8 143c 1 <internal>:(.dynstr)
+ 2bf8 2bf8 540 8 .rela.dyn
+ 2bf8 2bf8 540 8 <internal>:(.rela.dyn)
+ 3138 3138 150 8 .relr.dyn
+ 3138 3138 150 8 <internal>:(.relr.dyn)
+ 3288 3288 10f8 8 .rela.plt
+ 3288 3288 10f8 8 <internal>:(.rela.plt)
+ 4380 4380 b88b 16 .rodata
+ 4380 4380 14 1 obj/zircon/system/uapp/blobfs/blobfs.main.cc.o:(.rodata.main)
+ 4380 4380 0 1 $d.1
+ 4394 4394 8a18 1 <internal>:(.rodata)
+ cdb0 cdb0 a8 8 <internal>:(.rodata)
+ ce58 ce58 c 4 obj/out/default/fidling/gen/sdk/fidl/fuchsia.hardware.block/fuchsia.hardware.block_tables.fuchsia.hardware.block.fidl.tables.c.o:(.rodata.HandlefifonullableTable)
+ ce58 ce58 0 1 $d.45
+ ce58 ce58 c 1 HandlefifonullableTable
+ ce64 ce64 c 4 obj/out/default/fidling/gen/sdk/fidl/fuchsia.hardware.block/fuchsia.hardware.block_tables.fuchsia.hardware.block.fidl.tables.c.o:(.rodata.HandlevmononnullableTable)
+ ce64 ce64 0 1 $d.46
+ ce64 ce64 c 1 HandlevmononnullableTable
+ ce70 ce70 c 4 obj/out/default/fidling/gen/sdk/fidl/fuchsia.security.resource/fuchsia.security.resource_tables.fuchsia.security.resource.fidl.tables.c.o:(.rodata.HandleresourcenonnullableTable)
+ ce70 ce70 0 1 $d.5
+ ce70 ce70 c 1 HandleresourcenonnullableTable
+ ce7c ce7c a 1 obj/out/default/fidling/gen/sdk/fidl/fuchsia.io/fuchsia/io/llcpp/fuchsia.io_llcpp.fidl.cc.o:(.rodata._ZN5llcpp7fuchsia2io8NodeInfo9reset_ptrEON4fidl12tracking_ptrIvEE)
+ ce7c ce7c 0 1 $d.1175
+ ce86 ce86 2b 1 obj/out/default/fidling/gen/sdk/fidl/fuchsia.io/fuchsia/io/llcpp/fuchsia.io_llcpp.fidl.cc.o:(.rodata._ZN4fidlL17kErrorWriteFailedE)
+ ce86 ce86 0 1 $d.1404
+ ce86 ce86 2b 1 fidl::kErrorWriteFailed
+ ceb4 ceb4 c 4 obj/out/default/fidling/gen/sdk/fidl/fuchsia.io/fuchsia.io_tables.fuchsia.io.fidl.tables.c.o:(.rodata.Request15fuchsia_io_NodenonnullableTable)
+ ceb4 ceb4 0 1 $d.251
+ ceb4 ceb4 c 1 Request15fuchsia_io_NodenonnullableTable
+ cec0 cec0 8 4 obj/out/default/fidling/gen/sdk/fidl/fuchsia.io/fuchsia.io_tables.fuchsia.io.fidl.tables.c.o:(.rodata.String4096nonnullableTable)
+ cec0 cec0 0 1 $d.253
+ cec0 cec0 8 1 String4096nonnullableTable
+ d030 d030 100 16 <internal>:(.rodata)
+ d130 d130 c 4 obj/out/default/fidling/gen/sdk/fidl/fuchsia.fs/fuchsia.fs_tables.fuchsia.fs.fidl.tables.c.o:(.rodata.HandleeventnonnullableTable)
+ d130 d130 0 1 $d.22
+ d130 d130 c 1 HandleeventnonnullableTable
+ d13c d13c 8 4 obj/out/default/fidling/gen/sdk/fidl/fuchsia.fs/fuchsia.fs_tables.fuchsia.fs.fidl.tables.c.o:(.rodata.String32nonnullableTable)
+ d13c d13c 0 1 $d.23
+ d13c d13c 8 1 String32nonnullableTable
+ fb38 fb38 9c 4 obj/zircon/public/lib/lz4/liblz4.a(liblz4.lz4hc.c.o):(.rodata.LZ4HC_compress_generic_internal.clTable)
+ fb38 fb38 0 1 $d.39
+ fb38 fb38 9c 1 LZ4HC_compress_generic_internal.clTable
+ fbd4 fbd4 10 1 obj/zircon/public/lib/lz4/liblz4.a(liblz4.xxhash.c.o):(.rodata.XXH32_finalize)
+ fbd4 fbd4 0 1 $d.22
+ fbe4 fbe4 27 1 obj/zircon/system/ulib/trace-provider/libtrace-provider-with-fdio.a(libtrace-provider-with-fdio.fdio_connect.cc.o):(.rodata._ZL12kServicePath)
+ fbe4 fbe4 0 1 $d.1
+ fbe4 fbe4 27 1 kServicePath
+ fc0c fc0c 361c 4 .eh_frame_hdr
+ fc0c fc0c 361c 4 <internal>:(.eh_frame_hdr)
+ 13228 13228 d63c 8 .eh_frame
+ 13228 13228 14 1 /usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/c/crt1.Scrt1.cc.o:(.eh_frame+0x0)
+ 13260 13260 54 1 obj/zircon/system/uapp/blobfs/blobfs.main.cc.o:(.eh_frame+0x14)
+ 13320 13320 2c 1 obj/out/default/fidling/gen/sdk/fidl/fuchsia.security.resource/fuchsia/security/resource/llcpp/fuchsia.security.resource_llcpp.fidl.cc.o:(.eh_frame+0x34)
+ 207d8 207d8 1c 1 obj/zircon/system/ulib/trace-provider/libtrace-provider-with-fdio.a(libtrace-provider-with-fdio.utils.cc.o):(.eh_frame+0x14)
+ 20818 20818 14 1 ../../out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/sync/libsync.a(sync._sources.completion.c.o):(.eh_frame+0x54)
+ 20848 20848 14 1 ../../prebuilt/third_party/clang/linux-x64/lib/clang/11.0.0/lib/aarch64-unknown-fuchsia/libclang_rt.builtins.a(udivmodti4.c.obj):(.eh_frame+0x14)
+ 21000 21000 e84f0 8 .text
+ 21000 21000 14 8 /usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/c/crt1.Scrt1.cc.o:(.text._start)
+ 21000 21000 0 1 $x.0
+ 21000 21000 14 1 _start
+ 21014 21014 494 4 obj/zircon/system/uapp/blobfs/blobfs.main.cc.o:(.text.main)
+ 21014 21014 0 1 $x.0
+ 21014 21014 494 1 main
+ 3a04c 3a04c 8 4 obj/zircon/system/ulib/blobfs/libblobfs.a(libblobfs.blobfs.cc.o):(.text._ZThn8_N6blobfs18TransactionManagerD1Ev)
+ 3a04c 3a04c 0 1 $x.83
+ 3a04c 3a04c 8 1 non-virtual thunk to blobfs::TransactionManager::~TransactionManager()
+ 108120 108120 dc 4 obj/zircon/system/ulib/trace-provider/libtrace-provider-with-fdio.a(libtrace-provider-with-fdio.session.cc.o):(.text._ZN5trace8internal7SessionD2Ev)
+ 108120 108120 0 1 $x.1
+ 108120 108120 dc 1 trace::internal::Session::~Session()
+ 0 0 d41f13 1 .debug_loc
+ 0 0 39 1 /usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/c/crt1.Scrt1.cc.o:(.debug_loc)
+ 0 0 0 1 $d.1 108120 108120 dc 1 trace::internal::Session::~Session()
+ d411ac d411ac 458 1 ../../out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/sync/libsync.a(sync._sources.completion.c.o):(.debug_loc)
+ d411ac d411ac 0 1 $d.6
+ d41604 d41604 50 1 ../../prebuilt/third_party/clang/linux-x64/lib/clang/11.0.0/lib/aarch64-unknown-fuchsia/libclang_rt.builtins.a(udivti3.c.obj):(.debug_loc)
+ d41604 d41604 0 1 $d.1
+ d41654 d41654 8bf 1 ../../prebuilt/third_party/clang/linux-x64/lib/clang/11.0.0/lib/aarch64-unknown-fuchsia/libclang_rt.builtins.a(udivmodti4.c.obj):(.debug_loc)
+ d41654 d41654 0 1 $d.1
+ 0 0 52a34 1 .debug_abbrev
+ 0 0 6d 1 /usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/c/crt1.Scrt1.cc.o:(.debug_abbrev)
+ 0 0 0 1 $d.2
+ 7fa 7fa 93f 1 obj/out/default/fidling/gen/sdk/fidl/fuchsia.security.resource/fuchsia/security/resource/llcpp/fuchsia.security.resource_llcpp.fidl.cc.o:(.debug_abbrev)
+ 7fa 7fa 0 1 $d.40
+ 3323a0 3323a0 30 1 ../../prebuilt/third_party/clang/linux-x64/lib/clang/11.0.0/lib/aarch64-unknown-fuchsia/libclang_rt.builtins.a(udivmodti4.c.obj):(.debug_ranges)
+ 3323a0 3323a0 0 1 $d.4
+ 0 0 2ed88 8 .symtab
+ 0 0 2ed88 8 <internal>:(.symtab)
+ 0 0 127 1 .shstrtab
+ 0 0 127 1 <internal>:(.shstrtab)
+ 0 0 3b51c 1 .strtab
+ 0 0 3b51c 1 <internal>:(.strtab)
diff --git a/tests/testdata/link_map/example_rust.map b/tests/testdata/link_map/example_rust.map
new file mode 100644
index 0000000..fda29bc
--- /dev/null
+++ b/tests/testdata/link_map/example_rust.map
@@ -0,0 +1,272 @@
+ VMA LMA Size Align Out In Symbol
+ 2e0 2e0 8 1 .interp
+ 2e0 2e0 8 1 <internal>:(.interp)
+ 2e8 2e8 18 4 .note.gnu.build-id
+ 2e8 2e8 18 4 <internal>:(.note.gnu.build-id)
+ 300 300 ac8 8 .dynsym
+ 300 300 ac8 8 <internal>:(.dynsym)
+ dc8 dc8 1c 8 .gnu.hash
+ dc8 dc8 1c 8 <internal>:(.gnu.hash)
+ de8 de8 170 8 .dynamic
+ de8 de8 170 8 <internal>:(.dynamic)
+ f58 f58 821 1 .dynstr
+ f58 f58 821 1 <internal>:(.dynstr)
+ 1780 1780 48 8 .rela.dyn
+ 1780 1780 48 8 <internal>:(.rela.dyn)
+ 17c8 17c8 5b8 8 .relr.dyn
+ 17c8 17c8 5b8 8 <internal>:(.relr.dyn)
+ 1d80 1d80 a80 8 .rela.plt
+ 1d80 1d80 a80 8 <internal>:(.rela.plt)
+ 2800 2800 38445 16 .rodata
+ 2800 2800 27 1 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.745d157954bc19b57d1f47812cead630.3)
+ 2800 2800 0 1 $d.27
+ 2828 2828 0 8 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.745d157954bc19b57d1f47812cead630.5)
+ 2828 2828 33 1 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.745d157954bc19b57d1f47812cead630.13)
+ 2828 2828 0 1 $d.30
+ 2885 2885 11 1 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.rodata.anon.745d157954bc19b57d1f47812cead630.19.llvm.1460520995096272372)
+ 2885 2885 0 1 $d.34
+ 2885 2885 11 1 anon.745d157954bc19b57d1f47812cead630.19.llvm.1460520995096272372
+ 2896 2896 3 1 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.745d157954bc19b57d1f47812cead630.23)
+ 2896 2896 0 1 $d.36
+ 2899 2899 16 1 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.745d157954bc19b57d1f47812cead630.26)
+ 2899 2899 0 1 $d.37
+ 292a 292a d 1 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.rodata.anon.745d157954bc19b57d1f47812cead630.56.llvm.1460520995096272372)
+ 292a 292a 0 1 $d.57
+ 292a 292a d 1 anon.745d157954bc19b57d1f47812cead630.56.llvm.1460520995096272372
+ 2937 2937 5 1 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.rodata.anon.745d157954bc19b57d1f47812cead630.57.llvm.1460520995096272372)
+ 2937 2937 0 1 $d.58
+ 2937 2937 5 1 anon.745d157954bc19b57d1f47812cead630.57.llvm.1460520995096272372
+ 293c 293c 5 1 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.rodata.anon.745d157954bc19b57d1f47812cead630.59.llvm.1460520995096272372)
+ 293c 293c 0 1 $d.60
+ 293c 293c 5 1 anon.745d157954bc19b57d1f47812cead630.59.llvm.1460520995096272372
+ 2950 2950 a90 16 <internal>:(.rodata)
+ 33e0 33e0 29 1 ./exe.unstripped/component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.256afffd819e3254289014381fd650e5.2)
+ 33e0 33e0 0 1 $d.20
+ 346d 346d 0 1 ./exe.unstripped/component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.256afffd819e3254289014381fd650e5.14)
+ 3470 3470 38 8 ./exe.unstripped/component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.256afffd819e3254289014381fd650e5.16)
+ 3470 3470 0 1 $d.25
+ 34a8 34a8 1b08 4 <internal>:(.rodata)
+ 4fb0 4fb0 3 1 ./exe.unstripped/component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.256afffd819e3254289014381fd650e5.18)
+ 4fb0 4fb0 0 1 $d.27
+ 5070 5070 2780 8 <internal>:(.rodata)
+ 77f0 77f0 6 1 ./exe.unstripped/component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.256afffd819e3254289014381fd650e5.26)
+ 77f0 77f0 0 1 $d.34
+ 77f8 77f8 118 8 ./exe.unstripped/component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.256afffd819e3254289014381fd650e5.28)
+ 77f8 77f8 0 1 $d.36
+ 7910 7910 c 1 ./exe.unstripped/component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o:(.rodata.anon.256afffd819e3254289014381fd650e5.29.llvm.13894642946385378479)
+ 7910 7910 0 1 $d.37
+ 7910 7910 c 1 anon.256afffd819e3254289014381fd650e5.29.llvm.13894642946385378479
+ 791c 791c 6 1 ./exe.unstripped/component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o:(.rodata.anon.256afffd819e3254289014381fd650e5.30.llvm.13894642946385378479)
+ 791c 791c 0 1 $d.38
+ 791c 791c 6 1 anon.256afffd819e3254289014381fd650e5.30.llvm.13894642946385378479
+ 7922 7922 10 1 ./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.0.rcgu.o:(.rodata._RINvNtCs6QHBvYLDz7W_4core3ptr13drop_in_placeNtCsbDqzXfLQacH_15fidl_fuchsia_io11FileRequestECs4fqI2P2rA04_17component_manager)
+ 7922 7922 0 1 $d.13
+ 7950 7950 b1 2 ./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.0.rcgu.o:(.rodata._RNvXsc_NtCsbDqzXfLQacH_17fuchsia_component6serverINtB5_9ServiceFsINtNtB5_7service10ServiceObjuEENtNtCseXumDWplyBO_12futures_core6stream6Stream9poll_nextCs4fqI2P2rA04_17component_manager)
+ 7950 7950 0 1 $d.44
+ 7a08 7a08 380 8 <internal>:(.rodata)
+ 7d88 7d88 2b 1 ./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.0.rcgu.o:(.rodata..Lanon.dda71605b8fec89c031783be146b7025.1)
+ 7d88 7d88 0 1 $d.101
+ 8cf1 8cf1 4e 1 ./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.7.rcgu.o:(.rodata..Lanon.8db4583f006f833985ea18d2ab89bab7.1)
+ 8cf1 8cf1 0 1 $d.47
+ 8d3f 8d3f 1f 1 ./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.7.rcgu.o:(.rodata.anon.8db4583f006f833985ea18d2ab89bab7.4.llvm.7664684806704912226)
+ 8d3f 8d3f 0 1 $d.50
+ 8d3f 8d3f 1f 1 anon.8db4583f006f833985ea18d2ab89bab7.4.llvm.7664684806704912226
+ 8d5e 8d5e 25 1 ./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.8.rcgu.o:(.rodata..Lanon.a1c52c190a58d1713a23e7a7bdb0f9c0.2)
+ 8d5e 8d5e 0 1 $d.17
+ 8d83 8d83 7 1 ./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.9.rcgu.o:(.rodata._RINvNtCs6QHBvYLDz7W_4core3ptr13drop_in_placeINtNvNtB4_6future14from_generator9GenFutureNCNvMNtNtNtCsbDqzXfLQacH_21component_manager_lib5model6events6sourceNtB1s_11EventSource3new0EECs4fqI2P2rA04_17component_manager)
+ 8d83 8d83 0 1 $d.56
+ 8d8a 8d8a 8 1 ./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.9.rcgu.o:(.rodata._RINvNtCs6QHBvYLDz7W_4core3ptr13drop_in_placeINtNvNtB4_6future14from_generator9GenFutureNCNvMs2_NtNtCsbDqzXfLQacH_21component_manager_lib5model5realmNtB1v_5Realm19lock_resolved_state0EECs4fqI2P2rA04_17component_manager)
+ 8d8a 8d8a 0 1 $d.59
+ 8d92 8d92 16 1 ./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.9.rcgu.o:(.rodata._RINvNtCs6QHBvYLDz7W_4core3ptr13drop_in_placeINtNvNtB4_6future14from_generator9GenFutureNCNvMs_NtCsbDqzXfLQacH_21component_manager_lib19builtin_environmentNtB1u_18BuiltinEnvironment3new0EECs4fqI2P2rA04_17component_manager)
+ 8d92 8d92 0 1 $d.64
+ 8da8 8da8 4 1 ./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.9.rcgu.o:(.rodata._RINvNtCs6QHBvYLDz7W_4core3ptr13drop_in_placeNtCsbDqzXfLQacH_7cm_rust10ExposeDeclECs4fqI2P2rA04_17component_manager)
+ 8da8 8da8 0 1 $d.74
+ 8dac 8dac 6 1 ./exe.unstripped/component_manager.component_manager.7rcbfp3g-cgu.9.rcgu.o:(.rodata._RINvNtCs6QHBvYLDz7W_4core3ptr13drop_in_placeNtCsbDqzXfLQacH_7cm_rust7UseDeclECs4fqI2P2rA04_17component_manager)
+ 8dac 8dac 0 1 $d.81
+ 91e3 91e3 4 1 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.rodata._ZN4core3fmt9Formatter3pad17h9c055cdb18c92cc1E)
+ 91e3 91e3 0 1 $d.54
+ 91e7 91e7 4 1 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.rodata._ZN4core3fmt9Formatter19pad_formatted_parts17heb4f002989281314E)
+ 91e7 91e7 0 1 $d.56
+ 9214 9214 29 1 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.rodata._ZN41_$LT$char$u20$as$u20$core..fmt..Debug$GT$3fmt17hb14158a7386f6b1aE)
+ 9214 9214 0 1 $d.73
+ 925c 925c 6f 1 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.rodata._ZN61_$LT$core..str..EscapeDebug$u20$as$u20$core..fmt..Display$GT$3fmt17hf1238c3db5de3de5E)
+ 925c 925c 0 1 $d.130
+ 92d0 92d0 0 8 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.rodata.anon.6741f024a682c8cb8e59ecab4fc7e9ed.12.llvm.9325873315546439775)
+ 92d0 92d0 0 1 anon.6741f024a682c8cb8e59ecab4fc7e9ed.12.llvm.9325873315546439775
+ 92d0 92d0 1 1 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.6741f024a682c8cb8e59ecab4fc7e9ed.14)
+ 92d0 92d0 0 1 $d.133
+ 92d1 92d1 1d 1 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.rodata..Lanon.6741f024a682c8cb8e59ecab4fc7e9ed.27)
+ 92d1 92d1 0 1 $d.134
+ 9344 9344 28 4 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.rodata._ZN4core3num7flt2dec8strategy6dragon5POW1017he881f1848ad745b6E)
+ 9344 9344 0 1 $d.142
+ 9344 9344 28 1 core::num::flt2dec::strategy::dragon::POW10::he881f1848ad745b6
+ 936c 936c 28 4 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.rodata._ZN4core3num7flt2dec8strategy6dragon8TWOPOW1017h8ee033ba72605fdeE)
+ 936c 936c 0 1 $d.143
+ 936c 936c 28 1 core::num::flt2dec::strategy::dragon::TWOPOW10::h8ee033ba72605fde
+ 9394 9394 8 4 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.rodata._ZN4core3num7flt2dec8strategy6dragon9POW10TO1617hd008c9b35d67c443E)
+ 9394 9394 0 1 $d.144
+ 9394 9394 8 1 core::num::flt2dec::strategy::dragon::POW10TO16::hd008c9b35d67c443
+ 939c 939c 10 4 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.rodata._ZN4core3num7flt2dec8strategy6dragon9POW10TO3217h94bb3ddee75ca692E)
+ 939c 939c 0 1 $d.145
+ 939c 939c 10 1 core::num::flt2dec::strategy::dragon::POW10TO32::h94bb3ddee75ca692
+ 93ac 93ac 1c 4 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.rodata._ZN4core3num7flt2dec8strategy6dragon9POW10TO6417h2607b29da45ca92bE)
+ 93ac 93ac 0 1 $d.146
+ 93ac 93ac 1c 1 core::num::flt2dec::strategy::dragon::POW10TO64::h2607b29da45ca92b
+ ebf8 ebf8 28 1 ./exe.unstripped/component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.2.rcgu.o.rcgu.o:(.rodata..Lanon.31de557278eced41e2a6dce0e33ed7ec.9)
+ ebf8 ebf8 0 1 $d.392
+ ec20 ec20 1c 1 ./exe.unstripped/component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.2.rcgu.o.rcgu.o:(.rodata..Lanon.31de557278eced41e2a6dce0e33ed7ec.15)
+ ec20 ec20 0 1 $d.393
+ ec92 ec92 2e 1 anon.31de557278eced41e2a6dce0e33ed7ec.20.llvm.11955293494338827311
+ ecc0 ecc0 13 1 ./exe.unstripped/component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.2.rcgu.o.rcgu.o:(.rodata.anon.31de557278eced41e2a6dce0e33ed7ec.33.llvm.13709288095524858756)
+ ecc0 ecc0 0 1 $d.400
+ ecc0 ecc0 13 1 anon.31de557278eced41e2a6dce0e33ed7ec.33.llvm.13709288095524858756
+ ecd3 ecd3 4a 1 ./exe.unstripped/component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.2.rcgu.o.rcgu.o:(.rodata..Lanon.31de557278eced41e2a6dce0e33ed7ec.34)
+ ecd3 ecd3 0 1 $d.401
+ 2e20c 2e20c c 4 ./exe.unstripped/component_manager.libidna-5c3c4f537fb8313f-5c3c4f537fb8313f.idna.axcknxfw-cgu.15.rcgu.o.rcgu.o:(.rodata..Lanon.93519e6636017643a07f78e678f4f11e.3501)
+ 2e20c 2e20c 0 1 $d.656
+ 2e218 2e218 c 4 ./exe.unstripped/component_manager.libidna-5c3c4f537fb8313f-5c3c4f537fb8313f.idna.axcknxfw-cgu.15.rcgu.o.rcgu.o:(.rodata..Lanon.93519e6636017643a07f78e678f4f11e.3502)
+ 2e218 2e218 0 1 $d.657
+ 2e224 2e224 c 4 ./exe.unstripped/component_manager.libidna-5c3c4f537fb8313f-5c3c4f537fb8313f.idna.axcknxfw-cgu.15.rcgu.o.rcgu.o:(.rodata..Lanon.93519e6636017643a07f78e678f4f11e.3503)
+ 2e224 2e224 0 1 $d.658
+ 2f4ca 2f4ca 1b 1 ./exe.unstripped/component_manager.libio_util.io_util.3a1fbbbh-cgu.3.rcgu.o.rcgu.o:(.rodata.anon.7cbed09a5c2e2961b1298b053de6d71a.14.llvm.4649089991547526960)
+ 2f4ca 2f4ca 0 1 $d.33
+ 2f4ca 2f4ca 1b 1 anon.7cbed09a5c2e2961b1298b053de6d71a.14.llvm.4649089991547526960
+ 2f4e5 2f4e5 28 1 ./exe.unstripped/component_manager.libio_util.io_util.3a1fbbbh-cgu.8.rcgu.o.rcgu.o:(.rodata..Lanon.2dc76abe0f971e71acd588990ffc8513.0)
+ 2f4e5 2f4e5 0 1 $d.16
+ 2f50d 2f50d 2b 1 ./exe.unstripped/component_manager.liblibrary_loader.library_loader.3a1fbbbh-cgu.1.rcgu.o.rcgu.o:(.rodata..Lanon.6f9bafb4adb671d7ac9f8408e86ff31a.0)
+ 2f50d 2f50d 0 1 $d.8
+ 3aacc 3aacc 134 2 /tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o):(.rodata._ZN9libunwind17DwarfInstructionsINS_17LocalAddressSpaceENS_15Registers_arm64EE18evaluateExpressionEmRS1_RKS2_m)
+ 3aacc 3aacc 0 1 $d.44
+ 3ac00 3ac00 39 1 /tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o):(.rodata._ZN9libunwind10CFI_ParserINS_17LocalAddressSpaceEE8parseCIEERS1_mPNS2_8CIE_InfoE)
+ 3ac00 3ac00 0 1 $d.51
+ 3ac39 3ac39 c 1 /tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o):(.rodata._ZN9libunwind14EHHeaderParserINS_17LocalAddressSpaceEE17getTableEntrySizeEh)
+ 3ac39 3ac39 0 1 $d.53
+ 3ac45 3ac45 22 1 .debug_gdb_scripts
+ 3ac45 3ac45 22 1 <internal>:(.debug_gdb_scripts)
+ 3ac68 3ac68 17624 4 .eh_frame_hdr
+ 3ac68 3ac68 17624 4 <internal>:(.eh_frame_hdr)
+ 52290 52290 503ac 8 .eh_frame
+ 52290 52290 14 1 /usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/c/crt1.Scrt1.cc.o:(.eh_frame+0x0)
+ 522a8 522a8 1c 1 /usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/c/crt1.Scrt1.cc.o:(.eh_frame+0x14)
+ 522c8 522c8 14 1 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.eh_frame+0x14)
+ 522e0 522e0 34 1 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.eh_frame+0x28)
+ a2620 a2620 14 1 /tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o):(.eh_frame+0x5ec)
+ b2640 b2640 13c78c 8 .text
+ b2640 b2640 14 8 /usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/c/crt1.Scrt1.cc.o:(.text._start)
+ b2640 b2640 0 1 $x.0
+ b2640 b2640 14 1 _start
+ b2654 b2654 1c 4 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.text._ZN42_$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$3fmt17h12b31caec1d188c4E)
+ b2654 b2654 0 1 $x.0
+ b2654 b2654 1c 1 _$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$::fmt::h12b31caec1d188c4
+ b2670 b2670 b4 4 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.text._ZN42_$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$3fmt17h268a568299531efbE)
+ b2670 b2670 0 1 $x.1
+ b2670 b2670 b4 1 _$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$::fmt::h268a568299531efb
+ cd52c cd52c 98 4 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.text._ZN4core3num52_$LT$impl$u20$core..str..FromStr$u20$for$u20$u64$GT$8from_str17he51bc12ed2f79990E)
+ cd52c cd52c 0 1 $x.102
+ cd52c cd52c 98 1 core::num::_$LT$impl$u20$core..str..FromStr$u20$for$u20$u64$GT$::from_str::he51bc12ed2f79990
+ cd52c cd52c 98 1 core::num::_$LT$impl$u20$core..str..FromStr$u20$for$u20$usize$GT$::from_str::h4398f35a1c0b4be7
+ cd5c4 cd5c4 e4 4 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.text._ZN63_$LT$core..num..TryFromIntError$u20$as$u20$core..fmt..Debug$GT$3fmt17h10047a113409f418E)
+ cd5c4 cd5c4 0 1 $x.103
+ cd5c4 cd5c4 e4 1 _$LT$core..num..TryFromIntError$u20$as$u20$core..fmt..Debug$GT$::fmt::h10047a113409f418
+ cd6a8 cd6a8 34 4 ./exe.unstripped/component_manager.core-6a4b1e7af979d229.core.3ocx6h1n-cgu.0.rcgu.o.rcgu.o:(.text._ZN4core3fmt5float52_$LT$impl$u20$core..fmt..Display$u20$for$u20$f64$GT$3fmt17hac033855e28d5eb4E)
+ cd6a8 cd6a8 0 1 $x.104
+ cd6a8 cd6a8 34 1 core::fmt::float::_$LT$impl$u20$core..fmt..Display$u20$for$u20$f64$GT$::fmt::hac033855e28d5eb4
+ e2554 e2554 10 4 ./exe.unstripped/component_manager.libcm_fidl_translator.cm_fidl_translator.3a1fbbbh-cgu.0.rcgu.o.rcgu.o:(.text.OUTLINED_FUNCTION_359)
+ e2554 e2554 0 1 $x.752
+ e2554 e2554 10 1 OUTLINED_FUNCTION_359
+ e2564 e2564 10 4 ./exe.unstripped/component_manager.libcm_fidl_translator.cm_fidl_translator.3a1fbbbh-cgu.0.rcgu.o.rcgu.o:(.text.OUTLINED_FUNCTION_360)
+ e2564 e2564 0 1 $x.753
+ e2564 e2564 10 1 OUTLINED_FUNCTION_360
+ eb888 eb888 40 4 ./exe.unstripped/component_manager.libcm_fidl_translator.cm_fidl_translator.3a1fbbbh-cgu.4.rcgu.o.rcgu.o:(.text._RINvNtNtCs6QHBvYLDz7W_4core4iter8adapters15process_resultsINtB2_3MapINtNtCsiEMmsMe2HZG_5alloc3vec8IntoIterNtNtCsbDqzXfLQacH_7cm_json2cm8ResolverENCNvXs_CsbDqzXfLQacH_18cm_fidl_translatorINtB17_3VecB1G_EINtB2q_6CmIntoIB2Z_NtCsbDqzXfLQacH_17fidl_fuchsia_sys212ResolverDeclEE7cm_into0EB3x_NtB1K_5ErrorNCINvXsx_NtB6_6resultINtB4V_6ResultB3s_B4A_EINtNtNtB4_6traits7collect12FromIteratorIB58_B3x_B4A_EE9from_iterBU_E0B3s_EB2q_)
+ eb888 eb888 0 1 $x.15
+ eb888 eb888 40 1 _RINvNtNtCs6QHBvYLDz7W_4core4iter8adapters15process_resultsINtB2_3MapINtNtCsiEMmsMe2HZG_5alloc3vec8IntoIterNtNtCsbDqzXfLQacH_7cm_json2cm8ResolverENCNvXs_CsbDqzXfLQacH_18cm_fidl_translatorINtB17_3VecB1G_EINtB2q_6CmIntoIB2Z_NtCsbDqzXfLQacH_17fidl_fuchsia_sys212ResolverDeclEE7cm_into0EB3x_NtB1K_5ErrorNCINvXsx_NtB6_6resultINtB4V_6ResultB3s_B4A_EINtNtNtB4_6traits7collect12FromIteratorIB58_B3x_B4A_EE9from_iterBU_E0B3s_EB2q_
+ 197340 197340 70 4 ./exe.unstripped/component_manager.libfuchsia_async.fuchsia_async.3a1fbbbh-cgu.1.rcgu.o.rcgu.o:(.text._RINvNtCsevZhND7KDbE_9hashbrown3raw16calculate_layoutTjINtNtCsiEMmsMe2HZG_5alloc4sync3ArcDNtNtCsbDqzXfLQacH_13fuchsia_async8executor14PacketReceiverEL_EEEB1t_)
+ 197340 197340 0 1 $x.6
+ 197340 197340 70 1 _RINvNtCsevZhND7KDbE_9hashbrown3raw16calculate_layoutTjINtNtCsiEMmsMe2HZG_5alloc4sync3ArcDNtNtCsbDqzXfLQacH_13fuchsia_async8executor14PacketReceiverEL_EEEB1t_
+ 200800 200800 38 32 ./exe.unstripped/component_manager.std-7a72ffd2f3a7a812.std.a0uwjzoe-cgu.0.rcgu.o.rcgu.o:(.tbss._ZN3std10sys_common11thread_info11THREAD_INFO7__getit5__KEY17h836843d10e59cadaE.llvm.8019338592007173243)
+ 1300 1300 38 1 std::sys_common::thread_info::THREAD_INFO::__getit::__KEY::h836843d10e59cada (.llvm.8019338592007173243)
+ 200800 200800 0 1 $d.517
+ 200840 200840 18 16 ./exe.unstripped/component_manager.std-7a72ffd2f3a7a812.std.a0uwjzoe-cgu.0.rcgu.o.rcgu.o:(.tbss._ZN3std9panicking18update_panic_count11PANIC_COUNT7__getit5__KEY17h69785e8fa31a63e6E.llvm.8019338592007173243)
+ 1340 1340 18 1 std::panicking::update_panic_count::PANIC_COUNT::__getit::__KEY::h69785e8fa31a63e6 (.llvm.8019338592007173243)
+ 200840 200840 0 1 $d.557
+ 1ff610 1ff610 15db8 8 .data.rel.ro
+ 1ff610 1ff610 30 8 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.data.rel.ro..Lanon.745d157954bc19b57d1f47812cead630.1)
+ 1ff610 1ff610 0 1 $d.25
+ 1ff640 1ff640 20 8 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.data.rel.ro..Lanon.745d157954bc19b57d1f47812cead630.2)
+ 1ff640 1ff640 0 1 $d.26
+ 1ff660 1ff660 18 8 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.data.rel.ro..Lanon.745d157954bc19b57d1f47812cead630.4)
+ 1ff660 1ff660 0 1 $d.28
+ 202a88 202a88 20 8 ./exe.unstripped/component_manager.libcm_fidl_validator.cm_fidl_validator.3a1fbbbh-cgu.0.rcgu.o.rcgu.o:(.data.rel.ro.anon.e57b3f2ae8b5861992cfe341e01c75d6.1.llvm.9882057474999025597)
+ 202a88 202a88 0 1 $d.287
+ 202a88 202a88 20 1 anon.e57b3f2ae8b5861992cfe341e01c75d6.1.llvm.9882057474999025597
+ 2110a8 2110a8 18 8 ./exe.unstripped/component_manager.liblibrary_loader.library_loader.3a1fbbbh-cgu.1.rcgu.o.rcgu.o:(.data.rel.ro..Lanon.6f9bafb4adb671d7ac9f8408e86ff31a.3)
+ 2110a8 2110a8 0 1 $d.10
+ 211108 211108 10 8 ./exe.unstripped/component_manager.liblibrary_loader.library_loader.3a1fbbbh-cgu.1.rcgu.o.rcgu.o:(.data.rel.ro.anon.6f9bafb4adb671d7ac9f8408e86ff31a.9.llvm.14732610969729869220)
+ 211108 211108 0 1 $d.16
+ 211108 211108 10 1 anon.6f9bafb4adb671d7ac9f8408e86ff31a.9.llvm.14732610969729869220
+ 211118 211118 38 8 ./exe.unstripped/component_manager.liblibrary_loader.library_loader.3a1fbbbh-cgu.1.rcgu.o.rcgu.o:(.data.rel.ro.anon.6f9bafb4adb671d7ac9f8408e86ff31a.12.llvm.14732610969729869220)
+ 211118 211118 0 1 $d.19
+ 211118 211118 38 1 anon.6f9bafb4adb671d7ac9f8408e86ff31a.12.llvm.14732610969729869220
+ 211150 211150 20 8 ./exe.unstripped/component_manager.liblibrary_loader.library_loader.3a1fbbbh-cgu.10.rcgu.o.rcgu.o:(.data.rel.ro.anon.54c490f3ebdb496a1657b64fb6de7217.0.llvm.16392173952262690328)
+ 211150 211150 0 1 $d.7
+ 211150 211150 20 1 anon.54c490f3ebdb496a1657b64fb6de7217.0.llvm.16392173952262690328
+ 211170 211170 18 8 ./exe.unstripped/component_manager.liblibrary_loader.library_loader.3a1fbbbh-cgu.11.rcgu.o.rcgu.o:(.data.rel.ro..Lanon.39cf32991562f2df00749835baa6fdc6.1)
+ 211170 211170 0 1 $d.19
+ 215018 215018 18 8 ./exe.unstripped/component_manager.std-7a72ffd2f3a7a812.std.a0uwjzoe-cgu.0.rcgu.o.rcgu.o:(.data.rel.ro.anon.d8e73c4ab2af7c41d0ca8802142f134e.759.llvm.8019338592007173243)
+ 215018 215018 0 1 $d.556
+ 215018 215018 18 1 anon.d8e73c4ab2af7c41d0ca8802142f134e.759.llvm.8019338592007173243
+ 215030 215030 88 8 /tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o):(.data.rel.ro._ZTVN9libunwind12UnwindCursorINS_17LocalAddressSpaceENS_15Registers_arm64EEE)
+ 215030 215030 0 1 $d.58
+ 215030 215030 88 1 vtable for libunwind::UnwindCursor<libunwind::LocalAddressSpace, libunwind::Registers_arm64>
+ 2150b8 2150b8 310 8 /tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o):(.data.rel.ro..Lswitch.table._ZN9libunwind15Registers_arm6415getRegisterNameEi)
+ 2150b8 2150b8 0 1 $d.64
+ 2153c8 2153c8 480 8 .got
+ 2153c8 2153c8 480 8 <internal>:(.got)
+ 225848 225848 88 8 .data
+ 225848 225848 10 8 ./exe.unstripped/component_manager.liblog-51a8548556518bd3-51a8548556518bd3.log.7ncv9mgr-cgu.0.rcgu.o.rcgu.o:(.data._RNvCsi1CgODtnl2B_3log6LOGGER.llvm.13428694738827280366)
+ 225848 225848 0 1 $d.13
+ 225848 225848 10 1 _RNvCsi1CgODtnl2B_3log6LOGGER.llvm.13428694738827280366
+ 225858 225858 8 8 ./exe.unstripped/component_manager.std-7a72ffd2f3a7a812.std.a0uwjzoe-cgu.0.rcgu.o.rcgu.o:(.data._ZN3std6thread8ThreadId3new7COUNTER17hb3a413a29c0f81f6E)
+ 225858 225858 0 1 $d.306
+ 225858 225858 8 1 std::thread::ThreadId::new::COUNTER::hb3a413a29c0f81f6
+ 225860 225860 38 8 ./exe.unstripped/component_manager.std-7a72ffd2f3a7a812.std.a0uwjzoe-cgu.0.rcgu.o.rcgu.o:(.data._ZN3std2io5stdio6stderr8INSTANCE17hb24941292040ef06E)
+ 225860 225860 0 1 $d.363
+ 225860 225860 38 1 std::io::stdio::stderr::INSTANCE::hb24941292040ef06
+ 2258c8 2258c8 8 8 /tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o):(.data._ZN9libunwind13DwarfFDECacheINS_17LocalAddressSpaceEE10_bufferEndE)
+ 2258c8 2258c8 0 1 $d.63
+ 2258c8 2258c8 8 1 libunwind::DwarfFDECache<libunwind::LocalAddressSpace>::_bufferEnd
+ 2258d0 2258d0 398 8 .got.plt
+ 2258d0 2258d0 398 8 <internal>:(.got.plt)
+ 225c68 225c68 e5d 8 .bss
+ 225c68 225c68 60 8 ./exe.unstripped/component_manager.libcm_rust.cm_rust.3a1fbbbh-cgu.0.rcgu.o.rcgu.o:(.bss..L_MergedGlobals)
+ 225c68 225c68 0 1 $d.378
+ 225c68 225c68 20 1 _RNvNvNvXs15_CsbDqzXfLQacH_7cm_rustNtBa_13DATA_TYPENAMENtNtNtCs6QHBvYLDz7W_4core3ops5deref5Deref5deref11___stability4LAZY.llvm.2893117974861896834
+ 225c88 225c88 20 1 _RNvNvNvXs17_CsbDqzXfLQacH_7cm_rustNtBa_14CACHE_TYPENAMENtNtNtCs6QHBvYLDz7W_4core3ops5deref5Deref5deref11___stability4LAZY.llvm.2893117974861896834
+ 225ca8 225ca8 20 1 _RNvNvNvXs19_CsbDqzXfLQacH_7cm_rustNtBa_13META_TYPENAMENtNtNtCs6QHBvYLDz7W_4core3ops5deref5Deref5deref11___stability4LAZY.llvm.2893117974861896834
+ 225cc8 225cc8 38 8 ./exe.unstripped/component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.12.rcgu.o.rcgu.o:(.bss._RNvNvNvXs0_NtNtNtCsbDqzXfLQacH_21component_manager_lib5model6events14source_factoryNtB9_25EVENT_SOURCE_SERVICE_PATHNtNtNtCs6QHBvYLDz7W_4core3ops5deref5Deref5deref11___stability4LAZY.llvm.16599889146946869799)
+ 225cc8 225cc8 0 1 $d.347
+ 225cc8 225cc8 38 1 _RNvNvNvXs0_NtNtNtCsbDqzXfLQacH_21component_manager_lib5model6events14source_factoryNtB9_25EVENT_SOURCE_SERVICE_PATHNtNtNtCs6QHBvYLDz7W_4core3ops5deref5Deref5deref11___stability4LAZY.llvm.16599889146946869799
+ 225d00 225d00 38 8 ./exe.unstripped/component_manager.libcomponent_manager_lib.component_manager_lib.3a1fbbbh-cgu.12.rcgu.o.rcgu.o:(.bss._RNvNvNvXs2_NtNtNtCsbDqzXfLQacH_21component_manager_lib5model6events14source_factoryNtB9_30EVENT_SOURCE_SYNC_SERVICE_PATHNtNtNtCs6QHBvYLDz7W_4core3ops5deref5Deref5deref11___stability4LAZY.llvm.16599889146946869799)
+ 225d00 225d00 0 1 $d.348
+ 225d00 225d00 38 1 _RNvNvNvXs2_NtNtNtCsbDqzXfLQacH_21component_manager_lib5model6events14source_factoryNtB9_30EVENT_SOURCE_SYNC_SERVICE_PATHNtNtNtCs6QHBvYLDz7W_4core3ops5deref5Deref5deref11___stability4LAZY.llvm.16599889146946869799
+ 2262ac 2262ac 8 4 /tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o):(.bss._ZN9libunwind13DwarfFDECacheINS_17LocalAddressSpaceEE5_lockE)
+ 2262ac 2262ac 0 1 $d.59
+ 2262ac 2262ac 8 1 libunwind::DwarfFDECache<libunwind::LocalAddressSpace>::_lock
+ 2262b8 2262b8 800 8 /tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o):(.bss._ZN9libunwind13DwarfFDECacheINS_17LocalAddressSpaceEE14_initialBufferE)
+ 2262b8 2262b8 0 1 $d.62
+ 2262b8 2262b8 800 1 libunwind::DwarfFDECache<libunwind::LocalAddressSpace>::_initialBuffer
+ 226ab8 226ab8 d 4 /tmp/rustcL27BaP/libunwind-e14dabfa3d8ce62b.rlib(libunwind.o):(.bss..L_MergedGlobals)
+ 226ab8 226ab8 0 1 $d.65
+ 226ab8 226ab8 1 1 logAPIs::checked
+ 226abc 226abc 1 1 logAPIs::log
+ 226abd 226abd 1 1 logUnwinding::checked
+ 226ac0 226ac0 1 1 logUnwinding::log
+ 226ac1 226ac1 1 1 logDWARF::checked
+ 226ac4 226ac4 1 1 logDWARF::log
+ 0 0 48c441 1 .debug_loc
+ 0 0 39 1 /usr/local/google/home/yifeit/vg/out/default.zircon/user-arm64-clang.shlib/obj/system/ulib/c/crt1.Scrt1.cc.o:(.debug_loc)
+ 0 0 0 1 $d.1
+ 39 39 6d97 1 ./exe.unstripped/component_manager.alloc-54127f36ba192482.alloc.4k1iwrm2-cgu.0.rcgu.o.rcgu.o:(.debug_loc)
+ 39 39 0 1 $d.63
+ 6dd0 6dd0 280c 1 ./exe.unstripped/component_manager.backtrace-5619ad41e613872b.backtrace.e1f4vorm-cgu.0.rcgu.o.rcgu.o:(.debug_loc)
+ 6dd0 6dd0 0 1 $d.41
diff --git a/tests/testdata/link_map/test_lld-lto_v1.map b/tests/testdata/link_map/test_lld-lto_v1.map
new file mode 100644
index 0000000..9658718
--- /dev/null
+++ b/tests/testdata/link_map/test_lld-lto_v1.map
@@ -0,0 +1,410 @@
+# Test Linker map for LLD with ThinLTO, "v1" format.
+# .map files actually don't have comments and blank lines! These are added to
+# improve documentation, and are stripped by tests.
+
+# First line is needed to identify .map file type.
+ VMA LMA Size Align Out In Symbol
+# Extract sizes for every section, and symbols for selected sections only.
+# Size-only sections.
+ 174 174 13 1 .interp
+ 174 174 13 1 <internal>:(.interp)
+ 188 188 1e2678 4 .ARM.exidx
+ 188 188 8 4 obj/third_party/breakpad/libclient.a(client/breakpad_getcontext.o):(.ARM.exidx)
+ 190 190 8 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(chrono.o):(.ARM.exidx.text.__clang_call_terminate)
+ 190 190 0 1 $d
+ 198 198 8 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(chrono.o):(.ARM.exidx.text._ZNSt6__ndk16chrono12steady_clock3nowEv)
+ 198 198 0 1 $d
+ 1a0 1a0 8 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(exception.o):(.ARM.exidx.text._ZSt18uncaught_exceptionv)
+ 1a0 1a0 0 1 $d
+ 2340 2340 8 4 lto.tmp:(.ARM.exidx.text._ZN5media18CdmPromiseTemplateIJEE26RejectPromiseOnDestructionEv)
+ 2348 2348 8 4 lto.tmp:(.ARM.exidx.text._ZN5media18CdmPromiseTemplateIJEED2Ev)
+ 1e2800 1e2800 1870 4 .dynsym
+ 1e2800 1e2800 1870 4 <internal>:(.dynsym)
+ 1e4070 1e4070 30e 2 .gnu.version
+ 1e4070 1e4070 30e 2 <internal>:(.gnu.version)
+ 1e4380 1e4380 60 4 .gnu.version_r
+ 1e4380 1e4380 60 4 <internal>:(.gnu.version_r)
+ 1e43e0 1e43e0 1c 4 .gnu.hash
+ 1e43e0 1e43e0 1c 4 <internal>:(.gnu.hash)
+ 1e43fc 1e43fc f6b 1 .dynstr
+ 1e43fc 1e43fc f6b 1 <internal>:(.dynstr)
+ 1e5368 1e5368 2d1d5 4 .rel.dyn
+ 1e5368 1e5368 2d1d5 4 <internal>:(.rel.dyn)
+ 212540 212540 bd8 4 .rel.plt
+ 212540 212540 bd8 4 <internal>:(.rel.plt)
+ 213118 213118 1c 4 .note.crashpad.info
+ 213118 213118 1c 4 obj/third_party/crashpad/crashpad/client/libclient.a(client/crashpad_info_note.o):(.note.crashpad.info)
+ 213118 213118 1c 1 CRASHPAD_NOTE
+ 213124 213124 0 1 name
+ 21312d 21312d 0 1 name_end
+ 213130 213130 0 1 desc
+ 213134 213134 0 1 desc_end
+ 213134 213134 98 4 .note.android.ident
+ 213134 213134 98 4 ../../third_party/android_ndk/platforms/android-16/arch-arm/usr/lib/crtbegin_so.o:(.note.android.ident)
+ 213134 213134 0 1 $d
+ 213134 213134 98 1 note_android_ident
+ 213140 213140 0 1 note_name
+ 213148 213148 0 1 note_data
+ 21314c 21314c 0 1 ndk_version
+ 21318c 21318c 0 1 ndk_build_number
+ 2131cc 2131cc 0 1 note_end
+ 2131cc 2131cc 24 4 .note.gnu.build-id
+ 2131cc 2131cc 24 4 <internal>:(.note.gnu.build-id)
+
+# .rodata: Extract symbols and size (read-only data).
+ 213200 213200 611e4b 256 .rodata
+ 213200 213200 4 1 obj/v8/v8_external_snapshot/embedded.o:(.rodata)
+ 213200 213200 0 1 v8_Default_embedded_blob_size_
+ 213210 213210 20 16 obj/third_party/ffmpeg/libffmpeg_internal.a(ffmpeg_internal/fft_neon.o):(.rodata)
+ 213210 213210 10 1 pmmp
+ 213220 213220 10 1 mppm
+ 213230 213230 180 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(hash.o):(.rodata)
+ 213230 213230 c0 1 std::__ndk1::(anonymous namespace)::small_primes
+ 213230 213230 0 1 $d
+ 2132f0 2132f0 c0 1 std::__ndk1::(anonymous namespace)::indices
+ 2133b0 2133b0 d1 16 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(ios.o):(.rodata)
+ 2133b0 2133b0 0 1 $d
+ 2133b0 2133b0 4 1 std::__ndk1::ios_base::boolalpha
+ 2133b4 2133b4 4 1 std::__ndk1::ios_base::dec
+ 2133b8 2133b8 4 1 std::__ndk1::ios_base::fixed
+ 2133bc 2133bc 4 1 std::__ndk1::ios_base::hex
+ 2133c0 2133c0 4 1 std::__ndk1::ios_base::internal
+ 2133c4 2133c4 4 1 std::__ndk1::ios_base::left
+ 2133c8 2133c8 4 1 std::__ndk1::ios_base::oct
+ 2133cc 2133cc 4 1 std::__ndk1::ios_base::right
+ 2133d0 2133d0 4 1 std::__ndk1::ios_base::scientific
+ 2133d4 2133d4 4 1 std::__ndk1::ios_base::showbase
+ 2133d8 2133d8 4 1 std::__ndk1::ios_base::showpoint
+ 2133dc 2133dc 4 1 std::__ndk1::ios_base::showpos
+ 2133e0 2133e0 4 1 std::__ndk1::ios_base::skipws
+ 2133e4 2133e4 4 1 std::__ndk1::ios_base::unitbuf
+ 2133e8 2133e8 4 1 std::__ndk1::ios_base::uppercase
+ 2133ec 2133ec 4 1 std::__ndk1::ios_base::adjustfield
+ 2133f0 2133f0 4 1 std::__ndk1::ios_base::basefield
+ 2133f4 2133f4 4 1 std::__ndk1::ios_base::floatfield
+ 2133f8 2133f8 4 1 std::__ndk1::ios_base::badbit
+ 2133fc 2133fc 4 1 std::__ndk1::ios_base::eofbit
+ 213400 213400 4 1 std::__ndk1::ios_base::failbit
+ 213404 213404 4 1 std::__ndk1::ios_base::goodbit
+ 213408 213408 4 1 std::__ndk1::ios_base::app
+ 21340c 21340c 4 1 std::__ndk1::ios_base::ate
+ 213410 213410 4 1 std::__ndk1::ios_base::binary
+ 213414 213414 4 1 std::__ndk1::ios_base::in
+ 213418 213418 4 1 std::__ndk1::ios_base::out
+ 21341c 21341c 4 1 std::__ndk1::ios_base::trunc
+ 213420 213420 1d 1 typeinfo name for std::__ndk1::ios_base::failure
+ 213440 213440 15 1 typeinfo name for std::__ndk1::ios_base
+ 213460 213460 21 1 typeinfo name for std::__ndk1::__iostream_category
+ 213490 213490 2d 16 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(ios.o):(.rodata._ZTSNSt6__ndk19basic_iosIcNS_11char_traitsIcEEEE)
+ 213490 213490 0 1 $d
+
+# <internal>, which is Level 2 only.
+ 21368b 21368b 2c378e 1 <internal>:(.rodata)
+# Single symbol mixed with $d.
+ 4d6e20 4d6e20 1b 16 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(iostream.o):(.rodata._ZTSNSt6__ndk110__stdinbufIwEE)
+ 4d6e20 4d6e20 0 1 $d
+ 4d6e20 4d6e20 1b 1 typeinfo name for std::__ndk1::__stdinbuf<wchar_t>
+
+# More <internal>.
+ 4d7920 4d7920 266c 4 <internal>:(.rodata)
+ 4d9f90 4d9f90 18 16 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(locale.o):(.rodata._ZTSNSt6__ndk110ctype_baseE)
+ 4d9f90 4d9f90 0 1 $d
+ 4d9f90 4d9f90 18 1 typeinfo name for std::__ndk1::ctype_base
+ 4d9fb0 4d9fb0 1a 16 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(locale.o):(.rodata._ZTSNSt6__ndk112codecvt_baseE)
+ 4d9fb0 4d9fb0 0 1 $d
+ 4d9fb0 4d9fb0 1a 1 typeinfo name for std::__ndk1::codecvt_base
+# thinlto-cache at Level 2.
+ 503eb3 503eb3 1 1 thinlto-cache/Thin-84596a.tmp.o:(.rodata._ZN7network5mojom45CookieManagerProxy_SetCanonicalCookie_Message11kMessageTagE)
+ 503eb3 503eb3 1 1 network::mojom::CookieManagerProxy_SetCanonicalCookie_Message::kMessageTag
+ 503eb4 503eb4 1 1 thinlto-cache/Thin-84596a.tmp.o:(.rodata._ZN7network5mojom48CookieManagerProxy_DeleteCanonicalCookie_Message11kMessageTagE)
+ 503eb4 503eb4 1 1 network::mojom::CookieManagerProxy_DeleteCanonicalCookie_Message::kMessageTag
+
+# Size-only sections.
+ 82504c 82504c 8ffc 4 .ARM.extab
+ 82504c 82504c 40 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(chrono.o):(.ARM.extab.text._ZNSt6__ndk16chrono12steady_clock3nowEv)
+ 82504c 82504c 0 1 $d
+ 825058 825058 0 1 GCC_except_table4
+ 82508c 82508c 38 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(hash.o):(.ARM.extab.text._ZNSt6__ndk112__next_primeEj)
+ 82508c 82508c 0 1 $d
+ 825098 825098 0 1 GCC_except_table0
+ 8250c4 8250c4 48 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(ios.o):(.ARM.extab.text._ZNSt6__ndk18ios_base5clearEj)
+ 8250c4 8250c4 0 1 $d
+ 8250d0 8250d0 0 1 GCC_except_table5
+
+# .text: Extract symbols and size (executable code).
+ 82f000 82f000 251b7e8 64 .text
+# Multiple Level 3 symbols under Level 2. $a annotates ARM symbols (not Thumb2).
+# Also, data precede first Level 3 line.
+ 82f000 82f000 cdc 32 obj/third_party/boringssl/boringssl_asm/chacha-armv4.o:(.text)
+ 82f004 82f004 0 1 $d.0
+ 82f040 82f040 0 1 $a.1
+ 82f040 82f040 3f0 1 ChaCha20_ctr32
+ 82f440 82f440 89c 1 ChaCha20_neon
+ 82fce0 82fce0 130c 32 obj/third_party/boringssl/boringssl_asm/aes-armv4.o:(.text)
+ 82fce0 82fce0 0 1 $d.0
+ 82fce0 82fce0 540 1 AES_Te
+ 830220 830220 0 1 $a.1
+ 830220 830220 60 1 aes_nohw_encrypt
+ 830280 830280 1d8 1 _armv4_AES_encrypt
+ 830460 830460 0 1 _armv4_AES_set_encrypt_key
+ 830460 830460 2a0 1 aes_nohw_set_encrypt_key
+ 830700 830700 20 1 aes_nohw_set_decrypt_key
+ 830720 830720 0 1 _armv4_AES_set_enc2dec_key
+ 830720 830720 124 1 AES_set_enc2dec_key
+ 830860 830860 0 1 $d.2
+ 830860 830860 500 1 AES_Td
+ 830d60 830d60 0 1 $a.3
+ 830d60 830d60 60 1 aes_nohw_decrypt
+ 830dc0 830dc0 1f8 1 _armv4_AES_decrypt
+ 830fb8 830fb8 0 1 $d.4
+
+# Many Level 3 annotations. Also, data precede first Level 3 line.
+ 831000 831000 a28 32 obj/third_party/boringssl/boringssl_asm/aesv8-armx32.o:(.text)
+ 831004 831004 0 1 $d.0
+ 831040 831040 0 1 $a.1
+ 831040 831040 0 1 .Lenc_key
+ 831040 831040 218 1 aes_hw_set_encrypt_key
+ 8310b0 8310b0 0 1 $d.2
+ 8310b4 8310b4 0 1 $a.3
+ 8310f0 8310f0 0 1 $d.4
+ 8310f4 8310f4 0 1 $a.5
+ 831124 831124 0 1 $d.6
+ 831128 831128 0 1 $a.7
+ 831180 831180 0 1 $d.8
+ 831184 831184 0 1 $a.9
+ 8311f0 8311f0 0 1 $d.10
+ 8311f4 8311f4 0 1 $a.11
+ 831228 831228 0 1 $d.12
+ 83122c 83122c 0 1 $a.13
+ 831260 831260 60 1 aes_hw_set_decrypt_key
+ 831294 831294 0 1 $d.14
+ 83129c 83129c 0 1 $a.15
+ 8312b0 8312b0 0 1 $d.16
+ 8312b4 8312b4 0 1 $a.17
+ 8312c0 8312c0 50 1 aes_hw_encrypt
+ 8312d4 8312d4 0 1 $d.18
+ 8312dc 8312dc 0 1 $a.19
+ 8312e4 8312e4 0 1 $d.20
+ 8312ec 8312ec 0 1 $a.21
+ 8312f4 8312f4 0 1 $d.22
+ 8312fc 8312fc 0 1 $a.23
+ 831300 831300 0 1 $d.24
+ 831304 831304 0 1 $a.25
+ 831320 831320 50 1 aes_hw_decrypt
+ 831334 831334 0 1 $d.26
+ 83133c 83133c 0 1 $a.27
+ 831344 831344 0 1 $d.28
+ 83134c 83134c 0 1 $a.29
+ 831354 831354 0 1 $d.30
+ 83135c 83135c 0 1 $a.31
+ 831360 831360 0 1 $d.32
+ 831364 831364 0 1 $a.33
+# ... (truncated).
+
+# Symbols with Size = 0 from assembly. Level 3 items are functions.
+ 83c640 83c640 13aee0 32 obj/v8/v8_external_snapshot/embedded.o:(.text)
+ 83c640 83c640 0 1 v8_Default_embedded_blob_data_
+ 83f4a0 83f4a0 0 1 Builtins_RecordWrite
+ 83f7c0 83f7c0 0 1 Builtins_AdaptorWithExitFrame
+ 83f800 83f800 0 1 Builtins_AdaptorWithBuiltinExitFrame
+ 83f840 83f840 0 1 Builtins_ArgumentsAdaptorTrampoline
+ 83f940 83f940 0 1 Builtins_CallFunction_ReceiverIsNullOrUndefined
+ 83fa40 83fa40 0 1 Builtins_CallFunction_ReceiverIsNotNullOrUndefined
+ 83fb80 83fb80 0 1 Builtins_CallFunction_ReceiverIsAny
+ 83fd00 83fd00 0 1 Builtins_CallBoundFunction
+# ... (truncated).
+
+ 977520 977520 18 16 obj/third_party/blink/renderer/platform/heap/asm/asm/SaveRegisters_arm.o:(.text)
+ 977520 977520 0 1 $a.0
+ 977520 977520 0 1 PushAllRegisters
+
+# Symbols with Size = 0 from assembly. Many Level 3 items are labels.
+ 97c834 97c834 4dc 4 obj/third_party/libvpx/libvpx_assembly_arm.a(libvpx_assembly_arm/vpx_convolve8_avg_horiz_filter_type1_neon.asm.o):(.text)
+ 97c834 97c834 0 1 $a.0
+ 97c834 97c834 0 1 _vpx_convolve8_avg_horiz_filter_type1_neon
+# This is a function.
+ 97c834 97c834 4dc 1 vpx_convolve8_avg_horiz_filter_type1_neon
+# These are labels.
+ 97c848 97c848 0 1 start_loop_count
+ 97c8d0 97c8d0 0 1 outer_loop8_residual
+ 97c8f0 97c8f0 0 1 outer_loop_8
+ 97c900 97c900 0 1 inner_loop_8
+ 97c9c8 97c9c8 0 1 end_inner_loop_8
+ 97c9e4 97c9e4 0 1 end_loops
+ 97c9e8 97c9e8 0 1 outer_loop_16
+ 97ca54 97ca54 0 1 inner_loop_16
+ 97cbcc 97cbcc 0 1 epilog_16
+ 97cc08 97cc08 0 1 end_loops1
+ 97cc0c 97cc0c 0 1 outer_loop4_residual
+ 97cc2c 97cc2c 0 1 outer_loop_4
+ 97cc3c 97cc3c 0 1 inner_loop_4
+ 97ccf8 97ccf8 0 1 end_inner_loop_4
+ 97cd08 97cd08 0 1 end_func
+
+# Thumb2 symbols (indicated by odd Level 3 addresses) with aliases.
+ 99cad8 99cad8 fc 8 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libandroid_support.a(e_sinh.o):(.text.sinh)
+ 99cad8 99cad8 0 1 $t
+ 99cad9 99cad9 fc 1 sinhl
+ 99cad9 99cad9 fc 1 sinh
+ 99cbb8 99cbb8 0 1 $d
+
+# On rare occasions, some symbol name start with '$'.
+ 1401788 1401788 a4 4 thinlto-cache/Thin-826b9f.tmp.o:(.text._ZNK4$_21clEP10FamilyDataPKcPS3_)
+ 1401788 1401788 0 1 $t.78
+ 1401789 1401789 a4 1 $_21::operator()(FamilyData*, char const*, char const**) const
+ 1401818 1401818 0 1 $d.79
+
+# Sections prefixed with ".unlikely" or ".hot"
+ 1401820 1401820 10 4 thinlto-cache/Thin-826b9f.tmp.o:(.text.unlikely.UnlikelyFunc)
+ 1401820 1401820 0 1 $t.78
+ 1401821 1401821 10 1 UnlikelyFunc
+ 1401831 1401831 10 1 UnlikelyFunc2
+ 1401841 1401841 0 1 $d.79
+
+# Sections prefixed with ".startup"
+ 1401850 1401850 10 4 thinlto-cache/Thin-826b9f.tmp.o:(.text.startup)
+ 1401850 1401850 0 1 $t.78
+ 1401851 1401851 10 1 StartUpFunc
+ 1401861 1401861 0 1 $d.79
+
+# Size-only sections.
+ 2d4a7f0 2d4a7f0 17d0 16 .plt
+ 2d4a7f0 2d4a7f0 17d0 16 <internal>:(.plt)
+
+# .data: Extract symbols and size.
+ 2d4c000 2d4c000 1cfc8 8 .data
+ 2d4c000 2d4c000 4 1 obj/v8/v8_external_snapshot/embedded.o:(.data)
+ 2d4c000 2d4c000 0 1 v8_Default_embedded_blob_
+ 2d4c004 2d4c004 4 4 ../../third_party/android_ndk/platforms/android-16/arch-arm/usr/lib/crtbegin_so.o:(.data)
+ 2d4c004 2d4c004 0 1 $d
+ 2d4c004 2d4c004 4 1 __dso_handle
+
+# .data.rel.ro: Extract symbols and size.
+ 2d69000 2d69000 1eb760 16 .data.rel.ro
+ 2d69000 2d69000 3c 4 obj/third_party/ffmpeg/libffmpeg_internal.a(ffmpeg_internal/fft_vfp.o):(.data.rel.ro)
+ 2d69000 2d69000 3c 1 fft_tab_vfp
+ 2d6903c 2d6903c 3c 4 obj/third_party/ffmpeg/libffmpeg_internal.a(ffmpeg_internal/fft_neon.o):(.data.rel.ro)
+ 2d6903c 2d6903c 3c 1 fft_tab_neon
+ 2d69078 2d69078 40 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(ios.o):(.data.rel.ro._ZTVNSt6__ndk115basic_streambufIwNS_11char_traitsIwEEEE)
+ 2d69078 2d69078 0 1 $d
+ 2d69078 2d69078 40 1 vtable for std::__ndk1::basic_streambuf<wchar_t, std::__ndk1::char_traits<wchar_t> >
+ 2d690b8 2d690b8 28 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(ios.o):(.data.rel.ro._ZTVNSt6__ndk113basic_istreamIcNS_11char_traitsIcEEEE)
+ 2d690b8 2d690b8 0 1 $d
+ 2d690b8 2d690b8 28 1 vtable for std::__ndk1::basic_istream<char, std::__ndk1::char_traits<char> >
+ 2d690e0 2d690e0 28 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(ios.o):(.data.rel.ro._ZTVNSt6__ndk113basic_istreamIwNS_11char_traitsIwEEEE)
+ 2d690e0 2d690e0 0 1 $d
+ 2d690e0 2d690e0 28 1 vtable for std::__ndk1::basic_istream<wchar_t, std::__ndk1::char_traits<wchar_t> >
+ 2d69108 2d69108 28 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(ios.o):(.data.rel.ro._ZTVNSt6__ndk113basic_ostreamIcNS_11char_traitsIcEEEE)
+ 2d69108 2d69108 0 1 $d
+ 2d69108 2d69108 28 1 vtable for std::__ndk1::basic_ostream<char, std::__ndk1::char_traits<char> >
+ 2d69130 2d69130 28 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(ios.o):(.data.rel.ro._ZTVNSt6__ndk113basic_ostreamIwNS_11char_traitsIwEEEE)
+ 2d69130 2d69130 0 1 $d
+ 2d69130 2d69130 28 1 vtable for std::__ndk1::basic_ostream<wchar_t, std::__ndk1::char_traits<wchar_t> >
+ 2d69158 2d69158 68 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(ios.o):(.data.rel.ro)
+ 2d69158 2d69158 0 1 $d
+ 2d69158 2d69158 24 1 vtable for std::__ndk1::__iostream_category
+ 2d6917c 2d6917c 14 1 vtable for std::__ndk1::ios_base::failure
+ 2d69190 2d69190 10 1 vtable for std::__ndk1::ios_base
+ 2d691a0 2d691a0 c 1 typeinfo for std::__ndk1::ios_base::failure
+ 2d691ac 2d691ac 8 1 typeinfo for std::__ndk1::ios_base
+ 2d691b4 2d691b4 c 1 typeinfo for std::__ndk1::__iostream_category
+
+# Size-only sections.
+ 2f54760 2f54760 8 4 .fini_array
+ 2f54760 2f54760 4 4 ../../third_party/android_ndk/platforms/android-16/arch-arm/usr/lib/crtbegin_so.o:(.fini_array)
+ 2f54760 2f54760 0 1 $d
+ 2f54764 2f54764 4 1 ../../third_party/android_ndk/platforms/android-16/arch-arm/usr/lib/crtend_so.o:(.fini_array)
+ 2f54768 2f54768 10 4 .init_array
+ 2f54768 2f54768 4 4 ../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(iostream.o):(.init_array)
+ 2f54768 2f54768 0 1 $d
+ 2f5476c 2f5476c 4 4 ../../third_party/gvr-android-sdk/libgvr_shim_static_arm.a(base_logging.o):(.init_array)
+ 2f54770 2f54770 4 1 ../../third_party/android_ndk/platforms/android-16/arch-arm/usr/lib/crtend_so.o:(.init_array)
+ 2f54774 2f54774 4 4 thinlto-cache/Thin-b0997c.tmp.o:(.init_array)
+ 2f54778 2f54778 f0 4 .dynamic
+ 2f54778 2f54778 f0 4 <internal>:(.dynamic)
+ 2f54868 2f54868 5ac 4 .got
+ 2f54868 2f54868 5ac 4 <internal>:(.got)
+ 2f54e14 2f54e14 5f8 4 .got.plt
+ 2f54e14 2f54e14 5f8 4 <internal>:(.got.plt)
+
+# .bss: Extract symbols and size (does not consume size on disk).
+ 2f56000 2f56000 109f7c 32 .bss
+ 2f56000 2f56000 4 4 thinlto-cache/Thin-cfd493.tmp.o:(COMMON)
+ 2f56000 2f56000 4 1 WebRtcSpl_CrossCorrelation
+ 2f56000 2f56000 4 1 WebRtcSpl_CrossCorrelation
+ 2f56000 2f56000 4 1 WebRtcSpl_CrossCorrelation
+ 2f56004 2f56004 4 4 thinlto-cache/Thin-cfd493.tmp.o:(COMMON)
+ 2f56004 2f56004 4 1 WebRtcSpl_DownsampleFast
+ 2f56004 2f56004 4 1 WebRtcSpl_DownsampleFast
+ 2f56004 2f56004 4 1 WebRtcSpl_DownsampleFast
+ 2f56004 2f56004 4 1 WebRtcSpl_DownsampleFast
+ 2f56008 2f56008 4 4 thinlto-cache/Thin-cfd493.tmp.o:(COMMON)
+ 2f56008 2f56008 4 1 WebRtcSpl_MaxAbsValueW16
+ 2f56008 2f56008 4 1 WebRtcSpl_MaxAbsValueW16
+ 2f56008 2f56008 4 1 WebRtcSpl_MaxAbsValueW16
+ 2f56008 2f56008 4 1 WebRtcSpl_MaxAbsValueW16
+ 2f56008 2f56008 4 1 WebRtcSpl_MaxAbsValueW16
+ 2f56008 2f56008 4 1 WebRtcSpl_MaxAbsValueW16
+ 2f56008 2f56008 4 1 WebRtcSpl_MaxAbsValueW16
+ 2f56008 2f56008 4 1 WebRtcSpl_MaxAbsValueW16
+ 2f56008 2f56008 4 1 WebRtcSpl_MaxAbsValueW16
+ 2f56008 2f56008 4 1 WebRtcSpl_MaxAbsValueW16
+ 2f56008 2f56008 4 1 WebRtcSpl_MaxAbsValueW16
+
+# Partitions should be ignored at this point. Otherwise, their .text, .rodata,
+# etc. sections will overwrite those of the main partition.
+ 34be000 34be000 34 1 vr_partition
+ 34be000 34be000 34 1 <internal>:(vr_partition)
+ 34be034 34be034 140 1 .phdrs
+ 34be034 34be034 140 1 <internal>:(.phdrs)
+ 34be174 34be174 13 1 .interp
+ 34be174 34be174 13 1 <internal>:(.interp)
+ 34be188 34be188 1c 4 .note.crashpad.info
+ 34be188 34be188 1c 4 obj/third_party/crashpad/crashpad/client/libclient.a(client/crashpad_info_note.o):(.note.crashpad.info)
+ 34be1a4 34be1a4 98 4 .note.android.ident
+ 34be1a4 34be1a4 98 4 ../../third_party/android_ndk/platforms/android-16/arch-arm/usr/lib/crtbegin_so.o:(.note.android.ident)
+ 34be23c 34be23c 24 4 .note.gnu.build-id
+ 34be23c 34be23c 24 4 <internal>:(.note.gnu.build-id)
+ 34be260 34be260 20 4 .dynsym
+ 34be260 34be260 20 4 <internal>:(.dynsym)
+ 34be280 34be280 4 2 .gnu.version
+ 34be280 34be280 4 2 <internal>:(.gnu.version)
+ 34be284 34be284 60 4 .gnu.version_r
+ 34be284 34be284 60 4 <internal>:(.gnu.version_r)
+ 34be2e4 34be2e4 1c 4 .gnu.hash
+ 34be2e4 34be2e4 1c 4 <internal>:(.gnu.hash)
+ 34be300 34be300 6c 1 .dynstr
+ 34be300 34be300 6c 1 <internal>:(.dynstr)
+ 34be36c 34be36c 257 4 .rel.dyn
+ 34be36c 34be36c 257 4 <internal>:(.rel.dyn)
+ 34be5c4 34be5c4 10 4 .ARM.exidx
+ 34be5c4 34be5c4 10 4 <internal>:(.ARM.exidx)
+ 34be5d8 34be5d8 2428 8 .rodata
+ 34be5d8 34be5d8 10 1 thinlto-cache/Thin-5e976b.tmp.o:(.rodata._ZN2vrL17kRepositionIconIdE)
+ 34be5d8 34be5d8 10 1 vr::kRepositionIconId
+ 34c0a00 34c0a00 2c9ec 16 .text
+ 34c0a00 34c0a00 18 4 thinlto-cache/Thin-ec84a4.tmp.o:(.text._ZN6SkFont7setSizeEf)
+ 34c0a00 34c0a00 0 1 $t.11
+ 34c0a01 34c0a01 18 1 SkFont::setSize(float)
+ 34c0a14 34c0a14 0 1 $d.12
+# .part.end is unique, even if multiple lib*.so, .phdrs, etc. exist.
+ 0 0 1000 1 .part.end
+ 0 0 1000 1 <internal>:(.part.end)
+
+# Various .debug sections can exist, but they're omitted for simplicity.
+
+# Size-only sections.
+ 0 0 23 1 .ARM.attributes
+ 0 0 23 1 obj/third_party/boringssl/boringssl_asm/chacha-armv4.o:(.ARM.attributes)
+ 0 0 a8 1 .comment
+ 0 0 a8 1 <internal>:(.comment)
+ 0 0 11c3610 4 .symtab
+ 0 0 11c3610 4 <internal>:(.symtab)
+ 0 0 1c4 1 .shstrtab
+ 0 0 1c4 1 <internal>:(.shstrtab)
+ 0 0 2f73332 1 .strtab
+ 0 0 2f73332 1 <internal>:(.strtab)
+
+# PROVIDE_HIDDEN are not Level 1 symbols.
+ 2f73332 0 0 1 PROVIDE_HIDDEN ( linker_script_start_of_text = ADDR ( .text ) )
+ 2f73332 0 0 1 PROVIDE_HIDDEN ( linker_script_end_of_text = ADDR ( .text ) + SIZEOF ( .text ) )
diff --git a/third_party/demumble b/third_party/demumble
index 01098ea..0cc7805 160000
--- a/third_party/demumble
+++ b/third_party/demumble
@@ -1 +1 @@
-Subproject commit 01098eab821b33bd31b9778aea38565cd796aa85
+Subproject commit 0cc780526909546f1e41bef501e248e1cdbf2a9b
diff --git a/third_party/googletest b/third_party/googletest
index 565f1b8..dcc92d0 160000
--- a/third_party/googletest
+++ b/third_party/googletest
@@ -1 +1 @@
-Subproject commit 565f1b848215b77c3732bca345fe76a0431d8b34
+Subproject commit dcc92d0ab6c4ce022162a23566d44f673251eee4
diff --git a/third_party/protobuf b/third_party/protobuf
index bc1773c..31ebe2a 160000
--- a/third_party/protobuf
+++ b/third_party/protobuf
@@ -1 +1 @@
-Subproject commit bc1773c42c9c3c522145a3119e989e0dff2a8d54
+Subproject commit 31ebe2ac71400344a5db91ffc13c4ddfb7589f92
diff --git a/third_party/rustc-demangle b/third_party/rustc-demangle
new file mode 160000
index 0000000..c4e3ab0
--- /dev/null
+++ b/third_party/rustc-demangle
@@ -0,0 +1 @@
+Subproject commit c4e3ab004edda9fbe5e2d8149d2b62c10668d10b