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 if($func->{parm_size}[$_] <= 1)
338 $override_locals{$p} //= "argv[$_]";
340 for my $comp(0..($func->{parm_size}[$_]-1))
342 $override_locals{$p} //= "argv[$_][$comp]";
345 printf INSTRUCTION_FORMAT, '', '', '.ARG';
346 printf OPERAND_FORMAT, "argv[$_]";
347 print OPERAND_SEPARATOR;
348 printf OPERAND_FORMAT, $func->{parm_size}[$_];
349 print INSTRUCTION_SEPARATOR;
351 for($func->{parm_start}..($func->{parm_start} + $func->{locals} - 1))
354 if exists $override_locals{$_};
355 $override_locals{$_} = "<local>\@$_";
357 printf INSTRUCTION_FORMAT, '', '', '.LOCAL';
358 printf OPERAND_FORMAT, "<local>\@$_";
360 print INSTRUCTION_SEPARATOR;
366 return $override_locals{$ofs}
367 if exists $override_locals{$ofs};
368 return $progs->{globaldef_byoffset}->($ofs)->{debugname};
373 my ($ip, $type, $operand) = @_;
374 if($type eq 'inglobal')
376 my $name = $getname->($operand);
377 printf OPERAND_FORMAT, "$name";
379 elsif($type eq 'outglobal')
381 my $name = $getname->($operand);
382 printf OPERAND_FORMAT, "&$name";
384 elsif($type eq 'inglobalvec')
386 my $name = $getname->($operand);
387 printf OPERAND_FORMAT, "$name\[\]";
389 elsif($type eq 'outglobalvec')
391 my $name = $getname->($operand);
392 printf OPERAND_FORMAT, "&$name\[\]";
394 elsif($type eq 'inglobalfunc')
396 my $name = $getname->($operand);
397 printf OPERAND_FORMAT, "$name()";
399 elsif($type eq 'ipoffset')
401 printf OPERAND_FORMAT, "@{[$ip + $operand]}" . sprintf ' ($%+d)', $operand;
405 die "unknown type: $type";
411 run_nfa $progs, $func->{first_statement}, "", id, id,
414 my ($ip, $state, $s, $c) = @_;
417 if(my $j = $c->{isjump})
419 my $t = $ip + $s->{$j};
420 $come_from{$t}{$ip} = $c->{isconditional};
425 for my $ip(sort { $a <=> $b } keys %statements)
427 if($ip == $func->{first_statement})
429 printf INSTRUCTION_FORMAT, $ip, '', '.ENTRY';
430 print INSTRUCTION_SEPARATOR;
432 if(defined $ipprev && $ip != $ipprev + 1)
434 printf INSTRUCTION_FORMAT, $ip, '', '.SKIP';
435 printf OPERAND_FORMAT, $ip - $ipprev - 1;
436 print INSTRUCTION_SEPARATOR;
438 if(my $cf = $come_from{$ip})
440 printf INSTRUCTION_FORMAT, $ip, '', '.XREF';
442 for(sort { $a <=> $b } keys %$cf)
444 print OPERAND_SEPARATOR
446 printf OPERAND_FORMAT, ($cf->{$_} ? 'c' : 'j') . $_ . sprintf ' ($%+d)', $_ - $ip;
448 print INSTRUCTION_SEPARATOR;
451 my $op = $progs->{statements}[$ip]{op};
452 my $ipt = $progs->{statements}[$ip];
453 my $opprop = checkop $op;
455 print PRE_MARK_STATEMENT
456 if $highlight and $highlight->{$ip};
458 my $showip = $opprop->{isjump};
459 printf INSTRUCTION_FORMAT, $showip ? $ip : '', $highlight->{$ip} ? "<!>" : "", $op;
465 if not defined $opprop->{$o};
466 print OPERAND_SEPARATOR
468 print PRE_MARK_OPERAND
469 if $highlight and $highlight->{$ip} and $highlight->{$ip}{$o};
470 $operand->($ip, $opprop->{$o}, $ipt->{$o});
471 print POST_MARK_OPERAND
472 if $highlight and $highlight->{$ip} and $highlight->{$ip}{$o};
475 print POST_MARK_STATEMENT
476 if $highlight and $highlight->{$ip};
478 print INSTRUCTION_SEPARATOR;
482 sub find_uninitialized_locals($$)
484 my ($progs, $func) = @_;
487 if $func->{first_statement} < 0; # builtin
489 print STDERR "Checking $func->{debugname}...\n";
491 my $p = $func->{parm_start};
492 for(0..($func->{numparms}-1))
494 $p += $func->{parm_size}[$_];
497 use constant WATCHME_R => 1;
498 use constant WATCHME_W => 2;
499 use constant WATCHME_X => 4;
500 use constant WATCHME_T => 8;
501 my %watchme = map { $_ => WATCHME_X } ($p .. ($func->{parm_start} + $func->{locals} - 1));
505 $watchme{$_} = WATCHME_T | WATCHME_X
506 if not exists $watchme{$_};
509 run_nfa $progs, $func->{first_statement}, "", id, id,
512 my ($ip, $state, $s, $c) = @_;
517 unless defined $type;
520 if($type eq 'inglobal' || $type eq 'inglobalfunc')
522 $watchme{$ofs} |= WATCHME_R;
524 elsif($type eq 'inglobalvec')
526 $watchme{$ofs} |= WATCHME_R;
527 $watchme{$ofs+1} |= WATCHME_R;
528 $watchme{$ofs+2} |= WATCHME_R;
530 elsif($type eq 'outglobal')
532 $watchme{$ofs} |= WATCHME_W;
534 elsif($type eq 'outglobalvec')
536 $watchme{$ofs} |= WATCHME_W;
537 $watchme{$ofs+1} |= WATCHME_W;
538 $watchme{$ofs+2} |= WATCHME_W;
546 if ($watchme{$_} & (WATCHME_R | WATCHME_W | WATCHME_X)) != (WATCHME_R | WATCHME_W | WATCHME_X);
550 if not keys %watchme;
554 $watchme{$_} = { flags => $watchme{$_}, valid => 0 };
558 run_nfa $progs, $func->{first_statement}, \%watchme,
561 return { map { $_ => { %{$h->{$_}} } } keys %$h };
565 return join ' ', map { $h->{$_}->{valid}; } sort keys %$h;
568 my ($ip, $state, $s, $c) = @_;
574 unless defined $type;
582 if not exists $state->{$ofs};
583 my $valid = $state->{$ofs}{valid};
586 print "; Use of uninitialized value $ofs in $func->{debugname} at $ip.$_\n";
591 print "; Use of temporary $ofs across CALL in $func->{debugname} at $ip.$_\n";
598 $state->{$ofs}{valid} = 1
599 if exists $state->{$ofs};
602 if($type eq 'inglobal' || $type eq 'inglobalfunc')
604 if($op ne 'OR' && $op ne 'AND') # fteqcc logicops cause this
609 elsif($type eq 'inglobalvec')
611 if($op ne 'OR' && $op ne 'AND') # fteqcc logicops cause this
618 elsif($type eq 'outglobal')
622 elsif($type eq 'outglobalvec')
634 if($_->{flags} & WATCHME_T)
642 disassemble_function($progs, $func, \%warned)
646 use constant DEFAULTGLOBALS => [
680 if($ofs < @{(DEFAULTGLOBALS)})
682 return { ofs => $ofs, s_name => undef, debugname => DEFAULTGLOBALS->[$ofs], type => undef };
684 return { ofs => $ofs, s_name => undef, debugname => "<undefined>\@$ofs", type => undef };
693 print STDERR "Parsing header...\n";
694 $p{header} = parse_section $fh, DPROGRAMS_T, 0, undef, 1;
696 print STDERR "Parsing strings...\n";
697 $p{strings} = get_section $fh, $p{header}{ofs_strings}, $p{header}{numstrings};
701 my $endpos = index $p{strings}, "\0", $startpos;
702 return substr $p{strings}, $startpos, $endpos - $startpos;
705 print STDERR "Parsing statements...\n";
706 $p{statements} = [parse_section $fh, DSTATEMENT_T, $p{header}{ofs_statements}, undef, $p{header}{numstatements}];
708 print STDERR "Parsing globaldefs...\n";
709 $p{globaldefs} = [parse_section $fh, DDEF_T, $p{header}{ofs_globaldefs}, undef, $p{header}{numglobaldefs}];
711 print STDERR "Parsing fielddefs...\n";
712 $p{fielddefs} = [parse_section $fh, DDEF_T, $p{header}{ofs_fielddefs}, undef, $p{header}{numfielddefs}];
714 print STDERR "Parsing globals...\n";
715 $p{globals} = [parse_section $fh, DGLOBAL_T, $p{header}{ofs_globals}, undef, $p{header}{numglobals}];
717 print STDERR "Parsing functions...\n";
718 $p{functions} = [parse_section $fh, DFUNCTION_T, $p{header}{ofs_functions}, undef, $p{header}{numfunctions}];
720 print STDERR "Detecting temps...\n";
721 my %offsets_saved = ();
722 for(@{$p{globaldefs}})
725 unless $_->{type}{save};
727 unless $p{getstring}->($_->{s_name}) eq "";
728 for my $i(0..(typesize($_->{type}{type})-1))
730 ++$offsets_saved{$_->{ofs}+$i};
734 for(0..(@{$p{globals}}-1))
737 if $offsets_saved{$_};
740 $p{temps} = [keys %istemp];
742 print STDERR "Naming...\n";
746 for(@{$p{globaldefs}})
748 $_->{debugname} = $p{getstring}->($_->{s_name});
750 for(@{$p{globaldefs}})
752 $globaldefs[$_->{ofs}] //= $_
753 if $_->{debugname} ne "";
755 for(@{$p{globaldefs}})
757 $globaldefs[$_->{ofs}] //= $_;
759 for(0..(@{$p{globals}}-1))
761 $globaldefs[$_] //= { ofs => $_, s_name => undef, debugname => ($istemp{$_} ? "<temp>" : "<nodef>") . "\@$_" },
764 for(@{$p{globaldefs}})
766 $_->{debugname} = "<anon>\@$_->{ofs}"
767 if $_->{debugname} eq "";
768 ++$globaldefs{$_->{debugname}};
770 for(@{$p{globaldefs}})
773 if $globaldefs{$_->{debugname}} <= 1;
774 $_->{debugname} .= "\@$_->{ofs}";
776 $p{globaldef_byoffset} = sub
779 if($ofs < @{(DEFAULTGLOBALS)})
781 return { ofs => $ofs, s_name => undef, debugname => DEFAULTGLOBALS->[$ofs], type => undef };
783 my $def = $globaldefs[$ofs];
788 for(@{$p{functions}})
790 my $file = $p{getstring}->($_->{s_file});
791 my $name = $p{getstring}->($_->{s_name});
792 $name = "$file:$name"
794 $_->{debugname} = $name;
795 $functions{$_->{first_statement}} = $_;
797 $p{function_byoffset} = sub
800 return $functions{$ofs};
803 # what do we want to do?
804 my $checkfunc = \&find_uninitialized_locals;
805 for(sort { $a->{debugname} cmp $b->{debugname} } @{$p{functions}})
807 $checkfunc->(\%p, $_);
811 open my $fh, '<', $ARGV[0];