From 8822115bc8d52fa61c15cef38fe77349f18747b9 Mon Sep 17 00:00:00 2001 From: Yazan Dabain Date: Fri, 21 Aug 2015 13:42:11 +0300 Subject: [PATCH 1/3] Adjust stacktrace to point at CALL instruction instead of return address --- src/core/runtime.d | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/runtime.d b/src/core/runtime.d index 986b17b870..5313dd6113 100644 --- a/src/core/runtime.d +++ b/src/core/runtime.d @@ -492,7 +492,9 @@ Throwable.TraceInfo defaultTraceHandler( void* ptr = null ) stackPtr < stackBottom && numframes < MAXFRAMES; ) { - callstack[numframes++] = *(stackPtr + 1); + enum CALL_INSTRUCTION_SIZE = 1; // it may not be 1 but it is good enough to get + // in CALL instruction address range for backtrace + callstack[numframes++] = *(stackPtr + 1) - CALL_INSTRUCTION_SIZE; stackPtr = cast(void**) *stackPtr; } } From 068be2edaa507d0e76f4e844c97e069e373951f6 Mon Sep 17 00:00:00 2001 From: Yazan Dabain Date: Fri, 21 Aug 2015 13:43:12 +0300 Subject: [PATCH 2/3] Add dwarf address to line resolver using .debug_line --- mak/SRCS | 3 + src/core/runtime.d | 59 ++-- src/rt/backtrace/dwarf.d | 483 +++++++++++++++++++++++++++++++ src/rt/backtrace/elf.d | 200 +++++++++++++ src/rt/util/container/common.d | 2 +- test/exceptions/Makefile | 13 + test/exceptions/line_trace.exp | 9 + test/exceptions/src/line_trace.d | 18 ++ 8 files changed, 769 insertions(+), 18 deletions(-) create mode 100644 src/rt/backtrace/dwarf.d create mode 100644 src/rt/backtrace/elf.d create mode 100644 test/exceptions/line_trace.exp create mode 100644 test/exceptions/src/line_trace.d diff --git a/mak/SRCS b/mak/SRCS index 834c094ec4..523b0bb674 100644 --- a/mak/SRCS +++ b/mak/SRCS @@ -129,6 +129,9 @@ SRCS=\ src\rt\trace.d \ src\rt\tracegc.d \ \ + src\rt\backtrace\dwarf.d \ + src\rt\backtrace\elf.d \ + \ src\rt\util\array.d \ src\rt\util\hash.d \ src\rt\util\random.d \ diff --git a/src/core/runtime.d b/src/core/runtime.d index 5313dd6113..0d1a4ab1f0 100644 --- a/src/core/runtime.d +++ b/src/core/runtime.d @@ -511,39 +511,64 @@ Throwable.TraceInfo defaultTraceHandler( void* ptr = null ) override int opApply( scope int delegate(ref size_t, ref const(char[])) dg ) const { - version( Posix ) + version(Posix) { - // NOTE: The first 5 frames with the current implementation are + // NOTE: The first 4 frames with the current implementation are // inside core.runtime and the object code, so eliminate // these for readability. The alternative would be to // exclude the first N frames that are in a list of // mangled function names. - static enum FIRSTFRAME = 5; + enum FIRSTFRAME = 4; } - else version( Windows ) + else version(Windows) { // NOTE: On Windows, the number of frames to exclude is based on // whether the exception is user or system-generated, so // it may be necessary to exclude a list of function names // instead. - static enum FIRSTFRAME = 0; + enum FIRSTFRAME = 0; } - int ret = 0; - const framelist = backtrace_symbols( callstack.ptr, numframes ); - scope(exit) free(cast(void*) framelist); + version(linux) + { + import core.internal.traits : externDFunc; + + alias traceHandlerOpApplyImpl = externDFunc!( + "rt.backtrace.dwarf.traceHandlerOpApplyImpl", + int function(const void*[], scope int delegate(ref size_t, ref const(char[]))) + ); - for( int i = FIRSTFRAME; i < numframes; ++i ) + if (numframes >= FIRSTFRAME) + { + return traceHandlerOpApplyImpl( + callstack[FIRSTFRAME .. numframes], + dg + ); + } + else + { + return 0; + } + } + else { - char[4096] fixbuf; - auto buf = framelist[i][0 .. strlen(framelist[i])]; - auto pos = cast(size_t)(i - FIRSTFRAME); - buf = fixline( buf, fixbuf ); - ret = dg( pos, buf ); - if( ret ) - break; + const framelist = backtrace_symbols( callstack.ptr, numframes ); + scope(exit) free(cast(void*) framelist); + + int ret = 0; + for( int i = FIRSTFRAME; i < numframes; ++i ) + { + char[4096] fixbuf; + auto buf = framelist[i][0 .. strlen(framelist[i])]; + auto pos = cast(size_t)(i - FIRSTFRAME); + buf = fixline( buf, fixbuf ); + ret = dg( pos, buf ); + if( ret ) + break; + } + return ret; } - return ret; + } override string toString() const diff --git a/src/rt/backtrace/dwarf.d b/src/rt/backtrace/dwarf.d new file mode 100644 index 0000000000..4a4d6292e5 --- /dev/null +++ b/src/rt/backtrace/dwarf.d @@ -0,0 +1,483 @@ +/** + * This code handles backtrace generation using dwarf .debug_line section + * in ELF files for linux. + * + * Reference: http://www.dwarfstd.org/ + * + * Copyright: Copyright Digital Mars 2015 - 2015. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Yazan Dabain, Sean Kelly + * Source: $(DRUNTIMESRC src/rt/backtrace/dwarf.d) + */ + +module rt.backtrace.dwarf; + +version(linux): + +import rt.util.container.array; +import rt.backtrace.elf; + +import core.stdc.string : strlen, memchr; + +struct Location +{ + const(char)[] file = null; // file is missing directory, but DMD emits directory directly into file + int line = -1; + size_t address; +} + +int traceHandlerOpApplyImpl(const void*[] callstack, scope int delegate(ref size_t, ref const(char[])) dg) +{ + import core.stdc.stdio : snprintf; + import core.sys.linux.execinfo : backtrace_symbols; + import core.sys.posix.stdlib : free; + + const char** frameList = backtrace_symbols(callstack.ptr, cast(int) callstack.length); + scope(exit) free(cast(void*) frameList); + + // find address -> file, line mapping using dwarf debug_line + ElfFile file; + ElfSection dbgSection; + Array!Location locations; + if (ElfFile.openSelf(&file)) + { + auto stringSectionHeader = ElfSectionHeader(&file, file.ehdr.e_shstrndx); + auto stringSection = ElfSection(&file, &stringSectionHeader); + + auto dbgSectionIndex = findSectionByName(&file, &stringSection, ".debug_line"); + if (dbgSectionIndex != -1) + { + auto dbgSectionHeader = ElfSectionHeader(&file, dbgSectionIndex); + dbgSection = ElfSection(&file, &dbgSectionHeader); + // debug_line section found and loaded + + // resolve addresses + locations.length = callstack.length; + foreach(size_t i; 0 .. callstack.length) + locations[i].address = cast(size_t) callstack[i]; + + resolveAddresses(&dbgSection, locations[]); + } + } + + int ret = 0; + foreach (size_t i; 0 .. callstack.length) + { + char[1536] buffer = void; buffer[0] = 0; + char[256] addressBuffer = void; addressBuffer[0] = 0; + + if (locations.length > 0 && locations[i].line != -1) + snprintf(addressBuffer.ptr, addressBuffer.length, "%.*s:%d ", cast(int) locations[i].file.length, locations[i].file.ptr, locations[i].line); + else + addressBuffer[] = "??:? \0"; + + char[1024] symbolBuffer = void; + int bufferLength; + auto symbol = getDemangledSymbol(frameList[i][0 .. strlen(frameList[i])], symbolBuffer); + if (symbol.length > 0) + bufferLength = snprintf(buffer.ptr, buffer.length, "%s%.*s [0x%x]", addressBuffer.ptr, cast(int) symbol.length, symbol.ptr, callstack[i]); + else + bufferLength = snprintf(buffer.ptr, buffer.length, "%s[0x%x]", addressBuffer.ptr, callstack[i]); + + assert(bufferLength >= 0); + + auto output = buffer[0 .. bufferLength]; + auto pos = i; + ret = dg(pos, output); + if (ret) break; + } + return ret; +} + +private: + +// the lifetime of the Location data is the lifetime of the mmapped ElfSection +void resolveAddresses(ElfSection* debugLineSection, Location[] locations) @nogc nothrow +{ + size_t numberOfLocationsFound = 0; + + const(ubyte)[] dbg = debugLineSection.get(); + while (dbg.length > 0) + { + const(LPHeader)* lph = cast(const(LPHeader)*) dbg.ptr; + + if (lph.unitLength == 0xffff_ffff) // is 64-bit dwarf? + return; // unable to read 64-bit dwarf + + const(ubyte)[] program = dbg[ + lph.headerLength + LPHeader.minimumInstructionLength.offsetof .. + lph.unitLength + LPHeader.dwarfVersion.offsetof + ]; + + const(ubyte)[] standardOpcodeLengths = dbg[ + LPHeader.sizeof .. LPHeader.sizeof + lph.opcodeBase - 1 + ]; + + const(ubyte)[] pathData = dbg[ + LPHeader.sizeof + lph.opcodeBase - 1 .. $ + ]; + + Array!(const(char)[]) directories; + directories.length = (const(ubyte)[] bytes) { + // count number of directories + int count = 0; + foreach (i; 0 .. bytes.length - 1) + { + if (bytes[i] == 0) + { + count++; + if (bytes[i + 1] == 0) return count; + } + } + return count; + }(pathData); + + // fill directories array from dwarf section + int currentDirectoryIndex = 0; + while (pathData[0] != 0) + { + directories[currentDirectoryIndex] = cast(const(char)[]) pathData[0 .. strlen(cast(char*) (pathData.ptr))]; + pathData = pathData[directories[currentDirectoryIndex].length + 1 .. $]; + currentDirectoryIndex++; + } + + pathData = pathData[1 .. $]; + + Array!(const(char)[]) filenames; + filenames.length = (const(ubyte)[] bytes) + { + // count number of files + int count = 0; + while (bytes[0] != 0) + { + auto filename = cast(const(char)[]) bytes[0 .. strlen(cast(char*) (bytes.ptr))]; + bytes = bytes[filename.length + 1 .. $]; + bytes.readULEB128(); // dir index + bytes.readULEB128(); // last mod + bytes.readULEB128(); // file len + count++; + } + return count; + }(pathData); + + // fill filenames array from dwarf section + int currentFileIndex = 0; + while (pathData[0] != 0) + { + filenames[currentFileIndex] = cast(const(char)[]) pathData[0 .. strlen(cast(char*) (pathData.ptr))]; + pathData = pathData[filenames[currentFileIndex].length + 1 .. $]; + + auto dirIndex = pathData.readULEB128(); // unused + auto lastMod = pathData.readULEB128(); // unused + auto fileLen = pathData.readULEB128(); // unused + + currentFileIndex++; + } + + LocationInfo lastLoc = LocationInfo(-1, -1); + size_t lastAddress = 0x0; + + runStateMachine(lph, program, standardOpcodeLengths, + (size_t address, LocationInfo locInfo) + { + foreach (ref loc; locations) + { + if (loc.address == address) + { + loc.file = filenames[locInfo.file - 1]; + loc.line = locInfo.line; + numberOfLocationsFound++; + } + else if (loc.address < address && lastAddress < loc.address && lastAddress != 0) + { + loc.file = filenames[lastLoc.file - 1]; + loc.line = lastLoc.line; + numberOfLocationsFound++; + } + } + + lastAddress = address; + lastLoc = locInfo; + return numberOfLocationsFound < locations.length; + } + ); + + if (numberOfLocationsFound == locations.length) return; + dbg = dbg[lph.unitLength + LPHeader.dwarfVersion.offsetof .. $]; + } +} + +alias RunStateMachineCallback = bool delegate(size_t, LocationInfo) @nogc nothrow; +bool runStateMachine(const(LPHeader)* lpHeader, const(ubyte)[] program, const(ubyte)[] standardOpcodeLengths, scope RunStateMachineCallback callback) @nogc nothrow +{ + StateMachine machine; + machine.isStatement = lpHeader.defaultIsStatement; + + while (program.length > 0) + { + ubyte opcode = program.read!ubyte(); + if (opcode < lpHeader.opcodeBase) + { + switch (opcode) with (StandardOpcode) + { + case extendedOp: + size_t len = cast(size_t) program.readULEB128(); + ubyte eopcode = program.read!ubyte(); + + switch (eopcode) with (ExtendedOpcode) + { + case endSequence: + machine.isEndSequence = true; + // trace("endSequence ", "0x%x".format(m.address)); + if (!callback(machine.address, LocationInfo(machine.fileIndex, machine.line))) return true; + machine = StateMachine.init; + machine.isStatement = lpHeader.defaultIsStatement; + break; + + case setAddress: + size_t address = program.read!size_t(); + // trace("setAddress ", "0x%x".format(address)); + machine.address = address; + break; + + case defineFile: // TODO: add proper implementation + // trace("defineFile"); + program = program[len - 1 .. $]; + break; + + default: + // unknown opcode + // trace("unknown extended opcode ", eopcode); + program = program[len - 1 .. $]; + break; + } + + break; + + case copy: + // trace("copy"); + if (!callback(machine.address, LocationInfo(machine.fileIndex, machine.line))) return true; + machine.isBasicBlock = false; + machine.isPrologueEnd = false; + machine.isEpilogueBegin = false; + break; + + case advancePC: + ulong op = readULEB128(program); + // trace("advancePC ", op * lpHeader.minimumInstructionLength); + machine.address += op * lpHeader.minimumInstructionLength; + break; + + case advanceLine: + long ad = readSLEB128(program); + // trace("advanceLine ", ad); + machine.line += ad; + break; + + case setFile: + uint index = cast(uint) readULEB128(program); + // trace("setFile to ", index); + machine.fileIndex = index; + break; + + case setColumn: + uint col = cast(uint) readULEB128(program); + // trace("setColumn ", col); + machine.column = col; + break; + + case negateStatement: + // trace("negateStatement"); + machine.isStatement = !machine.isStatement; + break; + + case setBasicBlock: + // trace("setBasicBlock"); + machine.isBasicBlock = true; + break; + + case constAddPC: + machine.address += (255 - lpHeader.opcodeBase) / lpHeader.lineRange * lpHeader.minimumInstructionLength; + // trace("constAddPC ", "0x%x".format(machine.address)); + break; + + case fixedAdvancePC: + uint add = program.read!uint(); + // trace("fixedAdvancePC ", add); + machine.address += add; + break; + + case setPrologueEnd: + machine.isPrologueEnd = true; + // trace("setPrologueEnd"); + break; + + case setEpilogueBegin: + machine.isEpilogueBegin = true; + // trace("setEpilogueBegin"); + break; + + case setISA: + machine.isa = cast(uint) readULEB128(program); + // trace("setISA ", m.isa); + break; + + default: + // unimplemented/invalid opcode + return false; + } + } + else + { + opcode -= lpHeader.opcodeBase; + auto ainc = (opcode / lpHeader.lineRange) * lpHeader.minimumInstructionLength; + machine.address += ainc; + auto linc = lpHeader.lineBase + (opcode % lpHeader.lineRange); + machine.line += linc; + + // trace("special ", ainc, " ", linc); + if (!callback(machine.address, LocationInfo(machine.fileIndex, machine.line))) return true; + } + } + + return true; +} + +const(char)[] getDemangledSymbol(const(char)[] btSymbol, ref char[1024] buffer) +{ + // format is: module(_D6module4funcAFZv) [0x00000000] + // or: module(_D6module4funcAFZv+0x78) [0x00000000] + auto bptr = cast(char*) memchr(btSymbol.ptr, '(', btSymbol.length); + auto eptr = cast(char*) memchr(btSymbol.ptr, ')', btSymbol.length); + auto pptr = cast(char*) memchr(btSymbol.ptr, '+', btSymbol.length); + + if (pptr && pptr < eptr) + eptr = pptr; + + size_t symBeg, symEnd; + if (bptr++ && eptr) + { + symBeg = bptr - btSymbol.ptr; + symEnd = eptr - btSymbol.ptr; + } + + assert(symBeg <= symEnd); + assert(symEnd < btSymbol.length); + + import core.demangle; + return demangle(btSymbol[symBeg .. symEnd], buffer[]); +} + +T read(T)(ref const(ubyte)[] buffer) @nogc nothrow +{ + T result = *(cast(T*) buffer[0 .. T.sizeof].ptr); + buffer = buffer[T.sizeof .. $]; + return result; +} + +ulong readULEB128(ref const(ubyte)[] buffer) @nogc nothrow +{ + ulong val = 0; + uint shift = 0; + + while (true) + { + ubyte b = buffer.read!ubyte(); + + val |= (b & 0x7f) << shift; + if ((b & 0x80) == 0) break; + shift += 7; + } + + return val; +} + +unittest +{ + const(ubyte)[] data = [0xe5, 0x8e, 0x26, 0xDE, 0xAD, 0xBE, 0xEF]; + assert(readULEB128(data) == 624_485); + assert(data[] == [0xDE, 0xAD, 0xBE, 0xEF]); +} + +long readSLEB128(ref const(ubyte)[] buffer) @nogc nothrow +{ + long val = 0; + uint shift = 0; + int size = 8 << 3; + ubyte b; + + while (true) + { + b = buffer.read!ubyte(); + val |= (b & 0x7f) << shift; + shift += 7; + if ((b & 0x80) == 0) + break; + } + + if (shift < size && (b & 0x40) != 0) + val |= -(1 << shift); + + return val; +} + +enum StandardOpcode : ubyte +{ + extendedOp = 0, + copy = 1, + advancePC = 2, + advanceLine = 3, + setFile = 4, + setColumn = 5, + negateStatement = 6, + setBasicBlock = 7, + constAddPC = 8, + fixedAdvancePC = 9, + setPrologueEnd = 10, + setEpilogueBegin = 11, + setISA = 12, +} + +enum ExtendedOpcode : ubyte +{ + endSequence = 1, + setAddress = 2, + defineFile = 3, +} + +struct StateMachine +{ + size_t address = 0; + uint operationIndex = 0; + uint fileIndex = 1; + uint line = 1; + uint column = 0; + bool isStatement; + bool isBasicBlock = false; + bool isEndSequence = false; + bool isPrologueEnd = false; + bool isEpilogueBegin = false; + uint isa = 0; + uint discriminator = 0; +} + +struct LocationInfo +{ + int file; + int line; +} + +// 32-bit DWARF +align(1) +struct LPHeader +{ +align(1): + uint unitLength; + ushort dwarfVersion; + uint headerLength; + ubyte minimumInstructionLength; + bool defaultIsStatement; + byte lineBase; + ubyte lineRange; + ubyte opcodeBase; +} diff --git a/src/rt/backtrace/elf.d b/src/rt/backtrace/elf.d new file mode 100644 index 0000000000..62ecb7da68 --- /dev/null +++ b/src/rt/backtrace/elf.d @@ -0,0 +1,200 @@ +/** + * This code reads ELF files and sections using memory mapped IO. + * + * Reference: http://www.dwarfstd.org/ + * + * Copyright: Copyright Digital Mars 2015 - 2015. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Yazan Dabain + * Source: $(DRUNTIMESRC src/rt/backtrace/elf.d) + */ + +module rt.backtrace.elf; + +version(linux): + +import core.sys.posix.fcntl; +import core.sys.posix.unistd; + +public import core.sys.linux.elf; + +struct ElfFile +{ + static bool openSelf(ElfFile* file) @nogc nothrow + { + file.fd = open("/proc/self/exe", O_RDONLY); + if (file.fd >= 0) + { + // memory map header + file.ehdr = MMapRegion!Elf_Ehdr(file.fd, 0, Elf_Ehdr.sizeof); + if (file.ehdr.isValidElfHeader()) + return true; + else + return false; + } + else + return false; + } + + @disable this(this); + + ~this() @nogc nothrow + { + if (fd != -1) close(fd); + } + + int fd = -1; + MMapRegion!Elf_Ehdr ehdr; +} + +struct ElfSectionHeader +{ + this(const(ElfFile)* file, size_t index) @nogc nothrow + { + assert(Elf_Shdr.sizeof == file.ehdr.e_shentsize); + shdr = MMapRegion!Elf_Shdr( + file.fd, + file.ehdr.e_shoff + index * file.ehdr.e_shentsize, + file.ehdr.e_shentsize + ); + } + + @disable this(this); + + alias shdr this; + MMapRegion!Elf_Shdr shdr; +} + +struct ElfSection +{ + this(ElfFile* file, ElfSectionHeader* shdr) @nogc nothrow + { + data = MMapRegion!ubyte( + file.fd, + shdr.sh_offset, + shdr.sh_size, + ); + + length = shdr.sh_size; + } + + @disable this(this); + + const(ubyte)[] get() @nogc nothrow + { + return data.get()[0 .. length]; + } + + alias get this; + + MMapRegion!ubyte data; + size_t length; +} + +const(char)[] getSectionName(const(ElfFile)* file, ElfSection* stringSection, size_t nameIndex) @nogc nothrow +{ + const(ubyte)[] data = stringSection.get(); + + foreach (i; nameIndex .. data.length) + { + if (data[i] == 0) + return cast(const(char)[])data[nameIndex .. i]; + } + + return null; +} + +size_t findSectionByName(const(ElfFile)* file, ElfSection* stringSection, const(char)[] sectionName) @nogc nothrow +{ + foreach (s; 0 .. file.ehdr.e_shnum) + { + auto sectionHeader = ElfSectionHeader(file, s); + auto currentName = getSectionName(file, stringSection, sectionHeader.sh_name); + if (sectionName == currentName) + return s; // TODO: attempt to move ElfSectionHeader instead of returning index + } + + // not found + return -1; +} + +private: + +bool isValidElfHeader(const(Elf_Ehdr)* ehdr) @nogc nothrow +{ + if (ehdr.e_ident[EI_MAG0] != ELFMAG0) return false; + if (ehdr.e_ident[EI_MAG1] != ELFMAG1) return false; + if (ehdr.e_ident[EI_MAG2] != ELFMAG2) return false; + if (ehdr.e_ident[EI_MAG3] != ELFMAG3) return false; + + // elf class and data encoding should match target's config + if (ehdr.e_ident[EI_CLASS] != ELFCLASS) return false; + if (ehdr.e_ident[EI_DATA] != ELFDATA ) return false; + + return true; +} + +struct MMapRegion(T) +{ + import core.sys.posix.sys.mman; + import core.sys.posix.unistd; + + this(int fd, size_t offset, size_t length) @nogc nothrow + { + auto pagesize = sysconf(_SC_PAGESIZE); + + auto realOffset = (offset / pagesize) * pagesize; + offsetDiff = offset - realOffset; + realLength = length + offsetDiff; + + mptr = mmap(null, realLength, PROT_READ, MAP_PRIVATE, fd, realOffset); + } + + @disable this(this); + + ~this() @nogc nothrow + { + if (mptr) munmap(mptr, realLength); + } + + const(T)* get() const @nogc nothrow + { + return cast(T*)(mptr + offsetDiff); + } + + alias get this; + + size_t realLength; + size_t offsetDiff; + void* mptr; +} + +version(X86) +{ + alias Elf_Ehdr = Elf32_Ehdr; + alias Elf_Shdr = Elf32_Shdr; + enum ELFCLASS = ELFCLASS32; +} +else version(X86_64) +{ + alias Elf_Ehdr = Elf64_Ehdr; + alias Elf_Shdr = Elf64_Shdr; + enum ELFCLASS = ELFCLASS64; +} +else +{ + static assert(0, "unsupported architecture"); +} + +version(LittleEndian) +{ + alias ELFDATA = ELFDATA2LSB; +} +else version(BigEndian) +{ + alias ELFDATA = ELFDATA2MSB; +} +else +{ + static assert(0, "unsupported byte order"); +} diff --git a/src/rt/util/container/common.d b/src/rt/util/container/common.d index c9210bd3f7..dba90b87d4 100644 --- a/src/rt/util/container/common.d +++ b/src/rt/util/container/common.d @@ -12,7 +12,7 @@ public import core.stdc.stdlib : free; import core.internal.traits : dtorIsNothrow; nothrow: -void* xrealloc(void* ptr, size_t sz) +void* xrealloc(void* ptr, size_t sz) nothrow @nogc { import core.exception; diff --git a/test/exceptions/Makefile b/test/exceptions/Makefile index a8b5c586ba..87e15dd17e 100644 --- a/test/exceptions/Makefile +++ b/test/exceptions/Makefile @@ -1,6 +1,11 @@ include ../common.mak TESTS:=stderr_msg unittest_assert +ifeq ($(OS)-$(BUILD),linux-debug) + TESTS:=$(TESTS) line_trace + DIFF:=diff + SED:=sed +endif .PHONY: all clean all: $(addprefix $(ROOT)/,$(addsuffix .done,$(TESTS))) @@ -20,7 +25,15 @@ $(ROOT)/unittest_assert.done: $(ROOT)/unittest_assert $(QUIET)./$(ROOT)/unittest_assert $(RUN_ARGS) 2>&1 1>/dev/null | grep -qF "unittest_assert msg" @touch $@ +$(ROOT)/line_trace.done: $(ROOT)/line_trace + @echo Testing line_trace + @rm -f line_trace.output + $(QUIET)./$(ROOT)/line_trace $(RUN_ARGS) >line_trace.output + $(QUIET)$(SED) "s/\[0x[0-9a-f]*\]/\[ADDR\]/g" line_trace.output | $(DIFF) line_trace.exp - + @touch $@ + $(ROOT)/unittest_assert: DFLAGS+=-unittest +$(ROOT)/line_trace: DFLAGS+=-L--export-dynamic $(ROOT)/%: $(SRC)/%.d $(QUIET)$(DMD) $(DFLAGS) -of$@ $< diff --git a/test/exceptions/line_trace.exp b/test/exceptions/line_trace.exp new file mode 100644 index 0000000000..8ddcb9f87c --- /dev/null +++ b/test/exceptions/line_trace.exp @@ -0,0 +1,9 @@ +object.Exception@src/line_trace.d(17): exception +---------------- +src/line_trace.d:17 void line_trace.f1() [ADDR] +src/line_trace.d:5 _Dmain [ADDR] +src/rt/dmain2.d:471 _D2rt6dmain211_d_run_mainUiPPaPUAAaZiZ6runAllMFZ9__lambda1MFZv [ADDR] +src/rt/dmain2.d:446 void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [ADDR] +src/rt/dmain2.d:471 void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).runAll() [ADDR] +src/rt/dmain2.d:446 void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [ADDR] +src/rt/dmain2.d:479 _d_run_main [ADDR] diff --git a/test/exceptions/src/line_trace.d b/test/exceptions/src/line_trace.d new file mode 100644 index 0000000000..7bd583ae33 --- /dev/null +++ b/test/exceptions/src/line_trace.d @@ -0,0 +1,18 @@ +void main() +{ + try + { + f1(); + } + catch (Exception e) + { + import core.stdc.stdio; + auto str = e.toString(); + printf("%.*s\n", str.length, str.ptr); + } +} + +void f1() +{ + throw new Exception("exception"); +} From 2f5ff93f6883d53c44783accdb8970332e68ea74 Mon Sep 17 00:00:00 2001 From: Yazan Dabain Date: Thu, 3 Sep 2015 20:06:22 +0300 Subject: [PATCH 3/3] Reset lastAddress to 0 on sequence end + use first file:line found for address --- src/rt/backtrace/dwarf.d | 78 +++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/src/rt/backtrace/dwarf.d b/src/rt/backtrace/dwarf.d index 4a4d6292e5..cd45de1080 100644 --- a/src/rt/backtrace/dwarf.d +++ b/src/rt/backtrace/dwarf.d @@ -19,6 +19,8 @@ import rt.backtrace.elf; import core.stdc.string : strlen, memchr; +//debug = DwarfDebugMachine; + struct Location { const(char)[] file = null; // file is missing directory, but DMD emits directory directly into file @@ -94,11 +96,14 @@ private: // the lifetime of the Location data is the lifetime of the mmapped ElfSection void resolveAddresses(ElfSection* debugLineSection, Location[] locations) @nogc nothrow { + debug(DwarfDebugMachine) import core.stdc.stdio; + size_t numberOfLocationsFound = 0; const(ubyte)[] dbg = debugLineSection.get(); while (dbg.length > 0) { + debug(DwarfDebugMachine) printf("new debug program\n"); const(LPHeader)* lph = cast(const(LPHeader)*) dbg.ptr; if (lph.unitLength == 0xffff_ffff) // is 64-bit dwarf? @@ -137,6 +142,7 @@ void resolveAddresses(ElfSection* debugLineSection, Location[] locations) @nogc while (pathData[0] != 0) { directories[currentDirectoryIndex] = cast(const(char)[]) pathData[0 .. strlen(cast(char*) (pathData.ptr))]; + debug(DwarfDebugMachine) printf("dir: %s\n", pathData.ptr); pathData = pathData[directories[currentDirectoryIndex].length + 1 .. $]; currentDirectoryIndex++; } @@ -165,6 +171,7 @@ void resolveAddresses(ElfSection* debugLineSection, Location[] locations) @nogc while (pathData[0] != 0) { filenames[currentFileIndex] = cast(const(char)[]) pathData[0 .. strlen(cast(char*) (pathData.ptr))]; + debug(DwarfDebugMachine) printf("file: %s\n", pathData.ptr); pathData = pathData[filenames[currentFileIndex].length + 1 .. $]; auto dirIndex = pathData.readULEB128(); // unused @@ -177,27 +184,46 @@ void resolveAddresses(ElfSection* debugLineSection, Location[] locations) @nogc LocationInfo lastLoc = LocationInfo(-1, -1); size_t lastAddress = 0x0; + debug(DwarfDebugMachine) printf("program:\n"); runStateMachine(lph, program, standardOpcodeLengths, - (size_t address, LocationInfo locInfo) + (size_t address, LocationInfo locInfo, bool isEndSequence) { - foreach (ref loc; locations) + // If loc.line != -1, then it has been set previously. + // Some implementations (eg. dmd) write an address to + // the debug data multiple times, but so far I have found + // that the first occurrence to be the correct one. + foreach (ref loc; locations) if (loc.line == -1) { if (loc.address == address) { + debug(DwarfDebugMachine) printf("-- found for [0x%x]:\n", loc.address); + debug(DwarfDebugMachine) printf("-- file: %.*s\n", filenames[locInfo.file - 1].length, filenames[locInfo.file - 1].ptr); + debug(DwarfDebugMachine) printf("-- line: %d\n", locInfo.line); loc.file = filenames[locInfo.file - 1]; loc.line = locInfo.line; numberOfLocationsFound++; } else if (loc.address < address && lastAddress < loc.address && lastAddress != 0) { + debug(DwarfDebugMachine) printf("-- found for [0x%x]:\n", loc.address); + debug(DwarfDebugMachine) printf("-- file: %.*s\n", filenames[lastLoc.file - 1].length, filenames[lastLoc.file - 1].ptr); + debug(DwarfDebugMachine) printf("-- line: %d\n", lastLoc.line); loc.file = filenames[lastLoc.file - 1]; loc.line = lastLoc.line; numberOfLocationsFound++; } } - lastAddress = address; - lastLoc = locInfo; + if (isEndSequence) + { + lastAddress = 0; + } + else + { + lastAddress = address; + lastLoc = locInfo; + } + return numberOfLocationsFound < locations.length; } ); @@ -207,9 +233,11 @@ void resolveAddresses(ElfSection* debugLineSection, Location[] locations) @nogc } } -alias RunStateMachineCallback = bool delegate(size_t, LocationInfo) @nogc nothrow; +alias RunStateMachineCallback = bool delegate(size_t, LocationInfo, bool) @nogc nothrow; bool runStateMachine(const(LPHeader)* lpHeader, const(ubyte)[] program, const(ubyte)[] standardOpcodeLengths, scope RunStateMachineCallback callback) @nogc nothrow { + debug(DwarfDebugMachine) import core.stdc.stdio; + StateMachine machine; machine.isStatement = lpHeader.defaultIsStatement; @@ -228,26 +256,26 @@ bool runStateMachine(const(LPHeader)* lpHeader, const(ubyte)[] program, const(ub { case endSequence: machine.isEndSequence = true; - // trace("endSequence ", "0x%x".format(m.address)); - if (!callback(machine.address, LocationInfo(machine.fileIndex, machine.line))) return true; + debug(DwarfDebugMachine) printf("endSequence 0x%x\n", machine.address); + if (!callback(machine.address, LocationInfo(machine.fileIndex, machine.line), true)) return true; machine = StateMachine.init; machine.isStatement = lpHeader.defaultIsStatement; break; case setAddress: size_t address = program.read!size_t(); - // trace("setAddress ", "0x%x".format(address)); + debug(DwarfDebugMachine) printf("setAddress 0x%x\n", address); machine.address = address; break; case defineFile: // TODO: add proper implementation - // trace("defineFile"); + debug(DwarfDebugMachine) printf("defineFile\n"); program = program[len - 1 .. $]; break; default: // unknown opcode - // trace("unknown extended opcode ", eopcode); + debug(DwarfDebugMachine) printf("unknown extended opcode %d\n", cast(int) eopcode); program = program[len - 1 .. $]; break; } @@ -255,8 +283,8 @@ bool runStateMachine(const(LPHeader)* lpHeader, const(ubyte)[] program, const(ub break; case copy: - // trace("copy"); - if (!callback(machine.address, LocationInfo(machine.fileIndex, machine.line))) return true; + debug(DwarfDebugMachine) printf("copy 0x%x\n", machine.address); + if (!callback(machine.address, LocationInfo(machine.fileIndex, machine.line), false)) return true; machine.isBasicBlock = false; machine.isPrologueEnd = false; machine.isEpilogueBegin = false; @@ -264,62 +292,62 @@ bool runStateMachine(const(LPHeader)* lpHeader, const(ubyte)[] program, const(ub case advancePC: ulong op = readULEB128(program); - // trace("advancePC ", op * lpHeader.minimumInstructionLength); machine.address += op * lpHeader.minimumInstructionLength; + debug(DwarfDebugMachine) printf("advancePC %d to 0x%x\n", cast(int) (op * lpHeader.minimumInstructionLength), machine.address); break; case advanceLine: long ad = readSLEB128(program); - // trace("advanceLine ", ad); machine.line += ad; + debug(DwarfDebugMachine) printf("advanceLine %d to %d\n", cast(int) ad, cast(int) machine.line); break; case setFile: uint index = cast(uint) readULEB128(program); - // trace("setFile to ", index); + debug(DwarfDebugMachine) printf("setFile to %d\n", cast(int) index); machine.fileIndex = index; break; case setColumn: uint col = cast(uint) readULEB128(program); - // trace("setColumn ", col); + debug(DwarfDebugMachine) printf("setColumn %d\n", cast(int) col); machine.column = col; break; case negateStatement: - // trace("negateStatement"); + debug(DwarfDebugMachine) printf("negateStatement\n"); machine.isStatement = !machine.isStatement; break; case setBasicBlock: - // trace("setBasicBlock"); + debug(DwarfDebugMachine) printf("setBasicBlock\n"); machine.isBasicBlock = true; break; case constAddPC: machine.address += (255 - lpHeader.opcodeBase) / lpHeader.lineRange * lpHeader.minimumInstructionLength; - // trace("constAddPC ", "0x%x".format(machine.address)); + debug(DwarfDebugMachine) printf("constAddPC 0x%x\n", machine.address); break; case fixedAdvancePC: uint add = program.read!uint(); - // trace("fixedAdvancePC ", add); machine.address += add; + debug(DwarfDebugMachine) printf("fixedAdvancePC %d to 0x%x\n", cast(int) add, machine.address); break; case setPrologueEnd: machine.isPrologueEnd = true; - // trace("setPrologueEnd"); + debug(DwarfDebugMachine) printf("setPrologueEnd\n"); break; case setEpilogueBegin: machine.isEpilogueBegin = true; - // trace("setEpilogueBegin"); + debug(DwarfDebugMachine) printf("setEpilogueBegin\n"); break; case setISA: machine.isa = cast(uint) readULEB128(program); - // trace("setISA ", m.isa); + debug(DwarfDebugMachine) printf("setISA %d\n", cast(int) machine.isa); break; default: @@ -335,8 +363,8 @@ bool runStateMachine(const(LPHeader)* lpHeader, const(ubyte)[] program, const(ub auto linc = lpHeader.lineBase + (opcode % lpHeader.lineRange); machine.line += linc; - // trace("special ", ainc, " ", linc); - if (!callback(machine.address, LocationInfo(machine.fileIndex, machine.line))) return true; + debug(DwarfDebugMachine) printf("special %d %d to 0x%x line %d\n", cast(int) ainc, cast(int) linc, machine.address, cast(int) machine.line); + if (!callback(machine.address, LocationInfo(machine.fileIndex, machine.line), false)) return true; } }