/* -*- 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #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* 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* 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* 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* 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* 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* 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