| 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(§ion, 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(§ion, 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; } } } |