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
43 return { a => 'inglobalvec', b => 'immediate', isjump => 'b', isconditional => 1 };
47 return { a => 'inglobal', b => 'immediate', isjump => 'b', isconditional => 1 };
51 return { a => 'immediate', isjump => 'a', isconditional => 0 };
53 if($op =~ /^ADD_V$|^SUB_V$/)
55 return { a => 'inglobalvec', b => 'inglobalvec', c => 'outglobalvec' };
57 if($op =~ /^MUL_V$|^EQ_V$|^NE_V$/)
59 return { a => 'inglobalvec', b => 'inglobalvec', c => 'outglobal' };
63 return { a => 'inglobal', b => 'inglobalvec', c => 'outglobalvec' };
67 return { a => 'inglobalvec', b => 'inglobal', c => 'outglobalvec' };
71 return { a => 'inglobal', b => 'inglobal', c => 'outglobalvec' };
75 return { a => 'inglobalvec', c => 'outglobal' };
79 return { a => 'inglobal', c => 'outglobal' };
83 return { a => 'inglobalvec', b => 'inglobal' };
87 return { a => 'inglobalvec', b => 'outglobalvec' };
91 return { a => 'inglobal', b => 'inglobal' };
95 return { a => 'inglobal', b => 'outglobal' };
99 return { a => 'inglobalfunc', iscall => 1 };
101 if($op =~ /^DONE|^RETURN/)
103 return { a => 'inglobal', isreturn => 1 };
105 return { a => 'inglobal', b => 'inglobal', c => 'outglobal' };
108 use constant TYPES => {
109 int => ['V', 4, signed 32],
110 ushort => ['v', 2, id],
111 short => ['v', 2, signed 16],
112 opcode => ['v', 2, sub { OPCODE_E->[$_[0]] or die "Invalid opcode: $_[0]"; }],
113 float => ['f', 4, id],
114 uchar8 => ['a8', 8, sub { [unpack 'C8', $_[0]] }],
115 global => ['i', 4, sub { { int => $_[0], float => unpack "f", pack "L", $_[0] }; }],
118 use constant DPROGRAMS_T => [
121 [int => 'ofs_statements'],
122 [int => 'numstatements'],
123 [int => 'ofs_globaldefs'],
124 [int => 'numglobaldefs'],
125 [int => 'ofs_fielddefs'],
126 [int => 'numfielddefs'],
127 [int => 'ofs_functions'],
128 [int => 'numfunctions'],
129 [int => 'ofs_strings'],
130 [int => 'numstrings'],
131 [int => 'ofs_globals'],
132 [int => 'numglobals'],
133 [int => 'entityfields']
136 use constant DSTATEMENT_T => [
143 use constant DDEF_T => [
149 use constant DGLOBAL_T => [
153 use constant DFUNCTION_T => [
154 [int => 'first_statement'],
155 [int => 'parm_start'],
161 [uchar8 => 'parm_size'],
166 my ($fh, $start, $len) = @_;
169 $len == read $fh, my $buf, $len
174 sub parse_section($$$$$)
176 my ($fh, $struct, $start, $len, $cnt) = @_;
179 $itemlen += TYPES->{$_->[0]}->[1]
181 my $packspec = join '', map { TYPES->{$_->[0]}->[0]; } @$struct;
182 my @packnames = map { $_->[1]; } @$struct;
184 $len = $cnt * $itemlen
185 if not defined $len and defined $cnt;
186 $cnt = int($len / $itemlen)
187 if not defined $cnt and defined $len;
188 die "Invalid length specification"
189 unless defined $len and defined $cnt and $len == $cnt * $itemlen;
190 die "Invalid length specification in scalar context"
191 unless wantarray or $cnt == 1;
197 $itemlen == read $fh, my $buf, $itemlen
200 @h{@packnames} = unpack $packspec, $buf;
201 $h{$_->[1]} = TYPES->{$_->[0]}->[2]->($h{$_->[1]})
211 use constant PRE_MARK_STATEMENT => "\e[1m";
212 use constant POST_MARK_STATEMENT => "\e[m";
213 use constant PRE_MARK_OPERAND => "\e[41m";
214 use constant POST_MARK_OPERAND => "\e[49m";
216 use constant INSTRUCTION_FORMAT => "%8s %3s | %-12s ";
217 use constant OPERAND_FORMAT => "%s";
218 use constant OPERAND_SEPARATOR => ", ";
219 use constant INSTRUCTION_SEPARATOR => "\n";
224 $str =~ s/[\000-\037\\\"\177-\377]/sprintf "\\%03o", ord $&/ge;
228 sub disassemble_function($$;$)
230 my ($progs, $func, $highlight) = @_;
232 print "$func->{debugname}:\n";
234 my $initializer = sub
237 my $g = $progs->{globals}[$ofs]{v};
241 elsif($g->{int} < 16777216)
243 print " = $g->{int}%";
244 if($g->{int} < length $progs->{strings} && $g->{int} > 0)
246 print " " . str($progs->{getstring}->($g->{int}));
251 print " = $g->{float}!";
255 printf INSTRUCTION_FORMAT, '', '', '.PARM_START';
256 printf OPERAND_FORMAT, "$func->{parm_start}";
257 print INSTRUCTION_SEPARATOR;
259 printf INSTRUCTION_FORMAT, '', '', '.LOCALS';
260 printf OPERAND_FORMAT, "$func->{locals}";
261 print INSTRUCTION_SEPARATOR;
263 my %override_locals = ();
264 my $p = $func->{parm_start};
265 for(0..($func->{numparms}-1))
267 if($func->{parm_size}[$_] <= 1)
269 $override_locals{$p} //= "argv[$_]";
271 for my $comp(0..($func->{parm_size}[$_]-1))
273 $override_locals{$p} //= "argv[$_][$comp]";
276 printf INSTRUCTION_FORMAT, '', '', '.ARG';
277 printf OPERAND_FORMAT, "argv[$_]";
278 print OPERAND_SEPARATOR;
279 printf OPERAND_FORMAT, $func->{parm_size}[$_];
280 print INSTRUCTION_SEPARATOR;
282 for($func->{parm_start}..($func->{parm_start} + $func->{locals} - 1))
285 if exists $override_locals{$_};
286 $override_locals{$_} = "<local>\@$_";
288 printf INSTRUCTION_FORMAT, '', '', '.LOCAL';
289 printf OPERAND_FORMAT, "<local>\@$_";
291 print INSTRUCTION_SEPARATOR;
297 return $override_locals{$ofs}
298 if exists $override_locals{$ofs};
299 return $progs->{globaldef_byoffset}->($ofs)->{debugname};
304 my ($type, $operand) = @_;
305 if($type eq 'inglobal')
307 my $name = $getname->($operand);
308 printf OPERAND_FORMAT, "$name";
310 elsif($type eq 'outglobal')
312 my $name = $getname->($operand);
313 printf OPERAND_FORMAT, "&$name";
315 elsif($type eq 'inglobalvec')
317 my $name = $getname->($operand);
318 printf OPERAND_FORMAT, "$name\[\]";
320 elsif($type eq 'outglobalvec')
322 my $name = $getname->($operand);
323 printf OPERAND_FORMAT, "&$name\[\]";
325 elsif($type eq 'inglobalfunc')
327 my $name = $getname->($operand);
328 printf OPERAND_FORMAT, "$name()";
330 elsif($type eq 'immediate')
332 printf OPERAND_FORMAT, "$operand";
336 die "unknown type: $type";
340 for my $s($func->{first_statement}..(@{$progs->{statements}}-1))
342 my $op = $progs->{statements}[$s]{op};
343 my $st = $progs->{statements}[$s];
344 my $opprop = checkop $op;
346 print PRE_MARK_STATEMENT
347 if $highlight and $highlight->{$s};
349 printf INSTRUCTION_FORMAT, $s, $highlight->{$s} ? "<!>" : "", $op;
355 if not defined $opprop->{$o};
356 print OPERAND_SEPARATOR
358 print PRE_MARK_OPERAND
359 if $highlight and $highlight->{$s} and $highlight->{$s}{$o};
360 $operand->($opprop->{$o}, $st->{$o});
361 print POST_MARK_OPERAND
362 if $highlight and $highlight->{$s} and $highlight->{$s}{$o};
365 print POST_MARK_STATEMENT
366 if $highlight and $highlight->{$s};
368 print INSTRUCTION_SEPARATOR;
370 last if $progs->{function_byoffset}->($s + 1);
376 my ($progs, $ip, $state, $copy_handler, $state_hasher, $instruction_handler) = @_;
379 my $statements = $progs->{statements};
384 no warnings 'recursion';
386 my ($ip, $state) = @_;
390 my $statestr = $state_hasher->($state);
392 if $seen{"$ip:$statestr"}++;
394 my $s = $statements->[$ip];
395 my $c = checkop $s->{op};
397 $instruction_handler->($ip, $state, $s, $c);
405 if($c->{isconditional})
407 $nfa->($ip+1, $copy_handler->($state));
408 $ip += $s->{$c->{isjump}};
412 $ip += $s->{$c->{isjump}};
422 $nfa->($ip, $copy_handler->($state));
425 sub find_uninitialized_locals($$)
427 my ($progs, $func) = @_;
430 if $func->{first_statement} < 0; # builtin
432 print STDERR "Checking $func->{debugname}...\n";
434 my $p = $func->{parm_start};
435 for(0..($func->{numparms}-1))
437 $p += $func->{parm_size}[$_];
440 use constant WATCHME_R => 1;
441 use constant WATCHME_W => 2;
442 use constant WATCHME_X => 4;
443 use constant WATCHME_T => 8;
444 my %watchme = map { $_ => WATCHME_X } ($p .. ($func->{parm_start} + $func->{locals} - 1));
446 # TODO mark temp globals as WATCHME_T
448 run_nfa $progs, $func->{first_statement}, "", sub { $_[0] }, sub { $_[0] },
451 my ($ip, $state, $s, $c) = @_;
456 unless defined $type;
459 if($type eq 'inglobal' || $type eq 'inglobalfunc')
461 $watchme{$ofs} |= WATCHME_R;
463 elsif($type eq 'inglobalvec')
465 $watchme{$ofs} |= WATCHME_R;
466 $watchme{$ofs+1} |= WATCHME_R;
467 $watchme{$ofs+2} |= WATCHME_R;
469 elsif($type eq 'outglobal')
471 $watchme{$ofs} |= WATCHME_W;
473 elsif($type eq 'outglobalvec')
475 $watchme{$ofs} |= WATCHME_W;
476 $watchme{$ofs+1} |= WATCHME_W;
477 $watchme{$ofs+2} |= WATCHME_W;
486 ($watchme{$_} & (WATCHME_T | WATCHME_X)) == 0
488 ($watchme{$_} & (WATCHME_R | WATCHME_W)) != (WATCHME_R | WATCHME_W);
492 if not keys %watchme;
496 $watchme{$_} = { flags => $watchme{$_}, valid => 0 };
500 run_nfa $progs, $func->{first_statement}, \%watchme,
503 return { map { $_ => { %{$h->{$_}} } } keys %$h };
507 return join ' ', map { $h->{$_}->{valid}; } sort keys %$h;
510 my ($ip, $state, $s, $c) = @_;
516 unless defined $type;
520 if($type eq 'inglobal' || $type eq 'inglobalfunc')
522 if($op ne 'OR' && $op ne 'AND') # fteqcc logicops cause this
524 if($state->{$ofs} && !$state->{$ofs}{valid})
526 print "; Use of uninitialized local $ofs in $func->{debugname} at $ip.$_\n";
531 elsif($type eq 'inglobalvec')
533 if($op ne 'OR' && $op ne 'AND') # fteqcc logicops cause this
536 $state->{$ofs} && !$state->{$ofs}{valid}
538 $state->{$ofs+1} && !$state->{$ofs+1}{valid}
540 $state->{$ofs+2} && !$state->{$ofs+2}{valid}
543 print "; Use of uninitialized local $ofs in $func->{debugname} at $ip.$_\n";
548 elsif($type eq 'outglobal')
550 $state->{$ofs}{valid} = 1
553 elsif($type eq 'outglobalvec')
555 $state->{$ofs}{valid} = 1
557 $state->{$ofs+1}{valid} = 1
559 $state->{$ofs+2}{valid} = 1
565 disassemble_function($progs, $func, \%warned)
569 use constant DEFAULTGLOBALS => [
603 if($ofs < @{(DEFAULTGLOBALS)})
605 return { ofs => $ofs, s_name => undef, debugname => DEFAULTGLOBALS->[$ofs], type => undef };
607 return { ofs => $ofs, s_name => undef, debugname => "<undefined>\@$ofs", type => undef };
616 print STDERR "Parsing header...\n";
617 $p{header} = parse_section $fh, DPROGRAMS_T, 0, undef, 1;
619 print STDERR "Parsing strings...\n";
620 $p{strings} = get_section $fh, $p{header}{ofs_strings}, $p{header}{numstrings};
622 print STDERR "Parsing statements...\n";
623 $p{statements} = [parse_section $fh, DSTATEMENT_T, $p{header}{ofs_statements}, undef, $p{header}{numstatements}];
625 print STDERR "Parsing globaldefs...\n";
626 $p{globaldefs} = [parse_section $fh, DDEF_T, $p{header}{ofs_globaldefs}, undef, $p{header}{numglobaldefs}];
628 print STDERR "Parsing fielddefs...\n";
629 $p{fielddefs} = [parse_section $fh, DDEF_T, $p{header}{ofs_fielddefs}, undef, $p{header}{numfielddefs}];
631 print STDERR "Parsing globals...\n";
632 $p{globals} = [parse_section $fh, DGLOBAL_T, $p{header}{ofs_globals}, undef, $p{header}{numglobals}];
634 print STDERR "Parsing functions...\n";
635 $p{functions} = [parse_section $fh, DFUNCTION_T, $p{header}{ofs_functions}, undef, $p{header}{numfunctions}];
637 print STDERR "Providing helpers...\n";
641 my $endpos = index $p{strings}, "\0", $startpos;
642 return substr $p{strings}, $startpos, $endpos - $startpos;
645 print STDERR "Naming...\n";
649 for(@{$p{globaldefs}})
651 $_->{debugname} = $p{getstring}->($_->{s_name});
653 for(@{$p{globaldefs}})
656 unless $_->{debugname};
657 if(!defined $globaldefs[$_->{ofs}] || length $globaldefs[$_->{ofs}]->{debugname} < length $_->{debugname})
659 $globaldefs[$_->{ofs}] = $_;
663 for(@{$p{globaldefs}})
665 $_->{debugname} = "<anon>\@$_->{ofs}"
666 if $_->{debugname} eq "";
667 ++$globaldefs{$_->{debugname}};
669 for(@{$p{globaldefs}})
672 if $globaldefs{$_->{debugname}} <= 1;
673 $_->{debugname} .= "\@$_->{ofs}";
675 $p{globaldef_byoffset} = sub
678 my $def = $globaldefs[$ofs]
679 or return defaultglobal $_[0];
684 for(@{$p{functions}})
686 my $file = $p{getstring}->($_->{s_file});
687 my $name = $p{getstring}->($_->{s_name});
688 $name = "$file:$name"
690 $_->{debugname} = $name;
691 $functions{$_->{first_statement}} = $_;
693 $p{function_byoffset} = sub
696 return $functions{$ofs};
699 # what do we want to do?
700 my $checkfunc = \&find_uninitialized_locals;
701 for(sort { $a->{debugname} cmp $b->{debugname} } @{$p{functions}})
703 $checkfunc->(\%p, $_);
707 open my $fh, '<', $ARGV[0];