* Darwin support for printing line info for backtraces when using Dwarf,

based on patches by Colin Western, mantis #38483)
   o requires that the program/library is compiled with -Xg (or that
     dsymutil is run on it after compiling), and that the .dSYM bundle
     is in the same directory as the program/library
   o always use the "dl" unit in exeinfo for Darwin, as that's needed for
     dynamic library support, and this does not cause an extra dependency
     since on Darwin we always use libc
   o cleaned up the exeinfo unit for Darwin, and sped it up by using mmap
     instead of small reads
   o fixed unit dependencies for exeinfo, lineinfo and lnfodwarf in Darwin
     RTL Makefile
  * use the process address info from the original exe even when reading
    the debug information from an external file
  - removed outdated ifdef'd darwin code from dl.pp (no longer needed now
    that processaddress gets set correctly in exeinfo for that platform)

git-svn-id: trunk@49140 -
This commit is contained in:
Jonas Maebe 2021-04-08 19:50:34 +00:00
parent 4166e8c464
commit 0eb9dd3879
4 changed files with 373 additions and 69 deletions

View File

@ -268,10 +268,13 @@ getopts$(PPUEXT) : $(INC)/getopts.pp $(SYSTEMUNIT)$(PPUEXT)
heaptrc$(PPUEXT) : $(INC)/heaptrc.pp $(SYSTEMUNIT)$(PPUEXT) heaptrc$(PPUEXT) : $(INC)/heaptrc.pp $(SYSTEMUNIT)$(PPUEXT)
$(COMPILER) -Sg $(INC)/heaptrc.pp $(COMPILER) -Sg $(INC)/heaptrc.pp
lineinfo$(PPUEXT) : $(INC)/lineinfo.pp $(SYSTEMUNIT)$(PPUEXT) strings$(PPUEXT) $(INC)/exeinfo.pp sysutils$(PPUEXT) exeinfo$(PPUEXT) : $(INC)/exeinfo.pp $(SYSTEMUNIT)$(PPUEXT) strings$(PPUEXT) ctypes$(PPUEXT) dl$(PPUEXT) baseunix$(PPUEXT)
$(COMPILER) $< $(COMPILER) $<
lnfodwrf$(PPUEXT) : $(INC)/lnfodwrf.pp $(SYSTEMUNIT)$(PPUEXT) strings$(PPUEXT) $(INC)/exeinfo.pp lineinfo$(PPUEXT) sysutils$(PPUEXT) lineinfo$(PPUEXT) : $(INC)/lineinfo.pp $(SYSTEMUNIT)$(PPUEXT) strings$(PPUEXT) exeinfo$(PPUEXT) sysutils$(PPUEXT)
$(COMPILER) $<
lnfodwrf$(PPUEXT) : $(INC)/lnfodwrf.pp $(SYSTEMUNIT)$(PPUEXT) strings$(PPUEXT) exeinfo$(PPUEXT) lineinfo$(PPUEXT) sysutils$(PPUEXT) baseunix$(PPUEXT) ctypes$(PPUEXT)
$(COMPILER) $< $(COMPILER) $<
lnfogdb$(PPUEXT) : $(UNIXINC)/lnfogdb.pp $(SYSTEMUNIT)$(PPUEXT) ctypes$(PPUEXT) baseunix$(PPUEXT) unix$(PPUEXT) lnfogdb$(PPUEXT) : $(UNIXINC)/lnfogdb.pp $(SYSTEMUNIT)$(PPUEXT) ctypes$(PPUEXT) baseunix$(PPUEXT) unix$(PPUEXT)

View File

@ -22,13 +22,15 @@
might be seen as invalid by heaptrc unit CheckPointer function } might be seen as invalid by heaptrc unit CheckPointer function }
{$checkpointer off} {$checkpointer off}
{$modeswitch out}
unit exeinfo; unit exeinfo;
interface interface
{$S-} {$S-}
type type
TExeProcessAddress = {$ifdef cpui8086}word{$else}ptruint{$endif};
TExeOffset = {$ifdef cpui8086}longword{$else}ptruint{$endif};
TExeFile=record TExeFile=record
f : file; f : file;
// cached filesize // cached filesize
@ -36,14 +38,18 @@ type
isopen : boolean; isopen : boolean;
nsects : longint; nsects : longint;
sechdrofs, sechdrofs,
secstrofs : {$ifdef cpui8086}longword{$else}ptruint{$endif}; secstrofs : TExeOffset;
processaddress : {$ifdef cpui8086}word{$else}ptruint{$endif}; processaddress : TExeProcessAddress;
{$ifdef cpui8086} {$ifdef cpui8086}
processsegment : word; processsegment : word;
{$endif cpui8086} {$endif cpui8086}
{$ifdef darwin}
{ total size of all headers }
loadcommandssize: ptruint;
{$endif}
FunctionRelative: boolean; FunctionRelative: boolean;
// Offset of the binary image forming permanent offset to all retrieved values // Offset of the binary image forming permanent offset to all retrieved values
ImgOffset: {$ifdef cpui8086}longword{$else}ptruint{$endif}; ImgOffset: TExeOffset;
filename : string; filename : string;
// Allocate static buffer for reading data // Allocate static buffer for reading data
buf : array[0..4095] of byte; buf : array[0..4095] of byte;
@ -65,6 +71,9 @@ procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: st
implementation implementation
uses uses
{$ifdef darwin}
ctypes, baseunix, dl,
{$endif}
strings{$ifdef windows},windows{$endif windows}; strings{$ifdef windows},windows{$endif windows};
{$if defined(unix) and not defined(beos) and not defined(haiku)} {$if defined(unix) and not defined(beos) and not defined(haiku)}
@ -1098,89 +1107,316 @@ end;
****************************************************************************} ****************************************************************************}
{$ifdef darwin} {$ifdef darwin}
{$push}
{$packrecords c}
type type
MachoFatHeader= packed record tmach_integer = cint;
magic: longint; tmach_cpu_type = tmach_integer;
nfatarch: longint; tmach_cpu_subtype = tmach_integer;
tmach_cpu_threadtype = tmach_integer;
tmach_fat_header=record
magic: cuint32;
nfatarch: cuint32;
end; end;
MachoHeader=packed record
magic: longword; tmach_fat_arch=record
cpu_type_t: longint; cputype: tmach_cpu_type;
cpu_subtype_t: longint; cpusubtype: tmach_cpu_subtype;
filetype: longint; offset: cuint32;
ncmds: longint; size: cuint32;
sizeofcmds: longint; align: cuint32;
flags: longint;
end; end;
cmdblock=packed record pmach_fat_arch = ^tmach_fat_arch;
cmd: longint;
cmdsize: longint; (* not yet supported (only needed for slices or combined slice size > 4GB; unrelated to 64 bit processes)
tmach_fat_arch_64=record
cputype: tmach_cpu_type;
cpusubtype: tmach_cpu_subtype;
offset: cuint64;
size: cuint64;
align: cuint32;
reserved: cuint32;
end; end;
symbSeg=packed record *)
symoff : longint;
nsyms : longint; { note: always big endian }
stroff : longint; tmach_header=record
strsize: longint; magic: cuint32;
cputype: tmach_cpu_type;
cpusubtype: tmach_cpu_subtype;
filetype: cuint32;
ncmds: cuint32;
sizeofcmds: cuint32;
flags: cuint32;
{$IFDEF CPU64}
reserved: cuint32;
{$ENDIF}
end; end;
tstab=packed record pmach_header = ^tmach_header;
strpos : longint;
tmach_load_command=record
cmd: cuint32;
cmdsize: cuint32;
end;
pmach_load_command=^tmach_load_command;
tmach_symtab_command=record
cmd : cuint32;
cmdsize: cuint32;
symoff : cuint32;
nsyms : cuint32;
stroff : cuint32;
strsize: cuint32;
end;
pmach_symtab_command = ^tmach_symtab_command;
tstab=record
strpos : longword;
ntype : byte; ntype : byte;
nother : byte; nother : byte;
ndesc : word; ndesc : word;
nvalue : dword; nvalue : longword;
end; end;
pstab = ^tstab;
tmach_vm_prot = cint;
function OpenMachO32PPC(var e:TExeFile):boolean; tmach_segment_command = record
cmd : cuint32;
cmdsize : cuint32;
segname : array [0..15] of Char;
vmaddr : {$IFDEF CPU64}cuint64{$ELSE}cuint32{$ENDIF};
vmsize : {$IFDEF CPU64}cuint64{$ELSE}cuint32{$ENDIF};
fileoff : {$IFDEF CPU64}cuint64{$ELSE}cuint32{$ENDIF};
filesize: {$IFDEF CPU64}cuint64{$ELSE}cuint32{$ENDIF};
maxprot : tmach_vm_prot;
initptot: tmach_vm_prot;
nsects : cuint32;
flags : cuint32;
end;
pmach_segment_command = ^tmach_segment_command;
tmach_uuid_command = record
cmd : cuint32;
cmdsize : cuint32;
uuid : array[0..15] of cuint8;
end;
pmach_uuid_command = ^tmach_uuid_command;
tmach_section = record
sectname : array [0..15] of Char;
segname : array [0..15] of Char;
addr : {$IFDEF CPU64}cuint64{$ELSE}cuint32{$ENDIF};
size : {$IFDEF CPU64}cuint64{$ELSE}cuint32{$ENDIF};
offset : cuint32;
align : cuint32;
reloff : cuint32;
nreloc : cuint32;
flags : cuint32;
reserved1: cuint32;
reserved2: cuint32;
{$IFDEF CPU64}
reserved3: cuint32;
{$ENDIF}
end;
pmach_section = ^tmach_section;
tmach_fat_archs = array[1..high(longint) div sizeof(tmach_header)] of tmach_fat_arch;
tmach_fat_header_archs = record
header: tmach_fat_header;
archs: tmach_fat_archs;
end;
pmach_fat_header_archs = ^tmach_fat_header_archs;
{$pop}
const
MACH_MH_EXECUTE = $02;
MACH_FAT_MAGIC = $cafebabe;
// not yet supported: only for binaries with slices > 4GB, or total size > 4GB
// MACH_FAT_MAGIC_64 = $cafebabf;
{$ifdef cpu32}
MACH_MAGIC = $feedface;
{$else}
MACH_MAGIC = $feedfacf;
{$endif}
MACH_CPU_ARCH_MASK = cuint32($ff000000);
{$ifdef cpu32}
MACH_LC_SEGMENT = $01;
{$else}
MACH_LC_SEGMENT = $19;
{$endif}
MACH_LC_SYMTAB = $02;
MACH_LC_UUID = $1b;
{ the in-memory mapping of the mach header of the main binary }
function _NSGetMachExecuteHeader: pmach_header; cdecl; external 'c';
function getpagesize: cint; cdecl; external 'c';
function MapMachO(const h: THandle; offset, len: SizeUInt; out addr: pointer; out memoffset, mappedsize: SizeUInt): boolean;
var var
mh:MachoHeader; pagesize: cint;
begin begin
OpenMachO32PPC:= false; pagesize:=getpagesize;
addr:=fpmmap(nil, len+(offset and (pagesize-1)), PROT_READ, MAP_PRIVATE, h, offset and not(pagesize-1));
if addr=MAP_FAILED then
begin
addr:=nil;
memoffset:=0;
mappedsize:=0;
end
else
begin
memoffset:=offset and (pagesize - 1);
mappedsize:=len+(offset and (pagesize-1));
end;
end;
procedure UnmapMachO(p: pointer; size: SizeUInt);
begin
fpmunmap(p,size);
end;
function OpenMachO(var e:TExeFile):boolean;
var
mh : tmach_header;
processmh : pmach_header;
cmd: pmach_load_command;
segmentcmd: pmach_segment_command;
mappedexe: pointer;
mappedoffset, mappedsize: SizeUInt;
i: cuint32;
foundpagezero: boolean;
begin
OpenMachO:=false;
E.FunctionRelative:=false; E.FunctionRelative:=false;
if e.size<sizeof(mh) then if e.size<sizeof(mh) then
exit; exit;
blockread (e.f, mh, sizeof(mh)); blockread (e.f, mh, sizeof(mh));
case mh.magic of
MACH_FAT_MAGIC:
begin
{ todo }
exit
end;
MACH_MAGIC:
begin
// check that at least the architecture matches (we should also check the subarch,
// but that's harder because of architecture-specific backward compatibility rules)
processmh:=_NSGetMachExecuteHeader;
if (mh.cputype and not(MACH_CPU_ARCH_MASK)) <> (processmh^.cputype and not(MACH_CPU_ARCH_MASK)) then
exit;
end;
else
exit;
end;
e.sechdrofs:=filepos(e.f); e.sechdrofs:=filepos(e.f);
e.nsects:=mh.ncmds; e.nsects:=mh.ncmds;
OpenMachO32PPC:=true; e.loadcommandssize:=mh.sizeofcmds;
if mh.filetype = MACH_MH_EXECUTE then
begin
foundpagezero:= false;
{ make sure to unmap again on all exit paths }
if not MapMachO(filerec(e.f).handle, e.sechdrofs, e.loadcommandssize, mappedexe, mappedoffset, mappedsize) then
exit;
cmd:=pmach_load_command(mappedexe+mappedoffset);
for i:= 1 to e.nsects do
begin
case cmd^.cmd of
MACH_LC_SEGMENT:
begin
segmentcmd:=pmach_segment_command(cmd);
if segmentcmd^.segname='__PAGEZERO' then
begin
e.processaddress:=segmentcmd^.vmaddr+segmentcmd^.vmsize;
OpenMachO:=true;
break;
end;
end;
end;
cmd:=pmach_load_command(pointer(cmd)+cmd^.cmdsize);
end;
UnmapMachO(mappedexe, mappedsize);
end
else
OpenMachO:=true;
end; end;
function FindSectionMachO32PPC(var e:TExeFile;const asecname:string;var secofs,seclen:longint):boolean; function FindSectionMachO(var e:TExeFile;const asecname:string;var secofs,seclen:longint):boolean;
var var
i: longint; i, j: cuint32;
block:cmdblock; cmd: pmach_load_command;
symbolsSeg: symbSeg; symtabcmd: pmach_symtab_command;
segmentcmd: pmach_segment_command;
section: pmach_section;
mappedexe: pointer;
mappedoffset, mappedsize: SizeUInt;
dwarfsecname: string;
begin begin
FindSectionMachO32PPC:=false; FindSectionMachO:=false;
seek(e.f,e.sechdrofs); { make sure to unmap again on all exit paths }
if not MapMachO(filerec(e.f).handle, e.sechdrofs, e.loadcommandssize, mappedexe, mappedoffset, mappedsize) then
exit;
cmd:=pmach_load_command(mappedexe+mappedoffset);
for i:= 1 to e.nsects do for i:= 1 to e.nsects do
begin begin
{$I-} case cmd^.cmd of
blockread (e.f, block, sizeof(block)); MACH_LC_SEGMENT:
{$I+} begin
if IOResult <> 0 then segmentcmd:=pmach_segment_command(cmd);
Exit; if segmentcmd^.segname='__DWARF' then
if block.cmd = $2 then begin
begin if asecname[1]='.' then
blockread (e.f, symbolsSeg, sizeof(symbolsSeg)); dwarfsecname:='__'+copy(asecname,2,length(asecname))
if asecname='.stab' then else
begin dwarfsecname:=asecname;
secofs:=symbolsSeg.symoff; section:=pmach_section(pointer(segmentcmd)+sizeof(segmentcmd^));
{ the caller will divide again by sizeof(tstab) } for j:=1 to segmentcmd^.nsects do
seclen:=symbolsSeg.nsyms*sizeof(tstab); begin
FindSectionMachO32PPC:=true; if section^.sectname = dwarfsecname then
end begin
else if asecname='.stabstr' then secofs:=section^.offset;
begin seclen:=section^.size;
secofs:=symbolsSeg.stroff; FindSectionMachO:=true;
seclen:=symbolsSeg.strsize; UnmapMachO(mappedexe, mappedsize);
FindSectionMachO32PPC:=true; exit;
end; end;
exit; inc(section);
end;
end;
end;
MACH_LC_SYMTAB:
begin
symtabcmd:=pmach_symtab_command(cmd);
if asecname='.stab' then
begin
secofs:=symtabcmd^.symoff;
{ the caller will divide again by sizeof(tstab) }
seclen:=symtabcmd^.nsyms*sizeof(tstab);
FindSectionMachO:=true;
end
else if asecname='.stabstr' then
begin
secofs:=symtabcmd^.stroff;
seclen:=symtabcmd^.strsize;
FindSectionMachO:=true;
end;
if FindSectionMachO then
begin
UnmapMachO(mappedexe, mappedsize);
exit;
end;
end;
end; end;
Seek(e.f, FilePos (e.f) + block.cmdsize - sizeof(block)); cmd:=pmach_load_command(pointer(cmd)+cmd^.cmdsize);
end; end;
UnmapMachO(mappedexe, mappedsize);
end; end;
{$endif darwin} {$endif darwin}
@ -1260,8 +1496,8 @@ const
findproc : @FindSectionElf; findproc : @FindSectionElf;
{$endif ELF32 or ELF64} {$endif ELF32 or ELF64}
{$ifdef darwin} {$ifdef darwin}
openproc : @OpenMachO32PPC; openproc : @OpenMachO;
findproc : @FindSectionMachO32PPC; findproc : @FindSectionMachO;
{$endif darwin} {$endif darwin}
{$IFDEF EMX} {$IFDEF EMX}
openproc : @OpenEMXaout; openproc : @OpenEMXaout;
@ -1351,7 +1587,7 @@ begin
CheckDbgFile:=(dbgcrc=c); CheckDbgFile:=(dbgcrc=c);
end; end;
{$ifndef darwin}
function ReadDebugLink(var e:TExeFile;var dbgfn:string):boolean; function ReadDebugLink(var e:TExeFile;var dbgfn:string):boolean;
var var
dbglink : array[0..255] of char; dbglink : array[0..255] of char;
@ -1395,6 +1631,71 @@ begin
end; end;
end; end;
end; end;
{$else}
function ReadDebugLink(var e:TExeFile;var dbgfn:string):boolean;
var
dsymexefile: TExeFile;
execmd, dsymcmd: pmach_load_command;
exeuuidcmd, dsymuuidcmd: pmach_uuid_command;
mappedexe, mappeddsym: pointer;
mappedexeoffset, mappedexesize, mappeddsymoffset, mappeddsymsize: SizeUInt;
i, j: cuint32;
filenamestartpos, b: byte;
begin
ReadDebugLink:=false;
if not MapMachO(filerec(e.f).handle, e.sechdrofs, e.loadcommandssize, mappedexe, mappedexeoffset, mappedexesize) then
exit;
execmd:=pmach_load_command(mappedexe+mappedexeoffset);
for i:=1 to e.nsects do
begin
case execmd^.cmd of
MACH_LC_UUID:
begin
exeuuidcmd:=pmach_uuid_command(execmd);
filenamestartpos:=1;
for b:=1 to length(e.filename) do
begin
if e.filename[b] = '/' then
filenamestartpos:=b+1;
end;
if not OpenExeFile(dsymexefile,e.filename+'.dSYM/Contents/Resources/DWARF/'+copy(e.filename,filenamestartpos,length(e.filename))) then
begin
UnmapMachO(mappedexe, mappedexesize);
exit;
end;
if not MapMachO(filerec(dsymexefile.f).handle, dsymexefile.sechdrofs, dsymexefile.loadcommandssize, mappeddsym, mappeddsymoffset, mappeddsymsize) then
begin
CloseExeFile(dsymexefile);
UnmapMachO(mappedexe, mappedexesize);
exit;
end;
dsymcmd:=pmach_load_command(mappeddsym+mappeddsymoffset);
for j:=1 to dsymexefile.nsects do
begin
case dsymcmd^.cmd of
MACH_LC_UUID:
begin
dsymuuidcmd:=pmach_uuid_command(dsymcmd);
if comparebyte(exeuuidcmd^.uuid, dsymuuidcmd^.uuid, sizeof(exeuuidcmd^.uuid)) = 0 then
begin
dbgfn:=dsymexefile.filename;
ReadDebugLink:=true;
end;
break;
end;
end;
end;
UnmapMachO(mappeddsym, mappeddsymsize);
CloseExeFile(dsymexefile);
UnmapMachO(mappedexe, mappedexesize);
exit;
end;
end;
execmd:=pmach_load_command(pointer(execmd)+execmd^.cmdsize);
end;
UnmapMachO(mappedexe, mappedexesize);
end;
{$endif}
begin begin

View File

@ -267,6 +267,8 @@ type
{$endif cpui8086} {$endif cpui8086}
function OpenDwarf(addr : codepointer) : boolean; function OpenDwarf(addr : codepointer) : boolean;
var
oldprocessaddress: TExeProcessAddress;
begin begin
// False by default // False by default
OpenDwarf:=false; OpenDwarf:=false;
@ -308,9 +310,11 @@ begin
exit; exit;
if ReadDebugLink(e,dbgfn) then if ReadDebugLink(e,dbgfn) then
begin begin
oldprocessaddress:=e.processaddress;
CloseExeFile(e); CloseExeFile(e);
if not OpenExeFile(e,dbgfn) then if not OpenExeFile(e,dbgfn) then
exit; exit;
e.processaddress:=oldprocessaddress;
end; end;
// Find debug data section // Find debug data section

View File

@ -136,7 +136,7 @@ uses
begin begin
SimpleExtractFilename:=Copy(s,PosLastSlash(s)+1,Length(s)-PosLastSlash(s)); SimpleExtractFilename:=Copy(s,PosLastSlash(s)+1,Length(s)-PosLastSlash(s));
end; end;
procedure UnixGetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: openstring); procedure UnixGetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: openstring);
var var
@ -147,10 +147,6 @@ uses
dladdr(addr, @dlinfo); dladdr(addr, @dlinfo);
baseaddr:=dlinfo.dli_fbase; baseaddr:=dlinfo.dli_fbase;
filename:=String(dlinfo.dli_fname); filename:=String(dlinfo.dli_fname);
{$ifdef darwin}
if SimpleExtractFilename(filename)=SimpleExtractFilename(ParamStr(0)) then
baseaddr:=nil;
{$endif darwin}
end; end;
{$ifdef aix} {$ifdef aix}