DebugServer2
Loading...
Searching...
No Matches
ProcessLaunchMixin.hpp
1//
2// Copyright (c) 2014-present, Facebook, Inc.
3// All rights reserved.
4//
5// This source code is licensed under the University of Illinois/NCSA Open
6// Source License found in the LICENSE file in the root directory of this
7// source tree. An additional grant of patent rights can be found in the
8// PATENTS file in the same directory.
9//
10
11#pragma once
12
13#include "DebugServer2/GDBRemote/Mixins/ProcessLaunchMixin.h"
14
15#include <algorithm>
16
17namespace ds2 {
18namespace GDBRemote {
19
20template <typename T>
21ErrorCode ProcessLaunchMixin<T>::onDisableASLR(Session &session, bool disable) {
22 _disableASLR = disable;
23 return kSuccess;
24}
25
26template <typename T>
27ErrorCode
28ProcessLaunchMixin<T>::onSetArchitecture(Session &,
29 std::string const &architecture) {
30 // Ignored for now.
31 return kSuccess;
32}
33
34template <typename T>
35ErrorCode
36ProcessLaunchMixin<T>::onSetWorkingDirectory(Session &,
37 std::string const &path) {
38 _workingDirectory = path;
39 return kSuccess;
40}
41
42template <typename T>
43ErrorCode
44ProcessLaunchMixin<T>::onQueryWorkingDirectory(Session &,
45 std::string &workingDir) const {
46 workingDir = _workingDirectory;
47 return kSuccess;
48}
49
50template <typename T>
51ErrorCode ProcessLaunchMixin<T>::onSetEnvironmentVariable(Session &,
52 std::string const &name, std::string const &value) {
53 if (value.empty()) {
54 _environment.erase(name);
55 } else {
56 _environment.insert(std::make_pair(name, value));
57 }
58 return kSuccess;
59}
60
61template <typename T>
62ErrorCode ProcessLaunchMixin<T>::onSetStdFile(Session &, int fileno,
63 std::string const &path) {
64 DS2LOG(Debug, "stdfile[%d] = %s", fileno, path.c_str());
65
66 if (fileno < 0 || fileno > 2)
67 return kErrorInvalidArgument;
68
69 if (fileno == 0) {
70 //
71 // TODO it seems that QSetSTDIN is the first message sent when
72 // launching a new process, it may be sane to invalidate all
73 // the process state at this point.
74 //
75 _disableASLR = false;
76 _arguments.clear();
77 _environment.clear();
78 _workingDirectory.clear();
79 _stdFile[0].clear();
80 _stdFile[1].clear();
81 _stdFile[2].clear();
82 }
83
84 _stdFile[fileno] = path;
85 return kSuccess;
86}
87
88template <typename T>
89ErrorCode
90ProcessLaunchMixin<T>::onSetProgramArguments(Session &,
91 StringCollection const &args) {
92 _arguments = args;
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");
100 }
101
102 return spawnProcess();
103
104}
105
106template <typename T>
107ErrorCode ProcessLaunchMixin<T>::onQueryLaunchSuccess(Session &,
108 ProcessId) const {
109 return _lastLaunchResult;
110}
111
112template <typename T>
113ErrorCode
114ProcessLaunchMixin<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
126template <typename T>
127ErrorCode 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.
151template <typename T>
152bool ProcessLaunchMixin<T>::isDebugServer(StringCollection const &args) {
153 return (args.size() > 1)
154 && (args[0] == Platform::GetSelfExecutablePath())
155 && (args[1] == "gdbserver");
156}
157
158template <typename T>
159ErrorCode 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
216 return kSuccess;
217}
218} // namespace GDBRemote
219} // namespace ds2