Skip to content

Commit dc866c2

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 d6e0798 commit dc866c2

File tree

8 files changed

+193
-58
lines changed

8 files changed

+193
-58
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 & 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():
@@ -90,7 +89,6 @@ def test_runInTerminal(self):
9089
env = self.dap_server.request_evaluate("foo")["body"]["result"]
9190
self.assertIn("bar", env)
9291

93-
@skipIf(archs=no_match(["x86_64"]))
9492
def test_runInTerminalWithObjectEnv(self):
9593
if not self.isTestSupported():
9694
return
@@ -113,7 +111,6 @@ def test_runInTerminalWithObjectEnv(self):
113111
self.assertIn("FOO", request_envs)
114112
self.assertEqual("BAR", request_envs["FOO"])
115113

116-
@skipIfWindows
117114
@skipIf(archs=no_match(["x86_64"]))
118115
def test_runInTerminalInvalidTarget(self):
119116
if not self.isTestSupported():
@@ -132,7 +129,6 @@ def test_runInTerminalInvalidTarget(self):
132129
response["message"],
133130
)
134131

135-
@skipIfWindows
136132
@skipIf(archs=no_match(["x86_64"]))
137133
def test_missingArgInRunInTerminalLauncher(self):
138134
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: 101 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
#include "FifoFiles.h"
1010
#include "JSONUtils.h"
1111

12-
#if !defined(_WIN32)
12+
#include "llvm/Support/FileSystem.h"
13+
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) {}
33+
std::error_code EC;
2834

35+
FifoFile::FifoFile(StringRef path)
36+
: m_path(path), m_file(fopen(path.data(), "r+")) {
37+
if (m_file == nullptr) {
38+
EC = std::error_code(errno, std::generic_category());
39+
llvm::errs() << "Failed to open fifo file " << path << ": " << EC.message()
40+
<< "\n";
41+
std::terminate();
42+
}
43+
if (setvbuf(m_file, NULL, _IONBF, 0))
44+
llvm::errs() << "Error setting unbuffered mode on C FILE\n";
45+
}
46+
FifoFile::FifoFile(StringRef path, FILE *f) : m_path(path), m_file(f) {}
47+
FifoFile::FifoFile(FifoFile &&other)
48+
: m_path(other.m_path), m_file(other.m_file) {
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 createNamedPipe(const Twine &Prefix, StringRef Suffix,
62+
int &ResultFd,
63+
SmallVectorImpl<char> &ResultPath) {
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+
ResultPath);
73+
if (EC)
74+
return EC;
75+
ResultPath.push_back(0);
76+
const char *path = ResultPath.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 std::error_code(::GetLastError(), std::system_category());
83+
ResultFd = _open_osfhandle((intptr_t)h, _O_TEXT | _O_RDWR);
84+
if (ResultFd == -1)
85+
return std::error_code(::GetLastError(), std::system_category());
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(ResultPath, ResultFd, sys::fs::CD_OpenExisting,
90+
sys::fs::OF_None, 0600);
91+
if (EC)
92+
return EC;
4393
#endif
94+
ResultPath.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(
176+
std::error_code(GetLastError(), std::system_category()),
177+
"Failed to connect to the " + 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: 16 additions & 11 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,22 @@ 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(llvm::StringRef path, FILE *f);
28+
FifoFile(FifoFile &&other);
29+
30+
FifoFile(const FifoFile &) = delete;
31+
FifoFile &operator=(const FifoFile &) = delete;
2432

2533
~FifoFile();
2634

2735
std::string m_path;
36+
FILE *m_file;
2837
};
2938

30-
/// Create a fifo file in the filesystem.
31-
///
32-
/// \param[in] path
33-
/// The path for the fifo file.
34-
///
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);
39+
std::error_code createNamedPipe(const llvm::Twine &Prefix,
40+
llvm::StringRef Suffix, int &ResultFd,
41+
llvm::SmallVectorImpl<char> &ResultPath);
3942

4043
class FifoFileIO {
4144
public:
@@ -45,7 +48,7 @@ class FifoFileIO {
4548
/// \param[in] other_endpoint_name
4649
/// A human readable name for the other endpoint that will communicate
4750
/// using this file. This is used for error messages.
48-
FifoFileIO(llvm::StringRef fifo_file, llvm::StringRef other_endpoint_name);
51+
FifoFileIO(FifoFile &&fifo_file, llvm::StringRef other_endpoint_name);
4952

5053
/// Read the next JSON object from the underlying input fifo file.
5154
///
@@ -75,8 +78,10 @@ class FifoFileIO {
7578
const llvm::json::Value &json,
7679
std::chrono::milliseconds timeout = std::chrono::milliseconds(20000));
7780

81+
llvm::Error WaitForPeer();
82+
7883
private:
79-
std::string m_fifo_file;
84+
FifoFile m_fifo_file;
8085
std::string m_other_endpoint_name;
8186
};
8287

lldb/tools/lldb-dap/RunInTerminal.cpp

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ static Error ToError(const RunInTerminalMessage &message) {
9797

9898
RunInTerminalLauncherCommChannel::RunInTerminalLauncherCommChannel(
9999
StringRef comm_file)
100-
: m_io(comm_file, "debug adaptor") {}
100+
: m_io(FifoFile(comm_file), "debug adaptor") {}
101101

102102
Error RunInTerminalLauncherCommChannel::WaitUntilDebugAdaptorAttaches(
103103
std::chrono::milliseconds timeout) {
@@ -111,8 +111,10 @@ Error RunInTerminalLauncherCommChannel::WaitUntilDebugAdaptorAttaches(
111111
return message.takeError();
112112
}
113113

114-
Error RunInTerminalLauncherCommChannel::NotifyPid() {
115-
return m_io.SendJSON(RunInTerminalMessagePid(getpid()).ToJSON());
114+
Error RunInTerminalLauncherCommChannel::NotifyPid(lldb::pid_t pid) {
115+
if (pid == 0)
116+
pid = getpid();
117+
return m_io.SendJSON(RunInTerminalMessagePid(pid).ToJSON());
116118
}
117119

118120
void RunInTerminalLauncherCommChannel::NotifyError(StringRef error) {
@@ -122,8 +124,12 @@ void RunInTerminalLauncherCommChannel::NotifyError(StringRef error) {
122124
}
123125

124126
RunInTerminalDebugAdapterCommChannel::RunInTerminalDebugAdapterCommChannel(
125-
StringRef comm_file)
126-
: m_io(comm_file, "runInTerminal launcher") {}
127+
FifoFile &comm_file)
128+
: m_io(std::move(comm_file), "runInTerminal launcher") {}
129+
130+
Error RunInTerminalDebugAdapterCommChannel::WaitForLauncher() {
131+
return m_io.WaitForPeer();
132+
}
127133

128134
// Can't use \a std::future<llvm::Error> because it doesn't compile on Windows
129135
std::future<lldb::SBError>
@@ -158,13 +164,25 @@ std::string RunInTerminalDebugAdapterCommChannel::GetLauncherError() {
158164
}
159165

160166
Expected<std::shared_ptr<FifoFile>> CreateRunInTerminalCommFile() {
167+
int comm_fd;
161168
SmallString<256> comm_file;
162-
if (std::error_code EC = sys::fs::getPotentiallyUniqueTempFileName(
163-
"lldb-dap-run-in-terminal-comm", "", comm_file))
169+
if (std::error_code EC = createNamedPipe("lldb-dap-run-in-terminal-comm", "",
170+
comm_fd, comm_file))
164171
return createStringError(EC, "Error making unique file name for "
165172
"runInTerminal communication files");
166-
167-
return CreateFifoFile(comm_file.str());
173+
FILE *cf = fdopen(comm_fd, "r+");
174+
if (setvbuf(cf, NULL, _IONBF, 0))
175+
return createStringError(std::error_code(errno, std::generic_category()),
176+
"Error setting unbuffered mode on C FILE");
177+
// There is no portable way to conjure an ofstream from HANDLE, so use FILE *
178+
// llvm::raw_fd_stream does not support getline() and there is no
179+
// llvm::buffer_istream
180+
181+
if (cf == NULL)
182+
return createStringError(std::error_code(errno, std::generic_category()),
183+
"Error converting file descriptor to C FILE for "
184+
"runInTerminal comm-file");
185+
return std::make_shared<FifoFile>(comm_file, cf);
168186
}
169187

170188
} // namespace lldb_dap

lldb/tools/lldb-dap/RunInTerminal.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class RunInTerminalLauncherCommChannel {
8787
/// \return
8888
/// An \a llvm::Error object in case of errors or if this operation times
8989
/// out.
90-
llvm::Error NotifyPid();
90+
llvm::Error NotifyPid(lldb::pid_t pid = 0);
9191

9292
/// Notify the debug adaptor that there's been an error.
9393
void NotifyError(llvm::StringRef error);
@@ -98,7 +98,7 @@ class RunInTerminalLauncherCommChannel {
9898

9999
class RunInTerminalDebugAdapterCommChannel {
100100
public:
101-
RunInTerminalDebugAdapterCommChannel(llvm::StringRef comm_file);
101+
RunInTerminalDebugAdapterCommChannel(FifoFile &comm_file);
102102

103103
/// Notify the runInTerminal launcher that it was attached.
104104
///
@@ -118,6 +118,8 @@ class RunInTerminalDebugAdapterCommChannel {
118118
/// default error message if a certain timeout if reached.
119119
std::string GetLauncherError();
120120

121+
llvm::Error WaitForLauncher();
122+
121123
private:
122124
FifoFileIO m_io;
123125
};

0 commit comments

Comments
 (0)