ComTest.mod 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  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, SetSource ;
  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. SetSource (z) ; (* {$I} resolves beside this file *)
  169. Clear ;
  170. prevCR := FALSE ;
  171. LOOP
  172. IF Length () >= TextLimit THEN EXIT END ;
  173. k := read (fd, ADR (b), 1) ;
  174. IF k # 1 THEN EXIT END ;
  175. IF b = 26 THEN
  176. EXIT
  177. ELSIF b = 10 THEN
  178. IF NOT prevCR THEN InsertCh (Length (), CHR (13)) END ;
  179. prevCR := FALSE
  180. ELSIF b = 13 THEN
  181. InsertCh (Length (), CHR (13)) ;
  182. prevCR := TRUE
  183. ELSE
  184. InsertCh (Length (), CHR (ORD (b))) ;
  185. prevCR := FALSE
  186. END
  187. END ;
  188. k := close (fd) ;
  189. RETURN TRUE
  190. END LoadFile ;
  191. (* ---- reading the .COM back ---------------------------------------- *)
  192. VAR
  193. fbuf : ARRAY [0..65535] OF BYTE ;
  194. fsize : CARDINAL ;
  195. PROCEDURE ReadCom (p : ARRAY OF CHAR) : BOOLEAN ;
  196. VAR fd : INTEGER ; k : LONGINT ; z : ARRAY [0..511] OF CHAR ;
  197. BEGIN
  198. StrCopy (z, p) ;
  199. fd := open (ADR (z), O_RDONLY, 0) ;
  200. IF fd < 0 THEN
  201. RETURN FALSE
  202. END ;
  203. fsize := 0 ;
  204. LOOP
  205. IF fsize > 65535 THEN EXIT END ;
  206. k := read (fd, ADR (fbuf [fsize]), 65535 - fsize) ;
  207. IF k <= 0 THEN EXIT END ;
  208. fsize := fsize + VAL (CARDINAL, k)
  209. END ;
  210. k := close (fd) ;
  211. RETURN TRUE
  212. END ReadCom ;
  213. PROCEDURE DumpAt (at, cnt : CARDINAL) ;
  214. VAR i, n : CARDINAL ; hx : ARRAY [0..1] OF CHAR ;
  215. BEGIN
  216. PutStr (" " ) ;
  217. PutCardHex4 (at) ;
  218. n := 0 ;
  219. WHILE (n < cnt) AND (at + n < fsize) DO
  220. Hex (fbuf [at + n], hx) ;
  221. PutCh (hx [0]) ; PutCh (hx [1]) ; PutCh (" ") ;
  222. INC (n)
  223. END ;
  224. NL
  225. END DumpAt ;
  226. (* Count non-zero bytes in [a,b) - the gap between the code and the data must
  227. be entirely zero, or a program's globals start as garbage. *)
  228. PROCEDURE NonZero (a, b : CARDINAL) : CARDINAL ;
  229. VAR i, k : CARDINAL ;
  230. BEGIN
  231. k := 0 ; i := a ;
  232. WHILE (i < b) AND (i < fsize) DO
  233. IF fbuf [i] # 0 THEN
  234. k := k + 1
  235. END ;
  236. INC (i)
  237. END ;
  238. RETURN k
  239. END NonZero ;
  240. PROCEDURE BaseName (src : PathBuf ; VAR dst : PathBuf) ;
  241. (* the fixture's own name, so the .COM lands beside us as NAME.COM rather
  242. than trying to graft a directory onto the source path.
  243. FIXED array formals, not open ones: with `VAR dst : ARRAY OF CHAR` the
  244. write did not reach the caller's buffer at all, so the .COM was written to
  245. a file named after whatever garbage was in the destination - literally
  246. "END", taken from adjacent string data. An open-array VAR formal is not
  247. worth the subtlety in a test harness. *)
  248. VAR i, j, last : CARDINAL ;
  249. BEGIN
  250. i := 0 ; j := 0 ; last := 0 ;
  251. WHILE (i <= HIGH (src)) AND (src [i] # 0C) DO
  252. IF src [i] = "/" THEN
  253. last := i + 1
  254. END ;
  255. INC (i)
  256. END ;
  257. i := last ;
  258. WHILE (i <= HIGH (src)) AND (src [i] # 0C) AND (j <= HIGH (dst)) DO
  259. dst [j] := src [i] ;
  260. INC (i) ; INC (j)
  261. END ;
  262. (* drop the .pas and add .COM *)
  263. IF j > 4 THEN
  264. IF (dst [j - 4] = ".") AND (dst [j - 3] = "p") AND (dst [j - 2] = "a")
  265. AND (dst [j - 1] = "s") THEN
  266. j := j - 4
  267. END
  268. END ;
  269. IF j + 4 <= HIGH (dst) THEN
  270. dst [j] := "." ; dst [j + 1] := "C" ; dst [j + 2] := "O" ;
  271. dst [j + 3] := "M" ; dst [j + 4] := 0C
  272. END
  273. END BaseName ;
  274. PROCEDURE IsHexCmd () : BOOLEAN ;
  275. VAR i : CARDINAL ;
  276. BEGIN
  277. IF lineBuf [0] # "@" THEN RETURN FALSE END ;
  278. i := 0 ;
  279. WHILE (i <= HIGH (lineBuf)) AND (lineBuf [i] # 0C) DO INC (i) END ;
  280. IF i # 4 THEN RETURN FALSE END ;
  281. RETURN (lineBuf [1] = "h") AND (lineBuf [2] = "e") AND (lineBuf [3] = "x")
  282. END IsHexCmd ;
  283. VAR
  284. errNo, errPos : CARDINAL ;
  285. ok : BOOLEAN ;
  286. zout : PathBuf ;
  287. nz : CARDINAL ;
  288. BEGIN
  289. PutStr ("FIXTURE RESULT") ;
  290. NL ;
  291. WHILE ReadLineStr (lineBuf) DO
  292. IF IsHexCmd () THEN
  293. hexOn := TRUE
  294. ELSE
  295. StrCopy (pathCopy, lineBuf) ;
  296. IF LoadFile (pathCopy) THEN
  297. ok := Compile (errNo, errPos) ;
  298. IF ok THEN
  299. BaseName (pathCopy, zout) ;
  300. IF WriteCom (zout) THEN
  301. IF ReadCom (zout) THEN
  302. nz := NonZero (ImageBytes (), fsize) ;
  303. PutStr (lineBuf) ;
  304. PutStr (" OK com=") ;
  305. PutCard (fsize) ;
  306. PutStr (" image=") ;
  307. PutCard (ImageBytes ()) ;
  308. PutStr (" data=") ;
  309. PutCard (DataBytes ()) ;
  310. PutStr (" nonzeroInGap=") ;
  311. PutCard (nz) ;
  312. NL ;
  313. IF hexOn THEN
  314. DumpAt (0, 16) ; (* runtime head *)
  315. DumpAt (ImageBytes () - 16, 16) ; (* code tail *)
  316. DumpAt (ImageBytes (), 16) (* the gap *)
  317. END
  318. ELSE
  319. PutStr (lineBuf) ; PutStr (" CANNOT READ BACK") ; NL
  320. END
  321. ELSE
  322. PutStr (lineBuf) ; PutStr (" WRITE_COM_FAILED") ; NL
  323. END
  324. ELSE
  325. PutStr (lineBuf) ; PutStr (" ERROR ") ;
  326. PutCard (errNo) ; PutStr (" at pos ") ; PutCard (errPos) ; NL
  327. END
  328. ELSE
  329. PutStr (lineBuf) ; PutStr (" CANNOT OPEN") ; NL
  330. END
  331. END
  332. END
  333. END ComTest.