Commit | Line | Data |
---|---|---|
f4abc3e7 JH |
1 | #!./perl -w |
2 | ||
3 | =pod | |
4 | ||
5 | =head1 TEST FOR B::Assembler.pm AND B::Disassembler.pm | |
6 | ||
7 | =head2 Description | |
8 | ||
9 | The general idea is to test by assembling a choice set of assembler | |
10 | instructions, then disassemble them, and check that we've completed the | |
11 | round trip. Also, error checking of Assembler.pm is tested by feeding | |
12 | it assorted errors. | |
13 | ||
14 | Since Assembler.pm likes to assemble a file, we comply by writing a | |
15 | text file. This file contains three sections: | |
16 | ||
17 | testing operand categories | |
18 | use each opcode | |
19 | erronous assembler instructions | |
20 | ||
21 | An "operand category" is identified by the suffix of the PUT_/GET_ | |
22 | subroutines as shown in the C<%Asmdata::insn_data> initialization, e.g. | |
23 | opcode C<ldsv> has operand category C<svindex>: | |
24 | ||
25 | insn_data{ldsv} = [1, \&PUT_svindex, "GET_svindex"]; | |
26 | ||
27 | Because Disassembler.pm also assumes input from a file, we write the | |
28 | resulting object code to a file. And disassembled output is written to | |
29 | yet another text file which is then compared to the original input. | |
30 | (Erronous assembler instructions still generate code, but this is not | |
31 | written to the object file; therefore disassembly bails out at the first | |
32 | instruction in error.) | |
33 | ||
34 | All files are kept in memory by using TIEHASH. | |
35 | ||
36 | ||
37 | =head2 Caveats | |
38 | ||
39 | An error where Assembler.pm and Disassembler.pm agree but Assembler.pm | |
40 | generates invalid object code will not be detected. | |
41 | ||
42 | Due to the way this test has been set up, failure of a single test | |
43 | could cause all subsequent tests to fail as well: After an unexpected | |
44 | assembler error no output is written, and disassembled lines will be | |
45 | out of sync for all lines thereafter. | |
46 | ||
47 | Not all possibilities for writing a valid operand value can be tested | |
48 | because disassembly results in a uniform representation. | |
49 | ||
50 | ||
51 | =head2 Maintenance | |
52 | ||
53 | New opcodes are added automatically. | |
54 | ||
55 | A new operand category will cause this program to die ("no operand list | |
56 | for XXX"). The cure is to add suitable entries to C<%goodlist> and | |
57 | C<%badlist>. (Since the data in Asmdata.pm is autogenerated, it may also | |
58 | happen that the corresponding assembly or disassembly subroutine is | |
59 | missing.) Note that an empty array as a C<%goodlist> entry means that | |
60 | opcodes of the operand category do not take an operand (and therefore the | |
61 | corresponding entry in C<%badlist> should have one). An C<undef> entry | |
62 | in C<%badlist> means that any value is acceptable (and thus there is no | |
63 | way to cause an error). | |
64 | ||
65 | Set C<$dbg> to debug this test. | |
66 | ||
67 | =cut | |
68 | ||
69 | package VirtFile; | |
70 | use strict; | |
71 | ||
72 | # Note: This is NOT a general purpose package. It implements | |
73 | # sequential text and binary file i/o in a rather simple form. | |
74 | ||
75 | sub TIEHANDLE($;$){ | |
76 | my( $class, $data ) = @_; | |
77 | my $obj = { data => defined( $data ) ? $data : '', | |
78 | pos => 0 }; | |
79 | return bless( $obj, $class ); | |
80 | } | |
81 | ||
82 | sub PRINT($@){ | |
83 | my( $self ) = shift; | |
84 | $self->{data} .= join( '', @_ ); | |
85 | } | |
86 | ||
87 | sub WRITE($$;$$){ | |
88 | my( $self, $buf, $len, $offset ) = @_; | |
89 | unless( defined( $len ) ){ | |
90 | $len = length( $buf ); | |
91 | $offset = 0; | |
92 | } | |
93 | unless( defined( $offset ) ){ | |
94 | $offset = 0; | |
95 | } | |
96 | $self->{data} .= substr( $buf, $offset, $len ); | |
97 | return $len; | |
98 | } | |
99 | ||
100 | ||
101 | sub GETC($){ | |
102 | my( $self ) = @_; | |
103 | return undef() if $self->{pos} >= length( $self->{data} ); | |
104 | return substr( $self->{data}, $self->{pos}++, 1 ); | |
105 | } | |
106 | ||
107 | sub READLINE($){ | |
108 | my( $self ) = @_; | |
109 | return undef() if $self->{pos} >= length( $self->{data} ); | |
110 | my $lfpos = index( $self->{data}, "\n", $self->{pos} ); | |
111 | if( $lfpos < 0 ){ | |
112 | $lfpos = length( $self->{data} ); | |
113 | } | |
114 | my $pos = $self->{pos}; | |
115 | $self->{pos} = $lfpos + 1; | |
116 | return substr( $self->{data}, $pos, $self->{pos} - $pos ); | |
117 | } | |
118 | ||
119 | sub READ($@){ | |
120 | my $self = shift(); | |
121 | my $bufref = \$_[0]; | |
122 | my( undef, $len, $offset ) = @_; | |
123 | if( $offset ){ | |
124 | die( "offset beyond end of buffer\n" ) | |
125 | if ! defined( $$bufref ) || $offset > length( $$bufref ); | |
126 | } else { | |
127 | $$bufref = ''; | |
128 | $offset = 0; | |
129 | } | |
130 | my $remlen = length( $self->{data} ) - $self->{pos}; | |
131 | $len = $remlen if $remlen < $len; | |
132 | return 0 unless $len; | |
133 | substr( $$bufref, $offset, $len ) = | |
134 | substr( $self->{data}, $self->{pos}, $len ); | |
135 | $self->{pos} += $len; | |
136 | return $len; | |
137 | } | |
138 | ||
139 | sub TELL($){ | |
140 | my $self = shift(); | |
141 | return $self->{pos}; | |
142 | } | |
143 | ||
144 | sub CLOSE($){ | |
145 | my( $self ) = @_; | |
146 | $self->{pos} = 0; | |
147 | } | |
148 | ||
149 | 1; | |
150 | ||
151 | package main; | |
152 | ||
153 | use strict; | |
154 | use Test::More; | |
155 | use Config qw(%Config); | |
156 | ||
ff924b36 | 157 | BEGIN { |
46b73616 MHM |
158 | if (($Config{'extensions'} !~ /\bB\b/) ){ |
159 | print "1..0 # Skip -- Perl configured without B module\n"; | |
160 | exit 0; | |
161 | } | |
ff924b36 CB |
162 | if (($Config{'extensions'} !~ /\bByteLoader\b/) ){ |
163 | print "1..0 # Skip -- Perl configured without ByteLoader module\n"; | |
164 | exit 0; | |
165 | } | |
166 | } | |
167 | ||
f4abc3e7 JH |
168 | use B::Asmdata qw( %insn_data ); |
169 | use B::Assembler qw( &assemble_fh ); | |
170 | use B::Disassembler qw( &disassemble_fh &get_header ); | |
171 | ||
172 | my( %opsByType, @code2name ); | |
173 | my( $lineno, $dbg, $firstbadline, @descr ); | |
174 | $dbg = 0; # debug switch | |
175 | ||
176 | # $SIG{__WARN__} handler to catch Assembler error messages | |
177 | # | |
178 | my $warnmsg; | |
179 | sub catchwarn($){ | |
180 | $warnmsg = $_[0]; | |
181 | print "error: $warnmsg\n" if $dbg; | |
182 | } | |
183 | ||
184 | # Callback for writing assembled bytes. This is where we check | |
185 | # that we do get an error. | |
186 | # | |
187 | sub putobj($){ | |
188 | if( ++$lineno >= $firstbadline ){ | |
189 | ok( $warnmsg && $warnmsg =~ /^\d+:\s/, $descr[$lineno] ); | |
190 | undef( $warnmsg ); | |
191 | } else { | |
192 | my $l = syswrite( OBJ, $_[0] ); | |
193 | } | |
194 | } | |
195 | ||
196 | # Callback for writing a disassembled statement. | |
197 | # | |
198 | sub putdis(@){ | |
199 | my $line = join( ' ', @_ ); | |
200 | ++$lineno; | |
201 | print DIS "$line\n"; | |
202 | printf "%5d %s\n", $lineno, $line if $dbg; | |
203 | } | |
204 | ||
205 | # Generate assembler instructions from a hash of operand types: each | |
206 | # existing entry contains a list of good or bad operand values. The | |
207 | # corresponding opcodes can be found in %opsByType. | |
208 | # | |
209 | sub gen_type($$$){ | |
210 | my( $href, $descref, $text ) = @_; | |
e53790c1 | 211 | for my $odt ( sort( keys( %opsByType ) ) ){ |
f4abc3e7 JH |
212 | my $opcode = $opsByType{$odt}->[0]; |
213 | my $sel = $odt; | |
214 | $sel =~ s/^GET_//; | |
215 | die( "no operand list for $sel\n" ) unless exists( $href->{$sel} ); | |
216 | if( defined( $href->{$sel} ) ){ | |
217 | if( @{$href->{$sel}} ){ | |
218 | for my $od ( @{$href->{$sel}} ){ | |
219 | ++$lineno; | |
220 | $descref->[$lineno] = "$text: $code2name[$opcode] $od"; | |
221 | print ASM "$code2name[$opcode] $od\n"; | |
222 | printf "%5d %s %s\n", $lineno, $code2name[$opcode], $od if $dbg; | |
223 | } | |
224 | } else { | |
225 | ++$lineno; | |
226 | $descref->[$lineno] = "$text: $code2name[$opcode]"; | |
227 | print ASM "$code2name[$opcode]\n"; | |
228 | printf "%5d %s\n", $lineno, $code2name[$opcode] if $dbg; | |
229 | } | |
230 | } | |
231 | } | |
232 | } | |
233 | ||
234 | # Interesting operand values | |
235 | # | |
236 | my %goodlist = ( | |
237 | comment_t => [ '"a comment"' ], # no \n | |
238 | none => [], | |
239 | svindex => [ 0x7fffffff, 0 ], | |
240 | opindex => [ 0x7fffffff, 0 ], | |
241 | pvindex => [ 0x7fffffff, 0 ], | |
242 | U32 => [ 0xffffffff, 0 ], | |
243 | U8 => [ 0xff, 0 ], | |
244 | PV => [ '""', '"a string"', ], | |
245 | I32 => [ -0x80000000, 0x7fffffff ], | |
246 | IV64 => [ '0x000000000', '0x0ffffffff', '0x000000001' ], # disass formats 0x%09x | |
247 | IV => $Config{ivsize} == 4 ? | |
248 | [ -0x80000000, 0x7fffffff ] : | |
249 | [ '0x000000000', '0x0ffffffff', '0x000000001' ], | |
250 | NV => [ 1.23456789E3 ], | |
251 | U16 => [ 0xffff, 0 ], | |
252 | pvcontents => [], | |
253 | strconst => [ '""', '"another string"' ], # no NUL | |
566ece03 | 254 | op_tr_array => [ join( ',', 256, 0..255 ) ], |
ca337316 | 255 | PADOFFSET => undef, |
53897bd5 | 256 | long => undef, |
f4abc3e7 JH |
257 | ); |
258 | ||
259 | # Erronous operand values | |
260 | # | |
261 | my %badlist = ( | |
262 | comment_t => [ '"multi-line\ncomment"' ], # no \n | |
263 | none => [ '"spurious arg"' ], | |
264 | svindex => [ 0xffffffff * 2, -1 ], | |
265 | opindex => [ 0xffffffff * 2, -2 ], | |
266 | pvindex => [ 0xffffffff * 2, -3 ], | |
267 | U32 => [ 0xffffffff * 2, -4 ], | |
268 | U16 => [ 0x5ffff, -5 ], | |
269 | U8 => [ 0x6ff, -6 ], | |
270 | PV => [ 'no quote"' ], | |
271 | I32 => [ -0x80000001, 0x80000000 ], | |
272 | IV64 => undef, # PUT_IV64 doesn't check - no integrity there | |
273 | IV => $Config{ivsize} == 4 ? | |
274 | [ -0x80000001, 0x80000000 ] : undef, | |
275 | NV => undef, # PUT_NV accepts anything - it shouldn't, real-ly | |
276 | pvcontents => [ '"spurious arg"' ], | |
277 | strconst => [ 'no quote"', '"with NUL '."\0".' char"' ], # no NUL | |
566ece03 | 278 | op_tr_array => undef, # op_pv_tr is no longer exactly 256 shorts |
ca337316 | 279 | PADOFFSET => undef, |
53897bd5 | 280 | long => undef, |
f4abc3e7 JH |
281 | ); |
282 | ||
283 | ||
284 | # Determine all operand types from %Asmdata::insn_data | |
285 | # | |
286 | for my $opname ( keys( %insn_data ) ){ | |
287 | my ( $opcode, $put, $getname ) = @{$insn_data{$opname}}; | |
288 | push( @{$opsByType{$getname}}, $opcode ); | |
289 | $code2name[$opcode] = $opname; | |
290 | } | |
291 | ||
292 | ||
293 | # Write instruction(s) for correct operand values each operand type class | |
294 | # | |
295 | $lineno = 0; | |
296 | tie( *ASM, 'VirtFile' ); | |
297 | gen_type( \%goodlist, \@descr, 'round trip' ); | |
298 | ||
299 | # Write one instruction for each opcode. | |
300 | # | |
301 | for my $opcode ( 0..$#code2name ){ | |
302 | next unless defined( $code2name[$opcode] ); | |
303 | my $sel = $insn_data{$code2name[$opcode]}->[2]; | |
304 | $sel =~ s/^GET_//; | |
305 | die( "no operand list for $sel\n" ) unless exists( $goodlist{$sel} ); | |
306 | if( defined( $goodlist{$sel} ) ){ | |
307 | ++$lineno; | |
308 | if( @{$goodlist{$sel}} ){ | |
309 | my $od = $goodlist{$sel}[0]; | |
310 | $descr[$lineno] = "round trip: $code2name[$opcode] $od"; | |
311 | print ASM "$code2name[$opcode] $od\n"; | |
312 | printf "%5d %s %s\n", $lineno, $code2name[$opcode], $od if $dbg; | |
313 | } else { | |
314 | $descr[$lineno] = "round trip: $code2name[$opcode]"; | |
315 | print ASM "$code2name[$opcode]\n"; | |
316 | printf "%5d %s\n", $lineno, $code2name[$opcode] if $dbg; | |
317 | } | |
318 | } | |
319 | } | |
320 | ||
321 | # Write instruction(s) for incorrect operand values each operand type class | |
322 | # | |
323 | $firstbadline = $lineno + 1; | |
324 | gen_type( \%badlist, \@descr, 'asm error' ); | |
325 | ||
326 | # invalid opcode is an odd-man-out ;-) | |
327 | # | |
328 | ++$lineno; | |
329 | $descr[$lineno] = "asm error: Gollum"; | |
330 | print ASM "Gollum\n"; | |
331 | printf "%5d %s\n", $lineno, 'Gollum' if $dbg; | |
332 | ||
333 | close( ASM ); | |
334 | ||
335 | # Now that we have defined all of our tests: plan | |
336 | # | |
337 | plan( tests => $lineno ); | |
338 | print "firstbadline=$firstbadline\n" if $dbg; | |
339 | ||
340 | # assemble (guard against warnings and death from assembly errors) | |
341 | # | |
342 | $SIG{'__WARN__'} = \&catchwarn; | |
343 | ||
344 | $lineno = -1; # account for the assembly header | |
345 | tie( *OBJ, 'VirtFile' ); | |
346 | eval { assemble_fh( \*ASM, \&putobj ); }; | |
347 | print "eval: $@" if $dbg; | |
348 | close( ASM ); | |
349 | close( OBJ ); | |
350 | $SIG{'__WARN__'} = 'DEFAULT'; | |
351 | ||
352 | # disassemble | |
353 | # | |
354 | print "--- disassembling ---\n" if $dbg; | |
355 | $lineno = 0; | |
356 | tie( *DIS, 'VirtFile' ); | |
357 | disassemble_fh( \*OBJ, \&putdis ); | |
358 | close( OBJ ); | |
359 | close( DIS ); | |
360 | ||
361 | # get header (for debugging only) | |
362 | # | |
363 | if( $dbg ){ | |
364 | my( $magic, $archname, $blversion, $ivsize, $ptrsize, $byteorder ) = | |
365 | get_header(); | |
366 | printf "Magic: 0x%08x\n", $magic; | |
367 | print "Architecture: $archname\n"; | |
368 | print "Byteloader V: $blversion\n"; | |
369 | print "ivsize: $ivsize\n"; | |
370 | print "ptrsize: $ptrsize\n"; | |
371 | print "Byteorder: $byteorder\n"; | |
372 | } | |
373 | ||
374 | # check by comparing files line by line | |
375 | # | |
376 | print "--- checking ---\n" if $dbg; | |
377 | $lineno = 0; | |
378 | my( $asmline, $disline ); | |
379 | while( defined( $asmline = <ASM> ) ){ | |
380 | $disline = <DIS>; | |
381 | ++$lineno; | |
382 | last if $lineno eq $firstbadline; # bail out where errors begin | |
383 | ok( $asmline eq $disline, $descr[$lineno] ); | |
384 | printf "%5d %s\n", $lineno, $asmline if $dbg; | |
385 | } | |
386 | close( ASM ); | |
387 | close( DIS ); | |
388 | ||
389 | __END__ |