// Helper that adds the given function breakpoint corresponding to the given // function and id as a breakpoint. If a duplicate breakpoint already exists, // it is overwritten. static void add_func_breakpoint(int id, XDebugBreakpoint& bp, const Func* func) { // Function id is added to the breakpoint once matched auto const func_id = func->getFuncId(); bp.funcId = func_id; // Add the appropriate function map switch (bp.type) { case BreakType::CALL: { auto iter = FUNC_ENTRY.find(func_id); if (iter != FUNC_ENTRY.end()) { XDEBUG_REMOVE_BREAKPOINT(iter->second); } FUNC_ENTRY[func->getFuncId()] = id; phpAddBreakPointFuncEntry(func); break; } case BreakType::RETURN: { auto iter = FUNC_EXIT.find(func_id); if (iter != FUNC_EXIT.end()) { XDEBUG_REMOVE_BREAKPOINT(iter->second); } FUNC_EXIT[func->getFuncId()] = id; phpAddBreakPointFuncExit(func); break; } default: throw Exception("Not passed a function breakpoint"); } }
void XDebugHook::onBreak(const BreakInfo& bi) { // Have to have a server to break. if (XDEBUG_GLOBAL(Server) == nullptr) { return; } // Grab the breakpoints matching the passed info std::vector<int> ids; get_breakpoint_ids(bi, ids); // Iterate. Note that we only tell the server to break once. bool have_broken = false; for (auto const id : ids) { // Look up the breakpoint, ensure it's hittable. auto& bp = BREAKPOINT_MAP.at(id); if (!is_breakpoint_hit(bp)) { continue; } // We only break once per location. auto const temporary = bp.temporary; // breakpoint could be deleted if (!have_broken) { have_broken = true; // Grab the breakpoint message and do the break. auto const msg = get_breakpoint_message(bi); if (!XDEBUG_GLOBAL(Server)->breakpoint(bp, msg)) { // Kill the server if there's an error. XDebugServer::detach(); return; } } // Remove the breakpoint if it was temporary. if (temporary) { XDEBUG_REMOVE_BREAKPOINT(id); } } }
// Adding a breakpoint. Returns a unique id for the breakpoint. int XDebugThreadBreakpoints::addBreakpoint(XDebugBreakpoint& bp) { auto const id = s_xdebug_breakpoints->m_nextBreakpointId; // php5 xdebug only accepts multiple breakpoints of the same type for // line breakpoints. A useful addition might be to allow multiple of all // types, but for now, multiple function or exception breakpoints results // in the most recent silently being used (matching php5 xdebug). switch (bp.type) { case BreakType::EXCEPTION: { // Remove duplicates then insert the name auto exceptionName = bp.exceptionName.toCppString(); auto iter = EXCEPTION_MAP.find(exceptionName); if (iter != EXCEPTION_MAP.end()) { XDEBUG_REMOVE_BREAKPOINT(iter->second); } EXCEPTION_MAP[exceptionName] = id; break; } // Attempt to find the unit/line combo case BreakType::LINE: { const Unit* unit = find_unit(bp.fileName); if (unit == nullptr) { UNMATCHED.insert(id); break; } // If the file/line combo is invalid, throw an error if (!phpAddBreakPointLine(unit, bp.line)) { throw XDebugServer::ERROR_BREAKPOINT_INVALID; } add_line_breakpoint(id, bp, unit); break; } // Try to find the breakpoint's function case BreakType::CALL: case BreakType::RETURN: { const Class* cls = nullptr; const Func* func = nullptr; if (bp.className.isNull()) { func = Unit::lookupFunc(bp.funcName.get()); } else { cls = Unit::lookupClass(bp.className.toString().get()); if (cls != nullptr) { func = cls->lookupMethod(bp.funcName.get()); } } // Either add the breakpoint or store it as unmatched. If the class // exists, we can verify that the method is valid. if (func != nullptr) { add_func_breakpoint(id, bp, func); } else if (!bp.className.isNull() && cls != nullptr) { throw XDebugServer::ERROR_BREAKPOINT_INVALID; } else { UNMATCHED.insert(id); } break; } } // Success, store the breakpoint and increment the id. BREAKPOINT_MAP[id] = bp; s_xdebug_breakpoints->m_nextBreakpointId++; return id; }