// Setup the last location filter on the VM context for all offsets covered by // the current source line. This will short-circuit the work done in // phpDebuggerOpcodeHook() and ensure we don't interrupt on this source line. // We exclude continuation opcodes which transfer control out of the function, // which allows cmds to get a chance to alter their behavior when those opcodes // are encountered. void CmdFlowControl::installLocationFilterForLine(InterruptSite *site) { // We may be stopped at a place with no source info. if (!site || !site->valid()) return; if (g_context->m_lastLocFilter) { g_context->m_lastLocFilter->clear(); } else { g_context->m_lastLocFilter = new PCFilter(); } TRACE(3, "Prepare location filter for %s:%d, unit %p:\n", site->getFile(), site->getLine0(), site->getUnit()); OffsetRangeVec ranges; const auto unit = site->getUnit(); if (m_smallStep) { // Get offset range for the pc only. OffsetRange range; if (unit->getOffsetRange(site->getCurOffset(), range)) { ranges.push_back(range); } } else { // Get offset ranges for the whole line. // We use line1 here because it seems to be working better than line0 // in a handful of cases for our bytecode-source mapping. if (!unit->getOffsetRanges(site->getLine1(), ranges)) { ranges.clear(); } } auto excludeContinuationReturns = [] (Op op) { return (op != OpContSuspend) && (op != OpContSuspendK) && (op != OpAsyncESuspend) && (op != OpContRetC); }; g_context->m_lastLocFilter->addRanges(unit, ranges, excludeContinuationReturns); }
static void addBreakPointInUnit(BreakPointInfoPtr bp, Unit* unit) { OffsetRangeVec offsets; if (!unit->getOffsetRanges(bp->m_line1, offsets) || offsets.size() == 0) { bp->m_bindState = BreakPointInfo::KnownToBeInvalid; return; } bp->m_bindState = BreakPointInfo::KnownToBeValid; TRACE(3, "Add to breakpoint filter for %s:%d, unit %p:\n", unit->filepath()->data(), bp->m_line1, unit); phpAddBreakPointRange(unit, offsets); }
int PCFilter::addRanges(const Unit* unit, const OffsetRangeVec& offsets) { int counter = 0; for (auto range = offsets.cbegin(); range != offsets.cend(); ++range) { for (PC pc = unit->at(range->m_base); pc < unit->at(range->m_past); pc += instrLen((Opcode*)pc)) { addPC(pc); counter++; } } return counter; }
int PCFilter::addRanges(const Unit* unit, const OffsetRangeVec& offsets) { int counter = 0; for (OffsetRangeVec::const_iterator it = offsets.begin(); it != offsets.end(); ++it) { for (PC pc = unit->at(it->m_base); pc < unit->at(it->m_past); pc += instrLen((Opcode*)pc)) { addPC(pc); counter++; } } return counter; }
// Ensure we interpret all code at the given offsets. This sets up a guard for // each piece of tranlated code to ensure we punt ot the interpreter when the // debugger is attached. static void blacklistRangesInJit(const Unit* unit, const OffsetRangeVec& offsets) { for (OffsetRangeVec::const_iterator it = offsets.begin(); it != offsets.end(); ++it) { for (PC pc = unit->at(it->m_base); pc < unit->at(it->m_past); pc += instrLen((Opcode*)pc)) { transl()->addDbgBLPC(pc); } } if (!transl()->addDbgGuards(unit)) { Logger::Warning("Failed to set breakpoints in Jitted code"); } }
void phpDebuggerStepIn() { // If this is called in the middle of a flow command we short-circuit the // other commands auto& req_data = RID(); req_data.setDebuggerStepIn(true); req_data.setDebuggerStepOut(StepOutState::NONE); req_data.setDebuggerNext(false); // Ensure the flow filter is fresh auto flow_filter = getFlowFilter(); flow_filter->clear(); // Check if the site is valid. VMRegAnchor _; ActRec* fp = vmfp(); PC pc = vmpc(); if (fp == nullptr || pc == nullptr) { TRACE(5, "Could not grab stack or program counter\n"); return; } // Try to get needed context info. Bail if we can't const Func* func = fp->func(); const Unit* unit = func != nullptr ? func->unit() : nullptr; if (func == nullptr || func == nullptr) { TRACE(5, "Could not grab the current unit or function\n"); return; } // We use line1 here because it works better than line0 in our // bytecode-source mapping. int line; SourceLoc source_loc; if (unit->getSourceLoc(unit->offsetOf(pc), source_loc)) { line = source_loc.line1; } else { TRACE(5, "Could not grab the current line number\n"); return; } TRACE(3, "Prepare location filter for %s:%d, unit %p:\n", unit->filepath()->data(), line, unit); // Get offset ranges for the whole line. OffsetRangeVec ranges; if (!unit->getOffsetRanges(line, ranges)) { ranges.clear(); } flow_filter->addRanges(unit, ranges); }
// Looks up the offset range in the given unit, of the given breakpoint. // If the offset cannot be found, the breakpoint is marked as invalid. // Otherwise it is marked as valid and the offset is added to the // breakpoint filter and the offset range is black listed for the JIT. static void addBreakPointInUnit(Eval::BreakPointInfoPtr bp, Unit* unit) { OffsetRangeVec offsets; if (!unit->getOffsetRanges(bp->m_line1, offsets) || offsets.size() == 0) { bp->m_bindState = Eval::BreakPointInfo::KnownToBeInvalid; return; } bp->m_bindState = Eval::BreakPointInfo::KnownToBeValid; TRACE(3, "Add to breakpoint filter for %s:%d, unit %p:\n", unit->filepath()->data(), bp->m_line1, unit); getBreakPointFilter()->addRanges(unit, offsets); if (RuntimeOption::EvalJit) { blacklistRangesInJit(unit, offsets); } }
bool phpAddBreakPointLine(const Unit* unit, int line) { // Grab the unit offsets OffsetRangeVec offsets; if (!unit->getOffsetRanges(line, offsets)) { return false; } // Add to the breakpoint filter and the line filter phpAddBreakPointRange(unit, offsets); assertx(offsets.size() > 0); auto pc = unit->at(offsets[0].base); RID().m_lineBreakPointFilter.addPC(pc); return true; }
// Adds a range of PCs to the filter given a collection of offset ranges. // Omit PCs which have opcodes that don't pass the given opcode filter. void PCFilter::addRanges(const Unit* unit, const OffsetRangeVec& offsets, OpcodeFilter isOpcodeAllowed) { for (auto range = offsets.cbegin(); range != offsets.cend(); ++range) { TRACE(3, "\toffsets [%d, %d)\n", range->m_base, range->m_past); for (PC pc = unit->at(range->m_base); pc < unit->at(range->m_past); pc += instrLen(pc)) { if (isOpcodeAllowed(*pc)) { TRACE(3, "\t\tpc %p\n", pc); addPC(pc); } else { TRACE(3, "\t\tpc %p -- skipping (offset %d)\n", pc, unit->offsetOf(pc)); } } } }
bool UnitRepoProxy::GetSourceLocPastOffsetsStmt ::get(int64 unitSn, int line, OffsetRangeVec& ranges) { try { RepoTxn txn(m_repo); if (!prepared()) { std::stringstream ssSelect; ssSelect << "SELECT pastOffset FROM " << m_repo.table(m_repoId, "UnitSourceLoc") << " WHERE unitSn == @unitSn AND line0 <= @line" " AND line1 >= @line;"; txn.prepare(*this, ssSelect.str()); } RepoTxnQuery query(txn, *this); query.bindInt64("@unitSn", unitSn); query.bindInt("@line", line); do { query.step(); if (query.row()) { Offset pastOffset; /**/ query.getOffset(0, pastOffset); ranges.push_back(OffsetRange(pastOffset, pastOffset)); } } while (!query.done()); txn.commit(); } catch (RepoExc& re) { return true; } return false; }
// Removes a range of PCs to the filter given a collection of offset ranges. // Omit PCs which have opcodes that don't pass the given opcode filter. void PCFilter::removeRanges(const Unit* unit, const OffsetRangeVec& offsets, OpcodeFilter isOpcodeAllowed) { for (auto range = offsets.cbegin(); range != offsets.cend(); ++range) { TRACE(3, "\toffsets [%d, %d) (remove)\n", range->m_base, range->m_past); for (PC pc = unit->at(range->m_base); pc < unit->at(range->m_past); pc += instrLen((Op*) pc)) { if (isOpcodeAllowed(*reinterpret_cast<const Op*>(pc))) { TRACE(3, "\t\tpc %p (remove)\n", pc); removePC(pc); } else { TRACE(3, "\t\tpc %p -- skipping (offset %d) (remove)\n", pc, unit->offsetOf(pc)); } } } }
bool Unit::getOffsetRanges(int line, OffsetRangeVec& offsets) const { ASSERT(offsets.size() == 0); if (m_repoId == RepoIdInvalid) { return false; } UnitRepoProxy& urp = Repo::get().urp(); if (urp.getSourceLocPastOffsets(m_repoId).get(m_sn, line, offsets)) { return false; } for (OffsetRangeVec::iterator it = offsets.begin(); it != offsets.end(); ++it) { if (urp.getSourceLocBaseOffset(m_repoId).get(m_sn, *it)) { return false; } } return true; }
// Ensure we interpret all code at the given offsets. This sets up a guard for // each piece of translated code to ensure we punt to the interpreter when the // debugger is attached. static void blacklistRangesInJit(const Unit* unit, const OffsetRangeVec& offsets) { for (OffsetRangeVec::const_iterator it = offsets.begin(); it != offsets.end(); ++it) { for (PC pc = unit->at(it->m_base); pc < unit->at(it->m_past); pc += instrLen((Opcode*)pc)) { transl()->addDbgBLPC(pc); } } if (!transl()->addDbgGuards(unit)) { Logger::Warning("Failed to set breakpoints in Jitted code"); } // In this case, we may be setting a breakpoint in a tracelet which could // already be jitted, and present on the stack. Make sure we don't return // to it so we have a chance to honor breakpoints. g_vmContext->preventReturnsToTC(); }
static void addBreakPointsInFile(Eval::DebuggerProxy* proxy, Eval::PhpFile* efile) { Eval::BreakPointInfoPtrVec bps; proxy->getBreakPoints(bps); for(unsigned int i = 0; i < bps.size(); i++) { Eval::BreakPointInfoPtr bp = bps[i]; if (bp->m_line1 == 0 || bp->m_file.empty()) { // invalid breakpoint for file:line continue; } if (!Eval::BreakPointInfo::MatchFile(bp->m_file, efile->getFileName(), efile->getRelPath())) { continue; } Unit* unit = efile->unit(); OffsetRangeVec offsets; if (!unit->getOffsetRanges(bp->m_line1, offsets)) { continue; } if (!g_vmContext->m_breakPointFilter) { g_vmContext->m_breakPointFilter = new PCFilter(); } if (debug && Trace::moduleEnabled(Trace::bcinterp, 5)) { for (OffsetRangeVec::const_iterator it = offsets.begin(); it != offsets.end(); ++it) { Trace::trace("file:line break %s:%d : unit %p offset [%d, %d)\n", efile->getFileName().c_str(), bp->m_line1, unit, it->m_base, it->m_past); } } g_vmContext->m_breakPointFilter->addRanges(unit, offsets); if (RuntimeOption::EvalJit) { blacklistRangesInJit(unit, offsets); } } }
// Ensure we interpret an entire function when the debugger is attached. static void blacklistFuncInJit(const Func* f) { Unit* unit = f->unit(); OffsetRangeVec ranges; ranges.push_back(OffsetRange(f->base(), f->past())); blacklistRangesInJit(unit, ranges); }