// Convert DMD CodeView/DWARF debug information to PDB files // Copyright (c) 2009-2012 by Rainer Schuetze, All Rights Reserved // // License for redistribution is given by the Artistic License 2.0 // see file LICENSE for further details // #include "PEImage.h" #include "mspdb.h" #include "dwarf.h" #include "readDwarf.h" bool isRelativePath(const std::string& s) { if(s.length() < 1) return true; if(s[0] == '/' || s[0] == '\\') return false; if(s.length() < 2) return true; if(s[1] == ':') return false; return true; } static int cmpAdr(const void* s1, const void* s2) { const mspdb::LineInfoEntry* e1 = (const mspdb::LineInfoEntry*) s1; const mspdb::LineInfoEntry* e2 = (const mspdb::LineInfoEntry*) s2; return e1->offset - e2->offset; } bool printLines(char const *fname, unsigned short sec, char const *secname, mspdb::LineInfoEntry* pLineInfo, long numLineInfo) { printf("Sym: %s\n", secname ? secname : ""); printf("File: %s\n", fname); for (int i = 0; i < numLineInfo; i++) printf("\tOff 0x%x: Line %d\n", pLineInfo[i].offset, pLineInfo[i].line); return true; } bool _flushDWARFLines(const PEImage& img, mspdb::Mod* mod, DWARF_LineState& state) { if(state.lineInfo.size() == 0) return true; unsigned int saddr = state.lineInfo[0].offset; unsigned int eaddr = state.lineInfo.back().offset; int segIndex = state.section; if (segIndex < 0) segIndex = img.findSection(saddr + state.seg_offset); if(segIndex < 0) { // throw away invalid lines (mostly due to "set address to 0") state.lineInfo.resize(0); return true; //return false; } // if(saddr >= 0x4000) // return true; const DWARF_FileName* dfn; if(state.lineInfo_file == 0) dfn = state.file_ptr; else if(state.lineInfo_file > 0 && state.lineInfo_file <= state.files.size()) dfn = &state.files[state.lineInfo_file - 1]; else return false; std::string fname = dfn->file_name; if(isRelativePath(fname) && dfn->dir_index > 0 && dfn->dir_index <= state.include_dirs.size()) { std::string dir = state.include_dirs[dfn->dir_index - 1]; if(dir.length() > 0 && dir[dir.length() - 1] != '/' && dir[dir.length() - 1] != '\\') dir.append("\\"); fname = dir + fname; } for(size_t i = 0; i < fname.length(); i++) if(fname[i] == '/') fname[i] = '\\'; if (!mod) { printLines(fname.c_str(), segIndex, img.findSectionSymbolName(segIndex), state.lineInfo.data(), state.lineInfo.size()); state.lineInfo.resize(0); return true; } #if 1 bool dump = false; // (fname == "cvtest.d"); //qsort(&state.lineInfo[0], state.lineInfo.size(), sizeof(state.lineInfo[0]), cmpAdr); #if 0 printf("%s:\n", fname.c_str()); for(size_t ln = 0; ln < state.lineInfo.size(); ln++) printf(" %08x: %4d\n", state.lineInfo[ln].offset + 0x401000, state.lineInfo[ln].line); #endif int rc = 1; unsigned int low_offset = state.lineInfo[0].offset; unsigned short low_line = state.lineInfo[0].line; for (size_t ln = 0; ln < state.lineInfo.size(); ++ln) { auto& line_entry = state.lineInfo[ln]; line_entry.line -= low_line; line_entry.offset -= low_offset; } unsigned int high_offset = state.address - state.seg_offset; if (high_offset < state.lineInfo.back().offset + low_offset) // The current address is before the previous address; use that as the end instead to ensure we capture all of the preceeding line info high_offset = state.lineInfo.back().offset + low_offset; // PDB address ranges are fully closed, so point to before the next instruction --high_offset; // This subtraction can underflow to (unsigned)-1 if this info is only for a single instruction, but AddLines will immediately increment it to 0, so this is fine. Not underflowing this can cause the debugger to ignore other line info for address ranges that include this address. unsigned int address_range_length = high_offset - low_offset; if (dump) printf("AddLines(%08x+%04x, Line=%4d+%3d, %s)\n", low_offset, address_range_length, low_line, state.lineInfo.size(), fname.c_str()); rc = mod->AddLines(fname.c_str(), segIndex + 1, low_offset, address_range_length, low_offset, low_line, (unsigned char*)&state.lineInfo[0], state.lineInfo.size() * sizeof(state.lineInfo[0])); #else unsigned int firstLine = 0; unsigned int firstAddr = 0; int rc = mod->AddLines(fname.c_str(), segIndex + 1, saddr, eaddr - saddr, firstAddr, firstLine, (unsigned char*) &state.lineInfo[0], state.lineInfo.size() * sizeof(state.lineInfo[0])); #endif state.lineInfo.resize(0); return rc > 0; } bool addLineInfo(const PEImage& img, mspdb::Mod* mod, DWARF_LineState& state) { // The DWARF standard says about end_sequence: "indicating that the current // address is that of the first byte after the end of a sequence of target // machine instructions". So if this is a end_sequence row, don't append any // lines to the list, just flush it. if (state.end_sequence) return _flushDWARFLines(img, mod, state); #if 0 const char* fname = (state.file == 0 ? state.file_ptr->file_name : state.files[state.file - 1].file_name); printf("Adr:%08x Line: %5d File: %s\n", state.address, state.line, fname); #endif if (state.address < state.seg_offset) return true; mspdb::LineInfoEntry entry; entry.offset = state.address - state.seg_offset; entry.line = state.line; if (!state.lineInfo.empty()) { auto first_entry = state.lineInfo.front(); auto last_entry = state.lineInfo.back(); // We can handle out-of-order line numbers, but we can't handle out-of-order addresses if (entry.line < first_entry.line || entry.offset < last_entry.offset || state.lineInfo_file != state.file) { if (!_flushDWARFLines(img, mod, state)) return false; } else if (entry.line == last_entry.line && entry.offset == last_entry.offset) { // There's no need to add duplicate entries return true; } } state.lineInfo.push_back(entry); state.lineInfo_file = state.file; return true; } bool interpretDWARFLines(const PEImage& img, mspdb::Mod* mod) { DWARF_CompilationUnit* cu = (DWARF_CompilationUnit*)img.debug_info; int ptrsize = cu ? cu->address_size : 4; DWARF_LineNumberProgramHeader hdr5; for(unsigned long off = 0; off < img.debug_line_length; ) { DWARF_LineNumberProgramHeader* hdrver = (DWARF_LineNumberProgramHeader*) (img.debug_line + off); int length = hdrver->unit_length; if(length < 0) break; length += sizeof(length); DWARF_LineNumberProgramHeader* hdr; if (hdrver->version <= 3) { auto hdr2 = (DWARF2_LineNumberProgramHeader*)hdrver; hdr5.default_is_stmt = hdr2->default_is_stmt; hdr5.header_length = hdr2->header_length; hdr5.line_base = hdr2->line_base; hdr5.line_range = hdr2->line_range; hdr5.minimum_instruction_length = hdr2->minimum_instruction_length; hdr5.maximum_operations_per_instruction = 0xff; hdr5.opcode_base = hdr2->opcode_base; hdr5.unit_length = hdr2->unit_length; hdr5.version = hdr2->version; hdr = &hdr5; } else if (hdrver->version == 4) { auto hdr4 = (DWARF4_LineNumberProgramHeader*)hdrver; hdr5.default_is_stmt = hdr4->default_is_stmt; hdr5.header_length = hdr4->header_length; hdr5.line_base = hdr4->line_base; hdr5.line_range = hdr4->line_range; hdr5.minimum_instruction_length = hdr4->minimum_instruction_length; hdr5.maximum_operations_per_instruction = hdr4->maximum_operations_per_instruction; hdr5.opcode_base = hdr4->opcode_base; hdr5.unit_length = hdr4->unit_length; hdr5.version = hdr4->version; hdr = &hdr5; } else hdr = hdrver; int hdrlength = hdr->version <= 3 ? sizeof(DWARF2_LineNumberProgramHeader) : hdr->version == 4 ? sizeof(DWARF4_LineNumberProgramHeader) : sizeof(DWARF_LineNumberProgramHeader); unsigned char* p = (unsigned char*) hdrver + hdrlength; unsigned char* end = (unsigned char*) hdrver + length; std::vector opcode_lengths; opcode_lengths.resize(hdr->opcode_base); if (hdr->opcode_base > 0) { opcode_lengths[0] = 0; for(int o = 1; o < hdr->opcode_base && p < end; o++) opcode_lengths[o] = LEB128(p); } DWARF_LineState state; state.seg_offset = img.getImageBase() + img.getSection(img.codeSegment).VirtualAddress; DWARF_FileName fname; if (hdr->version <= 4) { // dirs while(p < end) { if(*p == 0) break; state.include_dirs.push_back((const char*) p); p += strlen((const char*) p) + 1; } p++; // files while(p < end && *p) { fname.read(p); state.files.push_back(fname); } p++; } else { DWARF_TypeForm type_and_form; byte directory_entry_format_count = *(p++); std::vector directory_entry_format; for (int i = 0; i < directory_entry_format_count; i++) { type_and_form.type = LEB128(p); type_and_form.form = LEB128(p); directory_entry_format.push_back(type_and_form); } unsigned int directories_count = LEB128(p); for (int o = 0; o < directories_count; o++) { for (int i = 0; i < directory_entry_format_count; i++) { switch (directory_entry_format[i].type) { case DW_LNCT_path: switch (directory_entry_format[i].form) { case DW_FORM_line_strp: { size_t offset = cu->isDWARF64() ? RD8(p) : RD4(p); state.include_dirs.push_back(img.debug_line_str + offset); break; } case DW_FORM_string: state.include_dirs.push_back((const char*)p); p += strlen((const char*)p) + 1; break; default: return false; } break; case DW_LNCT_directory_index: case DW_LNCT_timestamp: case DW_LNCT_size: default: return false; } } } byte file_name_entry_format_count = *(p++); std::vector file_name_entry_format; for (int i = 0; i < file_name_entry_format_count; i++) { type_and_form.type = LEB128(p); type_and_form.form = LEB128(p); file_name_entry_format.push_back(type_and_form); } unsigned int file_names_count = LEB128(p); for (int o = 0; o < file_names_count; o++) { for (int i = 0; i < file_name_entry_format_count; i++) { switch (file_name_entry_format[i].type) { case DW_LNCT_path: switch (directory_entry_format[i].form) { case DW_FORM_line_strp: { size_t offset = cu->isDWARF64() ? RD8(p) : RD4(p); fname.file_name = img.debug_line_str + offset; break; } case DW_FORM_string: fname.file_name = (const char*)p; p += strlen((const char*)p) + 1; break; default: return false; } break; case DW_LNCT_directory_index: if (file_name_entry_format[i].form == DW_FORM_udata) fname.dir_index = LEB128(p); else return false; break; case DW_LNCT_timestamp: case DW_LNCT_size: default: return false; } } state.files.push_back(fname); } } state.init(hdr); while(p < end) { int opcode = *p++; if(opcode >= hdr->opcode_base) { // special opcode int adjusted_opcode = opcode - hdr->opcode_base; int operation_advance = adjusted_opcode / hdr->line_range; state.advance_addr(hdr, operation_advance); int line_advance = hdr->line_base + (adjusted_opcode % hdr->line_range); state.line += line_advance; if (!addLineInfo(img, mod, state)) return false; state.basic_block = false; state.prologue_end = false; state.epilogue_end = false; state.discriminator = 0; } else { switch(opcode) { case 0: // extended { int exlength = LEB128(p); unsigned char* q = p + exlength; int excode = *p++; switch(excode) { case DW_LNE_end_sequence: if((char*)p - img.debug_line >= 0xe4e0) p = p; state.end_sequence = true; state.last_addr = state.address; if(!addLineInfo(img, mod, state)) return false; state.init(hdr); break; case DW_LNE_set_address: { if (!mod && state.section == -1) state.section = img.getRelocationInLineSegment((char*)p - img.debug_line); unsigned long adr = ptrsize == 8 ? RD8(p) : RD4(p); state.address = adr; state.op_index = 0; break; } case DW_LNE_define_file: fname.read(p); state.file_ptr = &fname; state.file = 0; break; case DW_LNE_set_discriminator: state.discriminator = LEB128(p); break; } p = q; break; } case DW_LNS_copy: if (!addLineInfo(img, mod, state)) return false; state.basic_block = false; state.prologue_end = false; state.epilogue_end = false; state.discriminator = 0; break; case DW_LNS_advance_pc: state.advance_addr(hdr, LEB128(p)); break; case DW_LNS_advance_line: state.line += SLEB128(p); break; case DW_LNS_set_file: state.file = LEB128(p); break; case DW_LNS_set_column: state.column = LEB128(p); break; case DW_LNS_negate_stmt: state.is_stmt = !state.is_stmt; break; case DW_LNS_set_basic_block: state.basic_block = true; break; case DW_LNS_const_add_pc: state.advance_addr(hdr, (255 - hdr->opcode_base) / hdr->line_range); break; case DW_LNS_fixed_advance_pc: state.address += RD2(p); state.op_index = 0; break; case DW_LNS_set_prologue_end: state.prologue_end = true; break; case DW_LNS_set_epilogue_begin: state.epilogue_end = true; break; case DW_LNS_set_isa: state.isa = LEB128(p); break; default: // unknown standard opcode for(unsigned int arg = 0; arg < opcode_lengths[opcode]; arg++) LEB128(p); break; } } } if(!_flushDWARFLines(img, mod, state)) return false; off += length; } return true; }