MODULE Dev0ElfLinker;

	(* THIS IS TEXT COPY OF ElfLinker16.odc *)
	(* DO NOT EDIT *)

(**
	project	= "BlackBox"
	organization	= "www.oberon.ch"
	contributors	= "Oberon microsystems"
	version	= "System/Rsrc/AboutBB"
	copyright	= "System/Rsrc/AboutBB"
	license	= "Docu/BB-License"
	changes	= ""
	issues	= ""

**)

(*
	DevElfLinker version compatible with BlackBox Component Builder release 1.6.
	This module will replace DevElfLinker, once the final version of BlackBox 1.6 will be released.
*)

(*
	A. V. Shiryaev, 2012.09

	Based on DevElfLinker16; modified to not depend on TextModels (TextModels -> Console)
*)

	IMPORT
		Strings,
		Kernel, Files, (* Dialog, TextMappers, StdLog, DevCommanders *) Console;

	CONST
		NewRecFP = 4E27A847H;
		NewArrFP = 76068C78H;

		OFdir = "Code";
		SYSdir = "System";

		(* meta interface consts *)
		mConst = 1; mTyp = 2; mVar = 3; mProc = 4;
		mInternal = 1; mExported = 4;

		(* mod desc fields *)
		modOpts = 4; modRefcnt = 8; modTerm = 40; modNames = 84; modImports = 92; modExports = 96;

		(* .dynsym entries *)
		stbLocal = 0; stbGlobal = 1;
		sttNotype = 0; sttObject = 1; sttFunc = 2; sttSection = 3;
		shnUnd = 0; shnAbs = 0FFF1H;

		fixup = 0;
		noSymbol = MIN(INTEGER);
		noAddr = MIN(INTEGER);
		firstDllSymbolVal = 12;

		(* distinguished section header indexes. *)
		textIndexVal = 1;	(* index of the .text section header in the section header table *)
		rodataIndexVal = 3;	(* index of the .rodata section header in the section header table *)
		dynsymIndexVal = 5;	(* index of the .dynsym section header in the section header table *)
		dynstrIndexVal = 6;	(* index of the .dynstr section header in the section header table *)

		(* fixed elements dimensions *)
		elfHeaderSizeVal = 52;	(* size of the ELF file header *)
		shEntrySizeVal = 40;	(* size of an entry in the section header table *)
		dynsymEntrySizeVal = 16; (* size of a symbol table entry *)
		dynamicEntrySizeVal = 8; (* size of an entry in the dynamic section *)
		gotEntrySizeVal = 4; (* size of an entry in the got section *)
		relEntrySizeVal = 8; (* size of an entry in a relocation section *)
		phEntrySizeVal = 32; (* size of an entry in the program header *)

		shNumVal = 12; (* number of entries in the section header table. See WriteSectionHeaderTable *)
		shStrndxVal = shNumVal - 1; (* index of the string table for section names. See WriteSectionHeaderTable *)
		phNumVal = 3; (* number of entries in the program header table *)

		(* sections alignments (in bytes) *)
		textAlign = 4H;
		dynsymAlign = 4H;
		dynstrAlign = 1H;
		hashAlign = 4H;
		gotAlign = 4H;
		dynamicAlign = 4H;
		shstrtabAlign = 1H;
		bssAlign = 4H;
		rodataAlign = 8H;
		relAlign = 4H;

		pageSize = 1000H; (* I386 page size *)

		r38632 = 1; r386pc32 = 2; r386Relative = 8; (* ELF relocation types *)

	
		(* A. V. Shiryaev: Scanner *)
		TMChar = 0; TMString = 1; TMEOT = 2;

	TYPE
		Name = ARRAY 40 OF SHORTCHAR;

		Export = POINTER TO RECORD
			next: Export;
			name: Name;
			adr: INTEGER
		END;

		Module = POINTER TO RECORD
			next: Module;
			name: Name;
			fileName: Files.Name;
			file: Files.File;
			hs, ms, ds, cs, vs, ni, ma, ca, va: INTEGER;
			dll, intf: BOOLEAN;
			exp: Export;
			imp: POINTER TO ARRAY OF Module;
			data: POINTER TO ARRAY OF BYTE
		END;

		Strtab = RECORD
			tab: ARRAY 4096 OF SHORTCHAR;
			cur: INTEGER
		END;

		Relocation = RECORD
			offset, type: INTEGER
		END;

		RelTab = RECORD
			tab: ARRAY 65536 OF Relocation;
			cur: INTEGER
		END;

		Section = RECORD
			fileOffset,
			memOffset,
			size: INTEGER
		END;

		(* A. V. Shiryaev: Scanner *)
			ScanRider = RECORD
				s: POINTER TO ARRAY OF CHAR;
				i: INTEGER
			END;
			Scanner = RECORD
				rider: ScanRider;
				start, type: INTEGER;

				string: ARRAY 100 OF CHAR;
				char: CHAR
			END;
	
	VAR
		Out: Files.File;
		R: Files.Reader;
		Ro: Files.Writer;
		error, isDll, isStatic: BOOLEAN;
		modList, kernel, main, last, impg, impd: Module;
		numMod, lastTerm: INTEGER;
		firstExp, lastExp: Export;
		CodeSize, DataSize, ConSize: INTEGER;
		maxCode, numExp: INTEGER;
		newRec, newArr: Name;
		code: POINTER TO ARRAY OF BYTE;

		(* fixup positions *)
		entryPos,
		expPos,
		shstrtabPos,
		finiPos: INTEGER;

		(* sections *)
		text, reltext, relrodata, rodata, dynstr, shstrtab, hash, got, dynsym, dynamic, bss: Section;

		(* distinguished file and memory offsets *)
		shOffsetVal,	(* section header table file offset *)
		phOffsetVal,	(* program header table file offset *)
		finiMemOffsetVal: INTEGER;	(* memory offset (aka virtual address) of the finalization code (CLOSE sections) *)

		dynsymInfoVal,	(* value of the info field for the .dynsym section *)
		sonameStrIndexVal: INTEGER;	(* string table index of the name of hte library *)

		(* segment dimensions *)
		textSegmentSizeVal,
		dataSegmentSizeVal,
		dynamicSegmentSizeVal: INTEGER;

		headerstrtab, dynstrtab: Strtab;
		hashtab: ARRAY 256 OF Name;

		neededIdx: ARRAY 256 OF INTEGER;

		relTextTab, relRodataTab: RelTab;

		soName: Name;

		doWrite: BOOLEAN;

	PROCEDURE (VAR t: Strtab) AddName (IN s: ARRAY OF SHORTCHAR; OUT idx: INTEGER), NEW;
		VAR i: INTEGER;
	BEGIN
		ASSERT((t.cur + LEN(s$)) <= LEN(t.tab), 20); (* table buffer not large enough: TODO enlarge? *)
		idx := t.cur;
		i := 0;
		WHILE s[i] # 0X DO
			t.tab[t.cur] := s[i];
			INC(i); INC(t.cur)
		END;
		t.tab[t.cur] := s[i]; (* copy the 0X *)
		INC(t.cur)
	END AddName;

	PROCEDURE (VAR t: RelTab) Add (offset, type: INTEGER), NEW;
	BEGIN
		ASSERT(t.cur < LEN(t.tab), 20); (* table buffer not large enough: TODO enlarge? *)
		t.tab[t.cur].offset := offset;		
		t.tab[t.cur].type := type;
		INC(t.cur)
	END Add;

	PROCEDURE AddNeededIdx (idx: INTEGER);
		VAR i, len: INTEGER;
	BEGIN
		ASSERT(idx > 0, 20);	(* index must be positive *)
		len := LEN(neededIdx);
		i := 0;
		WHILE (i # len) & (neededIdx[i] # 0) DO INC(i) END;
		IF i # len THEN
			neededIdx[i] := idx
		ELSE
			HALT(21)	(* no more space for indexes *)
		END
	END AddNeededIdx;

	(* A. V. Shiryaev: Console *)

		PROCEDURE WriteString (s: ARRAY OF CHAR);
		BEGIN
			Console.WriteStr(s)
		END WriteString;

		PROCEDURE WriteChar (c: CHAR);
			VAR s: ARRAY 2 OF CHAR;
		BEGIN
			s[0] := c; s[1] := 0X;
			Console.WriteStr(s)
		END WriteChar;

		PROCEDURE WriteSString (ss: ARRAY OF SHORTCHAR);
		BEGIN
			Console.WriteStr(ss$)
		END WriteSString;

		PROCEDURE WriteInt (x: INTEGER);
			VAR s: ARRAY 16 OF CHAR;
		BEGIN
			Strings.IntToString(x, s);
			Console.WriteStr(s)
		END WriteInt;

		PROCEDURE WriteLn;
		BEGIN
			Console.WriteLn
		END WriteLn;

		PROCEDURE FlushW;
		BEGIN
		END FlushW;

	PROCEDURE ThisFile (modname: ARRAY OF CHAR): Files.File;
		VAR dir, name: Files.Name; loc: Files.Locator; f: Files.File;
	BEGIN
		Kernel.SplitName(modname, dir, name);
		Kernel.MakeFileName(name, Kernel.objType);
		loc := Files.dir.This(dir); loc := loc.This(OFdir);
		f := Files.dir.Old(loc, name, TRUE);
		IF (f = NIL) & (dir = "") THEN
			loc := Files.dir.This(SYSdir); loc := loc.This(OFdir);
			f := Files.dir.Old(loc, name, TRUE)
		END;
		RETURN f
	END ThisFile;

	PROCEDURE Read4 (VAR x: INTEGER);
		VAR b: BYTE;
	BEGIN
		R.ReadByte(b); x := b MOD 256;
		R.ReadByte(b); x := x + 100H * (b MOD 256);
		R.ReadByte(b); x := x + 10000H * (b MOD 256);
		R.ReadByte(b); x := x + 1000000H * b
	END Read4;

	PROCEDURE ReadName (VAR name: ARRAY OF SHORTCHAR);
		VAR i: INTEGER; b: BYTE;
	BEGIN i := 0;
		REPEAT
			R.ReadByte(b); name[i] := SHORT(CHR(b)); INC(i)
		UNTIL b = 0
	END ReadName;

	PROCEDURE RNum (VAR i: INTEGER);
		VAR b: BYTE; s, y: INTEGER;
	BEGIN
		s := 0; y := 0; R.ReadByte(b);
		WHILE b < 0 DO INC(y, ASH(b + 128, s)); INC(s, 7); R.ReadByte(b) END;
		i := ASH((b + 64) MOD 128 - 64, s) + y
	END RNum;

	PROCEDURE WriteCh (ch: SHORTCHAR);
	BEGIN
		IF doWrite THEN
		Ro.WriteByte(SHORT(ORD(ch)))
		END
	END WriteCh;

	PROCEDURE Write2 (x: INTEGER);
	BEGIN
		IF doWrite THEN
			Ro.WriteByte(SHORT(SHORT(x MOD 256))); x := x DIV 256;
			Ro.WriteByte(SHORT(SHORT(x MOD 256)))
		END
	END Write2;

	PROCEDURE Write4 (x: INTEGER);
	BEGIN
		IF doWrite THEN
			Ro.WriteByte(SHORT(SHORT(x MOD 256))); x := x DIV 256;
			Ro.WriteByte(SHORT(SHORT(x MOD 256))); x := x DIV 256;
			Ro.WriteByte(SHORT(SHORT(x MOD 256))); x := x DIV 256;
			Ro.WriteByte(SHORT(SHORT(x MOD 256)))
		END
	END Write4;

	PROCEDURE WriteBytes (IN x: ARRAY OF BYTE; beg, len: INTEGER);
	BEGIN
		IF doWrite THEN
			Ro.WriteBytes(x, beg, len)
		END
	END WriteBytes;

	PROCEDURE Align (alignment: INTEGER);
	BEGIN
		WHILE Ro.Pos() MOD alignment # 0 DO WriteCh(0X) END
	END Align;
	
	PROCEDURE Aligned (pos, alignment: INTEGER): INTEGER;
	BEGIN
		RETURN (pos + (alignment - 1)) DIV alignment * alignment
	END Aligned;
	
	PROCEDURE Put (mod: Module; a, x: INTEGER);
	BEGIN
		ASSERT((mod.data # NIL) & ((a >= 0) & (a <= LEN(mod.data))), 20);
		mod.data[a] := SHORT(SHORT(x)); INC(a); x := x DIV 256;
		mod.data[a] := SHORT(SHORT(x)); INC(a); x := x DIV 256;
		mod.data[a] := SHORT(SHORT(x)); INC(a); x := x DIV 256;
		mod.data[a] := SHORT(SHORT(x))
	END Put;

	PROCEDURE Get (mod: Module; a: INTEGER; VAR x: INTEGER);
	BEGIN
		ASSERT((mod.data # NIL) & ((a >= 0) & (a + 3 <= LEN(mod.data))), 20);
		x := ((mod.data[a + 3] * 256 +
			(mod.data[a + 2] MOD 256)) * 256 +
			(mod.data[a + 1] MOD 256)) * 256 +
			(mod.data[a] MOD 256)
	END Get;

	PROCEDURE CheckDllImports (mod: Module);
		VAR i, x, y: INTEGER; name: Name; imp: Module; exp: Export;

		PROCEDURE SkipLink;
			VAR a: INTEGER;
		BEGIN
			RNum(a);
			WHILE a # 0 DO RNum(a); RNum(a) END
		END SkipLink;

	BEGIN
		R := mod.file.NewReader(R);
		R.SetPos(mod.hs + mod.ms + mod.ds + mod.cs);
		SkipLink; SkipLink; SkipLink; SkipLink; SkipLink; SkipLink;
		i := 0;
		WHILE i < mod.ni DO
			imp := mod.imp[i];
			IF imp # NIL THEN
				RNum(x);
				WHILE x # 0 DO
					ReadName(name); RNum(y);
					IF x = mVar THEN
						SkipLink;
						IF imp.dll THEN
							exp := imp.exp;
							WHILE (exp # NIL) & (exp.name # name) DO exp := exp.next END;
							IF exp = NIL THEN
								NEW(exp); exp.name := name$;
								exp.next := imp.exp; imp.exp := exp
							 END
						END
					ELSIF x = mTyp THEN RNum(y);
						IF imp.dll THEN
							RNum(y);
							IF y # 0 THEN
								WriteString("type descriptor (");
								WriteString(imp.name$); WriteChar(".");
								WriteSString(name);
								WriteString(") imported from DLL in ");
								WriteString(mod.name$);
								WriteLn; FlushW; error := TRUE;
								RETURN
							END
						ELSE SkipLink
						END
					ELSIF x = mProc THEN
						IF imp.dll THEN
							SkipLink;
							exp := imp.exp;
							WHILE (exp # NIL) & (exp.name # name) DO exp := exp.next END;
							IF exp = NIL THEN
								NEW(exp); exp.name := name$;
								exp.next := imp.exp; imp.exp := exp
							 END
						END
					END;
					RNum(x)
				END
			END;
			INC(i)
		END
	END CheckDllImports;

	PROCEDURE ReadHeaders;
		VAR mod, im, t: Module; x, i, pos: INTEGER; impdll: BOOLEAN; name: Name;
	BEGIN
		ASSERT(isDll, 126);
		mod := modList; modList := NIL; numMod := 0;
		WHILE mod # NIL DO	(* reverse mod list & count modules *)
			IF ~mod.dll THEN INC(numMod) END;
			t := mod; mod := t.next; t.next := modList; modList := t
		END;
		IF isStatic THEN
			CodeSize :=
				6 + 5 * numMod + 2	(* _init() *)
				+ 1 + 5 * numMod + 2	(* _fini() *)
		ELSE
			CodeSize :=
				6 + 5 + 2	(* _init() *)
				+ 1 + 5 + 2	(* _fini() *)
		END;
		DataSize := 0; ConSize := 0;
		maxCode := 0; numExp := 0;
		mod := modList;
		WHILE mod # NIL DO
			IF ~mod.dll THEN
				mod.file := ThisFile(mod.fileName);
				IF mod.file # NIL THEN
					R := mod.file.NewReader(R); R.SetPos(0);
					Read4(x);
					IF x = 6F4F4346H THEN
						Read4(x);
						Read4(mod.hs); Read4(mod.ms); Read4(mod.ds); Read4(mod.cs);
						Read4(mod.vs); RNum(mod.ni); ReadName(mod.name); impdll := FALSE;
						IF mod.ni > 0 THEN
							NEW(mod.imp, mod.ni);
							x := 0;
							WHILE x < mod.ni DO
								ReadName(name);
								IF name = "$$" THEN
									IF (mod # kernel) & (kernel # NIL) THEN
										mod.imp[x] := kernel
									ELSE
										WriteSString("no kernel"); WriteLn;
										FlushW; error := TRUE
									END
								ELSIF name[0] = "$" THEN
									(* StdLog.String(name$);  *)
									Console.WriteStr(name$);
									i := 1;
									WHILE name[i] # 0X DO name[i-1] := name[i]; INC(i) END;
									name[i-1] := 0X; 
									IF i # 1 THEN
										Strings.Find(name$, ".so", 0, pos);
										IF pos = -1 THEN
											name[i - 1] := "."; name[i] := "s"; name[i + 1] := "o"; name[i + 2] := 0X
										END
									END;
									(* StdLog.String("  "); StdLog.String(name$); StdLog.Ln; *)
									Console.WriteStr(" "); Console.WriteStr(name$); Console.WriteLn;
									impdll := TRUE; im := modList;
									WHILE (im # mod) & (im.name # name) DO im := im.next END;
									IF (im = NIL) OR ~im.dll THEN
										NEW(im); im.next := modList; modList := im;
										im.dll := TRUE;
										im.name := name$; 
										dynstrtab.AddName(name, i);
										AddNeededIdx(i)
									END;
									mod.imp[x] := im
								ELSE
									im := modList;
									WHILE (im # mod) & (im.name # name) DO im := im.next END;
									IF im # mod THEN
										mod.imp[x] := im
									ELSE
										WriteSString(name);
										WriteString(" not present (imported in ");
										WriteString(mod.name$); WriteChar(")");
										WriteLn; FlushW; error := TRUE
									END
								END;
								INC(x)
							END
						END;
						IF impdll & ~error THEN CheckDllImports(mod) END;
						mod.ma := ConSize; INC(ConSize, mod.ms + mod.ds);
						mod.va := DataSize; INC(DataSize, mod.vs);
						mod.ca := CodeSize; INC(CodeSize, mod.cs);
						IF mod.cs > maxCode THEN maxCode := mod.cs END
					ELSE
						WriteString(mod.name$); WriteString(": wrong file type"); 
						WriteLn; FlushW; error := TRUE
					END;
					mod.file.Close; mod.file := NIL
				ELSE
					WriteString(mod.fileName$ (* A. V. Shiryaev *)); WriteString(" not found"); 
					WriteLn; FlushW; error := TRUE
				END;
				last := mod
			END;
			mod := mod.next
		END;
		IF ~isStatic & (main = NIL) THEN
			WriteSString("no main module specified"); WriteLn;
			FlushW; error := TRUE
		END;
		IF DataSize = 0 THEN DataSize := 1 END
	END ReadHeaders;

	PROCEDURE WriteElfHeader;
	BEGIN
		ASSERT(Ro.Pos() = 0, 100);
		dynstrtab.AddName(soName$, sonameStrIndexVal);
		Write4(464C457FH); Write4(00010101H); Write4(0); Write4(0); (* Magic *)
		Write2(3); (* ET_DYN e_type Object file type *)
		Write2(3); (* EM_386 e_machine Architecture *)
		Write4(1); (* EV_CURRENT e_version Object file version *)
		Write4(text.memOffset); (* e_entry Entry point virtual address *)
		entryPos := Ro.Pos();
		Write4(fixup); (* e_phoff Program header table file offset *)
		Write4(fixup); (* e_shoff: Section header table file offset *)
		Write4(0); (* e_flags Processor-specific flags *)
		Write2(elfHeaderSizeVal); (* e_ehsize ELF header size in bytes *)
		Write2(phEntrySizeVal); (* e_phentsize Program header table entry size *)
		Write2(phNumVal); (* e_phnum Program header table entry count *)
		Write2(shEntrySizeVal); (* e_shentsize Section header table entry size *)
		Write2(shNumVal); (* e_shnum Section header table entry count *)
		Write2(shStrndxVal); (* e_shstrndx Section header string table index *)
		ASSERT(Ro.Pos() = elfHeaderSizeVal, 101)
	END WriteElfHeader;

	PROCEDURE FixupElfHeader;
	BEGIN
		Ro.SetPos(entryPos);
		Write4(phOffsetVal);
		Write4(shOffsetVal)
	END FixupElfHeader;

	PROCEDURE WriteNullSectionHeader;
	BEGIN
		Write4(0); (* sh_name Section name (string tbl index) *)
		Write4(0); (* SHT_NULL sh_type Section type *)
		Write4(0); (* sh_flags Section flags *)
		Write4(0); (* ELF header + program header table; sh_addr Section virtual addr at execution *)
		Write4(0); (* sh_offset Section file offset *)
		Write4(0); (* sh_size Section size in bytes *)
		Write4(0); (* SHN_UNDEF sh_link Link to another section *)
		Write4(0); (* sh_info Additional section information *)
		Write4(0); (* sh_addralign Section alignment *)
		Write4(0) (* sh_entsize Entry size if section holds table *)
	END WriteNullSectionHeader;

	PROCEDURE WriteTextSectionHeader;
		VAR i: INTEGER;
	BEGIN
		headerstrtab.AddName(".text", i);
		Write4(i); (* sh_name Section name (string tbl index) *)
		Write4(1); (* SHT_PROGBITS sh_type Section type *)
		Write4(2H + 4H); (* SHF_ALLOC + SHF_EXECINSTR sh_flags Section flags *)
		Write4(text.memOffset); (* sh_addr Section virtual addr at execution *)
		Write4(text.fileOffset); (* sh_offset Section file offset *)
		Write4(text.size); (* sh_size Section size in bytes *)
		Write4(0); (* SHN_UNDEF sh_link Link to another section *)
		Write4(0); (* sh_info Additional section information *)
		Write4(textAlign); (* sh_addralign Section alignment *)
		Write4(0) (* sh_entsize Entry size if section holds table *)
	END WriteTextSectionHeader;

	PROCEDURE WriteRelTextSectionHeader;
		VAR i: INTEGER;
	BEGIN
		headerstrtab.AddName(".rel.text", i);
		Write4(i); (* sh_name Section name (string tbl index) *)
		Write4(9); (* SHT_REL sh_type Section type *)
		Write4(2H); (* SHF_ALLOC sh_flags Section flags *)
		Write4(reltext.memOffset); (* sh_addr Section virtual addr at execution *)
		Write4(reltext.fileOffset); (* sh_offset Section file offset *)
		Write4(reltext.size); (* sh_size Section size in bytes *)
		Write4(dynsymIndexVal); (* sh_link Link to another section -> index of the associated symbol table *)
		Write4(textIndexVal); (* sh_info Additional section information -> index of the relocated section *)
		Write4(relAlign); (* sh_addralign Section alignment *)
		Write4(relEntrySizeVal) (* sh_entsize Entry size if section holds table *)
	END WriteRelTextSectionHeader;

	PROCEDURE WriteRelRodataSectionHeader;
		VAR i: INTEGER;
	BEGIN
		headerstrtab.AddName(".rel.rodata", i);
		Write4(i); (* sh_name Section name (string tbl index) *)
		Write4(9); (* SHT_REL sh_type Section type *)
		Write4(2H); (* SHF_ALLOC sh_flags Section flags *)
		Write4(relrodata.memOffset); (* sh_addr Section virtual addr at execution *)
		Write4(relrodata.fileOffset); (* sh_offset Section file offset *)
		Write4(relrodata.size); (* sh_size Section size in bytes *)
		Write4(dynsymIndexVal); (* sh_link Link to another section -> index of the associated symbol table *)
		Write4(rodataIndexVal); (* sh_info Additional section information -> index of the relocated section *)
		Write4(relAlign); (* sh_addralign Section alignment *)
		Write4(relEntrySizeVal) (* sh_entsize Entry size if section holds table *)
	END WriteRelRodataSectionHeader;

	PROCEDURE WriteRodataSectionHeader;
		VAR i: INTEGER;
	BEGIN
		headerstrtab.AddName(".rodata", i);
		Write4(i); (* sh_name Section name (string tbl index) *)
		Write4(1); (* SHT_PROGBITS sh_type Section type *)
		Write4(2H); (* SHF_ALLOC sh_flags Section flags *)
		Write4(rodata.memOffset); (* sh_addr Section virtual addr at execution *)
		Write4(rodata.fileOffset); (* sh_offset Section file offset *)
		Write4(rodata.size); (* sh_size Section size in bytes *)
		Write4(0); (* sh_link Link to another section *)
		Write4(0); (* sh_info Additional section information *)
		Write4(rodataAlign); (* sh_addralign Section alignment *)
		Write4(0) (* sh_entsize Entry size if section holds table *)
	END WriteRodataSectionHeader;

	PROCEDURE WriteDynsymSectionHeader;
		VAR i: INTEGER;
	BEGIN
		headerstrtab.AddName(".dynsym", i);
		Write4(i); (* sh_name Section name (string tbl index) *)
		Write4(11); (* SHT_DYNSYM sh_type Section type *)
		Write4(2H); (* SHF_ALLOC sh_flags Section flags *)
		Write4(dynsym.memOffset); (* sh_addr Section virtual addr at execution *)
		Write4(dynsym.fileOffset); (* sh_offset Section file offset *)
		Write4(dynsym.size); (* sh_size Section size in bytes *)
		Write4(dynstrIndexVal); (* sh_link Link to another section -> index of the associated string table *)
		expPos := Ro.Pos();
		Write4(fixup); (* sh_info Additional section information -> see docu 4-17 *)
		Write4(dynsymAlign); (* sh_addralign Section alignment *)
		Write4(dynsymEntrySizeVal) (* sh_entsize Entry size if section holds table *)
	END WriteDynsymSectionHeader;

	PROCEDURE FixupDynsymSectionHeader;
	BEGIN
		Ro.SetPos(expPos);
		Write4(dynsymInfoVal)
	END FixupDynsymSectionHeader;
	
	PROCEDURE WriteDynstrSectionHeader;
		VAR i: INTEGER;
	BEGIN
		headerstrtab.AddName(".dynstr", i);
		Write4(i); (* sh_name Section name (string tbl index) *)
		Write4(3); (* SHT_STRTAB sh_type Section type *)
		Write4(2H); (* SHF_ALLOC sh_flags Section flags *)
		Write4(dynstr.memOffset); (* sh_addr Section virtual addr at execution *)
		Write4(dynstr.fileOffset); (* sh_offset Section file offset *)
		Write4(dynstr.size); (* sh_size Section size in bytes *)
		Write4(0); (* SHN_UNDEF sh_link Link to another section *)
		Write4(0); (* sh_info Additional section information *)
		Write4(dynstrAlign); (* sh_addralign Section alignment *)
		Write4(0) (* sh_entsize Entry size if section holds table *)
	END WriteDynstrSectionHeader;
	
	PROCEDURE WriteHashSectionHeader;
		VAR i: INTEGER;
	BEGIN
		headerstrtab.AddName(".hash", i);
		Write4(i); (* sh_name Section name (string tbl index) *)
		Write4(5); (* SHT_HASH sh_type Section type *)
		Write4(2H); (* SHF_ALLOC sh_flags Section flags *)
		Write4(hash.memOffset); (* sh_addr Section virtual addr at execution *)
		Write4(hash.fileOffset); (* sh_offset Section file offset *)
		Write4(hash.size); (* sh_size Section size in bytes *)
		Write4(dynsymIndexVal); (* sh_link Link to another section *)
		Write4(0); (* sh_info Additional section information *)
		Write4(hashAlign); (* sh_addralign Section alignment *)
		Write4(4H) (* sh_entsize Entry size if section holds table *)
	END WriteHashSectionHeader;

	PROCEDURE WriteGotSectionHeader;
		VAR i: INTEGER;
	BEGIN
		headerstrtab.AddName(".got", i);
		Write4(i); (* sh_name Section name (string tbl index) *)
		Write4(1); (* SHT_PROGBITS sh_type Section type *)
		Write4(2H + 1H); (* SHF_ALLOC + SHF_WRITE sh_flags Section flags *)
		Write4(got.memOffset); (* sh_addr Section virtual addr at execution *)
		Write4(got.fileOffset); (* sh_offset Section file offset *)
		Write4(got.size); (* sh_size Section size in bytes *)
		Write4(0); (* SHN_UNDEF sh_link Link to another section *)
		Write4(0); (* sh_info Additional section information *)
		Write4(gotAlign); (* sh_addralign Section alignment *)
		Write4(gotEntrySizeVal) (* sh_entsize Entry size if section holds table *)
	END WriteGotSectionHeader;
	
	PROCEDURE WriteBssSectionHeader;
		VAR i: INTEGER;
	BEGIN
		headerstrtab.AddName(".bss", i);
		Write4(i); (* sh_name Section name (string tbl index) *)
		Write4(8); (* SHT_NOBITS sh_type Section type *)
		Write4(2H + 1H); (* SHF_ALLOC + SHF_WRITE sh_flags Section flags *)
		Write4(bss.memOffset); (* sh_addr Section virtual addr at execution *)
		Write4(bss.fileOffset); (* sh_offset Section file offset *)
		Write4(bss.size); (* sh_size Section size in bytes *)
		Write4(0); (* SHN_UNDEF sh_link Link to another section *)
		Write4(0); (* sh_info Additional section information *)
		Write4(bssAlign); (* sh_addralign Section alignment *)
		Write4(0) (* sh_entsize Entry size if section holds table *)
	END WriteBssSectionHeader;
	
	PROCEDURE WriteDynamicSectionHeader;
		VAR i: INTEGER;
	BEGIN
		headerstrtab.AddName(".dynamic", i);
		Write4(i); (* sh_name Section name (string tbl index) *)
		Write4(6); (* SHT_DYNAMIC sh_type Section type *)
		Write4(2H); (* SHF_ALLOC sh_flags Section flags *)
		Write4(dynamic.memOffset); (* sh_addr Section virtual addr at execution *)
		Write4(dynamic.fileOffset); (* sh_offset Section file offset *)
		Write4(dynamic.size); (* sh_size Section size in bytes *)
		Write4(dynstrIndexVal); (* sh_link Link to another section -> index of the associated symbol table *)
		Write4(0); (* sh_info Additional section information *)
		Write4(dynamicAlign); (* sh_addralign Section alignment *)
		Write4(dynamicEntrySizeVal) (* sh_entsize Entry size if section holds table *)
	END WriteDynamicSectionHeader;
	
	PROCEDURE WriteShstrtabSectionHeader;
		VAR i: INTEGER;
	BEGIN
		headerstrtab.AddName(".shstrtab", i);
		Write4(i); (* sh_name Section name (string tbl index) *)
		Write4(3); (* SHT_STRTAB sh_type Section type *)
		Write4(0); (* sh_flags Section flags *)
		Write4(0); (* sh_addr Section virtual addr at execution *)
		Write4(shstrtab.fileOffset); (* sh_offset Section file offset *)
		shstrtabPos := Ro.Pos();
		Write4(fixup); (* sh_size Section size in bytes *)
		Write4(0); (* SHN_UNDEF sh_link Link to another section *)
		Write4(0); (* sh_info Additional section information *)
		Write4(shstrtabAlign); (* sh_addralign Section alignment *)
		Write4(0) (* sh_entsize Entry size if section holds table *)
	END WriteShstrtabSectionHeader;
	
	PROCEDURE FixupShstrtabSectionHeader;
	BEGIN
		Ro.SetPos(shstrtabPos);
		Write4(shstrtab.size)
	END FixupShstrtabSectionHeader;

	PROCEDURE WriteRelSectionHeaders;
	BEGIN
		WriteRelTextSectionHeader;
		WriteRelRodataSectionHeader
	END WriteRelSectionHeaders;
	
	PROCEDURE WriteSectionHeaderTable;
	BEGIN
		shOffsetVal := Ro.Pos();
		WriteNullSectionHeader;
		WriteTextSectionHeader;
		WriteRodataSectionHeader;
		WriteRelSectionHeaders;
		WriteDynsymSectionHeader;
		WriteDynstrSectionHeader;
		WriteHashSectionHeader;
		WriteGotSectionHeader;
		WriteDynamicSectionHeader;
		WriteBssSectionHeader;
		WriteShstrtabSectionHeader	(* see shStrndxVal *)
		(* see shNumVal *)
	END WriteSectionHeaderTable;

	PROCEDURE FixupSectionHeaderTable;
	BEGIN
		FixupDynsymSectionHeader;
		FixupShstrtabSectionHeader
	END FixupSectionHeaderTable;

	PROCEDURE WriteTextSegment;
	BEGIN
		Write4(1); (* PT_LOAD *)
		Write4(0); (* offset *)
		Write4(0); (* vaddr *)
		Write4(0); (* paddr *)
		Write4(textSegmentSizeVal); (* file size *)
		Write4(textSegmentSizeVal); (* mem size *)
		Write4(4H + 1H + 2H); (* flags: R+E+W *)
		Write4(pageSize) (* I386 page size *)
	END WriteTextSegment;
	
	PROCEDURE WriteDataSegment;
	BEGIN
		Write4(1); (* PT_LOAD *)
		Write4(got.fileOffset); (* offset text segment size *)
		Write4(got.memOffset); (* vaddr: offset + alignment * nof pages of text segment *)
		Write4(got.memOffset); (* paddr: offset + alignment * nof pages of text segment *)
		Write4(dataSegmentSizeVal); (* file size *)
		Write4(dataSegmentSizeVal + bss.size); (* mem size -> dataSegmentSizeVal + NOBITS sections *)
		Write4(4H + 2H); (* flags: R+W *)
		Write4(pageSize) (* I386 page size *)
	END WriteDataSegment;
	
	PROCEDURE WriteDynamicSegment;
	BEGIN
		Write4(2); (* PT_DYNAMIC *)
		Write4(dynamic.fileOffset); (* offset text segment size *)
		Write4(dynamic.memOffset); (* vaddr: offset of .dynamic section *)
		Write4(dynamic.memOffset); (* paddr: vaddr + alignment * nof pages of text segment *)
		Write4(dynamicSegmentSizeVal); (* file size *)
		Write4(dynamicSegmentSizeVal); (* mem size *)
		Write4(4H + 2H); (* flags: R+W *)
		Write4(dynamicAlign) (* dynamic section alignement*)
	END WriteDynamicSegment;
	
	PROCEDURE WriteProgramHeaderTable;
	BEGIN
		phOffsetVal := Ro.Pos();
		WriteTextSegment; (* .text .rel.text .rodata .dynsym .dynstr .hash *)
		WriteDataSegment; (* .got .dynamic .bss *)
		WriteDynamicSegment (* .dynamic *)
	END WriteProgramHeaderTable;
	
	PROCEDURE SearchObj (mod: Module; VAR name: ARRAY OF SHORTCHAR; m, fp, opt: INTEGER; VAR adr: INTEGER);
		VAR dir, len, ntab, f, id, l, r, p, n, i, j: INTEGER; nch, och: SHORTCHAR;
	BEGIN
		Get(mod, mod.ms + modExports, dir); DEC(dir, rodata.memOffset + mod.ma); Get(mod, dir, len); INC(dir, 4);
		Get(mod, mod.ms + modNames, ntab); DEC(ntab, rodata.memOffset + mod.ma);
		IF name # "" THEN
			l := 0; r := len;
			WHILE l < r DO	(* binary search *)
				n := (l + r) DIV 2; p := dir + n * 16;
				Get(mod, p + 8, id);
				i := 0; j := ntab + id DIV 256; nch := name[0]; och := SHORT(CHR(mod.data[j]));
				WHILE (nch = och) & (nch # 0X) DO INC(i); INC(j); nch := name[i]; och := SHORT(CHR(mod.data[j])) END;
				IF och = nch THEN
					IF id MOD 16 = m THEN
						Get(mod, p, f);
						IF m = mTyp THEN
							IF ODD(opt) THEN Get(mod, p + 4, f) END;
							IF (opt > 1) & (id DIV 16 MOD 16 # mExported) THEN
								WriteString(mod.name$); WriteChar("."); WriteSString(name);
								WriteString(" imported from "); WriteString(impg.name$);
								WriteString(" has wrong visibility"); WriteLn; error := TRUE
							END;
							Get(mod, p + 12, adr)
						ELSIF m = mVar THEN
							Get(mod, p + 4, adr); INC(adr, bss.memOffset + mod.va)
						ELSIF m = mProc THEN
							Get(mod, p + 4, adr); INC(adr, text.memOffset + mod.ca)
						END;
						IF f # fp THEN
							WriteString(mod.name$); WriteChar("."); WriteSString(name);
							WriteString(" imported from "); WriteString(impg.name$);
							WriteString(" has wrong fprint"); WriteLn; error := TRUE
						END
					ELSE
						WriteString(mod.name$); WriteChar("."); WriteSString(name);
						WriteString(" imported from "); WriteString(impg.name$);
						WriteString(" has wrong class"); WriteLn; error := TRUE
					END;
					RETURN
				END;
				IF och < nch THEN l := n + 1 ELSE r := n END
			END;
			WriteString(mod.name$); WriteChar("."); WriteSString(name);
			WriteString(" not found (imported from "); WriteString(impg.name$);
			WriteChar(")"); WriteLn; error := TRUE
		ELSE (* anonymous type *)
			WHILE len > 0 DO
				Get(mod, dir + 4, f); Get(mod, dir + 8, id);
				IF (f = fp) & (id MOD 16 = mTyp) & (id DIV 256 = 0) THEN
					Get(mod, dir + 12, adr); RETURN
				END;
				DEC(len); INC(dir, 16)
			END;
			WriteString("anonymous type in "); WriteString(mod.name$);
			WriteString(" not found"); WriteLn; error := TRUE
		END
	END SearchObj;
	
	PROCEDURE CollectExports (mod: Module);
		VAR dir, len, ntab, id, i, j, n: INTEGER; e, exp: Export;
	BEGIN
		ASSERT(mod.intf & ~mod.dll, 20);
		Get(mod, mod.ms + modExports, dir);
		DEC(dir, rodata.memOffset + mod.ma); Get(mod, dir, len); INC(dir, 4);
		Get(mod, mod.ms + modNames, ntab); DEC(ntab, rodata.memOffset + mod.ma); n := 0;
		WHILE n < len DO
			Get(mod, dir + 8, id);
			IF (id DIV 16 MOD 16 # mInternal) & (id MOD 16 = mProc) THEN	(* exported procedure *)
				NEW(exp);
				i := 0; j := ntab + id DIV 256;
				WHILE mod.data[j] # 0 DO exp.name[i] := SHORT(CHR(mod.data[j])); INC(i); INC(j) END;
				exp.name[i] := 0X;
				Get(mod, dir + 4, exp.adr);
				IF id MOD 16 = mProc THEN
					INC(exp.adr, text.memOffset + mod.ca)
				ELSE
					HALT(126);
					ASSERT(id MOD 16 = mVar); INC(exp.adr, bss.memOffset + mod.va)
				END;
				IF (firstExp = NIL) OR (exp.name < firstExp.name) THEN
					exp.next := firstExp; firstExp := exp;
					IF lastExp = NIL THEN lastExp := exp END
				ELSE
					e := firstExp;
					WHILE (e.next # NIL) & (exp.name > e.next.name) DO e := e.next END;
					exp.next := e.next; e.next := exp;
					IF lastExp = e THEN lastExp := exp END
				END;
				INC(numExp)
			END;
			INC(n); INC(dir, 16)
		END
	END CollectExports;

	PROCEDURE Relocate0 (link, adr, sym: INTEGER);
		CONST
			absolute = 100; relative = 101; copy = 102; table = 103; tableend = 104; (* BB fixup types *)
			noElfType = MIN(INTEGER);
		VAR
			offset, linkadr, bbType, elfType, n, x: INTEGER; relText: BOOLEAN;
	BEGIN
		WHILE link # 0 DO
			RNum(offset);
			WHILE link # 0 DO
				IF link > 0 THEN
					n := (code[link] MOD 256) + (code[link+1] MOD 256) * 256 + code[link+2] * 65536;
					bbType := code[link+3];
					linkadr := text.memOffset + impg.ca + link
				ELSE
					n := (impg.data[-link] MOD 256) + (impg.data[-link+1] MOD 256) * 256 + impg.data[-link+2] * 65536;
					bbType := impg.data[-link+3];
					linkadr := rodata.memOffset + impg.ma - link
				END;
				elfType := noElfType;
				IF bbType = absolute THEN
					IF sym = noSymbol THEN
						x := adr + offset;
						elfType := r386Relative
					ELSE
						x := 0H;
						elfType := r38632 + sym * 256
					END
				ELSIF bbType = relative THEN
					IF sym = noSymbol THEN
						x := adr + offset - linkadr - 4
					ELSE
						x := 0FFFFFFFCH;
						elfType := r386pc32 + sym * 256
					END
				ELSIF bbType = copy THEN
					Get(impd, adr + offset - rodata.memOffset - impd.ma, x);
					IF x # 0 THEN elfType := r386Relative END
				ELSIF bbType = table THEN
					x := adr + n; n := link + 4;
					elfType := r386Relative
				ELSIF bbType = tableend THEN
					x := adr + n; n := 0;
					elfType := r386Relative
				ELSE HALT(99)
				END;
				relText := link > 0; 
				IF link > 0 THEN
					code[link] := SHORT(SHORT(x));
					code[link+1] := SHORT(SHORT(x DIV 100H));
					code[link+2] := SHORT(SHORT(x DIV 10000H));
					code[link+3] := SHORT(SHORT(x DIV 1000000H))
				ELSE
					link := -link;
					impg.data[link] := SHORT(SHORT(x));
					impg.data[link+1] := SHORT(SHORT(x DIV 100H));
					impg.data[link+2] := SHORT(SHORT(x DIV 10000H));
					impg.data[link+3] := SHORT(SHORT(x DIV 1000000H))
				END;
				IF elfType # noElfType THEN
					IF relText THEN
						relTextTab.Add(linkadr, elfType)
					ELSE
						relRodataTab.Add(linkadr, elfType)
					END
				END;
				link := n
			END;
			RNum(link)
		END
	END Relocate0;
	
	PROCEDURE Relocate (adr: INTEGER);
		VAR link: INTEGER;
	BEGIN
		RNum(link); Relocate0(link, adr, noSymbol)
	END Relocate;

	PROCEDURE RelocateSymbol (adr, sym: INTEGER);
		VAR link: INTEGER;
	BEGIN
		RNum(link); Relocate0(link, adr, sym)
	END RelocateSymbol;
	
	PROCEDURE SymbolIndex (IN name: Name): INTEGER;
		VAR n: INTEGER; exp: Export; m: Module;
	BEGIN
		n := 0; exp := NIL;
		m := modList;
		WHILE (m # NIL) & (exp = NIL) DO
			IF m.dll THEN
				exp := m.exp;
				WHILE (exp # NIL) & (exp.name$ # name$) DO
					INC(n);
					exp := exp.next
				END
			END;
			m := m.next
		END;
		ASSERT((exp # NIL) & (exp.name$ = name$), 60);
		RETURN firstDllSymbolVal + n
	END SymbolIndex;

	PROCEDURE WriteTextSection;
		VAR mod, m: Module; i, x, a, sym, fp, opt: INTEGER; exp: Export; name: Name;
	BEGIN
		ASSERT(isDll, 126);
		ASSERT(~doWrite OR (Ro.Pos() = text.fileOffset), 100);
		WriteCh(053X);	(* push ebx *)	(* _init() *)
		a := 1;
		WriteCh(0BBX); Write4(rodata.memOffset  + last.ma + last.ms);	(* mov bx, modlist *)
		relTextTab.Add(text.memOffset + a + 1, r386Relative);
		INC(a, 5);
		IF isStatic THEN
			m := modList;
			WHILE m # NIL DO
				IF ~m.dll THEN
					WriteCh(0E8X); INC(a, 5); Write4(m.ca - a)	(* call body *)
				END;
				m := m.next
			END
		ELSE
			WriteCh(0E8X); INC(a, 5); Write4(main.ca - a)	(* call main *)
		END;
		WriteCh(05BX); 	(* pop ebx *)
		WriteCh(0C3X);	(* ret *)
		INC(a, 2);
		finiMemOffsetVal := text.memOffset + a;
		WriteCh(053X);	(* push ebx *)	(* _fini() *)
		INC(a);
		finiPos := text.memOffset + a;
		IF isStatic THEN
			i := 0;
			WHILE i < numMod DO	(* nop for call terminator *)
				WriteCh(02DX); Write4(0);	(* sub EAX, 0 *)
				INC(i); INC(a, 5)
			END
		ELSE
			WriteCh(02DX); Write4(0);	(* sub EAX, 0 *)
			INC(a, 5)
		END;
		lastTerm := a;
		WriteCh(05BX); 	(* pop ebx *)
		WriteCh(0C3X);	(* ret *)	
		IF ~doWrite THEN NEW(code, maxCode) END;
		mod := modList;
		WHILE mod # NIL DO
			impg := mod;
			impd := mod;
			IF ~mod.dll THEN
				mod.file := ThisFile(mod.fileName);
				R := mod.file.NewReader(R);
				R.SetPos(mod.hs);
				IF ~doWrite THEN NEW(mod.data, mod.ms + mod.ds) END;
				R.ReadBytes(mod.data^, 0, mod.ms + mod.ds);
				R.ReadBytes(code^, 0, mod.cs);
				RNum(x);
				IF x # 0 THEN
					IF (mod # kernel) & (kernel # NIL) THEN
						SearchObj(kernel, newRec, mProc, NewRecFP, 0, a);
						IF error THEN RETURN END;
						Relocate0(x, a, noSymbol)
					ELSE
						WriteSString("no kernel"); WriteLn;
						FlushW;
						error := TRUE;
						RETURN
					END
				END;
				RNum(x);
				IF x # 0 THEN
					IF (mod # kernel) & (kernel # NIL) THEN
						SearchObj(kernel, newArr, mProc, NewArrFP, 0, a);
						IF error THEN RETURN END;
						Relocate0(x, a, noSymbol)
					ELSE
						WriteSString("no kernel"); WriteLn;
						FlushW; error := TRUE;
						RETURN
					END
				END;
				Relocate(rodata.memOffset + mod.ma); (* metalink *)
				Relocate(rodata.memOffset + mod.ma + mod.ms); (* desclink *)
				Relocate(text.memOffset + mod.ca); (* codelink *)
				Relocate(bss.memOffset + mod.va); (* datalink *)
				i := 0;
				WHILE i < mod.ni DO
					m := mod.imp[i]; impd := m; RNum(x);
					WHILE x # 0 DO
						ReadName(name); RNum(fp); opt := 0;
						IF x = mTyp THEN RNum(opt) END;
						sym := noSymbol;
						IF m.dll THEN
							IF (x = mProc) OR (x = mVar) THEN
								exp := m.exp;
								WHILE exp.name # name DO exp := exp.next END;
								a := noAddr;
								sym := SymbolIndex(name)
							END
						ELSE
							SearchObj(m, name, x, fp, opt, a);
							IF error THEN RETURN END
						END;
						IF x # mConst THEN
							RelocateSymbol(a, sym)
						END;
						RNum(x)
					END;
					IF ~m.dll THEN
						Get(mod, mod.ms + modImports, x); DEC(x, rodata.memOffset + mod.ma); INC(x, 4 * i);
						Put(mod, x, rodata.memOffset + m.ma + m.ms);	(* imp ref *)
						relRodataTab.Add(rodata.memOffset + mod.ma + x, r386Relative);
						Get(m, m.ms + modRefcnt, x); Put(m, m.ms + modRefcnt, x + 1)	(* inc ref count *)
					END;
					INC(i)
				END;
				WriteBytes(code^, 0, mod.cs);
				IF mod.intf THEN CollectExports(mod) END;
				mod.file.Close; mod.file := NIL
			END;
			mod := mod.next
		END;
		ASSERT(~doWrite OR (text.size = Ro.Pos() - text.fileOffset), 101)
	END WriteTextSection;

	PROCEDURE WriteTermCode (m: Module; i: INTEGER);
		VAR x: INTEGER;
	BEGIN
		IF m # NIL THEN
			IF m.dll THEN WriteTermCode(m.next, i)
			ELSE
				IF isStatic THEN WriteTermCode(m.next, i + 1) END;
				Get(m, m.ms + modTerm, x);	(* terminator address in mod desc*)
				IF x = 0 THEN
					WriteCh(005X); Write4(0)	(* add EAX, 0 (nop) *)
				ELSE
					WriteCh(0E8X); Write4(x - lastTerm + 5 * i - text.memOffset)	(* call term *)
				END
			END
		END
	END WriteTermCode;

	PROCEDURE FixupTextSection;
	BEGIN
		ASSERT(isDll, 126);
		Ro.SetPos(finiPos);
		IF isStatic THEN
			WriteTermCode(modList, 0)
		ELSE
			WriteTermCode(main, 0)
		END
	END FixupTextSection;

	PROCEDURE WriteRelSection (IN s: Section; IN t: RelTab);
		VAR i: INTEGER;
	BEGIN
		ASSERT(s.fileOffset = Ro.Pos(), 100);
		i := 0;
		WHILE i # t.cur DO
			Write4(t.tab[i].offset);
			Write4(t.tab[i].type);
			INC(i)
		END;
		ASSERT(s.size = Ro.Pos() - s.fileOffset, 101)
	END WriteRelSection;

	PROCEDURE WriteRelSections;
	BEGIN
		WriteRelSection(reltext, relTextTab);
		WriteRelSection(relrodata, relRodataTab)
	END WriteRelSections;
	
	PROCEDURE WriteRodataSection;
		VAR mod, lastMod: Module; x: INTEGER;
	BEGIN
		ASSERT(~doWrite OR (rodata.fileOffset = Ro.Pos()), 100);
		mod := modList; lastMod := NIL;
		WHILE mod # NIL DO
			IF ~mod.dll THEN
				IF lastMod # NIL THEN
					Put(mod, mod.ms, rodata.memOffset + lastMod.ma + lastMod.ms);	(* mod list *)
					relRodataTab.Add(rodata.memOffset + mod.ma + mod.ms, r386Relative)
				END;
				Get(mod, mod.ms + modOpts, x);
				IF isStatic THEN INC(x, 10000H) END;	(* set init bit (16) *)
				IF isDll THEN INC(x, 1000000H) END;	(* set dll bit (24) *)
				Put(mod, mod.ms + modOpts, x);
				WriteBytes(mod.data^, 0, mod.ms + mod.ds);
				lastMod := mod
			END;
			mod := mod.next
		END;		
		ASSERT(~doWrite OR (rodata.size = Ro.Pos() - rodata.fileOffset), 101)
	END WriteRodataSection;
		
	PROCEDURE WriteSymbolTableEntry (IN name: ARRAY OF SHORTCHAR; val, size: INTEGER; bind, type: BYTE; shndx: INTEGER);
		VAR i: INTEGER; info: SHORTCHAR;
	BEGIN
		IF name # "" THEN dynstrtab.AddName(name, i)
		ELSE i := 0
		END;
		Write4(i);
		Write4(val);
		Write4(size);
		info := SHORT(CHR(bind * 16 + type));
		WriteCh(info);
		WriteCh(0X); (* Symbol visibility *)
		Write2(shndx)
	END WriteSymbolTableEntry;
	
	PROCEDURE FixupSymbolTableEntry (val, size: INTEGER; bind, type: BYTE; shndx: INTEGER);
		VAR info: SHORTCHAR;
	BEGIN
		Ro.SetPos(Ro.Pos() + 4); (* skip name *)
		Write4(val);
		Write4(size);
		info := SHORT(CHR(bind * 16 + type));
		WriteCh(info);
		WriteCh(0X); (* Symbol visibility *)
		Write2(shndx)
	END FixupSymbolTableEntry;
	
	PROCEDURE WriteDynsymSection;
		VAR e: Export; m: Module; i: INTEGER;
	BEGIN
		ASSERT(Ro.Pos() = dynsym.fileOffset, 100);
		WriteSymbolTableEntry("", 0, 0, 0, 0, 0);
		WriteSymbolTableEntry("", text.memOffset, 0, stbLocal, sttSection, 1); (* .text section *)
		WriteSymbolTableEntry("", rodata.memOffset, 0, stbLocal, sttSection, 2); (* .rodata section *)
		WriteSymbolTableEntry("", reltext.memOffset, 0, stbLocal, sttSection, 3); (* .rel.text.section *)
		WriteSymbolTableEntry("", relrodata.memOffset, 0, stbLocal, sttSection, 4); (* .rel.rodata section *)
		WriteSymbolTableEntry("", dynsym.memOffset, 0, stbLocal, sttSection, 5); (* .dynsym section *)
		WriteSymbolTableEntry("", dynstr.memOffset, 0, stbLocal, sttSection, 6); (* .dynstr section *)
		WriteSymbolTableEntry("", hash.memOffset, 0, stbLocal, sttSection, 7); (* .hash section *)
		WriteSymbolTableEntry("", got.memOffset, 0, stbLocal, sttSection, 8); (* .got section *)
		WriteSymbolTableEntry("", dynamic.memOffset, 0, stbLocal, sttSection, 9); (* .dynamic section *)
		WriteSymbolTableEntry("", bss.memOffset, 0, stbLocal, sttSection, 10); (* .bss section *)
		dynsymInfoVal := 11;
		i := dynsymInfoVal;
		WriteSymbolTableEntry("_DYNAMIC", dynamic.memOffset, 0, stbGlobal, sttObject, shnAbs);
		hashtab[i] := "_DYNAMIC";
		INC(i);
		ASSERT(i = firstDllSymbolVal);
		m := modList;
		WHILE m # NIL DO
			IF m.dll THEN
				e := m.exp;
				WHILE e # NIL DO
					WriteSymbolTableEntry(e.name, 0, 0, stbGlobal, sttNotype, shnUnd);
					hashtab[i] := e.name$;
					INC(i);
					e := e.next
				END
			END;
			m := m.next
		END;
		e := firstExp;
		WHILE e # NIL DO
			WriteSymbolTableEntry(e.name, fixup, 0, stbGlobal, sttFunc, textIndexVal);
			hashtab[i] := e.name$; INC(i);
			e := e.next
		END;
		WriteSymbolTableEntry("_GLOBAL_OFFSET_TABLE_", got.memOffset, 0, stbGlobal, sttObject, shnAbs);
		hashtab[i] := "_GLOBAL_OFFSET_TABLE_";
		ASSERT(dynsym.size = Ro.Pos() - dynsym.fileOffset, 101)
	END WriteDynsymSection;
	
	PROCEDURE FixupDynsymSection;
		VAR e: Export; m: Module;
	BEGIN
		Ro.SetPos(dynsym.fileOffset + dynsymEntrySizeVal * firstDllSymbolVal);
		m := modList;
		WHILE m # NIL DO
			IF m.dll THEN
				e := m.exp;
				WHILE e # NIL DO
					Ro.SetPos(Ro.Pos() + dynsymEntrySizeVal);
					e := e.next
				END
			END;
			m := m.next
		END;
		Ro.SetPos(Ro.Pos() + 4);
		e := firstExp;
		WHILE e # NIL DO
			Write4(e.adr);
			Ro.SetPos(Ro.Pos() + 12);
			e := e.next
		END
	END FixupDynsymSection;

	PROCEDURE WriteStringTable (IN t: Strtab);
		VAR i: INTEGER;
	BEGIN
		i := 0;
		WHILE i # t.cur DO
			WriteCh(t.tab[i]);
			INC(i)
		END
	END WriteStringTable;

	PROCEDURE WriteDynstrSection;
	BEGIN
		ASSERT(Ro.Pos() = dynstr.fileOffset, 100);
		WriteStringTable(dynstrtab);
		ASSERT(dynstr.size = Ro.Pos() - dynstr.fileOffset, 101)
	END WriteDynstrSection;

	PROCEDURE Hash (name: ARRAY OF SHORTCHAR): INTEGER;
		VAR i, h, g: INTEGER;
	BEGIN
		h := 0; i := 0;
		WHILE name[i] # 0X DO
			h := ASH(h, 4) + ORD(name[i]);
			g := ORD(BITS(h) * BITS(0F0000000H));
			IF g # 0 THEN
				h := ORD(BITS(h) / BITS(SHORT((g MOD 100000000L) DIV 1000000H)))
			END;
			h := ORD(BITS(h) * (-BITS(g)));
			INC(i)
		END;
		RETURN h
	END Hash;

	PROCEDURE AddToChain (VAR c: ARRAY OF INTEGER; i, idx: INTEGER);
		VAR k: INTEGER;
	BEGIN
		IF c[i] # 0 THEN
			k := i;
			WHILE c[k] # 0 DO k := c[k] END;
			c[k] := idx
		ELSE
			c[i] := idx
		END
	END AddToChain;

	PROCEDURE WriteHashSection;
		VAR n, i, hi: INTEGER; b, c: POINTER TO ARRAY OF INTEGER;
	BEGIN
		ASSERT(hash.fileOffset = Ro.Pos(), 100);
		n := dynsym.size DIV dynsymEntrySizeVal; (* number of enties in the symbol table *)
		NEW(b, n);
		NEW(c, n);
		i := 0;
		WHILE i # n DO
			c[i] := 0; (* STN_UNDEF *)
			IF hashtab[i] # "" THEN
				hi := Hash(hashtab[i]) MOD n;
				IF b[hi] # 0 THEN (* another word has the same index *)
					AddToChain(c, i, b[hi])  (*c[i] := b[hi]*)
				END;
				b[hi] := i
			END;
			INC(i)
		END;
		Write4(n); (* nbucket *)
		Write4(n); (* nchain *)
		i := 0;
		WHILE i # n DO
			Write4(b[i]);
			INC(i)
		END;
		i := 0;
		WHILE i # n DO
			Write4(c[i]);
			INC(i)
		END;
		ASSERT(hash.size = Ro.Pos() - hash.fileOffset, 101)
	END WriteHashSection;
	
	PROCEDURE WriteGotSection;
	BEGIN
		ASSERT(got.fileOffset = Ro.Pos(), 100);
		Write4(dynamic.memOffset); (* addr of .dynamic section *)
		Write4(0); (* reserved for ? *)
		Write4(0); (* reserved for ? *)
		ASSERT(got.size = Ro.Pos() - got.fileOffset, 101)
	END WriteGotSection;
	
	PROCEDURE WriteDynamicSectionEntry (tag, val: INTEGER);
	BEGIN
		Write4(tag);
		Write4(val)
	END WriteDynamicSectionEntry;
	
	PROCEDURE WriteDynamicSection;
		CONST dtNull = 0; dtNeeded = 1; dtHash = 4; dtStrtab = 5; dtSymtab = 6;
			dtStrsz = 10; dtSyment = 11; dtInit = 12; dtFini = 13; dtSoname = 14; dtRel = 17; dtRelsz = 18; dtRelent = 19;
			dtTextrel = 22;
		VAR i: INTEGER;
	BEGIN
		ASSERT(dynamic.fileOffset = Ro.Pos(), 100);
		WriteDynamicSectionEntry(dtSoname, fixup);
		WriteDynamicSectionEntry(dtFini, fixup);
		WriteDynamicSectionEntry(dtInit, text.memOffset);
		WriteDynamicSectionEntry(dtHash, hash.memOffset);
		WriteDynamicSectionEntry(dtStrtab, dynstr.memOffset);
		WriteDynamicSectionEntry(dtSymtab, dynsym.memOffset);
		WriteDynamicSectionEntry(dtStrsz, dynstr.size);
		WriteDynamicSectionEntry(dtSyment, dynsymEntrySizeVal);
		WriteDynamicSectionEntry(dtRel, reltext.memOffset);
		WriteDynamicSectionEntry(dtRelsz, reltext.size + relrodata.size);
		WriteDynamicSectionEntry(dtRelent, relEntrySizeVal);
		i := 0;
		WHILE neededIdx[i] # 0 DO
			WriteDynamicSectionEntry(dtNeeded, neededIdx[i]);
			INC(i)
		END;
		WriteDynamicSectionEntry(dtTextrel, 0);
		WriteDynamicSectionEntry(dtNull, 0); (* DT_NULL: marks the end *)
		ASSERT(dynamic.size = Ro.Pos() - dynamic.fileOffset, 101)
	END WriteDynamicSection;
	
	PROCEDURE FixupDynamicSection;
		VAR i: INTEGER;
	BEGIN
		Ro.SetPos(dynamic.fileOffset + 4);
		Write4(sonameStrIndexVal);
		Ro.SetPos(Ro.Pos() + 4);
		Write4(finiMemOffsetVal)
	END FixupDynamicSection;
	
	PROCEDURE WriteBssSection;
	BEGIN
(*
		The .bss section does not take space in the file.
		This procedure serves consistency-check purposes.
*)
		ASSERT(bss.fileOffset = Ro.Pos(), 100)
	END WriteBssSection;

	PROCEDURE WriteShstrtabSection;
	BEGIN
		ASSERT(shstrtab.fileOffset = Ro.Pos(), 100);
		WriteStringTable(headerstrtab);
		shstrtab.size := Ro.Pos() - shstrtab.fileOffset
	END WriteShstrtabSection;

	PROCEDURE GetImpListSize (OUT len: INTEGER; OUT count: INTEGER);
		VAR m: Module; e: Export;
	BEGIN
		len := 0; count := 0;
		m := modList;
		WHILE m # NIL DO
			IF m.dll THEN
				e := m.exp;
				WHILE e # NIL DO
					INC(len, LEN(e.name$) + 1);
					INC(count);
					e := e.next
				END
			END;
			m := m.next
		END
	END GetImpListSize;
	
	PROCEDURE GetExpListSize (OUT len: INTEGER; OUT count: INTEGER);
		VAR e: Export;
	BEGIN
		count := 0; len := 0;
		e := firstExp;
		WHILE e # NIL DO
			INC(len, LEN(e.name$) + 1);
			INC(count);
			e := e.next
		END
	END GetExpListSize;
	
	PROCEDURE DynsymSize (init: INTEGER): INTEGER;
		VAR size: INTEGER;
	BEGIN
		size := init;
		INC(size, dynsymEntrySizeVal * 11); (* sections entries *)
		INC(size, dynsymEntrySizeVal); (* _DYNAMIC symbol *)
		INC(size, dynsymEntrySizeVal); (* _GLOBAL_OFFSET_TABLE_ symbol *)
		RETURN size
	END DynsymSize;
	
	PROCEDURE DynstrSize (init: INTEGER): INTEGER;
		VAR size: INTEGER;
	BEGIN
		size := init + 1;
		INC(size, dynstrtab.cur - 1);
		INC(size, LEN(soName$) + 1); (* library name *)
		INC(size, 9); (* "_DYNAMIC" symbol + 0X *)
		INC(size, 21 + 1); (* "_GLOBAL_OFFSET_TABLE_" symbol + trailing 0X *)
		RETURN size
	END DynstrSize;
	
	PROCEDURE DynamicSize (init: INTEGER): INTEGER;
		VAR i, size: INTEGER;
	BEGIN
		size := init;
		i := 0;
		WHILE neededIdx[i] # 0 DO
			INC(size, dynamicEntrySizeVal);
			INC(i)
		END;
		RETURN size
	END DynamicSize;
	
	PROCEDURE CalculateLayout;
		VAR headerSize, impCount, expCount, impLen, expLen: INTEGER;
	BEGIN
		ASSERT(~error, 20);
		headerSize := elfHeaderSizeVal + shEntrySizeVal * shNumVal + phEntrySizeVal * phNumVal;
		text.fileOffset := Aligned(headerSize, textAlign);
		text.memOffset := text.fileOffset;
		text.size := CodeSize;
		rodata.fileOffset := Aligned(text.fileOffset + text.size, rodataAlign);
		rodata.memOffset := rodata.fileOffset;
		rodata.size := ConSize;
		reltext.fileOffset := Aligned(rodata.fileOffset + rodata.size, relAlign);
		reltext.memOffset := reltext.fileOffset;
		doWrite := FALSE;
		WriteTextSection;	(* this only calculates the number of text relocations *)
		IF error THEN RETURN END;
		reltext.size := relEntrySizeVal * relTextTab.cur;
		relrodata.fileOffset := reltext.fileOffset + reltext.size;
		relrodata.memOffset := relrodata.fileOffset;
		IF ~error THEN
			WriteRodataSection	(* this only calculates the number of data relocations *)
		ELSE
			RETURN
		END;
		relrodata.size := relEntrySizeVal * relRodataTab.cur;
		dynsym.fileOffset := Aligned(relrodata.fileOffset + relrodata.size, dynsymAlign);
		dynsym.memOffset := dynsym.fileOffset;
		GetImpListSize(impLen, impCount);
		GetExpListSize(expLen, expCount);
		dynsym.size := DynsymSize((impCount + expCount) * dynsymEntrySizeVal);
		dynstr.fileOffset := Aligned(dynsym.fileOffset + dynsym.size, dynstrAlign);
		dynstr.memOffset := dynstr.fileOffset;
		dynstr.size := DynstrSize(impLen + expLen);
		hash.fileOffset := Aligned(dynstr.fileOffset + dynstr.size, hashAlign);
		hash.memOffset := hash.fileOffset;
		hash.size := 8 + dynsym.size DIV dynsymEntrySizeVal * 4 * 2;
		got.fileOffset := Aligned(hash.fileOffset + hash.size, gotAlign);
		got.memOffset := Aligned(got.fileOffset, pageSize) + got.fileOffset MOD pageSize;
		got.size := 3 * gotEntrySizeVal;
		dynamic.fileOffset := Aligned(got.fileOffset + got.size, dynamicAlign);
		dynamic.memOffset := got.memOffset + dynamic.fileOffset - got.fileOffset;
		dynamic.size := DynamicSize(13 * dynamicEntrySizeVal);
		bss.fileOffset := Aligned(dynamic.fileOffset + dynamic.size, bssAlign);
		bss.memOffset := dynamic.memOffset + bss.fileOffset - dynamic.fileOffset;		
		bss.size := DataSize;
		shstrtab.fileOffset := Aligned(bss.fileOffset, shstrtabAlign);
		shstrtab.size := fixup;
		textSegmentSizeVal := got.fileOffset;
		dataSegmentSizeVal := shstrtab.fileOffset - got.fileOffset;
		dynamicSegmentSizeVal := shstrtab.fileOffset - dynamic.fileOffset;
		relTextTab.cur := 0;
		relRodataTab.cur := 0;
		firstExp := NIL; lastExp := NIL;
		doWrite := TRUE
	END CalculateLayout;

	PROCEDURE WriteOut;
		VAR res: INTEGER;
	BEGIN
		ASSERT(~error, 20);
		Out := Files.dir.New(Files.dir.This(""), Files.ask);
		IF Out # NIL THEN
			Ro := Out.NewWriter(Ro); Ro.SetPos(0);
			CalculateLayout;
			IF ~error THEN WriteElfHeader END;
			IF ~error THEN WriteSectionHeaderTable END;
			IF ~error THEN WriteProgramHeaderTable END;
			IF ~error THEN Align(textAlign); WriteTextSection END;
			IF ~error THEN Align(rodataAlign); WriteRodataSection END;
			IF ~error THEN Align(relAlign); WriteRelSections END;
			IF ~error THEN Align(dynsymAlign); WriteDynsymSection END;
			IF ~error THEN Align(dynstrAlign); WriteDynstrSection END;
			IF ~error THEN Align(hashAlign); WriteHashSection END;
			IF ~error THEN Align(gotAlign); WriteGotSection END;
			IF ~error THEN Align(dynamicAlign); WriteDynamicSection END;
			IF ~error THEN Align(bssAlign); WriteBssSection END;
			IF ~error THEN Align(shstrtabAlign); WriteShstrtabSection END;

			IF ~error THEN FixupElfHeader END;
			IF ~error THEN FixupSectionHeaderTable END;
			IF ~error THEN FixupTextSection END;
			IF ~error THEN FixupDynsymSection END;
			IF ~error THEN FixupDynamicSection END;
			Out.Register(soName$, "so", Files.ask, res);
			IF res # 0 THEN error := TRUE END
		ELSE
			error := TRUE
		END
	END WriteOut;
	
	PROCEDURE ResetHashtab;
		VAR i: INTEGER;
	BEGIN
		i := 0;
		WHILE i # LEN(hashtab) DO
			hashtab[i] := "";
			INC(i)
		END
	END ResetHashtab;

	PROCEDURE ResetNeededIdx;
		VAR i: INTEGER;
	BEGIN
		i := 0;
		WHILE i # LEN(neededIdx) DO
			neededIdx[i] := 0;
			INC(i)
		END
	END ResetNeededIdx;

	PROCEDURE MakeSoName (VAR name: ARRAY OF CHAR; type: ARRAY OF CHAR);
		VAR i, j: INTEGER; ext: Files.Name; ch: CHAR;
	BEGIN
		ASSERT((type = "") OR (type[0] = "."), 20);
		i := 0;
		WHILE (name[i] # 0X) & (name[i] # ".") DO INC(i) END;
		IF name[i] = "." THEN
			IF name[i + 1] = 0X THEN name[i] := 0X END
		ELSIF i < LEN(name) - (LEN(type$) + 1) THEN
			IF type = "" THEN ext := ".so" ELSE ext := type$ END;
			j := 0; ch := ext[0];
			WHILE ch # 0X DO
				IF (ch >= "A") & (ch <= "Z") THEN
					ch := CHR(ORD(ch) + ORD("a") - ORD("A"))
				END;
				name[i] := ch; INC(i); INC(j); ch := ext[j]
			END;
			name[i] := 0X
		END
	END MakeSoName;

	(* A. V. Shiryaev: Scanner *)

		PROCEDURE (VAR S: Scanner) SetPos (x: INTEGER), NEW;
		BEGIN
			S.rider.i := x
		END SetPos;

		PROCEDURE (VAR S: Scanner) ConnectTo (IN src: ARRAY OF CHAR), NEW;
		BEGIN
			NEW(S.rider.s, LEN(src$) + 1);
			S.rider.s^ := src$;
			S.rider.i := 0;
			S.start := 0;
			S.type := TMEOT
		END ConnectTo;

		PROCEDURE (VAR R: ScanRider) ReadPrevChar (VAR ch: CHAR), NEW;
		BEGIN
			ch := R.s[R.i]
		END ReadPrevChar;

		PROCEDURE (VAR R: ScanRider) ReadChar (VAR ch: CHAR), NEW;
		BEGIN
			ch := R.s[R.i];
			INC(R.i)
		END ReadChar;

		PROCEDURE (VAR R: ScanRider) Pos (): INTEGER, NEW;
		BEGIN
			RETURN R.i
		END Pos;

		PROCEDURE (VAR S: Scanner) Scan, NEW;
			VAR j: INTEGER;
		
			PROCEDURE IsLetter (c: CHAR): BOOLEAN;
			BEGIN
				RETURN ((c >= 'A') & (c <= 'Z')) OR ((c >= 'a') & (c <= 'z')) OR (c = '_')
			END IsLetter;

			PROCEDURE IsDigit (c: CHAR): BOOLEAN;
			BEGIN
				RETURN (c >= '0') & (c <= '9')
			END IsDigit;

		BEGIN
			WHILE (S.rider.i < LEN(S.rider.s$)) & (S.rider.s[S.rider.i] = ' ') DO
				INC(S.rider.i)
			END;
			IF S.rider.i < LEN(S.rider.s$) THEN
				S.start := S.rider.i;
				IF IsDigit(S.rider.s[S.rider.i]) THEN
					S.type := TMEOT (* XXX *)
				ELSIF IsLetter(S.rider.s[S.rider.i]) THEN
					S.type := TMString;
					j := 0;
					WHILE (S.rider.i < LEN(S.rider.s$)) & (IsLetter(S.rider.s[S.rider.i]) OR IsDigit(S.rider.s[S.rider.i])) DO
						S.string[j] := S.rider.s[S.rider.i];
						INC(j);
						INC(S.rider.i)
					END;
					S.string[j] := 0X
				ELSE
					S.type := TMChar;
					S.char := S.rider.s[S.rider.i];
					INC(S.rider.i)
				END
			ELSE
				S.type := TMEOT
			END
		END Scan;

	PROCEDURE ParseExt (IN S: Scanner; OUT ext: Files.Name);
		VAR ch: CHAR; i: INTEGER;
	BEGIN
		ext := "";
		S.rider.ReadPrevChar(ch);
		IF ch = "." THEN
			S.rider.ReadChar(ch);
			i := 0;
			WHILE (ch # 20X) & (ch # 9X) DO
				ext[i] := ch;
				INC(i);
				S.rider.ReadChar(ch)
			END;
			ext[i] := 0X
		ELSIF (ch # 20X) & (ch # 9X) THEN
			WriteSString("Invalid character '");WriteChar(ch); WriteSString("' for file name.");
			WriteLn; FlushW; error := TRUE
		END;
		S.SetPos(S.rider.Pos())
	END ParseExt;

	PROCEDURE ParseModList (S: Scanner; end: INTEGER);
		VAR mod: Module;
	BEGIN
		WHILE (S.start < end) & (S.type = TMString) DO
			NEW(mod); mod.fileName := S.string$;
			mod.next := modList; modList := mod;
			S.Scan;
			WHILE (S.start < end) & (S.type = TMChar) &
				((S.char = "*") OR (S.char = "+") OR (S.char = "$") OR (S.char = "#")) DO
				IF S.char = "*" THEN mod.dll := TRUE
				ELSIF S.char = "+" THEN kernel := mod
				ELSIF S.char = "$" THEN main := mod
				ELSE mod.intf := TRUE;
					ASSERT(isDll, 126);
					IF ~isDll THEN
						WriteSString("Exports from Exe not possible. Use LinkDll or LinkDynDll.");
						WriteLn; FlushW; error := TRUE
					END
				END;
				S.Scan
			END
		END
	END ParseModList;

	PROCEDURE LinkIt (IN txt: ARRAY OF CHAR);
		VAR S: Scanner; name, ext: Files.Name; end: INTEGER;
	BEGIN
		doWrite := TRUE;
		headerstrtab.tab[0] := 0X;
		headerstrtab.cur := 1;
		dynstrtab.tab[0] := 0X;
		dynstrtab.cur := 1;
		relTextTab.cur := 0;
		relRodataTab.cur := 0;
		ResetHashtab;
		ResetNeededIdx;
		modList := NIL; kernel := NIL; main := NIL;
		last := NIL; impg := NIL; impd := NIL;
		firstExp := NIL; lastExp := NIL;
		(* Dialog.ShowStatus("linking"); *)
		Console.WriteStr("linking"); Console.WriteLn;
		error := FALSE; modList := NIL;

		(*
		IF DevCommanders.par = NIL THEN RETURN END;
		S.ConnectTo(DevCommanders.par.text);
		S.SetPos(DevCommanders.par.beg);
		end := DevCommanders.par.end;
		DevCommanders.par := NIL;
		*)

		S.ConnectTo(txt);
		S.SetPos(0);
		end := LEN(txt$);

		S.Scan;

		IF S.type = TMString THEN
			name := S.string$;
			ext := "";
			ParseExt(S, ext); S.Scan;
			IF ~error THEN
				MakeSoName(name, ext);
				IF (S.type = TMChar) & (S.char = ":") THEN S.Scan;
					IF (S.type = TMChar) & (S.char = "=") THEN S.Scan;
						ParseModList(S, end);
						ReadHeaders;
						soName := SHORT(name$);
						IF ~error THEN
							WriteOut
						END;
						IF ~error THEN
							WriteString("Library " + name + " written: ");
							WriteInt(Out.Length()); WriteString("    "); WriteInt(text.size)
						END
					ELSE
						error := TRUE;
						WriteString(" := missing")
					END
				ELSE
					error := TRUE;
					WriteString(" := missing")
				END;
				WriteLn; FlushW
			END
		END;
		(* IF error THEN Dialog.ShowStatus("Failed to write library") ELSE Dialog.ShowStatus("Ok") END; *)
		IF error THEN Console.WriteStr("Failed to write library"); Console.WriteLn ELSE Console.WriteStr("Ok"); Console.WriteLn END;
		S.ConnectTo("");
		modList := NIL; kernel := NIL; main := NIL; firstExp := NIL; lastExp := NIL;
		last := NIL; impg := NIL; impd := NIL; code := NIL
	END LinkIt;

(*
	exes are not supported

	PROCEDURE Link*;
	BEGIN
		HALT(126);
		isDll := FALSE; isStatic := FALSE;
		LinkIt
	END Link;
	
	PROCEDURE LinkExe*;
	BEGIN
		HALT(126);
		isDll := FALSE; isStatic := TRUE;
		LinkIt
	END LinkExe;
*)
	
	PROCEDURE LinkDll* (IN txt: ARRAY OF CHAR);
	BEGIN
		isDll := TRUE; isStatic := TRUE;
		LinkIt(txt)
	END LinkDll;
	
	PROCEDURE LinkDynDll* (IN txt: ARRAY OF CHAR);
	BEGIN
		isDll := TRUE; isStatic := FALSE;
		LinkIt(txt)
	END LinkDynDll;
		
BEGIN
	newRec := "NewRec"; newArr := "NewArr"
END Dev0ElfLinker.

LinTestSo LinTestSo2 LinKernel

DevElfLinker.LinkDynDll libtestbb.so := LinKernel+$ LinTestSo2 LinTestSo# ~
DevElfLinker.LinkDll libtestbb.so := LinTestSo2 LinTestSo# ~

