101
103
my $PROGRAM_NAME_PAGE = "/pprof/cmdline";
104
# There is a pervasive dependency on the length (in hex characters, i.e.,
105
# nibbles) of an address, distinguishing between 32-bit and 64-bit profiles:
106
my $address_length = 8; # Hope for 32-bit, reset if 64-bit detected.
106
# There is a pervasive dependency on the length (in hex characters,
107
# i.e., nibbles) of an address, distinguishing between 32-bit and
108
# 64-bit profiles. To err on the safe size, default to 64-bit here:
109
my $address_length = 16;
111
# A list of paths to search for shared object files
112
my @prefix_list = ();
108
114
##### Argument parsing #####
273
290
# Are we using $SYMBOL_PAGE?
274
291
$main::use_symbol_page = 0;
276
# Are we printing a heap profile?
277
$main::heap_profile = 0;
279
# Are we printing a lock profile?
280
$main::lock_profile = 0;
293
# Type of profile we are dealing with
299
$main::profile_type = ''; # Empty type means "unknown"
282
301
GetOptions("help!" => \$main::opt_help,
283
302
"version!" => \$main::opt_version,
284
303
"cum!" => \$main::opt_cum,
285
304
"base=s" => \$main::opt_base,
286
305
"seconds=i" => \$main::opt_seconds,
306
"add_lib=s" => \$main::opt_lib,
307
"lib_prefix=s" => \$main::opt_lib_prefix,
287
308
"functions!" => \$main::opt_functions,
288
309
"lines!" => \$main::opt_lines,
289
310
"addresses!" => \$main::opt_addresses,
290
311
"files!" => \$main::opt_files,
291
312
"text!" => \$main::opt_text,
313
"callgrind!" => \$main::opt_callgrind,
292
314
"list=s" => \$main::opt_list,
293
315
"disasm=s" => \$main::opt_disasm,
294
316
"gv!" => \$main::opt_gv,
443
479
# Read one profile, pick the last item on the list
444
480
my $data = ReadProfile($main::prog, pop(@main::profile_files));
445
481
my $profile = $data->{profile};
482
my $pcs = $data->{pcs};
446
483
my $libs = $data->{libs}; # Info about main program and shared libraries
448
# List of function names to skip
450
$main::skip_regexp = 'NOMATCH';
451
if ($main::heap_profile) {
452
foreach my $name ('calloc',
461
'DoSampledAllocation',
462
'simple_alloc::allocate',
463
'__malloc_alloc_template::allocate',
466
'__builtin_vec_delete',
467
'__builtin_vec_new') {
468
$main::skip{$name} = 1;
470
$main::skip_regexp = "TCMalloc";
472
if ($main::lock_profile) {
473
foreach my $vname ('Mutex::Unlock', 'Mutex::UnlockSlow') {
474
$main::skip{$vname} = 1;
478
485
# Add additional profiles, if available.
479
486
if (scalar(@main::profile_files) > 0) {
480
487
foreach my $pname (@main::profile_files) {
481
my $p = ReadProfile($main::prog, $pname)->{profile};
482
$profile = AddProfile($profile, $p);
488
my $data2 = ReadProfile($main::prog, $pname);
489
$profile = AddProfile($profile, $data2->{profile});
490
$pcs = AddPcs($pcs, $data2->{pcs});
495
503
# Collect symbols
496
504
my $symbols = undef;
497
505
if ($main::use_symbol_page) {
498
$symbols = FetchSymbols($data->{pcs});
506
$symbols = FetchSymbols($pcs);
500
$symbols = ExtractSymbols($libs, $profile, $data->{pcs});
508
$symbols = ExtractSymbols($libs, $profile, $pcs);
511
my $calls = ExtractCalls($symbols, $profile);
513
# Remove uniniteresting stack items
514
$profile = RemoveUninterestingFrames($symbols, $profile);
504
517
if ($main::opt_focus ne '') {
505
518
$profile = FocusProfile($symbols, $profile, $main::opt_focus);
526
539
PrintListing($libs, $flat, $cumulative, $main::opt_list);
527
540
} elsif ($main::opt_text) {
528
541
PrintText($symbols, $flat, $cumulative, $total, -1);
542
} elsif ($main::opt_callgrind) {
543
PrintCallgrind($calls);
530
545
if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {
531
546
if ($main::opt_gv) {
532
if (!system("$GV --version >/dev/null 2>&1")) {
533
# Options using double dash are supported by this gv version.
534
system("$GV --scale=$main::opt_scale " .
535
PsTempName($main::next_tmpfile));
537
# Old gv version - only supports options that use single dash.
538
system("$GV -scale $main::opt_scale " .
539
PsTempName($main::next_tmpfile));
547
RunGV(PsTempName($main::next_tmpfile), "");
580
my $bg = shift; # "" or " &" if we should run in background
581
if (!system("$GV --version >/dev/null 2>&1")) {
582
# Options using double dash are supported by this gv version.
583
# Also, turn on noantialias to better handle bug in gv for
584
# postscript files with large dimensions.
585
# TODO: Maybe we should not pass the --noantialias flag
586
# if the gv version is known to work properly without the flag.
587
system("$GV --scale=$main::opt_scale --noantialias " . $fname . $bg);
589
# Old gv version - only supports options that use single dash.
590
print STDERR "$GV -scale $main::opt_scale\n";
591
system("$GV -scale $main::opt_scale " . $fname . $bg);
571
596
##### Interactive helper routines #####
573
598
sub InteractiveMode {
574
599
$| = 1; # Make output unbuffered for interactive mode
575
600
my ($orig_profile, $symbols, $libs, $total) = @_;
602
print "Welcome to pprof! For help, type 'help'.\n";
577
604
# Use ReadLine if it's installed.
578
605
if ( !ReadlineMightFail() &&
579
606
defined(eval {require Term::ReadLine}) ) {
695
723
my $cumulative = CumulativeProfile($reduced);
697
725
if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {
698
if (!system("$GV --version >/dev/null 2>&1")) {
699
# Options using double dash are supported by this gv version.
700
system("$GV --scale=$main::opt_scale --noresize " .
701
PsTempName($main::next_tmpfile) . " &");
703
# Old gv version - only supports options that use single dash.
704
system("$GV -scale $main::opt_scale -noresize " .
705
PsTempName($main::next_tmpfile) . " &");
726
RunGV(PsTempName($main::next_tmpfile), " &");
707
727
$main::next_tmpfile++;
877
# Print the call graph in a way that's suiteable for callgrind.
880
printf("events: Hits\n\n");
881
foreach my $call ( map { $_->[0] }
882
sort { $a->[1] cmp $b ->[1] ||
883
$a->[2] <=> $b->[2] }
884
map { /([^:]+):(\d+):([^ ]+)( -> ([^:]+):(\d+):(.+))?/;
887
my $count = $calls->{$call};
888
$call =~ /([^:]+):(\d+):([^ ]+)( -> ([^:]+):(\d+):(.+))?/;
889
my ( $caller_file, $caller_line, $caller_function,
890
$callee_file, $callee_line, $callee_function ) =
891
( $1, $2, $3, $5, $6, $7 );
892
printf("fl=$caller_file\nfn=$caller_function\n");
894
printf("cfl=$callee_file\n");
895
printf("cfn=$callee_function\n");
896
printf("calls=$count $callee_line\n");
898
printf("$caller_line $count\n\n");
856
902
# Print disassembly for all all routines that match $main::opt_disasm
857
903
sub PrintDisassembly {
858
904
my $libs = shift;
1003
1048
# Disassemble all instructions (just to get line numbers)
1004
1049
my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);
1006
# Hack 1: assume that the last source location mentioned in the
1007
# disassembly is the end of the source code.
1051
# Hack 1: assume that the first source file encountered in the
1052
# disassembly contains the routine
1008
1053
my $filename = undef;
1010
for (my $i = $#instructions; $i >= 0; $i--) {
1054
for (my $i = 0; $i <= $#instructions; $i++) {
1011
1055
if ($instructions[$i]->[2] >= 0) {
1012
1056
$filename = $instructions[$i]->[1];
1013
$lastline = $instructions[$i]->[2];
1022
# Hack 2: assume the first source location from "filename" is the start of
1065
# Hack 2: assume that the largest line number from $filename is the
1066
# end of the procedure. This is typically safe since if P1 contains
1067
# an inlined call to P2, then P2 usually occurs earlier in the
1068
# source file. If this does not work, we might have to compute a
1069
# density profile or just print all regions we find.
1071
for (my $i = 0; $i <= $#instructions; $i++) {
1072
my $f = $instructions[$i]->[1];
1073
my $l = $instructions[$i]->[2];
1074
if (($f eq $filename) && ($l > $lastline)) {
1079
# Hack 3: assume the first source location from "filename" is the start of
1023
1080
# the source code.
1024
1081
my $firstline = 1;
1025
1082
for (my $i = 0; $i <= $#instructions; $i++) {
1223
1282
Percent($c, $overall_total));
1285
if ($main::opt_heapcheck) {
1287
# make leak-causing nodes more visible (add a background)
1288
$style = ",style=filled,fillcolor=gray"
1290
# make anti-leak-causing nodes (which almost never occur)
1291
# stand out as well (triple border)
1292
$style = ",peripheries=3"
1225
1296
printf DOT ("N%d [label=\"%s\\n%s (%s)%s\\r" .
1226
"\",shape=box,fontsize=%.1f];\n",
1297
"\",shape=box,fontsize=%.1f%s];\n",
1230
1301
Percent($f, $overall_total),
1420
1493
return $result;
1496
# If the second-youngest PC on the stack is always the same, returns
1497
# that pc. Otherwise, returns undef.
1498
sub IsSecondPcAlwaysTheSame {
1499
my $profile = shift;
1501
my $second_pc = undef;
1502
foreach my $k (keys(%{$profile})) {
1503
my @addrs = split(/\n/, $k);
1507
if (not defined $second_pc) {
1508
$second_pc = $addrs[1];
1510
if ($second_pc ne $addrs[1]) {
1518
# Extracts a graph of calls.
1520
my $symbols = shift;
1521
my $profile = shift;
1524
while( my ($stack_trace, $count) = each %$profile ) {
1525
my @address = split(/\n/, $stack_trace);
1526
for (my $i = 1; $i <= $#address; $i++) {
1527
if (exists $symbols->{$address[$i]}) {
1528
my $source = $symbols->{$address[$i]}->[1] . ":" .
1529
$symbols->{$address[$i]}->[0];
1530
my $destination = $symbols->{$address[$i-1]}->[1] . ":" .
1531
$symbols->{$address[$i-1]}->[0];
1532
my $call = "$source -> $destination";
1533
AddEntry($calls, $call, $count);
1536
AddEntry($calls, $destination, $count);
1545
sub RemoveUninterestingFrames {
1546
my $symbols = shift;
1547
my $profile = shift;
1549
# List of function names to skip
1551
my $skip_regexp = 'NOMATCH';
1552
if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
1553
foreach my $name ('calloc',
1562
'DoSampledAllocation',
1563
'simple_alloc::allocate',
1564
'__malloc_alloc_template::allocate',
1567
'__builtin_vec_delete',
1568
'__builtin_vec_new',
1573
$skip_regexp = "TCMalloc";
1574
} elsif ($main::profile_type eq 'contention') {
1575
foreach my $vname ('Mutex::Unlock', 'Mutex::UnlockSlow') {
1578
} elsif ($main::profile_type eq 'cpu') {
1579
# Drop signal handlers used for CPU profile collection
1580
# TODO(dpeng): this should not be necessary; it's taken
1581
# care of by the general 2nd-pc mechanism below.
1582
foreach my $name ('ProfileData::Add', # historical
1583
'ProfileData::prof_handler', # historical
1584
'CpuProfiler::prof_handler',
1585
'__pthread_sighandler',
1590
# Nothing skipped for unknown types
1593
if ($main::profile_type eq 'cpu') {
1594
# If all the second-youngest program counters are the same,
1595
# this STRONGLY suggests that it is an artifact of measurement,
1596
# i.e., stack frames pushed by the CPU profiler signal handler.
1597
# Hence, we delete them.
1598
# (The topmost PC is read from the signal structure, not from
1599
# the stack, so it does not get involved.)
1600
while (my $second_pc = IsSecondPcAlwaysTheSame($profile)) {
1603
if (exists($symbols->{$second_pc})) {
1604
$second_pc = $symbols->{$second_pc}->[0];
1606
print STDERR "Removing $second_pc from all stack traces.\n";
1607
foreach my $k (keys(%{$profile})) {
1608
my $count = $profile->{$k};
1609
my @addrs = split(/\n/, $k);
1610
splice @addrs, 1, 1;
1611
my $reduced_path = join("\n", @addrs);
1612
AddEntry($result, $reduced_path, $count);
1619
foreach my $k (keys(%{$profile})) {
1620
my $count = $profile->{$k};
1621
my @addrs = split(/\n/, $k);
1623
foreach my $a (@addrs) {
1624
if (exists($symbols->{$a})) {
1625
my $func = $symbols->{$a}->[0];
1626
if ($skip{$func} || ($func =~ m/$skip_regexp/)) {
1632
my $reduced_path = join("\n", @path);
1633
AddEntry($result, $reduced_path, $count);
1423
1638
# Reduce profile to granularity given by user
1424
1639
sub ReduceProfile {
1425
1640
my $symbols = shift;
1845
2087
my $prog = shift;
1846
2088
my $fname = shift;
1848
$main::heap_profile = 0;
1849
$main::lock_profile = 0;
2090
$main::profile_type = '';
1851
2092
# Look at first line to see if it is a heap or a CPU profile
1852
2093
open(PROFILE, "<$fname") || error("$fname: $!\n");
1853
2094
binmode PROFILE; # New perls do UTF-8 processing
1854
2095
my $header = <PROFILE>;
1855
2096
my $contention_marker = substr($CONTENTION_PAGE, 1); # remove leading /
1856
if ($header =~ m/^heap profile:/) {
1857
$main::heap_profile = 1;
2097
if ($header =~ m/^heap profile:.*growthz/) {
2098
$main::profile_type = 'growth';
2099
return ReadHeapProfile($prog, $fname, $header);
2100
} elsif ($header =~ m/^heap profile:/) {
2101
$main::profile_type = 'heap';
1858
2102
return ReadHeapProfile($prog, $fname, $header);
1859
2103
} elsif ($header =~ m/^--- *$contention_marker/o ) {
1860
$main::lock_profile = 1;
2104
$main::profile_type = 'contention';
1861
2105
return ReadSynchProfile($prog, $fname);
1862
2106
} elsif ($header =~ m/^--- *Stacks:/ ) {
1864
2108
"Old format contention profile: mistakenly reports " .
1865
2109
"condition variable signals as lock contentions.\n";
1866
$main::lock_profile = 1;
2110
$main::profile_type = 'contention';
1867
2111
return ReadSynchProfile($prog, $fname);
1869
2113
# Need to unread the line we just read
2114
$main::profile_type = 'cpu';
1870
2115
close(PROFILE);
1871
2116
open(PROFILE, "<$fname") || error("$fname: $!\n");
1872
2117
binmode PROFILE; # New perls do UTF-8 processing
2172
2421
# Convert cycles to nanoseconds
2173
2422
$cycles /= $cyclespernanosec;
2424
# Adjust for sampling done by application
2425
$cycles *= $sampling_period;
2174
2427
AddEntries($profile, $pcs, $stack, $cycles);
2176
} elsif ( $line =~ m|cycles/second = (\d+)|) {
2177
$cyclespernanosec = $1 / 1e9;
2178
$seen_clockrate = 1;
2179
} elsif ( $line =~ /sampling period = (\d+)/ ) {
2180
$sampling_period = $1;
2429
} elsif ( $line =~ m/^([^=]*)=(.*)$/ ) {
2430
my ($variable, $value) = ($1,$2);
2431
for ($variable, $value) {
2435
if($variable eq "cycles/second") {
2436
$cyclespernanosec = $value / 1e9;
2437
$seen_clockrate = 1;
2438
} elsif ($variable eq "sampling period") {
2439
$sampling_period = $value;
2440
} elsif ($variable eq "ms since reset") {
2441
# Currently nothing is done with this value in pprof
2442
# So we just silently ignore it for now
2443
} elsif ($variable eq "discarded samples") {
2444
# Currently nothing is done with this value in pprof
2445
# So we just silently ignore it for now
2447
printf STDERR ("Ignoring unnknown variable in /contentionz output: " .
2448
"'%s' = '%s'\n",$variable,$value);
2183
2451
# Memory map entry
2207
2475
my $addr = shift;
2209
2477
$addr =~ s/^0x//;
2479
if (length $addr > $address_length) {
2480
printf STDERR "Warning: address $addr is longer than address length $address_length\n";
2210
2483
return substr("000000000000000".$addr, -$address_length);
2213
2486
##### Symbol extraction #####
2488
# Aggressively search the lib_prefix values for the given library
2489
# If all else fails, just return the name of the library unmodified.
2490
# If the lib_prefix is "/my/path,/other/path" and $file is "/lib/dir/mylib.so"
2491
# it will search the following locations in this order, until it finds a file:
2492
# /my/path/lib/dir/mylib.so
2493
# /other/path/lib/dir/mylib.so
2494
# /my/path/dir/mylib.so
2495
# /other/path/dir/mylib.so
2497
# /other/path/mylib.so
2498
# /lib/dir/mylib.so (returned as last resort)
2503
# Search for the library as described above
2505
foreach my $prefix (@prefix_list) {
2506
my $fullpath = $prefix . $suffix;
2511
} while ($suffix =~ s|^/[^/]+/|/|);
2515
# Return path to library with debugging symbols.
2516
# For libc libraries, the copy in /usr/lib/debug contains debugging symbols
2517
sub DebuggingLibrary {
2519
if ($file =~ m|^/| && -f "/usr/lib/debug$file") {
2520
return "/usr/lib/debug$file";
2525
# Parse text section header of a library using objdump
2526
sub ParseTextSectionHeader {
2532
# Get objdump output from the library file to figure out how to
2533
# map between mapped addresses and addresses in the library.
2534
my $objdump = $obj_tool_map{"objdump"};
2535
open(OBJDUMP, "$objdump -h $lib |")
2536
|| error("$objdump $lib: $!\n");
2538
# Idx Name Size VMA LMA File off Algn
2539
# 10 .text 00104b2c 420156f0 420156f0 000156f0 2**4
2540
# For 64-bit objects, VMA and LMA will be 16 hex digits, size and file
2541
# offset may still be 8. But AddressSub below will still handle that.
2543
if (($#x >= 6) && ($x[1] eq '.text')) {
2546
$file_offset = $x[5];
2552
if (!defined($size)) {
2559
$r->{file_offset} = $file_offset;
2215
2564
# Split /proc/pid/maps dump into a list of libraries
2216
2565
sub ParseLibraries {
2217
2566
return if $main::use_symbol_page; # We don't need libraries info.
2254
2603
# Expand "$build" variable if available
2255
2604
$lib =~ s/\$build\b/$buildvar/g;
2257
# Get objdump output from the library file to figure out how to
2258
# map between mapped addresses and addresses in the library.
2259
my $objdump = $obj_tool_map{"objdump"};
2260
open(OBJDUMP, "$objdump -h $lib |")
2261
|| error("$objdump $lib: $!\n");
2263
# Idx Name Size VMA LMA File off Algn
2264
# 10 .text 00104b2c 420156f0 420156f0 000156f0 2**4
2265
# For 64-bit objects, VMA and LMA will be 16 hex digits, size and file
2266
# offset may still be 8. But AddressSub below will still handle that.
2268
if (($#x >= 6) && ($x[1] eq '.text')) {
2270
my $file_offset = $x[5];
2271
my $vma_offset = AddressSub($vma, $file_offset);
2272
$offset = AddressAdd($offset, $vma_offset);
2606
$lib = FindLibrary($lib);
2608
# Check for pre-relocated libraries, which use pre-relocated symbol tables
2609
# and thus require adjusting the offset that we'll use to translate
2610
# VM addresses into symbol table addresses.
2611
# Only do this if we're not going to fetch the symbol table from a
2612
# debugging copy of the library.
2613
if (!DebuggingLibrary($lib)) {
2614
my $text = ParseTextSectionHeader($lib);
2615
if (defined($text)) {
2616
my $vma_offset = AddressSub($text->{vma}, $text->{file_offset});
2617
$offset = AddressAdd($offset, $vma_offset);
2278
2621
push(@{$result}, [$lib, $start, $finish, $offset]);
2624
# Append special entry for additional library (not relocated)
2625
if ($main::opt_lib ne "") {
2626
my $text = ParseTextSectionHeader($main::opt_lib);
2627
if (defined($text)) {
2628
my $start = $text->{vma};
2629
my $finish = AddressAdd($start, $text->{size});
2631
push(@{$result}, [$main::opt_lib, $start, $finish, $start]);
2281
2635
# Append special entry for the main program
2282
2636
my $max_pc = "0";
2283
2637
foreach my $pc (keys(%{$pcs})) {
2599
2966
# Figure out the right default pathname prefix based on the program file
2601
2968
my $default_prefix = "/usr/bin/";
2602
my $file_type = `/usr/bin/file $prog_file`;
2603
if ($file_type =~ /ELF 32-bit/) {
2604
$default_prefix = "/usr/bin/";
2605
} elsif ($file_type =~ /ELF 64-bit/) {
2969
# Follow symlinks (at least for systems where "file" supports that)
2970
my $file_type = `/usr/bin/file -L $prog_file 2>/dev/null || /usr/bin/file $prog_file`;
2971
if ($file_type !~ /executable/) {
2972
warn "WARNING: program $prog_file is apparently not an executable\n";
2973
# Don't change the default prefix.
2974
} elsif ($file_type =~ /64-bit/) {
2606
2975
# Change $address_length to 16 if the program file is ELF 64-bit.
2607
2976
# We can't detect this from many (most?) heap or lock contention
2608
2977
# profiles, since the actual addresses referenced are generally in low
2609
2978
# memory even for 64-bit programs.
2610
2979
$address_length = 16;
2612
print STDERR "WARNING: program $prog_file is apparently not an ELF file\n";
2613
# Don't change the default prefix.
2616
2982
# Go fill in %obj_tool_map with the pathnames to use:
2617
2983
foreach my $tool (keys %obj_tool_map) {
2618
$obj_tool_map{$tool} = ConfigureTool($tool, $default_prefix);
2984
$obj_tool_map{$tool} = ConfigureTool($obj_tool_map{$tool},
2689
# Gets the procedure boundaries for all routines in "$image" whose names
2690
# match "$regexp" and returns them in a hashtable mapping from procedure
2691
# name to a two-element vector of [start address, end address]
2692
sub GetProcedureBoundaries {
3056
# Run $nm_command and get all the resulting procedure boundaries whose
3057
# names match "$regexp" and returns them in a hashtable mapping from
3058
# procedure name to a two-element vector of [start address, end address]
3059
sub GetProcedureBoundariesViaNm {
3060
my $nm_command = shift;
2694
3061
my $regexp = shift;
2696
3063
my $symbol_table = {};
2697
my $nm = $obj_tool_map{"nm"};
2698
open(NM, "$nm -C -n $image |") || error("$nm: $!\n");
3064
open(NM, "$nm_command |") || error("$nm_command: $!\n");
2699
3065
my $last_start = "0";
2700
3066
my $routine = "";
2714
3081
return $symbol_table;
3084
# Gets the procedure boundaries for all routines in "$image" whose names
3085
# match "$regexp" and returns them in a hashtable mapping from procedure
3086
# name to a two-element vector of [start address, end address].
3087
# Will return an empty map if nm is not installed or not working properly.
3088
sub GetProcedureBoundaries {
3092
# For libc libraries, the copy in /usr/lib/debug contains debugging symbols
3093
my $debugging = DebuggingLibrary($image);
3095
$image = $debugging;
3098
my $nm = $obj_tool_map{"nm"};
3099
my $cppfilt = $obj_tool_map{"c++filt"};
3101
# nm can fail for two reasons: 1) $image isn't a debug library; 2) nm
3102
# binary doesn't support --demangle. For the first, we try with -D
3103
# to at least get *exported* symbols. For the second, we use c++filt
3104
# instead of --demangle. (c++filt is less reliable though, because it
3105
# might interpret nm meta-data as c++ symbols and try to demangle it :-/)
3106
foreach my $nm_command ("$nm -n --demangle $image 2>/dev/null",
3107
"$nm -n $image 2>&1 | $cppfilt",
3108
"$nm -D -n --demangle $image 2>/dev/null",
3109
"$nm -D -n $image 2>&1 | $cppfilt",
3110
"$nm -n $image 2>/dev/null",
3111
"$nm -D -n $image 2>/dev/null") {
3112
my $symbol_table = GetProcedureBoundariesViaNm($nm_command, $regexp);
3113
return $symbol_table if (%{$symbol_table});
3115
my $symbol_table = {};
3116
return $symbol_table;
2718
3120
# The test vectors for AddressAdd/Sub/Inc are 8-16-nibble hex strings.
2719
3121
# To make them more readable, we add underscores at interesting places.