File Coverage

lib/SIRTX/VM/Disassembler.pm
Criterion Covered Total %
statement 23 134 17.1
branch 0 54 0.0
condition 0 22 0.0
subroutine 8 13 61.5
pod 2 2 100.0
total 33 225 14.6


line stmt bran cond sub pod time code
1             # Copyright (c) 2025 Philipp Schafft
2              
3             # licensed under Artistic License 2.0 (see LICENSE file)
4              
5             # ABSTRACT: module for assembling SIRTX VM code
6              
7              
8             package SIRTX::VM::Disassembler;
9              
10 1     1   2257 use v5.16;
  1         5  
11 1     1   7 use strict;
  1         3  
  1         29  
12 1     1   5 use warnings;
  1         2  
  1         60  
13              
14 1     1   6 use Carp;
  1         2  
  1         86  
15 1     1   23 use Fcntl qw(SEEK_SET SEEK_END SEEK_CUR);
  1         3  
  1         66  
16 1     1   6 use SIRTX::VM::RegisterFile;
  1         1  
  1         87  
17 1     1   9 use SIRTX::VM::Opcode;
  1         2  
  1         32  
18              
19 1     1   5 use parent 'Data::Identifier::Interface::Userdata';
  1         2  
  1         9  
20              
21             our $VERSION = v0.12;
22              
23              
24             sub new {
25 0     0 1   my ($pkg, %opts) = @_;
26             my $self = bless({
27 0           max_data => delete($opts{max_data}),
28             }, $pkg);
29              
30             {
31 0           my $fh = delete $opts{in};
32 0 0         croak 'No input given' unless defined $fh;
33              
34 0 0         unless (ref $fh) {
35 0 0         open(my $x, '<', $fh) or die $!;
36 0           $fh = $x;
37             }
38              
39 0           $fh->binmode;
40 0           $self->{in} = $fh;
41             }
42              
43             {
44 0           my $fh = delete $opts{out};
  0            
  0            
45 0 0         croak 'No output given' unless defined $fh;
46              
47 0 0         unless (ref $fh) {
48 0 0         open(my $x, '>', $fh) or die $!;
49 0           $fh = $x;
50             }
51              
52 0           $fh->binmode;
53 0           $fh->binmode(':utf8');
54 0           $self->{out} = $fh;
55             }
56              
57 0 0         croak 'Stray options passed' if scalar keys %opts;
58              
59 0           return $self;
60             }
61              
62              
63             sub run {
64 0     0 1   my ($self, @opts) = @_;
65 0           my $in = $self->{in};
66 0           my $out = $self->{out};
67 0           my $in_length = $self->_in_length;
68              
69 0           $self->{starts} = {0 => undef};
70              
71 0 0         croak 'Stray options passed' if scalar @opts;
72              
73 0           while (1) {
74 0   0       my $pos = $in->tell // croak 'Cannot tell on input';
75              
76 0 0         last if $pos >= $in_length;
77              
78 0 0         if (exists $self->{starts}{$pos}) {
79 0           $self->_run_text;
80             } else {
81 0           $self->_run_data;
82             }
83             }
84             }
85              
86             # ---- Private helpers ----
87              
88             sub _in_length {
89 0     0     my ($self) = @_;
90              
91 0   0       return $self->{in_length} //= do {
92 0           my $fh = $self->{in};
93 0           my $l;
94              
95 0           $fh->seek(0, SEEK_END);
96              
97 0           $l = $fh->tell;
98              
99 0           $fh->seek(0, SEEK_SET);
100              
101 0           $l;
102             };
103             }
104              
105             sub _run_text {
106 0     0     my ($self) = @_;
107 0           my $in = $self->{in};
108 0           my $out = $self->{out};
109 0           my $in_length = $self->_in_length;
110              
111 0   0       while ($in->tell < $in_length && defined(my $opcode = SIRTX::VM::Opcode->read($in))) {
112 0           my %extra = $opcode->_extra;
113              
114 0   0       $self->{starts}{$_} //= undef for @{$extra{start_offsets}//[]};
  0   0        
115              
116              
117 0 0         if ($extra{type} eq 'chunk') {
118 0           my $length = $extra{length};
119              
120 0 0         if ($length >= 4) {
121 0           my $command = '.chunk';
122 0           my $data_length = $length - 4;
123 0           my ($flags, $type);
124 0           my $chunk_identifier;
125 0           my $raw;
126              
127 0 0         $in->read($raw, 4) == 4 or croak 'Cannot read input';
128              
129 0           ($flags, $type) = unpack('nn', $raw);
130              
131             {
132 0           my $identifier_type = $flags & ((1<<15)|(1<<14));
  0            
133 0 0         if ($identifier_type == ((1<<15)|(1<<14))) {
    0          
    0          
    0          
134 0           croak 'Unsupported identifier type: user defined';
135             } elsif ($identifier_type == ((1<<15)|(0<<14))) {
136 0           $command .= ' of ~'.$type;
137             } elsif ($identifier_type == ((0<<15)|(1<<14))) {
138 0           $command .= ' of sid:'.$type;
139             } elsif ($identifier_type == ((0<<15)|(0<<14))) {
140 0           $command .= ' of sni:'.$type;
141             }
142             }
143              
144 0 0         if ($flags & (1<<7)) {
145 0           $command .= ' standalone';
146             }
147              
148 0 0         if ($flags & (1<<1)) {
149 0 0         $in->read($raw, 2) == 2 or croak 'Cannot read input';
150 0           $chunk_identifier = unpack('n', $raw);
151 0           $data_length -= 2;
152 0           $command .= ' as ~'.$chunk_identifier;
153             }
154              
155 0 0         if ($flags & (1<<0)) {
156 0           $data_length -= 1;
157             }
158              
159             #warn sprintf('; ### flags: %04x type: %04x, chunk identifier: %s, data_length: %u', $flags, $type, $chunk_identifier // '', $data_length);
160 0           $out->say($command);
161 0           $self->_run_data($data_length);
162              
163 0 0         if ($flags & (1<<0)) {
164 0           my $pos = $in->tell;
165 0 0         $in->read($raw, 1) == 1 or croak 'Cannot read input';
166 0           $out->say(sprintf(' ; at 0x%04x: Padding: 0x%02x', $pos, ord $raw));
167             }
168 0           $out->say('.endchunk');
169             }
170             } else {
171 0           $out->say($opcode->as_text);
172 0 0         if ($opcode->is_end_of_text) {
173 0           $out->say('; End of text');
174 0           last;
175             }
176             }
177             }
178             }
179              
180             sub _run_data {
181 0     0     my ($self, $todo) = @_;
182 0           my $in = $self->{in};
183 0           my $out = $self->{out};
184 0   0       my $pos = $in->tell // croak 'Cannot tell on input';
185 0           my ($next_code) = sort { $a <=> $b } grep { $_ >= $pos } keys(%{$self->{starts}}), $self->_in_length;
  0            
  0            
  0            
186              
187 0   0       $todo //= $next_code - $pos;
188              
189 0 0 0       if (defined($self->{max_data}) && $todo > $self->{max_data}) {
190 0           $out->say(sprintf('; %u bytes of data skipped', $todo));
191 0           $in->seek($todo, SEEK_CUR);
192 0           return;
193             }
194              
195 0           $out->say(sprintf('; %u bytes of data follow', $todo));
196              
197 0           while ($todo > 0) {
198 0 0         my $step = $todo > 8 ? 8 : $todo;
199 0           my $line = '.byte';
200 0           my $rendered;
201              
202 0 0         $in->read(my $raw, $step) == $step or croak 'Error reading data';
203              
204 0           $line .= sprintf(' 0x%02x', ord) foreach split //, $raw;
205              
206 0           $rendered = $raw =~ tr/\x21-\x7E/./rc;
207              
208 0           $line = sprintf('%-48s ; at 0x%04x: | %-8s |',
209             $line, $pos,
210             $rendered,
211             );
212              
213 0           $out->say($line);
214              
215 0           $todo -= $step;
216 0           $pos += $step;
217             }
218             }
219              
220             1;
221              
222             __END__