]> git.xonotic.org Git - xonotic/xonotic.git/blobdiff - misc/tools/progs-analyzer.pl
LNO support.
[xonotic/xonotic.git] / misc / tools / progs-analyzer.pl
old mode 100644 (file)
new mode 100755 (executable)
index b4ffbca..026d6af
@@ -1,3 +1,5 @@
+#!/usr/bin/perl
+
 use strict;
 use warnings;
 use Digest::SHA;
@@ -125,6 +127,10 @@ sub checkop($)
        {
                return { a => 'inglobal', b => 'inglobalfunc' };
        }
+       if($op =~ /^INVALID#/)
+       {
+               return { isinvalid => 1 };
+       }
        return { a => 'inglobal', b => 'inglobal', c => 'outglobal' };
 }
 
@@ -185,6 +191,19 @@ use constant DFUNCTION_T => [
        [uchar8 => 'parm_size'],
 ];
 
+use constant LNOHEADER_T => [
+       [int => 'lnotype'],
+       [int => 'version'],
+       [int => 'numglobaldefs'],
+       [int => 'numglobals'],
+       [int => 'numfielddefs'],
+       [int => 'numstatements'],
+];
+
+use constant LNO_T => [
+       [int => 'v'],
+];
+
 sub get_section($$$)
 {
        my ($fh, $start, $len) = @_;
@@ -358,6 +377,20 @@ sub str($)
        return "\"$str\"";
 }
 
+sub debugpos($$$) {
+       my ($progs, $func, $ip) = @_;
+       my $s = $func->{debugname};
+       if ($progs->{cno}) {
+               my $column = $progs->{cno}[$ip]{v};
+               $s =~ s/:/:$column:/;
+       }
+       if ($progs->{lno}) {
+               my $line = $progs->{lno}[$ip]{v};
+               $s =~ s/:/:$line:/;
+       }
+       return $s;
+}
+
 sub disassemble_function($$;$)
 {
        my ($progs, $func, $highlight) = @_;
@@ -505,7 +538,8 @@ sub disassemble_function($$;$)
                                {
                                        print PRE_MARK_STATEMENT;
                                        printf INSTRUCTION_FORMAT, '', '<!>', '.WARN';
-                                       printf OPERAND_FORMAT, "$_ (in $func->{debugname})";
+                                       my $pos = debugpos $progs, $func, $ip;
+                                       printf OPERAND_FORMAT, "$_ (in $pos)";
                                        print INSTRUCTION_SEPARATOR;
                                }
                        }
@@ -673,6 +707,10 @@ sub find_uninitialized_locals($$)
                                }
                        }
 
+                       if($c->{isinvalid})
+                       {
+                               ++$warned{$ip}{''}{"Invalid opcode"};
+                       }
                        for(qw(a b c))
                        {
                                my $type = $c->{$_};
@@ -897,7 +935,8 @@ sub detect_constants($)
        use constant GLOBALFLAG_Q => 32; # unique to function
        use constant GLOBALFLAG_U => 64; # unused
        use constant GLOBALFLAG_P => 128; # possibly parameter passing
-       my @globalflags = (GLOBALFLAG_Q | GLOBALFLAG_U) x @{$progs->{globals}};
+       use constant GLOBALFLAG_D => 256; # has a def
+       my @globalflags = (GLOBALFLAG_Q | GLOBALFLAG_U) x (@{$progs->{globals}} + 2);
 
        for(@{$progs->{functions}})
        {
@@ -979,21 +1018,42 @@ sub detect_constants($)
                        if $name eq 'IMMEDIATE' or $name =~ /^\./;
                $_->{debugname} = $name
                        if $name ne '';
+               $globalflags[$_->{ofs}] |= GLOBALFLAG_D;
                if($type->{save})
                {
-                       for my $i(0..(typesize($_->{type}{type})-1))
-                       {
-                               $globalflags[$_->{ofs}] |= GLOBALFLAG_S;
-                       }
+                       $globalflags[$_->{ofs}] |= GLOBALFLAG_S;
+               }
+               if(defined $_->{debugname})
+               {
+                       $globalflags[$_->{ofs}] |= GLOBALFLAG_N;
                }
-               if($name ne '')
+       }
+       # fix up vectors
+       my @extradefs = ();
+       for(@{$progs->{globaldefs}})
+       {
+               my $type = $_->{type};
+               for my $i(1..(typesize($type->{type})-1))
                {
-                       for my $i(0..(typesize($_->{type}{type})-1))
+                       # add missing def
+                       if(!($globalflags[$_->{ofs}+$i] & GLOBALFLAG_D))
                        {
-                               $globalflags[$_->{ofs}] |= GLOBALFLAG_N;
+                               print "Missing globaldef for a component@{[defined $_->{debugname} ? ' of ' . $_->{debugname} : '']} at $_->{ofs}+$i\n";
+                               push @extradefs, {
+                                       type => {
+                                               saved => 0,
+                                               type => 'float'
+                                       },
+                                       ofs => $_->{ofs} + $i,
+                                       debugname => defined $_->{debugname} ? $_->{debugname} . "[$i]" : undef
+                               };
                        }
+                       # "saved" and "named" states hit adjacent globals too
+                       $globalflags[$_->{ofs}+$i] |= $globalflags[$_->{ofs}] & (GLOBALFLAG_S | GLOBALFLAG_N | GLOBALFLAG_D);
                }
        }
+       push @{$progs->{globaldefs}}, @extradefs;
+
        my %offsets_initialized = ();
        for(0..(@{$progs->{globals}}-1))
        {
@@ -1086,7 +1146,8 @@ sub detect_constants($)
                $globaldefs[$_] //= {
                        ofs => $_,
                        s_name => undef,
-                       debugname => undef
+                       debugname => undef,
+                       type => undef
                };
        }
        for(0..(@{(DEFAULTGLOBALS)}-1))
@@ -1127,15 +1188,40 @@ sub detect_constants($)
        };
 }
 
-sub parse_progs($)
+sub parse_progs($$)
 {
-       my ($fh) = @_;
+       my ($fh, $lnofh) = @_;
 
        my %p = ();
 
        print STDERR "Parsing header...\n";
        $p{header} = parse_section $fh, DPROGRAMS_T, 0, undef, 1;
        
+       if (defined $lnofh) {
+               print STDERR "Parsing LNO...\n";
+               my $lnoheader = parse_section $lnofh, LNOHEADER_T, 0, undef, 1;
+               eval {
+                       die "Not a LNOF"
+                               if $lnoheader->{lnotype} != unpack 'V', 'LNOF';
+                       die "Not version 1"
+                               if $lnoheader->{version} != 1;
+                       die "Not same count of globaldefs"
+                               if $lnoheader->{numglobaldefs} != $p{header}{numglobaldefs};
+                       die "Not same count of globals"
+                               if $lnoheader->{numglobals} != $p{header}{numglobals};
+                       die "Not same count of fielddefs"
+                               if $lnoheader->{numfielddefs} != $p{header}{numfielddefs};
+                       die "Not same count of statements"
+                               if $lnoheader->{numstatements} != $p{header}{numstatements};
+                       $p{lno} = [parse_section $lnofh, LNO_T, 24, undef, $lnoheader->{numstatements}];
+                       eval {
+                               $p{lno} = [parse_section $lnofh, LNO_T, 24, undef, $lnoheader->{numstatements} * 2];
+                               $p{cno} = [splice $p{lno}, $lnoheader->{numstatements}];
+                               print STDERR "Cool, this LNO even has column number info!\n";
+                       };
+               } or warn "Skipping LNO: $@";
+       }
+
        print STDERR "Parsing strings...\n";
        $p{strings} = get_section $fh, $p{header}{ofs_strings}, $p{header}{numstrings};
        $p{getstring} = sub
@@ -1158,7 +1244,7 @@ sub parse_progs($)
                die "Out of range name in globaldef $_"
                        if $g->{s_name} < 0 || $g->{s_name} >= length $p{strings};
                my $name = $p{getstring}->($g->{s_name});
-               die "Out of range ofs in globaldef $_ (name: \"$name\")"
+               die "Out of range ofs $g->{ofs} in globaldef $_ (name: \"$name\")"
                        if $g->{ofs} >= $p{globals};
        }
 
@@ -1172,7 +1258,7 @@ sub parse_progs($)
                die "Out of range name in fielddef $_"
                        if $g->{s_name} < 0 || $g->{s_name} >= length $p{strings};
                my $name = $p{getstring}->($g->{s_name});
-               die "Out of range ofs in globaldef $_ (name: \"$name\")"
+               die "Out of range ofs $g->{ofs} in fielddef $_ (name: \"$name\")"
                        if $g->{ofs} >= $p{header}{entityfields};
        }
 
@@ -1194,12 +1280,12 @@ sub parse_progs($)
                my $file = $p{getstring}->($f->{s_file});
                die "Out of range first_statement in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
                        if $f->{first_statement} >= @{$p{statements}};
-               die "Out of range parm_start in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
-                       if $f->{parm_start} < 0 || $f->{parm_start} >= @{$p{globals}};
-               die "Out of range locals in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
-                       if $f->{locals} < 0 || $f->{parm_start} + $f->{locals} >= @{$p{globals}};
                if($f->{first_statement} >= 0)
                {
+                       die "Out of range parm_start in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
+                               if $f->{parm_start} < 0 || $f->{parm_start} >= @{$p{globals}};
+                       die "Out of range locals in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
+                               if $f->{locals} < 0 || $f->{parm_start} + $f->{locals} > @{$p{globals}};
                        die "Out of range numparms $f->{numparms} in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
                                if $f->{numparms} < 0 || $f->{numparms} > 8;
                        my $totalparms = 0;
@@ -1210,7 +1296,9 @@ sub parse_progs($)
                                $totalparms += $f->{parm_size}[$_];
                        }
                        die "Out of range parms in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
-                               if $f->{locals} < 0 || $f->{parm_start} + $totalparms >= @{$p{globals}};
+                               if $f->{parm_start} + $totalparms > @{$p{globals}};
+                       die "More parms than locals in function $_ (name: \"$name\", file: \"$file\", first statement: $f->{first_statement})"
+                               if $totalparms > $f->{locals};
                }
        }
 
@@ -1235,8 +1323,18 @@ sub parse_progs($)
                        elsif($type eq 'inglobalvec')
                        {
                                $s->{$_} &= 0xFFFF;
-                               die "Out of range global offset in statement $ip - cannot continue"
-                                       if $s->{$_} >= @{$p{globals}}-2;
+                               if($c->{isreturn})
+                               {
+                                       die "Out of range global offset in statement $ip - cannot continue"
+                                               if $s->{$_} >= @{$p{globals}};
+                                       print "Potentially out of range global offset in statement $ip - may crash engines"
+                                               if $s->{$_} >= @{$p{globals}}-2;
+                               }
+                               else
+                               {
+                                       die "Out of range global offset in statement $ip - cannot continue"
+                                               if $s->{$_} >= @{$p{globals}}-2;
+                               }
                        }
                        elsif($type eq 'outglobal')
                        {
@@ -1385,5 +1483,15 @@ sub parse_progs($)
        }
 }
 
-open my $fh, '<', $ARGV[0];
-parse_progs $fh;
+for my $progs (@ARGV) {
+       my $lno = "$progs.lno";
+       $lno =~ s/\.dat\.lno$/.lno/;
+
+       open my $fh, '<', $progs
+               or die "$progs: $!";
+
+       open my $lnofh, '<', $lno
+               or warn "$lno: $!";
+
+       parse_progs $fh, $lnofh;
+}