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);
374 sub find_uninitialized_locals($$)
376 my ($progs, $func) = @_;
378 no warnings 'recursion';
382 my %instructions_seen;
383 my $checkinstruction;
384 $checkinstruction = sub
386 my ($ip, $watchlist) = @_;
389 my $statestr = join ' ', map { $watchlist->{$_}->{valid}; } sort keys %$watchlist;
391 if $instructions_seen{"$ip $statestr"}++;
392 my %s = %{$progs->{statements}[$ip]};
393 my %c = %{checkop $s{op}};
400 elsif($c{$_} eq 'inglobal' || $c{$_} eq 'inglobalfunc')
402 if($s{op} ne 'OR' && $s{op} ne 'AND') # fteqcc logicops cause this
404 if($watchlist->{$x} && !$watchlist->{$x}{valid})
406 print "; Use of uninitialized local $x in $func->{debugname} at $ip.$_\n";
411 elsif($c{$_} eq 'inglobalvec')
413 if($s{op} ne 'OR' && $s{op} ne 'AND') # fteqcc logicops cause this
416 $watchlist->{$x} && !$watchlist->{$x}{valid}
418 $watchlist->{$x+1} && !$watchlist->{$x+1}{valid}
420 $watchlist->{$x+2} && !$watchlist->{$x+2}{valid}
423 print "; Use of uninitialized local $x in $func->{debugname} at $ip.$_\n";
428 elsif($c{$_} eq 'outglobal')
430 $watchlist->{$x}{valid} = 1
433 elsif($c{$_} eq 'outglobalvec')
435 $watchlist->{$x}{valid} = 1
437 $watchlist->{$x+1}{valid} = 1
438 if $watchlist->{$x+1};
439 $watchlist->{$x+2}{valid} = 1
440 if $watchlist->{$x+2};
442 elsif($c{$_} eq 'immediate')
453 if($c{isconditional})
455 $checkinstruction->($ip+1, { map { $_ => { %{$watchlist->{$_}} } } keys %$watchlist });
456 $ip += $s{$c{isjump}};
460 $ip += $s{$c{isjump}};
471 if $func->{first_statement} < 0; # builtin
474 print STDERR "Checking $func->{debugname}...\n";
476 my $p = $func->{parm_start};
477 for(0..($func->{numparms}-1))
479 $p += $func->{parm_size}[$_];
482 use constant WATCHME_R => 1;
483 use constant WATCHME_W => 2;
484 use constant WATCHME_X => 4;
485 use constant WATCHME_T => 8;
486 my %watchme = map { $_ => WATCHME_X } ($p .. ($func->{parm_start} + $func->{locals} - 1));
487 # TODO mark temp globals as WATCHME_T
490 $fixinitialstate = sub
496 if $instructions_seen{$ip}++;
497 my %s = %{$progs->{statements}[$ip]};
498 my %c = %{checkop $s{op}};
504 elsif($c{$_} eq 'inglobal' || $c{$_} eq 'inglobalfunc')
506 $watchme{$s{$_}} |= WATCHME_R;
508 elsif($c{$_} eq 'inglobalvec')
510 $watchme{$s{$_}} |= WATCHME_R;
511 $watchme{$s{$_}+1} |= WATCHME_R;
512 $watchme{$s{$_}+2} |= WATCHME_R;
514 elsif($c{$_} eq 'outglobal')
516 $watchme{$s{$_}} |= WATCHME_W;
518 elsif($c{$_} eq 'outglobalvec')
520 $watchme{$s{$_}} |= WATCHME_W;
521 $watchme{$s{$_}+1} |= WATCHME_W;
522 $watchme{$s{$_}+2} |= WATCHME_W;
524 elsif($c{$_} eq 'immediate')
535 if($c{isconditional})
537 $fixinitialstate->($ip+1);
538 $ip += $s{$c{isjump}};
542 $ip += $s{$c{isjump}};
551 %instructions_seen = ();
552 $fixinitialstate->($func->{first_statement});
558 ($watchme{$_} & (WATCHME_T | WATCHME_X)) == 0
560 ($watchme{$_} & (WATCHME_R | WATCHME_W)) != (WATCHME_R | WATCHME_W);
564 if not keys %watchme;
568 $watchme{$_} = { flags => $watchme{$_}, valid => 0 };
571 %instructions_seen = ();
572 $checkinstruction->($func->{first_statement}, \%watchme);
573 disassemble_function($progs, $func, \%warned)
577 use constant DEFAULTGLOBALS => [
611 if($ofs < @{(DEFAULTGLOBALS)})
613 return { ofs => $ofs, s_name => undef, debugname => DEFAULTGLOBALS->[$ofs], type => undef };
615 return { ofs => $ofs, s_name => undef, debugname => "<undefined>\@$ofs", type => undef };
624 print STDERR "Parsing header...\n";
625 $p{header} = parse_section $fh, DPROGRAMS_T, 0, undef, 1;
627 print STDERR "Parsing strings...\n";
628 $p{strings} = get_section $fh, $p{header}{ofs_strings}, $p{header}{numstrings};
630 print STDERR "Parsing statements...\n";
631 $p{statements} = [parse_section $fh, DSTATEMENT_T, $p{header}{ofs_statements}, undef, $p{header}{numstatements}];
633 print STDERR "Parsing globaldefs...\n";
634 $p{globaldefs} = [parse_section $fh, DDEF_T, $p{header}{ofs_globaldefs}, undef, $p{header}{numglobaldefs}];
636 print STDERR "Parsing fielddefs...\n";
637 $p{fielddefs} = [parse_section $fh, DDEF_T, $p{header}{ofs_fielddefs}, undef, $p{header}{numfielddefs}];
639 print STDERR "Parsing globals...\n";
640 $p{globals} = [parse_section $fh, DGLOBAL_T, $p{header}{ofs_globals}, undef, $p{header}{numglobals}];
642 print STDERR "Parsing functions...\n";
643 $p{functions} = [parse_section $fh, DFUNCTION_T, $p{header}{ofs_functions}, undef, $p{header}{numfunctions}];
645 print STDERR "Providing helpers...\n";
649 my $endpos = index $p{strings}, "\0", $startpos;
650 return substr $p{strings}, $startpos, $endpos - $startpos;
653 print STDERR "Naming...\n";
657 for(@{$p{globaldefs}})
659 $_->{debugname} = $p{getstring}->($_->{s_name});
661 for(@{$p{globaldefs}})
664 unless $_->{debugname};
665 if(!defined $globaldefs[$_->{ofs}] || length $globaldefs[$_->{ofs}]->{debugname} < length $_->{debugname})
667 $globaldefs[$_->{ofs}] = $_;
671 for(@{$p{globaldefs}})
673 $_->{debugname} = "<anon>\@$_->{ofs}"
674 if $_->{debugname} eq "";
675 ++$globaldefs{$_->{debugname}};
677 for(@{$p{globaldefs}})
680 if $globaldefs{$_->{debugname}} <= 1;
681 $_->{debugname} .= "\@$_->{ofs}";
683 $p{globaldef_byoffset} = sub
686 my $def = $globaldefs[$ofs]
687 or return defaultglobal $_[0];
692 for(@{$p{functions}})
694 my $file = $p{getstring}->($_->{s_file});
695 my $name = $p{getstring}->($_->{s_name});
696 $name = "$file:$name"
698 $_->{debugname} = $name;
699 $functions{$_->{first_statement}} = $_;
701 $p{function_byoffset} = sub
704 return $functions{$ofs};
707 # what do we want to do?
708 my $checkfunc = \&find_uninitialized_locals;
709 for(sort { $a->{debugname} <=> $b->{debugname} } @{$p{functions}})
711 $checkfunc->(\%p, $_);
715 open my $fh, '<', $ARGV[0];