123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998
/**
 *   Linux Stacktracing
 *
 *   Functions to parse the ELF format and create a symbolic trace.
 *
 *   The core Elf handling was taken from Thomas Kühne flectioned,
 *   with some minor pieces taken from winterwar/wm4
 *   But the routines and flow have been (sometime heavily) changed.
 *
 *  Copyright: Copyright (C) 2009 Fawzi, Thomas Kühne, wm4
 *  License:   Tango License
 *  Author:    Fawzi Mohamed
 */
module tango.core.tools.LinuxStackTrace;

import tango.core.tools.FrameInfo;

version(TangoDoc)
{

}
else
{

version(D_Version2)
{
        private void ThWriteOut(Throwable th, void delegate(in char[])sink){
        if (th.file.length>0 || th.line!=0)
        {
            char[25]buf;
            sink(th.classinfo.name);
            sink("@");
            sink(th.file);
            sink("(");
            sink(ulongToUtf8(buf, th.line));
            sink("): ");
            sink(th.toString());
            sink("\n");
        }
        else
        {
           sink(th.classinfo.name);
           sink(": ");
           sink(th.toString());
           sink("\n");
        }
        if (th.info)
        {
            sink("----------------\n");
            th.info.opApply((ref const(char[]) msg){sink(msg); return 0;});
        }
        if (th.next){
            sink("\n++++++++++++++++\n");
            ThWriteOut(th, sink);
        }
    }
}


version(linux){
    import tango.stdc.stdlib;
    import tango.stdc.stdio : FILE, fopen, fread, fseek, fclose, SEEK_SET, fgets, sscanf;
    import tango.stdc.string : strcmp, strlen,memcmp;
    import tango.stdc.stringz : fromStringz;
    import tango.stdc.signal;
    import tango.stdc.errno: errno, EFAULT;
    import tango.stdc.posix.unistd: access;
    import tango.text.Util : delimit;
    import tango.core.Array : find, rfind;
    import tango.core.Runtime;

    class SymbolException:Exception {
        this(immutable(char)[] msg, immutable(char)[] file,long lineNr,Exception next=null){
            super(msg,file,cast(uint)lineNr,next);
        }
    }
    bool may_read(size_t addr){
        errno(0);
        access(cast(char*)addr, 0);
        return errno() != EFAULT;
    }

    private extern(C){
        alias ushort Elf32_Half;
        alias ushort Elf64_Half;
        alias uint Elf32_Word;
        alias int Elf32_Sword;
        alias uint Elf64_Word;
        alias int Elf64_Sword;
        alias ulong Elf32_Xword;
        alias long Elf32_Sxword;
        alias ulong Elf64_Xword;
        alias long Elf64_Sxword;
        alias uint Elf32_Addr;
        alias ulong Elf64_Addr;
        alias uint Elf32_Off;
        alias ulong Elf64_Off;
        alias ushort Elf32_Section;
        alias ushort Elf64_Section;
        alias Elf32_Half Elf32_Versym;
        alias Elf64_Half Elf64_Versym;

        struct Elf32_Sym{
            Elf32_Word  st_name;
            Elf32_Addr  st_value;
            Elf32_Word  st_size;
            ubyte st_info;
            ubyte st_other;
            Elf32_Section   st_shndx;
        }

        struct Elf64_Sym{
            Elf64_Word  st_name;
            ubyte       st_info;
            ubyte       st_other;
            Elf64_Section   st_shndx;
            Elf64_Addr  st_value;
            Elf64_Xword st_size;
        }

        struct Elf32_Phdr{
            Elf32_Word  p_type;
            Elf32_Off   p_offset;
            Elf32_Addr  p_vaddr;
            Elf32_Addr  p_paddr;
            Elf32_Word  p_filesz;
            Elf32_Word  p_memsz;
            Elf32_Word  p_flags;
            Elf32_Word  p_align;
        }

        struct Elf64_Phdr{
            Elf64_Word  p_type;
            Elf64_Word  p_flags;
            Elf64_Off   p_offset;
            Elf64_Addr  p_vaddr;
            Elf64_Addr  p_paddr;
            Elf64_Xword p_filesz;
            Elf64_Xword p_memsz;
            Elf64_Xword p_align;
        }

        struct Elf32_Dyn{
            Elf32_Sword d_tag;
            union{
                Elf32_Word d_val;
                Elf32_Addr d_ptr;
            }
        }

        struct Elf64_Dyn{
            Elf64_Sxword    d_tag;
            union{
                Elf64_Xword d_val;
                Elf64_Addr d_ptr;
            }
        }
        enum { EI_NIDENT = 16 }

        struct Elf32_Ehdr{
            char        e_ident[EI_NIDENT]; /* Magic number and other info */
            Elf32_Half  e_type;         /* Object file type */
            Elf32_Half  e_machine;      /* Architecture */
            Elf32_Word  e_version;      /* Object file version */
            Elf32_Addr  e_entry;        /* Entry point virtual address */
            Elf32_Off   e_phoff;        /* Program header table file offset */
            Elf32_Off   e_shoff;        /* Section header table file offset */
            Elf32_Word  e_flags;        /* Processor-specific flags */
            Elf32_Half  e_ehsize;       /* ELF header size in bytes */
            Elf32_Half  e_phentsize;        /* Program header table entry size */
            Elf32_Half  e_phnum;        /* Program header table entry count */
            Elf32_Half  e_shentsize;        /* Section header table entry size */
            Elf32_Half  e_shnum;        /* Section header table entry count */
            Elf32_Half  e_shstrndx;     /* Section header string table index */
        }

        struct Elf64_Ehdr{
            char        e_ident[EI_NIDENT]; /* Magic number and other info */
            Elf64_Half  e_type;         /* Object file type */
            Elf64_Half  e_machine;      /* Architecture */
            Elf64_Word  e_version;      /* Object file version */
            Elf64_Addr  e_entry;        /* Entry point virtual address */
            Elf64_Off   e_phoff;        /* Program header table file offset */
            Elf64_Off   e_shoff;        /* Section header table file offset */
            Elf64_Word  e_flags;        /* Processor-specific flags */
            Elf64_Half  e_ehsize;       /* ELF header size in bytes */
            Elf64_Half  e_phentsize;        /* Program header table entry size */
            Elf64_Half  e_phnum;        /* Program header table entry count */
            Elf64_Half  e_shentsize;        /* Section header table entry size */
            Elf64_Half  e_shnum;        /* Section header table entry count */
            Elf64_Half  e_shstrndx;     /* Section header string table index */
        }

        struct Elf32_Shdr{
            Elf32_Word  sh_name;        /* Section name (string tbl index) */
            Elf32_Word  sh_type;        /* Section type */
            Elf32_Word  sh_flags;       /* Section flags */
            Elf32_Addr  sh_addr;        /* Section virtual addr at execution */
            Elf32_Off   sh_offset;      /* Section file offset */
            Elf32_Word  sh_size;        /* Section size in bytes */
            Elf32_Word  sh_link;        /* Link to another section */
            Elf32_Word  sh_info;        /* Additional section information */
            Elf32_Word  sh_addralign;       /* Section alignment */
            Elf32_Word  sh_entsize;     /* Entry size if section holds table */
        }

        struct Elf64_Shdr{
            Elf64_Word  sh_name;        /* Section name (string tbl index) */
            Elf64_Word  sh_type;        /* Section type */
            Elf64_Xword sh_flags;       /* Section flags */
            Elf64_Addr  sh_addr;        /* Section virtual addr at execution */
            Elf64_Off   sh_offset;      /* Section file offset */
            Elf64_Xword sh_size;        /* Section size in bytes */
            Elf64_Word  sh_link;        /* Link to another section */
            Elf64_Word  sh_info;        /* Additional section information */
            Elf64_Xword sh_addralign;       /* Section alignment */
            Elf64_Xword sh_entsize;     /* Entry size if section holds table */
        }

        enum{
            PT_DYNAMIC  = 2,
            DT_STRTAB   = 5,
            DT_SYMTAB   = 6,
            DT_STRSZ    = 10,
            DT_DEBUG    = 21,
            SHT_SYMTAB  = 2,
            SHT_STRTAB  = 3,
            STB_LOCAL   = 0,
        }

    }

    ubyte ELF32_ST_BIND(ulong info){
        return  cast(ubyte)((info & 0xF0) >> 4);
    }

    static if(4 == (void*).sizeof){
        alias Elf32_Sym Elf_Sym;
        alias Elf32_Dyn Elf_Dyn;
        alias Elf32_Addr Elf_Addr;
        alias Elf32_Phdr Elf_Phdr;
        alias Elf32_Half Elf_Half;
        alias Elf32_Ehdr Elf_Ehdr;
        alias Elf32_Shdr Elf_Shdr;
    }else static if(8 == (void*).sizeof){
        alias Elf64_Sym Elf_Sym;
        alias Elf64_Dyn Elf_Dyn;
        alias Elf64_Addr Elf_Addr;
        alias Elf64_Phdr Elf_Phdr;
        alias Elf64_Half Elf_Half;
        alias Elf64_Ehdr Elf_Ehdr;
        alias Elf64_Shdr Elf_Shdr;
    }else{
        static assert(0);
    }

    struct StaticSectionInfo{
        Elf_Ehdr header;
        const(char)[] stringTable;
        Elf_Sym[] sym;
        ubyte[] debugLine;   //contents of the .debug_line section, if available
        const(char)[] fileName;
        void* mmapBase;
        size_t mmapLen;
        /// initalizer
        static StaticSectionInfo opCall(Elf_Ehdr header, const(char)[] stringTable, Elf_Sym[] sym,
            ubyte[] debugLine, const(char)[] fileName, void* mmapBase=null, size_t mmapLen=0) {
            StaticSectionInfo newV;
            newV.header=header;
            newV.stringTable=stringTable;
            newV.sym=sym;
            newV.debugLine = debugLine;
            newV.fileName=fileName;
            newV.mmapBase=mmapBase;
            newV.mmapLen=mmapLen;
            return newV;
        }

        // stores the global sections
        const MAX_SECTS=5;
        static StaticSectionInfo[MAX_SECTS] _gSections;
        static size_t _nGSections,_nFileBuf;
        static char[MAX_SECTS*256] _fileNameBuf;

        /// loops on the global sections
        static int opApply(scope int delegate(ref StaticSectionInfo) loop){
            for (size_t i=0;i<_nGSections;++i){
                auto res=loop(_gSections[i]);
                if (res) return res;
            }
            return 0;
        }
        /// loops on the static symbols
        static int opApply(scope int delegate(ref const(char)[] sNameP,ref size_t startAddr,
            ref size_t endAddr, ref bool pub) loop){
            for (size_t isect=0;isect<_nGSections;++isect){
                StaticSectionInfo *sec=&(_gSections[isect]);
                for (size_t isym=0;isym<sec.sym.length;++isym) {
                    auto symb=sec.sym[isym];
                    if(!symb.st_name || !symb.st_value){
                        // anonymous || undefined
                        continue;
                    }

                    bool isPublic = true;
                    if(STB_LOCAL == ELF32_ST_BIND(symb.st_info)){
                        isPublic = false;
                    }
                    const(char) *sName;
                    if (symb.st_name<sec.stringTable.length) {
                        sName=&(sec.stringTable[symb.st_name]);
                    } else {
                        debug(elf) printf("symbol name out of bounds %p\n",symb.st_value);
                    }
                    const(char)[] symbName=sName[0..(sName?strlen(sName):0)];
                    size_t endAddr=symb.st_value+symb.st_size;
                    auto res=loop(symbName,symb.st_value,endAddr,isPublic);
                    if (res) return res;
                }
            }
            return 0;
        }
        /// returns a new section to fill out
        static StaticSectionInfo *addGSection(Elf_Ehdr header,const(char)[] stringTable, Elf_Sym[] sym,
            ubyte[] debugLine, const(char)[] fileName,void *mmapBase=null, size_t mmapLen=0){
            if (_nGSections>=MAX_SECTS){
                throw new Exception("too many static sections",__FILE__,__LINE__);
            }
            auto len=fileName.length;
            const(char)[] newFileName;
            if (_fileNameBuf.length< _nFileBuf+len) {
                newFileName=fileName[0..len].dup;
            } else {
                _fileNameBuf[_nFileBuf.._nFileBuf+len]=fileName[0..len];
                newFileName=_fileNameBuf[_nFileBuf.._nFileBuf+len];
                _nFileBuf+=len;
            }
            _gSections[_nGSections]=StaticSectionInfo(header,stringTable,sym,debugLine,newFileName,
                                                      mmapBase,mmapLen);
            _nGSections++;
            return &(_gSections[_nGSections-1]);
        }

        static void resolveLineNumber(ref FrameInfo info) {
            foreach (ref section; _gSections[0.._nGSections]) {
                //dwarf stores the directory component of filenames separately
                //dmd doesn't care, and directory components are in the filename
                //linked in gcc produced files still use them
                const(char)[] dir;
                //assumption: if exactAddress=false, it's a return address
                if (find_line_number(section.debugLine, info.address, !info.exactAddress, dir, info.file, info.line))
                    break;
            }
        }
    }

    private void scan_static(in char *file){
        // should try to use mmap,for this reason the "original" format is kept
        // if copying (as now) one could discard the unused strings, and pack the symbols in
        // a platform independent format, but the mmap approach is probably better
        /+auto fdesc=open(file,O_RDONLY);
        ptr_diff_t some_offset=0;
        size_t len=lseek(fdesc,0,SEEK_END);
        lseek(fdesc,0,SEEK_SET);
        address = mmap(0, len, PROT_READ, MAP_PRIVATE, fdesc, some_offset);+/
        FILE * fd=fopen(file,"r");
        bool first_symbol = true;
        Elf_Ehdr header;
        Elf_Shdr section;
        Elf_Sym sym;

        void read(void* ptr, size_t size){
            auto readB=fread(ptr, 1, size,fd);
            if(readB != size){
                throw new SymbolException("read failure in file "~file[0..strlen(file)].idup,__FILE__,__LINE__);
            }
        }

        void seek(ptrdiff_t offset){
            if(fseek(fd, offset, SEEK_SET) == -1){
                throw new SymbolException("seek failure",__FILE__,__LINE__);
            }
        }

        /* read elf header */
        read(&header, header.sizeof);
        if(header.e_shoff == 0){
            return;
        }
        const bool useShAddr=false;
        char[] sectionStrs;
        for(ptrdiff_t i = header.e_shnum - 1; i > -1; i--){
            seek(header.e_shoff + i * header.e_shentsize);
            read(&section, section.sizeof);
            debug(none) printf("[%i] %i\n", i, section.sh_type);

            if (section.sh_type == SHT_STRTAB) {
                /* read string table */
                debug(elf) printf("looking for .shstrtab, [%i] is STRING (size:%i)\n", i, section.sh_size);
                seek(section.sh_offset);
                if (section.sh_name<section.sh_size) {
                    if (useShAddr && section.sh_addr) {
                        if (!may_read(cast(size_t)section.sh_addr)){
                            Runtime.console.stderr("section '");
                            Runtime.console.stderr(i);
                            Runtime.console.stderr("' has invalid address, relocated?\n");
                        } else {
                            sectionStrs=(cast(char*)section.sh_addr)[0..section.sh_size];
                        }
                    }
                    sectionStrs.length = section.sh_size;
                    read(sectionStrs.ptr, sectionStrs.length);
                    char* p=&(sectionStrs[section.sh_name]);
                    if (strcmp(p,".shstrtab".ptr)==0) break;
                }
            }
        }
        if (sectionStrs) {
            char* p=&(sectionStrs[section.sh_name]);
            if (strcmp(p,".shstrtab".ptr)!=0) {
                sectionStrs="\0".dup;
            } else {
                debug(elf) printf("found .shstrtab\n");
            }
        } else {
            sectionStrs="\0".dup;
        }


        /* find sections */
        char[] string_table;
        Elf_Sym[] symbs;
        ubyte[] debug_line;
        for(ptrdiff_t i = header.e_shnum - 1; i > -1; i--){
            seek(header.e_shoff + i * header.e_shentsize);
            read(&section, section.sizeof);
            debug(none) printf("[%i] %i\n", i, section.sh_type);

            if (section.sh_name>=sectionStrs.length) {
                Runtime.console.stderr("could not find name for ELF section at ");
                Runtime.console.stderr(section.sh_name);
                Runtime.console.stderr("\n");
                continue;
            }
            debug(elf) printf("Elf section %s\n",sectionStrs.ptr+section.sh_name);
            if (section.sh_type == SHT_STRTAB && !string_table) {
                /* read string table */
                debug(elf) printf("[%i] is STRING (size:%i)\n", i, section.sh_size);
                if  (strcmp(sectionStrs.ptr+section.sh_name,".strtab")==0){
                    seek(section.sh_offset);
                    if (useShAddr && section.sh_addr){
                        if (!may_read(cast(size_t)section.sh_addr)){
                            Runtime.console.stderr("section '");
                            Runtime.console.stderr(fromStringz(&(sectionStrs[section.sh_name])));
                            Runtime.console.stderr("' has invalid address, relocated?\n");
                        } else {
                            string_table=(cast(char*)section.sh_addr)[0..section.sh_size];
                        }
                    } else {
                        string_table.length = section.sh_size;
                        read(string_table.ptr, string_table.length);
                    }
                }
            } else if(section.sh_type == SHT_SYMTAB) {
                /* read symtab */
                debug(elf) printf("[%i] is SYMTAB (size:%i)\n", i, section.sh_size);
                if (strcmp(sectionStrs.ptr+section.sh_name,".symtab")==0 && !symbs) {
                    if (useShAddr && section.sh_addr){
                        if (!may_read(cast(size_t)section.sh_addr)){
                            Runtime.console.stderr("section '");
                            Runtime.console.stderr(fromStringz(&(sectionStrs[section.sh_name])));
                            Runtime.console.stderr("' has invalid address, relocated?\n");
                        } else {
                            symbs=(cast(Elf_Sym*)section.sh_addr)[0..section.sh_size/Elf_Sym.sizeof];
                        }
                    } else {
                        if(section.sh_offset == 0){
                            continue;
                        }
                        auto p=malloc(section.sh_size);
                        if (p is null)
                            throw new Exception("failed alloc",__FILE__,__LINE__);
                        symbs=(cast(Elf_Sym*)p)[0..section.sh_size/Elf_Sym.sizeof];
                        seek(section.sh_offset);
                        read(symbs.ptr,symbs.length*Elf_Sym.sizeof);
                    }
                }
            } else if (strcmp(sectionStrs.ptr+section.sh_name,".debug_line")==0 && !debug_line) {
                seek(section.sh_offset);
                if (useShAddr && section.sh_addr){
                    if (!may_read(cast(size_t)section.sh_addr)){
                        Runtime.console.stderr("section '");
                        Runtime.console.stderr(fromStringz(&(sectionStrs[section.sh_name])));
                        Runtime.console.stderr("' has invalid address, relocated?\n");
                    } else {
                        debug_line=(cast(ubyte*)section.sh_addr)[0..section.sh_size];
                    }
                } else {
                    auto p=malloc(section.sh_size);
                    if (p is null)
                        throw new Exception("failed alloc",__FILE__,__LINE__);
                    debug_line=(cast(ubyte*)p)[0..section.sh_size];
                    seek(section.sh_offset);
                    read(debug_line.ptr,debug_line.length);
                }
            }
        }

        if (string_table.ptr && symbs.ptr) {
            StaticSectionInfo.addGSection(header,string_table,symbs,debug_line,file[0..strlen(file)]);
            string_table=null;
            symbs=null;
            debug_line=null;
        }
    }

    private void find_symbols(){
        // static symbols
        find_static();
        // dynamic symbols handled with dladdr
    }

    private void find_static(){
        FILE* maps;
        char[4096] buffer;

        maps = fopen("/proc/self/maps", "r");
        if(maps is null){
            debug{
                throw new SymbolException("couldn't read '/proc/self/maps'",__FILE__,__LINE__);
            }else{
                return;
            }
        }
        scope(exit) fclose(maps);

        buffer[] = 0;
        while(fgets(buffer.ptr, buffer.length - 1, maps)){
            scope(exit){
                buffer[] = 0;
            }
            const(char)[] tmp;
            cleanEnd: for(size_t i = buffer.length - 1; i >= 0; i--){
                switch(buffer[i]){
                    case 0, '\r', '\n':
                        buffer[i] = 0;
                        break;
                    default:
                        tmp = buffer[0 .. i+1];
                        break cleanEnd;
                }
            }

Lsplit:
            static if(is(typeof(split(""c)) == string[])){
                string[] tok = split(tmp);
                if(tok.length != 6){
                    // no source file
                    continue;
                }
            }else{
                const(char)[][] tok = delimit(tmp, " \t");
                if(tok.length < 6){
                    // no source file
                    continue;
                }
                const tok_len = 33;
            }
            if(find(tok[$-1], "[") == 0){
                // pseudo source
                continue;
            }
            if(rfind(tok[$-1], ".so") == tok[$-1].length - 3){
                // dynamic lib
                continue;
            }
            if(rfind(tok[$-1], ".so.") != tok[$-1].length ){
                // dynamic lib
                continue;
            }
            if(find(tok[1], "r") == -1){
                // no read
                continue;
            }
            if(find(tok[1], "x") == -1){
                // no execute
                continue;
            }
            const(char)[] addr = tok[0] ~ "\u0000";
            const(char)[] source = tok[$-1] ~ "\u0000";
            enum immutable(char)[] marker = "\x7FELF"c;

            void* start, end;
            if(2 != sscanf(addr.ptr, "%zX-%zX", &start, &end)){
                continue;
            }
            if(cast(size_t)end - cast(size_t)start < 4){
                continue;
            }
            if(!may_read(cast(size_t)start)){
                Runtime.console.stderr("got invalid start ptr from '");
                Runtime.console.stderr(fromStringz(source.ptr));
                Runtime.console.stderr("'\n");
                Runtime.console.stderr("ignoring error in ");
                Runtime.console.stderr(__FILE__);
                Runtime.console.stderr(":");
                Runtime.console.stderr(__FILE__);
                Runtime.console.stderr("\n");
                return;
            }
            if(memcmp(start, marker.ptr, marker.length) != 0){
                // not an ELF file
                continue;
            }
            try{
                scan_static(source.ptr);
                debug(elfTable){
                    printf("XX symbols\n");
                    foreach(sName,startAddr,endAddr,pub;StaticSectionInfo){
                        printf("%p %p %d %*s\n",startAddr,endAddr,pub,sName.length,sName.ptr);
                    }
                    printf("XX symbols end\n");
                }
            } catch (Exception e) {
                Runtime.console.stderr("failed reading symbols from '");
                Runtime.console.stderr(fromStringz(source.ptr));
                Runtime.console.stderr("'\n");
                Runtime.console.stderr("ignoring error in ");
                Runtime.console.stderr(__FILE__);
                Runtime.console.stderr(":");
                Runtime.console.stderr(__FILE__);
                Runtime.console.stderr("\n");
                ThWriteOut(e, (in char[] s){ Runtime.console.stderr(s); });
                return;
            }
                
        }
    }

    shared static this() {
        find_symbols();
    }


    private void dwarf_error(const(char)[] msg) {
        Runtime.console.stderr("Tango stacktracer DWARF error: ");
        Runtime.console.stderr(msg);
        Runtime.console.stderr("\n");
    }

    alias short uhalf;

    struct DwarfReader {
        ubyte[] data;
        size_t read_pos;
        bool is_dwarf_64;

        @property size_t left() {
            return data.length - read_pos;
        }

        @property ubyte next() {
            ubyte r = data[read_pos];
            read_pos++;
            return r;
        }

        //read the length field, and set the is_dwarf_64 flag accordingly
        //return 0 on error
        size_t read_initial_length() {
            //64 bit applications normally use 32 bit DWARF information
            //this means on 64 bit, we have to handle both 32 bit and 64 bit infos
            //the 64 bit version seems to be rare, though
            //independent from this, 32 bit DWARF still uses some 64 bit types in
            //64 bit executables (at least the DW_LNE_set_address opcode does)
            auto initlen = read!(uint)();
            is_dwarf_64 = (initlen == 0xff_ff_ff_ff);
            if (is_dwarf_64) {
                //--can handle this, but need testing (this format seems to be uncommon)
                //--remove the following 2 lines to see if it works, and fix the code if needed
                dwarf_error("dwarf 64 detected, aborting");
                abort();
                //--
                static if (size_t.sizeof > 4) {
                    dwarf_error("64 bit DWARF in a 32 bit excecutable?");
                    return 0;
                }
                else return cast(size_t)read!(ulong)();
            } else {
                if (initlen >= 0xff_ff_ff_00) {
                    //see dwarf spec 7.5.1
                    dwarf_error("corrupt debugging information?");
                }
                return initlen;
            }
        }

        //adapted from example code in dwarf spec. appendix c
        //defined max. size is 128 bit; we provide up to 64 bit
        private ulong do_read_leb(bool sign_ext) {
            ulong res;
            int shift;
            ubyte b;
            do {
                b = next();
                res = res | ((b & 0x7f) << shift);
                shift += 7;
            } while (b & 0x80);
            if (sign_ext && shift < ulong.sizeof*8 && (b & 0x40))
                res = res - (1L << shift);
            return res;
        }
        ulong uleb128() {
            return do_read_leb(false);
        }
        long sleb128() {
            return do_read_leb(true);
        }

        T read(T)() {
            T r = *cast(T*)data[read_pos..read_pos+T.sizeof].ptr;
            read_pos += T.sizeof;
            return r;
        }

        size_t read_header_length() {
            if (is_dwarf_64) {
                return cast(size_t)read!(ulong)();
            } else {
                return cast(size_t)read!(uint)();
            }
        }

        //null terminated string
        const(char)[] str() {
            char* start = cast(char*)&data[read_pos];
            size_t len = strlen(start);
            read_pos += len + 1;
            return start[0..len];
        }
    }

    unittest {
        //examples from dwarf spec section 7.6
        ubyte[] bytes = [2,127,0x80,1,0x81,1,0x82,1,57+0x80,100,2,0x7e,127+0x80,0,
            0x81,0x7f,0x80,1,0x80,0x7f,0x81,1,0x7f+0x80,0x7e];
        ulong[] u = [2, 127, 128, 129, 130, 12857];
        long[] s = [2, -2, 127, -127, 128, -128, 129, -129];
        auto rd = DwarfReader(bytes);
        foreach (x; u)
            assert(rd.uleb128() == x);
        foreach (x; s)
            assert(rd.sleb128() == x);
    }

    //debug_line = contents of the .debug_line section
    //is_return_address = true if address is a return address (found by stacktrace)
    bool find_line_number(ubyte[] debug_line, size_t address, bool is_return_address,
        ref const(char)[] out_directory, ref const(char)[] out_file, ref long out_line)
    {
        DwarfReader rd = DwarfReader(debug_line);


        //NOTE:
        //  - instead of saving the filenames when the debug infos are first parsed,
        //    we only save a reference to the debug infos (with FileRef), and
        //    reparse the debug infos when we need the actual filenames
        //  - the same code is used for skipping over the debug infos, and for
        //    getting the filenames later
        //  - this is just for avoiding memory allocation

        struct FileRef {
            int file;           //file number
            size_t directories; //offset to directory info
            size_t filenames;   //offset to filename info
        }

        //include_directories
        void reparse_dirs(void delegate(int idx, const(char)[] d) entry) {
            int idx = 1;
            for (;;) {
                auto s = rd.str();
                if (!s.length)
                    break;
                if (entry)
                    entry(idx, s);
                idx++;
            }
        }
        //file_names
        void reparse_files(void delegate(int idx, int dir, const(char)[] fn) entry) {
            int idx = 1;
            for (;;) {
                auto s = rd.str();
                if (!s.length)
                    break;
                int dir = cast(int)rd.uleb128(); //directory index
                rd.uleb128();           //last modification time (unused)
                rd.uleb128();           //length of file (unused)
                if (entry)
                    entry(idx, dir, s);
                idx++;
            }
        }

        //associated with the found entry
        FileRef found_file;
        bool found = false;

        //the section is made up of independent blocks of line number programs
        blocks: while (rd.left > 0) {
            size_t unit_length = rd.read_initial_length();

            if (unit_length == 0)
                return false;

            size_t start = rd.read_pos;
            size_t end = start + unit_length;

            auto ver = rd.read!(uhalf)();
            auto header_length = rd.read_header_length();

            size_t header_start = rd.read_pos;

            auto min_instr_len = rd.read!(ubyte)();
            auto def_is_stmt = rd.read!(ubyte)();
            auto line_base = rd.read!(byte)();
            auto line_range = rd.read!(ubyte)();
            auto opcode_base = rd.read!(ubyte)();
            ubyte[256] sol_store; //to avoid heap allocation
            ubyte[] standard_opcode_lengths = sol_store[0..opcode_base-1];
            foreach (ref x; standard_opcode_lengths) {
                x = rd.read!(ubyte)();
            }

            size_t dirs_offset = rd.read_pos;
            reparse_dirs(null);
            size_t files_offset = rd.read_pos;
            reparse_files(null);

            rd.read_pos = header_start + header_length;

            //state machine registers
            struct LineRegs {
                bool valid() { return address != 0; }
                int file = 1;               //file index
                int line = 1;               //line number
                size_t address = 0;         //absolute address
                bool end_sequence = false;  //last row in a block
            }

            LineRegs regs;      //current row
            LineRegs regs_prev; //row before

            //append row to virtual line number table, using current register contents
            //NOTE: reg_address is supposed to be increased only (within  a block)
            //      reg_line can be increased or decreased randomly
            void append() {
                if (regs_prev.valid()) {
                    if (is_return_address) {
                        if (address >= regs_prev.address && address <= regs.address)
                            found = true;
                    } else {
                        //some special case *shrug*
                        if (regs_prev.address == address)
                            found = true;
                        //not special case
                        if (address >= regs_prev.address && address < regs.address)
                            found = true;
                    }

                    if (found) {
                        out_line = regs_prev.line;
                        found_file.file = regs_prev.file;
                        found_file.directories = dirs_offset;
                        found_file.filenames = files_offset;
                    }
                }

                regs_prev = regs;
            }

            //actual line number program
            loop: while (rd.read_pos < end) {
                ubyte cur = rd.next();

                if (found)
                    break blocks;

                //"special opcodes"
                if (cur >= opcode_base) {
                    int adj = cur - opcode_base;
                    long addr_inc = (adj / line_range) * min_instr_len;
                    long line_inc = line_base + (adj % line_range);
                    regs.address += addr_inc;
                    regs.line += line_inc;
                    append();
                    continue loop;
                }

                //standard opcodes
                switch (cur) {
                case 1: //DW_LNS_copy
                    append();
                    continue loop;
                case 2: //DW_LNS_advance_pc
                    regs.address += rd.uleb128() * min_instr_len;
                    continue loop;
                case 3: //DW_LNS_advance_line
                    regs.line += rd.sleb128();
                    continue loop;
                case 4: //DW_LNS_set_file
                    regs.file =  cast(int)rd.uleb128();
                    continue loop;
                case 8: //DW_LNS_const_add_pc
                    //add address increment according to special opcode 255
                    //sorry logic duplicated from special opcode handling above
                    regs.address += ((255-opcode_base)/line_range)*min_instr_len;
                    continue loop;
                case 9: //DW_LNS_fixed_advance_pc
                    regs.address += rd.read!(uhalf)();
                    continue loop;
                default:
                }

                //"unknown"/unhandled standard opcode, skip
                if (cur != 0) {
                    //skip parameters
                    auto count = standard_opcode_lengths[cur-1];
                    while (count--) {
                        rd.uleb128();
                    }
                    continue loop;
                }

                //extended opcodes
                size_t instr_len =  cast(size_t)rd.uleb128(); //length of this instruction
                cur = rd.next();
                switch (cur) {
                case 1: //DW_LNE_end_sequence
                    regs.end_sequence = true;
                    append();
                    //reset
                    regs = LineRegs.init;
                    regs_prev = LineRegs.init;
                    continue loop;
                case 2: //DW_LNE_set_address
                    regs.address = rd.read!(size_t)();
                    continue loop;
                case 3: //DW_LNE_define_file
                    //can't handle this lol
                    //would need to append the file to the file table, but to avoid
                    //memory allocation, we don't copy out and store the normal file
                    //table; only a pointer to the original dwarf file entries
                    //solutions:
                    //  - give up and pre-parse debugging infos on program startup
                    //  - give up and allocate heap memory (but: signal handlers?)
                    //  - use alloca or a static array on the stack
                    dwarf_error("can't handle DW_LNE_define_file yet");
                    return false;
                default:
                }

                //unknown extended opcode, skip
                rd.read_pos += instr_len;
                continue loop;
            }

            //ensure correct start of next block (?)
            assert(rd.read_pos == end);
        }

        if (!found)
            return false;

        //resolve found_file to the actual filename & directory strings
        int dir;
        rd.read_pos = found_file.filenames;
        reparse_files((int idx, int a_dir, const(char)[] a_file) {
            if (idx == found_file.file) {
                dir = a_dir;
                out_file = a_file;
            }
        });
        rd.read_pos = found_file.directories;
        reparse_dirs((int idx, const(char)[] a_dir) {
            if (idx == dir) {
                out_directory = a_dir;
            }
        });

        return true;
    }

}

}