123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504

/**
 *   Stacktracing
 *
 *   Functions to generate a stacktrace.
 *
 *  Copyright: Copyright (C) 2009 Fawzi
 *  License:   Tango License
 *  Author:    Fawzi Mohamed
 */
module tango.core.tools.StackTrace;
import tango.core.tools.Demangler;
import tango.core.Runtime;
import tango.core.Thread;
import tango.core.Traits: ctfe_i2a;
import tango.stdc.string;
import tango.stdc.stringz : fromStringz;
import tango.stdc.stdlib: abort;
import tango.core.tools.FrameInfo;

version(Windows){
    import tango.core.tools.WinStackTrace;
} else {
    import tango.stdc.posix.ucontext;
    import tango.stdc.posix.sys.types: pid_t,pthread_t;
    import tango.stdc.signal;
    import tango.stdc.stdlib;
}
version(linux){
    import tango.core.tools.LinuxStackTrace;
}

version(CatchRecursiveTracing){
    __gshared ThreadLocal!(int) recursiveStackTraces;

    shared static this(){
        recursiveStackTraces=new ThreadLocal!(int)(0);
    }
}

version(Windows){
} else {
   struct TraceContext{
       bool hasContext;
       ucontext_t context;
       pid_t hProcess;
       pthread_t hThread;
   }
}

alias size_t function(TraceContext* context,TraceContext* contextOut,size_t*traceBuf,size_t bufLength,int *flags) AddrBacktraceFunc;
__gshared AddrBacktraceFunc addrBacktraceFnc;
alias bool function(ref FrameInfo fInfo,const(TraceContext)* context,char[] buf) SymbolizeFrameInfoFnc;
__gshared SymbolizeFrameInfoFnc symbolizeFrameInfoFnc;

shared static this(){
    addrBacktraceFnc=&defaultAddrBacktrace;
    symbolizeFrameInfoFnc=&defaultSymbolizeFrameInfo;
}

/// sets the function used for address stacktraces
extern(C) void rt_setAddrBacktraceFnc(AddrBacktraceFunc f){
    addrBacktraceFnc=f;
}
/// sets the function used to symbolize a FrameInfo
extern(C) void rt_setSymbolizeFrameInfoFnc(SymbolizeFrameInfoFnc f){
    symbolizeFrameInfoFnc=f;
}
/// creates a stack trace (defined in the runtime)
extern(C) Exception.TraceInfo rt_createTraceContext( void* ptr );

alias Exception.TraceInfo function( void* ptr = null ) TraceHandler;

/// builds a backtrace of addresses, the addresses are addresses of the *next* instruction,
/// *return* addresses, the most likely the calling instruction is the one before them
/// (stack top excluded)
extern(C) size_t rt_addrBacktrace(TraceContext* context, TraceContext *contextOut,size_t*traceBuf,size_t bufLength,int *flags){
    if (addrBacktraceFnc !is null){
        return addrBacktraceFnc(context,contextOut,traceBuf,bufLength,flags);
    } else {
        return 0;
    }
}

/// tries to sybolize a frame information, this should try to build the best
/// backtrace information, if possible finding the calling context, thus
/// if fInfo.exactAddress is false the address might be changed to the one preceding it
/// returns true if it managed to at least find the function name
extern(C) bool rt_symbolizeFrameInfo(ref FrameInfo fInfo,const(TraceContext)* context,char[] buf){
    if (symbolizeFrameInfoFnc !is null){
        return symbolizeFrameInfoFnc(fInfo,context,buf);
    } else {
        return false;
    }
}

// names of the functions that should be ignored for the backtrace
__gshared int[const(char)[]] internalFuncs;
shared static this(){
    /* TODO these probably are inaccurate given some parameter constness changes. Some might not even exist anymore. */
    internalFuncs["D5tango4core10stacktrace10StackTrace20defaultAddrBacktraceFPS5tango4core10stacktrace10StackTrace12TraceContextPS5tango4core10stacktrace10StackTrace12TraceContextPkkPiZk"]=1;
    internalFuncs["_D5tango4core10stacktrace10StackTrace20defaultAddrBacktraceFPS5tango4core10stacktrace10StackTrace12TraceContextPS5tango4core10stacktrace10StackTrace12TraceContextPmmPiZm"]=1;
    internalFuncs["rt_addrBacktrace"]=1;
    internalFuncs["D5tango4core10stacktrace10StackTrace14BasicTraceInfo5traceMFPS5tango4core10stacktrace10StackTrace12TraceContextiZv"]=1;
    internalFuncs["D5tango4core10stacktrace10StackTrace11basicTracerFPvZC9Exception9TraceInfo"]=1;
    internalFuncs["rt_createTraceContext"]=1;
    internalFuncs["D2rt6dmain24mainUiPPaZi7runMainMFZv"]=1;
    internalFuncs["D2rt6dmain24mainUiPPaZi6runAllMFZv"]=1;
    internalFuncs["D2rt6dmain24mainUiPPaZi7tryExecMFDFZvZv"]=1;
    internalFuncs["_D5tango4core10stacktrace10StackTrace20defaultAddrBacktraceFPS5tango4core10stacktrace10StackTrace12TraceContextPS5tango4core10stacktrace10StackTrace12TraceContextPkkPiZk"]=1;
    internalFuncs["_rt_addrBacktrace"]=1;
    internalFuncs["_D5tango4core10stacktrace10StackTrace14BasicTraceInfo5traceMFPS5tango4core10stacktrace10StackTrace12TraceContextiZv"]=1;
    internalFuncs["_D5tango4core10stacktrace10StackTrace11basicTracerFPvZC9Exception9TraceInfo"]=1;
    internalFuncs["_rt_createTraceContext"]=1;
    internalFuncs["_D2rt8compiler3dmd2rt6dmain24mainUiPPaZi7runMainMFZv"]=1;
    internalFuncs["_D2rt8compiler3dmd2rt6dmain24mainUiPPaZi6runAllMFZv"]=1;
    internalFuncs["_D2rt8compiler3dmd2rt6dmain24mainUiPPaZi7tryExecMFDFZvZv"]=1;
    internalFuncs["main"]=1;
    // glib specific
    internalFuncs["__libc_start_main"]=1;
    // backtrace() gets always the backtrace at the point it were called, so
    // ignore things we don't really want to see
    internalFuncs["_D5tango4core5tools10StackTrace20defaultAddrBacktraceFPS5tango4core5tools10StackTrace12TraceContextPS5tango4core5tools10StackTrace12TraceContextPkkPiZk"]=1;
    internalFuncs["_D5tango4core5tools10StackTrace14BasicTraceInfo5traceMFPS5tango4core5tools10StackTrace12TraceContextiZv"]=1;
    internalFuncs["_D5tango4core5tools10StackTrace11basicTracerFPvZC9Exception9TraceInfo"]=1;
    // assertion internals should not be shown to users
    internalFuncs["onAssertError"]=1;
    internalFuncs["_d_assert"]=1;
    internalFuncs["onAssertErrorMsg"]=1;
    internalFuncs["_d_assert_msg"]=1;
    // ignore calls when called for uncaught exceptions
    internalFuncs["_d_throwc"]=1;
}

// function to determine if a name is an internal method
const(char[])[] internalMethodEnders = [
    "8__assertFiZv",
    "9__requireMFZv"
];
bool isInternalMethod(const(char)[] name)
{
    static bool endsWith(const(char)[] str, const(char)[] what)
    {
        if (str.length < what.length)
            return false;
        return str[$-what.length .. $] == what;
    }
    if (name[0..2] != "_D"){
        return false;
    }
    foreach (end; internalMethodEnders){
        if (endsWith(name, end)){
            return true;
        }
    }
    return false;
}

/// returns the name of the function at the given adress (if possible)
/// function@ and then the address. For delegates you can use .funcptr
/// does not demangle
const(char)[] nameOfFunctionAt(void* addr, char[] buf){
    FrameInfo fInfo;
    fInfo.clear();
    fInfo.address=cast(size_t)addr;
    if (rt_symbolizeFrameInfo(fInfo,null,buf) && fInfo.func.length){
        return fInfo.func;
    } else {
        return "function@"~ctfe_i2a(cast(size_t)addr);
    }
}
/// ditto
const(char)[] nameOfFunctionAt(void * addr){
    char[1024] buf;
    return nameOfFunctionAt(addr,buf).dup;
}

/// precision of the addresses given by the backtrace function
enum AddrPrecision{
    AllReturn=0,
    TopExact=1,
    AllExact=3
}

/// basic class that represents a stacktrace
class BasicTraceInfo: Throwable.TraceInfo{
    size_t[] traceAddresses;
    size_t[128] traceBuf;
    AddrPrecision addrPrecision;
    TraceContext context;
    /// cretes an empty stacktrace
    this(){}
    /// creates a stacktrace with the given traceAddresses
    this(size_t[] traceAddresses,AddrPrecision addrPrecision){
        this.traceAddresses[]=traceAddresses;
        if (traceAddresses.length<=traceBuf.length){
            // change to either always copy (and truncate) or never copy?
            traceBuf[0..traceAddresses.length]=traceAddresses;
            this.traceAddresses=traceBuf[0..traceAddresses.length];
        }
        this.addrPrecision=addrPrecision;
    }
    /// takes a stacktrace
    void trace(TraceContext *contextIn=null,int skipFrames=0){
        int flags;
        size_t nFrames=rt_addrBacktrace(contextIn,&context,traceBuf.ptr,traceBuf.length,&flags);
        traceAddresses=traceBuf[skipFrames..nFrames];
        addrPrecision=cast(AddrPrecision)flags;
        if (flags==AddrPrecision.TopExact && skipFrames!=0)
            addrPrecision=AddrPrecision.AllReturn;
    }
    /// loops on the stacktrace
    override int opApply(scope int delegate(ref const(char[])) dg ) const
    {
        return opApply( (ref size_t, ref const(char[]) buf)
                        {
                            return dg( buf );
                        } );
    }

    override int opApply(scope int delegate(ref size_t line, ref const(char[]) func) loopBody) const
    {
            FrameInfo fInfo;
            for (size_t iframe=0;iframe<traceAddresses.length;++iframe){
                    char[2048] buf;
                    char[1024] buf2;
                    fInfo.clear();
                    fInfo.address=cast(size_t)traceAddresses[iframe];
                    fInfo.iframe=cast(ptrdiff_t)iframe;
                    fInfo.exactAddress=(addrPrecision & 2) || (iframe==0 && (addrPrecision & 1));
                    rt_symbolizeFrameInfo(fInfo,&context,buf);

                    auto r= fInfo.func in internalFuncs;
                    fInfo.internalFunction |= (r !is null);
                    fInfo.func = demangler.demangle(fInfo.func.dup,buf2);
                    int res=loopBody(fInfo.iframe, fInfo.func);
                    if (res) return res;
            }
            return 0;
    }
    int opApply(scope int delegate( ref FrameInfo fInfo ) loopBody) const {
        FrameInfo fInfo;
        for (size_t iframe=0;iframe<traceAddresses.length;++iframe){
            char[2048] buf;
            char[1024] buf2;
            fInfo.clear();
            fInfo.address=cast(size_t)traceAddresses[iframe];
            fInfo.iframe=cast(ptrdiff_t)iframe;
            fInfo.exactAddress=(addrPrecision & 2) || (iframe==0 && (addrPrecision & 1));
            rt_symbolizeFrameInfo(fInfo,&context,buf);

            if (!fInfo.internalFunction){
                auto r= (fInfo.func in internalFuncs);
                fInfo.internalFunction = (r !is null);
                if (!fInfo.internalFunction){
                    fInfo.internalFunction = isInternalMethod(fInfo.func);
                }
            }
            fInfo.func = demangler.demangle(fInfo.func,buf2);
            int res=loopBody(fInfo);
            if (res) return res;
        }
        return 0;
    }

    /// Writes out the stacktrace.
    void writeOut(scope void delegate(const(char[])) sink) const {
        int ignored = 0;
        foreach (ref FrameInfo fInfo; this){
            if (!fInfo.internalFunction){
                fInfo.iframe -= ignored;
                fInfo.writeOut(sink);
                fInfo.iframe += ignored;
                sink("\n");
            }
            else ignored++;
        }
    }
    
    override immutable(char)[] toString()
    {
        immutable(char)[] ret;
        writeOut((str) { ret ~= str; });
        return ret;
    }
}

version(linux){
    version=LibCBacktrace;
    version=DladdrSymbolification;
    version=ElfSymbolification;
}
version(darwin){
    version=LibCBacktrace;
    version=DladdrSymbolification;
}
version(LibCBacktrace){
    extern(C)int backtrace(void**,int);
}

/// default (tango given) backtrace function
size_t defaultAddrBacktrace(TraceContext* context,TraceContext*contextOut,
    size_t*traceBuf,size_t length,int*flags){
    version(LibCBacktrace){
        //if (context!is null) return 0; // now it just gives a local trace, uncomment & skip?
        *flags=AddrPrecision.TopExact;
        return cast(size_t)backtrace(cast(void**)traceBuf,cast(int)length);
    } else version (Windows){
        return winAddrBacktrace(context,contextOut,traceBuf,length,flags);
    } else {
        return 0;
    }
}

version(DladdrSymbolification){
    extern(C) struct Dl_info {
      char *dli_fname;      /* Filename of defining object */
      void *dli_fbase;      /* Load address of that object */
      char *dli_sname;      /* Name of nearest lower symbol */
      void *dli_saddr;      /* Exact value of nearest symbol */
    }

    extern(C)int dladdr(void* addr, Dl_info* info);

    /// poor symbolication, uses dladdr, gives no line info, limited info on statically linked files
    bool dladdrSymbolizeFrameInfo(ref FrameInfo fInfo,const(TraceContext)*context,char[] buf){
        Dl_info dli;
        void *ip=cast(void*)(fInfo.address);
        if (!fInfo.exactAddress) --ip;
        if (dladdr(ip, &dli))
        {
            if (dli.dli_fname && dli.dli_fbase){
                fInfo.offsetImg = cast(ptrdiff_t)ip - cast(ptrdiff_t)dli.dli_fbase;
                fInfo.baseImg = cast(size_t)dli.dli_fbase;
                fInfo.file=dli.dli_fname[0..strlen(dli.dli_fname)];
            }
            if (dli.dli_sname && dli.dli_saddr){
                fInfo.offsetSymb = cast(ptrdiff_t)ip - cast(ptrdiff_t)dli.dli_saddr;
                fInfo.baseSymb = cast(size_t)dli.dli_saddr;
                fInfo.func = dli.dli_sname[0..strlen(dli.dli_sname)];
            }
        }
        return true;
    }
}

version(ElfSymbolification){
version(TangoDoc)
{
    bool elfSymbolizeFrameInfo(ref FrameInfo fInfo,
        const(TraceContext)* context, char[] buf);
}
else
{
    bool elfSymbolizeFrameInfo(ref FrameInfo fInfo,
        const(TraceContext)* context, char[] buf)
    {
        Dl_info dli;
        void *ip=cast(void*)(fInfo.address);
        if (!fInfo.exactAddress) --ip;
        if (dladdr(ip, &dli))
        {
            if (dli.dli_fname && dli.dli_fbase){
                fInfo.offsetImg = cast(ptrdiff_t)ip - cast(ptrdiff_t)dli.dli_fbase;
                fInfo.baseImg = cast(size_t)dli.dli_fbase;
                fInfo.file=dli.dli_fname[0..strlen(dli.dli_fname)];
            }
            if (dli.dli_sname && dli.dli_saddr){
                fInfo.offsetSymb = cast(ptrdiff_t)ip - cast(ptrdiff_t)dli.dli_saddr;
                fInfo.baseSymb = cast(size_t)dli.dli_saddr;
                fInfo.func = dli.dli_sname[0..strlen(dli.dli_sname)];
            } else {
                // try static symbols
                foreach(symName,symAddr,symEnd,pub;StaticSectionInfo) {
                    if (cast(size_t)ip>=symAddr && cast(size_t)ip<symEnd) {
                        fInfo.offsetSymb = cast(ptrdiff_t)ip - cast(ptrdiff_t)symAddr;
                        fInfo.baseSymb = cast(size_t)symAddr;
                        fInfo.func = symName.dup;
                        break;
                    }
                }
            }
        }
        StaticSectionInfo.resolveLineNumber(fInfo);
        return true;
    }

}
}

/// loads symbols for the given frame info with the methods defined in tango itself
bool defaultSymbolizeFrameInfo(ref FrameInfo fInfo,const(TraceContext) *context, char[] buf){
    version(ElfSymbolification) {
        return elfSymbolizeFrameInfo(fInfo,context,buf);
    } else version(DladdrSymbolification){
        return dladdrSymbolizeFrameInfo(fInfo,context,buf);
    } else version(Windows) {
        return winSymbolizeFrameInfo(fInfo,context,buf);
    } else {
        return false;
    }
}

/// function that generates a trace (handler compatible with old TraceInfo)
Exception.TraceInfo basicTracer( void* ptr = null ){
    BasicTraceInfo res;
    try{
        version(CatchRecursiveTracing){
            recursiveStackTraces.val=recursiveStackTraces.val+1;
            scope(exit) recursiveStackTraces.val=recursiveStackTraces.val-1;
            // printf("tracer %d\n",recursiveStackTraces.val);
            if (recursiveStackTraces.val>10) {
                Runtime.console.stderr("hit maximum recursive tracing (tracer asserting...?)\n");
                abort();
                return null;
            }
        }
        res=new BasicTraceInfo();
        res.trace(cast(TraceContext*)ptr);
    } catch (Throwable e){
        Runtime.console.stderr("tracer got exception:\n");
        Runtime.console.stderr(e.msg);
        Runtime.console.stderr(e.toString());
        Runtime.console.stderr("\n");
    }
    return res;
}

// signal handling
version(Posix){
    version(linux){
        version(X86){
            version = haveSegfaultTrace;
        }else version(X86_64){
            version = haveSegfaultTrace;
        }
    }

    extern(C) void tango_stacktrace_fault_handler (int sn, siginfo_t * si, void *ctx){
        Runtime.console.stderr(fromStringz(strsignal(sn)));
        Runtime.console.stderr(" encountered at:\n");
        ucontext_t * context = cast(ucontext_t *) ctx;
        version(haveSegfaultTrace){
            void* stack;
            void* code;
            version(X86){
                code = cast(void*) context.uc_mcontext.gregs[14];
            }else version(X86_64){
                code = cast(void*) context.uc_mcontext.gregs[0x10];
            }else{
                static assert(0);
            }

            FrameInfo fInfo;
            char[1024] buf1,buf2;
            fInfo.clear();
            fInfo.address=cast(size_t)code;
            rt_symbolizeFrameInfo(fInfo,null,buf1);
            fInfo.func = demangler.demangle(fInfo.func,buf2);
            fInfo.writeOut((in char[] s) { Runtime.console.stderr(s); });
        }
        Runtime.console.stderr("\n Stacktrace:\n");
        TraceContext tc;
        tc.hasContext=ctx is null;
        if (tc.hasContext) tc.context=*(cast(ucontext_t*)ctx);
        Exception.TraceInfo info=basicTracer(&tc);

        info.opApply((ref const(char[]) s) { Runtime.console.stderr(s~"\n"); return 0;});

        Runtime.console.stderr("Stacktrace signal handler abort().\n");
        abort();
    }

    __gshared sigaction_t fault_action;

    void setupSegfaultTracer(){
        //use an alternative stack; this is useful when infinite recursion
        //  triggers a SIGSEGV
        void* altstack = malloc(SIGSTKSZ);
        if (altstack) {
            stack_t stack;
            stack.ss_sp = altstack;
            stack.ss_size = SIGSTKSZ;
            sigaltstack(&stack, null);
        }
        fault_action.sa_handler = cast(typeof(fault_action.sa_handler)) &tango_stacktrace_fault_handler;
        sigemptyset(&fault_action.sa_mask);
        fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
        foreach (sig;[SIGSEGV,SIGFPE,SIGILL,SIGBUS,SIGINT]){
            sigaction(sig, &fault_action, null);
        }
    }

    version(noSegfaultTrace){
    } else {
        shared static this(){
            setupSegfaultTracer();
        }
    }
}else version(Windows){
}else {
    pragma(msg, "[INFO] SEGFAULT trace not yet implemented for this OS");
}