bool XDebugServer::doCommandLoop() { log("Entered command loop"); bool should_continue = false; // Pause the polling thread if it isn't already. (It might have paused itself // if it read a "break" command) m_pausePollingThread.store(true); // Unpause the polling thread when we leave the command loop. SCOPE_EXIT { m_pausePollingThread.store(false); }; std::lock_guard<std::mutex> lock(m_pollingMtx); do { // If we are detached, short circuit if (m_status == Status::Detached) { return true; } // If we have no input buffered, read from socket, store into m_buffer. // On failure, return. if (m_bufferAvail == 0 && !readInput()) { return false; } // Initialize the response auto response = xdebug_xml_node_init("response"); addXmlns(*response); try { // Parse the command and store it as the last command auto cmd = parseCommand(); if (m_lastCommand != nullptr) { delete m_lastCommand; } m_lastCommand = cmd; // Try to handle the command. Possibly send a response. should_continue = cmd->handle(*response); if (cmd->shouldRespond()) { sendMessage(*response); } } catch (const XDebugExn& exn) { addError(*response, exn.error); sendMessage(*response); } // Free the response node. xdebug_xml_node_dtor(response); } while (!should_continue); return true; }
bool XDebugServer::breakpoint(const Variant& filename, const Variant& exception, const Variant& message, int line) { log("Hit breakpoint at %s:%d", filename.toString().data(), line); setStatus(Status::Break, Reason::Ok); // Initialize the response node auto response = xdebug_xml_node_init("response"); addXmlns(*response); addStatus(*response); if (m_lastCommand != nullptr) { addCommand(*response, *m_lastCommand); } // Grab the c strings auto to_c_str = [] (const Variant& var) { return !var.isString() ? nullptr : var.toString().data(); }; auto filename_str = to_c_str(filename); auto exception_str = to_c_str(exception); auto message_str = to_c_str(message); auto line_str = xdebug_sprintf("%d", line); // Create the message node auto msg = xdebug_xml_node_init("xdebug:message"); xdebug_xml_add_attribute_ex(msg, "lineno", line_str, 0, 1); if (filename_str != nullptr) { filename_str = XDebugUtils::pathToUrl(filename_str); // output file format xdebug_xml_add_attribute_ex( msg, "filename", filename_str, 0 /* freeAttr */, 1 /* freeVal */ ); } if (exception_str != nullptr) { xdebug_xml_add_attribute(msg, "exception", exception_str); } if (message_str != nullptr) { xdebug_xml_add_text(msg, message_str, 0); } // Add the message node then send the response xdebug_xml_add_child(response, msg); sendMessage(*response); xdebug_xml_node_dtor(response); // Wait for a resonse from the user return doCommandLoop(); }
void XDebugServer::sendStream(const char* name, const char* bytes, int len) { // Casts are necessary due to xml api auto name_str = const_cast<char*>(name); auto bytes_str = const_cast<char*>(bytes); auto message = xdebug_xml_node_init("stream"); addXmlns(*message); xdebug_xml_add_attribute(message, "type", name_str); xdebug_xml_add_text_ex(message, bytes_str, len, 0, 1); sendMessage(*message); xdebug_xml_node_dtor(message); }
void XDebugHook::onOpcode(PC pc) { auto server = XDEBUG_GLOBAL(Server); if (server == nullptr) { return; } // Likely case is that there was no break command. auto brk = server->getAndClearBreak(); if (LIKELY(brk == nullptr)) { return; } server->log("Request thread received break command"); VMRegAnchor anchor; auto const unit = vmfp()->func()->unit(); auto const line = unit->getLineNumber(unit->offsetOf(pc)); auto const filepath = const_cast<StringData*>(unit->filepath()); auto const transpath = File::TranslatePath(String(filepath)); // XDebugServer::breakpoint will send the response for the command before the // break command, but we first need to send a response for the break command. auto response = xdebug_xml_node_init("response"); server->addXmlns(*response); auto const& cmd_str = brk->getCommandStr(); auto const& trans_id = brk->getTransactionId(); // Manually add status and reason. XDebugServer still thinks we're running // because we haven't run XDebugServer::breakpoint yet. xdebug_xml_add_attribute(response, "status", "break"); xdebug_xml_add_attribute(response, "reason", "ok"); // Ditto with command, XDebugServer is tracking the command before the break. xdebug_xml_add_attribute_dup(response, "command", cmd_str.data()); xdebug_xml_add_attribute_dup(response, "transaction_id", trans_id.data()); delete brk; server->sendMessage(*response); xdebug_xml_node_dtor(response); // Now we can go into a command loop. server->breakpoint(transpath, init_null(), init_null(), line); }
void XDebugServer::deinitDbgp() { // Unless we've already stopped, send the shutdown message if (m_status != Status::Stopped) { setStatus(Status::Stopping, Reason::Ok); // Send the xml shutdown response auto response = xdebug_xml_node_init("response"); addXmlns(*response); addStatus(*response); if (m_lastCommand != nullptr) { addCommand(*response, *m_lastCommand); } sendMessage(*response); xdebug_xml_node_dtor(response); // Wait for a response from the client. Regardless of the command loop // result, we exit. doCommandLoop(); } // Free the input buffer & the last command req::free(m_buffer); delete m_lastCommand; }
bool XDebugServer::initDbgp() { // Initialize the status and reason switch (m_mode) { case Mode::REQ: setStatus(Status::Starting, Reason::Ok); break; case Mode::JIT: setStatus(Status::Break, Reason::Error); break; } // Create the response auto response = xdebug_xml_node_init("init"); addXmlns(*response); // Add the engine info auto child = xdebug_xml_node_init("engine"); xdebug_xml_add_attribute(child, "version", XDEBUG_VERSION); xdebug_xml_add_text(child, XDEBUG_NAME, 0); xdebug_xml_add_child(response, child); // Add the author child = xdebug_xml_node_init("author"); xdebug_xml_add_text(child, XDEBUG_AUTHOR, 0); xdebug_xml_add_child(response, child); // Add the url child = xdebug_xml_node_init("url"); xdebug_xml_add_text(child, XDEBUG_URL, 0); xdebug_xml_add_child(response, child); // Add the copyright child = xdebug_xml_node_init("copyright"); xdebug_xml_add_text(child, XDEBUG_COPYRIGHT, 0); xdebug_xml_add_child(response, child); // Grab the absolute path of the script filename auto globals = get_global_variables()->asArrayData(); Variant scriptname_var = globals->get(s_SERVER).toArray()[s_SCRIPT_FILENAME]; assert(scriptname_var.isString()); auto scriptname = scriptname_var.toString().get()->mutableData(); auto fileuri = XDebugUtils::pathToUrl(scriptname); // Add attributes to the root init node xdebug_xml_add_attribute_ex(response, "fileuri", fileuri, 0, 1); xdebug_xml_add_attribute(response, "language", "PHP"); xdebug_xml_add_attribute(response, "protocol_version", DBGP_VERSION); xdebug_xml_add_attribute(response, "appid", getpid()); // Add the DBGP_COOKIE environment variable const String dbgp_cookie = g_context->getenv(s_DBGP_COOKIE); if (!dbgp_cookie.empty()) { xdebug_xml_add_attribute(response, "session", dbgp_cookie.data()); } // Add the idekey if (XDEBUG_GLOBAL(IdeKey).size() > 0) { xdebug_xml_add_attribute(response, "idekey", XDEBUG_GLOBAL(IdeKey).c_str()); } // Sent the response sendMessage(*response); xdebug_xml_node_dtor(response); // Wait for a response from the client return doCommandLoop(); }