Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Prebuilt/Android/backtrace.o
Binary file not shown.
2 changes: 1 addition & 1 deletion Proj/DelphiXE5/PrintLines.bat
Original file line number Diff line number Diff line change
Expand Up @@ -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
38 changes: 16 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,38 @@
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
-----
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
----
Expand Down
235 changes: 158 additions & 77 deletions Source/Posix.Backtrace.pas
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading