Exec86Run.mod 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. MODULE Exec86Run ;
  2. (* Exec86Run -- run one .COM under the Exec86 interpreter and report what
  3. happened, so that a Python harness can compare it against qemu.
  4. stdin is: one line naming the .COM, then everything else is the guest's
  5. input. That line is read a byte at a time through the raw read(), not
  6. through any buffered reader, because the guest reads fd 0 itself: a stdio
  7. buffer that had already swallowed the first few input bytes would look
  8. exactly like a program that parses its input wrongly, and that is the
  9. kind of failure that gets blamed on the code under test.
  10. stdout belongs to the guest and is never written to here.
  11. stderr carries exactly one line
  12. exec86: status=<0|1|2> exit=<n> steps=<n>
  13. where 0 = halted through INT 21h AH=4Ch, 1 = fault (its diagnostic is on
  14. stderr just before this line), 2 = the runaway step limit. The harness
  15. asserts on that line rather than on the process exit code: the guest is
  16. free to exit with 126, and an exit code that cannot tell "the program
  17. returned 126" from "the interpreter broke" would be an assertion with a
  18. hole in it. The process exit code is set to agree anyway - the guest's
  19. own code when status is 0, 126 on a fault, 127 on the step limit - so
  20. that running this by hand in a shell still says something useful.
  21. The input is delivered on fd 0 rather than through the memory descriptor
  22. bootcom.s uses (INLEN at 2000h, INBUF at 2004h). The two are the same
  23. machine as far as a guest can tell: bootcom returns the descriptor's bytes
  24. in order and then 1Ah for ever, and fd 0 returns the same bytes in order
  25. and then read() = 0, which is turned into 1Ah for ever by the same
  26. clause. They agree exactly when the pipe holds exactly the fixture's
  27. .in file, which is what the harness feeds. Writing that down because
  28. "we feed it differently and it still matched" is precisely the kind of
  29. agreement that stops holding the day someone appends a newline. *)
  30. FROM Posix IMPORT read, write, open, close ;
  31. FROM Exec86 IMPORT Clear86, Poke86, Run86 ;
  32. FROM SYSTEM IMPORT ADR, BYTE ;
  33. CONST
  34. STDIN = 0 ;
  35. STDERR = 2 ;
  36. O_RDONLY = 0 ;
  37. LoadAt = 100H ; (* DOS's .COM entry point, same as bootcom's *)
  38. VAR
  39. path : ARRAY [0..511] OF CHAR ;
  40. fbuf : ARRAY [0..65535] OF BYTE ;
  41. fsize : CARDINAL ;
  42. PROCEDURE PutS (s : ARRAY OF CHAR) ;
  43. VAR i : CARDINAL ;
  44. n : LONGINT ;
  45. c : CHAR ;
  46. BEGIN
  47. i := 0 ;
  48. WHILE (i <= HIGH (s)) AND (s [i] # 0C) DO
  49. c := s [i] ;
  50. n := write (STDERR, ADR (c), 1) ;
  51. i := i + 1
  52. END
  53. END PutS ;
  54. PROCEDURE PutCh (c : CHAR) ;
  55. VAR n : LONGINT ;
  56. BEGIN
  57. n := write (STDERR, ADR (c), 1)
  58. END PutCh ;
  59. PROCEDURE PutLong (n : LONGCARD) ;
  60. (* Long enough for LONGCARD itself, though the only value printed with it is
  61. the step count, which the runaway cap already bounds below CARDINAL's
  62. maximum. Printed anyway rather than narrowed with VAL, because narrowing
  63. a counter is how a count that has grown past its type stops being a count. *)
  64. VAR dig : ARRAY [0..20] OF CHAR ;
  65. i : CARDINAL ;
  66. v : LONGCARD ;
  67. BEGIN
  68. i := 20 ;
  69. dig [20] := 0C ;
  70. v := n ;
  71. REPEAT
  72. i := i - 1 ;
  73. dig [i] := CHR (ORD ('0') + VAL (CARDINAL, v MOD VAL (LONGCARD, 10))) ;
  74. v := v DIV VAL (LONGCARD, 10)
  75. UNTIL v = 0 ;
  76. (* digits occupy i..19 with the most significant first, and dig[20] was
  77. set only so that the arithmetic above cannot walk off the end. *)
  78. WHILE i <= 19 DO
  79. PutCh (dig [i]) ;
  80. i := i + 1
  81. END
  82. END PutLong ;
  83. PROCEDURE PutN (v : CARDINAL) ;
  84. VAR dig : ARRAY [0..10] OF CHAR ;
  85. i, x : CARDINAL ;
  86. n : LONGINT ;
  87. c : CHAR ;
  88. BEGIN
  89. i := 10 ;
  90. dig [10] := 0C ;
  91. x := v ;
  92. REPEAT
  93. i := i - 1 ;
  94. dig [i] := CHR (ORD ('0') + x MOD 10) ;
  95. x := x DIV 10
  96. UNTIL x = 0 ;
  97. x := i ;
  98. WHILE x <= 9 DO
  99. c := dig [x] ;
  100. n := write (STDERR, ADR (c), 1) ;
  101. x := x + 1
  102. END
  103. END PutN ;
  104. PROCEDURE Terminate (code : CARDINAL) ;
  105. BEGIN
  106. HALT (code)
  107. END Terminate ;
  108. PROCEDURE Die (msg : ARRAY OF CHAR; code : CARDINAL) ;
  109. BEGIN
  110. PutS ("exec86run: ") ;
  111. PutS (msg) ;
  112. PutCh (CHR (10)) ;
  113. Terminate (code)
  114. END Die ;
  115. PROCEDURE ReadPathLine () : BOOLEAN ;
  116. (* Reads the first line of stdin into `path', one byte at a time through
  117. read(). Returns FALSE at end of input with nothing read. *)
  118. VAR ch : CHAR ;
  119. n : LONGINT ;
  120. i : CARDINAL ;
  121. BEGIN
  122. i := 0 ;
  123. LOOP
  124. n := read (STDIN, ADR (ch), 1) ;
  125. IF n # 1 THEN
  126. path [i] := 0C ;
  127. RETURN i > 0
  128. END ;
  129. IF (ch = CHR (10)) OR (ch = CHR (13)) THEN
  130. path [i] := 0C ;
  131. RETURN TRUE
  132. END ;
  133. IF i < HIGH (path) - 1 THEN
  134. path [i] := ch ;
  135. i := i + 1
  136. END
  137. END
  138. END ReadPathLine ;
  139. PROCEDURE ReadCom () : BOOLEAN ;
  140. VAR fd, k : LONGINT ;
  141. z : ARRAY [0..511] OF CHAR ;
  142. i : CARDINAL ;
  143. BEGIN
  144. i := 0 ;
  145. WHILE (i <= HIGH (path)) AND (i < HIGH (z)) AND (path [i] # 0C) DO
  146. z [i] := path [i] ;
  147. i := i + 1
  148. END ;
  149. z [i] := 0C ;
  150. fd := open (ADR (z), O_RDONLY, 0) ;
  151. IF fd < 0 THEN
  152. RETURN FALSE
  153. END ;
  154. fsize := 0 ;
  155. LOOP
  156. IF fsize >= 65536 THEN EXIT END ;
  157. k := read (fd, ADR (fbuf [fsize]), VAL (LONGCARD, 65536 - fsize)) ;
  158. IF k <= 0 THEN EXIT END ;
  159. fsize := fsize + VAL (CARDINAL, k)
  160. END ;
  161. k := close (fd) ;
  162. RETURN TRUE
  163. END ReadCom ;
  164. VAR
  165. exitCode : CARDINAL ;
  166. steps : LONGCARD ;
  167. ix, st : CARDINAL ;
  168. BEGIN
  169. IF NOT ReadPathLine () THEN
  170. Die ("no .COM path on stdin", 125)
  171. END ;
  172. IF NOT ReadCom () THEN
  173. Die ("cannot open the .COM", 125)
  174. END ;
  175. IF fsize = 0 THEN
  176. Die ("the .COM is empty", 125)
  177. END ;
  178. IF fsize > 65536 - LoadAt THEN
  179. Die ("the .COM does not fit below 10000h", 125)
  180. END ;
  181. Clear86 ;
  182. ix := 0 ;
  183. WHILE ix < fsize DO
  184. Poke86 (LoadAt + ix, ORD (fbuf [ix])) ;
  185. ix := ix + 1
  186. END ;
  187. st := Run86 (exitCode, steps) ;
  188. PutS ("exec86: status=") ;
  189. PutN (st) ;
  190. PutS (" exit=") ;
  191. PutN (exitCode) ;
  192. PutS (" steps=") ;
  193. PutLong (steps) ;
  194. PutCh (CHR (10)) ;
  195. IF st = 0 THEN
  196. Terminate (exitCode)
  197. ELSIF st = 1 THEN
  198. Terminate (126)
  199. ELSE
  200. Terminate (127)
  201. END
  202. END Exec86Run.