1 /**
2  *   Linux Stacktracing
3  *
4  *   Functions to parse the ELF format and create a symbolic trace.
5  *
6  *   The core Elf handling was taken from Thomas Kühne flectioned,
7  *   with some minor pieces taken from winterwar/wm4
8  *   But the routines and flow have been (sometime heavily) changed.
9  *
10  *  Copyright: Copyright (C) 2009 Fawzi, Thomas Kühne, wm4
11  *  License:   Tango License
12  *  Author:    Fawzi Mohamed
13  */
14 module tango.core.tools.LinuxStackTrace;
15 
16 import tango.core.tools.FrameInfo;
17 
18 version(TangoDoc)
19 {
20 
21 }
22 else
23 {
24 
25 version(D_Version2)
26 {
27         private void ThWriteOut(Throwable th, void delegate(in char[])sink){
28         if (th.file.length>0 || th.line!=0)
29         {
30             char[25]buf;
31             sink(th.classinfo.name);
32             sink("@");
33             sink(th.file);
34             sink("(");
35             sink(ulongToUtf8(buf, th.line));
36             sink("): ");
37             sink(th.toString());
38             sink("\n");
39         }
40         else
41         {
42            sink(th.classinfo.name);
43            sink(": ");
44            sink(th.toString());
45            sink("\n");
46         }
47         if (th.info)
48         {
49             sink("----------------\n");
50             th.info.opApply((ref const(char[]) msg){sink(msg); return 0;});
51         }
52         if (th.next){
53             sink("\n++++++++++++++++\n");
54             ThWriteOut(th, sink);
55         }
56     }
57 }
58 
59 
60 version(linux){
61     import tango.stdc.stdlib;
62     import tango.stdc.stdio : FILE, fopen, fread, fseek, fclose, SEEK_SET, fgets, sscanf;
63     import tango.stdc.string : strcmp, strlen,memcmp;
64     import tango.stdc.stringz : fromStringz;
65     import tango.stdc.signal;
66     import tango.stdc.errno: errno, EFAULT;
67     import tango.stdc.posix.unistd: access;
68     import tango.text.Util : delimit;
69     import tango.core.Array : find, rfind;
70     import tango.core.Runtime;
71 
72     class SymbolException:Exception {
73         this(immutable(char)[] msg, immutable(char)[] file,long lineNr,Exception next=null){
74             super(msg,file,cast(uint)lineNr,next);
75         }
76     }
77     bool may_read(size_t addr){
78         errno(0);
79         access(cast(char*)addr, 0);
80         return errno() != EFAULT;
81     }
82 
83     private extern(C){
84         alias ushort Elf32_Half;
85         alias ushort Elf64_Half;
86         alias uint Elf32_Word;
87         alias int Elf32_Sword;
88         alias uint Elf64_Word;
89         alias int Elf64_Sword;
90         alias ulong Elf32_Xword;
91         alias long Elf32_Sxword;
92         alias ulong Elf64_Xword;
93         alias long Elf64_Sxword;
94         alias uint Elf32_Addr;
95         alias ulong Elf64_Addr;
96         alias uint Elf32_Off;
97         alias ulong Elf64_Off;
98         alias ushort Elf32_Section;
99         alias ushort Elf64_Section;
100         alias Elf32_Half Elf32_Versym;
101         alias Elf64_Half Elf64_Versym;
102 
103         struct Elf32_Sym{
104             Elf32_Word  st_name;
105             Elf32_Addr  st_value;
106             Elf32_Word  st_size;
107             ubyte st_info;
108             ubyte st_other;
109             Elf32_Section   st_shndx;
110         }
111 
112         struct Elf64_Sym{
113             Elf64_Word  st_name;
114             ubyte       st_info;
115             ubyte       st_other;
116             Elf64_Section   st_shndx;
117             Elf64_Addr  st_value;
118             Elf64_Xword st_size;
119         }
120 
121         struct Elf32_Phdr{
122             Elf32_Word  p_type;
123             Elf32_Off   p_offset;
124             Elf32_Addr  p_vaddr;
125             Elf32_Addr  p_paddr;
126             Elf32_Word  p_filesz;
127             Elf32_Word  p_memsz;
128             Elf32_Word  p_flags;
129             Elf32_Word  p_align;
130         }
131 
132         struct Elf64_Phdr{
133             Elf64_Word  p_type;
134             Elf64_Word  p_flags;
135             Elf64_Off   p_offset;
136             Elf64_Addr  p_vaddr;
137             Elf64_Addr  p_paddr;
138             Elf64_Xword p_filesz;
139             Elf64_Xword p_memsz;
140             Elf64_Xword p_align;
141         }
142 
143         struct Elf32_Dyn{
144             Elf32_Sword d_tag;
145             union{
146                 Elf32_Word d_val;
147                 Elf32_Addr d_ptr;
148             }
149         }
150 
151         struct Elf64_Dyn{
152             Elf64_Sxword    d_tag;
153             union{
154                 Elf64_Xword d_val;
155                 Elf64_Addr d_ptr;
156             }
157         }
158         enum { EI_NIDENT = 16 }
159 
160         struct Elf32_Ehdr{
161             char[EI_NIDENT]    e_ident; /* Magic number and other info */
162             Elf32_Half  e_type;         /* Object file type */
163             Elf32_Half  e_machine;      /* Architecture */
164             Elf32_Word  e_version;      /* Object file version */
165             Elf32_Addr  e_entry;        /* Entry point virtual address */
166             Elf32_Off   e_phoff;        /* Program header table file offset */
167             Elf32_Off   e_shoff;        /* Section header table file offset */
168             Elf32_Word  e_flags;        /* Processor-specific flags */
169             Elf32_Half  e_ehsize;       /* ELF header size in bytes */
170             Elf32_Half  e_phentsize;        /* Program header table entry size */
171             Elf32_Half  e_phnum;        /* Program header table entry count */
172             Elf32_Half  e_shentsize;        /* Section header table entry size */
173             Elf32_Half  e_shnum;        /* Section header table entry count */
174             Elf32_Half  e_shstrndx;     /* Section header string table index */
175         }
176 
177         struct Elf64_Ehdr{
178             char[EI_NIDENT]    e_ident; /* Magic number and other info */
179             Elf64_Half  e_type;         /* Object file type */
180             Elf64_Half  e_machine;      /* Architecture */
181             Elf64_Word  e_version;      /* Object file version */
182             Elf64_Addr  e_entry;        /* Entry point virtual address */
183             Elf64_Off   e_phoff;        /* Program header table file offset */
184             Elf64_Off   e_shoff;        /* Section header table file offset */
185             Elf64_Word  e_flags;        /* Processor-specific flags */
186             Elf64_Half  e_ehsize;       /* ELF header size in bytes */
187             Elf64_Half  e_phentsize;        /* Program header table entry size */
188             Elf64_Half  e_phnum;        /* Program header table entry count */
189             Elf64_Half  e_shentsize;        /* Section header table entry size */
190             Elf64_Half  e_shnum;        /* Section header table entry count */
191             Elf64_Half  e_shstrndx;     /* Section header string table index */
192         }
193 
194         struct Elf32_Shdr{
195             Elf32_Word  sh_name;        /* Section name (string tbl index) */
196             Elf32_Word  sh_type;        /* Section type */
197             Elf32_Word  sh_flags;       /* Section flags */
198             Elf32_Addr  sh_addr;        /* Section virtual addr at execution */
199             Elf32_Off   sh_offset;      /* Section file offset */
200             Elf32_Word  sh_size;        /* Section size in bytes */
201             Elf32_Word  sh_link;        /* Link to another section */
202             Elf32_Word  sh_info;        /* Additional section information */
203             Elf32_Word  sh_addralign;       /* Section alignment */
204             Elf32_Word  sh_entsize;     /* Entry size if section holds table */
205         }
206 
207         struct Elf64_Shdr{
208             Elf64_Word  sh_name;        /* Section name (string tbl index) */
209             Elf64_Word  sh_type;        /* Section type */
210             Elf64_Xword sh_flags;       /* Section flags */
211             Elf64_Addr  sh_addr;        /* Section virtual addr at execution */
212             Elf64_Off   sh_offset;      /* Section file offset */
213             Elf64_Xword sh_size;        /* Section size in bytes */
214             Elf64_Word  sh_link;        /* Link to another section */
215             Elf64_Word  sh_info;        /* Additional section information */
216             Elf64_Xword sh_addralign;       /* Section alignment */
217             Elf64_Xword sh_entsize;     /* Entry size if section holds table */
218         }
219 
220         enum{
221             PT_DYNAMIC  = 2,
222             DT_STRTAB   = 5,
223             DT_SYMTAB   = 6,
224             DT_STRSZ    = 10,
225             DT_DEBUG    = 21,
226             SHT_SYMTAB  = 2,
227             SHT_STRTAB  = 3,
228             STB_LOCAL   = 0,
229         }
230 
231     }
232 
233     ubyte ELF32_ST_BIND(ulong info){
234         return  cast(ubyte)((info & 0xF0) >> 4);
235     }
236 
237     static if(4 == (void*).sizeof){
238         alias Elf32_Sym Elf_Sym;
239         alias Elf32_Dyn Elf_Dyn;
240         alias Elf32_Addr Elf_Addr;
241         alias Elf32_Phdr Elf_Phdr;
242         alias Elf32_Half Elf_Half;
243         alias Elf32_Ehdr Elf_Ehdr;
244         alias Elf32_Shdr Elf_Shdr;
245     }else static if(8 == (void*).sizeof){
246         alias Elf64_Sym Elf_Sym;
247         alias Elf64_Dyn Elf_Dyn;
248         alias Elf64_Addr Elf_Addr;
249         alias Elf64_Phdr Elf_Phdr;
250         alias Elf64_Half Elf_Half;
251         alias Elf64_Ehdr Elf_Ehdr;
252         alias Elf64_Shdr Elf_Shdr;
253     }else{
254         static assert(0);
255     }
256 
257     struct StaticSectionInfo{
258         Elf_Ehdr header;
259         const(char)[] stringTable;
260         Elf_Sym[] sym;
261         ubyte[] debugLine;   //contents of the .debug_line section, if available
262         const(char)[] fileName;
263         void* mmapBase;
264         size_t mmapLen;
265         /// initalizer
266         static StaticSectionInfo opCall(Elf_Ehdr header, const(char)[] stringTable, Elf_Sym[] sym,
267             ubyte[] debugLine, const(char)[] fileName, void* mmapBase=null, size_t mmapLen=0) {
268             StaticSectionInfo newV;
269             newV.header=header;
270             newV.stringTable=stringTable;
271             newV.sym=sym;
272             newV.debugLine = debugLine;
273             newV.fileName=fileName;
274             newV.mmapBase=mmapBase;
275             newV.mmapLen=mmapLen;
276             return newV;
277         }
278 
279         // stores the global sections
280         enum MAX_SECTS=5;
281         static StaticSectionInfo[MAX_SECTS] _gSections;
282         static size_t _nGSections,_nFileBuf;
283         static char[MAX_SECTS*256] _fileNameBuf;
284 
285         /// loops on the global sections
286         static int opApply(scope int delegate(ref StaticSectionInfo) loop){
287             for (size_t i=0;i<_nGSections;++i){
288                 auto res=loop(_gSections[i]);
289                 if (res) return res;
290             }
291             return 0;
292         }
293         /// loops on the static symbols
294         static int opApply(scope int delegate(ref const(char)[] sNameP,ref size_t startAddr,
295             ref size_t endAddr, ref bool pub) loop){
296             for (size_t isect=0;isect<_nGSections;++isect){
297                 StaticSectionInfo *sec=&(_gSections[isect]);
298                 for (size_t isym=0;isym<sec.sym.length;++isym) {
299                     auto symb=sec.sym[isym];
300                     if(!symb.st_name || !symb.st_value){
301                         // anonymous || undefined
302                         continue;
303                     }
304 
305                     bool isPublic = true;
306                     if(STB_LOCAL == ELF32_ST_BIND(symb.st_info)){
307                         isPublic = false;
308                     }
309                     const(char) *sName;
310                     if (symb.st_name<sec.stringTable.length) {
311                         sName=&(sec.stringTable[symb.st_name]);
312                     } else {
313                         debug(elf) printf("symbol name out of bounds %p\n",symb.st_value);
314                     }
315                     const(char)[] symbName=sName[0..(sName?strlen(sName):0)];
316                     size_t endAddr=symb.st_value+symb.st_size;
317                     auto res=loop(symbName,symb.st_value,endAddr,isPublic);
318                     if (res) return res;
319                 }
320             }
321             return 0;
322         }
323         /// returns a new section to fill out
324         static StaticSectionInfo *addGSection(Elf_Ehdr header,const(char)[] stringTable, Elf_Sym[] sym,
325             ubyte[] debugLine, const(char)[] fileName,void *mmapBase=null, size_t mmapLen=0){
326             if (_nGSections>=MAX_SECTS){
327                 throw new Exception("too many static sections",__FILE__,__LINE__);
328             }
329             auto len=fileName.length;
330             const(char)[] newFileName;
331             if (_fileNameBuf.length< _nFileBuf+len) {
332                 newFileName=fileName[0..len].dup;
333             } else {
334                 _fileNameBuf[_nFileBuf.._nFileBuf+len]=fileName[0..len];
335                 newFileName=_fileNameBuf[_nFileBuf.._nFileBuf+len];
336                 _nFileBuf+=len;
337             }
338             _gSections[_nGSections]=StaticSectionInfo(header,stringTable,sym,debugLine,newFileName,
339                                                       mmapBase,mmapLen);
340             _nGSections++;
341             return &(_gSections[_nGSections-1]);
342         }
343 
344         static void resolveLineNumber(ref FrameInfo info) {
345             foreach (ref section; _gSections[0.._nGSections]) {
346                 //dwarf stores the directory component of filenames separately
347                 //dmd doesn't care, and directory components are in the filename
348                 //linked in gcc produced files still use them
349                 const(char)[] dir;
350                 //assumption: if exactAddress=false, it's a return address
351                 if (find_line_number(section.debugLine, info.address, !info.exactAddress, dir, info.file, info.line))
352                     break;
353             }
354         }
355     }
356 
357     private void scan_static(in char *file){
358         // should try to use mmap,for this reason the "original" format is kept
359         // if copying (as now) one could discard the unused strings, and pack the symbols in
360         // a platform independent format, but the mmap approach is probably better
361         /+auto fdesc=open(file,O_RDONLY);
362         ptr_diff_t some_offset=0;
363         size_t len=lseek(fdesc,0,SEEK_END);
364         lseek(fdesc,0,SEEK_SET);
365         address = mmap(0, len, PROT_READ, MAP_PRIVATE, fdesc, some_offset);+/
366         FILE * fd=fopen(file,"r");
367         bool first_symbol = true;
368         Elf_Ehdr header;
369         Elf_Shdr section;
370         Elf_Sym sym;
371 
372         void read(void* ptr, size_t size){
373             auto readB=fread(ptr, 1, size,fd);
374             if(readB != size){
375                 throw new SymbolException("read failure in file "~file[0..strlen(file)].idup,__FILE__,__LINE__);
376             }
377         }
378 
379         void seek(ptrdiff_t offset){
380             if(fseek(fd, offset, SEEK_SET) == -1){
381                 throw new SymbolException("seek failure",__FILE__,__LINE__);
382             }
383         }
384 
385         /* read elf header */
386         read(&header, header.sizeof);
387         if(header.e_shoff == 0){
388             return;
389         }
390         const bool useShAddr=false;
391         char[] sectionStrs;
392         for(ptrdiff_t i = header.e_shnum - 1; i > -1; i--){
393             seek(header.e_shoff + i * header.e_shentsize);
394             read(&section, section.sizeof);
395             debug(none) printf("[%i] %i\n", i, section.sh_type);
396 
397             if (section.sh_type == SHT_STRTAB) {
398                 /* read string table */
399                 debug(elf) printf("looking for .shstrtab, [%i] is STRING (size:%i)\n", i, section.sh_size);
400                 seek(section.sh_offset);
401                 if (section.sh_name<section.sh_size) {
402                     if (useShAddr && section.sh_addr) {
403                         if (!may_read(cast(size_t)section.sh_addr)){
404                             Runtime.console.stderr("section '");
405                             Runtime.console.stderr(i);
406                             Runtime.console.stderr("' has invalid address, relocated?\n");
407                         } else {
408                             sectionStrs=(cast(char*)section.sh_addr)[0..section.sh_size];
409                         }
410                     }
411                     sectionStrs.length = section.sh_size;
412                     read(sectionStrs.ptr, sectionStrs.length);
413                     char* p=&(sectionStrs[section.sh_name]);
414                     if (strcmp(p,".shstrtab".ptr)==0) break;
415                 }
416             }
417         }
418         if (sectionStrs) {
419             char* p=&(sectionStrs[section.sh_name]);
420             if (strcmp(p,".shstrtab".ptr)!=0) {
421                 sectionStrs="\0".dup;
422             } else {
423                 debug(elf) printf("found .shstrtab\n");
424             }
425         } else {
426             sectionStrs="\0".dup;
427         }
428 
429 
430         /* find sections */
431         char[] string_table;
432         Elf_Sym[] symbs;
433         ubyte[] debug_line;
434         for(ptrdiff_t i = header.e_shnum - 1; i > -1; i--){
435             seek(header.e_shoff + i * header.e_shentsize);
436             read(&section, section.sizeof);
437             debug(none) printf("[%i] %i\n", i, section.sh_type);
438 
439             if (section.sh_name>=sectionStrs.length) {
440                 Runtime.console.stderr("could not find name for ELF section at ");
441                 Runtime.console.stderr(section.sh_name);
442                 Runtime.console.stderr("\n");
443                 continue;
444             }
445             debug(elf) printf("Elf section %s\n",sectionStrs.ptr+section.sh_name);
446             if (section.sh_type == SHT_STRTAB && !string_table) {
447                 /* read string table */
448                 debug(elf) printf("[%i] is STRING (size:%i)\n", i, section.sh_size);
449                 if  (strcmp(sectionStrs.ptr+section.sh_name,".strtab")==0){
450                     seek(section.sh_offset);
451                     if (useShAddr && section.sh_addr){
452                         if (!may_read(cast(size_t)section.sh_addr)){
453                             Runtime.console.stderr("section '");
454                             Runtime.console.stderr(fromStringz(&(sectionStrs[section.sh_name])));
455                             Runtime.console.stderr("' has invalid address, relocated?\n");
456                         } else {
457                             string_table=(cast(char*)section.sh_addr)[0..section.sh_size];
458                         }
459                     } else {
460                         string_table.length = section.sh_size;
461                         read(string_table.ptr, string_table.length);
462                     }
463                 }
464             } else if(section.sh_type == SHT_SYMTAB) {
465                 /* read symtab */
466                 debug(elf) printf("[%i] is SYMTAB (size:%i)\n", i, section.sh_size);
467                 if (strcmp(sectionStrs.ptr+section.sh_name,".symtab")==0 && !symbs) {
468                     if (useShAddr && section.sh_addr){
469                         if (!may_read(cast(size_t)section.sh_addr)){
470                             Runtime.console.stderr("section '");
471                             Runtime.console.stderr(fromStringz(&(sectionStrs[section.sh_name])));
472                             Runtime.console.stderr("' has invalid address, relocated?\n");
473                         } else {
474                             symbs=(cast(Elf_Sym*)section.sh_addr)[0..section.sh_size/Elf_Sym.sizeof];
475                         }
476                     } else {
477                         if(section.sh_offset == 0){
478                             continue;
479                         }
480                         auto p=malloc(section.sh_size);
481                         if (p is null)
482                             throw new Exception("failed alloc",__FILE__,__LINE__);
483                         symbs=(cast(Elf_Sym*)p)[0..section.sh_size/Elf_Sym.sizeof];
484                         seek(section.sh_offset);
485                         read(symbs.ptr,symbs.length*Elf_Sym.sizeof);
486                     }
487                 }
488             } else if (strcmp(sectionStrs.ptr+section.sh_name,".debug_line")==0 && !debug_line) {
489                 seek(section.sh_offset);
490                 if (useShAddr && section.sh_addr){
491                     if (!may_read(cast(size_t)section.sh_addr)){
492                         Runtime.console.stderr("section '");
493                         Runtime.console.stderr(fromStringz(&(sectionStrs[section.sh_name])));
494                         Runtime.console.stderr("' has invalid address, relocated?\n");
495                     } else {
496                         debug_line=(cast(ubyte*)section.sh_addr)[0..section.sh_size];
497                     }
498                 } else {
499                     auto p=malloc(section.sh_size);
500                     if (p is null)
501                         throw new Exception("failed alloc",__FILE__,__LINE__);
502                     debug_line=(cast(ubyte*)p)[0..section.sh_size];
503                     seek(section.sh_offset);
504                     read(debug_line.ptr,debug_line.length);
505                 }
506             }
507         }
508 
509         if (string_table.ptr && symbs.ptr) {
510             StaticSectionInfo.addGSection(header,string_table,symbs,debug_line,file[0..strlen(file)]);
511             string_table=null;
512             symbs=null;
513             debug_line=null;
514         }
515     }
516 
517     private void find_symbols(){
518         // static symbols
519         find_static();
520         // dynamic symbols handled with dladdr
521     }
522 
523     private void find_static(){
524         FILE* maps;
525         char[4096] buffer;
526 
527         maps = fopen("/proc/self/maps", "r");
528         if(maps is null){
529             debug{
530                 throw new SymbolException("couldn't read '/proc/self/maps'",__FILE__,__LINE__);
531             }else{
532                 return;
533             }
534         }
535         scope(exit) fclose(maps);
536 
537         buffer[] = 0;
538         while(fgets(buffer.ptr, buffer.length - 1, maps)){
539             scope(exit){
540                 buffer[] = 0;
541             }
542             const(char)[] tmp;
543             cleanEnd: for(size_t i = buffer.length - 1; i >= 0; i--){
544                 switch(buffer[i]){
545                     case 0, '\r', '\n':
546                         buffer[i] = 0;
547                         break;
548                     default:
549                         tmp = buffer[0 .. i+1];
550                         break cleanEnd;
551                 }
552             }
553 
554 Lsplit:
555             static if(is(typeof(split(""c)) == string[])){
556                 string[] tok = split(tmp);
557                 if(tok.length != 6){
558                     // no source file
559                     continue;
560                 }
561             }else{
562                 const(char)[][] tok = delimit(tmp, " \t");
563                 if(tok.length < 6){
564                     // no source file
565                     continue;
566                 }
567                 const tok_len = 33;
568             }
569             if(find(tok[$-1], "[") == 0){
570                 // pseudo source
571                 continue;
572             }
573             if(rfind(tok[$-1], ".so") == tok[$-1].length - 3){
574                 // dynamic lib
575                 continue;
576             }
577             if(rfind(tok[$-1], ".so.") != tok[$-1].length ){
578                 // dynamic lib
579                 continue;
580             }
581             if(find(tok[1], "r") == -1){
582                 // no read
583                 continue;
584             }
585             if(find(tok[1], "x") == -1){
586                 // no execute
587                 continue;
588             }
589             const(char)[] addr = tok[0] ~ "\u0000";
590             const(char)[] source = tok[$-1] ~ "\u0000";
591             __gshared immutable immutable(char)[] marker = "\x7FELF"c;
592 
593             void* start, end;
594             if(2 != sscanf(addr.ptr, "%zX-%zX", &start, &end)){
595                 continue;
596             }
597             if(cast(size_t)end - cast(size_t)start < 4){
598                 continue;
599             }
600             if(!may_read(cast(size_t)start)){
601                 Runtime.console.stderr("got invalid start ptr from '");
602                 Runtime.console.stderr(fromStringz(source.ptr));
603                 Runtime.console.stderr("'\n");
604                 Runtime.console.stderr("ignoring error in ");
605                 Runtime.console.stderr(__FILE__);
606                 Runtime.console.stderr(":");
607                 Runtime.console.stderr(__FILE__);
608                 Runtime.console.stderr("\n");
609                 return;
610             }
611             if(memcmp(start, marker.ptr, marker.length) != 0){
612                 // not an ELF file
613                 continue;
614             }
615             try{
616                 scan_static(source.ptr);
617                 debug(elfTable){
618                     printf("XX symbols\n");
619                     foreach(sName,startAddr,endAddr,pub;StaticSectionInfo){
620                         printf("%p %p %d %*s\n",startAddr,endAddr,pub,sName.length,sName.ptr);
621                     }
622                     printf("XX symbols end\n");
623                 }
624             } catch (Exception e) {
625                 Runtime.console.stderr("failed reading symbols from '");
626                 Runtime.console.stderr(fromStringz(source.ptr));
627                 Runtime.console.stderr("'\n");
628                 Runtime.console.stderr("ignoring error in ");
629                 Runtime.console.stderr(__FILE__);
630                 Runtime.console.stderr(":");
631                 Runtime.console.stderr(__FILE__);
632                 Runtime.console.stderr("\n");
633                 ThWriteOut(e, (in char[] s){ Runtime.console.stderr(s); });
634                 return;
635             }
636                 
637         }
638     }
639 
640     shared static this() {
641         find_symbols();
642     }
643 
644 
645     private void dwarf_error(const(char)[] msg) {
646         Runtime.console.stderr("Tango stacktracer DWARF error: ");
647         Runtime.console.stderr(msg);
648         Runtime.console.stderr("\n");
649     }
650 
651     alias short uhalf;
652 
653     struct DwarfReader {
654         ubyte[] data;
655         size_t read_pos;
656         bool is_dwarf_64;
657 
658         @property size_t left() {
659             return data.length - read_pos;
660         }
661 
662         @property ubyte next() {
663             ubyte r = data[read_pos];
664             read_pos++;
665             return r;
666         }
667 
668         //read the length field, and set the is_dwarf_64 flag accordingly
669         //return 0 on error
670         size_t read_initial_length() {
671             //64 bit applications normally use 32 bit DWARF information
672             //this means on 64 bit, we have to handle both 32 bit and 64 bit infos
673             //the 64 bit version seems to be rare, though
674             //independent from this, 32 bit DWARF still uses some 64 bit types in
675             //64 bit executables (at least the DW_LNE_set_address opcode does)
676             auto initlen = read!(uint)();
677             is_dwarf_64 = (initlen == 0xff_ff_ff_ff);
678             if (is_dwarf_64) {
679                 //--can handle this, but need testing (this format seems to be uncommon)
680                 //--remove the following 2 lines to see if it works, and fix the code if needed
681                 dwarf_error("dwarf 64 detected, aborting");
682                 abort();
683                 //--
684                 static if (size_t.sizeof > 4) {
685                     dwarf_error("64 bit DWARF in a 32 bit excecutable?");
686                     return 0;
687                 }
688                 else return cast(size_t)read!(ulong)();
689             } else {
690                 if (initlen >= 0xff_ff_ff_00) {
691                     //see dwarf spec 7.5.1
692                     dwarf_error("corrupt debugging information?");
693                 }
694                 return initlen;
695             }
696         }
697 
698         //adapted from example code in dwarf spec. appendix c
699         //defined max. size is 128 bit; we provide up to 64 bit
700         private ulong do_read_leb(bool sign_ext) {
701             ulong res;
702             int shift;
703             ubyte b;
704             do {
705                 b = next();
706                 res = res | ((b & 0x7f) << shift);
707                 shift += 7;
708             } while (b & 0x80);
709             if (sign_ext && shift < ulong.sizeof*8 && (b & 0x40))
710                 res = res - (1L << shift);
711             return res;
712         }
713         ulong uleb128() {
714             return do_read_leb(false);
715         }
716         long sleb128() {
717             return do_read_leb(true);
718         }
719 
720         T read(T)() {
721             T r = *cast(T*)data[read_pos..read_pos+T.sizeof].ptr;
722             read_pos += T.sizeof;
723             return r;
724         }
725 
726         size_t read_header_length() {
727             if (is_dwarf_64) {
728                 return cast(size_t)read!(ulong)();
729             } else {
730                 return cast(size_t)read!(uint)();
731             }
732         }
733 
734         //null terminated string
735         const(char)[] str() {
736             char* start = cast(char*)&data[read_pos];
737             size_t len = strlen(start);
738             read_pos += len + 1;
739             return start[0..len];
740         }
741     }
742 
743     unittest {
744         //examples from dwarf spec section 7.6
745         ubyte[] bytes = [2,127,0x80,1,0x81,1,0x82,1,57+0x80,100,2,0x7e,127+0x80,0,
746             0x81,0x7f,0x80,1,0x80,0x7f,0x81,1,0x7f+0x80,0x7e];
747         ulong[] u = [2, 127, 128, 129, 130, 12857];
748         long[] s = [2, -2, 127, -127, 128, -128, 129, -129];
749         auto rd = DwarfReader(bytes);
750         foreach (x; u)
751             assert(rd.uleb128() == x);
752         foreach (x; s)
753             assert(rd.sleb128() == x);
754     }
755 
756     //debug_line = contents of the .debug_line section
757     //is_return_address = true if address is a return address (found by stacktrace)
758     bool find_line_number(ubyte[] debug_line, size_t address, bool is_return_address,
759         ref const(char)[] out_directory, ref const(char)[] out_file, ref long out_line)
760     {
761         DwarfReader rd = DwarfReader(debug_line);
762 
763 
764         //NOTE:
765         //  - instead of saving the filenames when the debug infos are first parsed,
766         //    we only save a reference to the debug infos (with FileRef), and
767         //    reparse the debug infos when we need the actual filenames
768         //  - the same code is used for skipping over the debug infos, and for
769         //    getting the filenames later
770         //  - this is just for avoiding memory allocation
771 
772         struct FileRef {
773             int file;           //file number
774             size_t directories; //offset to directory info
775             size_t filenames;   //offset to filename info
776         }
777 
778         //include_directories
779         void reparse_dirs(void delegate(int idx, const(char)[] d) entry) {
780             int idx = 1;
781             for (;;) {
782                 auto s = rd.str();
783                 if (!s.length)
784                     break;
785                 if (entry)
786                     entry(idx, s);
787                 idx++;
788             }
789         }
790         //file_names
791         void reparse_files(void delegate(int idx, int dir, const(char)[] fn) entry) {
792             int idx = 1;
793             for (;;) {
794                 auto s = rd.str();
795                 if (!s.length)
796                     break;
797                 int dir = cast(int)rd.uleb128(); //directory index
798                 rd.uleb128();           //last modification time (unused)
799                 rd.uleb128();           //length of file (unused)
800                 if (entry)
801                     entry(idx, dir, s);
802                 idx++;
803             }
804         }
805 
806         //associated with the found entry
807         FileRef found_file;
808         bool found = false;
809 
810         //the section is made up of independent blocks of line number programs
811         blocks: while (rd.left > 0) {
812             size_t unit_length = rd.read_initial_length();
813 
814             if (unit_length == 0)
815                 return false;
816 
817             size_t start = rd.read_pos;
818             size_t end = start + unit_length;
819 
820             auto ver = rd.read!(uhalf)();
821             auto header_length = rd.read_header_length();
822 
823             size_t header_start = rd.read_pos;
824 
825             auto min_instr_len = rd.read!(ubyte)();
826             auto def_is_stmt = rd.read!(ubyte)();
827             auto line_base = rd.read!(byte)();
828             auto line_range = rd.read!(ubyte)();
829             auto opcode_base = rd.read!(ubyte)();
830             ubyte[256] sol_store; //to avoid heap allocation
831             ubyte[] standard_opcode_lengths = sol_store[0..opcode_base-1];
832             foreach (ref x; standard_opcode_lengths) {
833                 x = rd.read!(ubyte)();
834             }
835 
836             size_t dirs_offset = rd.read_pos;
837             reparse_dirs(null);
838             size_t files_offset = rd.read_pos;
839             reparse_files(null);
840 
841             rd.read_pos = header_start + header_length;
842 
843             //state machine registers
844             struct LineRegs {
845                 bool valid() { return address != 0; }
846                 int file = 1;               //file index
847                 int line = 1;               //line number
848                 size_t address = 0;         //absolute address
849                 bool end_sequence = false;  //last row in a block
850             }
851 
852             LineRegs regs;      //current row
853             LineRegs regs_prev; //row before
854 
855             //append row to virtual line number table, using current register contents
856             //NOTE: reg_address is supposed to be increased only (within  a block)
857             //      reg_line can be increased or decreased randomly
858             void append() {
859                 if (regs_prev.valid()) {
860                     if (is_return_address) {
861                         if (address >= regs_prev.address && address <= regs.address)
862                             found = true;
863                     } else {
864                         //some special case *shrug*
865                         if (regs_prev.address == address)
866                             found = true;
867                         //not special case
868                         if (address >= regs_prev.address && address < regs.address)
869                             found = true;
870                     }
871 
872                     if (found) {
873                         out_line = regs_prev.line;
874                         found_file.file = regs_prev.file;
875                         found_file.directories = dirs_offset;
876                         found_file.filenames = files_offset;
877                     }
878                 }
879 
880                 regs_prev = regs;
881             }
882 
883             //actual line number program
884             loop: while (rd.read_pos < end) {
885                 ubyte cur = rd.next();
886 
887                 if (found)
888                     break blocks;
889 
890                 //"special opcodes"
891                 if (cur >= opcode_base) {
892                     int adj = cur - opcode_base;
893                     long addr_inc = (adj / line_range) * min_instr_len;
894                     long line_inc = line_base + (adj % line_range);
895                     regs.address += addr_inc;
896                     regs.line += line_inc;
897                     append();
898                     continue loop;
899                 }
900 
901                 //standard opcodes
902                 switch (cur) {
903                 case 1: //DW_LNS_copy
904                     append();
905                     continue loop;
906                 case 2: //DW_LNS_advance_pc
907                     regs.address += rd.uleb128() * min_instr_len;
908                     continue loop;
909                 case 3: //DW_LNS_advance_line
910                     regs.line += rd.sleb128();
911                     continue loop;
912                 case 4: //DW_LNS_set_file
913                     regs.file =  cast(int)rd.uleb128();
914                     continue loop;
915                 case 8: //DW_LNS_const_add_pc
916                     //add address increment according to special opcode 255
917                     //sorry logic duplicated from special opcode handling above
918                     regs.address += ((255-opcode_base)/line_range)*min_instr_len;
919                     continue loop;
920                 case 9: //DW_LNS_fixed_advance_pc
921                     regs.address += rd.read!(uhalf)();
922                     continue loop;
923                 default:
924                 }
925 
926                 //"unknown"/unhandled standard opcode, skip
927                 if (cur != 0) {
928                     //skip parameters
929                     auto count = standard_opcode_lengths[cur-1];
930                     while (count--) {
931                         rd.uleb128();
932                     }
933                     continue loop;
934                 }
935 
936                 //extended opcodes
937                 size_t instr_len =  cast(size_t)rd.uleb128(); //length of this instruction
938                 cur = rd.next();
939                 switch (cur) {
940                 case 1: //DW_LNE_end_sequence
941                     regs.end_sequence = true;
942                     append();
943                     //reset
944                     regs = LineRegs.init;
945                     regs_prev = LineRegs.init;
946                     continue loop;
947                 case 2: //DW_LNE_set_address
948                     regs.address = rd.read!(size_t)();
949                     continue loop;
950                 case 3: //DW_LNE_define_file
951                     //can't handle this lol
952                     //would need to append the file to the file table, but to avoid
953                     //memory allocation, we don't copy out and store the normal file
954                     //table; only a pointer to the original dwarf file entries
955                     //solutions:
956                     //  - give up and pre-parse debugging infos on program startup
957                     //  - give up and allocate heap memory (but: signal handlers?)
958                     //  - use alloca or a static array on the stack
959                     dwarf_error("can't handle DW_LNE_define_file yet");
960                     return false;
961                 default:
962                 }
963 
964                 //unknown extended opcode, skip
965                 rd.read_pos += instr_len;
966                 continue loop;
967             }
968 
969             //ensure correct start of next block (?)
970             assert(rd.read_pos == end);
971         }
972 
973         if (!found)
974             return false;
975 
976         //resolve found_file to the actual filename & directory strings
977         int dir;
978         rd.read_pos = found_file.filenames;
979         reparse_files((int idx, int a_dir, const(char)[] a_file) {
980             if (idx == found_file.file) {
981                 dir = a_dir;
982                 out_file = a_file;
983             }
984         });
985         rd.read_pos = found_file.directories;
986         reparse_dirs((int idx, const(char)[] a_dir) {
987             if (idx == dir) {
988                 out_directory = a_dir;
989             }
990         });
991 
992         return true;
993     }
994 
995 }
996 
997 }