diff --git a/Prebuilt/Android/backtrace.o b/Prebuilt/Android/backtrace.o new file mode 100644 index 0000000..13f365a Binary files /dev/null and b/Prebuilt/Android/backtrace.o differ diff --git a/Proj/DelphiXE5/PrintLines.bat b/Proj/DelphiXE5/PrintLines.bat index 232d032..17d89bd 100644 --- a/Proj/DelphiXE5/PrintLines.bat +++ b/Proj/DelphiXE5/PrintLines.bat @@ -21,4 +21,4 @@ call ..\..\Source\compile.bat ::Then use these symbols as a map, we could also load the so itself but this ::better demonstrates how to preserve symbols only - for release purposes -%TOOLS_PREFIX%addr2line -f -p +%TOOLS_PREFIX%addr2line -f -s -C -p diff --git a/README.md b/README.md index a60d6c6..eeda543 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Delphi ARM Backtrace library ============================ This is a simple library that allows you to create a stack trace and transform -it into format that can be fed to addr2line to convert it to function/line no. +it into format that can be fed to `addr2line` to convert it to function/line no. information. Usage @@ -9,36 +9,30 @@ Usage Add the functions to your project and add compile.bat from sources to your pre-build events. (Make sure compile.bat can find your NDK.) -Create a back trace by one of the functions from Posix.Backtrace and translate -it to symbol addresses using TPosixProcEntryList. +Create a back trace by one of the functions from `Posix.Backtrace` and translate +it to symbol addresses using `TPosixProcEntryList`. -Feed the generated lines to addr2line to get symbolic function names and line -information. (See PrintLines.bat how to do that.) +Feed the generated lines to `addr2line` to get symbolic function names and line +information. (See `PrintLines.bat` how to do that.) The result will look like this: - 0x00AC5AC6 (0x75A48AC6) /data/app-lib/com.embarcadero.BacktraceTestProj-1/libBacktraceTestProj.so - _ZN13Backtracetest9SomeFunc2Ei at D:\Documents\RAD Studio\Projects\ArmBacktrace\Tests/BacktraceTest.pas:57 - 0x00AC5E90 (0x75A48E90) /data/app-lib/com.embarcadero.BacktraceTestProj-1/libBacktraceTestProj.so - _ZN13Backtracetest9SomeFunc1Ei at D:\Documents\RAD Studio\Projects\ArmBacktrace\Tests/BacktraceTest.pas:92 - 0x00AC5EA8 (0x75A48EA8) /data/app-lib/com.embarcadero.BacktraceTestProj-1/libBacktraceTestProj.so - _ZN13Backtracetest4TObj4TestEv at D:\Documents\RAD Studio\Projects\ArmBacktrace\Tests/BacktraceTest.pas:118 - 0x00AC5EC0 (0x75A48EC0) /data/app-lib/com.embarcadero.BacktraceTestProj-1/libBacktraceTestProj.so - _ZN13Backtracetest5TObj24TestEv at D:\Documents\RAD Studio\Projects\ArmBacktrace\Tests/BacktraceTest.pas:111 - 0x00AC5A60 (0x75A48A60) /data/app-lib/com.embarcadero.BacktraceTestProj-1/libBacktraceTestProj.so - _ZN13Backtracetest9TTestForm12cmdTestClickEPN6System7TObjectE at D:\Documents\RAD Studio\Projects\ArmBacktrace\Tests/BacktraceTest.pas:125 - 0x009EF360 (0x75972360) /data/app-lib/com.embarcadero.BacktraceTestProj-1/libBacktraceTestProj.so - _ZN3Fmx8Controls8TControl5ClickEv at C:\Builds\TP\runtime\fmx/FMX.Controls.pas:3455 - 0x000022E9 (0x400B52E9) {Not executable} /system/lib/libc.so - ?? - ??:0 +``` +0x001FC9BE (0xC972A9BE) /data/app/com.embarcadero.TestProject-2/lib/arm/libTestProject.so +System::_GetMem(NativeInt) at System.pas:4588 +0x001F60F8 (0xC97240F8) /data/app/com.embarcadero.TestProject-2/lib/arm/libTestProject.so +System::TObject::NewInstance() at System.pas:16452 +0x001FDAE0 (0xC972BAE0) /data/app/com.embarcadero.TestProject-2/lib/arm/libTestProject.so +System::_ClassCreate(void*, signed char) at System.pas:17777 +0x001F61DA (0xC97241DA) /data/app/com.embarcadero.TestProject-2/lib/arm/libTestProject.so +System::TObject::TObject() at System.pas:16516 +``` Known issues ------------ * There are no unit tests right now as this project is currently in proof of concept state. - * Only tested on Android (Nexus 7, let me know if it works on your device), - no iOS support right now (patches welcome ;-) ). + * Tested on Android, MacOS, Linux no iOS support right now (patches welcome ;-) ). TODO ---- diff --git a/Source/Posix.Backtrace.pas b/Source/Posix.Backtrace.pas index dac8166..83ad531 100644 --- a/Source/Posix.Backtrace.pas +++ b/Source/Posix.Backtrace.pas @@ -17,110 +17,191 @@ interface -{$IF Defined(ANDROID) OR Defined(IOS)} - {$DEFINE OWN_BACKTRACE} +{$LEGACYIFEND ON} + +{$IF (Defined(CPUX86) OR Defined(CPUX64)) AND Defined(POSIX)} + {$DEFINE INTELABI} // x32 ABI or System V (AMD64) ABI +{$IFEND} + +type + TBacktraceMode = (bmARM, bmIntelABI, bmLibc); + +const + BacktraceMode = +{$IF Defined(CPUARM) AND (Defined(ANDROID) OR Defined(IOS))} + bmARM {$ELSEIF Defined(MACOS)} - {$DEFINE LIBC_BACKTRACE} - {$IFDEF CPUX86} - {$DEFINE EXC_BACKTRACE} - {$ENDIF} + bmLibc + {$IFDEF CPUX86} + {$DEFINE EXC_BACKTRACE} + {$ENDIF} +{$ELSEIF Defined(POSIX) AND Defined(INTELABI)} + // Libc - Causes segfault + bmIntelABI {$ELSE} - {$MESSAGE FATAL 'Unsupported OS'} + {$MESSAGE FATAL 'Unsupported OS'} {$IFEND} + ; + BacktraceSupportsIgnore = BacktraceMode in [bmARM, bmIntelABI]; -function StackWalk(Data : PPointer; Count : Integer) : Integer; inline; +function StackWalk(Data: PPointer; Size, IgnoredFrames: Integer): Integer; inline; // execinfo.h shadow procedure -function backtrace(buffer : PPointer; size : Integer) : Integer; - {$IFDEF LIBC_BACKTRACE}cdecl;{$ENDIF} +function backtrace(buffer: PPointer; size: Integer + {$IF BacktraceSupportsIgnore}; ignored: Integer = 0{$IFEND}): Integer; + {$IF BacktraceMode = bmLibc}cdecl;{$IFEND} {$IFDEF EXC_BACKTRACE} -//Exception stack frame gets corrupted while handling exceptions and own -//backtrace have to be used -function backtrace2(base : NativeUInt; buffer : PPointer; size : Integer) : Integer; +// Exception stack frame gets corrupted while handling exceptions and own +// backtrace have to be used +function backtrace2(base: NativeUInt; buffer: PPointer; size: Integer; + ignored: Integer = 0): Integer; {$ENDIF} -{$IFDEF LIBC_BACKTRACE} -function backtrace_symbols(buffer : PPointer; size : Integer) : PPointer{PPAnsiChar}; cdecl; -procedure backtrace_symbols_free(ptr : Pointer); cdecl; -{$ENDIF} +{$IF BacktraceMode = bmLibc} +function backtrace_symbols(buffer: PPointer; size: Integer): PPointer{PPAnsiChar}; cdecl; +procedure backtrace_symbols_free(ptr: Pointer); cdecl; +{$IFEND} implementation -{$IFDEF LIBC_BACKTRACE} -uses Posix.Base; -{$ENDIF} +{$IF BacktraceMode = bmLibc} +uses + Posix.Base; +{$IFEND} + +{$IFDEF INTELABI} +function ABIX86_64Backtrace(base: NativeUInt; buffer: PPointer; + size: Integer; ignored: Integer): Integer; +const + STACK_MAX_SIZE = 2 * 1024 * 1024; +var + SPMin: NativeUInt; +begin + SPMin := base; + Result := 0; + while (size > 0) and (base >= SPMin) and (base <> 0) do + begin + // We can rewrite the buffer as long as we don't increment Result during + // ignoring. + buffer^ := PPointer(base + SizeOf(Pointer))^; + base := PNativeInt(base)^; + if ignored > 0 then + begin + Dec(ignored); + Continue; + end; + + Inc(Result); + + Inc(buffer); + Dec(size); + end; + if (size > 0) then + buffer^ := nil; +end; -{$IFDEF OWN_BACKTRACE} +{$IFDEF CPUX86} +function GetESP: Pointer; +asm + mov eax, ebp +end; +{$ENDIF CPUX64} + +{$IFDEF CPUX64} +function GetRSP: Pointer; +begin + // Grab RBP of this frame. + Result := PByte(@Result) + 8; + // Return RBP of previous frame. + Result := PPointer(Result)^; +end; +{$ENDIF CPUX64} +{$ENDIF INTELABI} + +{$IF BacktraceMode = bmARM} const BacktraceLibName = 'backtrace.o'; -function get_frame : NativeUInt; cdecl; external BacktraceLibName; +function get_frame: NativeUInt; cdecl; external BacktraceLibName; {$WARN SYMBOL_PLATFORM OFF} -{$LINK LibName} +{$LINK BacktraceLibName} {$WARN SYMBOL_PLATFORM ON} -{$ENDIF OWN_BACKTRACE} -function StackWalk(Data : PPointer; Count : Integer) : Integer; inline; +function backtrace(buffer: PPointer; size: Integer; ignored: Integer): Integer; +const + MEM_MASK = $FFF00000; + STACK_MAX_SIZE = 2 * 1024 * 1024; // Default UNIX stack size +var + FP: NativeUInt; + LR: Pointer; + SPMax: NativeUInt; + SPMin: NativeUInt; begin - Result:=backtrace(Data, Count); + // Push instruction decrements SP, we're walking stack up + FP := get_frame; + SPMin := FP; + SPMax := SPMin + STACK_MAX_SIZE; + Result := 0; + // FP = nil should indicate parent most Stack Frame + while (size > 0) and (FP <= SPMax) and (FP >= SPMin) and (FP <> 0{nil}) do + begin + // This is how Delphi compiler uses stack, but depends on ABI. + // Delphi probably uses R7 as Frame pointer since it is the least register + // accessible by THUMB (16-bit) instructions in comparison to ARM (32-bit) + // instructions see backtrace.c. + LR := PPointer(FP + 4)^; + FP := PNativeUInt(FP)^; + if ignored > 0 then + begin + Dec(ignored); + Continue; + end; + + // LR is set to PC + 3 (branch instruction size is 2 and is adjusted for + // prefetch). + NativeUInt(buffer^) := NativeUInt(LR) - 3; + Inc(Result); + + Inc(buffer); + Dec(size); + end; + if (size > 0) then + buffer^ := nil; end; +{$IFEND ARM} -{$IFDEF OWN_BACKTRACE} -function backtrace(buffer : PPointer; size : Integer) : Integer; -const - MEM_MASK = $FFF00000; - STACK_MAX_SIZE = 2 * 1024 * 1024; //Default UNIX stack size -var FPp : Pointer; - FP : NativeUInt absolute FPp; - LR : Pointer; - SPMax : NativeUInt; - SPMin : NativeUInt; +{$IF BacktraceMode = bmIntelABI} +function backtrace(buffer: PPointer; size: Integer; ignored: Integer): Integer; begin - //Push instruction decrements SP, we're walking stack up - FP:=get_frame; - SPMin:=FP; - SPMax:=SPMin + STACK_MAX_SIZE; - Result:=0; - //FP = nil should indicate parent most Stack Frame - while (size > 0) and (FP <= SPMax) and (FP >= SPMin) and (FP <> 0{nil}) do begin - //This is how Delphi compiler uses stack, but depends on ABI - //Delphi probably uses R7 as Frame pointer since it is the least register - //accessible by THUMB (16-bit) instructions in comparison to ARM (32-bit) - //instructions see backtrace.c - LR:=PPointer(FP + 4)^; - FP:=PNativeUInt(FP)^; - - NativeUInt(buffer^):=NativeUInt(LR) - 3; //LR is set to PC + 3 (branch instruction size is 2 and is adjusted for prefetch) - Inc(Result); - - Inc(buffer); - Dec(size); - end; - if (size > 0) then buffer^:=nil; + Result := ABIX86_64Backtrace(NativeUInt(GetRSP), buffer, size, ignored); end; -{$ENDIF OWN_BACKTRACE} +{$IFEND INTELABI} -{$IFDEF LIBC_BACKTRACE} -function backtrace; external libc name '_backtrace'; -function backtrace_symbols; external libc name '_backtrace_symbols'; -procedure backtrace_symbols_free; external libc name '_free'; -{$ENDIF} +{$IF BacktraceMode = bmLibc} +function backtrace; external libc name _PU + 'backtrace'; +function backtrace_symbols; external libc name _PU + 'backtrace_symbols'; +procedure backtrace_symbols_free; external libc name _PU + 'free'; +{$IFEND} {$IFDEF EXC_BACKTRACE} -function backtrace2(base : NativeUInt; buffer : PPointer; size : Integer) : Integer; -const STACK_MAX_SIZE = 2 * 1024 * 1024; -var SPMin : NativeUInt; +function backtrace2(base: NativeUInt; buffer: PPointer; size, ignored: Integer): Integer; begin - SPMin:=base; - Result:=0; - while (size > 0) and (base >= SPMin) and (base <> 0) do begin - buffer^:=PPointer(base + 4)^; - base:=PNativeInt(base)^; - Inc(Result); - - Inc(buffer); - Dec(size); - end; - if (size > 0) then buffer^:=nil; + Result := ABIX86_64Backtrace(base, buffer, size, ignored); end; {$ENDIF} +function StackWalk(Data: PPointer; Size, IgnoredFrames: Integer): Integer; inline; +begin +{$IF BacktraceSupportsIgnore} + Result := backtrace(Data, Size, IgnoredFrames); +{$ELSE} + Result := backtrace(Data, Size); + if IgnoredFrames > 0 then + begin + if Result <= IgnoredFrames then + Exit(0); + Move(PPointer(NativeInt(Data) + (IgnoredFrames * SizeOf(Pointer)))^, Data^, + (Result - IgnoredFrames) * SizeOf(Pointer)); + Dec(Result, IgnoredFrames); + end; +{$IFEND} +end; end. diff --git a/Source/Posix.ExceptionUtil.pas b/Source/Posix.ExceptionUtil.pas index 900a62d..e12e0f9 100644 --- a/Source/Posix.ExceptionUtil.pas +++ b/Source/Posix.ExceptionUtil.pas @@ -29,7 +29,7 @@ TExceptionStackInfo = record STACK_DEPTH = 32; STACK_ALL = STACK_SKIP + STACK_DEPTH; private class var -{$IF Defined(ANDROID) OR Defined(IOS)} +{$IF Defined(ANDROID) OR Defined(IOS) OR Defined(Linux64)} FProcEntries : TPosixProcEntryList; {$ENDIF} private @@ -49,6 +49,9 @@ implementation {$IFDEF POSIX} uses + {$IF Defined(ANDROID) OR Defined(IOS) OR Defined(Linux64)} + System.SyncObjs, + {$ENDIF} Posix.Backtrace; {$ELSE} {$MESSAGE FATAL 'Unsupported OS'} @@ -61,6 +64,10 @@ implementation //application will most likely crash anyway) HandlingException : Integer; +{$IF Defined(ANDROID) OR Defined(IOS) OR Defined(Linux64)} +var + _CS: TCriticalSection; +{$ENDIF} { TExceptionStackInfo } @@ -98,7 +105,7 @@ class function TExceptionStackInfo.GetExceptionStackInfoProc( //0x14 bytes which is compiler dependant value Info^.Count:=backtrace2(b + $14, @Info^.Stack, STACK_ALL); {$ELSE} - Info^.Count:=StackWalk(@Info^.Stack, STACK_ALL); + Info^.Count:=StackWalk(@Info^.Stack, STACK_ALL, 0); {$ENDIF} except //Shouldn't happen but still we'll rather have a small leak then stop @@ -132,17 +139,24 @@ class function TExceptionStackInfo.GetStackInfoStringProc( class function TExceptionStackInfo.GetSymbols(Stack: PPointer; Count: Integer): string; -{$IF Defined(MACOS) AND NOT Defined(IOS)} +{$IF Defined(MACOS) AND NOT Defined(IOS) OR Defined(Linux64)} var Res : PPointer; P : PPointer; i : Integer; {$ENDIF} begin -{$IF Defined(ANDROID) OR Defined(IOS)} - //TODO threadsafe +{$IF Defined(ANDROID) OR Defined(IOS) OR Defined(Linux64)} + //threadsafety if (FProcEntries = nil) then begin - FProcEntries:=TPosixProcEntryList.Create; - FProcEntries.LoadFromCurrentProcess; + _CS.Enter; + try + if (FProcEntries = nil) then begin + FProcEntries:=TPosixProcEntryList.Create; + FProcEntries.LoadFromCurrentProcess; + end; + finally + _CS.Leave; + end; end; Result:=FProcEntries.ConvertStackTrace(Stack, STACK_SKIP, Count - STACK_SKIP); @@ -164,4 +178,11 @@ class function TExceptionStackInfo.GetSymbols(Stack: PPointer; {$ENDIF} end; +{$IF Defined(ANDROID) OR Defined(IOS) OR Defined(Linux64)} +initialization + _CS := TCriticalSection.Create; +finalization + FreeAndNil(_CS); +{$ENDIF} + end. diff --git a/Source/Posix.Proc.pas b/Source/Posix.Proc.pas index c0a65b6..ca7bec2 100644 --- a/Source/Posix.Proc.pas +++ b/Source/Posix.Proc.pas @@ -220,6 +220,17 @@ function TPosixProcEntryList.GetStackLine(const Address: PosixProcInt): string; if (E = nil) then Exit('0x' + IntToHex(Address, CHARS) + ' ' + ' {Unknown address}'); + {$IFDEF Linux64} + // just provide virtual addresses not subtracted from range - that confuses addr2line + if (peExecute in E^.Perms) then + Result := + '0x' + IntToHex(Address, CHARS) + + ' ' + E^.Path + else + Result := + '0x' + IntToHex(Address, CHARS) + + ' {Not executable} ' + E^.Path; + {$ELSE} if (peExecute in E^.Perms) then Result := '0x' + IntToHex(Address - E^.RangeStart, CHARS) + ' (0x' + IntToHex(Address, CHARS)+ ')' + @@ -228,6 +239,7 @@ function TPosixProcEntryList.GetStackLine(const Address: PosixProcInt): string; Result := '0x' + IntToHex(Address - E^.RangeStart, CHARS) + ' (0x' + IntToHex(Address, CHARS)+ ')' + ' {Not executable} ' + E^.Path; + {$ENDIF} end; {$IFDEF POSIX}