Feature Request: Stack Trace Capability

General FreeBASIC programming questions.
SoruCoder
Posts: 5
Joined: Mar 20, 2020 10:31

Feature Request: Stack Trace Capability

Postby SoruCoder » Dec 30, 2021 4:20

Specifically, make new keywords that model the existing error handling functions, maybe with "s" (plural) or "St" (Stack Trace) at the end:

  • ErLs or ErLSt:

    Code: Select all

    Declare Function ErLs() As Integer Ptr '  each line in stack trace, with 0 as terminator.
  • ErFNs or ErFNSt:

    Code: Select all

    Declare Function ErFNs() As ZString Ptr Ptr ' each function name in stack trace, with 0 pointer as terminator.
  • ErMNs or ErMNSt:

    Code: Select all

    Declare Function ErMNs() As ZString Ptr Ptr ' each module name in stack trace, with 0 pointer as terminator.
adeyblue
Posts: 134
Joined: Nov 07, 2019 20:08

Re: Feature Request: Stack Trace Capability

Postby adeyblue » Jan 02, 2022 19:54

I'm not sure how much appetite there is for this to be completely built in, (to read the debug info that has line and function info you need to cart around a separate program or a dll on Windows) but I've made a start on a lib to translate stack traces to symbols.

I can get the line and file info from dwarf and stabs debug info (the two fbc can produce) on Windows, now I've got to see how fancy the combination of backtrace and dladdr is on Linux and/or backtrace_symbols since it looks like a magic wand.

Then maybe there could be a way to only get a stacktrace for Error built-in, which would be far less hassle
adeyblue
Posts: 134
Joined: Nov 07, 2019 20:08

Re: Feature Request: Stack Trace Capability

Postby adeyblue » Jan 13, 2022 21:57

As usual, I did all the work for this assuming the basic lynchpin on Windows (CaptureStackBackTrace) would just work without checking it first. And it turns out, FB must do weird things to the stack that it doesn't like. Because it doesn't

This C code compiled with Nuwen GCC 9.2 64-bit

Code: Select all

#include <stdio.h>
#include <windows.h>

int Fun2()
{
    void* array[63] = {0};
    DWORD hash = 0;
    USHORT num = CaptureStackBackTrace(0, 63, array, &hash);
    printf("Caught %hu frames from stacktrace\n", num);
    for(USHORT i = 0; i < num; ++i)
    {
        printf("%hu: %p\n", num - i, array[i]);
    }
    return num;
}

void Fun1(int val)
{
    int numFun = Fun2();
    printf("Fun2 returned %d with val %d\n", numFun, val);
}

int main()
{
    Fun1(76);
}

Produces this output, which is perfect
Caught 7 frames from stacktrace
7: 000000000040159D
6: 000000000040162D
5: 0000000000401663
4: 00000000004013B2
3: 00000000004014FB
2: 000000007719556D
1: 00000000773F372D
Fun2 returned 7 with val 76


This FB code though with the same GCC
%fbc64% -O 0 -gen gcc

Code: Select all

#include "windows.bi"

Private Function Fn2() As Long
    dim frames(0 to 60) As Any Ptr
    dim framesPtr As Any Ptr Ptr = @frames(0)
    dim hash as DWORD
    dim as Long caught = CaptureStackBackTrace(0, 61, framesPtr, @hash)
    Print Using "Caught & frames using stack capture"; caught
    For i As Long = 0 To caught - 1
        Print Using "&) &"; caught - i; Hex(frames(i))
    Next
    Return caught
End Function

Private Sub Fn1(num as ULong)
    dim as Long numFn2 = Fn2()
    Print Using "Fn2 returned & with num = &"; numFn2; num
End Sub

Fn1(87)


Only prints this
Caught 1 frames using stack capture
1) 4016D2
Fn2 returned 1 with num = 87


StackWalk64 is a little better

Code: Select all

Const as HANDLE g_hProc = Cast(HANDLE, -1)

Type ThreadParams
    dim threadId As ULong
    dim pCtx as CONTEXT ptr
End Type

Private Sub CaptureThread(p as Any Ptr)
    dim pParams as ThreadParams ptr = p
    const threadPerms as ULONG = THREAD_GET_CONTEXT Or THREAD_SUSPEND_RESUME
    dim hThread as HANDLE = OpenThread(threadPerms, FALSE, pParams->threadId)
    SuspendThread(hThread)
    GetThreadContext(hThread, pParams->pCtx)
    ResumeThread(hThread)
    CloseHandle(hThread)     
End Sub

Private Sub TryOldStackWalk()
    dim ctx as CONTEXT
    ctx.ContextFlags = CONTEXT_ALL
    dim tp as ThreadParams
    tp.threadId = GetCurrentThreadId()
    tp.pCtx = @ctx
    dim thread as Any Ptr = ThreadCreate(@CaptureThread, @tp)
    ThreadWait(thread)
    ''RtlCaptureContext(@ctx)
    Print "Stack is around " & Hex(ctx.Rbp)
    dim sf as STACKFRAME64
    sf.AddrPC.Mode = AddrModeFlat
    sf.AddrFrame.Mode = AddrModeFlat
    sf.AddrStack.Mode = AddrModeFlat
    sf.AddrPC.Offset = ctx.Rip
    sf.AddrFrame.Offset = ctx.Rbp
    sf.AddrStack.Offset = ctx.Rsp
    dim i as Long = 1
    Print "StackWalk64 produces:"
    While StackWalk64( _
        IMAGE_FILE_MACHINE_AMD64, _
        g_hProc, _
        GetCurrentThread(), _
        @sf, _
        @ctx, _
        NULL, _
        @SymFunctionTableAccess64, _
        @SymGetModuleBase64, _
        NULL _
    )
        Print Using "&) &"; i; Hex(sf.AddrPC.Offset)
        i += 1
    Wend
End Sub

Private Sub Init()
    Const symOpts As DWORD = SYMOPT_DEBUG Or SYMOPT_OMAP_FIND_NEAREST Or SYMOPT_UNDNAME Or SYMOPT_LOAD_LINES
    SymSetOptions(symOpts)
    dim ret as WINBOOL = SymInitialize(g_hProc, NULL, TRUE)
    Print "SymInitialize = " & Str(ret)
End Sub

SymInitialize = 1
Stack is around 22FDD0
StackWalk64 produces:
1) 7795FEFA
2) 22FDD0
3) 410050
4) 40456A
5) 22F868
6) 22FDD0
7) 10
8) 3700000000315F80
Press key to exit


But still, only 1 & 4 out of those are code addresses, when there should be at least 5/6
adeyblue
Posts: 134
Joined: Nov 07, 2019 20:08

Re: Feature Request: Stack Trace Capability

Postby adeyblue » Jan 13, 2022 23:11

Turns out backtrace is similarly bad for FB :-(
bob@UBUNTUVIRT:~/dumprep$ gcc-9 -O0 -g stack.c
bob@UBUNTUVIRT:~/dumprep$ ./a.out
backtrace returned 6 elems
6) 0x5587121721df
5) 0x558712172283
4) 0x5587121722a6
3) 0x5587121722c5
2) 0x7f06e74010b3
1) 0x5587121720ce
FirstFunc returned 0x6

bob@UBUNTUVIRT:~/dumprep$ $FBC64 -g -gen gcc -O 0 stack.bas
bob@UBUNTUVIRT:~/dumprep$ ./stack
backtrace returned 1 elems
1) 0x402857
FirstFunc returned 0x1
SARG
Posts: 1332
Joined: May 27, 2005 7:15
Location: FRANCE

Re: Feature Request: Stack Trace Capability

Postby SARG » Jan 14, 2022 10:32

@adeyblue
Maybe I can help you : fbdebugger has its own 'stackwalk' for retrieving at any moment all the called procedures. However you need to use gas64 on 64bit OS.

The principle should also work with gas32 as prologue/epilogue are the same (push [r/e]bp -mov [r/e]bp,[r/e]sp).
adeyblue
Posts: 134
Joined: Nov 07, 2019 20:08

Re: Feature Request: Stack Trace Capability

Postby adeyblue » Jan 15, 2022 0:51

Thanks, I tried finding it in your debugger code but couldn't.

I found out why they're so broken. It seems these functions lean on the unwind tables quite heavily to get it right, but FBC is hardcoded to turn them off, even for debug builds

Code: Select all

FBC.bas
      '' Avoid gcc exception handling bloat
      ln += "-fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables "

If you compile with
-Wc -funwind-tables
then
SymInitialize = 1
Caught 7 frames using stack
7) 401A27
6) 401ED7
5) 40202A
4) 4013B4
3) 40150B
2) 77A2F33D
1) 77B63281
TryUnwind
1) 401B5F
2) 401ED7
3) 40202A
4) 4013B4
5) 40150B
6) 77A2F33D
7) 77B63281
Unwind found 7 frames
Fn2 returned 7 with num = 87
Stack is around 22F7A0
StackWalk64 produces:
1) 401C44
2) 401F3E
3) 40202A
4) 4013B4
5) 40150B
6) 77A2F33D
7) 77B63281

They work fine. Gas needs special directives in the asm to generate them.

Also my FBC is upto date with master, but this works fine with -gen gcc but crashes after printing "Before context" with -gen gas64

Code: Select all

#include "windows.bi"

Sub TryUnwind()
Print "in tryunwind"
   dim as UNWIND_HISTORY_TABLE unwindTable
   unwindTable.Search = TRUE
   dim as ULONGLONG prevImageBase = 0
   dim as CONTEXT ctx
   ctx.ContextFlags = CONTEXT_CONTROL Or CONTEXT_INTEGER
Print "Before context"
   RtlCaptureContext(@ctx)
Print "After context"
   dim as KNONVOLATILE_CONTEXT_POINTERS NvContext
   dim as PVOID HandlerData = 0
   dim as ULONGLONG EstablisherFrame = 0
   dim as ULong counter = 0
   Print "Before loop"
   While True
      Print "CCRip: " & Hex(ctx.Rip)
      dim as PRUNTIME_FUNCTION pPrevFunc = RtlLookupFunctionEntry(ctx.Rip, @prevImageBase, @unwindTable)
      if(pPrevFunc = 0) Then      
         Print "IN Manual adjust"
         ctx.Rip  = cast(ULONGLONG, *cast(ULONGLONG ptr, ctx.Rsp))
         ctx.Rsp += 8
      else
         RtlVirtualUnwind( _
            UNW_FLAG_NHANDLER, _
            prevImageBase, _
            ctx.Rip, _
            pPrevFunc, _
            @ctx, _
            @HandlerData, _
            @EstablisherFrame, _
            @NvContext _
         )
      End If
      if(ctx.Rip = 0) Then Exit While
      counter += 1
      Print Using "&) &"; counter; Hex(ctx.Rip)
   Wend
   Print Using "Unwind found & frames"; counter
End Sub

Private Function Fn2() As Long
    dim frames(0 to 60) As Any Ptr
    dim framesPtr As Any Ptr Ptr = @frames(0)
    dim hash as DWORD
    dim as Long caught = CaptureStackBackTrace(0, 61, framesPtr, @hash)
    Print Using "Caught & frames using stack capture"; caught
    For i As Long = 0 To caught - 1
        Print Using "&) &"; caught - i; Hex(frames(i))
    Next
    TryUnwind()
    Return caught
End Function

Private Sub Fn1(num as ULong)
    dim as Long numFn2 = Fn2()
    Print Using "Fn2 returned & with num = &"; numFn2; num
End Sub

Fn1(87)
SARG
Posts: 1332
Joined: May 27, 2005 7:15
Location: FRANCE

Re: Feature Request: Stack Trace Capability

Postby SARG » Jan 15, 2022 8:28

adeyblue wrote:I tried finding it in your debugger code but couldn't.


proc(j).db And proc(j).fn contains first/last addresses of every procedure. They are retrieved in debug (stabs) data.
At the end of this code (after wend) pridx() contains a list of the running procedure indexes. The rest of the procedure (not attached) looks for procedures already running.
Made for each thread.

It's a relatively easy way. If you need more information no problem. Linux and Windows compatible, readprocessmemory is a generic function.

Code: Select all

'=======================================================
'' after stopping run  retrieves all procedures
'=======================================================
private sub proc_runnew()

   #ifdef __fb_win32__
      dim as integer dummy
      Dim vcontext As CONTEXT

      if cast(integer,@vcontext) mod 16 <>0 then
         messbox("PRBM","Context not 16byte aligned")
      EndIf
      vcontext.contextflags=CONTEXT_CONTROL or CONTEXT_INTEGER
   #endif   
   Dim libel As String
   Dim As Integer regbp,regip,regbpnb,regbpp(PROCRMAX),ret(PROCRMAX),retadr
   Dim As ULong j,k,pridx(PROCRMAX)
   Dim tv As integer



   ''loading with rbp/ebp and proc index
   For ithd As Integer =0 To threadnb
      if thread(ithd).sv=-1 then continue for
      regbpnb=0
      #ifdef __fb_win32__
            GetThreadContext(thread(ithd).hd,@vcontext)
            regbp=vcontext.regbp
            regip=vcontext.regip 'current proc
         #else
            ptrace(PTRACE_GETREGS, threadcur, NULL, @regs)
            regbp=regs.xbp
            regip=regs.xip
      #endif
      While 1
         For j =1 To procnb
            If regip>=proc(j).db And regip<=proc(j).fn Then
               regbpnb+=1
               regbpp(regbpnb)=regbp
               ReadProcessMemory(dbghand,Cast(LPCVOID,regbp+SizeOf(integer)),@regip,SizeOf(Integer),0) 'return EIP/RIP
               ret(regbpnb)=regip
               pridx(regbpnb)=j
               Exit For
            EndIf
         Next
         If j>procnb Then Exit While
         ReadProcessMemory(dbghand,Cast(LPCVOID,regbp),@regbp,SizeOf(integer),0) 'previous RBP/EBP
      Wend

Return to “General”

Who is online

Users browsing this forum: No registered users and 9 guests