Skip to content

Commit 0b7cc85

Browse files
committed
[lldb-dap] Implement runInTerminal for Windows
Currently, the named pipe is passed by name and a transient ofstream is constructed at each I/O request. This assumes, - Blocking semantics: FIFO I/O waits for the other side to connect. - Buffered semantics: Closing one side does not discard existing data. The former can be replaced by WaitNamedPipe/ConnectNamedPipe on Win32, but the second cannot be easily worked around. It is also impossible to have another "keep-alive" pipe server instance, as server-client pairs are fixed on connection on Win32 and the client may get connected to it instead of the real one. Refactor FifoFile[IO] to use an open file handles rather than file name. --- Win32 provides no way to replace the process image. Under the hood exec* actually creates a new process with a new PID. DebugActiveProcess also cannot get notified of process creations. Create the new process in a suspended state and resume it after attach.
1 parent e15545c commit 0b7cc85

File tree

11 files changed

+243
-83
lines changed

11 files changed

+243
-83
lines changed

lldb/packages/Python/lldbsuite/test/dotest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ def setupSysPath():
545545

546546
lldbDir = os.path.dirname(lldbtest_config.lldbExec)
547547

548-
lldbDAPExec = os.path.join(lldbDir, "lldb-dap")
548+
lldbDAPExec = os.path.join(lldbDir, "lldb-dap.exe" if os.name == "nt" else "lldb-dap")
549549
if is_exe(lldbDAPExec):
550550
os.environ["LLDBDAP_EXEC"] = lldbDAPExec
551551

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
C_SOURCES := main.c
1+
CXX_SOURCES := main.cpp
22

33
include Makefile.rules

lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ def isTestSupported(self):
4343
except:
4444
return False
4545

46-
@skipIfWindows
4746
@skipIf(archs=no_match(["x86_64"]))
4847
def test_runInTerminal(self):
4948
if not self.isTestSupported():
@@ -53,7 +52,7 @@ def test_runInTerminal(self):
5352
launch the inferior with the correct environment variables and arguments.
5453
"""
5554
program = self.getBuildArtifact("a.out")
56-
source = "main.c"
55+
source = "main.cpp"
5756
self.build_and_launch(
5857
program, runInTerminal=True, args=["foobar"], env=["FOO=bar"]
5958
)
@@ -113,7 +112,6 @@ def test_runInTerminalWithObjectEnv(self):
113112
self.assertIn("FOO", request_envs)
114113
self.assertEqual("BAR", request_envs["FOO"])
115114

116-
@skipIfWindows
117115
@skipIf(archs=no_match(["x86_64"]))
118116
def test_runInTerminalInvalidTarget(self):
119117
if not self.isTestSupported():
@@ -132,7 +130,6 @@ def test_runInTerminalInvalidTarget(self):
132130
response["message"],
133131
)
134132

135-
@skipIfWindows
136133
@skipIf(archs=no_match(["x86_64"]))
137134
def test_missingArgInRunInTerminalLauncher(self):
138135
if not self.isTestSupported():

lldb/test/API/tools/lldb-dap/runInTerminal/main.c

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <chrono>
2+
#include <cstdlib>
3+
#include <thread>
4+
5+
using namespace std;
6+
7+
int main(int argc, char *argv[]) {
8+
const char *foo = getenv("FOO");
9+
for (int counter = 1;; counter++) {
10+
this_thread::sleep_for(chrono::seconds(1)); // breakpoint
11+
}
12+
return 0;
13+
}

lldb/tools/lldb-dap/FifoFiles.cpp

Lines changed: 112 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,15 @@
88

99
#include "FifoFiles.h"
1010
#include "JSONUtils.h"
11+
#include "llvm/Support/FileSystem.h"
12+
#include "llvm/Support/Path.h"
13+
#include "llvm/Support/WindowsError.h"
1114

12-
#if !defined(_WIN32)
15+
#if defined(_WIN32)
16+
#include <Windows.h>
17+
#include <fcntl.h>
18+
#include <io.h>
19+
#else
1320
#include <sys/stat.h>
1421
#include <sys/types.h>
1522
#include <unistd.h>
@@ -24,41 +31,105 @@ using namespace llvm;
2431

2532
namespace lldb_dap {
2633

27-
FifoFile::FifoFile(StringRef path) : m_path(path) {}
34+
FifoFile::FifoFile(std::string path, FILE *f) : m_path(path), m_file(f) {}
35+
36+
Expected<FifoFile> FifoFile::create(StringRef path) {
37+
auto file = fopen(path.data(), "r+");
38+
if (file == nullptr)
39+
return createStringError(inconvertibleErrorCode(),
40+
"Failed to open fifo file " + path);
41+
if (setvbuf(file, NULL, _IONBF, 0))
42+
return createStringError(inconvertibleErrorCode(),
43+
"Failed to setvbuf on fifo file " + path);
44+
return FifoFile(path, file);
45+
}
46+
47+
FifoFile::FifoFile(StringRef path, FILE *f) : m_path(path), m_file(f) {}
2848

49+
FifoFile::FifoFile(FifoFile &&other)
50+
: m_path(other.m_path), m_file(other.m_file) {
51+
other.m_path.clear();
52+
other.m_file = nullptr;
53+
}
2954
FifoFile::~FifoFile() {
55+
if (m_file)
56+
fclose(m_file);
3057
#if !defined(_WIN32)
58+
// Unreferenced named pipes are deleted automatically on Win32
3159
unlink(m_path.c_str());
3260
#endif
3361
}
3462

35-
Expected<std::shared_ptr<FifoFile>> CreateFifoFile(StringRef path) {
36-
#if defined(_WIN32)
37-
return createStringError(inconvertibleErrorCode(), "Unimplemented");
63+
// This probably belongs to llvm::sys::fs as another FSEntity type
64+
Error createUniqueNamedPipe(const Twine &prefix, StringRef suffix,
65+
int &result_fd,
66+
SmallVectorImpl<char> &result_path) {
67+
std::error_code EC;
68+
#ifdef _WIN32
69+
const char *middle = suffix.empty() ? "-%%%%%%" : "-%%%%%%.";
70+
EC = sys::fs::getPotentiallyUniqueFileName(
71+
"\\\\.\\pipe\\LOCAL\\" + prefix + middle + suffix, result_path);
72+
#else
73+
EC = sys::fs::getPotentiallyUniqueTempFileName(prefix, suffix, result_path);
74+
#endif
75+
if (EC)
76+
return errorCodeToError(EC);
77+
result_path.push_back(0);
78+
const char *path = result_path.data();
79+
#ifdef _WIN32
80+
HANDLE h = ::CreateNamedPipeA(
81+
path, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
82+
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 512, 512, 0, NULL);
83+
if (h == INVALID_HANDLE_VALUE)
84+
return createStringError(mapLastWindowsError(), "CreateNamedPipe");
85+
result_fd = _open_osfhandle((intptr_t)h, _O_TEXT | _O_RDWR);
86+
if (result_fd == -1)
87+
return createStringError(mapLastWindowsError(), "_open_osfhandle");
3888
#else
39-
if (int err = mkfifo(path.data(), 0600))
40-
return createStringError(std::error_code(err, std::generic_category()),
41-
"Couldn't create fifo file: %s", path.data());
42-
return std::make_shared<FifoFile>(path);
89+
if (mkfifo(path, 0600) == -1)
90+
return createStringError(std::error_code(errno, std::generic_category()),
91+
"mkfifo");
92+
EC = openFileForWrite(result_path, result_fd, sys::fs::CD_OpenExisting,
93+
sys::fs::OF_None, 0600);
94+
if (EC)
95+
return errorCodeToError(EC);
4396
#endif
97+
result_path.pop_back();
98+
return Error::success();
4499
}
45100

46-
FifoFileIO::FifoFileIO(StringRef fifo_file, StringRef other_endpoint_name)
47-
: m_fifo_file(fifo_file), m_other_endpoint_name(other_endpoint_name) {}
101+
FifoFileIO::FifoFileIO(FifoFile &&fifo_file, StringRef other_endpoint_name)
102+
: m_fifo_file(std::move(fifo_file)),
103+
m_other_endpoint_name(other_endpoint_name) {}
48104

49105
Expected<json::Value> FifoFileIO::ReadJSON(std::chrono::milliseconds timeout) {
50106
// We use a pointer for this future, because otherwise its normal destructor
51107
// would wait for the getline to end, rendering the timeout useless.
52108
std::optional<std::string> line;
53109
std::future<void> *future =
54110
new std::future<void>(std::async(std::launch::async, [&]() {
55-
std::ifstream reader(m_fifo_file, std::ifstream::in);
56-
std::string buffer;
57-
std::getline(reader, buffer);
58-
if (!buffer.empty())
59-
line = buffer;
111+
rewind(m_fifo_file.m_file);
112+
/*
113+
* The types of messages passing through the fifo are:
114+
* {"kind": "pid", "pid": 9223372036854775807}
115+
* {"kind": "didAttach"}
116+
* {"kind": "error", "error": "error message"}
117+
*
118+
* 512 characters should be more than enough.
119+
*/
120+
constexpr size_t buffer_size = 512;
121+
char buffer[buffer_size];
122+
char *ptr = fgets(buffer, buffer_size, m_fifo_file.m_file);
123+
if (ptr == nullptr || *ptr == 0)
124+
return;
125+
size_t len = strlen(buffer);
126+
if (len <= 1)
127+
return;
128+
buffer[len - 1] = '\0'; // remove newline
129+
line = buffer;
60130
}));
61-
if (future->wait_for(timeout) == std::future_status::timeout || !line)
131+
132+
if (future->wait_for(timeout) == std::future_status::timeout)
62133
// Indeed this is a leak, but it's intentional. "future" obj destructor
63134
// will block on waiting for the worker thread to join. And the worker
64135
// thread might be stuck in blocking I/O. Intentionally leaking the obj
@@ -69,6 +140,11 @@ Expected<json::Value> FifoFileIO::ReadJSON(std::chrono::milliseconds timeout) {
69140
return createStringError(inconvertibleErrorCode(),
70141
"Timed out trying to get messages from the " +
71142
m_other_endpoint_name);
143+
if (!line) {
144+
return createStringError(inconvertibleErrorCode(),
145+
"Failed to get messages from the " +
146+
m_other_endpoint_name);
147+
}
72148
delete future;
73149
return json::parse(*line);
74150
}
@@ -78,8 +154,12 @@ Error FifoFileIO::SendJSON(const json::Value &json,
78154
bool done = false;
79155
std::future<void> *future =
80156
new std::future<void>(std::async(std::launch::async, [&]() {
81-
std::ofstream writer(m_fifo_file, std::ofstream::out);
82-
writer << JSONToString(json) << std::endl;
157+
rewind(m_fifo_file.m_file);
158+
auto payload = JSONToString(json);
159+
if (fputs(payload.c_str(), m_fifo_file.m_file) == EOF ||
160+
fputc('\n', m_fifo_file.m_file) == EOF) {
161+
return;
162+
}
83163
done = true;
84164
}));
85165
if (future->wait_for(timeout) == std::future_status::timeout || !done) {
@@ -98,4 +178,17 @@ Error FifoFileIO::SendJSON(const json::Value &json,
98178
return Error::success();
99179
}
100180

181+
Error FifoFileIO::WaitForPeer() {
182+
#ifdef _WIN32
183+
if (!::ConnectNamedPipe((HANDLE)_get_osfhandle(fileno(m_fifo_file.m_file)),
184+
NULL) &&
185+
GetLastError() != ERROR_PIPE_CONNECTED) {
186+
return createStringError(mapLastWindowsError(),
187+
"Failed to connect to the " +
188+
m_other_endpoint_name);
189+
}
190+
#endif
191+
return Error::success();
192+
}
193+
101194
} // namespace lldb_dap

lldb/tools/lldb-dap/FifoFiles.h

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,44 @@
1111

1212
#include "llvm/Support/Error.h"
1313
#include "llvm/Support/JSON.h"
14+
#include "llvm/Support/raw_ostream.h"
1415

1516
#include <chrono>
17+
#include <fstream>
1618

1719
namespace lldb_dap {
1820

1921
/// Struct that controls the life of a fifo file in the filesystem.
2022
///
2123
/// The file is destroyed when the destructor is invoked.
2224
struct FifoFile {
23-
FifoFile(llvm::StringRef path);
25+
FifoFile(FifoFile &&other);
26+
27+
FifoFile(const FifoFile &) = delete;
28+
FifoFile &operator=(const FifoFile &) = delete;
2429

2530
~FifoFile();
2631

32+
static llvm::Expected<FifoFile> create(llvm::StringRef path);
33+
FifoFile(llvm::StringRef path, FILE *f);
34+
2735
std::string m_path;
36+
FILE *m_file;
37+
38+
private:
39+
FifoFile(std::string path, FILE *f);
2840
};
2941

30-
/// Create a fifo file in the filesystem.
42+
/// Create and open a named pipe with a unique name.
3143
///
32-
/// \param[in] path
33-
/// The path for the fifo file.
44+
/// The arguments have identical meanings to those of
45+
/// llvm::sys::fs::createTemporaryFile.
3446
///
35-
/// \return
36-
/// A \a std::shared_ptr<FifoFile> if the file could be created, or an
37-
/// \a llvm::Error in case of failures.
38-
llvm::Expected<std::shared_ptr<FifoFile>> CreateFifoFile(llvm::StringRef path);
47+
/// Note that the resulting filename is further prepended by \c \\.\pipe\\LOCAL\
48+
/// on Windows and the native temp directory on other platforms.
49+
llvm::Error createUniqueNamedPipe(const llvm::Twine &prefix,
50+
llvm::StringRef suffix, int &result_fd,
51+
llvm::SmallVectorImpl<char> &result_path);
3952

4053
class FifoFileIO {
4154
public:
@@ -45,7 +58,7 @@ class FifoFileIO {
4558
/// \param[in] other_endpoint_name
4659
/// A human readable name for the other endpoint that will communicate
4760
/// using this file. This is used for error messages.
48-
FifoFileIO(llvm::StringRef fifo_file, llvm::StringRef other_endpoint_name);
61+
FifoFileIO(FifoFile &&fifo_file, llvm::StringRef other_endpoint_name);
4962

5063
/// Read the next JSON object from the underlying input fifo file.
5164
///
@@ -75,8 +88,10 @@ class FifoFileIO {
7588
const llvm::json::Value &json,
7689
std::chrono::milliseconds timeout = std::chrono::milliseconds(20000));
7790

91+
llvm::Error WaitForPeer();
92+
7893
private:
79-
std::string m_fifo_file;
94+
FifoFile m_fifo_file;
8095
std::string m_other_endpoint_name;
8196
};
8297

lldb/tools/lldb-dap/Handler/RequestHandler.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,22 @@ static llvm::Error RunInTerminal(DAP &dap,
104104
if (!comm_file_or_err)
105105
return comm_file_or_err.takeError();
106106
FifoFile &comm_file = *comm_file_or_err.get();
107+
std::string comm_filename = comm_file.m_path;
107108

108-
RunInTerminalDebugAdapterCommChannel comm_channel(comm_file.m_path);
109+
RunInTerminalDebugAdapterCommChannel comm_channel(comm_file);
109110

110111
lldb::pid_t debugger_pid = LLDB_INVALID_PROCESS_ID;
111112
#if !defined(_WIN32)
112113
debugger_pid = getpid();
113114
#endif
114115
llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest(
115-
launch_request, dap.debug_adapter_path, comm_file.m_path, debugger_pid);
116+
launch_request, dap.debug_adapter_path, comm_filename, debugger_pid);
116117
dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal",
117118
std::move(reverse_request));
118119

120+
auto err = comm_channel.WaitForLauncher();
121+
if (err)
122+
return err;
119123
if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid())
120124
attach_info.SetProcessID(*pid);
121125
else

0 commit comments

Comments
 (0)