| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551 | /** * 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 someone whose name I forgot * and that should announce himself to get credit. * But the routines and flow have been (sometime heavily) changed * * Copyright: 2009 Fawzi, Thomas Kühne * License: tango license * Authors: Fawzi Mohamed */ module tango.core.tools.LinuxStackTrace; version(linux){ import tango.stdc.stdlib; import tango.stdc.stdio; import tango.stdc.string : strcmp, strlen,memcmp; 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; class SymbolException:Exception { this(char[]msg,char[]file,long lineNr,Exception next=null){ super(msg,file,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; } } const 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; char[] stringTable; Elf_Sym[] sym; char[] fileName; void* mmapBase; size_t mmapLen; /// initalizer static StaticSectionInfo opCall(Elf_Ehdr header, char[] stringTable, Elf_Sym[] sym, char[] fileName, void* mmapBase=null, size_t mmapLen=0) { StaticSectionInfo newV; newV.header=header; newV.stringTable=stringTable; newV.sym=sym; 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(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(int delegate(ref 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; } 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); } 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,char[] stringTable, Elf_Sym[] sym, 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; 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,newFileName, mmapBase,mmapLen); _nGSections++; return &(_gSections[_nGSections-1]); } } private void scan_static(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)],__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)){ fprintf(stderr,"section '%d' has invalid address, relocated?\n",i); } 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")==0) break; } } } if (sectionStrs) { char* p=&(sectionStrs[section.sh_name]); if (strcmp(p,".shstrtab")!=0) { sectionStrs="\0"; } else { debug(elf) printf("found .shstrtab\n"); } } else { sectionStrs="\0"; } /* find sections */ char[] string_table; Elf_Sym[] symbs; 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) { fprintf(stderr,"could not find name for ELF section at %d\n", section.sh_name); 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)){ fprintf(stderr,"section '%s' has invalid address, relocated?\n", &(sectionStrs[section.sh_name])); } 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)){ fprintf(stderr,"section '%s' has invalid address, relocated?\n", &(sectionStrs[section.sh_name])); } 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); } } } if (string_table && symbs) { StaticSectionInfo.addGSection(header,string_table,symbs,file[0..strlen(file)]); string_table=null; symbs=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; } 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{ 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; } char[] addr = tok[0] ~ "\u0000"; char[] source = tok[$-1] ~ "\u0000"; const 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)){ fprintf(stderr, "got invalid start ptr from %s\n",source.ptr); fprintf(stderr, "ignoring error in %*s:%ld\n",__FILE__.length,__FILE__.ptr,__LINE__); 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) { fprintf(stderr, "failed reading symbols from %s\n", source.ptr); fprintf(stderr, "ignoring error in %*s:%ld\n",__FILE__.length,__FILE__.ptr,__LINE__); e.writeOut((char[]s){ fprintf(stderr,"%*s",s.length,s.ptr); fflush(stderr); }); return; } } } static this() { find_symbols(); } } |