1//===-- TestClient.cpp ----------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "TestClient.h"
10#include "TestingSupport/Host/SocketTestUtilities.h"
11#include "lldb/Host/HostInfo.h"
12#include "lldb/Host/common/TCPSocket.h"
13#include "lldb/Host/posix/ConnectionFileDescriptorPosix.h"
14#include "lldb/Utility/Args.h"
15#include "llvm/ADT/StringExtras.h"
16#include "llvm/Support/Path.h"
17#include "llvm/Testing/Support/Error.h"
18#include "gtest/gtest.h"
19#include <cstdlib>
20#include <future>
21#include <sstream>
22#include <string>
23
24using namespace lldb;
25using namespace lldb_private;
26using namespace llvm;
27using namespace llgs_tests;
28
29static std::chrono::seconds GetDefaultTimeout() {
30 return std::chrono::seconds{10};
31}
32
33TestClient::TestClient(std::unique_ptr<Connection> Conn) {
34 SetConnection(std::move(Conn));
35 SetPacketTimeout(GetDefaultTimeout());
36}
37
38TestClient::~TestClient() {
39 if (!IsConnected())
40 return;
41
42 EXPECT_THAT_ERROR(SendMessage("k"), Succeeded());
43}
44
45Error TestClient::initializeConnection() {
46 if (SendAck() == 0)
47 return make_error<StringError>(Args: "Sending initial ACK failed.",
48 Args: inconvertibleErrorCode());
49
50 if (Error E = SendMessage(message: "QStartNoAckMode"))
51 return E;
52
53 m_send_acks = false;
54 return Error::success();
55}
56
57Expected<std::unique_ptr<TestClient>> TestClient::launch(StringRef Log) {
58 return launch(Log, InferiorArgs: {});
59}
60
61Expected<std::unique_ptr<TestClient>> TestClient::launch(StringRef Log, ArrayRef<StringRef> InferiorArgs) {
62 return launchCustom(Log, disable_stdio: false, ServerArgs: {}, InferiorArgs);
63}
64
65Expected<std::unique_ptr<TestClient>>
66TestClient::launchCustom(StringRef Log, bool disable_stdio,
67 ArrayRef<StringRef> ServerArgs,
68 ArrayRef<StringRef> InferiorArgs) {
69 const ArchSpec &arch_spec = HostInfo::GetArchitecture();
70 Args args;
71 args.AppendArgument(LLDB_SERVER);
72 if (IsLldbServer())
73 args.AppendArgument(arg_str: "gdbserver");
74 args.AppendArgument(arg_str: "--reverse-connect");
75
76 if (!Log.empty()) {
77 args.AppendArgument(arg_str: ("--log-file=" + Log).str());
78 if (IsLldbServer())
79 args.AppendArgument(arg_str: "--log-channels=gdb-remote packets");
80 else
81 args.AppendArgument(arg_str: "--log-flags=0x800000");
82 }
83
84 auto LocalhostIPOrErr = GetLocalhostIP();
85 if (!LocalhostIPOrErr)
86 return LocalhostIPOrErr.takeError();
87 const std::string &LocalhostIP = *LocalhostIPOrErr;
88
89 Status status;
90 TCPSocket listen_socket(true);
91 status = listen_socket.Listen(name: LocalhostIP + ":0", backlog: 5);
92 if (status.Fail())
93 return status.ToError();
94
95 args.AppendArgument(
96 arg_str: formatv(Fmt: "{0}:{1}", Vals: LocalhostIP, Vals: listen_socket.GetLocalPortNumber())
97 .str());
98
99 for (StringRef arg : ServerArgs)
100 args.AppendArgument(arg_str: arg);
101
102 if (!InferiorArgs.empty()) {
103 args.AppendArgument(arg_str: "--");
104 for (StringRef arg : InferiorArgs)
105 args.AppendArgument(arg_str: arg);
106 }
107
108 ProcessLaunchInfo Info;
109 Info.SetArchitecture(arch_spec);
110 Info.SetArguments(args, first_arg_is_executable: true);
111 Info.GetEnvironment() = Host::GetEnvironment();
112 // TODO: Use this callback to detect botched launches. If lldb-server does not
113 // start, we can print a nice error message here instead of hanging in
114 // Accept().
115 Info.SetMonitorProcessCallback(&ProcessLaunchInfo::NoOpMonitorCallback);
116
117 if (disable_stdio)
118 Info.GetFlags().Set(lldb::eLaunchFlagDisableSTDIO);
119 status = Host::LaunchProcess(launch_info&: Info);
120 if (status.Fail())
121 return status.ToError();
122
123 Socket *accept_socket;
124 if (llvm::Error E =
125 listen_socket.Accept(timeout: 2 * GetDefaultTimeout(), socket&: accept_socket)
126 .takeError())
127 return E;
128 auto Conn = std::make_unique<ConnectionFileDescriptor>(
129 args: std::unique_ptr<Socket>(accept_socket));
130 auto Client = std::unique_ptr<TestClient>(new TestClient(std::move(Conn)));
131
132 if (Error E = Client->initializeConnection())
133 return std::move(E);
134
135 if (!InferiorArgs.empty()) {
136 if (Error E = Client->queryProcess())
137 return std::move(E);
138 }
139
140 return std::move(Client);
141}
142
143Error TestClient::SetInferior(llvm::ArrayRef<std::string> inferior_args) {
144 if (SendEnvironment(env: Host::GetEnvironment()) != 0) {
145 return make_error<StringError>(Args: "Failed to set launch environment",
146 Args: inconvertibleErrorCode());
147 }
148 std::stringstream command;
149 command << "A";
150 for (size_t i = 0; i < inferior_args.size(); i++) {
151 if (i > 0)
152 command << ',';
153 std::string hex_encoded = toHex(Input: inferior_args[i]);
154 command << hex_encoded.size() << ',' << i << ',' << hex_encoded;
155 }
156
157 if (Error E = SendMessage(message: command.str()))
158 return E;
159 if (Error E = SendMessage(message: "qLaunchSuccess"))
160 return E;
161 if (Error E = queryProcess())
162 return E;
163 return Error::success();
164}
165
166Error TestClient::ListThreadsInStopReply() {
167 return SendMessage(message: "QListThreadsInStopReply");
168}
169
170Error TestClient::SetBreakpoint(unsigned long address) {
171 return SendMessage(message: formatv(Fmt: "Z0,{0:x-},1", Vals&: address).str());
172}
173
174Error TestClient::ContinueAll() { return Continue(message: "vCont;c"); }
175
176Error TestClient::ContinueThread(unsigned long thread_id) {
177 return Continue(message: formatv(Fmt: "vCont;c:{0:x-}", Vals&: thread_id).str());
178}
179
180const llgs_tests::ProcessInfo &TestClient::GetProcessInfo() {
181 return *m_process_info;
182}
183
184Expected<JThreadsInfo> TestClient::GetJThreadsInfo() {
185 return SendMessage<JThreadsInfo>(Message: "jThreadsInfo", Args&: m_register_infos);
186}
187
188const StopReply &TestClient::GetLatestStopReply() {
189 assert(m_stop_reply);
190 return *m_stop_reply;
191}
192
193Error TestClient::SendMessage(StringRef message) {
194 std::string dummy_string;
195 return SendMessage(message, response_string&: dummy_string);
196}
197
198Error TestClient::SendMessage(StringRef message, std::string &response_string) {
199 if (Error E = SendMessage(message, response_string, expected_result: PacketResult::Success))
200 return E;
201 StringExtractorGDBRemote Extractor(response_string);
202 if (Extractor.IsErrorResponse())
203 return Extractor.GetStatus().ToError();
204 return Error::success();
205}
206
207Error TestClient::SendMessage(StringRef message, std::string &response_string,
208 PacketResult expected_result) {
209 StringExtractorGDBRemote response;
210 GTEST_LOG_(INFO) << "Send Packet: " << message.str();
211 PacketResult result = SendPacketAndWaitForResponse(payload: message, response);
212 response.GetEscapedBinaryData(str&: response_string);
213 GTEST_LOG_(INFO) << "Read Packet: " << response_string;
214 if (result != expected_result)
215 return make_error<StringError>(
216 Args: formatv(Fmt: "Error sending message `{0}`: {1}", Vals&: message, Vals&: result).str(),
217 Args: inconvertibleErrorCode());
218
219 return Error::success();
220}
221
222unsigned int TestClient::GetPcRegisterId() {
223 assert(m_pc_register != LLDB_INVALID_REGNUM);
224 return m_pc_register;
225}
226
227Error TestClient::qProcessInfo() {
228 m_process_info = std::nullopt;
229 auto InfoOr = SendMessage<ProcessInfo>(Message: "qProcessInfo");
230 if (!InfoOr)
231 return InfoOr.takeError();
232 m_process_info = std::move(*InfoOr);
233 return Error::success();
234}
235
236Error TestClient::qRegisterInfos() {
237 uint32_t reg_offset = 0;
238 for (unsigned int Reg = 0;; ++Reg) {
239 std::string Message = formatv(Fmt: "qRegisterInfo{0:x-}", Vals&: Reg).str();
240 Expected<RegisterInfo> InfoOr = SendMessage<RegisterInfoParser>(Message);
241 if (!InfoOr) {
242 consumeError(Err: InfoOr.takeError());
243 break;
244 }
245 m_register_infos.emplace_back(args: std::move(*InfoOr));
246
247 if (m_register_infos[Reg].byte_offset == LLDB_INVALID_INDEX32)
248 m_register_infos[Reg].byte_offset = reg_offset;
249
250 reg_offset =
251 m_register_infos[Reg].byte_offset + m_register_infos[Reg].byte_size;
252 if (m_register_infos[Reg].kinds[eRegisterKindGeneric] ==
253 LLDB_REGNUM_GENERIC_PC)
254 m_pc_register = Reg;
255 }
256 if (m_pc_register == LLDB_INVALID_REGNUM)
257 return make_parsing_error(format: "qRegisterInfo: generic");
258 return Error::success();
259}
260
261Error TestClient::queryProcess() {
262 if (Error E = qProcessInfo())
263 return E;
264 if (Error E = qRegisterInfos())
265 return E;
266 return Error::success();
267}
268
269Error TestClient::Continue(StringRef message) {
270 assert(m_process_info);
271
272 auto StopReplyOr = SendMessage<StopReply>(
273 Message: message, Args: m_process_info->GetEndian(), Args&: m_register_infos);
274 if (!StopReplyOr)
275 return StopReplyOr.takeError();
276
277 m_stop_reply = std::move(*StopReplyOr);
278 if (!isa<StopReplyStop>(Val: m_stop_reply)) {
279 StringExtractorGDBRemote R;
280 PacketResult result = ReadPacket(response&: R, timeout: GetPacketTimeout(), sync_on_timeout: false);
281 if (result != PacketResult::ErrorDisconnected) {
282 return make_error<StringError>(
283 Args: formatv(Fmt: "Expected connection close after sending {0}. Got {1}/{2} "
284 "instead.",
285 Vals&: message, Vals&: result, Vals: R.GetStringRef())
286 .str(),
287 Args: inconvertibleErrorCode());
288 }
289 }
290 return Error::success();
291}
292

source code of lldb/unittests/tools/lldb-server/tests/TestClient.cpp