34 #include <unordered_map>
43 #if !defined(_MSC_VER)
51 #include <sys/types.h>
53 #include "absl/debugging/internal/demangle.h"
54 #include "absl/memory/memory.h"
55 #include "absl/strings/numbers.h"
56 #include "absl/strings/string_view.h"
57 #include "absl/strings/str_join.h"
58 #include "absl/strings/substitute.h"
59 #include "google/protobuf/io/zero_copy_stream_impl.h"
60 #include "google/protobuf/text_format.h"
63 #include "bloaty.pb.h"
86 "source file for the .o file (translation unit). requires debug info."},
88 "the filename specified on the Bloaty command-line"},
90 "source line/file where inlined code came from. requires debug info."},
95 "symbols from symbol table (configure demangling with --demangle)"},
101 #define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
109 fprintf(
stderr,
"Unknown data source label: %d\n",
static_cast<int>(source));
117 }
else if (val > 0) {
125 #if ABSL_HAVE_BUILTIN(__builtin_add_overflow)
126 if (__builtin_add_overflow(*accum, val, accum)) {
127 THROW(
"integer overflow");
130 bool safe = *accum < 0
134 THROW(
"integer overflow");
141 bool need_escape =
false;
143 for (
char ch :
str) {
144 if (
ch ==
'"' ||
ch ==
',') {
152 for (
char ch :
str) {
182 char demangled[1024];
184 sizeof(demangled))) {
200 printf(
"Unexpected source: %d\n", (
int)source);
209 auto reg = absl::make_unique<ReImpl>(regex);
271 output->diff_mode_ =
false;
283 output->diff_mode_ =
true;
296 for (
const auto& other_child : other.
children_) {
298 if (
child.get() == NULL) {
301 child->Subtract(*other_child.second);
310 for (
const auto& other_child : other.
children_) {
312 if (
child.get() == NULL) {
315 child->Add(*other_child.second);
332 typedef std::unordered_map<std::string, std::unique_ptr<Rollup>>
ChildMap;
351 bool any_matched =
false;
379 if (
child.get() ==
nullptr) {
390 }
else if (part > 0) {
396 return static_cast<double>(part) /
static_cast<double>(whole) * 100;
401 bool is_toplevel)
const;
403 const Options&
options,
bool is_toplevel)
const;
407 const Options&
options,
bool is_toplevel)
const {
416 if (
value.second->vm_total_ != 0 ||
value.second->file_total_ != 0) {
431 bool is_toplevel)
const {
436 if (!is_toplevel && child_rows.size() == 1 &&
437 (child_rows[0].name ==
"[None]" || child_rows[0].name ==
"[Unmapped]")) {
443 if (child_rows.size() == 1 && child_rows[0].name == row->
name) {
447 if (child_rows.empty()) {
452 for (
auto&
child : child_rows) {
454 case Options::SORTBY_VMSIZE:
457 case Options::SORTBY_FILESIZE:
460 case Options::SORTBY_BOTH:
479 size_t i = child_rows.size() - 1;
480 while (
i >=
options.max_rows_per_level()) {
484 auto it =
base->children_.find(child_rows[
i].
name);
485 if (
it !=
base->children_.end()) {
491 child_rows.erase(child_rows.end() - 1);
495 if (std::abs(others_row.
vmsize) > 0 || std::abs(others_row.
filesize) > 0) {
496 child_rows.push_back(others_row);
502 for (
auto&
child : child_rows) {
504 case Options::SORTBY_VMSIZE:
507 case Options::SORTBY_FILESIZE:
510 case Options::SORTBY_BOTH:
511 if (std::abs(
child.vmsize) > std::abs(
child.filesize)) {
526 for (
auto& child_row : child_rows) {
527 child_row.vmpercent =
Percent(child_row.vmsize, row->
vmsize);
533 for (
auto& child_row : child_rows) {
534 const Rollup* child_rollup;
535 const Rollup* child_base =
nullptr;
537 if (child_row.other_count > 0) {
538 child_rollup = &others_rollup;
540 child_base = &others_base;
545 THROWF(
"internal error, couldn't find name $0", child_row.name);
547 child_rollup =
it->second.get();
548 assert(child_rollup);
551 auto it =
base->children_.find(child_row.name);
552 if (
it ==
base->children_.end()) {
555 child_base =
it->second.get();
590 bool ShowVM(
const OutputOptions&
options) {
610 const char *prefixes[] = {
"",
"Ki",
"Mi",
"Gi",
"Ti"};
611 size_t num_prefixes = 5;
613 double size_d =
size;
614 while (fabs(size_d) > 1024 && n < num_prefixes - 2) {
621 if (fabs(size_d) > 100 || n == 0) {
623 if (force_sign &&
size > 0) {
626 }
else if (fabs(size_d) > 10) {
628 ret = DoubleStringPrintf(
"%+0.1f", size_d) + prefixes[
n];
630 ret = DoubleStringPrintf(
"%0.1f", size_d) + prefixes[
n];
634 ret = DoubleStringPrintf(
"%+0.2f", size_d) + prefixes[
n];
636 ret = DoubleStringPrintf(
"%0.2f", size_d) + prefixes[
n];
640 return LeftPad(
ret, 7);
643 std::string PercentString(
double percent,
bool diff_mode) {
647 }
else if (percent == -100) {
654 if (percent > 1000) {
655 int digits = log10(percent) - 1;
656 str = DoubleStringPrintf(
"%+2.0f", percent / pow(10, digits)) +
"e" +
658 }
else if (percent > 10) {
659 str = DoubleStringPrintf(
"%+4.0f%%", percent);
661 str = DoubleStringPrintf(
"%+5.1F%%", percent);
664 return LeftPad(
str, 6);
667 return DoubleStringPrintf(
"%5.1F%%", percent);
675 switch (
options.output_format) {
697 std::ostream*
out)
const {
707 *
out << FixedWidthString(
"",
indent) <<
" ";
719 *
out <<
" " << row.
name <<
"\n";
736 std::ostream*
out)
const {
756 std::ostream*
out)
const {
758 *
out <<
" FILE SIZE ";
768 *
out <<
" -------------- ";
772 *
out <<
" -------------- ";
793 if (vm_filtered == 0 && file_filtered == 0) {
797 *
out <<
"Filtering enabled (source_filter); omitted";
799 if (file_filtered > 0 && vm_filtered > 0) {
800 *
out <<
" file =" << SiPrint(file_filtered,
false)
801 <<
", vm =" << SiPrint(vm_filtered,
false);
802 }
else if (file_filtered > 0) {
803 *
out << SiPrint(file_filtered,
false);
805 *
out << SiPrint(vm_filtered,
false);
808 *
out <<
" of entries\n";
812 std::vector<std::string> parent_labels,
813 std::ostream*
out,
bool tabs)
const {
816 parent_labels.push_back(
"");
827 std::vector<std::string> parent_labels,
828 std::ostream*
out,
bool tabs)
const {
830 parent_labels.push_back(row.
name);
846 names.push_back(
"vmsize");
847 names.push_back(
"filesize");
862 #if !defined(_MSC_VER)
878 fprintf(
stderr,
"bloaty: error calling close(): %s\n", strerror(errno));
898 if (fstat(fd.
fd(), &
buf) < 0) {
902 map =
static_cast<char*
>(
903 mmap(
nullptr,
buf.st_size, PROT_READ, MAP_SHARED, fd.
fd(), 0));
905 if (
map == MAP_FAILED) {
915 fprintf(
stderr,
"bloaty: error calling munmap(): %s\n", strerror(errno));
921 return absl::make_unique<MmapInputFile>(
filename);
928 class Win32MMapInputFile :
public InputFile {
931 Win32MMapInputFile(
const Win32MMapInputFile&) =
delete;
932 Win32MMapInputFile& operator=(
const Win32MMapInputFile&) =
delete;
933 ~Win32MMapInputFile()
override;
938 Win32Handle(HANDLE h) : h_(h) {}
942 fprintf(
stderr,
"bloaty: error calling CloseHandle(): %d\n",
947 HANDLE
h() {
return h_; }
955 Win32Handle fd(::CreateFileA(
filename.c_str(), FILE_GENERIC_READ,
956 FILE_SHARE_READ, NULL, OPEN_EXISTING,
957 FILE_ATTRIBUTE_NORMAL, NULL));
958 LARGE_INTEGER li = {};
962 THROWF(
"couldn't open file '$0': $1",
filename, ::GetLastError());
966 THROWF(
"couldn't stat file '$0': $1",
filename, ::GetLastError());
970 ::CreateFileMappingA(fd.h(), NULL, PAGE_READONLY, 0, 0,
nullptr));
972 THROWF(
"couldn't create file mapping '$0': $1",
filename, ::GetLastError());
975 map =
static_cast<char*
>(::MapViewOfFile(mapfd.h(), FILE_MAP_READ, 0, 0, 0));
977 THROWF(
"couldn't MapViewOfFile file '$0': $1",
filename, ::GetLastError());
983 Win32MMapInputFile::~Win32MMapInputFile() {
984 if (
data_.data() !=
nullptr && !::UnmapViewOfFile(
data_.data())) {
985 fprintf(
stderr,
"bloaty: error calling UnmapViewOfFile(): %d\n",
992 return absl::make_unique<Win32MMapInputFile>(
filename);
1011 return options_.verbose_level() > 1 ||
1013 options_.debug_vmaddr() < (vmaddr + vmsize));
1017 return options_.verbose_level() > 1 ||
1019 options_.debug_fileoff() < (fileoff + filesize));
1027 if (vmaddr + vmsize < vmaddr) {
1028 THROWF(
"Overflow in vm range, vmaddr=$0, vmsize=$1", vmaddr, vmsize);
1058 if (fileoff + filesize < fileoff) {
1059 THROWF(
"Overflow in file range, fileoff=$0, filesize=$1", fileoff,
1092 printf(
"[%s, %s] AddFileRange(%.*s, %" PRIx64
", %" PRIx64
")\n",
1094 name.data(), fileoff, filesize);
1099 bool ok =
pair.first->file_map.AddRangeWithTranslation(
1101 &
pair.first->vm_map);
1103 WARN(
"File range ($0, $1) for label $2 extends beyond base map",
1104 fileoff, filesize,
name);
1107 pair.first->file_map.AddRange(fileoff, filesize,
label);
1118 printf(
"[%s, %s] AddFileRangeForVMAddr(%" PRIx64
", [%" PRIx64
", %zx])\n",
1120 file_offset, file_range.
size());
1125 if (
pair.first->vm_map.TryGetLabel(label_from_vmaddr, &
label)) {
1126 bool ok =
pair.first->file_map.AddRangeWithTranslation(
1128 &
pair.first->vm_map);
1130 WARN(
"File range ($0, $1) for label $2 extends beyond base map",
1134 printf(
"No label found for vmaddr %" PRIx64
"\n", label_from_vmaddr);
1146 printf(
"[%s, %s] AddFileRangeForFileRange([%" PRIx64
", %zx], [%" PRIx64
1149 from_file_range.
size(), file_offset, file_range.
size());
1154 if (
pair.first->file_map.TryGetLabelForRange(
1155 from_file_offset, from_file_range.
size(), &
label)) {
1156 bool ok =
pair.first->file_map.AddRangeWithTranslation(
1158 &
pair.first->vm_map);
1160 WARN(
"File range ($0, $1) for label $2 extends beyond base map",
1164 printf(
"No label found for file range [%" PRIx64
", %zx]\n",
1165 from_file_offset, from_file_range.
size());
1175 printf(
"[%s, %s] AddVMRangeForVMAddr(%" PRIx64
", [%" PRIx64
", %" PRIx64
1183 if (
pair.first->vm_map.TryGetLabel(label_from_vmaddr, &
label)) {
1184 bool ok =
pair.first->vm_map.AddRangeWithTranslation(
1186 &
pair.first->file_map);
1188 WARN(
"VM range ($0, $1) for label $2 extends beyond base map",
addr,
1192 printf(
"No label found for vmaddr %" PRIx64
"\n", label_from_vmaddr);
1201 printf(
"[%s, %s] AddVMRange(%.*s, %" PRIx64
", %" PRIx64
")\n",
1203 name.data(), vmaddr, vmsize);
1208 bool ok =
pair.first->vm_map.AddRangeWithTranslation(
1210 &
pair.first->file_map);
1212 WARN(
"VM range ($0, $1) for label $2 extends beyond base map", vmaddr,
1239 THROW(
"AddRange() does not allow unknown size.");
1244 printf(
"[%s, %s] AddRange(%.*s, %" PRIx64
", %" PRIx64
", %" PRIx64
1247 name.data(), vmaddr, vmsize, fileoff, filesize);
1253 THROW(
"Tried to add range that is not covered by base map.");
1275 THROWF(
"Can't translate file offset ($0) to VM, contains: $1, map:\n$2",
1287 THROW(
"Can't translate VM pointer to file");
1295 THROW(
"This range sink isn't prepared to zlib decompress.");
1299 if (uncompressed_size >
static_cast<uint64_t>(
data.size()) * 30 + (128 * mb)) {
1301 "warning: ignoring compressed debug data, implausible uncompressed "
1302 "size (compressed: %zu, uncompressed: %" PRIu64
")\n",
1303 data.size(), uncompressed_size);
1306 unsigned char *dbuf =
1307 arena_->google::protobuf::Arena::CreateArray<
unsigned char>(
1308 arena_, uncompressed_size);
1309 uLongf zliblen = uncompressed_size;
1311 THROW(
"Error decompressing debug info");
1313 string_view sv(
reinterpret_cast<char *
>(dbuf), zliblen);
1324 int ret =
index_.fetch_add(1, std::memory_order_relaxed);
1334 std::lock_guard<std::mutex> lock(
mutex_);
1340 std::lock_guard<std::mutex> lock(
mutex_);
1396 for (
size_t i = 0;
i <
T;
i++) {
1398 auto configured_source = absl::make_unique<ConfiguredDataSource>(source);
1410 case Options::DEMANGLE_NONE:
1412 case Options::DEMANGLE_SHORT:
1414 case Options::DEMANGLE_FULL:
1422 std::vector<std::string>* build_ids,
1425 std::vector<std::string>* out_build_ids)
const;
1434 std::map<std::string, std::unique_ptr<ConfiguredDataSource>>
1451 std::unique_ptr<google::protobuf::Arena>
arena_;
1465 if (!object_file.get()) {
1469 if (!object_file.get()) {
1473 if (!object_file.get()) {
1477 if (!object_file.get()) {
1498 if (build_id.size() == 0) {
1499 THROWF(
"File '$0' has no build ID, cannot be used as a debug file",
1506 if (source.base_data_source() ==
"symbols") {
1508 "For custom data sources, use one of {rawsymbols, shortsymbols, "
1509 "fullsymbols} for base_data_source instead of 'symbols', so you aren't "
1510 "sensitive to the --demangle parameter.");
1516 THROWF(
"custom data source '$0': no such base source '$1'.\nTry --list-sources to see valid sources.", source.name(),
1517 source.base_data_source());
1518 }
else if (!
iter->second->munger->IsEmpty()) {
1519 THROWF(
"custom data source '$0' tries to depend on custom data source '$1'",
1520 source.name(), source.base_data_source());
1524 absl::make_unique<ConfiguredDataSource>(
iter->second->definition);
1526 for (
const auto& regex : source.rewrite()) {
1527 munger->
AddRegex(regex.pattern(), regex.replacement());
1535 THROWF(
"no such data source: $0.\nTry --list-sources to see valid sources.",
name);
1551 return maps_.back().get();
1556 map->vm_map.Compress();
1557 map->file_map.Compress();
1573 int hex_digits =
max > 0 ? std::ceil(std::log2(
max) / 4) : 0;
1592 for (
size_t i = 1;
i <
keys.size();
i++) {
1603 printf(
"%.*" PRIx64
"-%.*" PRIx64
"\t %s\t\t%.*s\n", hex_digits,
start,
1605 (
int)
str.size(),
str.data());
1611 std::vector<const RangeMap*>
VmMaps()
const {
1612 std::vector<const RangeMap*>
ret;
1614 ret.push_back(&
map->vm_map);
1620 std::vector<const RangeMap*>
ret;
1622 ret.push_back(&
map->file_map);
1627 std::vector<std::unique_ptr<DualMap>>
maps_;
1631 std::vector<std::string>* out_build_ids)
const {
1635 std::vector<std::unique_ptr<RangeSink>> sinks;
1636 std::vector<RangeSink*> sink_ptrs;
1637 std::vector<RangeSink*> filename_sink_ptrs;
1640 sinks.push_back(absl::make_unique<RangeSink>(
1643 sinks.back()->AddOutput(maps.
base_map(), &empty_munger);
1644 sink_ptrs.push_back(sinks.back().get());
1647 sinks.push_back(absl::make_unique<RangeSink>(&
file->file_data(),
options_,
1648 source->effective_source,
1650 sinks.back()->AddOutput(maps.
AppendMap(), source->munger.get());
1655 filename_sink_ptrs.push_back(sinks.back().get());
1657 sink_ptrs.push_back(sinks.back().get());
1661 std::unique_ptr<ObjectFile> debug_file;
1663 if (!build_id.empty()) {
1667 file->set_debug_file(debug_file.get());
1668 out_build_ids->push_back(build_id);
1674 file->ProcessFile(sink_ptrs);
1677 for (
auto sink : filename_sink_ptrs) {
1681 sink->input_file().filename());
1685 sink->AddFileRange(
"inputfile_filecopier",
1696 assert(filesize ==
file->file_data().data().
size());
1712 const std::vector<std::string>& filenames,
1713 std::vector<std::string>* build_ids,
1715 int num_cpus = std::thread::hardware_concurrency();
1718 struct PerThreadData {
1720 std::vector<std::string> build_ids;
1723 std::vector<PerThreadData> thread_data(
num_threads);
1727 std::unique_ptr<ReImpl> regex =
nullptr;
1728 if (
options_.has_source_filter()) {
1729 regex = absl::make_unique<ReImpl>(
options_.source_filter());
1733 thread_data[
i].rollup.SetFilterRegex(regex.get());
1738 while (
index.TryGetNext(&j)) {
1739 ScanAndRollupFile(filenames[j], &data->rollup, &data->build_ids);
1742 index.Abort(e.what());
1744 }, &thread_data[
i]);
1749 PerThreadData*
data = &thread_data[
i];
1756 build_ids->insert(build_ids->end(),
1757 data->build_ids.begin(),
1758 data->build_ids.end());
1769 THROW(
"no filename specified");
1777 std::vector<std::string> build_ids;
1778 std::vector<std::string> input_filenames;
1780 input_filenames.push_back(file_info.filename_);
1786 std::vector<std::string> base_filenames;
1788 base_filenames.push_back(file_info.filename_);
1797 for (
const auto& build_id : build_ids) {
1809 pair.second.c_str());
1815 file_info.filename_.c_str());
1820 file_info.filename_.c_str());
1823 "Debug file(s) did not match any input file:\n$0\nInput Files:\n$1",
1824 unused_debug.c_str(), input_files.c_str());
1840 THROWF(
"Couldn't find function $0 to disassemble",
function);
1843 const char usage[] = R
"(Bloaty McBloatface: a size profiler for binaries.
1845 USAGE: bloaty [OPTION]... FILE... [-- BASE_FILE...]
1849 --csv Output in CSV format instead of human-readable.
1850 --tsv Output in TSV format instead of human-readable.
1851 -c FILE Load configuration from <file>.
1852 -d SOURCE,SOURCE Comma-separated list of sources to scan.
1853 --debug-file=FILE Use this file for debug symbols and/or symbol table.
1854 -C MODE How to demangle symbols. Possible values are:
1855 --demangle=MODE --demangle=none no demangling, print raw symbols
1856 --demangle=short demangle, but omit arg/return types
1857 --demangle=full print full demangled type
1858 The default is --demangle=short.
1859 --disassemble=FUNCTION
1860 Disassemble this function (EXPERIMENTAL)
1861 --domain=DOMAIN Which domains to show. Possible values are:
1864 --domain=both (the default)
1865 -n NUM How many rows to show per level before collapsing
1866 other keys into '[Other]'. Set to '0' for unlimited.
1868 -s SORTBY Whether to sort by VM or File size. Possible values
1872 -s both (the default: sorts by max(vm, file)).
1873 -w Wide output; don't truncate long labels.
1874 --help Display this message and exit.
1875 --list-sources Show a list of available sources and exit.
1876 --source-filter=PATTERN
1877 Only show keys with names matching this pattern.
1879 Options for debugging Bloaty:
1883 Print extended debugging information for the given
1884 VM address and/or file offset.
1885 -v Verbose output. Dumps warnings encountered during
1886 processing and full VM/file maps at the end.
1887 Add more v's (-vv, -vvv) for even more.
1894 argv_(*argv, *argv + *argc),
1936 assert(
flag.size() > 1);
1937 bool is_long =
flag[1] ==
'-';
1941 THROWF(
"option '$0' requires an argument",
flag);
1961 THROWF(
"option '$0' had non-integral argument: $1",
flag, val_str);
1974 *val = std::stoull(
std::string(val_str),
nullptr, 0);
1976 THROWF(
"option '$0' had non-integral argument: $1",
flag, val_str);
1992 bool saw_separator =
false;
1997 bool has_domain =
false;
1999 while (!
args.IsDone()) {
2000 if (
args.TryParseFlag(
"--")) {
2001 if (saw_separator) {
2002 THROW(
"'--' option should only be specified once");
2004 saw_separator =
true;
2005 }
else if (
args.TryParseFlag(
"--csv")) {
2007 }
else if (
args.TryParseFlag(
"--tsv")) {
2009 }
else if (
args.TryParseFlag(
"--raw-map")) {
2010 options->set_dump_raw_map(
true);
2011 }
else if (
args.TryParseOption(
"-c", &
option)) {
2013 if (!input_file.is_open()) {
2018 THROWF(
"error parsing configuration out of file $0",
option);
2020 }
else if (
args.TryParseOption(
"-d", &
option)) {
2025 }
else if (
args.TryParseOption(
"-C", &
option) ||
2026 args.TryParseOption(
"--demangle", &
option)) {
2028 options->set_demangle(Options::DEMANGLE_NONE);
2029 }
else if (
option ==
"short") {
2030 options->set_demangle(Options::DEMANGLE_SHORT);
2031 }
else if (
option ==
"full") {
2032 options->set_demangle(Options::DEMANGLE_FULL);
2036 }
else if (
args.TryParseOption(
"--debug-file", &
option)) {
2038 }
else if (
args.TryParseUint64Option(
"--debug-fileoff", &uint64_option)) {
2039 if (
options->has_debug_fileoff()) {
2040 THROW(
"currently we only support a single debug fileoff");
2042 options->set_debug_fileoff(uint64_option);
2043 }
else if (
args.TryParseUint64Option(
"--debug-vmaddr", &uint64_option)) {
2044 if (
options->has_debug_vmaddr()) {
2045 THROW(
"currently we only support a single debug vmaddr");
2047 options->set_debug_vmaddr(uint64_option);
2048 }
else if (
args.TryParseOption(
"--disassemble", &
option)) {
2050 }
else if (
args.TryParseIntegerOption(
"-n", &int_option)) {
2051 if (int_option == 0) {
2054 options->set_max_rows_per_level(int_option);
2056 }
else if (
args.TryParseOption(
"--domain", &
option)) {
2060 }
else if (
option ==
"file") {
2062 }
else if (
option ==
"both") {
2067 }
else if (
args.TryParseOption(
"-s", &
option)) {
2069 options->set_sort_by(Options::SORTBY_VMSIZE);
2070 }
else if (
option ==
"file") {
2071 options->set_sort_by(Options::SORTBY_FILESIZE);
2072 }
else if (
option ==
"both") {
2073 options->set_sort_by(Options::SORTBY_BOTH);
2077 }
else if (
args.TryParseOption(
"--source-filter", &
option)) {
2079 }
else if (
args.TryParseFlag(
"-v")) {
2080 options->set_verbose_level(1);
2081 }
else if (
args.TryParseFlag(
"-vv")) {
2082 options->set_verbose_level(2);
2083 }
else if (
args.TryParseFlag(
"-vvv")) {
2084 options->set_verbose_level(3);
2085 }
else if (
args.TryParseFlag(
"-w")) {
2087 }
else if (
args.TryParseFlag(
"--list-sources")) {
2089 fprintf(
stderr,
"%s %s\n", FixedWidthString(source.name, 15).c_str(),
2090 source.description);
2093 }
else if (
args.TryParseFlag(
"--help")) {
2096 }
else if (
args.TryParseFlag(
"--version")) {
2097 printf(
"Bloaty McBloatface 1.1\n");
2101 args.ConsumeAndSaveArg();
2106 if (saw_separator) {
2114 if (
options->data_source_size() == 0 &&
2115 !
options->has_disassemble_function()) {
2117 options->add_data_source(
"sections");
2120 if (has_domain && !
options->has_sort_by()) {
2122 switch (output_options->
show) {
2124 options->set_sort_by(Options::SORTBY_FILESIZE);
2127 options->set_sort_by(Options::SORTBY_VMSIZE);
2130 options->set_sort_by(Options::SORTBY_BOTH);
2143 error->assign(e.what());
2152 if (
options.filename_size() == 0) {
2153 THROW(
"must specify at least one file");
2156 if (
options.max_rows_per_level() < 1) {
2157 THROW(
"max_rows_per_level must be at least 1");
2164 for (
auto& base_filename :
options.base_filename()) {
2165 bloaty.AddFilename(base_filename,
true);
2168 for (
auto& debug_filename :
options.debug_filename()) {
2169 bloaty.AddDebugFilename(debug_filename);
2172 for (
const auto& custom_data_source :
options.custom_data_source()) {
2173 bloaty.DefineCustomDataSource(custom_data_source);
2176 for (
const auto& data_source :
options.data_source()) {
2177 bloaty.AddDataSource(data_source);
2180 if (
options.has_source_filter()) {
2183 THROW(
"invalid regex for source_filter");
2189 if (
options.data_source_size() > 0) {
2191 }
else if (
options.has_disassemble_function()) {
2202 error->assign(e.what());