12 return sub { $_[0] >= (2**($bits-1)) ? $_[0]-(2**$bits) : $_[0]; };
15 use constant OPCODE_E => [qw[
17 MUL_F MUL_V MUL_FV MUL_VF
21 EQ_F EQ_V EQ_S EQ_E EQ_FNC
22 NE_F NE_V NE_S NE_E NE_FNC
24 LOAD_F LOAD_V LOAD_S LOAD_ENT LOAD_FLD LOAD_FNC
26 STORE_F STORE_V STORE_S STORE_ENT STORE_FLD STORE_FNC
27 STOREP_F STOREP_V STOREP_S STOREP_ENT STOREP_FLD STOREP_FNC
29 NOT_F NOT_V NOT_S NOT_ENT NOT_FNC
31 CALL0 CALL1 CALL2 CALL3 CALL4 CALL5 CALL6 CALL7 CALL8
37 use constant ETYPE_E => [qw[
47 use constant DEF_SAVEGLOBAL => 32768;
51 return 3 if $type eq 'vector';
60 return { a => 'inglobalvec', b => 'ipoffset', isjump => 'b', isconditional => 1 };
64 return { a => 'inglobal', b => 'ipoffset', isjump => 'b', isconditional => 1 };
68 return { a => 'ipoffset', isjump => 'a', isconditional => 0 };
70 if($op =~ /^ADD_V$|^SUB_V$/)
72 return { a => 'inglobalvec', b => 'inglobalvec', c => 'outglobalvec' };
74 if($op =~ /^MUL_V$|^EQ_V$|^NE_V$/)
76 return { a => 'inglobalvec', b => 'inglobalvec', c => 'outglobal' };
80 return { a => 'inglobal', b => 'inglobalvec', c => 'outglobalvec' };
84 return { a => 'inglobalvec', b => 'inglobal', c => 'outglobalvec' };
88 return { a => 'inglobal', b => 'inglobal', c => 'outglobalvec' };
92 return { a => 'inglobalvec', c => 'outglobal' };
96 return { a => 'inglobal', c => 'outglobal' };
100 return { a => 'inglobalvec', b => 'inglobal' };
104 return { a => 'inglobalvec', b => 'outglobalvec' };
106 if($op =~ /^STOREP_/)
108 return { a => 'inglobal', b => 'inglobal' };
112 return { a => 'inglobal', b => 'outglobal' };
116 return { a => 'inglobalfunc', iscall => 1 };
118 if($op =~ /^DONE$|^RETURN$/)
120 return { a => 'inglobal', isreturn => 1 };
122 return { a => 'inglobal', b => 'inglobal', c => 'outglobal' };
125 use constant TYPES => {
126 int => ['V', 4, signed 32],
127 ushort => ['v', 2, id],
128 short => ['v', 2, signed 16],
129 opcode => ['v', 2, sub { OPCODE_E->[$_[0]] or die "Invalid opcode: $_[0]"; }],
130 float => ['f', 4, id],
131 uchar8 => ['a8', 8, sub { [unpack 'C8', $_[0]] }],
132 global => ['i', 4, sub { { int => $_[0], float => unpack "f", pack "L", $_[0] }; }],
133 deftype => ['v', 2, sub { { type => ETYPE_E->[$_[0] & ~DEF_SAVEGLOBAL], save => !!($_[0] & DEF_SAVEGLOBAL) }; }],
136 use constant DPROGRAMS_T => [
139 [int => 'ofs_statements'],
140 [int => 'numstatements'],
141 [int => 'ofs_globaldefs'],
142 [int => 'numglobaldefs'],
143 [int => 'ofs_fielddefs'],
144 [int => 'numfielddefs'],
145 [int => 'ofs_functions'],
146 [int => 'numfunctions'],
147 [int => 'ofs_strings'],
148 [int => 'numstrings'],
149 [int => 'ofs_globals'],
150 [int => 'numglobals'],
151 [int => 'entityfields']
154 use constant DSTATEMENT_T => [
161 use constant DDEF_T => [
167 use constant DGLOBAL_T => [
171 use constant DFUNCTION_T => [
172 [int => 'first_statement'],
173 [int => 'parm_start'],
179 [uchar8 => 'parm_size'],
184 my ($fh, $start, $len) = @_;
187 $len == read $fh, my $buf, $len
192 sub parse_section($$$$$)
194 my ($fh, $struct, $start, $len, $cnt) = @_;
197 $itemlen += TYPES->{$_->[0]}->[1]
199 my $packspec = join '', map { TYPES->{$_->[0]}->[0]; } @$struct;
200 my @packnames = map { $_->[1]; } @$struct;
202 $len = $cnt * $itemlen
203 if not defined $len and defined $cnt;
204 $cnt = int($len / $itemlen)
205 if not defined $cnt and defined $len;
206 die "Invalid length specification"
207 unless defined $len and defined $cnt and $len == $cnt * $itemlen;
208 die "Invalid length specification in scalar context"
209 unless wantarray or $cnt == 1;
215 $itemlen == read $fh, my $buf, $itemlen
218 @h{@packnames} = unpack $packspec, $buf;
219 $h{$_->[1]} = TYPES->{$_->[0]}->[2]->($h{$_->[1]})
231 my ($progs, $ip, $state, $copy_handler, $state_hasher, $instruction_handler) = @_;
234 my $statements = $progs->{statements};
239 no warnings 'recursion';
241 my ($ip, $state) = @_;
245 my $statestr = $state_hasher->($state);
247 if $seen{"$ip:$statestr"}++;
249 my $s = $statements->[$ip];
250 my $c = checkop $s->{op};
252 $instruction_handler->($ip, $state, $s, $c);
260 if($c->{isconditional})
262 $nfa->($ip+1, $copy_handler->($state));
263 $ip += $s->{$c->{isjump}};
267 $ip += $s->{$c->{isjump}};
277 $nfa->($ip, $copy_handler->($state));
280 use constant PRE_MARK_STATEMENT => "\e[1m";
281 use constant POST_MARK_STATEMENT => "\e[m";
282 use constant PRE_MARK_OPERAND => "\e[41m";
283 use constant POST_MARK_OPERAND => "\e[49m";
285 use constant INSTRUCTION_FORMAT => "%8s %3s | %-12s ";
286 use constant OPERAND_FORMAT => "%s";
287 use constant OPERAND_SEPARATOR => ", ";
288 use constant INSTRUCTION_SEPARATOR => "\n";
293 $str =~ s/[\000-\037\\\"\177-\377]/sprintf "\\%03o", ord $&/ge;
297 sub disassemble_function($$;$)
299 my ($progs, $func, $highlight) = @_;
301 print "$func->{debugname}:\n";
303 my $initializer = sub
306 my $g = $progs->{globals}[$ofs]{v};
310 elsif($g->{int} < 16777216)
312 print " = $g->{int}%";
313 if($g->{int} < length $progs->{strings} && $g->{int} > 0)
315 print " " . str($progs->{getstring}->($g->{int}));
320 print " = $g->{float}!";
324 printf INSTRUCTION_FORMAT, '', '', '.PARM_START';
325 printf OPERAND_FORMAT, "$func->{parm_start}";
326 print INSTRUCTION_SEPARATOR;
328 printf INSTRUCTION_FORMAT, '', '', '.LOCALS';
329 printf OPERAND_FORMAT, "$func->{locals}";
330 print INSTRUCTION_SEPARATOR;
332 my %override_locals = ();
333 my $p = $func->{parm_start};
334 for(0..($func->{numparms}-1))
336 $override_locals{$p} //= "argv[$_]";
337 for my $comp(0..($func->{parm_size}[$_]-1))
339 $override_locals{$p} //= "argv[$_][$comp]";
342 printf INSTRUCTION_FORMAT, '', '', '.ARG';
343 printf OPERAND_FORMAT, "argv[$_]";
344 print OPERAND_SEPARATOR;
345 printf OPERAND_FORMAT, $func->{parm_size}[$_];
346 print INSTRUCTION_SEPARATOR;
348 for($func->{parm_start}..($func->{parm_start} + $func->{locals} - 1))
351 if exists $override_locals{$_};
352 $override_locals{$_} = "<local>\@$_";
354 printf INSTRUCTION_FORMAT, '', '', '.LOCAL';
355 printf OPERAND_FORMAT, "<local>\@$_";
357 print INSTRUCTION_SEPARATOR;
364 return $override_locals{$ofs}
365 if exists $override_locals{$ofs};
366 my $def = $progs->{globaldef_byoffset}->($ofs);
367 return $def->{debugname};
372 my ($ip, $type, $operand) = @_;
373 if($type eq 'inglobal')
375 my $name = $getname->($operand);
376 printf OPERAND_FORMAT, "$name";
378 elsif($type eq 'outglobal')
380 my $name = $getname->($operand);
381 printf OPERAND_FORMAT, "&$name";
383 elsif($type eq 'inglobalvec')
385 my $name = $getname->($operand);
386 printf OPERAND_FORMAT, "$name\[\]";
388 elsif($type eq 'outglobalvec')
390 my $name = $getname->($operand);
391 printf OPERAND_FORMAT, "&$name\[\]";
393 elsif($type eq 'inglobalfunc')
395 my $name = $getname->($operand);
396 printf OPERAND_FORMAT, "$name()";
398 elsif($type eq 'ipoffset')
400 printf OPERAND_FORMAT, "@{[$ip + $operand]}" . sprintf ' ($%+d)', $operand;
404 die "unknown type: $type";
410 run_nfa $progs, $func->{first_statement}, "", id, id,
413 my ($ip, $state, $s, $c) = @_;
416 if(my $j = $c->{isjump})
418 my $t = $ip + $s->{$j};
419 $come_from{$t}{$ip} = $c->{isconditional};
424 for my $ip(sort { $a <=> $b } keys %statements)
426 if($ip == $func->{first_statement})
428 printf INSTRUCTION_FORMAT, $ip, '', '.ENTRY';
429 print INSTRUCTION_SEPARATOR;
431 if(defined $ipprev && $ip != $ipprev + 1)
433 printf INSTRUCTION_FORMAT, $ip, '', '.SKIP';
434 printf OPERAND_FORMAT, $ip - $ipprev - 1;
435 print INSTRUCTION_SEPARATOR;
437 if(my $cf = $come_from{$ip})
439 printf INSTRUCTION_FORMAT, $ip, '', '.XREF';
441 for(sort { $a <=> $b } keys %$cf)
443 print OPERAND_SEPARATOR
445 printf OPERAND_FORMAT, ($cf->{$_} ? 'c' : 'j') . $_ . sprintf ' ($%+d)', $_ - $ip;
447 print INSTRUCTION_SEPARATOR;
450 my $op = $progs->{statements}[$ip]{op};
451 my $ipt = $progs->{statements}[$ip];
452 my $opprop = checkop $op;
454 print PRE_MARK_STATEMENT
455 if $highlight and $highlight->{$ip};
457 my $showip = $opprop->{isjump};
458 printf INSTRUCTION_FORMAT, $showip ? $ip : '', $highlight->{$ip} ? "<!>" : "", $op;
464 if not defined $opprop->{$o};
465 print OPERAND_SEPARATOR
467 print PRE_MARK_OPERAND
468 if $highlight and $highlight->{$ip} and $highlight->{$ip}{$o};
469 $operand->($ip, $opprop->{$o}, $ipt->{$o});
470 print POST_MARK_OPERAND
471 if $highlight and $highlight->{$ip} and $highlight->{$ip}{$o};
474 print POST_MARK_STATEMENT
475 if $highlight and $highlight->{$ip};
477 print INSTRUCTION_SEPARATOR;
481 sub find_uninitialized_locals($$)
483 my ($progs, $func) = @_;
486 if $func->{first_statement} < 0; # builtin
488 print STDERR "Checking $func->{debugname}...\n";
490 my $p = $func->{parm_start};
491 for(0..($func->{numparms}-1))
493 $p += $func->{parm_size}[$_];
496 use constant WATCHME_R => 1;
497 use constant WATCHME_W => 2;
498 use constant WATCHME_X => 4;
499 use constant WATCHME_T => 8;
500 my %watchme = map { $_ => WATCHME_X } ($func->{parm_start} .. ($func->{parm_start} + $func->{locals} - 1));
502 for(keys %{$progs->{temps}})
504 $watchme{$_} = WATCHME_T | WATCHME_X
505 if not exists $watchme{$_};
508 run_nfa $progs, $func->{first_statement}, "", id, id,
511 my ($ip, $state, $s, $c) = @_;
516 unless defined $type;
519 if($type eq 'inglobal' || $type eq 'inglobalfunc')
521 $watchme{$ofs} |= WATCHME_R;
523 elsif($type eq 'inglobalvec')
525 $watchme{$ofs} |= WATCHME_R;
526 $watchme{$ofs+1} |= WATCHME_R;
527 $watchme{$ofs+2} |= WATCHME_R;
529 elsif($type eq 'outglobal')
531 $watchme{$ofs} |= WATCHME_W;
533 elsif($type eq 'outglobalvec')
535 $watchme{$ofs} |= WATCHME_W;
536 $watchme{$ofs+1} |= WATCHME_W;
537 $watchme{$ofs+2} |= WATCHME_W;
545 if ($watchme{$_} & (WATCHME_R | WATCHME_W | WATCHME_X)) != (WATCHME_R | WATCHME_W | WATCHME_X);
549 if not keys %watchme;
554 flags => $watchme{$_},
559 # mark parameters as initialized
560 for($func->{parm_start} .. ($p-1))
562 $watchme{$_}{valid} = 1
563 if defined $watchme{$_};
565 # an initial run of STORE instruction is for receiving extra parameters
566 # (beyond 8). Only possible if the function is declared as having 8 params.
567 # Extra parameters behave otherwise like temps, but are initialized at
569 for($func->{first_statement} .. (@{$progs->{statements}}-1))
571 my $s = $progs->{statements}[$_];
572 if($s->{op} eq 'STORE_V')
574 $watchme{$s->{a}}{valid} = 1
575 if defined $watchme{$s->{a}};
576 $watchme{$s->{a}+1}{valid} = 1
577 if defined $watchme{$s->{a}+1};
578 $watchme{$s->{a}+2}{valid} = 1
579 if defined $watchme{$s->{a}+2};
581 elsif($s->{op} =~ /^STORE_/)
583 $watchme{$s->{a}}{valid} = 1
584 if defined $watchme{$s->{a}};
593 run_nfa $progs, $func->{first_statement}, \%watchme,
596 return { map { $_ => { %{$h->{$_}} } } keys %$h };
600 return join ' ', map { $h->{$_}->{valid}; } sort keys %$h;
603 my ($ip, $state, $s, $c) = @_;
609 unless defined $type;
617 if not exists $state->{$ofs};
618 my $valid = $state->{$ofs}{valid};
621 print "; Use of uninitialized value $ofs in $func->{debugname} at $ip.$_\n";
626 print "; Use of temporary $ofs across CALL in $func->{debugname} at $ip.$_\n";
633 $state->{$ofs}{valid} = 1
634 if exists $state->{$ofs};
637 if($type eq 'inglobal' || $type eq 'inglobalfunc')
639 if($op ne 'OR' && $op ne 'AND') # fteqcc logicops cause this
644 elsif($type eq 'inglobalvec')
646 if($op ne 'OR' && $op ne 'AND') # fteqcc logicops cause this
653 elsif($type eq 'outglobal')
657 elsif($type eq 'outglobalvec')
666 # builtin calls may clobber stuff
668 my $funcid = $progs->{globals}[$func]{v}{int};
669 my $first_statement = $progs->{functions}[$funcid]{first_statement};
670 if($first_statement >= 0)
675 if($_->{flags} & WATCHME_T)
684 disassemble_function($progs, $func, \%warned)
688 use constant DEFAULTGLOBALS => [
722 if($ofs < @{(DEFAULTGLOBALS)})
724 return { ofs => $ofs, s_name => undef, debugname => DEFAULTGLOBALS->[$ofs], type => undef };
726 return { ofs => $ofs, s_name => undef, debugname => "<undefined>\@$ofs", type => undef };
735 print STDERR "Parsing header...\n";
736 $p{header} = parse_section $fh, DPROGRAMS_T, 0, undef, 1;
738 print STDERR "Parsing strings...\n";
739 $p{strings} = get_section $fh, $p{header}{ofs_strings}, $p{header}{numstrings};
743 my $endpos = index $p{strings}, "\0", $startpos;
744 return substr $p{strings}, $startpos, $endpos - $startpos;
747 print STDERR "Parsing statements...\n";
748 $p{statements} = [parse_section $fh, DSTATEMENT_T, $p{header}{ofs_statements}, undef, $p{header}{numstatements}];
750 print STDERR "Parsing globaldefs...\n";
751 $p{globaldefs} = [parse_section $fh, DDEF_T, $p{header}{ofs_globaldefs}, undef, $p{header}{numglobaldefs}];
753 print STDERR "Parsing fielddefs...\n";
754 $p{fielddefs} = [parse_section $fh, DDEF_T, $p{header}{ofs_fielddefs}, undef, $p{header}{numfielddefs}];
756 print STDERR "Parsing globals...\n";
757 $p{globals} = [parse_section $fh, DGLOBAL_T, $p{header}{ofs_globals}, undef, $p{header}{numglobals}];
759 print STDERR "Parsing functions...\n";
760 $p{functions} = [parse_section $fh, DFUNCTION_T, $p{header}{ofs_functions}, undef, $p{header}{numfunctions}];
762 print STDERR "Detecting temps...\n";
763 my %offsets_saved = ();
764 for(@{$p{globaldefs}})
766 my $type = $_->{type};
767 my $name = $p{getstring}->($_->{s_name});
769 unless $type->{save} or $name ne "";
770 for my $i(0..(typesize($_->{type}{type})-1))
772 ++$offsets_saved{$_->{ofs}+$i};
775 my %offsets_initialized = ();
776 for(0..(@{$p{globals}}-1))
778 if($p{globals}[$_]{v}{int})
780 ++$offsets_initialized{$_};
785 for(0..(@{$p{globals}}-1))
788 if $_ < @{(DEFAULTGLOBALS)};
790 if !$offsets_saved{$_} and $offsets_initialized{$_};
792 if !$offsets_saved{$_} and !$offsets_initialized{$_};
794 $p{temps} = \%istemp;
795 $p{consts} = \%isconst;
797 print STDERR "Naming...\n";
801 for(@{$p{globaldefs}})
803 $_->{debugname} = $p{getstring}->($_->{s_name});
805 for(@{$p{globaldefs}})
807 $globaldefs[$_->{ofs}] //= $_
808 if $_->{debugname} ne "";
810 for(@{$p{globaldefs}})
812 $globaldefs[$_->{ofs}] //= $_;
814 for(0..(@{$p{globals}}-1))
816 $globaldefs[$_] //= { ofs => $_, s_name => undef, debugname => ($istemp{$_} ? "<temp>" : $isconst{$_} ? "<const>" : "<nodef>") . "\@$_" },
819 for(@{$p{globaldefs}})
821 $_->{debugname} = "<anon>\@$_->{ofs}"
822 if $_->{debugname} eq "";
823 ++$globaldefs{$_->{debugname}};
825 for(@{$p{globaldefs}})
828 if $globaldefs{$_->{debugname}} <= 1;
829 $_->{debugname} .= "\@$_->{ofs}";
831 $p{globaldef_byoffset} = sub
835 if($ofs >= 0 && $ofs < @{(DEFAULTGLOBALS)})
837 return { ofs => $ofs, s_name => undef, debugname => DEFAULTGLOBALS->[$ofs], type => undef };
839 my $def = $globaldefs[$ofs];
845 for(@{$p{functions}})
847 my $file = $p{getstring}->($_->{s_file});
848 my $name = $p{getstring}->($_->{s_name});
849 $name = "$file:$name"
851 $_->{debugname} = $name;
852 $functions{$_->{first_statement}} = $_;
854 $p{function_byoffset} = sub
857 return $functions{$ofs};
860 # what do we want to do?
861 my $checkfunc = \&find_uninitialized_locals;
862 #my $checkfunc = \&disassemble_function;
863 for(sort { $a->{debugname} cmp $b->{debugname} } @{$p{functions}})
865 $checkfunc->(\%p, $_);
869 open my $fh, '<', $ARGV[0];