//===--------------------------- DwarfParser.hpp --------------------------===// // // The LLVM Compiler Infrastructure // // This file is dual licensed under the MIT and the University of Illinois Open // Source Licenses. See LICENSE.TXT for details. // // // Parses DWARF CFIs (FDEs and CIEs). // //===----------------------------------------------------------------------===// #ifndef __DWARF_PARSER_HPP__ #define __DWARF_PARSER_HPP__ #include #include #include #include #include "libunwind.h" #include "dwarf2.h" #include "AddressSpace.hpp" namespace libunwind { /// CFI_Parser does basic parsing of a CFI (Call Frame Information) records. /// See Dwarf Spec for details: /// http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html /// template class CFI_Parser { public: typedef typename A::pint_t pint_t; /// Information encoded in a CIE (Common Information Entry) struct CIE_Info { pint_t cieStart; pint_t cieLength; pint_t cieInstructions; uint8_t pointerEncoding; uint8_t lsdaEncoding; uint8_t personalityEncoding; uint8_t personalityOffsetInCIE; pint_t personality; uint32_t codeAlignFactor; int dataAlignFactor; bool isSignalFrame; bool fdesHaveAugmentationData; }; /// Information about an FDE (Frame Description Entry) struct FDE_Info { pint_t fdeStart; pint_t fdeLength; pint_t fdeInstructions; pint_t pcStart; pint_t pcEnd; pint_t lsda; }; enum { kMaxRegisterNumber = 120 }; enum RegisterSavedWhere { kRegisterUnused, kRegisterInCFA, kRegisterOffsetFromCFA, kRegisterInRegister, kRegisterAtExpression, kRegisterIsExpression }; struct RegisterLocation { RegisterSavedWhere location; int64_t value; }; /// Information about a frame layout and registers saved determined /// by "running" the dwarf FDE "instructions" struct PrologInfo { uint32_t cfaRegister; int32_t cfaRegisterOffset; // CFA = (cfaRegister)+cfaRegisterOffset int64_t cfaExpression; // CFA = expression uint32_t spExtraArgSize; uint32_t codeOffsetAtStackDecrement; bool registersInOtherRegisters; bool sameValueUsed; RegisterLocation savedRegisters[kMaxRegisterNumber]; }; struct PrologInfoStackEntry { PrologInfoStackEntry(PrologInfoStackEntry *n, const PrologInfo &i) : next(n), info(i) {} PrologInfoStackEntry *next; PrologInfo info; }; static bool findFDE(A &addressSpace, pint_t pc, pint_t ehSectionStart, uint32_t sectionLength, pint_t fdeHint, FDE_Info *fdeInfo, CIE_Info *cieInfo); static const char *decodeFDE(A &addressSpace, pint_t fdeStart, FDE_Info *fdeInfo, CIE_Info *cieInfo); static bool parseFDEInstructions(A &addressSpace, const FDE_Info &fdeInfo, const CIE_Info &cieInfo, pint_t upToPC, PrologInfo *results); static const char *parseCIE(A &addressSpace, pint_t cie, CIE_Info *cieInfo); private: static bool parseInstructions(A &addressSpace, pint_t instructions, pint_t instructionsEnd, const CIE_Info &cieInfo, pint_t pcoffset, PrologInfoStackEntry *&rememberStack, PrologInfo *results); }; /// Parse a FDE into a CIE_Info and an FDE_Info template const char *CFI_Parser::decodeFDE(A &addressSpace, pint_t fdeStart, FDE_Info *fdeInfo, CIE_Info *cieInfo) { pint_t p = fdeStart; pint_t cfiLength = (pint_t)addressSpace.get32(p); p += 4; if (cfiLength == 0xffffffff) { // 0xffffffff means length is really next 8 bytes cfiLength = (pint_t)addressSpace.get64(p); p += 8; } if (cfiLength == 0) return "FDE has zero length"; // end marker uint32_t ciePointer = addressSpace.get32(p); if (ciePointer == 0) return "FDE is really a CIE"; // this is a CIE not an FDE pint_t nextCFI = p + cfiLength; pint_t cieStart = p - ciePointer; const char *err = parseCIE(addressSpace, cieStart, cieInfo); if (err != NULL) return err; p += 4; // parse pc begin and range pint_t pcStart = addressSpace.getEncodedP(p, nextCFI, cieInfo->pointerEncoding); pint_t pcRange = addressSpace.getEncodedP(p, nextCFI, cieInfo->pointerEncoding & 0x0F); // parse rest of info fdeInfo->lsda = 0; // check for augmentation length if (cieInfo->fdesHaveAugmentationData) { pint_t augLen = (pint_t)addressSpace.getULEB128(p, nextCFI); pint_t endOfAug = p + augLen; if (cieInfo->lsdaEncoding != 0) { // peek at value (without indirection). Zero means no lsda pint_t lsdaStart = p; if (addressSpace.getEncodedP(p, nextCFI, cieInfo->lsdaEncoding & 0x0F) != 0) { // reset pointer and re-parse lsda address p = lsdaStart; fdeInfo->lsda = addressSpace.getEncodedP(p, nextCFI, cieInfo->lsdaEncoding); } } p = endOfAug; } fdeInfo->fdeStart = fdeStart; fdeInfo->fdeLength = nextCFI - fdeStart; fdeInfo->fdeInstructions = p; fdeInfo->pcStart = pcStart; fdeInfo->pcEnd = pcStart + pcRange; return NULL; // success } /// Scan an eh_frame section to find an FDE for a pc template bool CFI_Parser::findFDE(A &addressSpace, pint_t pc, pint_t ehSectionStart, uint32_t sectionLength, pint_t fdeHint, FDE_Info *fdeInfo, CIE_Info *cieInfo) { //fprintf(stderr, "findFDE(0x%llX)\n", (long long)pc); pint_t p = (fdeHint != 0) ? fdeHint : ehSectionStart; const pint_t ehSectionEnd = p + sectionLength; while (p < ehSectionEnd) { pint_t currentCFI = p; //fprintf(stderr, "findFDE() CFI at 0x%llX\n", (long long)p); pint_t cfiLength = addressSpace.get32(p); p += 4; if (cfiLength == 0xffffffff) { // 0xffffffff means length is really next 8 bytes cfiLength = (pint_t)addressSpace.get64(p); p += 8; } if (cfiLength == 0) return false; // end marker uint32_t id = addressSpace.get32(p); if (id == 0) { // skip over CIEs p += cfiLength; } else { // process FDE to see if it covers pc pint_t nextCFI = p + cfiLength; uint32_t ciePointer = addressSpace.get32(p); pint_t cieStart = p - ciePointer; // validate pointer to CIE is within section if ((ehSectionStart <= cieStart) && (cieStart < ehSectionEnd)) { if (parseCIE(addressSpace, cieStart, cieInfo) == NULL) { p += 4; // parse pc begin and range pint_t pcStart = addressSpace.getEncodedP(p, nextCFI, cieInfo->pointerEncoding); pint_t pcRange = addressSpace.getEncodedP( p, nextCFI, cieInfo->pointerEncoding & 0x0F); // test if pc is within the function this FDE covers if ((pcStart < pc) && (pc <= pcStart + pcRange)) { // parse rest of info fdeInfo->lsda = 0; // check for augmentation length if (cieInfo->fdesHaveAugmentationData) { pint_t augLen = (pint_t)addressSpace.getULEB128(p, nextCFI); pint_t endOfAug = p + augLen; if (cieInfo->lsdaEncoding != 0) { // peek at value (without indirection). Zero means no lsda pint_t lsdaStart = p; if (addressSpace.getEncodedP( p, nextCFI, cieInfo->lsdaEncoding & 0x0F) != 0) { // reset pointer and re-parse lsda address p = lsdaStart; fdeInfo->lsda = addressSpace .getEncodedP(p, nextCFI, cieInfo->lsdaEncoding); } } p = endOfAug; } fdeInfo->fdeStart = currentCFI; fdeInfo->fdeLength = nextCFI - currentCFI; fdeInfo->fdeInstructions = p; fdeInfo->pcStart = pcStart; fdeInfo->pcEnd = pcStart + pcRange; return true; } else { // pc is not in begin/range, skip this FDE } } else { // malformed CIE, now augmentation describing pc range encoding } } else { // malformed FDE. CIE is bad } p = nextCFI; } } return false; } /// Extract info from a CIE template const char *CFI_Parser::parseCIE(A &addressSpace, pint_t cie, CIE_Info *cieInfo) { cieInfo->pointerEncoding = 0; cieInfo->lsdaEncoding = 0; cieInfo->personalityEncoding = 0; cieInfo->personalityOffsetInCIE = 0; cieInfo->personality = 0; cieInfo->codeAlignFactor = 0; cieInfo->dataAlignFactor = 0; cieInfo->isSignalFrame = false; cieInfo->fdesHaveAugmentationData = false; cieInfo->cieStart = cie; pint_t p = cie; pint_t cieLength = (pint_t)addressSpace.get32(p); p += 4; pint_t cieContentEnd = p + cieLength; if (cieLength == 0xffffffff) { // 0xffffffff means length is really next 8 bytes cieLength = (pint_t)addressSpace.get64(p); p += 8; cieContentEnd = p + cieLength; } if (cieLength == 0) return NULL; // CIE ID is always 0 if (addressSpace.get32(p) != 0) return "CIE ID is not zero"; p += 4; // Version is always 1 or 3 uint8_t version = addressSpace.get8(p); if ((version != 1) && (version != 3)) return "CIE version is not 1 or 3"; ++p; // save start of augmentation string and find end pint_t strStart = p; while (addressSpace.get8(p) != 0) ++p; ++p; // parse code aligment factor cieInfo->codeAlignFactor = (uint32_t)addressSpace.getULEB128(p, cieContentEnd); // parse data alignment factor cieInfo->dataAlignFactor = (int)addressSpace.getSLEB128(p, cieContentEnd); // parse return address register addressSpace.getULEB128(p, cieContentEnd); // parse augmentation data based on augmentation string const char *result = NULL; if (addressSpace.get8(strStart) == 'z') { // parse augmentation data length addressSpace.getULEB128(p, cieContentEnd); for (pint_t s = strStart; addressSpace.get8(s) != '\0'; ++s) { switch (addressSpace.get8(s)) { case 'z': cieInfo->fdesHaveAugmentationData = true; break; case 'P': cieInfo->personalityEncoding = addressSpace.get8(p); ++p; cieInfo->personalityOffsetInCIE = (uint8_t)(p - cie); cieInfo->personality = addressSpace .getEncodedP(p, cieContentEnd, cieInfo->personalityEncoding); break; case 'L': cieInfo->lsdaEncoding = addressSpace.get8(p); ++p; break; case 'R': cieInfo->pointerEncoding = addressSpace.get8(p); ++p; break; case 'S': cieInfo->isSignalFrame = true; break; default: // ignore unknown letters break; } } } cieInfo->cieLength = cieContentEnd - cieInfo->cieStart; cieInfo->cieInstructions = p; return result; } /// "run" the dwarf instructions and create the abstact PrologInfo for an FDE template bool CFI_Parser::parseFDEInstructions(A &addressSpace, const FDE_Info &fdeInfo, const CIE_Info &cieInfo, pint_t upToPC, PrologInfo *results) { // clear results bzero(results, sizeof(PrologInfo)); PrologInfoStackEntry *rememberStack = NULL; // parse CIE then FDE instructions return parseInstructions(addressSpace, cieInfo.cieInstructions, cieInfo.cieStart + cieInfo.cieLength, cieInfo, (pint_t)(-1), rememberStack, results) && parseInstructions(addressSpace, fdeInfo.fdeInstructions, fdeInfo.fdeStart + fdeInfo.fdeLength, cieInfo, upToPC - fdeInfo.pcStart, rememberStack, results); } /// "run" the dwarf instructions template bool CFI_Parser::parseInstructions(A &addressSpace, pint_t instructions, pint_t instructionsEnd, const CIE_Info &cieInfo, pint_t pcoffset, PrologInfoStackEntry *&rememberStack, PrologInfo *results) { const bool logDwarf = false; pint_t p = instructions; pint_t codeOffset = 0; PrologInfo initialState = *results; if (logDwarf) fprintf(stderr, "parseInstructions(instructions=0x%0llX)\n", (uint64_t) instructionsEnd); // see Dwarf Spec, section 6.4.2 for details on unwind opcodes while ((p < instructionsEnd) && (codeOffset < pcoffset)) { uint64_t reg; uint64_t reg2; int64_t offset; uint64_t length; uint8_t opcode = addressSpace.get8(p); uint8_t operand; PrologInfoStackEntry *entry; ++p; switch (opcode) { case DW_CFA_nop: if (logDwarf) fprintf(stderr, "DW_CFA_nop\n"); break; case DW_CFA_set_loc: codeOffset = addressSpace.getEncodedP(p, instructionsEnd, cieInfo.pointerEncoding); if (logDwarf) fprintf(stderr, "DW_CFA_set_loc\n"); break; case DW_CFA_advance_loc1: codeOffset += (addressSpace.get8(p) * cieInfo.codeAlignFactor); p += 1; if (logDwarf) fprintf(stderr, "DW_CFA_advance_loc1: new offset=%llu\n", (uint64_t)codeOffset); break; case DW_CFA_advance_loc2: codeOffset += (addressSpace.get16(p) * cieInfo.codeAlignFactor); p += 2; if (logDwarf) fprintf(stderr, "DW_CFA_advance_loc2: new offset=%llu\n", (uint64_t)codeOffset); break; case DW_CFA_advance_loc4: codeOffset += (addressSpace.get32(p) * cieInfo.codeAlignFactor); p += 4; if (logDwarf) fprintf(stderr, "DW_CFA_advance_loc4: new offset=%llu\n", (uint64_t)codeOffset); break; case DW_CFA_offset_extended: reg = addressSpace.getULEB128(p, instructionsEnd); offset = (int64_t)addressSpace.getULEB128(p, instructionsEnd) * cieInfo.dataAlignFactor; if (reg > kMaxRegisterNumber) { fprintf(stderr, "malformed DW_CFA_offset_extended dwarf unwind, reg too big\n"); return false; } results->savedRegisters[reg].location = kRegisterInCFA; results->savedRegisters[reg].value = offset; if (logDwarf) fprintf(stderr, "DW_CFA_offset_extended(reg=%lld, offset=%lld)\n", reg, offset); break; case DW_CFA_restore_extended: reg = addressSpace.getULEB128(p, instructionsEnd); ; if (reg > kMaxRegisterNumber) { fprintf( stderr, "malformed DW_CFA_restore_extended dwarf unwind, reg too big\n"); return false; } results->savedRegisters[reg] = initialState.savedRegisters[reg]; if (logDwarf) fprintf(stderr, "DW_CFA_restore_extended(reg=%lld)\n", reg); break; case DW_CFA_undefined: reg = addressSpace.getULEB128(p, instructionsEnd); if (reg > kMaxRegisterNumber) { fprintf(stderr, "malformed DW_CFA_undefined dwarf unwind, reg too big\n"); return false; } results->savedRegisters[reg].location = kRegisterUnused; if (logDwarf) fprintf(stderr, "DW_CFA_undefined(reg=%lld)\n", reg); break; case DW_CFA_same_value: reg = addressSpace.getULEB128(p, instructionsEnd); if (reg > kMaxRegisterNumber) { fprintf(stderr, "malformed DW_CFA_same_value dwarf unwind, reg too big\n"); return false; } // DW_CFA_same_value unsupported // "same value" means register was stored in frame, but its current // value has not changed, so no need to restore from frame. // We model this as if the register was never saved. results->savedRegisters[reg].location = kRegisterUnused; // set flag to disable conversion to compact unwind results->sameValueUsed = true; if (logDwarf) fprintf(stderr, "DW_CFA_same_value(reg=%lld)\n", reg); break; case DW_CFA_register: reg = addressSpace.getULEB128(p, instructionsEnd); reg2 = addressSpace.getULEB128(p, instructionsEnd); if (reg > kMaxRegisterNumber) { fprintf(stderr, "malformed DW_CFA_register dwarf unwind, reg too big\n"); return false; } if (reg2 > kMaxRegisterNumber) { fprintf(stderr, "malformed DW_CFA_register dwarf unwind, reg2 too big\n"); return false; } results->savedRegisters[reg].location = kRegisterInRegister; results->savedRegisters[reg].value = (int64_t)reg2; // set flag to disable conversion to compact unwind results->registersInOtherRegisters = true; if (logDwarf) fprintf(stderr, "DW_CFA_register(reg=%lld, reg2=%lld)\n", reg, reg2); break; case DW_CFA_remember_state: // avoid operator new, because that would be an upward dependency entry = (PrologInfoStackEntry *)malloc(sizeof(PrologInfoStackEntry)); if (entry != NULL) { entry->next = rememberStack; entry->info = *results; rememberStack = entry; } else { return false; } if (logDwarf) fprintf(stderr, "DW_CFA_remember_state\n"); break; case DW_CFA_restore_state: if (rememberStack != NULL) { PrologInfoStackEntry *top = rememberStack; *results = top->info; rememberStack = top->next; free((char *)top); } else { return false; } if (logDwarf) fprintf(stderr, "DW_CFA_restore_state\n"); break; case DW_CFA_def_cfa: reg = addressSpace.getULEB128(p, instructionsEnd); offset = (int64_t)addressSpace.getULEB128(p, instructionsEnd); if (reg > kMaxRegisterNumber) { fprintf(stderr, "malformed DW_CFA_def_cfa dwarf unwind, reg too big\n"); return false; } results->cfaRegister = (uint32_t)reg; results->cfaRegisterOffset = (int32_t)offset; if (logDwarf) fprintf(stderr, "DW_CFA_def_cfa(reg=%lld, offset=%lld)\n", reg, offset); break; case DW_CFA_def_cfa_register: reg = addressSpace.getULEB128(p, instructionsEnd); if (reg > kMaxRegisterNumber) { fprintf( stderr, "malformed DW_CFA_def_cfa_register dwarf unwind, reg too big\n"); return false; } results->cfaRegister = (uint32_t)reg; if (logDwarf) fprintf(stderr, "DW_CFA_def_cfa_register(%lld)\n", reg); break; case DW_CFA_def_cfa_offset: results->cfaRegisterOffset = (int32_t) addressSpace.getULEB128(p, instructionsEnd); results->codeOffsetAtStackDecrement = (uint32_t)codeOffset; if (logDwarf) fprintf(stderr, "DW_CFA_def_cfa_offset(%d)\n", results->cfaRegisterOffset); break; case DW_CFA_def_cfa_expression: results->cfaRegister = 0; results->cfaExpression = (int64_t)p; length = addressSpace.getULEB128(p, instructionsEnd); p += length; if (logDwarf) fprintf(stderr, "DW_CFA_def_cfa_expression(expression=0x%llX, length=%llu)\n", results->cfaExpression, length); break; case DW_CFA_expression: reg = addressSpace.getULEB128(p, instructionsEnd); if (reg > kMaxRegisterNumber) { fprintf(stderr, "malformed DW_CFA_expression dwarf unwind, reg too big\n"); return false; } results->savedRegisters[reg].location = kRegisterAtExpression; results->savedRegisters[reg].value = (int64_t)p; length = addressSpace.getULEB128(p, instructionsEnd); p += length; if (logDwarf) fprintf(stderr, "DW_CFA_expression(reg=%lld, expression=0x%llX, length=%llu)\n", reg, results->savedRegisters[reg].value, length); break; case DW_CFA_offset_extended_sf: reg = addressSpace.getULEB128(p, instructionsEnd); if (reg > kMaxRegisterNumber) { fprintf( stderr, "malformed DW_CFA_offset_extended_sf dwarf unwind, reg too big\n"); return false; } offset = addressSpace.getSLEB128(p, instructionsEnd) * cieInfo.dataAlignFactor; results->savedRegisters[reg].location = kRegisterInCFA; results->savedRegisters[reg].value = offset; if (logDwarf) fprintf(stderr, "DW_CFA_offset_extended_sf(reg=%lld, offset=%lld)\n", reg, offset); break; case DW_CFA_def_cfa_sf: reg = addressSpace.getULEB128(p, instructionsEnd); offset = addressSpace.getSLEB128(p, instructionsEnd) * cieInfo.dataAlignFactor; if (reg > kMaxRegisterNumber) { fprintf(stderr, "malformed DW_CFA_def_cfa_sf dwarf unwind, reg too big\n"); return false; } results->cfaRegister = (uint32_t)reg; results->cfaRegisterOffset = (int32_t)offset; if (logDwarf) fprintf(stderr, "DW_CFA_def_cfa_sf(reg=%lld, offset=%lld)\n", reg, offset); break; case DW_CFA_def_cfa_offset_sf: results->cfaRegisterOffset = (int32_t) (addressSpace.getSLEB128(p, instructionsEnd) * cieInfo.dataAlignFactor); results->codeOffsetAtStackDecrement = (uint32_t)codeOffset; if (logDwarf) fprintf(stderr, "DW_CFA_def_cfa_offset_sf(%d)\n", results->cfaRegisterOffset); break; case DW_CFA_val_offset: reg = addressSpace.getULEB128(p, instructionsEnd); offset = (int64_t)addressSpace.getULEB128(p, instructionsEnd) * cieInfo.dataAlignFactor; results->savedRegisters[reg].location = kRegisterOffsetFromCFA; results->savedRegisters[reg].value = offset; if (logDwarf) fprintf(stderr, "DW_CFA_val_offset(reg=%lld, offset=%lld\n", reg, offset); break; case DW_CFA_val_offset_sf: reg = addressSpace.getULEB128(p, instructionsEnd); if (reg > kMaxRegisterNumber) { fprintf(stderr, "malformed DW_CFA_val_offset_sf dwarf unwind, reg too big\n"); return false; } offset = addressSpace.getSLEB128(p, instructionsEnd) * cieInfo.dataAlignFactor; results->savedRegisters[reg].location = kRegisterOffsetFromCFA; results->savedRegisters[reg].value = offset; if (logDwarf) fprintf(stderr, "DW_CFA_val_offset_sf(reg=%lld, offset=%lld\n", reg, offset); break; case DW_CFA_val_expression: reg = addressSpace.getULEB128(p, instructionsEnd); if (reg > kMaxRegisterNumber) { fprintf(stderr, "malformed DW_CFA_val_expression dwarf unwind, reg too big\n"); return false; } results->savedRegisters[reg].location = kRegisterIsExpression; results->savedRegisters[reg].value = (int64_t)p; length = addressSpace.getULEB128(p, instructionsEnd); p += length; if (logDwarf) fprintf( stderr, "DW_CFA_val_expression(reg=%lld, expression=0x%llX, length=%lld)\n", reg, results->savedRegisters[reg].value, length); break; case DW_CFA_GNU_args_size: length = addressSpace.getULEB128(p, instructionsEnd); results->spExtraArgSize = (uint32_t)length; if (logDwarf) fprintf(stderr, "DW_CFA_GNU_args_size(%lld)\n", length); break; case DW_CFA_GNU_negative_offset_extended: reg = addressSpace.getULEB128(p, instructionsEnd); if (reg > kMaxRegisterNumber) { fprintf(stderr, "malformed DW_CFA_GNU_negative_offset_extended dwarf " "unwind, reg too big\n"); return false; } offset = (int64_t)addressSpace.getULEB128(p, instructionsEnd) * cieInfo.dataAlignFactor; results->savedRegisters[reg].location = kRegisterInCFA; results->savedRegisters[reg].value = -offset; if (logDwarf) fprintf(stderr, "DW_CFA_GNU_negative_offset_extended(%lld)\n", offset); break; default: operand = opcode & 0x3F; switch (opcode & 0xC0) { case DW_CFA_offset: reg = operand; offset = (int64_t)addressSpace.getULEB128(p, instructionsEnd) * cieInfo.dataAlignFactor; results->savedRegisters[reg].location = kRegisterInCFA; results->savedRegisters[reg].value = offset; if (logDwarf) fprintf(stderr, "DW_CFA_offset(reg=%d, offset=%lld)\n", operand, offset); break; case DW_CFA_advance_loc: codeOffset += operand * cieInfo.codeAlignFactor; if (logDwarf) fprintf(stderr, "DW_CFA_advance_loc: new offset=%llu\n", (uint64_t)codeOffset); break; case DW_CFA_restore: reg = operand; results->savedRegisters[reg] = initialState.savedRegisters[reg]; if (logDwarf) fprintf(stderr, "DW_CFA_restore(reg=%lld)\n", reg); break; default: if (logDwarf) fprintf(stderr, "unknown CFA opcode 0x%02X\n", opcode); return false; } } } return true; } } // namespace libunwind #endif // __DWARF_PARSER_HPP__