Skip to content

Commit c656de0

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 93b90a5 commit c656de0

File tree

8 files changed

+201
-63
lines changed

8 files changed

+201
-63
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

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

Lines changed: 0 additions & 3 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():
@@ -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():
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#include <stdio.h>
22
#include <stdlib.h>
3-
#include <unistd.h>
3+
4+
#include <threads.h>
5+
#include <time.h>
46

57
int main(int argc, char *argv[]) {
68
const char *foo = getenv("FOO");
79
for (int counter = 1;; counter++) {
8-
sleep(1); // breakpoint
10+
thrd_sleep(&(struct timespec){.tv_sec = 1}, NULL); // breakpoint
911
}
1012
return 0;
1113
}

lldb/tools/lldb-dap/FifoFiles.cpp

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,14 @@
88

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

12-
#if !defined(_WIN32)
14+
#if defined(_WIN32)
15+
#include <Windows.h>
16+
#include <fcntl.h>
17+
#include <io.h>
18+
#else
1319
#include <sys/stat.h>
1420
#include <sys/types.h>
1521
#include <unistd.h>
@@ -24,41 +30,95 @@ using namespace llvm;
2430

2531
namespace lldb_dap {
2632

27-
FifoFile::FifoFile(StringRef path) : m_path(path) {}
28-
33+
FifoFile::FifoFile(StringRef path)
34+
: m_path(path), m_file(fopen(path.data(), "r+")) {
35+
std::error_code EC;
36+
if (m_file == nullptr) {
37+
EC = std::error_code(errno, std::generic_category());
38+
llvm::errs() << "Failed to open fifo file " << path << ": " << EC.message()
39+
<< "\n";
40+
std::terminate();
41+
}
42+
if (setvbuf(m_file, NULL, _IONBF, 0))
43+
llvm::errs() << "Error setting unbuffered mode on C FILE\n";
44+
}
45+
FifoFile::FifoFile(StringRef path, FILE *f) : m_path(path), m_file(f) {}
46+
FifoFile::FifoFile(FifoFile &&other)
47+
: m_path(other.m_path), m_file(other.m_file) {
48+
other.m_path.clear();
49+
other.m_file = nullptr;
50+
}
2951
FifoFile::~FifoFile() {
52+
if (m_file)
53+
fclose(m_file);
3054
#if !defined(_WIN32)
55+
// Unreferenced named pipes are deleted automatically on Win32
3156
unlink(m_path.c_str());
3257
#endif
3358
}
3459

35-
Expected<std::shared_ptr<FifoFile>> CreateFifoFile(StringRef path) {
36-
#if defined(_WIN32)
37-
return createStringError(inconvertibleErrorCode(), "Unimplemented");
60+
// This probably belongs to llvm::sys::fs as another FSEntity type
61+
std::error_code createUniqueNamedPipe(const Twine &prefix, StringRef suffix,
62+
int &result_fd,
63+
SmallVectorImpl<char> &result_path) {
64+
const char *middle = suffix.empty() ? "-%%%%%%" : "-%%%%%%.";
65+
auto EC = sys::fs::getPotentiallyUniqueFileName(
66+
#ifdef _WIN32
67+
"\\\\.\\pipe\\LOCAL\\"
68+
#else
69+
"/tmp/"
70+
#endif
71+
+ prefix + middle + suffix,
72+
result_path);
73+
if (EC)
74+
return EC;
75+
result_path.push_back(0);
76+
const char *path = result_path.data();
77+
#ifdef _WIN32
78+
HANDLE h = ::CreateNamedPipeA(
79+
path, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
80+
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 0, NULL);
81+
if (h == INVALID_HANDLE_VALUE)
82+
return llvm::mapLastWindowsError();
83+
result_fd = _open_osfhandle((intptr_t)h, _O_TEXT | _O_RDWR);
84+
if (result_fd == -1)
85+
return llvm::mapLastWindowsError();
3886
#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);
87+
if (mkfifo(path, 0600) == -1)
88+
return std::error_code(errno, std::generic_category());
89+
EC = openFileForWrite(result_path, result_fd, sys::fs::CD_OpenExisting,
90+
sys::fs::OF_None, 0600);
91+
if (EC)
92+
return EC;
4393
#endif
94+
result_path.pop_back();
95+
return std::error_code();
4496
}
4597

46-
FifoFileIO::FifoFileIO(StringRef fifo_file, StringRef other_endpoint_name)
47-
: m_fifo_file(fifo_file), m_other_endpoint_name(other_endpoint_name) {}
98+
FifoFileIO::FifoFileIO(FifoFile &&fifo_file, StringRef other_endpoint_name)
99+
: m_fifo_file(std::move(fifo_file)),
100+
m_other_endpoint_name(other_endpoint_name) {}
48101

49102
Expected<json::Value> FifoFileIO::ReadJSON(std::chrono::milliseconds timeout) {
50103
// We use a pointer for this future, because otherwise its normal destructor
51104
// would wait for the getline to end, rendering the timeout useless.
52105
std::optional<std::string> line;
53106
std::future<void> *future =
54107
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;
108+
rewind(m_fifo_file.m_file);
109+
constexpr size_t buffer_size = 2048;
110+
char buffer[buffer_size];
111+
char *ptr = fgets(buffer, buffer_size, m_fifo_file.m_file);
112+
if (ptr == nullptr || *ptr == 0)
113+
return;
114+
size_t len = strlen(buffer);
115+
if (len <= 1)
116+
return;
117+
buffer[len - 1] = '\0'; // remove newline
118+
line = buffer;
60119
}));
61-
if (future->wait_for(timeout) == std::future_status::timeout || !line)
120+
121+
if (future->wait_for(timeout) == std::future_status::timeout)
62122
// Indeed this is a leak, but it's intentional. "future" obj destructor
63123
// will block on waiting for the worker thread to join. And the worker
64124
// thread might be stuck in blocking I/O. Intentionally leaking the obj
@@ -69,6 +129,11 @@ Expected<json::Value> FifoFileIO::ReadJSON(std::chrono::milliseconds timeout) {
69129
return createStringError(inconvertibleErrorCode(),
70130
"Timed out trying to get messages from the " +
71131
m_other_endpoint_name);
132+
if (!line) {
133+
return createStringError(inconvertibleErrorCode(),
134+
"Failed to get messages from the " +
135+
m_other_endpoint_name);
136+
}
72137
delete future;
73138
return json::parse(*line);
74139
}
@@ -78,8 +143,12 @@ Error FifoFileIO::SendJSON(const json::Value &json,
78143
bool done = false;
79144
std::future<void> *future =
80145
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;
146+
rewind(m_fifo_file.m_file);
147+
auto payload = JSONToString(json);
148+
if (fputs(payload.c_str(), m_fifo_file.m_file) == EOF ||
149+
fputc('\n', m_fifo_file.m_file) == EOF) {
150+
return;
151+
}
83152
done = true;
84153
}));
85154
if (future->wait_for(timeout) == std::future_status::timeout || !done) {
@@ -98,4 +167,17 @@ Error FifoFileIO::SendJSON(const json::Value &json,
98167
return Error::success();
99168
}
100169

170+
Error FifoFileIO::WaitForPeer() {
171+
#ifdef _WIN32
172+
if (!::ConnectNamedPipe((HANDLE)_get_osfhandle(fileno(m_fifo_file.m_file)),
173+
NULL) &&
174+
GetLastError() != ERROR_PIPE_CONNECTED) {
175+
return createStringError(llvm::mapLastWindowsError(),
176+
"Failed to connect to the " +
177+
m_other_endpoint_name);
178+
}
179+
#endif
180+
return Error::success();
181+
}
182+
101183
} // namespace lldb_dap

lldb/tools/lldb-dap/FifoFiles.h

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
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

@@ -21,21 +23,28 @@ namespace lldb_dap {
2123
/// The file is destroyed when the destructor is invoked.
2224
struct FifoFile {
2325
FifoFile(llvm::StringRef path);
26+
FifoFile(llvm::StringRef path, FILE *f);
27+
FifoFile(FifoFile &&other);
28+
29+
FifoFile(const FifoFile &) = delete;
30+
FifoFile &operator=(const FifoFile &) = delete;
2431

2532
~FifoFile();
2633

2734
std::string m_path;
35+
FILE *m_file;
2836
};
2937

30-
/// Create a fifo file in the filesystem.
38+
/// Create and open a named pipe with a unique name.
3139
///
32-
/// \param[in] path
33-
/// The path for the fifo file.
40+
/// The arguments have identical meanings to those of
41+
/// llvm::sys::fs::createTemporaryFile.
3442
///
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);
43+
/// Note that the resulting filename is further prepended by \c \\.\pipe\\LOCAL\
44+
/// on Windows and \c /tmp on other platforms.
45+
std::error_code createUniqueNamedPipe(const llvm::Twine &prefix,
46+
llvm::StringRef suffix, int &result_fd,
47+
llvm::SmallVectorImpl<char> &result_path);
3948

4049
class FifoFileIO {
4150
public:
@@ -45,7 +54,7 @@ class FifoFileIO {
4554
/// \param[in] other_endpoint_name
4655
/// A human readable name for the other endpoint that will communicate
4756
/// using this file. This is used for error messages.
48-
FifoFileIO(llvm::StringRef fifo_file, llvm::StringRef other_endpoint_name);
57+
FifoFileIO(FifoFile &&fifo_file, llvm::StringRef other_endpoint_name);
4958

5059
/// Read the next JSON object from the underlying input fifo file.
5160
///
@@ -75,8 +84,10 @@ class FifoFileIO {
7584
const llvm::json::Value &json,
7685
std::chrono::milliseconds timeout = std::chrono::milliseconds(20000));
7786

87+
llvm::Error WaitForPeer();
88+
7889
private:
79-
std::string m_fifo_file;
90+
FifoFile m_fifo_file;
8091
std::string m_other_endpoint_name;
8192
};
8293

lldb/tools/lldb-dap/RunInTerminal.cpp

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,6 @@
99
#include "RunInTerminal.h"
1010
#include "JSONUtils.h"
1111

12-
#if !defined(_WIN32)
13-
#include <sys/stat.h>
14-
#include <sys/types.h>
15-
#include <unistd.h>
16-
#endif
17-
1812
#include <chrono>
1913
#include <future>
2014

@@ -97,7 +91,7 @@ static Error ToError(const RunInTerminalMessage &message) {
9791

9892
RunInTerminalLauncherCommChannel::RunInTerminalLauncherCommChannel(
9993
StringRef comm_file)
100-
: m_io(comm_file, "debug adaptor") {}
94+
: m_io(FifoFile(comm_file), "debug adaptor") {}
10195

10296
Error RunInTerminalLauncherCommChannel::WaitUntilDebugAdaptorAttaches(
10397
std::chrono::milliseconds timeout) {
@@ -111,8 +105,8 @@ Error RunInTerminalLauncherCommChannel::WaitUntilDebugAdaptorAttaches(
111105
return message.takeError();
112106
}
113107

114-
Error RunInTerminalLauncherCommChannel::NotifyPid() {
115-
return m_io.SendJSON(RunInTerminalMessagePid(getpid()).ToJSON());
108+
Error RunInTerminalLauncherCommChannel::NotifyPid(lldb::pid_t pid) {
109+
return m_io.SendJSON(RunInTerminalMessagePid(pid).ToJSON());
116110
}
117111

118112
void RunInTerminalLauncherCommChannel::NotifyError(StringRef error) {
@@ -122,8 +116,12 @@ void RunInTerminalLauncherCommChannel::NotifyError(StringRef error) {
122116
}
123117

124118
RunInTerminalDebugAdapterCommChannel::RunInTerminalDebugAdapterCommChannel(
125-
StringRef comm_file)
126-
: m_io(comm_file, "runInTerminal launcher") {}
119+
FifoFile &comm_file)
120+
: m_io(std::move(comm_file), "runInTerminal launcher") {}
121+
122+
Error RunInTerminalDebugAdapterCommChannel::WaitForLauncher() {
123+
return m_io.WaitForPeer();
124+
}
127125

128126
// Can't use \a std::future<llvm::Error> because it doesn't compile on Windows
129127
std::future<lldb::SBError>
@@ -158,13 +156,25 @@ std::string RunInTerminalDebugAdapterCommChannel::GetLauncherError() {
158156
}
159157

160158
Expected<std::shared_ptr<FifoFile>> CreateRunInTerminalCommFile() {
159+
int comm_fd;
161160
SmallString<256> comm_file;
162-
if (std::error_code EC = sys::fs::getPotentiallyUniqueTempFileName(
163-
"lldb-dap-run-in-terminal-comm", "", comm_file))
161+
if (std::error_code EC = createUniqueNamedPipe(
162+
"lldb-dap-run-in-terminal-comm", "", comm_fd, comm_file))
164163
return createStringError(EC, "Error making unique file name for "
165164
"runInTerminal communication files");
166-
167-
return CreateFifoFile(comm_file.str());
165+
FILE *cf = fdopen(comm_fd, "r+");
166+
if (setvbuf(cf, NULL, _IONBF, 0))
167+
return createStringError(std::error_code(errno, std::generic_category()),
168+
"Error setting unbuffered mode on C FILE");
169+
// There is no portable way to conjure an ofstream from HANDLE, so use FILE *
170+
// llvm::raw_fd_stream does not support getline() and there is no
171+
// llvm::buffer_istream
172+
173+
if (cf == NULL)
174+
return createStringError(std::error_code(errno, std::generic_category()),
175+
"Error converting file descriptor to C FILE for "
176+
"runInTerminal comm-file");
177+
return std::make_shared<FifoFile>(comm_file, cf);
168178
}
169179

170180
} // namespace lldb_dap

0 commit comments

Comments
 (0)