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; |