void XDebugServer::addError(xdebug_xml_node& node, XDebugError code) { // Create the error node auto error = xdebug_xml_node_init("error"); xdebug_xml_add_attribute(error, "code", static_cast<int>(code)); xdebug_xml_add_child(&node, error); // Add the error code's error message auto message = xdebug_xml_node_init("message"); xdebug_xml_add_text(message, const_cast<char*>(xdebug_error_str(code)), 0); xdebug_xml_add_child(error, message); }
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(); }
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; }
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 XDebugServer::deinitDbgp() { setStatus(Status::STOPPING, Reason::OK); // Send the xml shutdown response xdebug_xml_node* response = xdebug_xml_node_init("response"); addXmnls(*response); addCmdAndTrans(*response); sendMessage(*response); xdebug_xml_node_dtor(response); // Wait for a response from the client doCommandLoop(); // Free the input buffer smart_free(m_buffer); }
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(); }
xdebug_xml_node* xdebug_var_export_xml_node(const char* name, const char* fullName, const char* facet, const Variant& var, XDebugExporter& exporter) { // Setup the node. Each const cast is necessary due to xml api xdebug_xml_node* node = xdebug_xml_node_init("property"); if (name != nullptr) { xdebug_xml_add_attribute_ex(node, "name", const_cast<char*>(name), 0, 1); } if (fullName != nullptr) { xdebug_xml_add_attribute_ex(node, "fullname", const_cast<char*>(fullName), 0, 1); } if (facet != nullptr) { xdebug_xml_add_attribute_ex(node, "facet", const_cast<char*>(facet), 0, 1); } xdebug_xml_add_attribute_ex(node, "address", xdebug_sprintf("%ld", (long) &var), 0, 1); // Case on the type for the rest if (var.isBoolean()) { xdebug_xml_add_attribute(node, "type", "bool"); xdebug_xml_add_text(node, xdebug_sprintf("%d", var.toBoolean())); } else if (var.isNull()) { xdebug_xml_add_attribute(node, "type", "null"); } else if (var.isInteger()) { xdebug_xml_add_attribute(node, "type", "int"); xdebug_xml_add_text(node, xdebug_sprintf("%ld", var.toInt64())); } else if (var.isDouble()) { xdebug_xml_add_attribute(node, "type", "float"); xdebug_xml_add_text(node, xdebug_sprintf("%lG", var.toDouble())); } else if (var.isString()) { // Add the type and the original size String str = var.toString(); xdebug_xml_add_attribute(node, "type", "string"); xdebug_xml_add_attribute_ex(node, "size", xdebug_sprintf("%d", str.size()), 0, 1); // Possibly shrink the string, then add it to the node if (exporter.max_data != 0 && str.size() > exporter.max_data) { str = str.substr(0, exporter.max_data); } xdebug_xml_add_text_encodel(node, xdstrdup(str.data()), str.size()); } else if (var.isArray()) { Array arr = var.toArray(); xdebug_xml_add_attribute(node, "type", "array"); xdebug_xml_add_attribute(node, "children", const_cast<char*>(arr.size() > 0 ? "1" : "0")); // If we've already seen this object, return if (exporter.counts[arr.get()]++ > 0) { xdebug_xml_add_attribute(node, "recursive", "1"); return node; } // Write the # of children then short-circuit if we are too deep xdebug_xml_add_attribute_ex(node, "numchildren", xdebug_sprintf("%d", arr.size()), 0, 1); if (exporter.level++ >= exporter.max_depth) { return node; } // Compute the page and the start/end indices // Note that php xdebug doesn't support pages except for at the top level uint32_t page = exporter.level == 1 ? exporter.page : 0; uint32_t start = page * exporter.max_children; uint32_t end = (page + 1) * exporter.max_children; xdebug_xml_add_attribute_ex(node, "page", xdebug_sprintf("%d", page), 0, 1); xdebug_xml_add_attribute_ex(node, "pagesize", xdebug_sprintf("%d", exporter.max_children), 0, 1); // Add each child ArrayIter iter(arr); iter.setPos(start); for (uint32_t i = start; i < end && iter; i++, ++iter) { xdebug_array_element_export_xml_node(*node, name, iter.first(), iter.second(), exporter); } // Done at this level exporter.level--; exporter.counts[arr.get()]--; } else if (var.isObject()) { // TODO(#3704) This could be merged into the above array code. For now, // it's separate as this was pulled originally from xdebug ObjectData* obj = var.toObject().get(); Class* cls = obj->getVMClass(); Array props = get_object_props(obj); // Add object info xdebug_xml_add_attribute(node, "type", "object"); xdebug_xml_add_attribute_ex(node, "classname", xdstrdup(cls->name()->data()), 0, 1); xdebug_xml_add_attribute(node, "children", const_cast<char*>(props.size() ? "1" : "0")); // If we've already seen this object, return if (exporter.counts[obj]++ > 0) { xdebug_xml_add_attribute(node, "recursive", "1"); return node; } // Add the # of props then short circuit if we are too deep xdebug_xml_add_attribute_ex(node, "numchildren", xdebug_sprintf("%d", props.size()), 0, 1); if (exporter.level++ >= exporter.max_depth) { return node; } // Compute the page and the start/end indices // Note that php xdebug doesn't support pages except for at the top level uint32_t page = exporter.level == 1 ? exporter.page : 0; uint32_t start = page * exporter.max_children; uint32_t end = (page + 1) * exporter.max_children; xdebug_xml_add_attribute_ex(node, "page", xdebug_sprintf("%d", page), 0, 1); xdebug_xml_add_attribute_ex(node, "pagesize", xdebug_sprintf("%d", exporter.max_children), 0, 1); // Add each property ArrayIter iter(props); iter.setPos(start); for (uint32_t i = start; i < end && iter; i++, ++iter) { xdebug_object_element_export_xml_node(*node, name, obj, iter.first(), iter.second(), exporter); } // Done at this level exporter.level--; exporter.counts[(void*) obj]--; } else if (var.isResource()) { ResourceData* res = var.toResource().get(); xdebug_xml_add_attribute(node, "type", "resource"); const char* text = xdebug_sprintf("resource id='%ld' type='%s'", res->o_getId(), res->o_getResourceName().data()); xdebug_xml_add_text(node, const_cast<char*>(text)); } else { xdebug_xml_add_attribute(node, "type", "null"); } return node; }
void 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 xdebug_xml_node* response = xdebug_xml_node_init("init"); addXmnls(*response); // Add the engine info xdebug_xml_node* 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 const ArrayData* globals = get_global_variables()->asArrayData(); Variant scriptname_var = globals->get(s_SERVER).toArray()[s_SCRIPT_FILENAME]; assert(scriptname_var.isString()); char* scriptname = scriptname_var.toString().get()->mutableData(); char* fileuri = XDebugUtils::pathToUrl(scriptname); // Grab the app id (pid) // TODO(#4489053) Specification mentions the parent app id as well, xdebug // doesn't include it. char* appid = xdebug_sprintf("%d", getpid()); // Add attributes to the root init node xdebug_xml_add_attribute_ex(response, "fileuri", fileuri, 0, 1); xdebug_xml_add_attribute_ex(response, "language", "PHP", 0, 0); xdebug_xml_add_attribute_ex(response, "protocol_version", DBGP_VERSION, 0, 0); xdebug_xml_add_attribute_ex(response, "appid", appid, 0, 1); // Add the DBGP_COOKIE environment variable char* dbgp_cookie = getenv("DBGP_COOKIE"); if (dbgp_cookie != nullptr) { xdebug_xml_add_attribute_ex(response, "session", dbgp_cookie, 0, 0); } // Add the idekey if (XDEBUG_GLOBAL(IdeKey).size() > 0) { // TODO(#4489053) Change this when xml api is changed char* idekey = const_cast<char*>(XDEBUG_GLOBAL(IdeKey).c_str()); xdebug_xml_add_attribute_ex(response, "idekey", idekey, 0, 0); } // Sent the response sendMessage(*response); xdebug_xml_node_dtor(response); // Wait for a response from the client doCommandLoop(); }