GenericLinker.Mod 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. MODULE GenericLinker; (* AUTHOR "negelef"; PURPOSE "Generic Object File Linker"; *)
  2. IMPORT ObjectFile, Streams, Diagnostics, Strings, SYSTEM;
  3. TYPE Address* = ADDRESS;
  4. CONST
  5. InvalidAddress* = -1 (* MAX (Address) *);
  6. CONST
  7. (* priorities -- do not coincide with ObjecFile section types *)
  8. Fixed* = 0; (* placed accroding to placement *)
  9. EntryCode*= 1; (* must be placed before all other code *)
  10. InitCode*=2;
  11. ExitCode*=3; (* must be placed after initcode but before code *)
  12. BodyCode* = 4;
  13. Code* = 5;
  14. Data* = 6;
  15. Const* = 7;
  16. Empty* = 8; (* must be placed last *)
  17. (* refers to priority classes, not to ObjectFile section types *)
  18. UseAll *= {Fixed .. Empty};
  19. UseInitCode*={Fixed .. ExitCode};
  20. UseAllButInitCode*={Fixed, BodyCode..Empty};
  21. TYPE
  22. HashEntrySegmentedName = RECORD
  23. key: ObjectFile.SegmentedName; (* key[0]= MIN(LONGINT) <=> empty *)
  24. value: Block;
  25. END;
  26. HashSegmentedNameArray = POINTER TO ARRAY OF HashEntrySegmentedName;
  27. HashTableSegmentedName* = OBJECT
  28. VAR
  29. table: HashSegmentedNameArray;
  30. size: LONGINT;
  31. used-: LONGINT;
  32. maxLoadFactor: REAL;
  33. (* Interface *)
  34. PROCEDURE & Init* (initialSize: LONGINT);
  35. BEGIN
  36. ASSERT(initialSize > 2);
  37. NEW(table, initialSize);
  38. size := initialSize;
  39. used := 0;
  40. maxLoadFactor := 0.75;
  41. Clear;
  42. END Init;
  43. PROCEDURE Put*(CONST key: ObjectFile.SegmentedName; value: Block);
  44. VAR hash: LONGINT;
  45. BEGIN
  46. ASSERT(used < size);
  47. hash := HashValue(key);
  48. IF table[hash].key[0] < 0 THEN
  49. INC(used, 1);
  50. END;
  51. table[hash].key := key;
  52. table[hash].value := value;
  53. IF (used / size) > maxLoadFactor THEN Grow END;
  54. END Put;
  55. PROCEDURE Get*(CONST key: ObjectFile.SegmentedName):Block;
  56. BEGIN
  57. IF table[HashValue(key)].key = key THEN
  58. RETURN table[HashValue(key)].value;
  59. ELSE
  60. RETURN NIL
  61. END;
  62. END Get;
  63. PROCEDURE Clear;
  64. VAR i: LONGINT;
  65. BEGIN FOR i := 0 TO size - 1 DO table[i].key[0] := -1; END; END Clear;
  66. (* Internals *)
  67. PROCEDURE Hash(CONST name: ObjectFile.SegmentedName): LONGINT;
  68. VAR fp,i: LONGINT;
  69. BEGIN
  70. fp := name[0]; i := 1;
  71. WHILE (i<LEN(name)) & (name[i] >= 0) DO
  72. fp:=SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, ROT(fp, 7)) / SYSTEM.VAL(SET, name[i]));
  73. INC(i);
  74. END;
  75. RETURN fp
  76. END Hash;
  77. PROCEDURE HashValue(CONST key: ObjectFile.SegmentedName):LONGINT;
  78. VAR value, h,i: LONGINT;
  79. BEGIN
  80. ASSERT(key[0] >= 0);
  81. h := Hash(key);
  82. i := 0;
  83. REPEAT
  84. value := (h + i) MOD size;
  85. INC(i);
  86. UNTIL((table[value].key[0] < 0) OR (table[value].key = key) OR (i > size));
  87. ASSERT((table[value].key[0] <0 ) OR (table[value].key = key));
  88. RETURN value;
  89. END HashValue;
  90. PROCEDURE Grow;
  91. VAR oldTable: HashSegmentedNameArray; oldSize, i: LONGINT; key: ObjectFile.SegmentedName;
  92. BEGIN
  93. oldSize := size;
  94. oldTable := table;
  95. Init(size*2);
  96. FOR i := 0 TO oldSize-1 DO
  97. key := oldTable[i].key;
  98. IF key[0] # MIN(LONGINT) THEN
  99. IF oldTable[i].value # NIL THEN
  100. Put(key, oldTable[i].value);
  101. END;
  102. END;
  103. END;
  104. END Grow;
  105. END HashTableSegmentedName;
  106. TYPE Arrangement* = OBJECT
  107. PROCEDURE Preallocate* (CONST section: ObjectFile.Section);
  108. END Preallocate;
  109. PROCEDURE Allocate* (CONST section: ObjectFile.Section): Address;
  110. END Allocate;
  111. PROCEDURE Patch* (pos, value: Address; offset, bits, unit: ObjectFile.Bits);
  112. END Patch;
  113. PROCEDURE CheckReloc*(target: Address; pattern: ObjectFile.Pattern; CONST patch: ObjectFile.Patch);
  114. BEGIN
  115. (* to be able to provide relocation information in an image*)
  116. END CheckReloc;
  117. END Arrangement;
  118. TYPE Block* = POINTER TO RECORD (ObjectFile.Section)
  119. next: Block;
  120. address*: Address;
  121. aliasOf*: Block;
  122. referenced, used: BOOLEAN;
  123. priority: LONGINT;
  124. END;
  125. TYPE Linker* = OBJECT
  126. VAR
  127. diagnostics: Diagnostics.Diagnostics;
  128. usedCategories: SET;
  129. error-: BOOLEAN;
  130. log-: Streams.Writer;
  131. code, data: Arrangement;
  132. firstBlock, firstLinkedBlock: Block;
  133. linkRoot: ObjectFile.SectionName;
  134. hash: HashTableSegmentedName;
  135. PROCEDURE &InitLinker* (diagnostics: Diagnostics.Diagnostics; log: Streams.Writer; useCategories: SET; code, data: Arrangement);
  136. BEGIN
  137. SELF.diagnostics := diagnostics; SELF.log := log; SELF.usedCategories := useCategories;
  138. error := FALSE; SELF.code := code; SELF.data := data; firstBlock := NIL; firstLinkedBlock := NIL;
  139. linkRoot := "";
  140. NEW(hash,64);
  141. END InitLinker;
  142. PROCEDURE SetLinkRoot*(CONST root: ARRAY OF CHAR);
  143. BEGIN COPY(root, linkRoot)
  144. END SetLinkRoot;
  145. PROCEDURE Error* (CONST source, message: ARRAY OF CHAR);
  146. BEGIN diagnostics.Error (source, Diagnostics.Invalid, Diagnostics.Invalid, message); error := TRUE;
  147. END Error;
  148. PROCEDURE Warning* (CONST source, message: ARRAY OF CHAR);
  149. BEGIN diagnostics.Warning (source, Diagnostics.Invalid, Diagnostics.Invalid, message);
  150. END Warning;
  151. PROCEDURE ErrorP*(CONST pooledName: ObjectFile.SegmentedName; CONST message: ARRAY OF CHAR);
  152. VAR source: ARRAY 256 OF CHAR;
  153. BEGIN
  154. ObjectFile.SegmentedNameToString(pooledName, source); Error(source, message);
  155. END ErrorP;
  156. PROCEDURE Information* (CONST source, message: ARRAY OF CHAR);
  157. BEGIN IF log#NIL THEN log.String(source); log.String(":"); log.String(message); log.Ln END;
  158. END Information;
  159. PROCEDURE InformationP*(CONST pooledName: ObjectFile.SegmentedName; CONST message: ARRAY OF CHAR);
  160. VAR source: ARRAY 256 OF CHAR;
  161. BEGIN
  162. ObjectFile.SegmentedNameToString(pooledName, source); Information(source, message);
  163. END InformationP;
  164. PROCEDURE FindBlock* (CONST identifier: ObjectFile.Identifier): Block;
  165. BEGIN
  166. RETURN hash.Get(identifier.name);
  167. END FindBlock;
  168. PROCEDURE ImportBlock*(CONST fixup: ObjectFile.Fixup): Block;
  169. BEGIN
  170. RETURN NIL
  171. END ImportBlock;
  172. PROCEDURE ExportBlock*(block: Block);
  173. BEGIN
  174. (* can be overwritten by implementers, for example for hashing the block *)
  175. END ExportBlock;
  176. PROCEDURE GetArrangement (block: Block): Arrangement;
  177. BEGIN IF ObjectFile.IsCode (block.type) THEN RETURN code; ELSE RETURN data; END;
  178. END GetArrangement;
  179. (* this procedure may be overwritten by implementations of the linker that need a special ordering, as, for example, the bodycode in the front or so *)
  180. PROCEDURE Precedes* (this, that: Block): BOOLEAN;
  181. VAR leftType, rightType: LONGINT;
  182. BEGIN
  183. leftType := this.priority;
  184. rightType := that.priority;
  185. RETURN (leftType < rightType)
  186. END Precedes;
  187. PROCEDURE AddSection* (CONST section: ObjectFile.Section);
  188. VAR block, current, previous,newBlock: Block; name: ARRAY 256 OF CHAR; i: LONGINT; alias: ObjectFile.Alias;
  189. BEGIN
  190. IF FindBlock (section.identifier) # NIL THEN ObjectFile.SegmentedNameToString(section.identifier.name,name); Error (name, "duplicated section"); RETURN; END;
  191. NEW (block); ObjectFile.CopySection (section, block^); block.address := InvalidAddress; block.referenced := FALSE; block.used := FALSE;
  192. current := firstBlock; previous := NIL;
  193. block.priority := GetPriority(block);
  194. WHILE (current # NIL) & ~Precedes(block,current) DO previous := current; current := current.next; END;
  195. IF previous # NIL THEN previous.next := block; ELSE firstBlock := block; END; block.next := current;
  196. hash.Put(block.identifier.name, block);
  197. ExportBlock(block);
  198. current := block;
  199. (* append all alias blocks after the block *)
  200. FOR i := 0 TO block.aliases-1 DO
  201. alias := block.alias[i];
  202. NEW(newBlock);
  203. newBlock.identifier := alias.identifier;
  204. newBlock.address := alias.offset;
  205. newBlock.aliasOf := block;
  206. newBlock.used := block.used;
  207. newBlock.next := current.next;
  208. current.next := newBlock;
  209. current := newBlock;
  210. hash.Put(current.identifier.name, current);
  211. ExportBlock(current);
  212. END;
  213. END AddSection;
  214. PROCEDURE Resolve*;
  215. VAR block: Block; used: BOOLEAN; name: ARRAY 256 OF CHAR;
  216. BEGIN
  217. IF ~error THEN block := firstBlock;
  218. WHILE (block # firstLinkedBlock) & ~error DO
  219. ObjectFile.SegmentedNameToString(block.identifier.name, name);
  220. used := (block.priority IN usedCategories) OR (linkRoot # "") & Strings.StartsWith(linkRoot,0,name) OR (block.aliases > 0);
  221. Reference (block, used); block := block.next;
  222. END;
  223. END;
  224. END Resolve;
  225. (*
  226. PROCEDURE Aliases*(CONST block: Block);
  227. VAR newBlock: Block; alias: ObjectFile.Alias; i: LONGINT; name: ARRAY 256 OF CHAR;
  228. BEGIN
  229. FOR i := 0 TO block.aliases-1 DO
  230. alias := block.alias[i];
  231. NEW(newBlock);
  232. newBlock.identifier := alias.identifier;
  233. newBlock.address := alias.offset;
  234. newBlock.aliasOf := block;
  235. newBlock.used := block.used;
  236. newBlock.next := firstBlock;
  237. firstBlock := newBlock;
  238. END;
  239. END Aliases;
  240. *)
  241. PROCEDURE PatchAlias*(block: Block);
  242. BEGIN
  243. IF block.aliasOf # NIL THEN INC(block.address, block.aliasOf.address) END;
  244. END PatchAlias;
  245. PROCEDURE Link*;
  246. VAR block: Block;
  247. BEGIN
  248. (*
  249. IF ~error THEN block := firstBlock; WHILE block # firstLinkedBlock DO Aliases (block); block := block.next; END; END;
  250. *)
  251. Resolve;
  252. IF ~error THEN block := firstBlock; WHILE block # firstLinkedBlock DO IF block.used & (block.aliasOf=NIL) THEN Prearrange (block); END; block := block.next; END; END;
  253. IF ~error THEN block := firstBlock; WHILE block # firstLinkedBlock DO IF block.used & (block.aliasOf=NIL) THEN Arrange (block); END; block := block.next; END; END;
  254. IF ~error THEN block := firstBlock; WHILE block # firstLinkedBlock DO PatchAlias (block); block := block.next; END; END;
  255. IF ~error THEN block := firstBlock; WHILE block # firstLinkedBlock DO IF block.used & (block.aliasOf = NIL) THEN Patch (block); END; block := block.next; END; END;
  256. IF ~error THEN firstLinkedBlock := firstBlock; END;
  257. IF ~error & (log # NIL) THEN block := firstBlock; WHILE block # NIL DO Diagnose (block); block := block.next; END; END;
  258. END Link;
  259. PROCEDURE Reference (block: Block; used: BOOLEAN);
  260. VAR i: LONGINT;
  261. PROCEDURE ReferenceFixup (CONST fixup: ObjectFile.Fixup);
  262. VAR reference: Block; str,name: ARRAY 256 OF CHAR;
  263. BEGIN
  264. reference := FindBlock (fixup.identifier);
  265. IF reference = NIL THEN reference := ImportBlock(fixup) END;
  266. IF reference = NIL THEN
  267. ObjectFile.SegmentedNameToString(fixup.identifier.name,str); Strings.Append(str," in " );
  268. ObjectFile.SegmentedNameToString(block.identifier.name,name);
  269. Strings.Append(str, name);
  270. Error(str, "unresolved");
  271. ELSIF (reference.identifier.fingerprint # 0) & (fixup.identifier.fingerprint # 0) & (reference.identifier.fingerprint # fixup.identifier.fingerprint) THEN
  272. ObjectFile.SegmentedNameToString(fixup.identifier.name,str); Strings.Append(str," in " );
  273. ObjectFile.SegmentedNameToString(block.identifier.name,name);
  274. Strings.Append(str, name);
  275. Error (str, "incompatible");
  276. ELSE Reference (reference, block.used); END;
  277. END ReferenceFixup;
  278. BEGIN
  279. IF used & ~block.used THEN block.used := TRUE;
  280. ELSIF block.referenced THEN RETURN; END; block.referenced := TRUE;
  281. IF ~used THEN RETURN END;
  282. FOR i := 0 TO block.fixups - 1 DO
  283. ReferenceFixup (block.fixup[i]);
  284. IF error THEN RETURN END;
  285. END;
  286. END Reference;
  287. PROCEDURE Prearrange (block: Block);
  288. VAR arrangement: Arrangement;
  289. BEGIN
  290. ASSERT (block.used);
  291. arrangement := GetArrangement (block);
  292. arrangement.Preallocate (block^);
  293. END Prearrange;
  294. PROCEDURE Arrange (block: Block);
  295. VAR arrangement: Arrangement;
  296. BEGIN
  297. ASSERT (block.used);
  298. arrangement := GetArrangement (block);
  299. block.address := arrangement.Allocate (block^);
  300. IF block.address = InvalidAddress THEN ErrorP (block.identifier.name, "failed to allocate"); RETURN; END;
  301. IF block.fixed THEN IF block.address # block.alignment THEN ErrorP (block.identifier.name, "address allocation problem"); RETURN END;
  302. ELSE ASSERT ((block.alignment = 0) OR (block.address MOD block.alignment = 0)); END;
  303. END Arrange;
  304. PROCEDURE Patch (block: Block);
  305. VAR arrangement: Arrangement; i: LONGINT;
  306. PROCEDURE PatchFixup (CONST fixup: ObjectFile.Fixup);
  307. VAR reference: Block; target, address: Address; i: LONGINT;
  308. PROCEDURE PatchPattern (CONST pattern: ObjectFile.FixupPattern);
  309. BEGIN arrangement.Patch (target, address, pattern.offset, pattern.bits, block.unit); address := ASH (address, -pattern.bits);
  310. END PatchPattern;
  311. PROCEDURE CheckBits(pattern: ObjectFile.Pattern; offset: LONGINT);
  312. VAR i, nobits,remainder: LONGINT; minval, maxval: ObjectFile.Unit; name: ObjectFile.SectionName; number: ARRAY 32 OF CHAR;
  313. BEGIN
  314. nobits := 0;
  315. FOR i := 0 TO pattern.patterns-1 DO
  316. INC(nobits,pattern.pattern[i].bits);
  317. END;
  318. remainder := LONGINT(ASH(address,-nobits));
  319. IF (nobits <32) & ((remainder > 0) OR (remainder < -1)) THEN
  320. IF pattern.mode = ObjectFile.Relative THEN (* negative values allowed *)
  321. maxval := ASH(1,nobits-1)-1; minval := -maxval-1
  322. ELSE
  323. minval := 0; maxval := ASH(1,nobits);
  324. END;
  325. ObjectFile.SegmentedNameToString(block.identifier.name,name);
  326. Strings.Append(name,":");
  327. Strings.IntToStr(offset,number);
  328. Strings.Append(name,number);
  329. Error(name,"fixup out of range");
  330. END;
  331. END CheckBits;
  332. PROCEDURE ApplyPatch(pattern: ObjectFile.Pattern; CONST patch: ObjectFile.Patch);
  333. VAR j: LONGINT;
  334. BEGIN
  335. target := block.address + patch.offset;
  336. address := reference.address + patch.displacement;
  337. IF pattern.mode = ObjectFile.Relative THEN
  338. DEC(address,target)
  339. END;
  340. address := ASH (address, pattern.scale);
  341. CheckBits(pattern, patch.offset);
  342. FOR j := 0 TO pattern.patterns-1 DO PatchPattern(pattern.pattern[j]) END;
  343. END ApplyPatch;
  344. BEGIN
  345. reference := FindBlock (fixup.identifier);
  346. IF reference = NIL THEN reference := ImportBlock(fixup) END;
  347. ASSERT (reference # NIL);
  348. FOR i := 0 TO fixup.patches-1 DO
  349. ApplyPatch(fixup.pattern, fixup.patch[i]);
  350. arrangement.CheckReloc(block.address, fixup.pattern, fixup.patch[i])
  351. END;
  352. END PatchFixup;
  353. BEGIN
  354. ASSERT (block.used);
  355. arrangement := GetArrangement (block);
  356. FOR i := 0 TO block.fixups - 1 DO
  357. PatchFixup (block.fixup[i])
  358. END;
  359. END Patch;
  360. PROCEDURE Diagnose (block: Block);
  361. VAR source, num,name: ARRAY 128 OF CHAR; msg: ARRAY 512 OF CHAR;
  362. BEGIN
  363. IF block.used THEN
  364. Strings.IntToHexStr(block.address, 8, num);
  365. source := "";
  366. Strings.Append(source,"0");
  367. Strings.Append(source, num);
  368. Strings.Append(source,"H");
  369. msg := "";
  370. ObjectFile.SegmentedNameToString(block.identifier.name, name);
  371. IF ObjectFile.IsCode(block.type) THEN msg := " code "
  372. ELSE msg := " data "
  373. END;
  374. Strings.Append(msg, name);
  375. IF block.bits # NIL THEN
  376. Strings.Append(msg, " to ");
  377. Strings.IntToHexStr(block.address+block.bits.GetSize() DIV block.unit-1, 8, num);
  378. Strings.Append(msg,"0");
  379. Strings.Append(msg, num);
  380. Strings.Append(msg,"H");
  381. (*Strings.IntToStr(block.address+block.bits.GetSize() DIV block.unit-1, num);
  382. Strings.Append(msg,num);
  383. *)
  384. END;
  385. (*
  386. Strings.IntToStr(block.address, num);
  387. Strings.Append(msg," ("); Strings.Append(msg,num); Strings.Append(msg,")");
  388. *)
  389. Information (source, msg);
  390. ELSE InformationP (block.identifier.name, "unused"); END;
  391. END Diagnose;
  392. END Linker;
  393. PROCEDURE GetPriority(block: Block): LONGINT;
  394. BEGIN
  395. IF block.fixed THEN RETURN Fixed END;
  396. IF block.type = ObjectFile.InitCode THEN RETURN InitCode END;
  397. IF block.type = ObjectFile.EntryCode THEN RETURN EntryCode END;
  398. IF block.type = ObjectFile.ExitCode THEN RETURN ExitCode END;
  399. IF (block.bits = NIL) OR (block.bits.GetSize () = 0) THEN RETURN Empty END;
  400. IF block.type = ObjectFile.BodyCode THEN RETURN Code END;
  401. IF block.type = ObjectFile.Code THEN RETURN Code END;
  402. IF block.type = ObjectFile.Data THEN RETURN Code END;
  403. IF block.type = ObjectFile.Const THEN RETURN Code END;
  404. HALT(100); (* undefined type *)
  405. END GetPriority;
  406. PROCEDURE Header(reader: Streams.Reader; linker: Linker; VAR binary: BOOLEAN; VAR poolMap: ObjectFile.PoolMap; VAR offers, requires: ObjectFile.NameList): LONGINT;
  407. VAR ch: CHAR; version: LONGINT; string: ARRAY 32 OF CHAR;
  408. BEGIN
  409. reader.String(string);
  410. binary := string="FoxOFB";
  411. IF ~binary THEN ASSERT(string="FoxOFT") END;
  412. reader.SkipWhitespace;
  413. reader.Char(ch); ASSERT(ch='v');
  414. reader.Int(version,FALSE);
  415. IF (version <2) & (linker # NIL) THEN linker.Error("","old object file version encountered. Recompile sources.") END;
  416. reader.Char(ch); ASSERT(ch='.');
  417. IF ~binary THEN reader.SkipWhitespace
  418. ELSE
  419. NEW(poolMap,64);
  420. poolMap.Read(reader);
  421. END;
  422. offers := NIL;
  423. requires := NIL;
  424. IF version >= 4 THEN
  425. IF ~binary THEN
  426. reader.String(string);
  427. ASSERT(string = "offers");
  428. ObjectFile.ReadNameList(reader, offers, binary, poolMap);
  429. reader.SkipWhitespace;
  430. reader.String(string);
  431. ASSERT(string = "requires");
  432. ObjectFile.ReadNameList(reader, requires, binary, poolMap);
  433. reader.SkipWhitespace;
  434. ELSE
  435. ObjectFile.ReadNameList(reader, offers, binary, poolMap);
  436. ObjectFile.ReadNameList(reader, requires, binary, poolMap);
  437. END
  438. END;
  439. RETURN version;
  440. END Header;
  441. PROCEDURE OffersRequires*(reader: Streams.Reader; VAR offers, requires: ObjectFile.NameList);
  442. VAR binary: BOOLEAN; poolMap: ObjectFile.PoolMap; version: LONGINT;
  443. BEGIN
  444. version := Header(reader, NIL, binary, poolMap, offers, requires);
  445. END OffersRequires;
  446. PROCEDURE Process* (reader: Streams.Reader; linker: Linker);
  447. VAR section: ObjectFile.Section; binary: BOOLEAN; poolMap: ObjectFile.PoolMap; offers, requires: ObjectFile.NameList; version: LONGINT;
  448. BEGIN
  449. version := Header(reader, linker, binary, poolMap, offers, requires);
  450. WHILE reader.Peek () # 0X DO
  451. ObjectFile.ReadSection (reader, version, section,binary,poolMap);
  452. reader.SkipWhitespace;
  453. IF reader.res = Streams.Ok THEN linker.AddSection (section); END;
  454. END;
  455. END Process;
  456. END GenericLinker.
  457. Compiler.Compile GenericLinker.Mod ~~~