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>(args&: accept_socket);
129 auto Client = std::unique_ptr<TestClient>(new TestClient(std::move(Conn)));
130
131 if (Error E = Client->initializeConnection())
132 return std::move(E);
133
134 if (!InferiorArgs.empty()) {
135 if (Error E = Client->queryProcess())
136 return std::move(E);
137 }
138
139 return std::move(Client);
140}
141
142Error TestClient::SetInferior(llvm::ArrayRef<std::string> inferior_args) {
143 if (SendEnvironment(env: Host::GetEnvironment()) != 0) {
144 return make_error<StringError>(Args: "Failed to set launch environment",
145 Args: inconvertibleErrorCode());
146 }
147 std::stringstream command;
148 command << "A";
149 for (size_t i = 0; i < inferior_args.size(); i++) {
150 if (i > 0)
151 command << ',';
152 std::string hex_encoded = toHex(Input: inferior_args[i]);
153 command << hex_encoded.size() << ',' << i << ',' << hex_encoded;
154 }
155
156 if (Error E = SendMessage(message: command.str()))
157 return E;
158 if (Error E = SendMessage(message: "qLaunchSuccess"))
159 return E;
160 if (Error E = queryProcess())
161 return E;
162 return Error::success();
163}
164
165Error TestClient::ListThreadsInStopReply() {
166 return SendMessage(message: "QListThreadsInStopReply");
167}
168
169Error TestClient::SetBreakpoint(unsigned long address) {
170 return SendMessage(message: formatv(Fmt: "Z0,{0:x-},1", Vals&: address).str());
171}
172
173Error TestClient::ContinueAll() { return Continue(message: "vCont;c"); }
174
175Error TestClient::ContinueThread(unsigned long thread_id) {
176 return Continue(message: formatv(Fmt: "vCont;c:{0:x-}", Vals&: thread_id).str());
177}
178
179const llgs_tests::ProcessInfo &TestClient::GetProcessInfo() {
180 return *m_process_info;
181}
182
183Expected<JThreadsInfo> TestClient::GetJThreadsInfo() {
184 return SendMessage<JThreadsInfo>(Message: "jThreadsInfo", Args&: m_register_infos);
185}
186
187const StopReply &TestClient::GetLatestStopReply() {
188 assert(m_stop_reply);
189 return *m_stop_reply;
190}
191
192Error TestClient::SendMessage(StringRef message) {
193 std::string dummy_string;
194 return SendMessage(message, response_string&: dummy_string);
195}
196
197Error TestClient::SendMessage(StringRef message, std::string &response_string) {
198 if (Error E = SendMessage(message, response_string, expected_result: PacketResult::Success))
199 return E;
200 StringExtractorGDBRemote Extractor(response_string);
201 if (Extractor.IsErrorResponse())
202 return Extractor.GetStatus().ToError();
203 return Error::success();
204}
205
206Error TestClient::SendMessage(StringRef message, std::string &response_string,
207 PacketResult expected_result) {
208 StringExtractorGDBRemote response;
209 GTEST_LOG_(INFO) << "Send Packet: " << message.str();
210 PacketResult result = SendPacketAndWaitForResponse(payload: message, response);
211 response.GetEscapedBinaryData(str&: response_string);
212 GTEST_LOG_(INFO) << "Read Packet: " << response_string;
213 if (result != expected_result)
214 return make_error<StringError>(
215 Args: formatv(Fmt: "Error sending message `{0}`: {1}", Vals&: message, Vals&: result).str(),
216 Args: inconvertibleErrorCode());
217
218 return Error::success();
219}
220
221unsigned int TestClient::GetPcRegisterId() {
222 assert(m_pc_register != LLDB_INVALID_REGNUM);
223 return m_pc_register;
224}
225
226Error TestClient::qProcessInfo() {
227 m_process_info = std::nullopt;
228 auto InfoOr = SendMessage<ProcessInfo>(Message: "qProcessInfo");
229 if (!InfoOr)
230 return InfoOr.takeError();
231 m_process_info = std::move(*InfoOr);
232 return Error::success();
233}
234
235Error TestClient::qRegisterInfos() {
236 uint32_t reg_offset = 0;
237 for (unsigned int Reg = 0;; ++Reg) {
238 std::string Message = formatv(Fmt: "qRegisterInfo{0:x-}", Vals&: Reg).str();
239 Expected<RegisterInfo> InfoOr = SendMessage<RegisterInfoParser>(Message);
240 if (!InfoOr) {
241 consumeError(Err: InfoOr.takeError());
242 break;
243 }
244 m_register_infos.emplace_back(args: std::move(*InfoOr));
245
246 if (m_register_infos[Reg].byte_offset == LLDB_INVALID_INDEX32)
247 m_register_infos[Reg].byte_offset = reg_offset;
248
249 reg_offset =
250 m_register_infos[Reg].byte_offset + m_register_infos[Reg].byte_size;
251 if (m_register_infos[Reg].kinds[eRegisterKindGeneric] ==
252 LLDB_REGNUM_GENERIC_PC)
253 m_pc_register = Reg;
254 }
255 if (m_pc_register == LLDB_INVALID_REGNUM)
256 return make_parsing_error(format: "qRegisterInfo: generic");
257 return Error::success();
258}
259
260Error TestClient::queryProcess() {
261 if (Error E = qProcessInfo())
262 return E;
263 if (Error E = qRegisterInfos())
264 return E;
265 return Error::success();
266}
267
268Error TestClient::Continue(StringRef message) {
269 assert(m_process_info);
270
271 auto StopReplyOr = SendMessage<StopReply>(
272 Message: message, Args: m_process_info->GetEndian(), Args&: m_register_infos);
273 if (!StopReplyOr)
274 return StopReplyOr.takeError();
275
276 m_stop_reply = std::move(*StopReplyOr);
277 if (!isa<StopReplyStop>(Val: m_stop_reply)) {
278 StringExtractorGDBRemote R;
279 PacketResult result = ReadPacket(response&: R, timeout: GetPacketTimeout(), sync_on_timeout: false);
280 if (result != PacketResult::ErrorDisconnected) {
281 return make_error<StringError>(
282 Args: formatv(Fmt: "Expected connection close after sending {0}. Got {1}/{2} "
283 "instead.",
284 Vals&: message, Vals&: result, Vals: R.GetStringRef())
285 .str(),
286 Args: inconvertibleErrorCode());
287 }
288 }
289 return Error::success();
290}
291

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