openGauss-server/Tools/memory_check/asan_report.pl

302 lines
9.6 KiB
Perl

#!/usr/bin/perl
use warnings;
use strict;
use Getopt::Long;
use File::Basename qw(dirname);
our $show_usage;
our $verbose;
our $ignored_dir;
our $asanlog_dir;
our $output;
use constant {
SEARCH_KEY_LEN => 64,
};
sub usage() {
print "perl asan_report.pl --asanlog-dir /Directory/to/AddressSanitize/Log/ \n";
print "perl asan_report.pl --asanlog-dir /Directory/to/AddressSanitize/Log/ --output /Path/to/Output/File --ignore-dir /Directory/to/Ignored/Results/ \n";
print "perl asan_report.pl --help \n";
}
sub get_asan_log_dir {
my $log_file_dir = $asanlog_dir;
unless ($asanlog_dir) {
my $asan_option = $ENV{'ASAN_OPTIONS'};
if ($asan_option =~ /log_path=([\w\/\.\-\s]+)/) {
$log_file_dir = dirname $1;
}
}
$log_file_dir =~ s/\/+$//g;
$log_file_dir;
}
sub check_component {
my($line_block) = @_;
my $component = '';
for my $line(@$line_block) {
if ($line =~ m/^\s+#(\d+)\s+(\w+)\s+in\s+(\w+)\s+(\S+)$/) {
my($index, $addr, $func, $file_lineno) = ($1, $2, $3, $4);
my ($file, $lno) = split (/:/, $file_lineno);
if ($func eq 'main') {
if ($file =~ m{/Code/src/}) {
my $fn = $';
if ($fn =~ m{^(gtm|cm)/}) {
$component = $1;
}
elsif ($fn =~ m{^backend/}) {
$component = 'gaussdb';
}
elsif ($fn =~ m{^bin/(\w+)/}) {
$component = $1
}
else {
print "Unknown main location: $line\n"
}
return $component if $component;
}
}
else {
# last result will win
if ($file =~ m{/Code/src/}) {
my $fn = $';
if ($fn =~ m{^(gtm|cm)/}) {
$component = $1;
}
elsif ($fn =~ m{^backend/}) {
$component = 'gaussdb';
}
elsif ($fn =~ m{^bin/(\w+)/}) {
$component = $1
}
}
}
}
}
$component = 'unknown' unless $component;
$component;
}
sub is_call_stack_unique {
my ($ignored_call_stacks, $uniq_call_stacks, $line_block) = @_;
my $search_key = '';
my $signature = '';
for my $line(@$line_block) {
if ($line =~ m/^\s+#(\d+)\s+(\w+)\s+in\s+(\w+)\s+(\S+)$/) {
my($index, $addr, $func, $file_lineno) = ($1, $2, $3, $4);
my ($file, $lno) = split (/:/, $file_lineno);
if ($file =~ m{/Code/src/}) {
$file = "src/" . $';
if (defined $lno) {
$search_key = $search_key . "$func:$file:$lno";
}
else {
$search_key = $search_key . "$func:$file";
}
}
if (defined $lno) {
$signature = $signature . "$func:$file:$lno";
}
else {
$signature = $signature . "$func:$file";
}
}
}
if (ref $ignored_call_stacks eq 'HASH') {
if ( exists $ignored_call_stacks->{substr($search_key, 0, SEARCH_KEY_LEN)} ) {
for my $sig(@{ $ignored_call_stacks->{substr($search_key, 0, SEARCH_KEY_LEN)} } ) {
return 0 if ($sig eq $signature);
}
}
}
if ( exists $uniq_call_stacks->{substr($search_key, 0, SEARCH_KEY_LEN)} ) {
for my $sig(@{ $uniq_call_stacks->{substr($search_key, 0, SEARCH_KEY_LEN)} } ) {
return 0 if ($sig eq $signature);
}
}
else {
$uniq_call_stacks->{substr($search_key, 0, SEARCH_KEY_LEN)} = []
}
push @{$uniq_call_stacks->{substr($search_key, 0, SEARCH_KEY_LEN)}}, $signature;
return 1;
}
sub gen_report {
my ($log_dir, $ignored_call_stacks, $output_file) = @_;
return {} unless $log_dir;
my %uniq_call_stacks;
my @mem_leak_block = ();
my @addr_issue_block = ();
for my $file(glob "${log_dir}/*") {
open my $fh, "<$file" or next;
my $type = 'none';
my @file_content = ();
while(<$fh>) {
push @file_content, $_;
}
close $fh;
my $component = check_component(\@file_content);
if ($output_file) {
}
foreach my $line(@file_content) {
chomp $line;
next unless $line =~ /\s*\S+/;
# headline
# assuming one type error
if ($line =~ /^==\d+==ERROR:/) {
if ($line =~ /LeakSanitizer:/) {
$type = 'memory-leak';
}
elsif ($line =~ /AddressSanitizer:/ && $line =~ /double-free/) {
$type = 'double-free';
}
elsif ($line =~ /AddressSanitizer:/ && $line =~ /attempting free/) {
$type = 'bad-free';
}
elsif ($line =~ /AddressSanitizer:\s+([-\w]+)/) {
$type = $1;
}
else {
print "Unknown error type in $file:\n$line\n" if $verbose;
}
}
# block header
elsif ($line =~ /(Direct|Indirect) leak/) {
$type = 'memory-leak' if $type eq 'none';
if (scalar @mem_leak_block) {
if (is_call_stack_unique($ignored_call_stacks, \%uniq_call_stacks, \@mem_leak_block)) {
if ($output_file) {
open MEMLEAK, ">>$output_file.memory-leak.$component";
print MEMLEAK "$_\n" foreach (@mem_leak_block);
print MEMLEAK "\n\n";
close MEMLEAK;
}
}
@mem_leak_block = ();
}
# $type = 'memory-leak';
push @mem_leak_block, $line;
}
elsif ($line =~ /^\s+#\d+/) {
if ($type eq 'memory-leak') {
push @mem_leak_block, $line
}
elsif ($type eq 'none') {
print "Unknown issue type for $file\n" if $verbose;
}
else {
push @addr_issue_block, $line
}
}
# start another block
elsif ( $line =~ /^\w+/ || $line =~ /^={3,}/ ) {
if ($type eq 'memory-leak' && scalar @mem_leak_block) {
if (is_call_stack_unique($ignored_call_stacks, \%uniq_call_stacks, \@mem_leak_block)) {
if ($output_file) {
open MEMLEAK, ">>$output_file.memory-leak.$component";
print MEMLEAK "$_\n" foreach (@mem_leak_block);
print MEMLEAK "\n\n";
close MEMLEAK;
}
}
@mem_leak_block = ();
}
elsif ($type eq 'none' && (scalar @addr_issue_block || scalar @mem_leak_block)) {
print "\n\n***FATAL***: you should not see me at all.\n";
print "Check in $file:\n$line\n";
}
elsif (scalar @addr_issue_block) {
# Fatal address issue:
# 1. stack-buffer-overflow/heap-buffer-overflow
# 2. double-free
#
# Assuming only ONE issue in each log file for above issue type
last
}
else {
# first block for fatal address issue is not yet filled
# second or subsequent blocks for memory leak
}
}
}
close $fh;
if ($type eq 'memory-leak' && scalar @mem_leak_block) {
if (is_call_stack_unique($ignored_call_stacks, \%uniq_call_stacks, \@mem_leak_block)) {
if ($output_file) {
open MEMLEAK, ">>$output_file.memory-leak.$component";
print MEMLEAK "$_\n" foreach (@mem_leak_block);
print MEMLEAK "\n\n";
close MEMLEAK;
}
}
}
elsif ($type ne 'none' && scalar @addr_issue_block) {
if (is_call_stack_unique($ignored_call_stacks, \%uniq_call_stacks, \@addr_issue_block)) {
open my $ifh, "<$file" or next;
open my $ofh, ">>$output.$type.$component" or next;
while (<$ifh>) {
print $ofh $_;
}
print $ofh "\n\n";
close $ifh;
close $ofh;
}
}
@mem_leak_block = ();
@addr_issue_block = ()
}
## for my $file(glob "${log_file_trunk}*") {
close MEMLEAK;
\%uniq_call_stacks
}
GetOptions (
"ignore-dir=s" => \$ignored_dir,
"asanlog-dir=s" => \$asanlog_dir,
"output|o=s" => \$output,
"verbose" => \$verbose,
"help|h" => \$show_usage
) or die("Error in command line arguments\n");
if ($show_usage) {
usage();
exit 0;
}
$output = "$$" unless $output;
my $ignored_call_stacks = gen_report($ignored_dir, undef, undef);
my $log_file_dir = get_asan_log_dir();
gen_report($log_file_dir, $ignored_call_stacks, $output);