[lldb/gdb-server] Better reporting of launch errors

Use our "rich error" facility to propagate error reported by the stub to
the user. lldb-server reports rich launch errors as of D133352.

To make this easier to implement, and reduce code duplication, I have
moved the vRun/A/qLaunchSuccess handling into a single
GDBRemoteCommunicationClient function.

Differential Revision: https://reviews.llvm.org/D134754
This commit is contained in:
Pavel Labath 2022-09-27 20:04:10 +02:00
parent 561443818a
commit 8d1de7b34a
5 changed files with 121 additions and 148 deletions

View File

@ -29,6 +29,7 @@
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StreamString.h"
#include "lldb/Utility/UriParser.h"
#include "llvm/Support/FormatAdapters.h"
#include "Plugins/Process/Utility/GDBRemoteSignals.h"
#include "Plugins/Process/gdb-remote/ProcessGDBRemote.h"
@ -361,39 +362,36 @@ Status PlatformRemoteGDBServer::LaunchProcess(ProcessLaunchInfo &launch_info) {
"PlatformRemoteGDBServer::%s() set launch architecture triple to '%s'",
__FUNCTION__, arch_triple ? arch_triple : "<NULL>");
int arg_packet_err;
{
// Scope for the scoped timeout object
process_gdb_remote::GDBRemoteCommunication::ScopedTimeout timeout(
*m_gdb_client_up, std::chrono::seconds(5));
arg_packet_err = m_gdb_client_up->SendArgumentsPacket(launch_info);
// Since we can't send argv0 separate from the executable path, we need to
// make sure to use the actual executable path found in the launch_info...
Args args = launch_info.GetArguments();
if (FileSpec exe_file = launch_info.GetExecutableFile())
args.ReplaceArgumentAtIndex(0, exe_file.GetPath(false));
if (llvm::Error err = m_gdb_client_up->LaunchProcess(args)) {
error.SetErrorStringWithFormatv("Cannot launch '{0}': {1}",
args.GetArgumentAtIndex(0),
llvm::fmt_consume(std::move(err)));
return error;
}
}
if (arg_packet_err == 0) {
std::string error_str;
if (m_gdb_client_up->GetLaunchSuccess(error_str)) {
const auto pid = m_gdb_client_up->GetCurrentProcessID(false);
if (pid != LLDB_INVALID_PROCESS_ID) {
launch_info.SetProcessID(pid);
LLDB_LOGF(log,
"PlatformRemoteGDBServer::%s() pid %" PRIu64
" launched successfully",
__FUNCTION__, pid);
} else {
LLDB_LOGF(log,
"PlatformRemoteGDBServer::%s() launch succeeded but we "
"didn't get a valid process id back!",
__FUNCTION__);
error.SetErrorString("failed to get PID");
}
} else {
error.SetErrorString(error_str.c_str());
LLDB_LOGF(log, "PlatformRemoteGDBServer::%s() launch failed: %s",
__FUNCTION__, error.AsCString());
}
const auto pid = m_gdb_client_up->GetCurrentProcessID(false);
if (pid != LLDB_INVALID_PROCESS_ID) {
launch_info.SetProcessID(pid);
LLDB_LOGF(log,
"PlatformRemoteGDBServer::%s() pid %" PRIu64
" launched successfully",
__FUNCTION__, pid);
} else {
error.SetErrorStringWithFormat("'A' packet returned an error: %i",
arg_packet_err);
LLDB_LOGF(log,
"PlatformRemoteGDBServer::%s() launch succeeded but we "
"didn't get a valid process id back!",
__FUNCTION__);
error.SetErrorString("failed to get PID");
}
return error;
}

View File

@ -746,108 +746,69 @@ lldb::pid_t GDBRemoteCommunicationClient::GetCurrentProcessID(bool allow_lazy) {
return LLDB_INVALID_PROCESS_ID;
}
bool GDBRemoteCommunicationClient::GetLaunchSuccess(std::string &error_str) {
error_str.clear();
StringExtractorGDBRemote response;
if (SendPacketAndWaitForResponse("qLaunchSuccess", response) ==
PacketResult::Success) {
if (response.IsOKResponse())
return true;
// GDB does not implement qLaunchSuccess -- but if we used vRun,
// then we already received a successful launch indication via stop
// reason.
if (response.IsUnsupportedResponse() && m_supports_vRun)
return true;
if (response.GetChar() == 'E') {
// A string the describes what failed when launching...
error_str = std::string(response.GetStringRef().substr(1));
} else {
error_str.assign("unknown error occurred launching process");
}
} else {
error_str.assign("timed out waiting for app to launch");
}
return false;
}
int GDBRemoteCommunicationClient::SendArgumentsPacket(
const ProcessLaunchInfo &launch_info) {
// Since we don't get the send argv0 separate from the executable path, we
// need to make sure to use the actual executable path found in the
// launch_info...
std::vector<const char *> argv;
FileSpec exe_file = launch_info.GetExecutableFile();
std::string exe_path;
const char *arg = nullptr;
const Args &launch_args = launch_info.GetArguments();
if (exe_file)
exe_path = exe_file.GetPath(false);
else {
arg = launch_args.GetArgumentAtIndex(0);
if (arg)
exe_path = arg;
}
if (!exe_path.empty()) {
argv.push_back(exe_path.c_str());
for (uint32_t i = 1; (arg = launch_args.GetArgumentAtIndex(i)) != nullptr;
++i) {
if (arg)
argv.push_back(arg);
}
}
if (!argv.empty()) {
// try vRun first
if (m_supports_vRun) {
StreamString packet;
packet.PutCString("vRun");
for (const char *arg : argv) {
packet.PutChar(';');
packet.PutBytesAsRawHex8(arg, strlen(arg));
}
StringExtractorGDBRemote response;
if (SendPacketAndWaitForResponse(packet.GetString(), response) !=
PacketResult::Success)
return -1;
if (response.IsErrorResponse()) {
uint8_t error = response.GetError();
if (error)
return error;
return -1;
}
// vRun replies with a stop reason packet
// FIXME: right now we just discard the packet and LLDB queries
// for stop reason again
if (!response.IsUnsupportedResponse())
return 0;
m_supports_vRun = false;
}
// fallback to A
llvm::Error GDBRemoteCommunicationClient::LaunchProcess(const Args &args) {
if (!args.GetArgumentAtIndex(0))
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"Nothing to launch");
// try vRun first
if (m_supports_vRun) {
StreamString packet;
packet.PutChar('A');
for (size_t i = 0, n = argv.size(); i < n; ++i) {
arg = argv[i];
const int arg_len = strlen(arg);
if (i > 0)
packet.PutChar(',');
packet.Printf("%i,%i,", arg_len * 2, (int)i);
packet.PutBytesAsRawHex8(arg, arg_len);
packet.PutCString("vRun");
for (const Args::ArgEntry &arg : args) {
packet.PutChar(';');
packet.PutStringAsRawHex8(arg.ref());
}
StringExtractorGDBRemote response;
if (SendPacketAndWaitForResponse(packet.GetString(), response) ==
PacketResult::Success) {
if (response.IsOKResponse())
return 0;
uint8_t error = response.GetError();
if (error)
return error;
}
if (SendPacketAndWaitForResponse(packet.GetString(), response) !=
PacketResult::Success)
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"Sending vRun packet failed");
if (response.IsErrorResponse())
return response.GetStatus().ToError();
// vRun replies with a stop reason packet
// FIXME: right now we just discard the packet and LLDB queries
// for stop reason again
if (!response.IsUnsupportedResponse())
return llvm::Error::success();
m_supports_vRun = false;
}
return -1;
// fallback to A
StreamString packet;
packet.PutChar('A');
llvm::ListSeparator LS(",");
for (const auto &arg : llvm::enumerate(args)) {
packet << LS;
packet.Format("{0},{1},", arg.value().ref().size() * 2, arg.index());
packet.PutStringAsRawHex8(arg.value().ref());
}
StringExtractorGDBRemote response;
if (SendPacketAndWaitForResponse(packet.GetString(), response) !=
PacketResult::Success) {
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"Sending A packet failed");
}
if (!response.IsOKResponse())
return response.GetStatus().ToError();
if (SendPacketAndWaitForResponse("qLaunchSuccess", response) !=
PacketResult::Success) {
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"Sending qLaunchSuccess packet failed");
}
if (response.IsOKResponse())
return llvm::Error::success();
if (response.GetChar() == 'E') {
return llvm::createStringError(llvm::inconvertibleErrorCode(),
response.GetStringRef().substr(1));
}
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"unknown error occurred launching process");
}
int GDBRemoteCommunicationClient::SendEnvironment(const Environment &env) {

View File

@ -80,8 +80,6 @@ public:
lldb::pid_t GetCurrentProcessID(bool allow_lazy = true);
bool GetLaunchSuccess(std::string &error_str);
bool LaunchGDBServer(const char *remote_accept_hostname, lldb::pid_t &pid,
uint16_t &port, std::string &socket_name);
@ -90,19 +88,11 @@ public:
bool KillSpawnedProcess(lldb::pid_t pid);
/// Sends a GDB remote protocol 'A' packet that delivers program
/// arguments to the remote server.
/// Launch the process using the provided arguments.
///
/// \param[in] launch_info
/// A NULL terminated array of const C strings to use as the
/// arguments.
///
/// \return
/// Zero if the response was "OK", a positive value if the
/// the response was "Exx" where xx are two hex digits, or
/// -1 if the call is unsupported or any other unexpected
/// response was received.
int SendArgumentsPacket(const ProcessLaunchInfo &launch_info);
/// \param[in] args
/// A list of program arguments. The first entry is the program being run.
llvm::Error LaunchProcess(const Args &args);
/// Sends a "QEnvironment:NAME=VALUE" packet that will build up the
/// environment that will get used when launching an application

View File

@ -84,6 +84,7 @@
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/FormatAdapters.h"
#include "llvm/Support/Threading.h"
#include "llvm/Support/raw_ostream.h"
@ -799,17 +800,17 @@ Status ProcessGDBRemote::DoLaunch(lldb_private::Module *exe_module,
GDBRemoteCommunication::ScopedTimeout timeout(m_gdb_comm,
std::chrono::seconds(10));
int arg_packet_err = m_gdb_comm.SendArgumentsPacket(launch_info);
if (arg_packet_err == 0) {
std::string error_str;
if (m_gdb_comm.GetLaunchSuccess(error_str)) {
SetID(m_gdb_comm.GetCurrentProcessID());
} else {
error.SetErrorString(error_str.c_str());
}
// Since we can't send argv0 separate from the executable path, we need to
// make sure to use the actual executable path found in the launch_info...
Args args = launch_info.GetArguments();
if (FileSpec exe_file = launch_info.GetExecutableFile())
args.ReplaceArgumentAtIndex(0, exe_file.GetPath(false));
if (llvm::Error err = m_gdb_comm.LaunchProcess(args)) {
error.SetErrorStringWithFormatv("Cannot launch '{0}': {1}",
args.GetArgumentAtIndex(0),
llvm::fmt_consume(std::move(err)));
} else {
error.SetErrorStringWithFormat("'A' packet returned an error: %i",
arg_packet_err);
SetID(m_gdb_comm.GetCurrentProcessID());
}
}

View File

@ -86,7 +86,30 @@ class TestGDBRemoteClient(GDBRemoteTestBase):
error = lldb.SBError()
target.Launch(lldb.SBListener(), None, None, None, None, None,
None, 0, True, error)
self.assertEquals("'A' packet returned an error: 71", error.GetCString())
self.assertRegex(error.GetCString(), "Cannot launch '.*a': Error 71")
def test_launch_rich_error(self):
class MyResponder(MockGDBServerResponder):
def qC(self):
return "E42"
def qfThreadInfo(self):
return "OK" # No threads.
# Then, when we are asked to attach, error out.
def vRun(self, packet):
return "Eff;" + seven.hexlify("I'm a teapot")
self.server.responder = MyResponder()
target = self.createTarget("a.yaml")
process = self.connect(target)
lldbutil.expect_state_changes(self, self.dbg.GetListener(), process, [lldb.eStateConnected])
error = lldb.SBError()
target.Launch(lldb.SBListener(), None, None, None, None, None,
None, 0, True, error)
self.assertRegex(error.GetCString(), "Cannot launch '.*a': I'm a teapot")
def test_read_registers_using_g_packets(self):
"""Test reading registers using 'g' packets (default behavior)"""