Summary: Copying all of the saved register state on every entry to parseInstruction is a severe performance contraint, especially because most of this saved state is never used. On x86 linux this is about 560 bytes, and will be more on other platforms. When performance testing libunwind, this memcpy appears at the top of nearly all our tests. By only saving this state as needed, we see increasing in performance of around 2.5% for the ctak test here. https://github.com/clasp-developers/ctak Certain internal extremely exception-heavy tasks run in about 2/3 the time. Note that by stashing the new boolean inside what had been padding in the original structure, this uses no additional memory. Subscribers: fedor.sergeev, libcxx-commits Tags: #libc Differential Revision: https://reviews.llvm.org/D75692
809 lines
30 KiB
C++
809 lines
30 KiB
C++
//===--------------------------- DwarfParser.hpp --------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//
|
|
// Parses DWARF CFIs (FDEs and CIEs).
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef __DWARF_PARSER_HPP__
|
|
#define __DWARF_PARSER_HPP__
|
|
|
|
#include <inttypes.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "libunwind.h"
|
|
#include "dwarf2.h"
|
|
#include "Registers.hpp"
|
|
|
|
#include "config.h"
|
|
|
|
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 <typename A>
|
|
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;
|
|
uint8_t returnAddressRegister;
|
|
#if defined(_LIBUNWIND_TARGET_AARCH64)
|
|
bool addressesSignedWithBKey;
|
|
#endif
|
|
};
|
|
|
|
/// 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 = _LIBUNWIND_HIGHEST_DWARF_REGISTER
|
|
};
|
|
enum RegisterSavedWhere {
|
|
kRegisterUnused,
|
|
kRegisterInCFA,
|
|
kRegisterOffsetFromCFA,
|
|
kRegisterInRegister,
|
|
kRegisterAtExpression,
|
|
kRegisterIsExpression
|
|
};
|
|
struct RegisterLocation {
|
|
RegisterSavedWhere location;
|
|
bool initialStateSaved;
|
|
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 + 1];
|
|
enum class InitializeTime { kLazy, kNormal };
|
|
|
|
// When saving registers, this data structure is lazily initialized.
|
|
PrologInfo(InitializeTime IT = InitializeTime::kNormal) {
|
|
if (IT == InitializeTime::kNormal)
|
|
memset(this, 0, sizeof(*this));
|
|
}
|
|
void checkSaveRegister(uint64_t reg, PrologInfo &initialState) {
|
|
if (!savedRegisters[reg].initialStateSaved) {
|
|
initialState.savedRegisters[reg] = savedRegisters[reg];
|
|
savedRegisters[reg].initialStateSaved = true;
|
|
}
|
|
}
|
|
void setRegister(uint64_t reg, RegisterSavedWhere newLocation,
|
|
int64_t newValue, PrologInfo &initialState) {
|
|
checkSaveRegister(reg, initialState);
|
|
savedRegisters[reg].location = newLocation;
|
|
savedRegisters[reg].value = newValue;
|
|
}
|
|
void setRegisterLocation(uint64_t reg, RegisterSavedWhere newLocation,
|
|
PrologInfo &initialState) {
|
|
checkSaveRegister(reg, initialState);
|
|
savedRegisters[reg].location = newLocation;
|
|
}
|
|
void setRegisterValue(uint64_t reg, int64_t newValue,
|
|
PrologInfo &initialState) {
|
|
checkSaveRegister(reg, initialState);
|
|
savedRegisters[reg].value = newValue;
|
|
}
|
|
void restoreRegisterToInitialState(uint64_t reg, PrologInfo &initialState) {
|
|
if (savedRegisters[reg].initialStateSaved)
|
|
savedRegisters[reg] = initialState.savedRegisters[reg];
|
|
// else the register still holds its initial state
|
|
}
|
|
};
|
|
|
|
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,
|
|
int arch, 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, int arch,
|
|
PrologInfo *results);
|
|
};
|
|
|
|
/// Parse a FDE into a CIE_Info and an FDE_Info
|
|
template <typename A>
|
|
const char *CFI_Parser<A>::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 != DW_EH_PE_omit) {
|
|
// 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 <typename A>
|
|
bool CFI_Parser<A>::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 != DW_EH_PE_omit) {
|
|
// 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 <typename A>
|
|
const char *CFI_Parser<A>::parseCIE(A &addressSpace, pint_t cie,
|
|
CIE_Info *cieInfo) {
|
|
cieInfo->pointerEncoding = 0;
|
|
cieInfo->lsdaEncoding = DW_EH_PE_omit;
|
|
cieInfo->personalityEncoding = 0;
|
|
cieInfo->personalityOffsetInCIE = 0;
|
|
cieInfo->personality = 0;
|
|
cieInfo->codeAlignFactor = 0;
|
|
cieInfo->dataAlignFactor = 0;
|
|
cieInfo->isSignalFrame = false;
|
|
cieInfo->fdesHaveAugmentationData = false;
|
|
#if defined(_LIBUNWIND_TARGET_AARCH64)
|
|
cieInfo->addressesSignedWithBKey = false;
|
|
#endif
|
|
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
|
|
uint64_t raReg = addressSpace.getULEB128(p, cieContentEnd);
|
|
assert(raReg < 255 && "return address register too large");
|
|
cieInfo->returnAddressRegister = (uint8_t)raReg;
|
|
// 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;
|
|
#if defined(_LIBUNWIND_TARGET_AARCH64)
|
|
case 'B':
|
|
cieInfo->addressesSignedWithBKey = true;
|
|
break;
|
|
#endif
|
|
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 <typename A>
|
|
bool CFI_Parser<A>::parseFDEInstructions(A &addressSpace,
|
|
const FDE_Info &fdeInfo,
|
|
const CIE_Info &cieInfo, pint_t upToPC,
|
|
int arch, PrologInfo *results) {
|
|
PrologInfoStackEntry *rememberStack = NULL;
|
|
|
|
// parse CIE then FDE instructions
|
|
bool returnValue =
|
|
parseInstructions(addressSpace, cieInfo.cieInstructions,
|
|
cieInfo.cieStart + cieInfo.cieLength, cieInfo,
|
|
(pint_t)(-1), rememberStack, arch, results) &&
|
|
parseInstructions(addressSpace, fdeInfo.fdeInstructions,
|
|
fdeInfo.fdeStart + fdeInfo.fdeLength, cieInfo,
|
|
upToPC - fdeInfo.pcStart, rememberStack, arch, results);
|
|
|
|
// Clean up rememberStack. Even in the case where every DW_CFA_remember_state
|
|
// is paired with a DW_CFA_restore_state, parseInstructions can skip restore
|
|
// opcodes if it reaches the target PC and stops interpreting, so we have to
|
|
// make sure we don't leak memory.
|
|
while (rememberStack) {
|
|
PrologInfoStackEntry *next = rememberStack->next;
|
|
free(rememberStack);
|
|
rememberStack = next;
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
/// "run" the DWARF instructions
|
|
template <typename A>
|
|
bool CFI_Parser<A>::parseInstructions(A &addressSpace, pint_t instructions,
|
|
pint_t instructionsEnd,
|
|
const CIE_Info &cieInfo, pint_t pcoffset,
|
|
PrologInfoStackEntry *&rememberStack,
|
|
int arch, PrologInfo *results) {
|
|
pint_t p = instructions;
|
|
pint_t codeOffset = 0;
|
|
// initialState initialized as registers in results are modified. Use
|
|
// PrologInfo accessor functions to avoid reading uninitialized data.
|
|
PrologInfo initialState(PrologInfo::InitializeTime::kLazy);
|
|
|
|
_LIBUNWIND_TRACE_DWARF("parseInstructions(instructions=0x%0" PRIx64 ")\n",
|
|
static_cast<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;
|
|
#if !defined(_LIBUNWIND_NO_HEAP)
|
|
PrologInfoStackEntry *entry;
|
|
#endif
|
|
++p;
|
|
switch (opcode) {
|
|
case DW_CFA_nop:
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_nop\n");
|
|
break;
|
|
case DW_CFA_set_loc:
|
|
codeOffset =
|
|
addressSpace.getEncodedP(p, instructionsEnd, cieInfo.pointerEncoding);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_set_loc\n");
|
|
break;
|
|
case DW_CFA_advance_loc1:
|
|
codeOffset += (addressSpace.get8(p) * cieInfo.codeAlignFactor);
|
|
p += 1;
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_advance_loc1: new offset=%" PRIu64 "\n",
|
|
static_cast<uint64_t>(codeOffset));
|
|
break;
|
|
case DW_CFA_advance_loc2:
|
|
codeOffset += (addressSpace.get16(p) * cieInfo.codeAlignFactor);
|
|
p += 2;
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_advance_loc2: new offset=%" PRIu64 "\n",
|
|
static_cast<uint64_t>(codeOffset));
|
|
break;
|
|
case DW_CFA_advance_loc4:
|
|
codeOffset += (addressSpace.get32(p) * cieInfo.codeAlignFactor);
|
|
p += 4;
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_advance_loc4: new offset=%" PRIu64 "\n",
|
|
static_cast<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) {
|
|
_LIBUNWIND_LOG0(
|
|
"malformed DW_CFA_offset_extended DWARF unwind, reg too big");
|
|
return false;
|
|
}
|
|
results->setRegister(reg, kRegisterInCFA, offset, initialState);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_offset_extended(reg=%" PRIu64 ", "
|
|
"offset=%" PRId64 ")\n",
|
|
reg, offset);
|
|
break;
|
|
case DW_CFA_restore_extended:
|
|
reg = addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG0(
|
|
"malformed DW_CFA_restore_extended DWARF unwind, reg too big");
|
|
return false;
|
|
}
|
|
results->restoreRegisterToInitialState(reg, initialState);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_restore_extended(reg=%" PRIu64 ")\n", reg);
|
|
break;
|
|
case DW_CFA_undefined:
|
|
reg = addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG0(
|
|
"malformed DW_CFA_undefined DWARF unwind, reg too big");
|
|
return false;
|
|
}
|
|
results->setRegisterLocation(reg, kRegisterUnused, initialState);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_undefined(reg=%" PRIu64 ")\n", reg);
|
|
break;
|
|
case DW_CFA_same_value:
|
|
reg = addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG0(
|
|
"malformed DW_CFA_same_value DWARF unwind, reg too big");
|
|
return false;
|
|
}
|
|
// <rdar://problem/8456377> 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->setRegisterLocation(reg, kRegisterUnused, initialState);
|
|
// set flag to disable conversion to compact unwind
|
|
results->sameValueUsed = true;
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_same_value(reg=%" PRIu64 ")\n", reg);
|
|
break;
|
|
case DW_CFA_register:
|
|
reg = addressSpace.getULEB128(p, instructionsEnd);
|
|
reg2 = addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG0(
|
|
"malformed DW_CFA_register DWARF unwind, reg too big");
|
|
return false;
|
|
}
|
|
if (reg2 > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG0(
|
|
"malformed DW_CFA_register DWARF unwind, reg2 too big");
|
|
return false;
|
|
}
|
|
results->setRegister(reg, kRegisterInRegister, (int64_t)reg2,
|
|
initialState);
|
|
// set flag to disable conversion to compact unwind
|
|
results->registersInOtherRegisters = true;
|
|
_LIBUNWIND_TRACE_DWARF(
|
|
"DW_CFA_register(reg=%" PRIu64 ", reg2=%" PRIu64 ")\n", reg, reg2);
|
|
break;
|
|
#if !defined(_LIBUNWIND_NO_HEAP)
|
|
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;
|
|
}
|
|
_LIBUNWIND_TRACE_DWARF("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;
|
|
}
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_restore_state\n");
|
|
break;
|
|
#endif
|
|
case DW_CFA_def_cfa:
|
|
reg = addressSpace.getULEB128(p, instructionsEnd);
|
|
offset = (int64_t)addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG0("malformed DW_CFA_def_cfa DWARF unwind, reg too big");
|
|
return false;
|
|
}
|
|
results->cfaRegister = (uint32_t)reg;
|
|
results->cfaRegisterOffset = (int32_t)offset;
|
|
_LIBUNWIND_TRACE_DWARF(
|
|
"DW_CFA_def_cfa(reg=%" PRIu64 ", offset=%" PRIu64 ")\n", reg, offset);
|
|
break;
|
|
case DW_CFA_def_cfa_register:
|
|
reg = addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG0(
|
|
"malformed DW_CFA_def_cfa_register DWARF unwind, reg too big");
|
|
return false;
|
|
}
|
|
results->cfaRegister = (uint32_t)reg;
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_def_cfa_register(%" PRIu64 ")\n", reg);
|
|
break;
|
|
case DW_CFA_def_cfa_offset:
|
|
results->cfaRegisterOffset = (int32_t)
|
|
addressSpace.getULEB128(p, instructionsEnd);
|
|
results->codeOffsetAtStackDecrement = (uint32_t)codeOffset;
|
|
_LIBUNWIND_TRACE_DWARF("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);
|
|
assert(length < static_cast<pint_t>(~0) && "pointer overflow");
|
|
p += static_cast<pint_t>(length);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_def_cfa_expression(expression=0x%" PRIx64
|
|
", length=%" PRIu64 ")\n",
|
|
results->cfaExpression, length);
|
|
break;
|
|
case DW_CFA_expression:
|
|
reg = addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG0(
|
|
"malformed DW_CFA_expression DWARF unwind, reg too big");
|
|
return false;
|
|
}
|
|
results->setRegister(reg, kRegisterAtExpression, (int64_t)p,
|
|
initialState);
|
|
length = addressSpace.getULEB128(p, instructionsEnd);
|
|
assert(length < static_cast<pint_t>(~0) && "pointer overflow");
|
|
p += static_cast<pint_t>(length);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_expression(reg=%" PRIu64 ", "
|
|
"expression=0x%" PRIx64 ", "
|
|
"length=%" PRIu64 ")\n",
|
|
reg, results->savedRegisters[reg].value, length);
|
|
break;
|
|
case DW_CFA_offset_extended_sf:
|
|
reg = addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG0(
|
|
"malformed DW_CFA_offset_extended_sf DWARF unwind, reg too big");
|
|
return false;
|
|
}
|
|
offset =
|
|
addressSpace.getSLEB128(p, instructionsEnd) * cieInfo.dataAlignFactor;
|
|
results->setRegister(reg, kRegisterInCFA, offset, initialState);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_offset_extended_sf(reg=%" PRIu64 ", "
|
|
"offset=%" PRId64 ")\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) {
|
|
_LIBUNWIND_LOG0(
|
|
"malformed DW_CFA_def_cfa_sf DWARF unwind, reg too big");
|
|
return false;
|
|
}
|
|
results->cfaRegister = (uint32_t)reg;
|
|
results->cfaRegisterOffset = (int32_t)offset;
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_def_cfa_sf(reg=%" PRIu64 ", "
|
|
"offset=%" PRId64 ")\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;
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_def_cfa_offset_sf(%d)\n",
|
|
results->cfaRegisterOffset);
|
|
break;
|
|
case DW_CFA_val_offset:
|
|
reg = addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG(
|
|
"malformed DW_CFA_val_offset DWARF unwind, reg (%" PRIu64
|
|
") out of range\n",
|
|
reg);
|
|
return false;
|
|
}
|
|
offset = (int64_t)addressSpace.getULEB128(p, instructionsEnd)
|
|
* cieInfo.dataAlignFactor;
|
|
results->setRegister(reg, kRegisterOffsetFromCFA, offset, initialState);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_val_offset(reg=%" PRIu64 ", "
|
|
"offset=%" PRId64 "\n",
|
|
reg, offset);
|
|
break;
|
|
case DW_CFA_val_offset_sf:
|
|
reg = addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG0(
|
|
"malformed DW_CFA_val_offset_sf DWARF unwind, reg too big");
|
|
return false;
|
|
}
|
|
offset =
|
|
addressSpace.getSLEB128(p, instructionsEnd) * cieInfo.dataAlignFactor;
|
|
results->setRegister(reg, kRegisterOffsetFromCFA, offset, initialState);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_val_offset_sf(reg=%" PRIu64 ", "
|
|
"offset=%" PRId64 "\n",
|
|
reg, offset);
|
|
break;
|
|
case DW_CFA_val_expression:
|
|
reg = addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG0(
|
|
"malformed DW_CFA_val_expression DWARF unwind, reg too big");
|
|
return false;
|
|
}
|
|
results->setRegister(reg, kRegisterIsExpression, (int64_t)p,
|
|
initialState);
|
|
length = addressSpace.getULEB128(p, instructionsEnd);
|
|
assert(length < static_cast<pint_t>(~0) && "pointer overflow");
|
|
p += static_cast<pint_t>(length);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_val_expression(reg=%" PRIu64 ", "
|
|
"expression=0x%" PRIx64 ", length=%" PRIu64 ")\n",
|
|
reg, results->savedRegisters[reg].value, length);
|
|
break;
|
|
case DW_CFA_GNU_args_size:
|
|
length = addressSpace.getULEB128(p, instructionsEnd);
|
|
results->spExtraArgSize = (uint32_t)length;
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_GNU_args_size(%" PRIu64 ")\n", length);
|
|
break;
|
|
case DW_CFA_GNU_negative_offset_extended:
|
|
reg = addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG0("malformed DW_CFA_GNU_negative_offset_extended DWARF "
|
|
"unwind, reg too big");
|
|
return false;
|
|
}
|
|
offset = (int64_t)addressSpace.getULEB128(p, instructionsEnd)
|
|
* cieInfo.dataAlignFactor;
|
|
results->setRegister(reg, kRegisterInCFA, -offset, initialState);
|
|
_LIBUNWIND_TRACE_DWARF(
|
|
"DW_CFA_GNU_negative_offset_extended(%" PRId64 ")\n", offset);
|
|
break;
|
|
|
|
#if defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_SPARC)
|
|
// The same constant is used to represent different instructions on
|
|
// AArch64 (negate_ra_state) and SPARC (window_save).
|
|
static_assert(DW_CFA_AARCH64_negate_ra_state == DW_CFA_GNU_window_save,
|
|
"uses the same constant");
|
|
case DW_CFA_AARCH64_negate_ra_state:
|
|
switch (arch) {
|
|
#if defined(_LIBUNWIND_TARGET_AARCH64)
|
|
case REGISTERS_ARM64: {
|
|
int64_t value =
|
|
results->savedRegisters[UNW_ARM64_RA_SIGN_STATE].value ^ 0x1;
|
|
results->setRegisterValue(UNW_ARM64_RA_SIGN_STATE, value, initialState);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_AARCH64_negate_ra_state\n");
|
|
} break;
|
|
#endif
|
|
|
|
#if defined(_LIBUNWIND_TARGET_SPARC)
|
|
// case DW_CFA_GNU_window_save:
|
|
case REGISTERS_SPARC:
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_GNU_window_save()\n");
|
|
for (reg = UNW_SPARC_O0; reg <= UNW_SPARC_O7; reg++) {
|
|
results->setRegister(reg, kRegisterInRegister,
|
|
((int64_t)reg - UNW_SPARC_O0) + UNW_SPARC_I0,
|
|
initialState);
|
|
}
|
|
|
|
for (reg = UNW_SPARC_L0; reg <= UNW_SPARC_I7; reg++) {
|
|
results->setRegister(reg, kRegisterInCFA,
|
|
((int64_t)reg - UNW_SPARC_L0) * 4, initialState);
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
break;
|
|
#else
|
|
(void)arch;
|
|
#endif
|
|
|
|
default:
|
|
operand = opcode & 0x3F;
|
|
switch (opcode & 0xC0) {
|
|
case DW_CFA_offset:
|
|
reg = operand;
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG("malformed DW_CFA_offset DWARF unwind, reg (%" PRIu64
|
|
") out of range",
|
|
reg);
|
|
return false;
|
|
}
|
|
offset = (int64_t)addressSpace.getULEB128(p, instructionsEnd)
|
|
* cieInfo.dataAlignFactor;
|
|
results->setRegister(reg, kRegisterInCFA, offset, initialState);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_offset(reg=%d, offset=%" PRId64 ")\n",
|
|
operand, offset);
|
|
break;
|
|
case DW_CFA_advance_loc:
|
|
codeOffset += operand * cieInfo.codeAlignFactor;
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_advance_loc: new offset=%" PRIu64 "\n",
|
|
static_cast<uint64_t>(codeOffset));
|
|
break;
|
|
case DW_CFA_restore:
|
|
reg = operand;
|
|
if (reg > kMaxRegisterNumber) {
|
|
_LIBUNWIND_LOG("malformed DW_CFA_restore DWARF unwind, reg (%" PRIu64
|
|
") out of range",
|
|
reg);
|
|
return false;
|
|
}
|
|
results->restoreRegisterToInitialState(reg, initialState);
|
|
_LIBUNWIND_TRACE_DWARF("DW_CFA_restore(reg=%" PRIu64 ")\n",
|
|
static_cast<uint64_t>(operand));
|
|
break;
|
|
default:
|
|
_LIBUNWIND_TRACE_DWARF("unknown CFA opcode 0x%02X\n", opcode);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace libunwind
|
|
|
|
#endif // __DWARF_PARSER_HPP__
|