Skip to content

Commit

Permalink
Fall back to symbol+offset when calculating source lines if mod+pc fails
Browse files Browse the repository at this point in the history
When using perf to profile jni code in java with help of a map agent [1],
the output of perf script contain raw $pc values for dynamically loaded
libraries. This does not get translated into source lines when using the
'stackcollapse-perf.pl --source --inline' command.
But perf script also produces symbol+offset in outputs, so this commit
adds a fallback that, if normal means fail, calculates address in mod by
getting the symbol address with nm and adding to the offset, and retries
with source line calculation for this address, producing a valid result.

[1] https://github.com/jvm-profiling-tools/perf-map-agent
  • Loading branch information
jan-konczak-cs-put committed Apr 21, 2021
1 parent 7b0ca27 commit 6b3d9bf
Showing 1 changed file with 38 additions and 13 deletions.
51 changes: 38 additions & 13 deletions stackcollapse-perf.pl
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,20 @@ sub remember_stack {

my %inlineCache;

my %nmCache;

sub inlineCacheAdd {
my ($pc, $mod, $result) = @_;
if (defined($inlineCache{$pc})) {
$inlineCache{$pc}{$mod} = $result;
} else {
$inlineCache{$pc} = {$mod => $result};
}
}

# for the --inline option
sub inline {
my ($pc, $mod) = @_;
my ($pc, $rawfunc, $mod) = @_;

return $inlineCache{$pc}{$mod} if defined($inlineCache{$pc}{$mod});

Expand All @@ -137,6 +148,24 @@ sub inline {
# remove first line
$a2l_output =~ s/^(.*\n){1}//;

if ($a2l_output =~ /\?\?\n\?\?:0/) {
# if addr2line fails and rawfunc is func+offset, then fall back to it
if ($rawfunc =~ /^(.+)\+0x([0-9a-f]+)$/) {
my $func = $1;
my $addr = hex $2;

$nmCache{$mod}=`nm $mod` unless defined $nmCache{$mod};

if ($nmCache{$mod} =~ /^([0-9a-f]+) . $func$/m) {
my $base = hex $1;
my $newPc = sprintf "0x%x", $base+$addr;
my $result = inline($newPc, '', $mod);
inlineCacheAdd($pc, $mod, $result);
return $result;
}
}
}

my @fullfunc;
my $one_item = "";
for (split /^/, $a2l_output) {
Expand All @@ -159,11 +188,7 @@ sub inline {

my $result = join ";" , @fullfunc;

if (defined($inlineCache{$pc})){
$inlineCache{$pc}{$mod}=$result;
} else {
$inlineCache{$pc} = {$mod => $result};
}
inlineCacheAdd($pc, $mod, $result);

return $result;
}
Expand Down Expand Up @@ -272,22 +297,22 @@ sub inline {

my ($pc, $rawfunc, $mod) = ($1, $2, $3);

# Linux 4.8 included symbol offsets in perf script output by default, eg:
# 7fffb84c9afc cpu_startup_entry+0x800047c022ec ([kernel.kallsyms])
# strip these off:
$rawfunc =~ s/\+0x[\da-f]+$//;

if ($show_inline == 1 && $mod !~ m/(perf-\d+.map|kernel\.|\[[^\]]+\])/) {
my $inlineRes = inline($pc, $mod);
my $inlineRes = inline($pc, $rawfunc, $mod);
# - empty result this happens e.g., when $mod does not exist or is a path to a compressed kernel module
# if this happens, the user will see error message from addr2line written to stderr
# - if addr2line results in "??" , then it's much more sane to fall back than produce a '??' in graph
if($inlineRes ne "" and $inlineRes ne "??" and $inlineRes ne "??:??:0" ) {
unshift @stack, inline($pc, $mod);
unshift @stack, $inlineRes;
next;
}
}

# Linux 4.8 included symbol offsets in perf script output by default, eg:
# 7fffb84c9afc cpu_startup_entry+0x800047c022ec ([kernel.kallsyms])
# strip these off:
$rawfunc =~ s/\+0x[\da-f]+$//;

next if $rawfunc =~ /^\(/; # skip process names

my $is_unknown=0;
Expand Down

0 comments on commit 6b3d9bf

Please sign in to comment.