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