ComTest.mod 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. MODULE ComTest ;
  2. (* Builds the linked .COM for fixtures and checks it, so the linker is tested
  3. by running it rather than by "it compiled".
  4. Reads fixture paths from stdin like CompileTest, and for each one prints
  5. <path> OK com=<bytes> image=<bytes> data=<bytes>
  6. plus, under @hex, the first bytes and the bytes around the code/data
  7. boundary - the two places where a linker bug would show up.
  8. What is actually being asserted, and why these bytes:
  9. * the file must be LinkSize bytes, i.e. padded past the code to cover
  10. the data area. A .COM shorter than the data area leaves the globals
  11. outside the file, so the first read of a global reads whatever the
  12. loader happened to leave there.
  13. * the file's byte 0 must be the runtime's first byte (8B C0 = MOV AX,AX,
  14. the head of initmem). If it is not, the runtime was not prepended and
  15. every CALL target is wrong.
  16. * the bytes from ImageBytes to the end must be ZERO. That gap is what
  17. the program's globals live in, so an uninitialised filler byte would
  18. be a global that starts as garbage.
  19. The @hex dump is the last line of defence: it is what caught the header
  20. words being written with a nibble shift instead of a byte shift. *)
  21. FROM Posix IMPORT read, write, open, close ;
  22. FROM TextBuf IMPORT TextLimit, Clear, Length, CharAt, InsertCh ;
  23. FROM Compiler IMPORT Compile, CodeBytes, DataBytes, ImageBytes, ImageByteAt, DataBase ;
  24. FROM Linker IMPORT LinkSize, WriteCom ;
  25. FROM SYSTEM IMPORT ADR, BYTE ;
  26. CONST
  27. STDIN = 0 ;
  28. STDOUT = 1 ;
  29. O_RDONLY = 0 ;
  30. O_RDONLY_RB = 0 ;
  31. TYPE
  32. PathBuf = ARRAY [0..511] OF CHAR ;
  33. VAR
  34. lineBuf : ARRAY [0..255] OF CHAR ;
  35. pathCopy : PathBuf ;
  36. outPath : PathBuf ;
  37. hexMode : BOOLEAN ;
  38. hexOn : BOOLEAN ;
  39. PROCEDURE PutCh (ch : CHAR) ;
  40. VAR n : LONGINT ;
  41. BEGIN
  42. n := write (STDOUT, ADR (ch), 1)
  43. END PutCh ;
  44. PROCEDURE PutStr (s : ARRAY OF CHAR) ;
  45. VAR i : CARDINAL ; n : LONGINT ;
  46. BEGIN
  47. i := 0 ;
  48. WHILE (i <= HIGH (s)) AND (s [i] # 0C) DO
  49. n := write (STDOUT, ADR (s [i]), 1) ;
  50. INC (i)
  51. END
  52. END PutStr ;
  53. PROCEDURE PutCard (n : CARDINAL) ;
  54. VAR dig : ARRAY [0..9] OF CHAR ; i : CARDINAL ;
  55. BEGIN
  56. IF n = 0 THEN
  57. PutCh ("0")
  58. ELSE
  59. i := 0 ;
  60. WHILE n > 0 DO
  61. dig [i] := CHR (ORD ("0") + (n MOD 10)) ;
  62. n := n DIV 10 ;
  63. INC (i)
  64. END ;
  65. WHILE i > 0 DO
  66. DEC (i) ;
  67. PutCh (dig [i])
  68. END
  69. END
  70. END PutCard ;
  71. PROCEDURE NL ;
  72. BEGIN
  73. PutCh (CHR (13)) ; PutCh (CHR (10))
  74. END NL ;
  75. PROCEDURE Hex (b : BYTE ; VAR out : ARRAY OF CHAR) ;
  76. VAR d : CARDINAL ;
  77. BEGIN
  78. d := VAL (CARDINAL, b) DIV 16 ;
  79. IF d < 10 THEN
  80. out [0] := CHR (ORD ("0") + d)
  81. ELSE
  82. out [0] := CHR (ORD ("A") + d - 10)
  83. END ;
  84. d := VAL (CARDINAL, b) MOD 16 ;
  85. IF d < 10 THEN
  86. out [1] := CHR (ORD ("0") + d)
  87. ELSE
  88. out [1] := CHR (ORD ("A") + d - 10)
  89. END
  90. END Hex ;
  91. PROCEDURE PutHexNib (d : CARDINAL) ;
  92. VAR c : CHAR ;
  93. BEGIN
  94. IF d < 10 THEN
  95. c := CHR (ORD ("0") + d)
  96. ELSE
  97. c := CHR (ORD ("A") + d - 10)
  98. END ;
  99. PutCh (c)
  100. END PutHexNib ;
  101. PROCEDURE PutCardHex4 (n : CARDINAL) ;
  102. VAR d : CARDINAL ;
  103. BEGIN
  104. d := 4096 ;
  105. WHILE d > 0 DO
  106. PutHexNib ((n DIV d) MOD 16) ;
  107. d := d DIV 16
  108. END ;
  109. PutStr (": ")
  110. END PutCardHex4 ;
  111. PROCEDURE StrCopy (VAR dst : ARRAY OF CHAR ; src : ARRAY OF CHAR) ;
  112. VAR i : CARDINAL ;
  113. BEGIN
  114. i := 0 ;
  115. WHILE (i <= HIGH (dst)) AND (i <= HIGH (src)) AND (src [i] # 0C) DO
  116. dst [i] := src [i] ;
  117. INC (i)
  118. END ;
  119. IF i <= HIGH (dst) THEN
  120. dst [i] := 0C
  121. END
  122. END StrCopy ;
  123. PROCEDURE AppStr (VAR dst : ARRAY OF CHAR ; s : ARRAY OF CHAR ) ;
  124. VAR i, j : CARDINAL ;
  125. BEGIN
  126. i := 0 ;
  127. WHILE (i <= HIGH (dst)) AND (dst [i] # 0C) DO
  128. INC (i)
  129. END ;
  130. j := 0 ;
  131. WHILE (j <= HIGH (s)) AND (s [j] # 0C) AND (i <= HIGH (dst)) DO
  132. dst [i] := s [j] ;
  133. INC (i) ; INC (j)
  134. END ;
  135. IF i <= HIGH (dst) THEN
  136. dst [i] := 0C
  137. END
  138. END AppStr ;
  139. PROCEDURE ReadLineStr (VAR s : ARRAY OF CHAR) : BOOLEAN ;
  140. VAR ch : CHAR ; n : LONGINT ; i : CARDINAL ;
  141. BEGIN
  142. i := 0 ; s [0] := 0C ;
  143. LOOP
  144. n := read (STDIN, ADR (ch), 1) ;
  145. IF n # 1 THEN
  146. IF i = 0 THEN RETURN FALSE END ;
  147. s [i] := 0C ; RETURN TRUE
  148. END ;
  149. IF (ch = CHR (10)) OR (ch = CHR (13)) THEN
  150. IF i = 0 THEN RETURN FALSE END ;
  151. s [i] := 0C ; RETURN TRUE
  152. END ;
  153. IF i < HIGH (s) THEN
  154. s [i] := ch ; INC (i)
  155. END
  156. END
  157. END ReadLineStr ;
  158. PROCEDURE LoadFile (p : ARRAY OF CHAR) : BOOLEAN ;
  159. (* same CR/LF normalisation as the shell's LoadWorkFile *)
  160. VAR fd : INTEGER ; k : LONGINT ; b : BYTE ; prevCR : BOOLEAN ;
  161. z : ARRAY [0..511] OF CHAR ;
  162. BEGIN
  163. StrCopy (z, p) ;
  164. fd := open (ADR (z), O_RDONLY, 0) ;
  165. IF fd < 0 THEN
  166. RETURN FALSE
  167. END ;
  168. Clear ;
  169. prevCR := FALSE ;
  170. LOOP
  171. IF Length () >= TextLimit THEN EXIT END ;
  172. k := read (fd, ADR (b), 1) ;
  173. IF k # 1 THEN EXIT END ;
  174. IF b = 26 THEN
  175. EXIT
  176. ELSIF b = 10 THEN
  177. IF NOT prevCR THEN InsertCh (Length (), CHR (13)) END ;
  178. prevCR := FALSE
  179. ELSIF b = 13 THEN
  180. InsertCh (Length (), CHR (13)) ;
  181. prevCR := TRUE
  182. ELSE
  183. InsertCh (Length (), CHR (ORD (b))) ;
  184. prevCR := FALSE
  185. END
  186. END ;
  187. k := close (fd) ;
  188. RETURN TRUE
  189. END LoadFile ;
  190. (* ---- reading the .COM back ---------------------------------------- *)
  191. VAR
  192. fbuf : ARRAY [0..65535] OF BYTE ;
  193. fsize : CARDINAL ;
  194. PROCEDURE ReadCom (p : ARRAY OF CHAR) : BOOLEAN ;
  195. VAR fd : INTEGER ; k : LONGINT ; z : ARRAY [0..511] OF CHAR ;
  196. BEGIN
  197. StrCopy (z, p) ;
  198. fd := open (ADR (z), O_RDONLY, 0) ;
  199. IF fd < 0 THEN
  200. RETURN FALSE
  201. END ;
  202. fsize := 0 ;
  203. LOOP
  204. IF fsize > 65535 THEN EXIT END ;
  205. k := read (fd, ADR (fbuf [fsize]), 65535 - fsize) ;
  206. IF k <= 0 THEN EXIT END ;
  207. fsize := fsize + VAL (CARDINAL, k)
  208. END ;
  209. k := close (fd) ;
  210. RETURN TRUE
  211. END ReadCom ;
  212. PROCEDURE DumpAt (at, cnt : CARDINAL) ;
  213. VAR i, n : CARDINAL ; hx : ARRAY [0..1] OF CHAR ;
  214. BEGIN
  215. PutStr (" " ) ;
  216. PutCardHex4 (at) ;
  217. n := 0 ;
  218. WHILE (n < cnt) AND (at + n < fsize) DO
  219. Hex (fbuf [at + n], hx) ;
  220. PutCh (hx [0]) ; PutCh (hx [1]) ; PutCh (" ") ;
  221. INC (n)
  222. END ;
  223. NL
  224. END DumpAt ;
  225. (* Count non-zero bytes in [a,b) - the gap between the code and the data must
  226. be entirely zero, or a program's globals start as garbage. *)
  227. PROCEDURE NonZero (a, b : CARDINAL) : CARDINAL ;
  228. VAR i, k : CARDINAL ;
  229. BEGIN
  230. k := 0 ; i := a ;
  231. WHILE (i < b) AND (i < fsize) DO
  232. IF fbuf [i] # 0 THEN
  233. k := k + 1
  234. END ;
  235. INC (i)
  236. END ;
  237. RETURN k
  238. END NonZero ;
  239. PROCEDURE BaseName (src : PathBuf ; VAR dst : PathBuf) ;
  240. (* the fixture's own name, so the .COM lands beside us as NAME.COM rather
  241. than trying to graft a directory onto the source path.
  242. FIXED array formals, not open ones: with `VAR dst : ARRAY OF CHAR` the
  243. write did not reach the caller's buffer at all, so the .COM was written to
  244. a file named after whatever garbage was in the destination - literally
  245. "END", taken from adjacent string data. An open-array VAR formal is not
  246. worth the subtlety in a test harness. *)
  247. VAR i, j, last : CARDINAL ;
  248. BEGIN
  249. i := 0 ; j := 0 ; last := 0 ;
  250. WHILE (i <= HIGH (src)) AND (src [i] # 0C) DO
  251. IF src [i] = "/" THEN
  252. last := i + 1
  253. END ;
  254. INC (i)
  255. END ;
  256. i := last ;
  257. WHILE (i <= HIGH (src)) AND (src [i] # 0C) AND (j <= HIGH (dst)) DO
  258. dst [j] := src [i] ;
  259. INC (i) ; INC (j)
  260. END ;
  261. (* drop the .pas and add .COM *)
  262. IF j > 4 THEN
  263. IF (dst [j - 4] = ".") AND (dst [j - 3] = "p") AND (dst [j - 2] = "a")
  264. AND (dst [j - 1] = "s") THEN
  265. j := j - 4
  266. END
  267. END ;
  268. IF j + 4 <= HIGH (dst) THEN
  269. dst [j] := "." ; dst [j + 1] := "C" ; dst [j + 2] := "O" ;
  270. dst [j + 3] := "M" ; dst [j + 4] := 0C
  271. END
  272. END BaseName ;
  273. PROCEDURE IsHexCmd () : BOOLEAN ;
  274. VAR i : CARDINAL ;
  275. BEGIN
  276. IF lineBuf [0] # "@" THEN RETURN FALSE END ;
  277. i := 0 ;
  278. WHILE (i <= HIGH (lineBuf)) AND (lineBuf [i] # 0C) DO INC (i) END ;
  279. IF i # 4 THEN RETURN FALSE END ;
  280. RETURN (lineBuf [1] = "h") AND (lineBuf [2] = "e") AND (lineBuf [3] = "x")
  281. END IsHexCmd ;
  282. VAR
  283. errNo, errPos : CARDINAL ;
  284. ok : BOOLEAN ;
  285. zout : PathBuf ;
  286. nz : CARDINAL ;
  287. BEGIN
  288. PutStr ("FIXTURE RESULT") ;
  289. NL ;
  290. WHILE ReadLineStr (lineBuf) DO
  291. IF IsHexCmd () THEN
  292. hexOn := TRUE
  293. ELSE
  294. StrCopy (pathCopy, lineBuf) ;
  295. IF LoadFile (pathCopy) THEN
  296. ok := Compile (errNo, errPos) ;
  297. IF ok THEN
  298. BaseName (pathCopy, zout) ;
  299. IF WriteCom (zout) THEN
  300. IF ReadCom (zout) THEN
  301. nz := NonZero (ImageBytes (), fsize) ;
  302. PutStr (lineBuf) ;
  303. PutStr (" OK com=") ;
  304. PutCard (fsize) ;
  305. PutStr (" image=") ;
  306. PutCard (ImageBytes ()) ;
  307. PutStr (" data=") ;
  308. PutCard (DataBytes ()) ;
  309. PutStr (" nonzeroInGap=") ;
  310. PutCard (nz) ;
  311. NL ;
  312. IF hexOn THEN
  313. DumpAt (0, 16) ; (* runtime head *)
  314. DumpAt (ImageBytes () - 16, 16) ; (* code tail *)
  315. DumpAt (ImageBytes (), 16) (* the gap *)
  316. END
  317. ELSE
  318. PutStr (lineBuf) ; PutStr (" CANNOT READ BACK") ; NL
  319. END
  320. ELSE
  321. PutStr (lineBuf) ; PutStr (" WRITE_COM_FAILED") ; NL
  322. END
  323. ELSE
  324. PutStr (lineBuf) ; PutStr (" ERROR ") ;
  325. PutCard (errNo) ; PutStr (" at pos ") ; PutCard (errPos) ; NL
  326. END
  327. ELSE
  328. PutStr (lineBuf) ; PutStr (" CANNOT OPEN") ; NL
  329. END
  330. END
  331. END
  332. END ComTest.