File Coverage

blib/lib/CPU/Emulator/DCPU16/Disassembler.pm
Criterion Covered Total %
statement 58 65 89.2
branch 30 38 78.9
condition n/a
subroutine 5 5 100.0
pod 2 2 100.0
total 95 110 86.3


line stmt bran cond sub pod time code
1             package CPU::Emulator::DCPU16::Disassembler;
2 4     4   24892 use strict;
  4         6  
  4         143  
3 4     4   534 use CPU::Emulator::DCPU16;
  4         8  
  4         3575  
4              
5             =head1 NAME
6              
7             CPU::Emulator::DCPU16::Disassembler - a disassembler for DCPU-16 bytecode
8              
9             =head1 SYNOPSIS
10            
11             # Disassemble a single instruction
12             my $instruction = CPU::Emulator::DCPU16::Disassembler->disassemble($pc, @memory);
13              
14             # Dump a whole program
15             my $asm = CPU::Emulator::DCPU16::Disassembler->dump($bytes);
16              
17             =cut
18              
19              
20              
21             our @OPCODES = qw(NOOP SET ADD SUB MUL DIV MOD SHL SHR AND BOR XOR IFE IFN IFG IFB);
22             our @REGISTERS = qw(A B C X Y Z I J);
23              
24              
25             sub _get_operand {
26 13     13   37 my $n = shift;
27 13         28 my $pc = shift;
28 13         43022 my @mem = @_;
29 13 100       202 if ($n < 0x08) {
    100          
    100          
    50          
    50          
    50          
    50          
    100          
    50          
    50          
    100          
30 3         1879 sprintf("%s", $REGISTERS[$n & 7]);
31             } elsif ($n < 0x10) {
32 1         1716 sprintf("[%s]", $REGISTERS[$n & 7]);
33             } elsif ($n < 0x18) {
34 1         1993 sprintf("[0x%04x+%s]", $mem[$$pc++], $REGISTERS[$n & 7]);
35             } elsif ($n == 0x18) {
36 0         0 "POP"
37             } elsif ($n == 0x19) {
38 0         0 "PEEK"
39             } elsif ($n == 0x1A) {
40 0         0 "PUSH"
41             } elsif ($n == 0x1B) {
42 0         0 "SP"
43             } elsif ($n == 0x1C) {
44 2         1672 "PC"
45             } elsif ($n == 0x1D) {
46 0         0 "O"
47             } elsif ($n == 0x1E) {
48 0         0 sprintf("[0x%04x]", $mem[$$pc++]);
49             } elsif ($n == 0x1F) {
50 4         4945 sprintf("0x%04x", $mem[$$pc++]);
51             } else {
52 2         8 ($n - 0x20);
53             }
54             }
55              
56             =head2 disassemble
57              
58             Given a program counter and an array of memory words will dissassemble the current instruction.
59              
60             =cut
61             sub disassemble {
62 7     7 1 197010 my $class = shift;
63 7         17 my $pc = shift;
64 7         22020 my @mem = @_;
65 7         37 my $word = $mem[$pc++];
66 7         18 my $op = $word & 0xF;
67 7         46 my $a = ($word >> 4) & 0x3F;
68 7         14 my $b = ($word >> 10);
69            
70 7         18 my $ret = "";
71 7 100       41 if ($op > 0) {
    50          
72 6         30 $ret .= $OPCODES[$op]." ";
73 6         4222 $ret .= _get_operand($a, \$pc, @mem);
74 6         28 $ret .= ", ";
75 6         4078 $ret .= _get_operand($b, \$pc, @mem);
76             } elsif ($a == 0x01) {
77 1         1079 $ret .= "JSR "._get_operand($b, \$pc, @mem);
78             } else {
79 0         0 $ret .= sprintf("UNK[%02x] ", $a)._get_operand($b, \$pc, @mem);
80             }
81 7 100       9009 wantarray ? ($ret, $pc) : $ret;
82             }
83              
84             =head2 dump
85              
86             Given an scalar containing program bytecode will return a string representing the assembler.
87              
88             =cut
89             our $CODE_INDENT = 10;
90             sub dump {
91 1     1 1 10 my $class = shift;
92 1         2 my $bytes = shift;
93            
94 1         9 my @words = CPU::Emulator::DCPU16->bytes_to_array($bytes);
95 1         3 my $pc = 0;
96 1         3 my %labels = ();
97 1         2 my %lines = ();
98            
99 1         5 while ($pc < scalar(@words)) {
100 3         11 my ($tmp, $new_pc) = $class->disassemble($pc, @words);
101 3 100       26 if ($tmp =~ /^(JSR|SET PC,)\s*(.+)$/) {
102 1         3 my $addr = "$2";
103             # TODO potentially replace faux address labels with generated ones
104 1 50       7 $labels{hex($addr)} = $addr if $addr =~ /^0x/;
105             }
106 3         8 $lines{$pc} = $tmp;
107 3         7 $pc = $new_pc;
108             }
109 1         2 my $indent = 0;
110 1         3 my $ret = "";
111 1         8 foreach $pc (sort { $a <=> $b } keys %lines) {
  3         9  
112 3         6 my $line = $lines{$pc};
113             #$ret .= sprintf "%d (0x%04x) ", $pc, $pc;
114 3 100       8 if ($labels{$pc}) {
115 1         6 $ret .= ":".$labels{$pc} . " " ." "x ($CODE_INDENT-length($labels{$pc})-2);
116             } else {
117 2         5 $ret .= " "x$CODE_INDENT;
118             }
119 3         5 $ret .= " " x $indent;
120 3         5 $ret .= "$line\n";
121 3 100       13 if ($line =~ /^IF/) {
    100          
122 1         3 $indent++;
123             } elsif ($indent) {
124 1         3 $indent--;
125             }
126             }
127 1         10 return $ret;
128             }
129             1;