ag386int.pas 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. {
  2. $Id$
  3. Copyright (c) 1998-2000 by Florian Klaempfl
  4. This unit implements an asmoutput class for Intel syntax with Intel i386+
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program; if not, write to the Free Software
  15. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  16. ****************************************************************************
  17. }
  18. unit ag386int;
  19. {$i defines.inc}
  20. interface
  21. uses aasm,assemble;
  22. type
  23. pi386intasmlist=^ti386intasmlist;
  24. ti386intasmlist = object(tasmlist)
  25. procedure WriteTree(p:TAAsmoutput);virtual;
  26. procedure WriteAsmList;virtual;
  27. procedure WriteExternals;
  28. end;
  29. implementation
  30. uses
  31. {$ifdef delphi}
  32. sysutils,
  33. {$endif}
  34. cutils,globtype,globals,systems,cobjects,
  35. verbose,cpubase,cpuasm
  36. {$ifdef extdebug}
  37. ,fmodule
  38. {$endif extdebug}
  39. ;
  40. const
  41. line_length = 70;
  42. function single2str(d : single) : string;
  43. var
  44. hs : string;
  45. p : byte;
  46. begin
  47. str(d,hs);
  48. { nasm expects a lowercase e }
  49. p:=pos('E',hs);
  50. if p>0 then
  51. hs[p]:='e';
  52. p:=pos('+',hs);
  53. if p>0 then
  54. delete(hs,p,1);
  55. single2str:=lower(hs);
  56. end;
  57. function double2str(d : double) : string;
  58. var
  59. hs : string;
  60. p : byte;
  61. begin
  62. str(d,hs);
  63. { nasm expects a lowercase e }
  64. p:=pos('E',hs);
  65. if p>0 then
  66. hs[p]:='e';
  67. p:=pos('+',hs);
  68. if p>0 then
  69. delete(hs,p,1);
  70. double2str:=lower(hs);
  71. end;
  72. function extended2str(e : extended) : string;
  73. var
  74. hs : string;
  75. p : byte;
  76. begin
  77. str(e,hs);
  78. { nasm expects a lowercase e }
  79. p:=pos('E',hs);
  80. if p>0 then
  81. hs[p]:='e';
  82. p:=pos('+',hs);
  83. if p>0 then
  84. delete(hs,p,1);
  85. extended2str:=lower(hs);
  86. end;
  87. function comp2str(d : bestreal) : string;
  88. type
  89. pdouble = ^double;
  90. var
  91. c : comp;
  92. dd : pdouble;
  93. begin
  94. {$ifdef FPC}
  95. c:=comp(d);
  96. {$else}
  97. c:=d;
  98. {$endif}
  99. dd:=pdouble(@c); { this makes a bitwise copy of c into a double }
  100. comp2str:=double2str(dd^);
  101. end;
  102. function getreferencestring(var ref : treference) : string;
  103. var
  104. s : string;
  105. first : boolean;
  106. begin
  107. if ref.is_immediate then
  108. begin
  109. getreferencestring:=tostr(ref.offset);
  110. exit;
  111. end
  112. else
  113. with ref do
  114. begin
  115. first:=true;
  116. inc(offset,offsetfixup);
  117. offsetfixup:=0;
  118. if ref.segment<>R_NO then
  119. s:=int_reg2str[segment]+':['
  120. else
  121. s:='[';
  122. if assigned(symbol) then
  123. begin
  124. s:=s+symbol^.name;
  125. first:=false;
  126. end;
  127. if (base<>R_NO) then
  128. begin
  129. if not(first) then
  130. s:=s+'+'
  131. else
  132. first:=false;
  133. s:=s+int_reg2str[base];
  134. end;
  135. if (index<>R_NO) then
  136. begin
  137. if not(first) then
  138. s:=s+'+'
  139. else
  140. first:=false;
  141. s:=s+int_reg2str[index];
  142. if scalefactor<>0 then
  143. s:=s+'*'+tostr(scalefactor);
  144. end;
  145. if offset<0 then
  146. s:=s+tostr(offset)
  147. else if (offset>0) then
  148. s:=s+'+'+tostr(offset);
  149. s:=s+']';
  150. end;
  151. getreferencestring:=s;
  152. end;
  153. function getopstr(const o:toper;s : topsize; opcode: tasmop;dest : boolean) : string;
  154. var
  155. hs : string;
  156. begin
  157. case o.typ of
  158. top_reg :
  159. getopstr:=int_reg2str[o.reg];
  160. top_const :
  161. getopstr:=tostr(o.val);
  162. top_symbol :
  163. begin
  164. if assigned(o.sym) then
  165. hs:='offset '+o.sym^.name
  166. else
  167. hs:='offset ';
  168. if o.symofs>0 then
  169. hs:=hs+'+'+tostr(o.symofs)
  170. else
  171. if o.symofs<0 then
  172. hs:=hs+tostr(o.symofs)
  173. else
  174. if not(assigned(o.sym)) then
  175. hs:=hs+'0';
  176. getopstr:=hs;
  177. end;
  178. top_ref :
  179. begin
  180. hs:=getreferencestring(o.ref^);
  181. if ((opcode <> A_LGS) and (opcode <> A_LSS) and
  182. (opcode <> A_LFS) and (opcode <> A_LDS) and
  183. (opcode <> A_LES)) then
  184. Begin
  185. case s of
  186. S_B : hs:='byte ptr '+hs;
  187. S_W : hs:='word ptr '+hs;
  188. S_L : hs:='dword ptr '+hs;
  189. S_IS : hs:='word ptr '+hs;
  190. S_IL : hs:='dword ptr '+hs;
  191. S_IQ : hs:='qword ptr '+hs;
  192. S_FS : hs:='dword ptr '+hs;
  193. S_FL : hs:='qword ptr '+hs;
  194. S_FX : hs:='tbyte ptr '+hs;
  195. S_BW : if dest then
  196. hs:='word ptr '+hs
  197. else
  198. hs:='byte ptr '+hs;
  199. S_BL : if dest then
  200. hs:='dword ptr '+hs
  201. else
  202. hs:='byte ptr '+hs;
  203. S_WL : if dest then
  204. hs:='dword ptr '+hs
  205. else
  206. hs:='word ptr '+hs;
  207. end;
  208. end;
  209. getopstr:=hs;
  210. end;
  211. else
  212. internalerror(10001);
  213. end;
  214. end;
  215. function getopstr_jmp(const o:toper) : string;
  216. var
  217. hs : string;
  218. begin
  219. case o.typ of
  220. top_reg :
  221. getopstr_jmp:=int_reg2str[o.reg];
  222. top_const :
  223. getopstr_jmp:=tostr(o.val);
  224. top_symbol :
  225. begin
  226. hs:=o.sym^.name;
  227. if o.symofs>0 then
  228. hs:=hs+'+'+tostr(o.symofs)
  229. else
  230. if o.symofs<0 then
  231. hs:=hs+tostr(o.symofs);
  232. getopstr_jmp:=hs;
  233. end;
  234. top_ref :
  235. getopstr_jmp:=getreferencestring(o.ref^);
  236. else
  237. internalerror(10001);
  238. end;
  239. end;
  240. {****************************************************************************
  241. TI386INTASMLIST
  242. ****************************************************************************}
  243. var
  244. LastSec : tsection;
  245. const
  246. ait_const2str:array[ait_const_32bit..ait_const_8bit] of string[8]=
  247. (#9'DD'#9,#9'DW'#9,#9'DB'#9);
  248. Function PadTabs(const p:string;addch:char):string;
  249. var
  250. s : string;
  251. i : longint;
  252. begin
  253. i:=length(p);
  254. if addch<>#0 then
  255. begin
  256. inc(i);
  257. s:=p+addch;
  258. end
  259. else
  260. s:=p;
  261. if i<8 then
  262. PadTabs:=s+#9#9
  263. else
  264. PadTabs:=s+#9;
  265. end;
  266. procedure ti386intasmlist.WriteTree(p:TAAsmoutput);
  267. const
  268. allocstr : array[boolean] of string[10]=(' released',' allocated');
  269. var
  270. s,
  271. prefix,
  272. suffix : string;
  273. hp : tai;
  274. counter,
  275. lines,
  276. i,j,l : longint;
  277. consttyp : tait;
  278. found,
  279. quoted : boolean;
  280. sep : char;
  281. begin
  282. if not assigned(p) then
  283. exit;
  284. hp:=tai(p.first);
  285. while assigned(hp) do
  286. begin
  287. case hp.typ of
  288. ait_comment : Begin
  289. AsmWrite(target_asm.comment);
  290. AsmWritePChar(tai_asm_comment(hp).str);
  291. AsmLn;
  292. End;
  293. ait_regalloc,
  294. ait_tempalloc : ;
  295. ait_section : begin
  296. if LastSec<>sec_none then
  297. AsmWriteLn('_'+target_asm.secnames[LastSec]+#9#9'ENDS');
  298. if tai_section(hp).sec<>sec_none then
  299. begin
  300. AsmLn;
  301. AsmWriteLn('_'+target_asm.secnames[tai_section(hp).sec]+#9#9+
  302. 'SEGMENT'#9'PARA PUBLIC USE32 '''+
  303. target_asm.secnames[tai_section(hp).sec]+'''');
  304. end;
  305. LastSec:=tai_section(hp).sec;
  306. end;
  307. ait_align : begin
  308. { CAUSES PROBLEMS WITH THE SEGMENT DEFINITION }
  309. { SEGMENT DEFINITION SHOULD MATCH TYPE OF ALIGN }
  310. { HERE UNDER TASM! }
  311. AsmWriteLn(#9'ALIGN '+tostr(tai_align(hp).aligntype));
  312. end;
  313. ait_datablock : begin
  314. if tai_datablock(hp).is_global then
  315. AsmWriteLn(#9'PUBLIC'#9+tai_datablock(hp).sym^.name);
  316. AsmWriteLn(PadTabs(tai_datablock(hp).sym^.name,#0)+'DB'#9+tostr(tai_datablock(hp).size)+' DUP(?)');
  317. end;
  318. ait_const_32bit,
  319. ait_const_8bit,
  320. ait_const_16bit : begin
  321. AsmWrite(ait_const2str[hp.typ]+tostr(tai_const(hp).value));
  322. consttyp:=hp.typ;
  323. l:=0;
  324. repeat
  325. found:=(not (tai(hp.next)=nil)) and (tai(hp.next).typ=consttyp);
  326. if found then
  327. begin
  328. hp:=tai(hp.next);
  329. s:=','+tostr(tai_const(hp).value);
  330. AsmWrite(s);
  331. inc(l,length(s));
  332. end;
  333. until (not found) or (l>line_length);
  334. AsmLn;
  335. end;
  336. ait_const_symbol : begin
  337. AsmWriteLn(#9#9'DD'#9'offset '+tai_const_symbol(hp).sym^.name);
  338. if tai_const_symbol(hp).offset>0 then
  339. AsmWrite('+'+tostr(tai_const_symbol(hp).offset))
  340. else if tai_const_symbol(hp).offset<0 then
  341. AsmWrite(tostr(tai_const_symbol(hp).offset));
  342. AsmLn;
  343. end;
  344. ait_const_rva : begin
  345. AsmWriteLn(#9#9'RVA'#9+tai_const_symbol(hp).sym^.name);
  346. end;
  347. ait_real_32bit : AsmWriteLn(#9#9'DD'#9+single2str(tai_real_32bit(hp).value));
  348. ait_real_64bit : AsmWriteLn(#9#9'DQ'#9+double2str(tai_real_64bit(hp).value));
  349. ait_real_80bit : AsmWriteLn(#9#9'DT'#9+extended2str(tai_real_80bit(hp).value));
  350. ait_comp_64bit : AsmWriteLn(#9#9'DQ'#9+comp2str(tai_real_80bit(hp).value));
  351. ait_string : begin
  352. counter := 0;
  353. lines := tai_string(hp).len div line_length;
  354. { separate lines in different parts }
  355. if tai_string(hp).len > 0 then
  356. Begin
  357. for j := 0 to lines-1 do
  358. begin
  359. AsmWrite(#9#9'DB'#9);
  360. quoted:=false;
  361. for i:=counter to counter+line_length do
  362. begin
  363. { it is an ascii character. }
  364. if (ord(tai_string(hp).str[i])>31) and
  365. (ord(tai_string(hp).str[i])<128) and
  366. (tai_string(hp).str[i]<>'"') then
  367. begin
  368. if not(quoted) then
  369. begin
  370. if i>counter then
  371. AsmWrite(',');
  372. AsmWrite('"');
  373. end;
  374. AsmWrite(tai_string(hp).str[i]);
  375. quoted:=true;
  376. end { if > 31 and < 128 and ord('"') }
  377. else
  378. begin
  379. if quoted then
  380. AsmWrite('"');
  381. if i>counter then
  382. AsmWrite(',');
  383. quoted:=false;
  384. AsmWrite(tostr(ord(tai_string(hp).str[i])));
  385. end;
  386. end; { end for i:=0 to... }
  387. if quoted then AsmWrite('"');
  388. AsmWrite(target_os.newline);
  389. counter := counter+line_length;
  390. end; { end for j:=0 ... }
  391. { do last line of lines }
  392. AsmWrite(#9#9'DB'#9);
  393. quoted:=false;
  394. for i:=counter to tai_string(hp).len-1 do
  395. begin
  396. { it is an ascii character. }
  397. if (ord(tai_string(hp).str[i])>31) and
  398. (ord(tai_string(hp).str[i])<128) and
  399. (tai_string(hp).str[i]<>'"') then
  400. begin
  401. if not(quoted) then
  402. begin
  403. if i>counter then
  404. AsmWrite(',');
  405. AsmWrite('"');
  406. end;
  407. AsmWrite(tai_string(hp).str[i]);
  408. quoted:=true;
  409. end { if > 31 and < 128 and " }
  410. else
  411. begin
  412. if quoted then
  413. AsmWrite('"');
  414. if i>counter then
  415. AsmWrite(',');
  416. quoted:=false;
  417. AsmWrite(tostr(ord(tai_string(hp).str[i])));
  418. end;
  419. end; { end for i:=0 to... }
  420. if quoted then
  421. AsmWrite('"');
  422. end;
  423. AsmLn;
  424. end;
  425. ait_label : begin
  426. if tai_label(hp).l^.is_used then
  427. begin
  428. AsmWrite(tai_label(hp).l^.name);
  429. if assigned(hp.next) and not(tai(hp.next).typ in
  430. [ait_const_32bit,ait_const_16bit,ait_const_8bit,
  431. ait_const_symbol,ait_const_rva,
  432. ait_real_32bit,ait_real_64bit,ait_real_80bit,ait_comp_64bit,ait_string]) then
  433. AsmWriteLn(':');
  434. end;
  435. end;
  436. ait_direct : begin
  437. AsmWritePChar(tai_direct(hp).str);
  438. AsmLn;
  439. end;
  440. ait_symbol : begin
  441. if tai_symbol(hp).is_global then
  442. AsmWriteLn(#9'PUBLIC'#9+tai_symbol(hp).sym^.name);
  443. AsmWrite(tai_symbol(hp).sym^.name);
  444. if assigned(hp.next) and not(tai(hp.next).typ in
  445. [ait_const_32bit,ait_const_16bit,ait_const_8bit,
  446. ait_const_symbol,ait_const_rva,
  447. ait_real_32bit,ait_real_64bit,ait_real_80bit,ait_comp_64bit,ait_string]) then
  448. AsmWriteLn(':')
  449. end;
  450. ait_symbol_end : begin
  451. end;
  452. ait_instruction : begin
  453. { Must be done with args in ATT order }
  454. taicpu(hp).CheckNonCommutativeOpcodes;
  455. { We need intel order, no At&t }
  456. taicpu(hp).SwapOperands;
  457. { Reset }
  458. suffix:='';
  459. prefix:= '';
  460. s:='';
  461. { We need to explicitely set
  462. word prefix to get selectors
  463. to be pushed in 2 bytes PM }
  464. if (taicpu(hp).opsize=S_W) and
  465. ((taicpu(hp).opcode=A_PUSH) or
  466. (taicpu(hp).opcode=A_POP)) and
  467. (taicpu(hp).oper[0].typ=top_reg) and
  468. ((taicpu(hp).oper[0].reg>=firstsreg) and
  469. (taicpu(hp).oper[0].reg<=lastsreg)) then
  470. AsmWriteln(#9#9'DB'#9'066h');
  471. { added prefix instructions, must be on same line as opcode }
  472. if (taicpu(hp).ops = 0) and
  473. ((taicpu(hp).opcode = A_REP) or
  474. (taicpu(hp).opcode = A_LOCK) or
  475. (taicpu(hp).opcode = A_REPE) or
  476. (taicpu(hp).opcode = A_REPNZ) or
  477. (taicpu(hp).opcode = A_REPZ) or
  478. (taicpu(hp).opcode = A_REPNE)) then
  479. Begin
  480. prefix:=int_op2str[taicpu(hp).opcode]+#9;
  481. hp:=tai(hp.next);
  482. { this is theorically impossible... }
  483. if hp=nil then
  484. begin
  485. s:=#9#9+prefix;
  486. AsmWriteLn(s);
  487. break;
  488. end;
  489. { nasm prefers prefix on a line alone }
  490. AsmWriteln(#9#9+prefix);
  491. prefix:='';
  492. end
  493. else
  494. prefix:= '';
  495. if taicpu(hp).ops<>0 then
  496. begin
  497. if is_calljmp(taicpu(hp).opcode) then
  498. s:=#9+getopstr_jmp(taicpu(hp).oper[0])
  499. else
  500. begin
  501. for i:=0to taicpu(hp).ops-1 do
  502. begin
  503. if i=0 then
  504. sep:=#9
  505. else
  506. sep:=',';
  507. s:=s+sep+getopstr(taicpu(hp).oper[i],taicpu(hp).opsize,taicpu(hp).opcode,(i=2));
  508. end;
  509. end;
  510. end;
  511. AsmWriteLn(#9#9+prefix+int_op2str[taicpu(hp).opcode]+cond2str[taicpu(hp).condition]+suffix+s);
  512. end;
  513. {$ifdef GDB}
  514. ait_stabn,
  515. ait_stabs,
  516. ait_force_line,
  517. ait_stab_function_name : ;
  518. {$endif GDB}
  519. ait_cut : begin
  520. { only reset buffer if nothing has changed }
  521. if AsmSize=AsmStartSize then
  522. AsmClear
  523. else
  524. begin
  525. if LastSec<>sec_none then
  526. AsmWriteLn('_'+target_asm.secnames[LastSec]+#9#9'ENDS');
  527. AsmLn;
  528. AsmWriteLn(#9'END');
  529. AsmClose;
  530. DoAssemble;
  531. AsmCreate(tai_cut(hp).place);
  532. end;
  533. { avoid empty files }
  534. while assigned(hp.next) and (tai(hp.next).typ in [ait_cut,ait_section,ait_comment]) do
  535. begin
  536. if tai(hp.next).typ=ait_section then
  537. begin
  538. lastsec:=tai_section(hp.next).sec;
  539. end;
  540. hp:=tai(hp.next);
  541. end;
  542. AsmWriteLn(#9'.386p');
  543. { I was told that this isn't necesarry because }
  544. { the labels generated by FPC are unique (FK) }
  545. { AsmWriteLn(#9'LOCALS '+target_asm.labelprefix); }
  546. if lastsec<>sec_none then
  547. AsmWriteLn('_'+target_asm.secnames[lastsec]+#9#9+
  548. 'SEGMENT'#9'PARA PUBLIC USE32 '''+
  549. target_asm.secnames[lastsec]+'''');
  550. AsmStartSize:=AsmSize;
  551. end;
  552. ait_marker: ;
  553. else
  554. internalerror(10000);
  555. end;
  556. hp:=tai(hp.next);
  557. end;
  558. end;
  559. var
  560. currentasmlist : PAsmList;
  561. procedure writeexternal(p:pnamedindexobject);
  562. begin
  563. if pasmsymbol(p)^.defbind=AB_EXTERNAL then
  564. currentasmList^.AsmWriteln(#9'EXTRN'#9+p^.name);
  565. end;
  566. procedure ti386intasmlist.WriteExternals;
  567. begin
  568. currentasmlist:=@self;
  569. AsmSymbolList^.foreach({$ifdef fpcprocvar}@{$endif}writeexternal);
  570. end;
  571. procedure ti386intasmlist.WriteAsmList;
  572. begin
  573. {$ifdef EXTDEBUG}
  574. if assigned(current_module.mainsource) then
  575. comment(v_info,'Start writing intel-styled assembler output for '+current_module.mainsource^);
  576. {$endif}
  577. LastSec:=sec_none;
  578. AsmWriteLn(#9'.386p');
  579. AsmWriteLn(#9'LOCALS '+target_asm.labelprefix);
  580. AsmWriteLn('DGROUP'#9'GROUP'#9'_BSS,_DATA');
  581. AsmWriteLn(#9'ASSUME'#9'CS:_CODE,ES:DGROUP,DS:DGROUP,SS:DGROUP');
  582. AsmLn;
  583. countlabelref:=false;
  584. WriteExternals;
  585. { INTEL ASM doesn't support stabs
  586. WriteTree(debuglist);}
  587. WriteTree(codesegment);
  588. WriteTree(datasegment);
  589. WriteTree(consts);
  590. WriteTree(rttilist);
  591. WriteTree(resourcestringlist);
  592. WriteTree(bsssegment);
  593. countlabelref:=true;
  594. AsmWriteLn(#9'END');
  595. AsmLn;
  596. {$ifdef EXTDEBUG}
  597. if assigned(current_module.mainsource) then
  598. comment(v_info,'Done writing intel-styled assembler output for '+current_module.mainsource^);
  599. {$endif EXTDEBUG}
  600. end;
  601. end.
  602. {
  603. $Log$
  604. Revision 1.4 2000-12-25 00:07:31 peter
  605. + new tlinkedlist class (merge of old tstringqueue,tcontainer and
  606. tlinkedlist objects)
  607. Revision 1.3 2000/12/18 21:56:52 peter
  608. * extdebug fixes
  609. Revision 1.2 2000/11/29 00:30:43 florian
  610. * unused units removed from uses clause
  611. * some changes for widestrings
  612. Revision 1.1 2000/10/15 09:47:42 peter
  613. * moved to i386/
  614. Revision 1.6 2000/09/24 15:06:10 peter
  615. * use defines.inc
  616. Revision 1.5 2000/08/27 16:11:49 peter
  617. * moved some util functions from globals,cobjects to cutils
  618. * splitted files into finput,fmodule
  619. Revision 1.4 2000/08/20 17:38:21 peter
  620. * smartlinking fixed for linux (merged)
  621. Revision 1.3 2000/07/13 12:08:24 michael
  622. + patched to 1.1.0 with former 1.09patch from peter
  623. Revision 1.2 2000/07/13 11:32:30 michael
  624. + removed logs
  625. }