ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/mips/run
Revision: 1.2
Committed: Sat Jun 26 18:02:49 2021 UTC (3 years ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.1: +9 -3 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3     # Copyright 2019 Marc Alexander Lehmann <schmorp@schmorp.de>
4     # with fixes inspired by Péter Szabó (https://github.com/pts/pts-mips-emulator/)
5     # This file is licensed under the General Public License (GPL) version 3
6    
7     sub STRACE (){ 0 }
8     sub PRCPU (){ 0 }
9     sub PRIPS (){ 0 }
10    
11     use common::sense;
12     no utf8; # activestate perl
13     use Time::HiRes;
14     use POSIX ();
15     use Cwd ();
16     use Fcntl ();
17    
18     pack "Q", 5; # make sure it's a 64 bit perl
19    
20     #############################################################################
21     # system state
22    
23     use integer; # minor speed improvement
24    
25     sub ADDR_SHIFT(){ 16 }
26     sub ADDR_MASK (){ 0x3fff }
27    
28     sub STACK (){ 0xf00f0000 }
29     sub MINBRK (){ 0x10000000 }
30    
31     sub M32 (){ 0xffffffff }
32     sub B31 (){ 0x80000000 }
33     sub M16 (){ 0xffff }
34     sub B15 (){ 0x8000 }
35     sub M8 (){ 0xff }
36     sub B7 (){ 0x80 }
37    
38     my @mem; # [16bits][14bits] = 32bits
39    
40     sub xxd($) {
41     open my $fh, "| xxd" or die;
42     print $fh $_[0];
43     }
44    
45     sub memset($$) {
46     for (0 .. (length $_[1]) - 1) {
47     my $i = $_[0] + $_;
48     my $c = unpack "C", substr $_[1], $_, 1;
49    
50     my $s = (~$i & 3) << 3;
51     $i = \$mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK];
52     $$i = $$i & ~(0xff << $s) | ($c << $s);
53     }
54     }
55    
56     sub memget($$) {
57     my $r;
58    
59     for my $i ($_[0] .. $_[0] + $_[1] - 1) {
60     my $s = (~$i & 3) << 3;
61     $r .= pack "C", (($mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK] >> $s) & 0xff);
62     }
63    
64     $r
65     }
66    
67     sub memstr($) {
68     my $r;
69    
70     for (my $i = $_[0]; ; ++$i) {
71     my $s = (~$i & 3) << 3;
72     $r .= pack "C", (($mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK] >> $s) & 0xff)
73     || last;
74     }
75    
76     $r
77     }
78    
79     sub mem_reset() {
80     @mem = ();
81     }
82    
83     ############################################
84    
85     my ($pc, $hi, $lo, $delay); # cpu state
86    
87     my (
88     $r0 , $r1 , $r2 , $r3 , $r4 , $r5 , $r6 , $r7 ,
89     $r8 , $r9 , $r10, $r11, $r12, $r13, $r14, $r15,
90     $r16, $r17, $r18, $r19, $r20, $r21, $r22, $r23,
91     $r24, $r25, $r26, $r27, $r28, $r29, $r30, $r31,
92     );
93    
94     my $insn;
95    
96     my %insn2src; # insn to perl source
97     my %insn2sub; # insn to compiled perl sub
98     my %sub2insn; # sub+0 to insn code
99    
100     {
101     our ($hint_bits, $warning_bits, %hint_hash);
102    
103     BEGIN {
104     ($hint_bits, $warning_bits, %hint_hash) = ($^H, ${^WARNING_BITS}, %^H);
105     }
106    
107     sub deparse {
108     require B::Deparse;
109     my $deparser = new B::Deparse;
110     $deparser->ambient_pragmas (
111     hint_bits => $hint_bits,
112     warning_bits => $warning_bits,
113     '$[' => $[+0,
114     integer => 1,
115     '%^H' => \%hint_hash,
116     );
117     $deparser->coderef2text ($_[0])
118     }
119     }
120    
121     sub cpu_pr {
122     printf " 0 %8x=zr %8x=at %8x=v0 %8x=v1 %8x=a0 %8x=a1 %8x=a2 %8x=a3\n", $r0 , $r1 , $r2 , $r3 , $r4 , $r5 , $r6 , $r7;
123     printf " 8 %8x=t0 %8x=t1 %8x=t2 %8x=t3 %8x=t4 %8x=t5 %8x=t6 %8x=t7\n", $r8 , $r9 , $r10, $r11, $r12, $r13, $r14, $r15;
124     printf "16 %8x=s0 %8x=s1 %8x=s2 %8x=s3 %8x=s4 %8x=s4 %8x=s6 %8x=s6\n", $r16, $r17, $r18, $r19, $r20, $r21, $r22, $r23;
125     printf "24 %8x=t8 %8x=t9 %8x=k0 %8x=k1 %8x=gp %8x=sp %8x=fp %8x=ra\n", $r24, $r25, $r26, $r27, $r28, $r29, $r30, $r31;
126    
127     my $code = $sub2insn{$insn+0};
128     my $src = $insn2src{$code} ||= deparse $insn;
129     $src =~ s/\s+/ /g;
130     printf "%x: %08x (%s)\n", $pc * 4, $code, $src;
131     }
132    
133     ############################################
134     # syscalls
135    
136     # mips abi
137     sub ENOENT (){ 2 }
138     sub EBADF (){ 9 }
139     sub ENOMEM (){ 12 }
140     sub ENOTTY (){ 25 }
141     sub ERANGE (){ 34 }
142     sub EAGAIN (){ 35 }
143     sub ENOTSOCK (){ 88 }
144     sub ENOSYS (){ 89 }
145    
146     sub O_ACCMODE (){ 0003 }
147     sub O_RDONLY (){ 00 }
148     sub O_WRONLY (){ 01 }
149     sub O_RDWR (){ 02 }
150     sub O_APPEND (){ 0x0008 }
151     sub O_SYNC (){ 0x0010 }
152     sub O_NONBLOCK (){ 0x0080 }
153     sub O_CREAT (){ 0x0100 } # not fcntl
154     sub O_TRUNC (){ 0x0200 } # not fcntl
155     sub O_EXCL (){ 0x0400 } # not fcntl
156     sub O_NOCTTY (){ 0x0800 } # not fcntl
157     sub O_ASYNC (){ 0x1000 }
158     sub O_NOFOLLOW (){ 0x20000 }
159     sub O_DIRECT (){ 0x8000 }
160     sub O_DIRECTORY (){ 0x10000 }
161     sub O_NOATIME (){ 0x40000 }
162    
163     sub errno2mips() {
164     $!*1 # wrong, wrong, wrong
165     }
166    
167     sub mips2omode($) {
168     my $mmode = shift;
169    
170     my $omode = 0;
171    
172     $omode |= Fcntl::O_RDONLY if ($mmode & O_ACCMODE) == O_RDONLY;
173     $omode |= Fcntl::O_WRONLY if ($mmode & O_ACCMODE) == O_WRONLY;
174     $omode |= Fcntl::O_RDWR if ($mmode & O_ACCMODE) == O_RDWR;
175    
176     for my $mode (qw(
177     APPEND SYNC NONBLOCK CREAT TRUNC EXCL NOCTTY
178     ASYNC NOFOLLOW DIRECT DIRECTORY NOATIME
179     )) {
180     eval "\$omode |= Fcntl::O_$mode if \$mmode & O_$mode";
181     }
182    
183     $omode
184     }
185    
186     my $enosys = sub {
187     warn "unimplemented syscall $r2\n";
188     die;
189     };
190    
191     my @syscall = ($enosys) x 7000;
192    
193     ############################################
194    
195     my @fh;
196     my @dh; # directory-handles, HACK
197     my $strace;
198    
199     for my $fd (3..9) {
200     open my $fh, "+<&=", $fd
201     or next;
202    
203     $fh[$fd] = $fh;
204     }
205     for my $fd (0..2) {
206     open my $fh, "+<&", $fd
207     or next;
208    
209     $fh[$fd] = $fh;
210     }
211    
212     sub fd_valid($) {
213     !($_[0] & ~65535)
214     && $fh[$_[0]]
215     }
216    
217     ############################################
218    
219     sub strace($;@) {
220     $strace = $#_
221     ? sprintf $_[0], @_[1..$#_]
222     : shift;
223     }
224    
225     sub newfd($) {
226     my $fd;
227     ++$fd while $fh[$fd];
228     $fh[$fd] = $_[0];
229     $fd
230     }
231    
232     sub reify_fds {
233     my $top = 512;
234    
235     for my $fd (0..$#fh) {
236     next unless $fh[$fd];
237    
238     POSIX::dup2 fileno $fh[$fd], $top + $fd;
239     close $fh[$fd];
240     }
241    
242     for my $fd (0..$#fh) {
243     next unless $fh[$fd];
244    
245     POSIX::dup2 $top + $fd, $fd;
246     POSIX::close $top + $fd;
247    
248     open my $fh, "+<&=", $fd
249     or die;
250    
251     $fh[$fd] = $fh;
252     }
253     }
254    
255     ############################################
256    
257     my $tls_udesc;
258    
259     sub newstat {
260     my $buf = shift;
261    
262     if (@_) {
263     my $stat = pack "N x12 NNNNN x8 N x4 NN NN NN N N x56",
264     $_[ 0], # dev
265     $_[ 1], # ino
266     $_[ 2], # mode
267     $_[ 3], # nlink
268     $_[ 4], # uid
269     $_[ 5], # gid
270     $_[ 6], # rdev
271     $_[ 7], # size
272     $_[ 8], 0, # atime
273     $_[ 9], 0, # mtime
274     $_[10], 0, # ctime
275     $_[11], # blksize
276     $_[12]; # blocks
277    
278     memset $buf, $stat;
279    
280     return 0;
281     }
282    
283     -errno2mips
284     };
285    
286     sub stat64 {
287     my $buf = shift;
288    
289     if (@_) {
290     my $stat = pack "N x12 NN NNNNN x12 NN NN NN NN N x4 NN",
291     $_[ 0], # dev
292     $_[ 1] >> 32, $_[1], # ino
293     $_[ 2], # mode
294     $_[ 3], # nlink
295     $_[ 4], # uid
296     $_[ 5], # gid
297     $_[ 6], # rdev
298     $_[ 7] >> 32, $_[7], # size
299     $_[ 8], 0, # atime
300     $_[ 9], 0, # mtime
301     $_[10], 0, # ctime
302     $_[11], # blksize
303     $_[12] >> 32, $_[12]; # blocks
304    
305     memset $buf, $stat;
306    
307     return 0;
308     }
309    
310     -errno2mips
311     };
312    
313     sub sys_fcntl {
314     my ($fd, $cmd, $arg) = @_;
315     strace "fcntl (%d, %x, %x)", $fd, $cmd, $arg;
316     fd_valid $fd or return -EBADF;
317    
318     if ($cmd == 0) { # F_DUPFD
319     ++$arg while $fh[$arg];
320     open my $fh, "+<&", fileno $fh[$fd]
321     or return -errno2mips;
322     $fh[$arg] = $fh;
323     return $arg;
324     } elsif ($cmd == 1) { # F_GETFD
325     return fcntl $fh[$fd], Fcntl::F_GETFD, 0;
326     } elsif ($cmd == 2) { # F_SETFD
327     return +(fcntl $fh[$fd], Fcntl::F_SETFD, $arg+0)
328     ? 0 : -errno2mips;
329     } elsif ($cmd == 3) { # F_GETFL
330     return O_RDWR; # HACK
331     } else {
332     # 4 F_SETFL
333     warn sprintf"unknown fcntl %d,%x,%x\n", $fd, $cmd, $arg;
334     }
335    
336     -ENOSYS
337     };
338    
339     ############################################
340    
341     $syscall[4001] = sub { # exit
342     strace "exit ($_[0])";
343     exit $_[0];
344     };
345     $syscall[4002] = sub { # fork
346     strace "fork";
347    
348     my $pid = fork;
349     return -errno2mips unless defined $pid;
350     $pid
351     };
352     $syscall[4003] = sub { # read
353     my ($fd, $rbuf, $count) = @_;
354     strace "read (%d, %x, %d)", $fd, $rbuf, $count;
355    
356     $count = sysread $fh[$fd], my $buf, $count;
357    
358     memset $rbuf, $buf;
359    
360     defined $count ? $count : -errno2mips
361     };
362    
363     $syscall[4004] = sub { # write
364     my ($fd, $buf, $count) = @_;
365     strace "write (%d, %x, %d)", $fd, $buf, $count;
366    
367     $count = syswrite $fh[$fd], memget $buf, $count;
368     defined $count ? $count : -errno2mips
369     };
370     $syscall[4005] = sub { # open
371     my ($path, $flags, $mode) = @_;
372     $path = memstr $path;
373     strace "open (%s, %x, %o)", $path, $flags, $mode;
374    
375     if (opendir my $dh, $path) {#d#
376     open my $fh, "</dev/null"or die;
377     my $fd = newfd $fh;
378     $dh[$fd] = $dh;
379     return $fd;
380     }
381    
382     sysopen my $fh, $path, mips2omode $flags, $mode
383     or return -errno2mips;
384    
385     newfd $fh
386     };
387     $syscall[4006] = sub { # close
388     my ($fd) = @_;
389     strace "close ($fd)";
390     fd_valid $fd or return -EBADF;
391    
392     undef $dh[$fd];#d#
393     (close delete $fh[$fd])
394     ? 0 : -errno2mips
395     };
396    
397     $syscall[4010] = sub { # unlink
398     my ($path) = @_;
399     $path = memstr $path;
400     strace "unlink (%s)", $path;
401    
402     (defined unlink $path)
403     ? 0 : -errno2mips
404     };
405     $syscall[4011] = sub { # execve
406     my ($path, $argv, $envv) = @_;
407     $path = memstr $path;
408    
409     for my $vec ($argv, $envv) {
410     my $addr = $vec;
411     $vec = [];
412     while () {
413     my $ptr = unpack "N", memget $addr, 4
414     or last;
415     push @$vec, memstr $ptr;
416     $addr += 4;
417     }
418     }
419    
420     local %ENV;
421     /([^=]*)=(.*)/s, $ENV{$1} = $2
422     for @$envv;
423    
424     ($path, $argv->[0]) = ($argv->[0], $path);
425    
426     reify_fds;
427    
428     exec {$path} @$argv;
429    
430     # not normally printed...
431     strace "execve (%s, [%s], [%s])", $path, (join "|", @$argv), (join "|", @$envv);
432    
433     -errno2mips
434     };
435     $syscall[4012] = sub { # chdir
436     my $path = memstr $_[0];
437     strace "chdir (%s)", $path;
438    
439     (chdir $path) ? 0 : -errno2mips
440     };
441     $syscall[4013] = sub { # time
442     my ($rbuf) = @_;
443     strace "time (%x)", $rbuf;
444    
445     my $time = time;
446    
447     memset $rbuf, pack "N", $time;
448     $time
449     };
450    
451     $syscall[4019] = sub { # lseek
452     my ($fd, $offset, $whence) = @_;
453     strace "lseek (%d, %x, %d)", $fd, $offset, $whence;
454    
455     my $res = sysseek $fh[$fd], $offset, $whence;
456    
457     defined $res
458     ? $res : -errno2mips
459     };
460    
461     $syscall[4020] = sub { strace "getpid ()"; $$ };
462    
463     $syscall[4024] = sub { strace "getuid ()"; $< };
464    
465     $syscall[4033] = sub { # access
466     my ($path, $mode) = @_;
467     $path = memstr $path;
468     strace "acess (%s, %o)", $path, $mode;
469    
470     0
471    
472     };
473    
474     $syscall[4037] = sub { # kill
475     strace "kill ($_[0], $_[1])";
476    
477     (kill $_[1], $_[0]) # todo signal numbers?
478     ? 0 : -errno2mips
479     };
480    
481     $syscall[4041] = sub { # dup
482     my ($fd) = @_;
483     strace "dup ($fd)";
484     fd_valid $fd or return -EBADF;
485    
486     open my $fh, "+<&", fileno $fh[$fd]
487     or return -errno2mips;
488    
489     newfd $fh
490     };
491     $syscall[4042] = sub { # sysm_pipe
492     strace "sysm_pipe ()";
493    
494     pipe my $r, my $w
495     or return -errno2mips;
496    
497     $r = newfd $r;
498     $w = newfd $w;
499    
500     strace "sysm_pipe ($r, $w)";
501    
502     $r3 = $w;
503     $r
504     };
505    
506     $syscall[4045] = sub { # brk
507     strace "brk (%x)", $_[0];
508     my $brk = $_[0];
509     $brk = MINBRK if $brk < MINBRK;
510     $brk
511     }; # brk
512    
513     $syscall[4047] = sub { strace "getgid ()" ; $( };
514    
515     $syscall[4049] = sub { strace "geteuid ()"; $> };
516     $syscall[4050] = sub { strace "getegid ()"; $) };
517    
518     $syscall[4054] = sub { # ioctl
519     my ($fd, $req, $arg) = @_;
520     strace "ioctl (%d, %x, %x)", $fd, $req, $arg;
521     fd_valid $fd or return -EBADF;
522    
523     if ($req == 0x540d) { # TCGETS
524     if (-t $fh[$fd]) {
525     memset $arg, pack "NNNN C C23";
526     return 0;
527     }
528     } elsif ($req == 0x540f) { # TCSETW
529     return 0;
530     } elsif ($req == 0x40047477) { # TIOCGPGRP # irix???
531     memset $arg, pack "N", getpgrp; # hacky, but does...
532     return 0;
533     } elsif ($req == 0x80047476) { # TIOCSPGRP # irix???
534     return 0; # whatever you say...
535     } elsif ($req == 0x40087468) { # TIOCGWINSZ
536     return -ENOSYS;
537     } else {
538     warn sprintf"unknown ioctl %d,%x,%x\n", $fd, $req, $arg;
539     }
540    
541     -ENOTTY
542     };
543     $syscall[4055] = \&sys_fcntl; # fcntl
544    
545     $syscall[4057] = sub { # getpgid
546     strace "getpgid ($_[0])";
547    
548     $_[0] == $$
549     ? getpgrp : $$ # hacky, but it's a bsd'ism
550     };
551    
552     $syscall[4060] = sub { strace "umask (%o)", $_[0]; umask $_[0] };
553    
554     $syscall[4063] = sub { # dup2
555     my ($fd1, $fd2) = @_;
556     strace "dup2 ($fd1, $fd2)";
557     fd_valid $fd1 or return -EBADF;
558    
559     open my $fh, "+<&", fileno $fh[$fd1]
560     or return -errno2mips;
561    
562     $dh[$fd2] = $dh[$fd1];
563     $fh[$fd2] = $fh;
564     $fd2
565     };
566    
567     $syscall[4064] = sub { strace "getppid ()"; getppid };
568     $syscall[4065] = sub { strace "getpgrp ()"; getpgrp };
569    
570     $syscall[4075] = sub { # setrlimit
571     my ($resource, $rlim) = @_;
572     strace "setrlimit (%d, %x)", $resource, $rlim;
573    
574     0
575     };
576     $syscall[4076] = sub { # getrlimit
577     my ($resource, $rlim) = @_;
578     strace "getrlimit (%d, %x)", $resource, $rlim;
579    
580     -ENOSYS
581     };
582    
583     $syscall[4078] = sub { # gettimeofday
584     my ($tv, $tz) = @_;
585     strace "gettimeofday (%x, %x)", $tv, $tz;
586    
587     my ($s, $m) = Time::HiRes::gettimeofday;
588     memset $tv, pack "NN", $s, $m if $tv;
589     memset $tz, pack "NN", 0, 0 if $tz; # minuteswest, dsttime
590    
591     0
592     };
593    
594     $syscall[4080] = sub { # getgroups
595     0
596     };
597    
598     my $mmap_base = 0x1000;
599    
600 root 1.2 $syscall[4090] = sub { # mips_mmap
601 root 1.1 my ($addr, $length, $prot, $flags, $fd, $offset) = @_;
602     strace "mips_mmap (%x, %d, %x, %x, %d, %d)", @_;
603    
604     return -ENOSYS
605     if $flags & 0x001; # MAP_SHARED
606    
607     unless ($addr) {
608     $addr = $mmap_base;
609     $mmap_base += ($length + 4095) & ~4096;
610     }
611    
612     if ($fd >= 0) {
613     my $old = sysseek $fh[$fd], $offset, 0;
614     sysread $fh[$fd], my $buf, $length;
615     memset $addr, $buf;
616     sysseek $fh[$fd], $old, 0;
617     }
618    
619     $addr
620     };
621 root 1.2 $syscall[4091] = sub { # munmap
622     my ($addr, $length) = @_;
623     strace "munmap (%x, %d)", @_;
624    
625     0
626     };
627 root 1.1
628     $syscall[4097] = sub { # setpriority
629     # ...
630     -ENOSYS
631     };
632    
633     $syscall[4099] = sub { # statfs
634     my ($path, $buf) = @_;
635     $path = memstr $path;
636     strace "statfs (%s, %x)", $path, $buf;
637    
638     -ENOSYS
639     };
640     $syscall[4100] = sub { # fstatfs
641     my ($fd, $buf) = @_;
642     strace "fstatfs (%d, %x)", $fd, $buf;
643    
644     -ENOSYS
645     };
646    
647     $syscall[4106] = sub { # newstat
648     my ($path, $buf) = @_;
649     $path = memstr $path;
650     strace "newstat (%s, %x)", $path, $buf;
651    
652     newstat $buf, stat $path
653     };
654     $syscall[4107] = sub { # newlstat
655     my ($path, $buf) = @_;
656     $path = memstr $path;
657     strace "newlstat (%s, %x)", $path, $buf;
658    
659     newstat $buf, lstat $path
660     };
661     $syscall[4108] = sub { # newfstat
662     my ($fd, $buf) = @_;
663     strace "newfstat (%d, %x)", $fd, $buf;
664    
665     newstat $buf, stat $fh[$fd]
666     };
667    
668     $syscall[4114] = sub { # wait4
669     my ($pid, $statusbuf, $options, $rusage) = @_;
670     $pid = unpack "l", pack "L", $pid;
671     strace "wait4 (%d, %x, %x, %x)", $pid, $statusbuf, $options, $rusage;
672    
673     memset $rusage, "\x00" x 72
674     if $rusage;
675    
676     $pid = waitpid $pid, $options;
677    
678     memset $statusbuf, pack "N", $? if $statusbuf;
679    
680     $pid >= 0 ? $pid : -errno2mips
681     };
682    
683     $syscall[4122] = sub { # newuname
684     my ($buf) = @_;
685     strace "newuname (%x)", $buf;
686    
687     memset $buf, pack "a65 a65 a65 a65 a65 a65", #d#
688     "Linux", # sysname
689     "nodename",
690     "4.4-perl", # release
691     "#201901240000 Thu Jan 24 13:59:03 CET 2019", # version
692     "mips", # machine
693     "(none)"; # domainname
694    
695     0
696     };
697    
698     $syscall[4146] = sub { # writev
699     my ($fd, $iov, $iovcnt) = @_;
700    
701     strace "writev (%d, %x, %d)", $fd, $iov, $iovcnt;
702    
703     my $data;
704    
705     while ($iovcnt--) {
706     my ($base, $len) = unpack "NN", memget $iov, 8;
707     $data .= memget $base, $len;
708     $iov += 8;
709     }
710    
711     my $len = syswrite $fh[$fd], $data;
712     defined $len ? $len : -errno2mips
713     };
714    
715     $syscall[4140] = sub { # llseek
716     my ($fd, $high, $low, $result, $whence) = @_;
717     strace "llseek (%d, %x, %08x, %x, %d)", $fd, $high, $low, $result, $whence;
718    
719     my $res = sysseek $fh[$fd], ($high << 32) | $low, $whence;
720    
721     memset $result, pack "NN", ($res >> 32), $res;
722    
723     defined $res
724     ? 0 : -errno2mips
725     };
726    
727     $syscall[4171] = sub { # getpeername
728     -ENOTSOCK
729     };
730    
731     $syscall[4194] = sub { # sigaction
732     my ($signum, $act, $oldact) = @_;
733     strace "sigaction (%d, %x, %x)", $signum, $act, $oldact;
734     -ENOSYS
735     };
736     $syscall[4195] = sub { # sigprocmask
737     my ($how, $set, $oldset) = @_;
738     strace "sigprocmask ($how, $set, $oldset)";
739    
740     -ENOSYS
741     };
742    
743     $syscall[4203] = sub { # getcwd
744     my $cwd = Cwd::getcwd;
745     strace "getcwd (%x, %d)", $_[0], $_[1];
746    
747     if ((length $cwd) < $_[1]) {
748     memset $_[0], "$cwd\x00";
749     return 1 + length $cwd;
750     } else {
751     return -ERANGE;
752     }
753     };
754    
755     $syscall[4213] = sub { # stat64
756     my ($path, $buf) = @_;
757     $path = memstr $path;
758     strace "stat64 (%s, %x)", $path, $buf;
759    
760     stat64 $buf, stat $path
761     };
762     $syscall[4214] = sub { # lstat64
763     my ($path, $buf) = @_;
764     $path = memstr $path;
765     strace "lstat64 (%s, %x)", $path, $buf;
766    
767     stat64 $buf, lstat $path
768     };
769     $syscall[4215] = sub { # fstat64
770     my ($fd, $buf) = @_;
771     strace "fstat64 (%d, %x)", $fd, $buf;
772    
773     stat64 $buf, stat $fh[$fd]
774     };
775    
776     $syscall[4219] = sub { # getdents64
777     my ($fd, $dirp, $count) = @_;
778     strace "getdents64 (%d, %x, %d)", $fd, $dirp, $count;
779    
780     my $name = readdir $dh[$fd];
781    
782     return 0 unless defined $name;
783    
784     my $ino = -1;
785     my $type = 0;
786    
787     my $entry = pack "NN NN n C Z*",
788     $ino >> 32, $ino,
789     0, 0, # offset
790     (length $name) + 20,
791     $type,
792     $name;
793    
794     memset $dirp, $entry;
795     length $entry
796     };
797     $syscall[4220] = \&sys_fcntl; # fcntl64
798    
799     $syscall[4246] = sub { # exit_group
800     POSIX::exit $_[0];
801     };
802    
803     $syscall[4283] = sub { # set_thread_area
804     my ($udesc) = @_;
805    
806     strace "set_thread_area (%x)", $udesc;
807    
808     $tls_udesc = $udesc;
809    
810     return 0;
811     };
812    
813     $syscall[4300] = sub { # faccessat
814     my ($dirfd, $path, $mode, $flags) = @_;
815    
816     strace "faccessat (%d, %s, %o, %x)", $dirfd, $path, $mode, $flags;
817    
818     0 # #d# grant any access
819     };
820    
821     sub sys {
822     my @args = map $_*1,
823     $r4, $r5, $r6, $r7, # first four args in regs
824     # extra arguments on stack
825     $mem[($r29 + 16) >> ADDR_SHIFT][(($r29 + 16) >> 2) & ADDR_MASK],
826     $mem[($r29 + 20) >> ADDR_SHIFT][(($r29 + 20) >> 2) & ADDR_MASK],
827     $mem[($r29 + 24) >> ADDR_SHIFT][(($r29 + 24) >> 2) & ADDR_MASK],
828     $mem[($r29 + 28) >> ADDR_SHIFT][(($r29 + 28) >> 2) & ADDR_MASK],
829     ;
830    
831     $strace = "$r2 (@args)";
832     my $retval = $syscall[$r2](@args);
833     print STDERR "$$ SYS_$strace = $retval\n" if STRACE;
834    
835     if ($retval > -4096 && $retval < 0) {
836     $r2 = -$retval;
837     $r7 = 1;
838     } else {
839     $r2 = $retval;
840     $r7 = 0;
841     }
842     }
843    
844     #############################################################################
845     # mips i big endian cpu emulator
846    
847     ############################################
848     # specials (opcode 0)
849    
850     my ($s, $t, $i); # temporary per-insn variables, declared globally for speed
851    
852     my @special = ('die sprintf "special %d in %08x \@ 0x%08x not supported\n", INSN & 63, INSN, $pc << 2') x 64;
853    
854     $special[ 0] = "RD = (RT << SA ) & M32"; # sll
855    
856     $special[ 2] = 'RD = RT >> SA '; # srl
857    
858     $special[ 3] = 'RD = ((RT - ((RT & B31) << 1)) >> SA ) & M32'; # sra
859     $special[ 4] = 'RD = (RT << (RS & 31)) & M32'; # sllv
860    
861     $special[ 6] = 'RD = RT >> (RS & 31) '; # srlv
862     $special[ 7] = 'RD = ((RT - ((RT & B31) << 1)) >> (RS & 31)) & M32'; # srav
863     $special[ 8] = ' $pc = RS >> 2'; # jr
864     $special[ 9] = 'RD = $pc << 2; $pc = RS >> 2'; # jalr
865    
866     $special[10] = 'RD = RS unless RT'; # movz, MIPS IV
867     $special[11] = 'RD = RS if RT'; # movn, MIPS IV
868    
869     $special[12] = "sys";
870     $special[13] = 'die sprintf "BREAK %0x8x \@ 0x%08x\n", INSN, $pc << 2'; # break
871    
872     $special[15] = ''; # sync, mips2
873     $special[16] = 'RD = $hi'; # mfhi
874     $special[17] = '$hi = RS'; # mthi
875     $special[18] = 'RD = $lo'; # mflo
876     $special[19] = '$lo = RS'; # mtlo
877    
878     $special[24] = '# mult
879     $lo = (RS - ((RS & B31) << 1))
880     * (RT - ((RT & B31) << 1));
881     $hi = ($lo >> 32) & M32;
882     $lo &= M32;
883     ';
884     $special[25] = ' # multu
885     no integer;
886     $lo = RS * RT;
887     $hi = $lo >> 32;
888     $lo &= M32;
889     ';
890     $special[26] = ' # div
891     $s = RS - ((RS & B31) << 1);
892     $t = RT - ((RT & B31) << 1)
893     or return;
894    
895     $lo = ($s / $t) & M32;
896     $hi = ($s % $t) & M32;
897     ';
898     $special[27] = ' # divu
899     RT or return;
900    
901     no integer;
902     $lo = int (RS / RT) & M32;
903     $hi = (RS - $lo * RT) & M32;
904     ';
905    
906     $special[32] = 'die "add instruction unsupported"';
907     $special[33] = "RD = (RS + RT) & M32"; # addu
908     $special[34] = 'die "sub instruction unsupported"';
909     $special[35] = "RD = (RS - RT) & M32"; # subu
910     #$special[32] = $special[33]; # add # buggy, does not trap #d#
911     #$special[34] = $special[35]; # sub # buggy, does not trap #d#
912    
913     $special[36] = "RD = RS & RT "; # and
914     $special[37] = "RD = RS | RT "; # or
915     $special[38] = "RD = RS ^ RT "; # xor
916 root 1.2 $special[39] = "RD = (RS | RT) ^ M32"; # nor
917 root 1.1
918     $special[42] = "RD = (RS - ((RS & B31) << 1)) < (RT - ((RT & B31) << 1))"; # slt
919     $special[43] = "RD = RS < RT "; # sltu
920    
921     # implement some trap insns from mips2, but not all
922     $special[52] = 'die sprintf "TRAP %0x8x \@ 0x%08x\n", INSN, $pc << 2 unless RS'; # teq, mips2
923     $special[54] = 'die sprintf "TRAP %0x8x \@ 0x%08x\n", INSN, $pc << 2 if RS'; # tne, mips2
924    
925     ############################################
926     # regimm (opcode 1)
927    
928     my @regimm = ('die sprintf "regimm %08x \@ 0x%08x not supported\n", INSN & 63, $pc << 2') x 64;
929    
930     $regimm[ 0] = ' $pc += IMM - 1 if RS & 0x80000000'; # bltz
931     $regimm[16] = '$r31 = $pc << 2; $pc += IMM - 1 if RS & 0x80000000'; # bltzal
932     $regimm[ 1] = ' $pc += IMM - 1 unless RS & 0x80000000'; # bgez
933     $regimm[17] = '$r31 = $pc << 2; $pc += IMM - 1 unless RS & 0x80000000'; # bgezal bal
934    
935     ############################################
936     # others
937    
938     my @opcode = (sub { 'die sprintf "opcode %d in %08x \@ 0x%08x not supported\n", INSN >> 26, INSN, $pc << 2' }) x 64;
939    
940     $opcode[ 0] = sub { $special[$insn & 63] }; # special
941     $opcode[ 1] = sub { $regimm[($insn >> 16) & 31] }; # regimm
942     $opcode[ 2] = sub { ' $pc = ($pc & 0x3c000000) | (' . $insn . ' & 0x03ffffff)' }; # j
943     $opcode[ 3] = sub { '$r31 = $pc << 2; $pc = ($pc & 0x3c000000) | (' . $insn . ' & 0x03ffffff)' }; # jal
944     $opcode[ 4] = sub { '$pc += IMM - 1 if RS == RT' }; # beq beqz b
945     $opcode[ 5] = sub { '$pc += IMM - 1 if RS != RT' }; # bne bnez
946     $opcode[ 6] = sub { '$pc += IMM - 1 if !RS || RS >= 0x80000000' }; # blez
947     $opcode[ 7] = sub { '$pc += IMM - 1 if RS && RS < 0x80000000' }; # bgtz
948     $opcode[ 8] = sub { die "addi instruction unsupported" }; # addi
949     $opcode[ 9] = sub { "RT = (RS + IMM) & M32" }; # addiu
950     $opcode[10] = sub { 'RT = ((RS - ((RS & B31) << 1))) < IMM ' }; # slti
951     $opcode[11] = sub { 'RT = RS < (IMM & M32)' }; # sltiu
952     $opcode[12] = sub { 'RT = RS & IMMU' }; # andi
953     $opcode[13] = sub { 'RT = RS | IMMU' }; # ori
954     $opcode[14] = sub { 'RT = RS ^ IMMU' }; # xori
955     $opcode[15] = sub { 'RT = IMMU << 16' }; # lui
956    
957     # opcode 28 is SPECIAL2, extended mips32
958    
959     $opcode[32] = sub {' # lb
960     $i = RS + IMM;
961     $s = (~$i & 3) << 3;
962     $s = $mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK] >> $s;
963     RT = (($s & M8) - (($s & B7) << 1)) & M32;
964     '};
965     $opcode[33] = sub {' # lh
966     $i = RS + IMM;
967     $s = (~$i & 2) << 3;
968     $s = $mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK] >> $s;
969     RT = (($s & M16) - (($s & B15) << 1)) & M32;
970     '};
971     $opcode[34] = sub {' # lwl
972     $i = RS + IMM;
973     $s = ($i & 3) << 3;
974     $i = $mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK];
975     RT = (RT & (M32 >> (32 - $s))) | ($i << $s & M32);
976     '};
977     $opcode[35] = sub {' # lw
978     $i = RS + IMM;
979     RT = $mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK];
980     '};
981     $opcode[36] = sub {' # lbu
982     $i = RS + IMM;
983     $s = (~$i & 3) << 3;
984     RT = ($mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK] >> $s) & M8;
985     '};
986     $opcode[37] = sub {' # lhu
987     $i = RS + IMM;
988     $s = (~$i & 2) << 3;
989     RT = ($mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK] >> $s) & M16;
990     '};
991     $opcode[38] = sub {' # lwr
992     $i = RS + IMM;
993     $s = (($i & 3) + 1) << 3;
994     $i = $mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK];
995     RT = (RT >> $s << $s) | ($i >> (32 - $s));
996     '};
997    
998     $opcode[40] = sub {' # sb
999     $i = RS + IMM;
1000     $s = (~$i & 3) << 3;
1001     $i = \$mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK];
1002     $$i = ($$i & ~(M8 << $s)) | (RT & M8 ) << $s;
1003     '};
1004     $opcode[41] = sub {' # sh
1005     $i = RS + IMM;
1006     $s = (~$i & 2) << 3;
1007     $i = \$mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK];
1008     $$i = ($$i & ~(M16 << $s)) | (RT & M16) << $s;
1009     '};
1010     $opcode[42] = sub {' # swl
1011     $i = RS + IMM;
1012     $s = ((~$i & 3) + 1) << 3;
1013     $i = \$mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK];
1014     $$i = ($$i >> $s << $s) | (RT >> (32 - $s));
1015     '};
1016     $opcode[43] = sub {' # sw
1017     $i = RS + IMM;
1018     $mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK] = RT;
1019     '};
1020    
1021     $opcode[46] = sub {' # swr
1022     $i = RS + IMM;
1023     $s = (($i & 3) + 1) << 3;
1024     $i = \$mem[$i >> ADDR_SHIFT][($i >> 2) & ADDR_MASK];
1025     $$i = $$i & (M32 >> $s) | ((RT << (32 - $s)) & M32);
1026     '};
1027    
1028 root 1.2 # HACK: ignore some coprocessor instructions to make
1029 root 1.1 # binaries work that do not use hardware fp, but the runtime saves/restores
1030     # the registers.
1031     $opcode[53] = sub { '' };
1032     $opcode[61] = sub { '' };
1033    
1034     my $NOP = sub { };
1035    
1036     $insn2sub{ ""} = $NOP; # "undef" insn
1037     $insn2sub{0x00000000} = $NOP; # official mips nop
1038     $insn2sub{0x00200825} = $NOP; # commonly used nop (longsoon2f workaround)
1039     $insn2sub{0x7c03e83b} = sub { '$r3 = $tls_udesc' }; # rdhwr $3, $29 ($29=ULR)- emulated by kernel normally, for thread support
1040    
1041     sub get_insn {
1042     $insn2sub{$_[0]} ||= do {
1043     my $old_insn = $insn;#d#
1044    
1045     $insn = $_[0]*1;
1046     my $src = &{ $opcode[$insn >> 26] };
1047    
1048     $src =~ s/\bIMM\b/($insn & M16) - (($insn & B15) << 1)/ge; # 16 bit signed immediate
1049     $src =~ s/\bIMMU\b/$insn & M16/ge; # 16 bit unsigned immediate
1050    
1051     $src =~ s/\bSA\b/($insn >> 6) & 31/ge; # shift amount
1052    
1053     $src =~ s/\bRS\b/'$r' . (($insn >> 21) & 31)/ge; # s register
1054     $src =~ s/\bRT\b/'$r' . (($insn >> 16) & 31)/ge; # t register
1055     $src =~ s/\bRD\b/'$r' . (($insn >> 11) & 31)/ge; # d register
1056    
1057     $src =~ s/\bINSN\b/$insn/ge; # for use in error messages only
1058    
1059     $src =~ s/\$r0 = //g; # optimize away r0 assignments
1060     $src =~ s/\$r0\b/0/g; # optimise away r0 access
1061    
1062     my $cb = eval "sub { $src }"
1063     || die "$insn<$src>: $@";
1064    
1065     # $insn2src{$insn} = $src;
1066     $sub2insn{$cb+0} = $insn;
1067    
1068     $insn = $old_insn;#d#
1069    
1070     $cb
1071     }
1072     }
1073    
1074     sub cpu_reset($) {
1075     $pc = $_[0] >> 2;
1076    
1077     $r0 = 0; # read-only, constant
1078    
1079     $r1 = $r2 = $r3 = $r4 = $r5 = $r6 = $r7 =
1080     $r8 = $r9 = $r10 = $r11 = $r12 = $r13 = $r14 = $r15 =
1081     $r16 = $r17 = $r18 = $r19 = $r20 = $r21 = $r22 = $r23 =
1082     $r24 = $r25 = $r26 = $r27 = $r28 = $r30 =
1083     $hi = $lo = 0; # was 0xdeadbeef, but kernel initialises to 0
1084    
1085     $r2 = 0;
1086     $r29 = STACK;
1087     $r31 = 0;
1088    
1089     $delay = $NOP; # start with a nop
1090     }
1091    
1092     sub cpu_run() {
1093     while () {
1094     $insn = $delay;
1095    
1096     if (PRIPS) {
1097     unless (++$::n & 0xfffff) {
1098     no integer;
1099     open my $tty, ">/dev/tty";
1100     printf {$tty} "%g ips\n", $::n / (Time::HiRes::time - $::t0);
1101     $::n = 0; $::t0 = Time::HiRes::time;#d#
1102     }
1103     }
1104    
1105     if (PRCPU) {
1106     defined $insn or die "undefined insn access\n";#d#
1107     cpu_pr;
1108     }
1109    
1110     $delay = $mem[$pc >> (ADDR_SHIFT - 2)][$pc & ADDR_MASK];
1111     unless (ref $delay) {
1112     $delay =
1113     $mem[$pc >> (ADDR_SHIFT - 2)][$pc & ADDR_MASK] =
1114     get_insn $delay;
1115     }
1116     ++$pc;
1117    
1118     &$insn;
1119     }
1120     }
1121    
1122     #############################################################################
1123    
1124     sub mips_exec($$;$$) {
1125     my ($path, $argv, $envv, $auxv) = @_;
1126    
1127     mem_reset;
1128    
1129     my $file= ref $path
1130     ? $$path
1131     : do { open my $fh, "<", $path; local $/; <$fh> };
1132    
1133     # 32 bit, msb, elf version
1134     "\x7fELF\x01\x02\x01" eq substr $file, 0, 7
1135     or die "not an elf file, or wrong class, encoding or version";
1136    
1137     my ($e_type, $machine, undef, $entry, $phoff, undef, undef, undef, $phentsize, $phnum) = unpack "nnNNNNNnnn", substr $file, 0x10;
1138    
1139     # $e_type == 2
1140     # or die "file not a (static) executable";
1141    
1142     $machine == 8
1143     or die "file not mips r3000 big endian";
1144    
1145     for my $i (0 .. $phnum - 1) {
1146     my ($type, $offset, $vaddr, $physaddr, $size, $memsz, $flags, $align) =
1147     unpack "N*", substr $file, $phoff + $i * $phentsize, 32;
1148    
1149     $type == 3
1150     and die "elf interpreter loading not supported, run it manually\n";
1151    
1152     next unless $type == 1;
1153     next unless $size;
1154    
1155     $vaddr += 0x55550000 if $e_type == 3; # pie executable (e.g. elf interpreter)
1156    
1157     for my $o (0 .. $size / 4 - 1) {
1158     my $w = unpack "N", substr $file, $offset + $o * 4, 4;
1159     my $a = $vaddr + $o * 4;
1160     # printf "LOAD %08x = %08x\n", $a, $w;
1161     $mem[$a >> ADDR_SHIFT][($a >> 2) & ADDR_MASK] = $w;
1162     }
1163     }
1164    
1165     $entry += 0x55550000 if $e_type == 3; # pie executable (e.g. elf interpreter)
1166    
1167     cpu_reset $entry;
1168    
1169     {
1170     my $str = STACK + 65536;
1171     my $ptr = STACK;
1172    
1173     my $add_int = sub {
1174     memset $ptr, pack "N", $_[0];
1175     $ptr += 4;
1176     };
1177    
1178     my $add_str = sub {
1179     $add_int->($str);
1180     memset $str, "$_[0]\x00";
1181     $str += 1 + length $_[0];
1182     };
1183    
1184     $add_int->(scalar @$argv);
1185     $add_str->($_) for @$argv;
1186     $add_int->(0);
1187    
1188     $add_str->($_) for @$envv;
1189     $add_int->(0);
1190    
1191     # auxv
1192     $add_int->($_->[0]), $add_int->($_->[1])
1193     for @$auxv;
1194    
1195     $add_int->(0);
1196     $add_int->(0);
1197     }
1198     }
1199    
1200     if (0) {
1201     mips_exec
1202     "/tmp/dash-mipsel",
1203     ["./run", @ARGV],
1204     # ["sh", "-c", "for d in 0 1 2 3 4 5 6 7 8 9; do for a in 0 1 2 3 4 5 6 7 8 9; do for b in 0 1 2 3 4 5 6 7 8 9; do for c in 0 1 2 3 4 5 6 7 8 9; do :;done;done;done;done"],
1205     [map "$_=$ENV{$_}", keys %ENV];
1206     } else {
1207    
1208     my $file;
1209    
1210     if (my $data = do { local $/; binmode DATA; <DATA> }) {
1211     $file = \$data;
1212     unshift @ARGV, $0;
1213     } else {
1214     $file = $ARGV[0];
1215     }
1216    
1217     mips_exec $file, \@ARGV, [map "$_=$ENV{$_}", keys %ENV];
1218     }
1219    
1220     $::t0 = Time::HiRes::time;
1221     $::n = 0;
1222     cpu_run;
1223