Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "LulDwarfSummariser.h"
#include "LulDwarfExt.h"
#include "mozilla/Assertions.h"
#include "mozilla/Sprintf.h"
// Set this to 1 for verbose logging
#define DEBUG_SUMMARISER 0
namespace lul {
// Do |s64|'s lowest 32 bits sign extend back to |s64| itself?
static inline bool fitsIn32Bits(int64 s64) {
return s64 == ((s64 & 0xffffffff) ^ 0x80000000) - 0x80000000;
}
// Check a LExpr prefix expression, starting at pfxInstrs[start] up to
// the next PX_End instruction, to ensure that:
// * It only mentions registers that are tracked on this target
// * The start point is sane
// If the expression is ok, return NULL. Else return a pointer
// a const char* holding a bit of text describing the problem.
static const char* checkPfxExpr(const vector<PfxInstr>* pfxInstrs,
int64_t start) {
size_t nInstrs = pfxInstrs->size();
if (start < 0 || start >= (ssize_t)nInstrs) {
return "bogus start point";
}
size_t i;
for (i = start; i < nInstrs; i++) {
PfxInstr pxi = (*pfxInstrs)[i];
if (pxi.mOpcode == PX_End) break;
if (pxi.mOpcode == PX_DwReg &&
!registerIsTracked((DW_REG_NUMBER)pxi.mOperand)) {
return "uses untracked reg";
}
}
return nullptr; // success
}
Summariser::Summariser(SecMap* aSecMap, uintptr_t aTextBias,
void (*aLog)(const char*))
: mSecMap(aSecMap), mTextBias(aTextBias), mLog(aLog) {
mCurrAddr = 0;
mMax1Addr = 0; // Gives an empty range.
// Initialise the running RuleSet to "haven't got a clue" status.
new (&mCurrRules) RuleSet();
}
void Summariser::Entry(uintptr_t aAddress, uintptr_t aLength) {
aAddress += mTextBias;
if (DEBUG_SUMMARISER) {
char buf[100];
SprintfLiteral(buf, "LUL Entry(%llx, %llu)\n",
(unsigned long long int)aAddress,
(unsigned long long int)aLength);
mLog(buf);
}
// This throws away any previous summary, that is, assumes
// that the previous summary, if any, has been properly finished
// by a call to End().
mCurrAddr = aAddress;
mMax1Addr = aAddress + aLength;
new (&mCurrRules) RuleSet();
}
void Summariser::Rule(uintptr_t aAddress, int aNewReg, LExprHow how,
int16_t oldReg, int64_t offset) {
aAddress += mTextBias;
if (DEBUG_SUMMARISER) {
char buf[100];
if (how == NODEREF || how == DEREF) {
bool deref = how == DEREF;
SprintfLiteral(buf, "LUL 0x%llx old-r%d = %sr%d + %lld%s\n",
(unsigned long long int)aAddress, aNewReg,
deref ? "*(" : "", (int)oldReg, (long long int)offset,
deref ? ")" : "");
} else if (how == PFXEXPR) {
SprintfLiteral(buf, "LUL 0x%llx old-r%d = pfx-expr-at %lld\n",
(unsigned long long int)aAddress, aNewReg,
(long long int)offset);
} else {
SprintfLiteral(buf, "LUL 0x%llx old-r%d = (invalid LExpr!)\n",
(unsigned long long int)aAddress, aNewReg);
}
mLog(buf);
}
if (mCurrAddr < aAddress) {
// Flush the existing summary first.
mSecMap->AddRuleSet(&mCurrRules, mCurrAddr, aAddress - mCurrAddr);
if (DEBUG_SUMMARISER) {
mLog("LUL ");
mCurrRules.Print(mCurrAddr, aAddress - mCurrAddr, mLog);
mLog("\n");
}
mCurrAddr = aAddress;
}
// If for some reason summarisation fails, either or both of these
// become non-null and point at constant text describing the
// problem. Using two rather than just one avoids complications of
// having to concatenate two strings to produce a complete error message.
const char* reason1 = nullptr;
const char* reason2 = nullptr;
// |offset| needs to be a 32 bit value that sign extends to 64 bits
// on a 64 bit target. We will need to incorporate |offset| into
// any LExpr made here. So we may as well check it right now.
if (!fitsIn32Bits(offset)) {
reason1 = "offset not in signed 32-bit range";
goto cant_summarise;
}
// FIXME: factor out common parts of the arch-dependent summarisers.
#if defined(GP_ARCH_arm)
// ----------------- arm ----------------- //
// Now, can we add the rule to our summary? This depends on whether
// the registers and the overall expression are representable. This
// is the heart of the summarisation process.
switch (aNewReg) {
case DW_REG_CFA:
// This is a rule that defines the CFA. The only forms we
// choose to represent are: r7/11/12/13 + offset. The offset
// must fit into 32 bits since 'uintptr_t' is 32 bit on ARM,
// hence there is no need to check it for overflow.
if (how != NODEREF) {
reason1 = "rule for DW_REG_CFA: invalid |how|";
goto cant_summarise;
}
switch (oldReg) {
case DW_REG_ARM_R7:
case DW_REG_ARM_R11:
case DW_REG_ARM_R12:
case DW_REG_ARM_R13:
break;
default:
reason1 = "rule for DW_REG_CFA: invalid |oldReg|";
goto cant_summarise;
}
mCurrRules.mCfaExpr = LExpr(how, oldReg, offset);
break;
case DW_REG_ARM_R7:
case DW_REG_ARM_R11:
case DW_REG_ARM_R12:
case DW_REG_ARM_R13:
case DW_REG_ARM_R14:
case DW_REG_ARM_R15: {
// This is a new rule for R7, R11, R12, R13 (SP), R14 (LR) or
// R15 (the return address).
switch (how) {
case NODEREF:
case DEREF:
// Check the old register is one we're tracking.
if (!registerIsTracked((DW_REG_NUMBER)oldReg) &&
oldReg != DW_REG_CFA) {
reason1 = "rule for R7/11/12/13/14/15: uses untracked reg";
goto cant_summarise;
}
break;
case PFXEXPR: {
// Check that the prefix expression only mentions tracked registers.
const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
reason2 = checkPfxExpr(pfxInstrs, offset);
if (reason2) {
reason1 = "rule for R7/11/12/13/14/15: ";
goto cant_summarise;
}
break;
}
default:
goto cant_summarise;
}
LExpr expr = LExpr(how, oldReg, offset);
switch (aNewReg) {
case DW_REG_ARM_R7:
mCurrRules.mR7expr = expr;
break;
case DW_REG_ARM_R11:
mCurrRules.mR11expr = expr;
break;
case DW_REG_ARM_R12:
mCurrRules.mR12expr = expr;
break;
case DW_REG_ARM_R13:
mCurrRules.mR13expr = expr;
break;
case DW_REG_ARM_R14:
mCurrRules.mR14expr = expr;
break;
case DW_REG_ARM_R15:
mCurrRules.mR15expr = expr;
break;
default:
MOZ_ASSERT(0);
}
break;
}
default:
// Leave |reason1| and |reason2| unset here. This program point
// is reached so often that it causes a flood of "Can't
// summarise" messages. In any case, we don't really care about
// the fact that this summary would produce a new value for a
// register that we're not tracking. We do on the other hand
// care if the summary's expression *uses* a register that we're
// not tracking. But in that case one of the above failures
// should tell us which.
goto cant_summarise;
}
// Mark callee-saved registers (r4 .. r11) as unchanged, if there is
// no other information about them. FIXME: do this just once, at
// the point where the ruleset is committed.
if (mCurrRules.mR7expr.mHow == UNKNOWN) {
mCurrRules.mR7expr = LExpr(NODEREF, DW_REG_ARM_R7, 0);
}
if (mCurrRules.mR11expr.mHow == UNKNOWN) {
mCurrRules.mR11expr = LExpr(NODEREF, DW_REG_ARM_R11, 0);
}
if (mCurrRules.mR12expr.mHow == UNKNOWN) {
mCurrRules.mR12expr = LExpr(NODEREF, DW_REG_ARM_R12, 0);
}
// The old r13 (SP) value before the call is always the same as the
// CFA.
mCurrRules.mR13expr = LExpr(NODEREF, DW_REG_CFA, 0);
// If there's no information about R15 (the return address), say
// it's a copy of R14 (the link register).
if (mCurrRules.mR15expr.mHow == UNKNOWN) {
mCurrRules.mR15expr = LExpr(NODEREF, DW_REG_ARM_R14, 0);
}
#elif defined(GP_ARCH_arm64)
// ----------------- arm64 ----------------- //
switch (aNewReg) {
case DW_REG_CFA:
if (how != NODEREF) {
reason1 = "rule for DW_REG_CFA: invalid |how|";
goto cant_summarise;
}
switch (oldReg) {
case DW_REG_AARCH64_X29:
case DW_REG_AARCH64_SP:
break;
default:
reason1 = "rule for DW_REG_CFA: invalid |oldReg|";
goto cant_summarise;
}
mCurrRules.mCfaExpr = LExpr(how, oldReg, offset);
break;
case DW_REG_AARCH64_X29:
case DW_REG_AARCH64_X30:
case DW_REG_AARCH64_SP: {
switch (how) {
case NODEREF:
case DEREF:
// Check the old register is one we're tracking.
if (!registerIsTracked((DW_REG_NUMBER)oldReg) &&
oldReg != DW_REG_CFA) {
reason1 = "rule for X29/X30/SP: uses untracked reg";
goto cant_summarise;
}
break;
case PFXEXPR: {
// Check that the prefix expression only mentions tracked registers.
const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
reason2 = checkPfxExpr(pfxInstrs, offset);
if (reason2) {
reason1 = "rule for X29/X30/SP: ";
goto cant_summarise;
}
break;
}
default:
goto cant_summarise;
}
LExpr expr = LExpr(how, oldReg, offset);
switch (aNewReg) {
case DW_REG_AARCH64_X29:
mCurrRules.mX29expr = expr;
break;
case DW_REG_AARCH64_X30:
mCurrRules.mX30expr = expr;
break;
case DW_REG_AARCH64_SP:
mCurrRules.mSPexpr = expr;
break;
default:
MOZ_ASSERT(0);
}
break;
}
default:
// Leave |reason1| and |reason2| unset here, for the reasons explained
// in the analogous point
goto cant_summarise;
}
if (mCurrRules.mX29expr.mHow == UNKNOWN) {
mCurrRules.mX29expr = LExpr(NODEREF, DW_REG_AARCH64_X29, 0);
}
if (mCurrRules.mX30expr.mHow == UNKNOWN) {
mCurrRules.mX30expr = LExpr(NODEREF, DW_REG_AARCH64_X30, 0);
}
// On aarch64, it seems the old SP value before the call is always the
// same as the CFA. Therefore, in the absence of any other way to
// recover the SP, specify that the CFA should be copied.
if (mCurrRules.mSPexpr.mHow == UNKNOWN) {
mCurrRules.mSPexpr = LExpr(NODEREF, DW_REG_CFA, 0);
}
#elif defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
// ---------------- x64/x86 ---------------- //
// Now, can we add the rule to our summary? This depends on whether
// the registers and the overall expression are representable. This
// is the heart of the summarisation process.
switch (aNewReg) {
case DW_REG_CFA: {
// This is a rule that defines the CFA. The only forms we choose to
// represent are: = SP+offset, = FP+offset, or =prefix-expr.
switch (how) {
case NODEREF:
if (oldReg != DW_REG_INTEL_XSP && oldReg != DW_REG_INTEL_XBP) {
reason1 = "rule for DW_REG_CFA: invalid |oldReg|";
goto cant_summarise;
}
break;
case DEREF:
reason1 = "rule for DW_REG_CFA: invalid |how|";
goto cant_summarise;
case PFXEXPR: {
// Check that the prefix expression only mentions tracked registers.
const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
reason2 = checkPfxExpr(pfxInstrs, offset);
if (reason2) {
reason1 = "rule for CFA: ";
goto cant_summarise;
}
break;
}
default:
goto cant_summarise;
}
mCurrRules.mCfaExpr = LExpr(how, oldReg, offset);
break;
}
case DW_REG_INTEL_XSP:
case DW_REG_INTEL_XBP:
case DW_REG_INTEL_XIP: {
// This is a new rule for XSP, XBP or XIP (the return address).
switch (how) {
case NODEREF:
case DEREF:
// Check the old register is one we're tracking.
if (!registerIsTracked((DW_REG_NUMBER)oldReg) &&
oldReg != DW_REG_CFA) {
reason1 = "rule for XSP/XBP/XIP: uses untracked reg";
goto cant_summarise;
}
break;
case PFXEXPR: {
// Check that the prefix expression only mentions tracked registers.
const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
reason2 = checkPfxExpr(pfxInstrs, offset);
if (reason2) {
reason1 = "rule for XSP/XBP/XIP: ";
goto cant_summarise;
}
break;
}
default:
goto cant_summarise;
}
LExpr expr = LExpr(how, oldReg, offset);
switch (aNewReg) {
case DW_REG_INTEL_XBP:
mCurrRules.mXbpExpr = expr;
break;
case DW_REG_INTEL_XSP:
mCurrRules.mXspExpr = expr;
break;
case DW_REG_INTEL_XIP:
mCurrRules.mXipExpr = expr;
break;
default:
MOZ_CRASH("impossible value for aNewReg");
}
break;
}
default:
// Leave |reason1| and |reason2| unset here, for the reasons
// explained in the analogous point in the ARM case just above.
goto cant_summarise;
}
// On Intel, it seems the old SP value before the call is always the
// same as the CFA. Therefore, in the absence of any other way to
// recover the SP, specify that the CFA should be copied.
if (mCurrRules.mXspExpr.mHow == UNKNOWN) {
mCurrRules.mXspExpr = LExpr(NODEREF, DW_REG_CFA, 0);
}
// Also, gcc says "Undef" for BP when it is unchanged.
if (mCurrRules.mXbpExpr.mHow == UNKNOWN) {
mCurrRules.mXbpExpr = LExpr(NODEREF, DW_REG_INTEL_XBP, 0);
}
#elif defined(GP_ARCH_mips64)
// ---------------- mips ---------------- //
//
// Now, can we add the rule to our summary? This depends on whether
// the registers and the overall expression are representable. This
// is the heart of the summarisation process.
switch (aNewReg) {
case DW_REG_CFA:
// This is a rule that defines the CFA. The only forms we can
// represent are: = SP+offset or = FP+offset.
if (how != NODEREF) {
reason1 = "rule for DW_REG_CFA: invalid |how|";
goto cant_summarise;
}
if (oldReg != DW_REG_MIPS_SP && oldReg != DW_REG_MIPS_FP) {
reason1 = "rule for DW_REG_CFA: invalid |oldReg|";
goto cant_summarise;
}
mCurrRules.mCfaExpr = LExpr(how, oldReg, offset);
break;
case DW_REG_MIPS_SP:
case DW_REG_MIPS_FP:
case DW_REG_MIPS_PC: {
// This is a new rule for SP, FP or PC (the return address).
switch (how) {
case NODEREF:
case DEREF:
// Check the old register is one we're tracking.
if (!registerIsTracked((DW_REG_NUMBER)oldReg) &&
oldReg != DW_REG_CFA) {
reason1 = "rule for SP/FP/PC: uses untracked reg";
goto cant_summarise;
}
break;
case PFXEXPR: {
// Check that the prefix expression only mentions tracked registers.
const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
reason2 = checkPfxExpr(pfxInstrs, offset);
if (reason2) {
reason1 = "rule for SP/FP/PC: ";
goto cant_summarise;
}
break;
}
default:
goto cant_summarise;
}
LExpr expr = LExpr(how, oldReg, offset);
switch (aNewReg) {
case DW_REG_MIPS_FP:
mCurrRules.mFPexpr = expr;
break;
case DW_REG_MIPS_SP:
mCurrRules.mSPexpr = expr;
break;
case DW_REG_MIPS_PC:
mCurrRules.mPCexpr = expr;
break;
default:
MOZ_CRASH("impossible value for aNewReg");
}
break;
}
default:
// Leave |reason1| and |reason2| unset here, for the reasons
// explained in the analogous point in the ARM case just above.
goto cant_summarise;
}
// On MIPS, it seems the old SP value before the call is always the
// same as the CFA. Therefore, in the absence of any other way to
// recover the SP, specify that the CFA should be copied.
if (mCurrRules.mSPexpr.mHow == UNKNOWN) {
mCurrRules.mSPexpr = LExpr(NODEREF, DW_REG_CFA, 0);
}
// Also, gcc says "Undef" for FP when it is unchanged.
if (mCurrRules.mFPexpr.mHow == UNKNOWN) {
mCurrRules.mFPexpr = LExpr(NODEREF, DW_REG_MIPS_FP, 0);
}
#else
# error "Unsupported arch"
#endif
return;
cant_summarise:
if (reason1 || reason2) {
char buf[200];
SprintfLiteral(buf,
"LUL can't summarise: "
"SVMA=0x%llx: %s%s, expr=LExpr(%s,%u,%lld)\n",
(unsigned long long int)(aAddress - mTextBias),
reason1 ? reason1 : "", reason2 ? reason2 : "",
NameOf_LExprHow(how), (unsigned int)oldReg,
(long long int)offset);
mLog(buf);
}
}
uint32_t Summariser::AddPfxInstr(PfxInstr pfxi) {
return mSecMap->AddPfxInstr(pfxi);
}
void Summariser::End() {
if (DEBUG_SUMMARISER) {
mLog("LUL End\n");
}
if (mCurrAddr < mMax1Addr) {
mSecMap->AddRuleSet(&mCurrRules, mCurrAddr, mMax1Addr - mCurrAddr);
if (DEBUG_SUMMARISER) {
mLog("LUL ");
mCurrRules.Print(mCurrAddr, mMax1Addr - mCurrAddr, mLog);
mLog("\n");
}
}
}
} // namespace lul