{ Copyright (c) 2020 by Free Pascal Development Team This unit implements support import, export, link routines for the m68k Sinclair QL target This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. **************************************************************************** } unit t_sinclairql; {$i fpcdefs.inc} interface uses rescmn, comprsrc, link; type PLinkerSinclairQL = ^TLinkerSinclairQL; TLinkerSinclairQL = class(texternallinker) private Origin: DWord; UseVLink: boolean; function WriteResponseFile(isdll: boolean): boolean; procedure SetSinclairQLInfo; function MakeSinclairQLExe: boolean; public constructor Create; override; procedure SetDefaultInfo; override; procedure InitSysInitUnitName; override; function MakeExecutable: boolean; override; end; implementation uses sysutils,cutils,cfileutl,cclasses,aasmbase, globtype,globals,systems,verbose,cscript,fmodule,i_sinclairql; type TQLHeader = packed record hdr_id: array[0..17] of char; hdr_reserved: byte; hdr_length: byte; hdr_access: byte; hdr_type: byte; hdr_data: dword; hdr_extra: dword; end; TXTccData = packed record xtcc_id: array[0..3] of char; xtcc_data: dword; end; const DefaultQLHeader: TQLHeader = ( hdr_id: ']!QDOS File Header'; hdr_reserved: 0; hdr_length: $f; hdr_access: 0; hdr_type: 1; hdr_data: 0; hdr_extra: 0; ); DefaultXTccData: TXTCCData = ( xtcc_id: 'XTcc'; xtcc_data: 0; ); const DefaultOrigin = $0; ProgramHeaderName = 'main'; constructor TLinkerSinclairQL.Create; begin UseVLink:=(cs_link_vlink in current_settings.globalswitches); Inherited Create; { allow duplicated libs (PM) } SharedLibFiles.doubles:=true; StaticLibFiles.doubles:=true; end; procedure TLinkerSinclairQL.SetSinclairQLInfo; begin if ImageBaseSetExplicity then Origin:=ImageBase else Origin:=DefaultOrigin; with Info do begin if not UseVLink then begin ExeCmd[1]:='ld $DYNLINK $OPT -d -n -o $EXE $RES'; end else begin ExeCmd[1]:='vlink $QLFLAGS $FLAGS $GCSECTIONS $OPT $STRIP $MAP -o $EXE -T $RES'; end; end; end; procedure TLinkerSinclairQL.SetDefaultInfo; begin if target_info.system = system_m68k_sinclairql then SetSinclairQLInfo; end; procedure TLinkerSinclairQL.InitSysInitUnitName; begin sysinitunit:='si_prc'; end; function TLinkerSinclairQL.WriteResponseFile(isdll: boolean): boolean; var linkres : TLinkRes; HPath : TCmdStrListItem; s : string; begin WriteResponseFile:=False; { Open link.res file } LinkRes:=TLinkRes.Create(outputexedir+Info.ResName,true); if UseVLink and (source_info.dirsep <> '/') then LinkRes.fForceUseForwardSlash:=true; { Write path to search libraries } HPath:=TCmdStrListItem(current_module.locallibrarysearchpath.First); while assigned(HPath) do begin s:=HPath.Str; if (cs_link_on_target in current_settings.globalswitches) then s:=ScriptFixFileName(s); LinkRes.Add('-L'+s); HPath:=TCmdStrListItem(HPath.Next); end; HPath:=TCmdStrListItem(LibrarySearchPath.First); while assigned(HPath) do begin s:=HPath.Str; if s<>'' then LinkRes.Add('SEARCH_DIR("'+s+'")'); HPath:=TCmdStrListItem(HPath.Next); end; LinkRes.Add('INPUT ('); { add objectfiles, start with prt0 always } if not (target_info.system in systems_internal_sysinit) then begin s:=FindObjectFile('prt0','',false); LinkRes.AddFileName(maybequoted(s)); end; while not ObjectFiles.Empty do begin s:=ObjectFiles.GetFirst; if s<>'' then begin { vlink doesn't use SEARCH_DIR for object files } if UseVLink then s:=FindObjectFile(s,'',false); LinkRes.AddFileName(maybequoted(s)); end; end; { Write staticlibraries } if not StaticLibFiles.Empty then begin { vlink doesn't need, and doesn't support GROUP } if not UseVLink then begin LinkRes.Add(')'); LinkRes.Add('GROUP('); end; while not StaticLibFiles.Empty do begin S:=StaticLibFiles.GetFirst; LinkRes.AddFileName(maybequoted(s)); end; end; LinkRes.Add(')'); with LinkRes do begin Add(''); Add('PHDRS {'); Add(' '+ProgramHeaderName+' PT_LOAD;'); Add('}'); Add('SECTIONS'); Add('{'); Add(' . = 0x'+hexstr(Origin,8)+';'); Add(' .text : {'); Add(' _stext = .;'); Add(' *(.text .text.* )'); Add(' *(.data .data.* .rodata .rodata.* .fpc.* )'); Add(' *(.stack .stack.*)'); { force the end of section to be word aligned } Add(' . = ALIGN(2); SHORT(0x514C);'); Add(' _etext = .;'); Add(' } :'+ProgramHeaderName); Add(' .bss (NOLOAD): {'); Add(' _sbss = .;'); Add(' *(.bss .bss.*)'); Add(' . = ALIGN(2); SHORT(0x0000);'); Add(' _ebss = .;'); Add(' } :'+ProgramHeaderName); Add('}'); end; { Write and Close response } linkres.writetodisk; linkres.free; WriteResponseFile:=True; end; function TLinkerSinclairQL.MakeSinclairQLExe: boolean; var BinStr, CmdStr : TCmdStr; StripStr: string[40]; DynLinkStr : ansistring; GCSectionsStr : string; FlagsStr : string; QLFlagsStr: string; MapStr : string; ExeName: string; fd,fs: file; fhdr: text; buf: pointer; bufread,bufsize: longint; HdrName: string; HeaderLine: string; HeaderSize: longint; code: word; QLHeader: TQLHeader; XTccData: TXTccData; BinSize: longint; RelocSize: longint; DataSpace: DWord; begin StripStr:=''; GCSectionsStr:=''; DynLinkStr:=''; FlagsStr:=''; QLFlagsStr:=''; MapStr:=''; if (cs_link_map in current_settings.globalswitches) then MapStr:='-M'+maybequoted(ScriptFixFilename(current_module.mapfilename)); if (cs_link_strip in current_settings.globalswitches) then StripStr:='-s'; if rlinkpath<>'' then DynLinkStr:='--rpath-link '+rlinkpath; if UseVLink then begin if create_smartlink_sections then GCSectionsStr:='-gc-all'; if sinclairql_vlink_experimental then QLFlagsStr:='-b sinclairql -q -'+lower(sinclairql_metadata_format)+' -stack='+tostr(StackSize) else QLFlagsStr:='-b rawseg -q'; end; ExeName:=current_module.exefilename; HdrName:=ExeName+'.hdr'; { Call linker } SplitBinCmd(Info.ExeCmd[1],BinStr,CmdStr); binstr:=FindUtil(utilsprefix+BinStr); Replace(cmdstr,'$OPT',Info.ExtraOptions); Replace(cmdstr,'$EXE',maybequoted(ScriptFixFileName(ExeName))); Replace(cmdstr,'$RES',maybequoted(ScriptFixFileName(outputexedir+Info.ResName))); Replace(cmdstr,'$MAP',MapStr); Replace(cmdstr,'$FLAGS',FlagsStr); Replace(cmdstr,'$STRIP',StripStr); Replace(cmdstr,'$GCSECTIONS',GCSectionsStr); Replace(cmdstr,'$DYNLINK',DynLinkStr); Replace(cmdstr,'$QLFLAGS',QLFlagsStr); MakeSinclairQLExe:=DoExec(BinStr,CmdStr,true,false); { Kludge: With the above linker script, vlink will produce two files. The main binary and the relocation info. Here we copy the two together. (KB) } if MakeSinclairQLExe and not sinclairql_vlink_experimental then begin QLHeader:=DefaultQLHeader; XTccData:=DefaultXTccData; BinSize:=0; RelocSize:=0; bufsize:=16384; {$push} {$i-} { Rename vlink's output file into the header file it is, then parse the expected length from it. Later we use either this size or the final binary size in the BASIC loader, depending on which one is bigger. (KB) } RenameFile(ExeName,HdrName); assign(fhdr,HdrName); reset(fhdr); readln(fhdr,HeaderLine); Val(Copy(HeaderLine,RPos('0x',HeaderLine),Length(HeaderLine)),HeaderSize,code); close(fhdr); buf:=GetMem(bufsize); assign(fd,ExeName); rewrite(fd,1); assign(fs,ExeName+'.'+ProgramHeaderName+'.rel'+ProgramHeaderName); reset(fs,1); RelocSize := FileSize(fs); close(fs); assign(fs,ExeName+'.'+ProgramHeaderName); reset(fs,1); BinSize := FileSize(fs); { We assume .bss size is total size indicated by linker minus emmited binary. DataSpace size is .bss + stack space } DataSpace := NToBE(DWord(max((HeaderSize - BinSize) - RelocSize + StackSize,0))); { Option: prepend QEmuLator and QPC2 v5 compatible header to EXE } if sinclairql_metadata_format='QHDR' then begin QLHeader.hdr_data:=DataSpace; blockwrite(fd, QLHeader, sizeof(QLHeader)); end; repeat blockread(fs,buf^,bufsize,bufread); blockwrite(fd,buf^,bufread); until eof(fs); close(fs); // erase(fs); assign(fs,ExeName+'.'+ProgramHeaderName+'.rel'+ProgramHeaderName); reset(fs,1); repeat blockread(fs,buf^,bufsize,bufread); blockwrite(fd,buf^,bufread); until eof(fs); close(fs); // erase(fs); { Option: append cross compilation data space marker, this can be picked up by a special version of InfoZIP (compiled with -DQLZIP and option -Q) or by any of the XTcc unpack utilities } if sinclairql_metadata_format='XTCC' then begin XTccData.xtcc_data:=DataSpace; blockwrite(fd, XTccData, sizeof(XTccData)); end; close(fd); {$pop} FreeMem(buf); MakeSinclairQLExe:=(code = 0) and not (BinSize = 0) and (IOResult = 0); end; end; function TLinkerSinclairQL.MakeExecutable:boolean; var success : boolean; bootfile : TScript; ExeName: String; begin if not(cs_link_nolink in current_settings.globalswitches) then Message1(exec_i_linking,current_module.exefilename); { Write used files and libraries } WriteResponseFile(false); success:=MakeSinclairQLExe; { Remove ReponseFile } if (success) and not(cs_link_nolink in current_settings.globalswitches) then DeleteFile(outputexedir+Info.ResName); MakeExecutable:=success; { otherwise a recursive call to link method } end; {***************************************************************************** Initialize *****************************************************************************} initialization RegisterLinker(ld_sinclairql,TLinkerSinclairQL); RegisterTarget(system_m68k_sinclairql_info); end.