GenericLinker.Mod 17 KB

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