Skip to content

Commit 6eb8c56

Browse files
authored
fix the platform session process launch implementation (#143)
## Purpose Add proper support for the `A` packet (program arguments) in the platform session implementation, launching programs on request from the connected debugger. Along with PR #146, addresses issue #137. ## Overview * Implement `ProcessLaunchMixin::onSetProgramArguments` to spawn the specified executable with stdio and environment variables memoized in previous calls to `onSetStdFile` and `onSetEnvironmentVariable`. Use the existing `ProcessSpawner` implementation for the heavy lifting. * Implement `ProcessLaunchMixin::onQueryProcessInfo` to enable the debugger to get details about the most recently launched process. Reuse the exiting `Platform::GetProcessInfo` function to retrieve the process information. * Rename the pid-based `SessionDelegate::onQueryProcessInfo` method to `SessionDelegate::onQueryProcessInfoPID` to fix a `-Woverloaded-virtual` compiler warning. This also makes the method name consistent with its associated command packet `qProcessInfoPID` (as `onQueryProcessInfo` is with the `qProcessInfo` packet). * Implement `ProcessLaunchMixin::onTerminate` to kill the specified process and wait for it to exit. This method is invoked when a `vKill` packet is received. Only allow processes that were previously launched via this mechanism to be killed in this way by maintaining a list of all launched processes. * Implement the POSIX version of a new `Platform::TerminateProcess` function, which uses `kill(2)` and `waitpid(2)` to kill the requested process. * Stub-out the Windows version of `Platform::TerminateProcess` for future implementation. There is no platform mode for Windows, so this code path is not currently reachable. * Special-case the scenario where the program being launched is ds2 in gdbserver mode and propagate logging settings to make it easier to debug test failures. ## Problem Details Typically, the `qLaunchGDBServer` packet is used to launch another instance of ds2 running in "gdb server" mode. Many of the lldb tests use this mechanism. However, some tests use the `A` (program arguments) packet to launch new ds2 instances instead, and this method doesn't work correctly when ds2 is running in "platform" mode. Currently, ds2 uses the `ProcessLaunchMixin` implementation in `PlatformSession` to ostensibly support launching processes. However, it only memoizes the program details (environment, stdio files) and never actually launches the program even though it reports success to the connected debugger. It is worth noting that the `A` packet *is* handled correctly by `DebugSession`, but that implementation is specific to launching inferiors to be debugged (setting up ptrace, etc). `PlatformSession` cannot share the same implementation because platform mode launches programs without the intent of debugging them. They can both use `ProcessSpawner` for the bulk of the implementation. ## Validation Once this PR and #146 are merged, most of the `TestGdbRemote*` and `TestLldbGdbServer*` lldb tests succeed with ds2 running on Android and Linux and can be enabled in CI. They do not all pass with this PR alone, but local validation confirms the following: * Child debug server instances are launching in response to `A` packets and connect to the debugger/test over independent sockets * Child processes are being killed and cleaned up in response to `vKill` packets received during test cleanup
1 parent 5628fcf commit 6eb8c56

File tree

11 files changed

+170
-16
lines changed

11 files changed

+170
-16
lines changed

Headers/DebugServer2/GDBRemote/DummySessionDelegateImpl.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ class DummySessionDelegateImpl : public SessionDelegate {
252252

253253
ErrorCode onQueryProcessList(Session &session, ProcessInfoMatch const &match,
254254
bool first, ProcessInfo &info) const override;
255-
ErrorCode onQueryProcessInfo(Session &session, ProcessId pid,
256-
ProcessInfo &info) const override;
255+
ErrorCode onQueryProcessInfoPID(Session &session, ProcessId pid,
256+
ProcessInfo &info) const override;
257257

258258
ErrorCode onLaunchDebugServer(Session &session, std::string const &host,
259259
uint16_t &port, ProcessId &pid) override;

Headers/DebugServer2/GDBRemote/Mixins/ProcessLaunchMixin.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ template <typename T> class ProcessLaunchMixin : public T {
3434
EnvironmentMap _environment;
3535
std::string _stdFile[3];
3636
StringCollection _arguments;
37+
std::vector<ProcessId> _processList;
38+
ErrorCode _lastLaunchResult;
3739

3840
public:
3941
template <typename... Args>
4042
explicit ProcessLaunchMixin(Args &&... args)
4143
: T(std::forward<Args>(args)...), _disableASLR(false),
42-
_workingDirectory(Platform::GetWorkingDirectory()) {}
44+
_workingDirectory(Platform::GetWorkingDirectory()),
45+
_lastLaunchResult(kSuccess) {}
4346

4447
public:
4548
ErrorCode onDisableASLR(Session &session, bool disable) override;
@@ -57,6 +60,14 @@ template <typename T> class ProcessLaunchMixin : public T {
5760
StringCollection const &args) override;
5861
ErrorCode onQueryLaunchSuccess(Session &session,
5962
ProcessId pid) const override;
63+
ErrorCode onQueryProcessInfo(Session &session,
64+
ProcessInfo &info) const override;
65+
ErrorCode onTerminate(Session &session, ProcessThreadId const &ptid,
66+
StopInfo &stop) override;
67+
68+
private:
69+
ErrorCode spawnProcess();
70+
static bool isDebugServer(StringCollection const &args);
6071
};
6172
} // namespace GDBRemote
6273
} // namespace ds2

Headers/DebugServer2/GDBRemote/PlatformSessionImpl.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ class PlatformSessionImplBase : public DummySessionDelegateImpl {
2929
protected:
3030
ErrorCode onQueryProcessList(Session &session, ProcessInfoMatch const &match,
3131
bool first, ProcessInfo &info) const override;
32-
ErrorCode onQueryProcessInfo(Session &session, ProcessId pid,
33-
ProcessInfo &info) const override;
32+
ErrorCode onQueryProcessInfoPID(Session &session, ProcessId pid,
33+
ProcessInfo &info) const override;
3434

3535
ErrorCode onExecuteProgram(Session &session, std::string const &command,
3636
uint32_t timeout,

Headers/DebugServer2/GDBRemote/SessionDelegate.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@ class SessionDelegate {
280280
virtual ErrorCode onQueryProcessList(Session &session,
281281
ProcessInfoMatch const &match,
282282
bool first, ProcessInfo &info) const = 0;
283-
virtual ErrorCode onQueryProcessInfo(Session &session, ProcessId pid,
284-
ProcessInfo &info) const = 0;
283+
virtual ErrorCode onQueryProcessInfoPID(Session &session, ProcessId pid,
284+
ProcessInfo &info) const = 0;
285285

286286
virtual ErrorCode onLaunchDebugServer(Session &session,
287287
std::string const &host, uint16_t &port,

Headers/DebugServer2/Host/Platform.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class Platform {
7474
static void
7575
EnumerateProcesses(bool allUsers, UserId const &uid,
7676
std::function<void(ProcessInfo const &info)> const &cb);
77+
static bool TerminateProcess(ProcessId pid);
7778

7879
public:
7980
static std::string GetThreadName(ProcessId pid, ThreadId tid);

Sources/GDBRemote/DummySessionDelegateImpl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ DUMMY_IMPL_EMPTY_CONST(onFileFstat, Session &, int, ByteVector &)
300300
DUMMY_IMPL_EMPTY_CONST(onQueryProcessList, Session &, ProcessInfoMatch const &,
301301
bool, ProcessInfo &)
302302

303-
DUMMY_IMPL_EMPTY_CONST(onQueryProcessInfo, Session &, ProcessId, ProcessInfo &)
303+
DUMMY_IMPL_EMPTY_CONST(onQueryProcessInfoPID, Session &, ProcessId, ProcessInfo &)
304304

305305
DUMMY_IMPL_EMPTY(onLaunchDebugServer, Session &, std::string const &,
306306
uint16_t &, ProcessId &)

Sources/GDBRemote/Mixins/ProcessLaunchMixin.hpp

Lines changed: 121 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
#include "DebugServer2/GDBRemote/Mixins/ProcessLaunchMixin.h"
1414

15+
#include <algorithm>
16+
1517
namespace ds2 {
1618
namespace GDBRemote {
1719

@@ -46,8 +48,8 @@ ProcessLaunchMixin<T>::onQueryWorkingDirectory(Session &,
4648
}
4749

4850
template <typename T>
49-
ErrorCode ProcessLaunchMixin<T>::onSetEnvironmentVariable(
50-
Session &, std::string const &name, std::string const &value) {
51+
ErrorCode ProcessLaunchMixin<T>::onSetEnvironmentVariable(Session &,
52+
std::string const &name, std::string const &value) {
5153
if (value.empty()) {
5254
_environment.erase(name);
5355
} else {
@@ -88,15 +90,129 @@ ErrorCode
8890
ProcessLaunchMixin<T>::onSetProgramArguments(Session &,
8991
StringCollection const &args) {
9092
_arguments = args;
91-
for (auto const &arg : _arguments) {
92-
DS2LOG(Debug, "arg=%s", arg.c_str());
93+
if (isDebugServer(args)) {
94+
// Propagate current logging settings when launching an instance of
95+
// the debug server to match qLaunchGDBServer behavior.
96+
if (GetLogLevel() == kLogLevelDebug)
97+
_arguments.push_back("--debug");
98+
else if (GetLogLevel() == kLogLevelPacket)
99+
_arguments.push_back("--remote-debug");
93100
}
94-
return kSuccess;
101+
102+
return spawnProcess();
103+
95104
}
96105

97106
template <typename T>
98107
ErrorCode ProcessLaunchMixin<T>::onQueryLaunchSuccess(Session &,
99108
ProcessId) const {
109+
return _lastLaunchResult;
110+
}
111+
112+
template <typename T>
113+
ErrorCode
114+
ProcessLaunchMixin<T>::onQueryProcessInfo(Session &,
115+
ProcessInfo &info) const {
116+
if (_processList.empty())
117+
return kErrorProcessNotFound;
118+
119+
const ProcessId pid = _processList.back();
120+
if (!Platform::GetProcessInfo(pid, info))
121+
return kErrorUnknown;
122+
123+
return kSuccess;
124+
}
125+
126+
template <typename T>
127+
ErrorCode ProcessLaunchMixin<T>::onTerminate(Session &session,
128+
ProcessThreadId const &ptid,
129+
StopInfo &stop) {
130+
auto it = std::find(_processList.begin(), _processList.end(), ptid.pid);
131+
if (it == _processList.end())
132+
return kErrorNotFound;
133+
134+
if (!Platform::TerminateProcess(ptid.pid))
135+
return kErrorUnknown;
136+
137+
DS2LOG(Debug, "killed spawned process %" PRI_PID, *it);
138+
_processList.erase(it);
139+
140+
// StopInfo is not populated by this implemenation of onTerminate since it is
141+
// only ever called in the context of a platform session in response to a
142+
// qKillSpawnedProcess packet.
143+
stop.clear();
144+
return kSuccess;
145+
}
146+
147+
// Some test cases use this code path to launch additional instances of the
148+
// debug server rather than sending a qLaunchGDBServer packet. Allow detection
149+
// of this scenario so we can propagate logging settings and make debugging
150+
// test failures easier.
151+
template <typename T>
152+
bool ProcessLaunchMixin<T>::isDebugServer(StringCollection const &args) {
153+
return (args.size() > 1)
154+
&& (args[0] == Platform::GetSelfExecutablePath())
155+
&& (args[1] == "gdbserver");
156+
}
157+
158+
template <typename T>
159+
ErrorCode ProcessLaunchMixin<T>::spawnProcess() {
160+
const bool displayArgs = _arguments.size() > 1;
161+
const bool displayEnv = !_environment.empty();
162+
auto it = _arguments.begin();
163+
DS2LOG(Debug, "spawning process '%s'%s", (it++)->c_str(),
164+
displayArgs ? " with args:" : "");
165+
while (it != _arguments.end()) {
166+
DS2LOG(Debug, " %s", (it++)->c_str());
167+
}
168+
169+
if (displayEnv) {
170+
DS2LOG(Debug, "%swith environment:", displayArgs ? "and " : "");
171+
for (auto const &val : _environment) {
172+
DS2LOG(Debug, " %s=%s", val.first.c_str(), val.second.c_str());
173+
}
174+
}
175+
176+
if (!_workingDirectory.empty()) {
177+
DS2LOG(Debug, "%swith working directory: %s",
178+
displayArgs || displayEnv ? "and " : "",
179+
_workingDirectory.c_str());
180+
}
181+
182+
Host::ProcessSpawner ps;
183+
if (!ps.setExecutable(_arguments[0]) ||
184+
!ps.setArguments(StringCollection(_arguments.begin() + 1,
185+
_arguments.end())) ||
186+
!ps.setWorkingDirectory(_workingDirectory) ||
187+
!ps.setEnvironment(_environment))
188+
return kErrorInvalidArgument;
189+
190+
if (isDebugServer(_arguments)) {
191+
// Always log to the console when launching an instance of the debug server
192+
// to match qLaunchGDBServer behavior.
193+
ps.redirectInputToNull();
194+
ps.redirectOutputToConsole();
195+
ps.redirectErrorToConsole();
196+
} else {
197+
if (!_stdFile[0].empty() && !ps.redirectInputToFile(_stdFile[0]))
198+
return kErrorInvalidArgument;
199+
if (!_stdFile[1].empty() && !ps.redirectOutputToFile(_stdFile[1]))
200+
return kErrorInvalidArgument;
201+
if (!_stdFile[2].empty() && !ps.redirectErrorToFile(_stdFile[2]))
202+
return kErrorInvalidArgument;
203+
}
204+
205+
_lastLaunchResult = ps.run();
206+
if (_lastLaunchResult != kSuccess)
207+
return _lastLaunchResult;
208+
209+
// Add the pid to the list of launched processes. It will be removed when
210+
// onTerminate is called.
211+
_processList.push_back(ps.pid());
212+
213+
DS2LOG(Debug, "launched %s as process %" PRI_PID, _arguments[0].c_str(),
214+
ps.pid());
215+
100216
return kSuccess;
101217
}
102218
} // namespace GDBRemote

Sources/GDBRemote/PlatformSessionImpl.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ ErrorCode PlatformSessionImplBase::onQueryProcessList(
3838
return kSuccess;
3939
}
4040

41-
ErrorCode PlatformSessionImplBase::onQueryProcessInfo(Session &, ProcessId pid,
42-
ProcessInfo &info) const {
41+
ErrorCode
42+
PlatformSessionImplBase::onQueryProcessInfoPID(Session &, ProcessId pid,
43+
ProcessInfo &info) const {
4344
if (!Platform::GetProcessInfo(pid, info))
4445
return kErrorProcessNotFound;
4546
else

Sources/GDBRemote/Session.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2019,7 +2019,7 @@ void Session::Handle_qProcessInfoPID(ProtocolInterpreter::Handler const &,
20192019
ProcessId pid = std::strtoul(args.c_str(), nullptr, 10);
20202020

20212021
ProcessInfo info;
2022-
CHK_SEND(_delegate->onQueryProcessInfo(*this, pid, info));
2022+
CHK_SEND(_delegate->onQueryProcessInfoPID(*this, pid, info));
20232023

20242024
send(info.encode(_compatMode, true));
20252025
}

Sources/Host/POSIX/Platform.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
#include <grp.h>
2020
#include <netdb.h>
2121
#include <pwd.h>
22+
#include <signal.h>
2223
#include <string>
2324
#include <unistd.h>
2425

26+
#include <sys/wait.h>
27+
2528
#if defined(OS_FREEBSD) || defined(OS_DARWIN)
2629
#include <sys/socket.h>
2730
#include <sys/types.h>
@@ -124,6 +127,23 @@ bool Platform::SetWorkingDirectory(std::string const &directory) {
124127

125128
ds2::ProcessId Platform::GetCurrentProcessId() { return ::getpid(); }
126129

130+
// Terminates the process and waits for it to exit before returning.
131+
bool Platform::TerminateProcess(ProcessId pid) {
132+
if (::kill(pid, SIGKILL) < 0 && errno != ESRCH)
133+
return false;
134+
135+
int status;
136+
pid_t ret;
137+
do {
138+
ret = ::waitpid(pid, &status, 0);
139+
} while (ret == -1 && errno == EINTR);
140+
141+
if (ret != pid)
142+
return false;
143+
144+
return WIFEXITED(status) || WIFSIGNALED(status);
145+
}
146+
127147
bool Platform::GetCurrentEnvironment(EnvironmentBlock &env) {
128148
for (int i = 0; environ[i] != nullptr; ++i) {
129149
char *equal = strchr(environ[i], '=');

Sources/Host/Windows/Platform.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,11 @@ void Platform::EnumerateProcesses(
375375
}
376376
}
377377

378+
bool Platform::TerminateProcess(ProcessId pid) {
379+
// TODO(andrurogerz): implement using OpenProcess and TerminateProcess.
380+
return false;
381+
}
382+
378383
std::string Platform::GetThreadName(ProcessId pid, ThreadId tid) {
379384
// Note(sas): There is no thread name concept on Windows.
380385
// http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx describes a

0 commit comments

Comments
 (0)