GenericLinker.Mod 17 KB

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