Linking Modules and Creating EXE File
This is the last phase of program compilation – linking all modules and creating the executable file for program. Turbo Pascal first marks all symbol table blocks for variables, typed constants, procedures and code blocks as unused, then it iterates through all modules and marks used blocks starting from the main program, after that it calculates code offsets, variable offsets, resolves references, and finally, it writes code to either overlay file or to the executable file.
Procedure Link;
Var SavedHeapPtr1: Word;
SavedHeapPtr2: Word;
UnitCodeSize: Word;
NumberOfUnprocessedUnitsForReferences: Word;
begin
UnitPtrRec.Ofs := 0;
If not (LinkBufferOnDisk in LinkerOptions) then
begin
UnitPtrRec.Seg := LastLoadedUsedUnit;
Repeat
LoadUnitFileSymbolTables (stCode, stTypedConstantsReferences);
UnitPtr^.UnitPathAndNameOffset := 0;
UnitPtrRec.Seg := UnitPtr^.PreviousUnitSegment;
until UnitPtrRec.Seg = 0;
end;
MarkProcCodeConstVarBlocksAsUnused;
PUnitHeader (Ptr (LastLoadedUsedUnit, 0))^.OverlayedUnitCodeSize := 0;
PUnitHeader (Ptr (SystemUnitSegment, 0))^.OverlayedUnitCodeSize := 0;
Repeat
UnitPtrRec.Seg := LastLoadedUsedUnit;
NumberOfUnprocessedUnitsForReferences := 0;
Repeat
If UnitPtr^.NumberOfUnprocessedBlocksForReferences <> 0 then
begin
SavedHeapPtr1 := HeapPtrRec.Seg;
LoadUnitFileSymbolTables (stCodeReferences, stTypedConstantsReferences);
MarkUsedBlocksInUnit;
HeapPtrRec.Seg := SavedHeapPtr1;
Inc (NumberOfUnprocessedUnitsForReferences);
end;
UnitPtrRec.Seg := UnitPtr^.PreviousUnitSegment;
until UnitPtrRec.Seg = 0;
until NumberOfUnprocessedUnitsForReferences = 0;
CurrentCodeSegment := 0;
OverlayHeapSize := 0;
CurrentRelocationItemOffset := RelocationTableOffset;
CalculateCodeBlockOffsetsAndTotalCodeSize;
DataSegment := CurrentCodeSegment;
CalculateVariableOffsetsAndTotalDataSize;
CalculateFirstFreeSegment;
If not (cmoCreateExeFile in CompilerModeOptions) then
begin
UnitPtrRec.Seg := LastLoadedUsedUnit;
HeapPtrRec.Seg := UnitPtr^.SymbolTableSegment [stCode];
Exit;
end;
SavedHeapPtr1 := HeapPtrRec.Seg;
CreateExeFile;
If OverlayHeapSize <> 0 then
begin
FindFilePath (StrCopy (@Identifier, CurrentFileName), Dir_Forced or Dir_EXE_TPU or Ext_Forced or Ext_OVR);
CreateFile (OverlayFile, @Identifier);
OutputFileHandle := FileRec (OverlayFile).Handle;
Seek (OverlayFile, 8);
end;
OverlayCodeList := 0;
OverlayHeader.OverlaySize.Long := 8;
ExeHeaderBuffer.Ofs := RelocationTableOffset;
UnitPtrRec.Seg := LastLoadedUsedUnit;
Repeat
SavedHeapPtr2 := HeapPtrRec.Seg;
LoadUnitFileSymbolTables (stCode, stTypedConstantsReferences);
With UnitPtr^ do
begin
UnitCodeSize := CodeSize;
If UnitCodeSize < OverlayedUnitCodeSize then UnitCodeSize := OverlayedUnitCodeSize;
end;
CurrentUnitCodeSegment := HeapPtrRec.Seg;
Inc (HeapPtrRec.Seg, (UnitCodeSize + $000F) shr 4);
If HeapPtrRec.Seg > HeapEndRec.Seg then Error (OutOfMemory);
ResolveReferences (stCodeBlocks, UnitPtr^.SymbolTableSegment [stCode], UnitPtr^.SymbolTableSegment [stCodeReferences]);
JoinUsedSymbolTables (stCodeBlocks, UnitPtr^.SymbolTableSegment [stCode], CurrentUnitCodeSegment);
If UnitPtr^.OverlayedUnitCodeSize <> 0 then
begin
BlockWrite (OverlayFile, Ptr (CurrentUnitCodeSegment, 0)^, UnitPtr^.OverlayedUnitCodeSize);
CreateSegmentReferencesTableForOverlayedUnit;
BlockWrite (OverlayFile, Ptr (CurrentUnitCodeSegment, 0)^, UnitPtr^.NumberOfSegmentReferencesInProgramCodeBlocks * 2);
CreateProcedureOffsetsTableForOverlayedUnit;
end else WriteRelocationItems (stCodeBlocks, UnitPtr^.SymbolTableSegment [stCodeReferences], UnitPtr^.CodeSegment);
FillChar (Ptr (CurrentUnitCodeSegment, UnitPtr^.CodeSize)^, 16 - UnitPtr^.CodeSize and $000F, 0);
BlockWrite (ExeFile, Ptr (CurrentUnitCodeSegment, 0)^, (UnitPtr^.CodeSize + $000F) and $FFF0);
ResolveReferences (stTypedConstantsBlocks, UnitPtr^.SymbolTableSegment [stTypedConstants],
UnitPtr^.SymbolTableSegment [stTypedConstantsReferences]);
JoinUsedSymbolTables (stTypedConstantsBlocks, UnitPtr^.SymbolTableSegment [stTypedConstants], TypedConstantsPointer.Seg);
WriteRelocationItems (stTypedConstantsBlocks, UnitPtr^.SymbolTableSegment [stTypedConstantsReferences], DataSegment);
HeapPtrRec.Seg := SavedHeapPtr2;
UnitPtrRec.Seg := UnitPtr^.PreviousUnitSegment;
until UnitPtrRec.Seg = 0;
WriteTypedConstantsAndExeHeader;
If OverlayHeapSize <> 0 then
begin
Seek (OverlayFile, 0);
Dec (OverlayHeader.OverlaySize.Long, 8);
BlockWrite (OverlayFile, OverlayHeader, 9);
Close (OverlayFile);
end;
HeapPtrRec.Seg := SavedHeapPtr1;
UnitPtrRec.Seg := LastLoadedUsedUnit;
HeapPtrRec.Seg := UnitPtr^.SymbolTableSegment [stCode];
If DebugInformationInExe in LinkerOptions then
begin
Seek (ExeFile, ExeFileSize);
SavedHeapPtr1 := HeapPtrRec.Seg;
AddDebugInfoToExeFile;
HeapPtrRec.Seg := SavedHeapPtr1;
end;
ExeFileHandle := 0;
Close (ExeFile);
If LinkerOptions * [MapFileWithSegments, MapFileWithPublics] <> [] then
begin
SavedHeapPtr1 := HeapPtrRec.Seg;
CreateMapFile;
HeapPtrRec.Seg := SavedHeapPtr1;
end;
end;
This procedure writes relocation items (pointers to segment references) for all used blocks in a unit.
Procedure WriteRelocationItems (Block: TSymbolTable; Seg1, Seg2: Word);
Var CodeConstVarBlockRecord: PCodeConstVarBlockRecord;
CodeConstVarBlockRecordPtrRec: PtrRec absolute CodeConstVarBlockRecord;
ReferencesBlockRecord: PReferencesBlockRecord;
ReferencesBlockRecordPtrRec: PtrRec absolute ReferencesBlockRecord;
Procedure WriteRelocationItemsForBlock;
Var EndOffset: Word;
begin
EndOffset := ReferencesBlockRecordPtrRec.Ofs + CodeConstVarBlockRecord^.ReferencesSize;
While ReferencesBlockRecordPtrRec.Ofs <> EndOffset do
begin
If rfSegment in ReferencesBlockRecord^.Flags then
begin
With RelocationTable^ do
begin
Offset := ReferencesBlockRecord^.PositionOfReference + CodeConstVarBlockRecord^.Offset;
If rfOffset in ReferencesBlockRecord^.Flags then Inc (Offset, 2);
Segment := Seg2;
end;
Inc (RelocationTable);
end;
Inc (ReferencesBlockRecord);
end;
end;
begin
ReferenceRecordsSegment := Seg1;
CodeConstVarBlockRecord := Ptr (UnitPtrRec.Seg, UnitPtr^.BlockOffset [Block]);
ReferencesBlockRecord := Ptr (ReferenceRecordsSegment, 0);
While CodeConstVarBlockRecordPtrRec.Ofs <> UnitPtr^.BlockOffset [Succ (Block)] do
begin
If CodeConstVarBlockRecord^.Offset <> $FFFF then WriteRelocationItemsForBlock else
Inc (ReferencesBlockRecordPtrRec.Ofs, CodeConstVarBlockRecord^.ReferencesSize);
Inc (CodeConstVarBlockRecord);
end;
end;
This procedure creates table of offsets to each segment reference in a unit.
Procedure CreateSegmentReferencesTableForOverlayedUnit;
Var ProgramCodeBlock: PCodeConstVarBlockRecord;
ProgramCodeBlockPtrRec: PtrRec absolute ProgramCodeBlock;
ReferencesBlockRecord: PReferencesBlockRecord;
ReferencesBlockRecordPtrRec: PtrRec absolute ReferencesBlockRecord;
SegmentReferencesTable: PWord;
Procedure CreateSegmentReferencesTableForOneCodeBlock;
Var EndOffset, OffsetOfReference: Word;
begin
EndOffset := ReferencesBlockRecordPtrRec.Ofs + ProgramCodeBlock^.ReferencesSize;
While ReferencesBlockRecordPtrRec.Ofs <> EndOffset do
begin
If rfSegment in ReferencesBlockRecord^.Flags then
begin
OffsetOfReference := ReferencesBlockRecord^.PositionOfReference + ProgramCodeBlock^.Offset;
If rfOffset in ReferencesBlockRecord^.Flags then Inc (OffsetOfReference, 2);
SegmentReferencesTable^ := OffsetOfReference;
Inc (SegmentReferencesTable);
end;
Inc (ReferencesBlockRecordPtrRec.Ofs, 8);
end;
end;
begin
ReferenceRecordsSegment := UnitPtr^.SymbolTableSegment [stCodeReferences];
ReferencesBlockRecord := Ptr (ReferenceRecordsSegment, 0);
SegmentReferencesTable := Ptr (CurrentUnitCodeSegment, 0);
ProgramCodeBlock := Ptr (UnitPtrRec.Seg, UnitPtr^.BlockOffset [stCodeBlocks]);
While ProgramCodeBlockPtrRec.Ofs <> UnitPtr^.BlockOffset [Succ (stCodeBlocks)] do
begin
If ProgramCodeBlock^.Offset <> $FFFF then
CreateSegmentReferencesTableForOneCodeBlock else
Inc (ReferencesBlockRecordPtrRec.Ofs, ProgramCodeBlock^.ReferencesSize);
Inc (ProgramCodeBlock);
end;
end;
This procedure creates table of offsets to procedures in an overlayed unit.
Procedure CreateProcedureOffsetsTableForOverlayedUnit;
Var TempPtr: PWord;
ProceduresBlockRecord: PProceduresBlockRecord;
ProceduresBlockRecordPtrRec: PtrRec absolute ProceduresBlockRecord;
N: Byte;
begin
TempPtr := Ptr (CurrentUnitCodeSegment, 0);
TempPtr^ := $3FCD;
Inc (TempPtr);
TempPtr^ := 0;
Inc (TempPtr);
TempPtr^ := OverlayHeader.OverlaySize.WordL;
Inc (TempPtr);
TempPtr^ := OverlayHeader.OverlaySize.WordH;
Inc (TempPtr);
TempPtr^ := UnitPtr^.OverlayedUnitCodeSize;
Inc (TempPtr);
TempPtr^ := UnitPtr^.NumberOfSegmentReferencesInProgramCodeBlocks * 2;
Inc (TempPtr);
TempPtr^ := (UnitPtr^.CodeSize - $20) div 5;
Inc (TempPtr);
TempPtr^ := OverlayCodeList;
Inc (TempPtr);
For N := 1 to 8 do
begin
TempPtr^ := 0;
Inc (TempPtr);
end;
ProceduresBlockRecord := Ptr (UnitPtrRec.Seg, UnitPtr^.BlockOffset [stProcedures]);
Repeat
If ProceduresBlockRecord^.OverlayedProcedureOffset <> 0 then
begin
TempPtr^ := $3FCD;
Inc (TempPtr);
TempPtr^ := ProceduresBlockRecord^.SizeOfConstants + PProgramCodeBlockRecord (Ptr (UnitPtrRec.Seg,
UnitPtr^.BlockOffset [stCodeBlocks] + ProceduresBlockRecord^.ProgramCodeBlockRecordOffset))^.Offset;
Inc (TempPtr);
Byte (TempPtr^) := 0;
Inc (PChar (TempPtr));
end;
Inc (ProceduresBlockRecord);
until ProceduresBlockRecordPtrRec.Ofs = UnitPtr^.BlockOffset [Succ (stProcedures)];
Inc (OverlayHeader.OverlaySize.Long, UnitPtr^.OverlayedUnitCodeSize);
Inc (OverlayHeader.OverlaySize.Long, UnitPtr^.NumberOfSegmentReferencesInProgramCodeBlocks * 2);
OverlayCodeList := UnitPtr^.CodeSegment;
end;