mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-05 05:58:02 +02:00
517 lines
16 KiB
ObjectPascal
517 lines
16 KiB
ObjectPascal
{
|
|
Copyright (c) 1998-2008 by Peter Vreman
|
|
|
|
This unit implements support import,export,link routines
|
|
for the Android 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_android;
|
|
|
|
{$i fpcdefs.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
globtype,
|
|
aasmdata,
|
|
symsym,symdef,ppu,
|
|
import,export,expunix,link;
|
|
|
|
type
|
|
timportlibandroid=class(timportlib)
|
|
procedure generatelib;override;
|
|
end;
|
|
|
|
{ texportlibandroid }
|
|
|
|
texportlibandroid=class(texportlibunix)
|
|
public
|
|
procedure setfininame(list: TAsmList; const s: string); override;
|
|
procedure exportprocedure(hp: texported_item); override;
|
|
procedure generatelib; override;
|
|
end;
|
|
|
|
{ tlinkerandroid }
|
|
|
|
tlinkerandroid=class(texternallinker)
|
|
private
|
|
prtobj : string[80];
|
|
reorder : boolean;
|
|
FJNIOnLoadName: TSymStr;
|
|
Function WriteResponseFile(isdll:boolean) : Boolean;
|
|
function DoLink(IsSharedLib: boolean): boolean;
|
|
public
|
|
constructor Create;override;
|
|
procedure SetDefaultInfo;override;
|
|
procedure InitSysInitUnitName;override;
|
|
function MakeExecutable:boolean;override;
|
|
function MakeSharedLibrary:boolean;override;
|
|
procedure LoadPredefinedLibraryOrder; override;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
SysUtils,
|
|
cutils,cfileutl,cclasses,
|
|
verbose,systems,globals,
|
|
symconst,cscript,
|
|
fmodule,
|
|
aasmbase,aasmtai,aasmcpu,cpubase,hlcgcpu,hlcgobj,
|
|
cgbase,cgobj,cgutils,ogbase,ncgutil,
|
|
comprsrc,
|
|
rescmn, i_android
|
|
;
|
|
|
|
const
|
|
SJNI_OnLoad = 'JNI_OnLoad';
|
|
|
|
{*****************************************************************************
|
|
TIMPORTLIBANDROID
|
|
*****************************************************************************}
|
|
|
|
procedure timportlibandroid.generatelib;
|
|
var
|
|
i : longint;
|
|
ImportLibrary : TImportLibrary;
|
|
begin
|
|
for i:=0 to current_module.ImportLibraryList.Count-1 do
|
|
begin
|
|
ImportLibrary:=TImportLibrary(current_module.ImportLibraryList[i]);
|
|
current_module.linkothersharedlibs.add(ImportLibrary.Name,link_always);
|
|
end;
|
|
end;
|
|
|
|
|
|
{*****************************************************************************
|
|
TEXPORTLIBANDROID
|
|
*****************************************************************************}
|
|
|
|
procedure texportlibandroid.setfininame(list: TAsmList; const s: string);
|
|
begin
|
|
{ the problem with not having a .fini section is that a finalization
|
|
routine in regular code can get "smart" linked away -> reference it
|
|
just like the debug info }
|
|
new_section(list,sec_fpc,'links',0);
|
|
list.concat(Tai_const.Createname(s,0));
|
|
inherited setfininame(list,s);
|
|
end;
|
|
|
|
procedure texportlibandroid.exportprocedure(hp: texported_item);
|
|
begin
|
|
{
|
|
Android versions prior to 4.1 do not support recursive dlopen() calls.
|
|
Therefore if a shared library is loaded by JVM ( using dlopen() ),
|
|
it is not possible to use dlopen() in a units initialization code -
|
|
dlopen() simply hangs.
|
|
To workaround this issue, if a library exports JNI_OnLoad(), then
|
|
no unit initialization is performed during library load.
|
|
The initialization is called when JVM has loaded the library and calls
|
|
JNI_OnLoad().
|
|
}
|
|
// Check for the JNI_OnLoad export
|
|
if current_module.islibrary and not hp.is_var and assigned(hp.sym) and
|
|
(hp.sym.typ = procsym) and (eo_name in hp.options) and
|
|
(hp.name^ = SJNI_OnLoad) and (tprocsym(hp.sym).procdeflist.count = 1) then
|
|
begin
|
|
// Save the JNI_OnLoad procdef
|
|
tlinkerandroid(Linker).FJNIOnLoadName:=tprocdef(tprocsym(hp.sym).procdeflist[0]).mangledname;
|
|
hp.Free;
|
|
exit;
|
|
end;
|
|
inherited exportprocedure(hp);
|
|
end;
|
|
|
|
procedure texportlibandroid.generatelib;
|
|
begin
|
|
inherited generatelib;
|
|
if tlinkerandroid(Linker).FJNIOnLoadName = '' then
|
|
exit;
|
|
// If JNI_OnLoad is exported, export a system proxy function instead
|
|
create_hlcodegen;
|
|
new_section(current_asmdata.asmlists[al_procedures],sec_code,'',0);
|
|
hlcg.g_external_wrapper(current_asmdata.asmlists[al_procedures],nil,SJNI_OnLoad,'FPC_JNI_ON_LOAD_PROXY',true);
|
|
destroy_hlcodegen;
|
|
exportedsymnames.insert(SJNI_OnLoad);
|
|
end;
|
|
|
|
{*****************************************************************************
|
|
TLINKERANDROID
|
|
*****************************************************************************}
|
|
|
|
Constructor TLinkerAndroid.Create;
|
|
begin
|
|
Inherited Create;
|
|
end;
|
|
|
|
|
|
procedure TLinkerAndroid.SetDefaultInfo;
|
|
var
|
|
s: string;
|
|
begin
|
|
with Info do
|
|
begin
|
|
{ Specify correct max-page-size and common-page-size to prevent big gaps between sections in resulting executable }
|
|
s:='ld -z max-page-size=0x1000 -z common-page-size=0x1000 -z noexecstack -z now -z relro --build-id $OPT -L. -T $RES -o $EXE';
|
|
ExeCmd[1]:=s + ' --entry=_start';
|
|
DllCmd[1]:=s + ' -shared -soname $SONAME';
|
|
DllCmd[2]:='strip --strip-unneeded $EXE';
|
|
ExtDbgCmd[1]:='objcopy --only-keep-debug $EXE $DBG';
|
|
ExtDbgCmd[2]:='objcopy --add-gnu-debuglink=$DBG $EXE';
|
|
ExtDbgCmd[3]:='strip --strip-unneeded $EXE';
|
|
{$ifdef cpu64bitalu}
|
|
DynamicLinker:='/system/bin/linker64';
|
|
{$else}
|
|
DynamicLinker:='/system/bin/linker';
|
|
{$endif cpu64bitalu}
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TLinkerAndroid.LoadPredefinedLibraryOrder;
|
|
// put your linkorder/linkalias overrides here.
|
|
// Note: assumes only called when reordering/aliasing is used.
|
|
Begin
|
|
if not (cs_link_no_default_lib_order in current_settings.globalswitches) Then
|
|
Begin
|
|
LinkLibraryOrder.add('gcc','',15);
|
|
LinkLibraryOrder.add('c','',100);
|
|
LinkLibraryOrder.add('gmon','',120);
|
|
LinkLibraryOrder.add('dl','',140);
|
|
LinkLibraryOrder.add('pthread','',160);
|
|
end;
|
|
End;
|
|
|
|
Procedure TLinkerAndroid.InitSysInitUnitName;
|
|
begin
|
|
reorder := ReOrderEntries;
|
|
if current_module.islibrary then
|
|
prtobj:='dllprt0'
|
|
else
|
|
prtobj:='prt0';
|
|
end;
|
|
|
|
Function TLinkerAndroid.WriteResponseFile(isdll:boolean) : Boolean;
|
|
Var
|
|
linkres : TLinkRes;
|
|
i : longint;
|
|
HPath : TCmdStrListItem;
|
|
s,s1 : TCmdStr;
|
|
begin
|
|
result:=False;
|
|
{ Always link to libc }
|
|
AddSharedLibrary('c');
|
|
|
|
{ Open link.res file }
|
|
LinkRes:=TLinkRes.Create(outputexedir+Info.ResName,true);
|
|
with linkres do
|
|
begin
|
|
{ Write path to search libraries }
|
|
HPath:=TCmdStrListItem(current_module.locallibrarysearchpath.First);
|
|
while assigned(HPath) do
|
|
begin
|
|
Add('SEARCH_DIR('+maybequoted(HPath.Str)+')');
|
|
HPath:=TCmdStrListItem(HPath.Next);
|
|
end;
|
|
HPath:=TCmdStrListItem(LibrarySearchPath.First);
|
|
while assigned(HPath) do
|
|
begin
|
|
Add('SEARCH_DIR('+maybequoted(HPath.Str)+')');
|
|
HPath:=TCmdStrListItem(HPath.Next);
|
|
end;
|
|
|
|
{ force local symbol resolution (i.e., inside the shared }
|
|
{ library itself) for all non-exorted symbols, otherwise }
|
|
{ several RTL symbols of FPC-compiled shared libraries }
|
|
{ will be bound to those of a single shared library or }
|
|
{ to the main program }
|
|
if isdll or (cs_create_pic in current_settings.moduleswitches) then
|
|
begin
|
|
add('VERSION');
|
|
add('{');
|
|
add(' {');
|
|
if not texportlibunix(exportlib).exportedsymnames.empty then
|
|
begin
|
|
add(' global:');
|
|
repeat
|
|
add(' '+texportlibunix(exportlib).exportedsymnames.getfirst+';');
|
|
until texportlibunix(exportlib).exportedsymnames.empty;
|
|
end;
|
|
add(' local:');
|
|
add(' *;');
|
|
add(' };');
|
|
add('}');
|
|
end;
|
|
|
|
StartSection('INPUT(');
|
|
{ add objectfiles, start with prt0 always }
|
|
if not (target_info.system in systems_internal_sysinit) and (prtobj<>'') then
|
|
AddFileName(maybequoted(FindObjectFile(prtobj,'',false)));
|
|
{ Add libc startup object file }
|
|
if isdll then
|
|
s:='crtbegin_so.o'
|
|
else
|
|
if cs_link_staticflag in current_settings.globalswitches then
|
|
s:='crtbegin_static.o'
|
|
else
|
|
s:='crtbegin_dynamic.o';
|
|
librarysearchpath.FindFile(s,false,s1);
|
|
AddFileName(maybequoted(s1));
|
|
{ main objectfiles }
|
|
while not ObjectFiles.Empty do
|
|
begin
|
|
s:=ObjectFiles.GetFirst;
|
|
if s<>'' then
|
|
AddFileName(maybequoted(s));
|
|
end;
|
|
EndSection(')');
|
|
|
|
{ Write staticlibraries }
|
|
if not StaticLibFiles.Empty then
|
|
begin
|
|
Add('GROUP(');
|
|
While not StaticLibFiles.Empty do
|
|
begin
|
|
S:=StaticLibFiles.GetFirst;
|
|
AddFileName(maybequoted(s))
|
|
end;
|
|
Add(')');
|
|
end;
|
|
|
|
// we must reorder here because the result could empty sharedlibfiles
|
|
if reorder Then
|
|
ExpandAndApplyOrder(SharedLibFiles);
|
|
// after this point addition of shared libs not allowed.
|
|
|
|
if not SharedLibFiles.Empty then
|
|
begin
|
|
if (SharedLibFiles.Count<>1) or reorder then
|
|
begin
|
|
Add('INPUT(');
|
|
While not SharedLibFiles.Empty do
|
|
begin
|
|
S:=SharedLibFiles.GetFirst;
|
|
i:=Pos(target_info.sharedlibext,S);
|
|
if i>0 then
|
|
Delete(S,i,255);
|
|
Add('-l'+s);
|
|
end;
|
|
Add(')');
|
|
end;
|
|
|
|
if (cs_link_staticflag in current_settings.globalswitches) or
|
|
(not reorder) then
|
|
begin
|
|
Add('GROUP(');
|
|
{ when we have -static for the linker the we also need libgcc }
|
|
if (cs_link_staticflag in current_settings.globalswitches) then
|
|
begin
|
|
Add('-lgcc');
|
|
if librarysearchpath.FindFile('libgcc_eh.a',false,s1) then
|
|
Add('-lgcc_eh');
|
|
end;
|
|
{ be sure that libc is the last lib }
|
|
if not reorder then
|
|
Add('-lc');
|
|
Add(')');
|
|
end;
|
|
end;
|
|
|
|
{ objects which must be at the end }
|
|
{ Add libc finalization object file }
|
|
Add('INPUT(');
|
|
if isdll then
|
|
s:='crtend_so.o'
|
|
else
|
|
s:='crtend_android.o';
|
|
librarysearchpath.FindFile(s,false,s1);
|
|
AddFileName(maybequoted(s1));
|
|
Add(')');
|
|
|
|
{ Additions to the linker script }
|
|
|
|
add('SECTIONS');
|
|
add('{');
|
|
add(' .data :');
|
|
add(' {');
|
|
{ extra by FPC }
|
|
add(' KEEP (*(.fpc .fpc.n_version .fpc.n_links))');
|
|
add(' }');
|
|
add('}');
|
|
add('INSERT AFTER .data1');
|
|
|
|
// Define different aliases for normal and JNI libraries
|
|
if FJNIOnLoadName <> '' then
|
|
begin
|
|
s:=FJNIOnLoadName;
|
|
s1:='FPC_JNI_LIB_MAIN_ANDROID';
|
|
end
|
|
else
|
|
begin
|
|
s:='0';
|
|
s1:='PASCALMAIN';
|
|
end;
|
|
add('FPC_JNI_ON_LOAD = ' + s + ';');
|
|
add('FPC_LIB_MAIN_ANDROID = ' + s1 + ';');
|
|
|
|
{ Write and Close response }
|
|
writetodisk;
|
|
Free;
|
|
end;
|
|
|
|
WriteResponseFile:=True;
|
|
end;
|
|
|
|
function tlinkerandroid.DoLink(IsSharedLib: boolean): boolean;
|
|
var
|
|
i: longint;
|
|
binstr, cmdstr: TCmdStr;
|
|
s, opts, outname: string;
|
|
success: boolean;
|
|
begin
|
|
Result:=False;
|
|
if IsSharedLib then
|
|
outname:=current_module.sharedlibfilename
|
|
else
|
|
outname:=current_module.exefilename;
|
|
if not(cs_link_nolink in current_settings.globalswitches) then
|
|
Message1(exec_i_linking, outname);
|
|
|
|
opts:='';
|
|
if not IsSharedLib and (cs_create_pic in current_settings.moduleswitches) then
|
|
opts:=opts + ' --pic-executable';
|
|
if (cs_link_strip in current_settings.globalswitches) and
|
|
not (cs_link_separate_dbg_file in current_settings.globalswitches) then
|
|
opts:=opts + ' -s';
|
|
if (cs_link_map in current_settings.globalswitches) then
|
|
opts:=opts + ' -Map '+maybequoted(ChangeFileExt(outname,'.map'));
|
|
if create_smartlink_sections then
|
|
opts:=opts + ' --gc-sections';
|
|
if (cs_link_staticflag in current_settings.globalswitches) then
|
|
opts:=opts + ' -static'
|
|
else
|
|
if cshared then
|
|
opts:=opts + ' -call_shared';
|
|
if rlinkpath<>'' then
|
|
opts:=opts+' --rpath-link '+rlinkpath;
|
|
|
|
if not IsSharedLib then
|
|
begin
|
|
opts:=opts + ' --dynamic-linker ' + Info.DynamicLinker;
|
|
{ create dynamic symbol table? }
|
|
if HasExports then
|
|
opts:=opts+' -E';
|
|
end;
|
|
|
|
opts:=Trim(opts + ' ' + Info.ExtraOptions);
|
|
|
|
{ Write used files and libraries }
|
|
WriteResponseFile(IsSharedLib);
|
|
|
|
{ Call linker }
|
|
if IsSharedLib then
|
|
s:=Info.DllCmd[1]
|
|
else
|
|
s:=Info.ExeCmd[1];
|
|
SplitBinCmd(s, binstr, cmdstr);
|
|
Replace(cmdstr,'$EXE',maybequoted(outname));
|
|
Replace(cmdstr,'$OPT',opts);
|
|
Replace(cmdstr,'$RES',maybequoted(outputexedir+Info.ResName));
|
|
if IsSharedLib then
|
|
Replace(cmdstr,'$SONAME',ExtractFileName(outname));
|
|
|
|
{ We should use BFD version of LD, since GOLD version does not support INSERT command in linker scripts }
|
|
s:=utilsprefix+binstr+'.bfd';
|
|
if (source_info.exeext<>'') then
|
|
s:=s+source_info.exeext;
|
|
s:=FindUtil(s,false);
|
|
if FileExists(s, True) then
|
|
binstr:=s
|
|
else
|
|
// fallback to ld for very old or custom binutils
|
|
binstr:=FindUtil(utilsprefix+BinStr);
|
|
|
|
success:=DoExec(binstr,CmdStr,true,false);
|
|
|
|
{ Create external .dbg file with debuginfo }
|
|
if success and (cs_link_separate_dbg_file in current_settings.globalswitches) then
|
|
begin
|
|
for i:=1 to 3 do
|
|
begin
|
|
SplitBinCmd(Info.ExtDbgCmd[i],binstr,cmdstr);
|
|
Replace(cmdstr,'$EXE',maybequoted(outname));
|
|
Replace(cmdstr,'$DBGFN',maybequoted(extractfilename(current_module.dbgfilename)));
|
|
Replace(cmdstr,'$DBG',maybequoted(current_module.dbgfilename));
|
|
success:=DoExec(FindUtil(utilsprefix+BinStr),CmdStr,true,false);
|
|
if not success then
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
{ Remove ReponseFile }
|
|
if (success) and not(cs_link_nolink in current_settings.globalswitches) then
|
|
DeleteFile(outputexedir+Info.ResName);
|
|
|
|
Result:=success; { otherwise a recursive call to link method }
|
|
end;
|
|
|
|
function TLinkerAndroid.MakeExecutable:boolean;
|
|
begin
|
|
Result:=DoLink(False);
|
|
end;
|
|
|
|
Function TLinkerAndroid.MakeSharedLibrary:boolean;
|
|
begin
|
|
Result:=DoLink(True);
|
|
end;
|
|
|
|
{*****************************************************************************
|
|
Initialize
|
|
*****************************************************************************}
|
|
|
|
initialization
|
|
RegisterLinker(ld_android,TLinkerAndroid);
|
|
{$ifdef ARM}
|
|
RegisterImport(system_arm_android,timportlibandroid);
|
|
RegisterExport(system_arm_android,texportlibandroid);
|
|
RegisterTarget(system_arm_android_info);
|
|
{$endif ARM}
|
|
{$ifdef AARCH64}
|
|
RegisterImport(system_aarch64_android,timportlibandroid);
|
|
RegisterExport(system_aarch64_android,texportlibandroid);
|
|
RegisterTarget(system_aarch64_android_info);
|
|
{$endif AARCH64}
|
|
{$ifdef I386}
|
|
RegisterImport(system_i386_android,timportlibandroid);
|
|
RegisterExport(system_i386_android,texportlibandroid);
|
|
RegisterTarget(system_i386_android_info);
|
|
{$endif I386}
|
|
{$ifdef X86_64}
|
|
RegisterImport(system_x86_64_android,timportlibandroid);
|
|
RegisterExport(system_x86_64_android,texportlibandroid);
|
|
RegisterTarget(system_x86_64_android_info);
|
|
{$endif X86_64}
|
|
{$ifdef MIPSEL}
|
|
RegisterImport(system_mipsel_android,timportlibandroid);
|
|
RegisterExport(system_mipsel_android,texportlibandroid);
|
|
RegisterTarget(system_mipsel_android_info);
|
|
{$endif MIPSEL}
|
|
RegisterRes(res_elf_info,TWinLikeResourceFile);
|
|
end.
|