From 149dd6f41fa3064e0d54810fef38d727d92582b4 Mon Sep 17 00:00:00 2001 From: Rainer Schuetze Date: Sat, 13 Jun 2015 11:53:42 +0200 Subject: DWARF: support debug_frame (CFA) and debug_loc (for frame base) --- src/PEImage.cpp | 35 +++-- src/PEImage.h | 14 +- src/cv2pdb.h | 24 +-- src/dwarf2pdb.cpp | 436 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 447 insertions(+), 62 deletions(-) diff --git a/src/PEImage.cpp b/src/PEImage.cpp index ed663a2..761da60 100644 --- a/src/PEImage.cpp +++ b/src/PEImage.cpp @@ -38,17 +38,16 @@ PEImage::PEImage(const TCHAR* iname) , debug_aranges(0) , debug_pubnames(0) , debug_pubtypes(0) -, debug_info(0) -, debug_abbrev(0) -, debug_line(0) -, debug_frame(0) +, debug_info(0), debug_info_length(0) +, debug_abbrev(0), debug_abbrev_length(0) +, debug_line(0), debug_line_length(0) +, debug_frame(0), debug_frame_length(0) , debug_str(0) -, debug_loc(0) -, debug_ranges(0) +, debug_loc(0), debug_loc_length(0) +, debug_ranges(0), debug_ranges_length(0) , codeSegment(0) , linesSegment(-1) -, reloc(0) -, reloc_length(0) +, reloc(0), reloc_length(0) , nsec(0) , nsym(0) , symtable(0) @@ -74,7 +73,7 @@ bool PEImage::readAll(const TCHAR* iname) return setError("file already open"); fd = T_sopen(iname, O_RDONLY | O_BINARY, SH_DENYWR); - if (fd == -1) + if (fd == -1) return setError("Can't open file"); struct stat s; @@ -85,9 +84,9 @@ bool PEImage::readAll(const TCHAR* iname) dump_base = alloc_aligned(dump_total_len, 0x1000); if (!dump_base) return setError("Out of memory"); - if (read(fd, dump_base, dump_total_len) != dump_total_len) + if (read(fd, dump_base, dump_total_len) != dump_total_len) return setError("Cannot read file"); - + close(fd); fd = -1; return true; @@ -121,10 +120,10 @@ bool PEImage::save(const TCHAR* oname) return setError("no data to dump"); fd = T_open(oname, O_WRONLY | O_CREAT | O_BINARY | O_TRUNC, S_IREAD | S_IWRITE | S_IEXEC); - if (fd == -1) + if (fd == -1) return setError("Can't create file"); - if (write(fd, dump_base, dump_total_len) != dump_total_len) + if (write(fd, dump_base, dump_total_len) != dump_total_len) return setError("Cannot write file"); close(fd); @@ -286,7 +285,7 @@ bool PEImage::initCVPtr(bool initDbgDir) { int off = IMGHDR(OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress) + i*sizeof(IMAGE_DEBUG_DIRECTORY); dbgDir = RVA(off, sizeof(IMAGE_DEBUG_DIRECTORY)); - if (!dbgDir) + if (!dbgDir) continue; //return setError("debug directory not placed in image"); if (dbgDir->Type != IMAGE_DEBUG_TYPE_CODEVIEW) continue; //return setError("debug directory not of type CodeView"); @@ -418,11 +417,11 @@ void PEImage::initDWARFSegments() if(strcmp(name, ".debug_line") == 0) debug_line = DPV(sec[linesSegment = s].PointerToRawData, debug_line_length = sizeInImage(sec[s])); if(strcmp(name, ".debug_frame") == 0) - debug_frame = DPV(sec[s].PointerToRawData, sizeInImage(sec[s])); + debug_frame = DPV(sec[s].PointerToRawData, debug_frame_length = sizeInImage(sec[s])); if(strcmp(name, ".debug_str") == 0) debug_str = DPV(sec[s].PointerToRawData, sizeInImage(sec[s])); if(strcmp(name, ".debug_loc") == 0) - debug_loc = DPV(sec[s].PointerToRawData, sizeInImage(sec[s])); + debug_loc = DPV(sec[s].PointerToRawData, debug_loc_length = sizeInImage(sec[s])); if(strcmp(name, ".debug_ranges") == 0) debug_ranges = DPV(sec[s].PointerToRawData, debug_ranges_length = sizeInImage(sec[s])); if(strcmp(name, ".reloc") == 0) @@ -710,12 +709,12 @@ int PEImage::findSymbol(const char* name, unsigned long& off) const /////////////////////////////////////////////////////////////////////// int PEImage::countCVEntries() const { - return dirHeader ? dirHeader->cDir : 0; + return dirHeader ? dirHeader->cDir : 0; } OMFDirEntry* PEImage::getCVEntry(int i) const { - return dirEntry + i; + return dirEntry + i; } diff --git a/src/PEImage.h b/src/PEImage.h index 350d59c..05e3d9e 100644 --- a/src/PEImage.h +++ b/src/PEImage.h @@ -24,13 +24,13 @@ public: template P* DP(int off) const { - return (P*) ((char*) dump_base + off); + return (P*) ((char*) dump_base + off); } template P* DPV(int off, int size) const - { + { if(off < 0 || off + size > dump_total_len) return 0; - return (P*) ((char*) dump_base + off); + return (P*) ((char*) dump_base + off); } template P* DPV(int off) const { @@ -112,7 +112,7 @@ private: const char* symtable; const char* strtable; bool bigobj; - + public: //dwarf char* debug_aranges; @@ -121,9 +121,9 @@ public: char* debug_info; unsigned long debug_info_length; char* debug_abbrev; unsigned long debug_abbrev_length; char* debug_line; unsigned long debug_line_length; - char* debug_frame; + char* debug_frame; unsigned long debug_frame_length; char* debug_str; - char* debug_loc; + char* debug_loc; unsigned long debug_loc_length; char* debug_ranges; unsigned long debug_ranges_length; char* reloc; unsigned long reloc_length; @@ -133,4 +133,4 @@ public: }; -#endif //__PEIMAGE_H__ \ No newline at end of file +#endif //__PEIMAGE_H__ diff --git a/src/cv2pdb.h b/src/cv2pdb.h index 0e9b96e..83d681f 100644 --- a/src/cv2pdb.h +++ b/src/cv2pdb.h @@ -40,11 +40,11 @@ public: const BYTE* getLibrary(int i); bool initSegMap(); - enum - { - kCmdAdd, - kCmdCount, - kCmdNestedTypes, + enum + { + kCmdAdd, + kCmdCount, + kCmdNestedTypes, kCmdOffsetFirstVirtualMethod, kCmdHasClassTypeEnum, kCmdCountBaseClasses @@ -54,13 +54,13 @@ public: int countFields(const codeview_reftype* fieldlist); int countNestedTypes(const codeview_reftype* fieldlist, int type); - int addAggregate(codeview_type* dtype, bool clss, int n_element, int fieldlist, int property, + int addAggregate(codeview_type* dtype, bool clss, int n_element, int fieldlist, int property, int derived, int vshape, int structlen, const char*name); - int addClass(codeview_type* dtype, int n_element, int fieldlist, int property, + int addClass(codeview_type* dtype, int n_element, int fieldlist, int property, int derived, int vshape, int structlen, const char*name); - int addStruct(codeview_type* dtype, int n_element, int fieldlist, int property, + int addStruct(codeview_type* dtype, int n_element, int fieldlist, int property, int derived, int vshape, int structlen, const char*name); - int addEnum(codeview_type* dtype, int count, int fieldlist, int property, + int addEnum(codeview_type* dtype, int count, int fieldlist, int property, int type, const char*name); int addPointerType(codeview_type* dtype, int type, int attr = 0x800A); @@ -121,7 +121,7 @@ public: void appendTypedefs(); int appendEnumerator(const char* typeName, const char* enumName, int enumValue, int prop); int appendClassTypeEnum(const codeview_type* fieldlist, int type, const char* name); - void appendStackVar(const char* name, int type, Location& loc); + void appendStackVar(const char* name, int type, Location& loc, Location& cfa); void appendGlobalVar(const char* name, int type, int seg, int offset); bool appendEndArg(); void appendEnd(); @@ -225,7 +225,7 @@ public: int nextUserType; int nextDwarfType; int objectType; - + int emptyFieldListType; int classEnumType; int ifaceEnumType; @@ -259,4 +259,4 @@ public: std::unordered_map mapOffsetToType; }; -#endif //__CV2PDB_H__ \ No newline at end of file +#endif //__CV2PDB_H__ diff --git a/src/dwarf2pdb.cpp b/src/dwarf2pdb.cpp index e9b33e7..09c68d3 100644 --- a/src/dwarf2pdb.cpp +++ b/src/dwarf2pdb.cpp @@ -17,7 +17,7 @@ #include "dwarf.h" -#include +#include #include #include @@ -141,21 +141,406 @@ CV_X86_REG dwarf_to_amd64_reg(unsigned dwarf_reg) case 54: return CV_REG_FS; case 55: return CV_REG_GS; - case 17: case 18: case 19: case 20: - case 21: case 22: case 23: case 24: + case 17: case 18: case 19: case 20: + case 21: case 22: case 23: case 24: return (CV_X86_REG)(CV_REG_XMM0 + dwarf_reg - 17); - case 25: case 26: case 27: case 28: - case 29: case 30: case 31: case 32: + case 25: case 26: case 27: case 28: + case 29: case 30: case 31: case 32: return (CV_X86_REG)(CV_REG_XMM8 + dwarf_reg - 25); - case 33: case 34: case 35: case 36: - case 37: case 38: case 39: case 40: + case 33: case 34: case 35: case 36: + case 37: case 38: case 39: case 40: return (CV_X86_REG)(CV_REG_ST0 + dwarf_reg - 33); default: return CV_REG_NONE; } } -void CV2PDB::appendStackVar(const char* name, int type, Location& loc) +// Call Frame Information entry (CIE or FDE) +class CFIEntry +{ +public: + enum Type + { + CIE, + FDE + }; + + byte* ptr; + byte* end; + byte type; + unsigned long CIE_pointer; // + + // CIE + byte version; + const char* augmentation; + byte address_size; + byte segment_size; + unsigned long code_alignment_factor; + unsigned long data_alignment_factor; + unsigned long return_address_register; + byte* initial_instructions; + unsigned long initial_instructions_length; + + // FDE + unsigned long segment; + unsigned long initial_location; + unsigned long address_range; + byte* instructions; + unsigned long instructions_length; +}; + +// Call Frame Information Cursor +class CFICursor +{ +public: + CFICursor(const PEImage& img) + : beg((byte*)img.debug_frame) + , end((byte*)img.debug_frame + img.debug_frame_length) + , ptr(beg) + { + default_address_size = img.isX64() ? 8 : 4; + } + + byte* beg; + byte* end; + byte* ptr; + byte default_address_size; + + bool readCIE(CFIEntry& entry, byte* &p) + { + entry.version = *p++; + entry.augmentation = (char*) p++; + if(entry.augmentation[0]) + { + // not supporting any augmentation + entry.address_size = 4; + entry.segment_size = 0; + entry.code_alignment_factor = 0; + entry.data_alignment_factor = 0; + entry.return_address_register = 0; + } + else + { + if (entry.version >= 4) + { + entry.address_size = *p++; + entry.segment_size = *p++; + } + else + { + entry.address_size = default_address_size; + entry.segment_size = 0; + } + entry.code_alignment_factor = LEB128(p); + entry.data_alignment_factor = SLEB128(p); + entry.return_address_register = LEB128(p); + } + entry.initial_instructions = p; + entry.initial_instructions_length = 0; // to be calculated outside + return true; + } + + bool readHeader(byte* &p, byte* &pend, unsigned long& CIE_pointer) + { + if (p >= end) + return false; + long long len = RDsize(p, 4); + bool dwarf64 = (len == 0xffffffff); + int ptrsize = dwarf64 ? 8 : 4; + if(dwarf64) + len = RDsize(p, 8); + if(p + len > end) + return false; + + pend = p + (unsigned long) len; + CIE_pointer = (unsigned long) RDsize(p, ptrsize); + return true; + } + + bool readNext(CFIEntry& entry) + { + byte* p = ptr; + if(!readHeader(p, entry.end, entry.CIE_pointer)) + return false; + + entry.ptr = ptr; + + if (entry.CIE_pointer == 0xffffffff) + { + entry.type = CFIEntry::CIE; + readCIE(entry, p); + entry.initial_instructions_length = entry.end - p; + } + else + { + entry.type = CFIEntry::FDE; + + byte* q = beg + entry.CIE_pointer, *qend; + unsigned long cie_off; + if (!readHeader(q, qend, cie_off)) + return false; + if (cie_off != 0xffffffff) + return false; + readCIE(entry, q); + entry.initial_instructions_length = qend - entry.initial_instructions; + + entry.segment = (unsigned long)(entry.segment_size > 0 ? RDsize(p, entry.segment_size) : 0); + entry.initial_location = (unsigned long)RDsize(p, entry.address_size); + entry.address_range = (unsigned long)RDsize(p, entry.address_size); + entry.instructions = p; + entry.instructions_length = entry.end - p; + } + ptr = entry.end; + return true; + } +}; + +class CFACursor +{ +public: + CFACursor(const CFIEntry& cfientry, unsigned long location) + : entry (cfientry) + { + loc = location; + cfa = { Location::RegRel, DW_REG_CFA, 0 }; + setInstructions(entry.initial_instructions, entry.initial_instructions_length); + } + + void setInstructions(byte* instructions, int length) + { + beg = instructions; + end = instructions + length; + ptr = beg; + } + + bool beforeRestore() + { + if(ptr >= end) + return false; + byte instr = *ptr; + if ((instr & 0xc0) == DW_CFA_restore || instr == DW_CFA_restore_extended || instr == DW_CFA_restore_state) + return true; + return false; + } + + bool processNext() + { + if(ptr >= end) + return false; + byte instr = *ptr++; + int reg, off; + + switch(instr & 0xc0) + { + case DW_CFA_advance_loc: + loc += (instr & 0x3f) * entry.code_alignment_factor; + break; + case DW_CFA_offset: + reg = instr & 0x3f; // set register rule to "factored offset" + off = LEB128(ptr) * entry.data_alignment_factor; + break; + case DW_CFA_restore: + reg = instr & 0x3f; // restore register to initial state + break; + + case DW_CFA_extended: + switch(instr) + { + case DW_CFA_set_loc: + loc = RDsize(ptr, entry.address_size); + break; + case DW_CFA_advance_loc1: + loc = *ptr++; + break; + case DW_CFA_advance_loc2: + loc = RDsize(ptr, 2); + break; + case DW_CFA_advance_loc4: + loc = RDsize(ptr, 4); + break; + + case DW_CFA_def_cfa: + cfa.reg = LEB128(ptr); + cfa.off = LEB128(ptr); + break; + case DW_CFA_def_cfa_sf: + cfa.reg = LEB128(ptr); + cfa.off = SLEB128(ptr) * entry.data_alignment_factor; + break; + case DW_CFA_def_cfa_register: + cfa.reg = LEB128(ptr); + break; + case DW_CFA_def_cfa_offset: + cfa.off = LEB128(ptr); + break; + case DW_CFA_def_cfa_offset_sf: + cfa.off = SLEB128(ptr) * entry.data_alignment_factor; + break; + case DW_CFA_def_cfa_expression: + { + DWARF_Attribute attr; + attr.type = ExprLoc; + attr.expr.len = LEB128(ptr); + attr.expr.ptr = ptr; + cfa = decodeLocation(attr); + ptr += attr.expr.len; + break; + } + + case DW_CFA_undefined: + reg = LEB128(ptr); // set register rule to "undefined" + break; + case DW_CFA_same_value: + reg = LEB128(ptr); // set register rule to "same value" + break; + case DW_CFA_offset_extended: + reg = LEB128(ptr); // set register rule to "factored offset" + off = LEB128(ptr) * entry.data_alignment_factor; + break; + case DW_CFA_offset_extended_sf: + reg = LEB128(ptr); // set register rule to "factored offset" + off = SLEB128(ptr) * entry.data_alignment_factor; + break; + case DW_CFA_val_offset: + reg = LEB128(ptr); // set register rule to "val offset" + off = LEB128(ptr) * entry.data_alignment_factor; + break; + case DW_CFA_val_offset_sf: + reg = LEB128(ptr); // set register rule to "val offset" + off = SLEB128(ptr) * entry.data_alignment_factor; + break; + case DW_CFA_register: + reg = LEB128(ptr); // set register rule to "register" + reg = LEB128(ptr); + break; + case DW_CFA_expression: + case DW_CFA_val_expression: + { + reg = LEB128(ptr); // set register rule to "expression" + DWARF_Attribute attr; + attr.type = Block; + attr.block.len = LEB128(ptr); + attr.block.ptr = ptr; + cfa = decodeLocation(attr); // TODO: push cfa on stack + ptr += attr.expr.len; + break; + } + case DW_CFA_restore_extended: + reg = LEB128(ptr); // restore register to initial state + break; + + case DW_CFA_remember_state: + case DW_CFA_restore_state: + case DW_CFA_nop: + break; + } + } + return true; + } + + const CFIEntry& entry; + byte* beg; + byte* end; + byte* ptr; + + unsigned long long loc; + Location cfa; +}; + +Location findBestCFA(const PEImage& img, unsigned int pclo, unsigned int pchi) +{ + bool x64 = img.isX64(); + Location ebp = { Location::RegRel, x64 ? 6 : 5, x64 ? 16 : 8 }; + if (!img.debug_frame) + return ebp; + + CFIEntry entry; + CFICursor cursor(img); + while(cursor.readNext(entry)) + { + if (entry.type == CFIEntry::FDE && + entry.initial_location <= pclo && entry.initial_location + entry.address_range >= pchi) + { + CFACursor cfa(entry, pclo); + while(cfa.processNext()) {} + cfa.setInstructions(entry.instructions, entry.instructions_length); + while(!cfa.beforeRestore() && cfa.processNext()) {} + return cfa.cfa; + } + } + return ebp; +} + +// Location list entry +class LOCEntry +{ +public: + byte* ptr; + unsigned long beg_offset; + unsigned long end_offset; + Location loc; + + bool eol() const { return beg_offset == 0 && end_offset == 0; } +}; + +// Location list cursor +class LOCCursor +{ +public: + LOCCursor(const PEImage& img, DWARF_CompilationUnit* cu, unsigned long off) + : beg((byte*)img.debug_loc) + , end((byte*)img.debug_loc + img.debug_loc_length) + , ptr(beg + off) + { + default_address_size = img.isX64() ? 8 : 4; + } + + byte* beg; + byte* end; + byte* ptr; + byte default_address_size; + + bool readNext(LOCEntry& entry) + { + if(ptr >= end) + return false; + entry.beg_offset = (unsigned long) RDsize(ptr, default_address_size); + entry.end_offset = (unsigned long) RDsize(ptr, default_address_size); + if (entry.eol()) + return true; + + DWARF_Attribute attr; + attr.type = Block; + attr.block.len = RD2(ptr); + attr.block.ptr = ptr; + entry.loc = decodeLocation(attr); + ptr += attr.expr.len; + return true; + } +}; + +Location findBestFBLoc(const PEImage& img, DWARF_CompilationUnit* cu, unsigned long fblocoff) +{ + int regebp = img.isX64() ? 6 : 5; + LOCCursor cursor(img, cu, fblocoff); + LOCEntry entry; + Location longest = { Location::RegRel, DW_REG_CFA, 0 }; + unsigned long longest_range = 0; + while(cursor.readNext(entry) && !entry.eol()) + { + if(entry.loc.is_regrel() && entry.loc.reg == regebp) + return entry.loc; + unsigned long range = entry.end_offset - entry.beg_offset; + if(range > longest_range) + { + longest_range = range; + longest = entry.loc; + } + } + return longest; +} + +void CV2PDB::appendStackVar(const char* name, int type, Location& loc, Location& cfa) { unsigned int len; unsigned int align = 4; @@ -163,17 +548,18 @@ void CV2PDB::appendStackVar(const char* name, int type, Location& loc) codeview_symbol*cvs = (codeview_symbol*) (udtSymbols + cbUdtSymbols); + int reg = loc.reg; int off = loc.off; CV_X86_REG baseReg; - if (loc.reg == DW_REG_CFA) + if (reg == DW_REG_CFA) { - baseReg = (img.isX64() ? CV_AMD64_RBP : CV_REG_EBP); - off += (img.isX64() ? 16 : 8); + reg = cfa.reg; + off += cfa.off; } - else if (img.isX64()) - baseReg = dwarf_to_amd64_reg(loc.reg); + if (img.isX64()) + baseReg = dwarf_to_amd64_reg(reg); else - baseReg = dwarf_to_x86_reg(loc.reg); + baseReg = dwarf_to_x86_reg(reg); if (baseReg == CV_REG_NONE) return; @@ -318,16 +704,16 @@ bool CV2PDB::addDWARFProc(DWARF_InfoData& procid, DWARF_CompilationUnit* cu, DIE #endif Location frameBase = decodeLocation(procid.frame_base, 0, DW_AT_frame_base); - if (frameBase.is_abs()) // pointer into location list in .debug_loc? - // assume standard ebp stack frame, we cannot have different locations anyway - frameBase = Location{ Location::RegRel, DW_REG_CFA, 0 }; + if (frameBase.is_abs()) // pointer into location list in .debug_loc? assume CFA + frameBase = findBestFBLoc(img, cu, frameBase.off); + + Location cfa = findBestCFA(img, procid.pclo, procid.pchi); if (cu) { bool endarg = false; DWARF_InfoData id; int off = 8; - int cvid; DIECursor prev = cursor; while (cursor.readNext(id, true) && id.tag == DW_TAG_formal_parameter) @@ -338,7 +724,7 @@ bool CV2PDB::addDWARFProc(DWARF_InfoData& procid, DWARF_CompilationUnit* cu, DIE { Location loc = decodeLocation(id.location, &frameBase); if (loc.is_regrel()) - appendStackVar(id.name, getTypeByDWARFPtr(cu, id.type), loc); + appendStackVar(id.name, getTypeByDWARFPtr(cu, id.type), loc, cfa); } } prev = cursor; @@ -373,7 +759,7 @@ bool CV2PDB::addDWARFProc(DWARF_InfoData& procid, DWARF_CompilationUnit* cu, DIE { Location loc = decodeLocation(id.location, &frameBase); if (loc.is_regrel()) - appendStackVar(id.name, getTypeByDWARFPtr(cu, id.type), loc); + appendStackVar(id.name, getTypeByDWARFPtr(cu, id.type), loc, cfa); } } cursor.gotoSibling(); @@ -452,7 +838,7 @@ int CV2PDB::addDWARFStructure(DWARF_InfoData& structid, DWARF_CompilationUnit* c int off; Location loc = decodeLocation(id.member_location, 0, DW_AT_data_member_location); if (loc.is_abs()) - { + { cvid = S_CONSTANT_V2; off = loc.off; } @@ -490,7 +876,7 @@ int CV2PDB::addDWARFStructure(DWARF_InfoData& structid, DWARF_CompilationUnit* c return cvtype; } -int CV2PDB::getDWARFArrayBounds(DWARF_InfoData& arrayid, DWARF_CompilationUnit* cu, +int CV2PDB::getDWARFArrayBounds(DWARF_InfoData& arrayid, DWARF_CompilationUnit* cu, DIECursor cursor, int& upperBound) { int lowerBound = 0; @@ -512,11 +898,11 @@ int CV2PDB::getDWARFArrayBounds(DWARF_InfoData& arrayid, DWARF_CompilationUnit* return lowerBound; } -int CV2PDB::addDWARFArray(DWARF_InfoData& arrayid, DWARF_CompilationUnit* cu, +int CV2PDB::addDWARFArray(DWARF_InfoData& arrayid, DWARF_CompilationUnit* cu, DIECursor cursor) { int upperBound, lowerBound = getDWARFArrayBounds(arrayid, cu, cursor, upperBound); - + checkUserTypeAlloc(kMaxNameLen + 100); codeview_type* cvt = (codeview_type*) (userTypes + cbUserTypes); @@ -562,7 +948,7 @@ bool CV2PDB::addDWARFTypes() // COMPILAND cvs = (codeview_symbol*) (data + off); cvs->compiland_v1.id = S_COMPILAND_V1; - cvs->compiland_v1.unknown = 0x800100; // ?, 0x100: C++, + cvs->compiland_v1.unknown = 0x800100; // ?, 0x100: C++, cvs->compiland_v1.unknown |= img.isX64() ? 0xd0 : 6; //0x06: Pentium Pro/II, 0xd0: x64 len = sizeof(cvs->compiland_v1) - sizeof(cvs->compiland_v1.p_name); len += c2p("cv2pdb", cvs->compiland_v1.p_name); -- cgit v0.12