mgorny updated this revision to Diff 380731.
mgorny retitled this revision from "[lldb] Add serial:// protocol for
connecting to serial port [WIP/PoC]" to "[lldb] Add serial:// protocol for
connecting to serial port".
mgorny added a comment.
Move `serial://` URL parsing to `SerialPort` class. Improve error handling.
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D111355/new/
https://reviews.llvm.org/D111355
Files:
lldb/include/lldb/Host/File.h
lldb/include/lldb/Host/posix/ConnectionFileDescriptorPosix.h
lldb/source/Host/common/File.cpp
lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
lldb/test/API/functionalities/gdb_remote_client/TestPty.py
lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
Index: lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
===================================================================
--- lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
+++ lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
@@ -427,7 +427,7 @@
return libc.ptsname(self._master.fileno()).decode()
def get_connect_url(self):
- return "file://" + self.get_connect_address()
+ return "serial://" + self.get_connect_address()
def close_server(self):
self._slave.close()
Index: lldb/test/API/functionalities/gdb_remote_client/TestPty.py
===================================================================
--- lldb/test/API/functionalities/gdb_remote_client/TestPty.py
+++ lldb/test/API/functionalities/gdb_remote_client/TestPty.py
@@ -9,6 +9,38 @@
mydir = TestBase.compute_mydir(__file__)
server_socket_class = PtyServerSocket
+ def get_term_attrs(self):
+ import termios
+ return termios.tcgetattr(self.server._socket._slave)
+
+ def setUp(self):
+ super().setUp()
+ self.orig_attr = self.get_term_attrs()
+
+ def assert_raw_mode(self, current_attr):
+ import termios
+ self.assertEqual(current_attr[0] & (termios.BRKINT |
+ termios.PARMRK |
+ termios.ISTRIP | termios.INLCR |
+ termios.IGNCR | termios.ICRNL |
+ termios.IXON),
+ 0)
+ self.assertEqual(current_attr[1] & termios.OPOST, 0)
+ self.assertEqual(current_attr[2] & termios.CSIZE, termios.CS8)
+ self.assertEqual(current_attr[3] & (termios.ICANON | termios.ECHO |
+ termios.ISIG | termios.IEXTEN),
+ 0)
+ self.assertEqual(current_attr[6][termios.VMIN], 1)
+ self.assertEqual(current_attr[6][termios.VTIME], 0)
+
+ def get_parity_flags(self, attr):
+ import termios
+ return attr[2] & (termios.PARENB | termios.PARODD)
+
+ def get_stop_bit_flags(self, attr):
+ import termios
+ return attr[2] & termios.CSTOPB
+
def test_process_connect_sync(self):
"""Test the process connect command in synchronous mode"""
try:
@@ -17,8 +49,20 @@
substrs=['Platform: remote-gdb-server', 'Connected: no'])
self.expect("process connect " + self.server.get_connect_url(),
substrs=['Process', 'stopped'])
+
+ current_attr = self.get_term_attrs()
+ # serial:// should set raw mode
+ self.assert_raw_mode(current_attr)
+ # other parameters should be unmodified
+ self.assertEqual(current_attr[4:6], self.orig_attr[4:6])
+ self.assertEqual(self.get_parity_flags(current_attr),
+ self.get_parity_flags(self.orig_attr))
+ self.assertEqual(self.get_stop_bit_flags(current_attr),
+ self.get_stop_bit_flags(self.orig_attr))
finally:
self.dbg.GetSelectedTarget().GetProcess().Kill()
+ # original mode should be restored on exit
+ self.assertEqual(self.get_term_attrs(), self.orig_attr)
def test_process_connect_async(self):
"""Test the process connect command in asynchronous mode"""
@@ -31,7 +75,63 @@
substrs=['Process', 'stopped'])
lldbutil.expect_state_changes(self, self.dbg.GetListener(),
self.process(), [lldb.eStateStopped])
+
+ current_attr = self.get_term_attrs()
+ # serial:// should set raw mode
+ self.assert_raw_mode(current_attr)
+ # other parameters should be unmodified
+ self.assertEqual(current_attr[4:6], self.orig_attr[4:6])
+ self.assertEqual(self.get_parity_flags(current_attr),
+ self.get_parity_flags(self.orig_attr))
+ self.assertEqual(self.get_stop_bit_flags(current_attr),
+ self.get_stop_bit_flags(self.orig_attr))
finally:
self.dbg.GetSelectedTarget().GetProcess().Kill()
lldbutil.expect_state_changes(self, self.dbg.GetListener(),
self.process(), [lldb.eStateExited])
+ # original mode should be restored on exit
+ self.assertEqual(self.get_term_attrs(), self.orig_attr)
+
+ def test_connect_via_file(self):
+ """Test connecting via the legacy file:// URL"""
+ import termios
+ try:
+ self.expect("platform select remote-gdb-server",
+ substrs=['Platform: remote-gdb-server', 'Connected: no'])
+ self.expect("process connect file://" +
+ self.server.get_connect_address(),
+ substrs=['Process', 'stopped'])
+
+ # file:// sets baud rate and some raw-related flags
+ current_attr = self.get_term_attrs()
+ self.assertEqual(current_attr[3] & (termios.ICANON | termios.ECHO |
+ termios.ECHOE | termios.ISIG),
+ 0)
+ self.assertEqual(current_attr[4], termios.B115200)
+ self.assertEqual(current_attr[5], termios.B115200)
+ self.assertEqual(current_attr[6][termios.VMIN], 1)
+ self.assertEqual(current_attr[6][termios.VTIME], 0)
+ finally:
+ self.dbg.GetSelectedTarget().GetProcess().Kill()
+
+ def test_process_connect_params(self):
+ """Test serial:// URL with parameters"""
+ import termios
+ try:
+ self.expect("platform select remote-gdb-server",
+ substrs=['Platform: remote-gdb-server', 'Connected: no'])
+ self.expect("process connect " + self.server.get_connect_url() +
+ "?baud=115200&stop-bits=2",
+ substrs=['Process', 'stopped'])
+
+ current_attr = self.get_term_attrs()
+ self.assert_raw_mode(current_attr)
+ self.assertEqual(current_attr[4:6], 2 * [termios.B115200])
+ self.assertEqual(self.get_parity_flags(current_attr),
+ self.get_parity_flags(self.orig_attr))
+ self.assertEqual(self.get_stop_bit_flags(current_attr),
+ termios.CSTOPB)
+ finally:
+ self.dbg.GetSelectedTarget().GetProcess().Kill()
+ # original mode should be restored on exit
+ self.assertEqual(self.get_term_attrs(), self.orig_attr)
Index: lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
===================================================================
--- lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
+++ lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
@@ -156,6 +156,7 @@
#if LLDB_ENABLE_POSIX
.Case("fd", &ConnectionFileDescriptor::ConnectFD)
.Case("file", &ConnectionFileDescriptor::ConnectFile)
+ .Case("serial", &ConnectionFileDescriptor::ConnectSerialPort)
#endif
.Default(nullptr);
@@ -735,6 +736,56 @@
llvm_unreachable("this function should be only called w/ LLDB_ENABLE_POSIX");
}
+ConnectionStatus
+ConnectionFileDescriptor::ConnectSerialPort(llvm::StringRef s,
+ Status *error_ptr) {
+#if LLDB_ENABLE_POSIX
+ llvm::StringRef path, qs;
+ // serial:///PATH?k1=v1&k2=v2...
+ std::tie(path, qs) = s.split('?');
+
+ llvm::Expected<SerialPort::Options> serial_options =
+ SerialPort::OptionsFromURL(qs);
+ if (!serial_options) {
+ if (error_ptr)
+ *error_ptr = serial_options.takeError();
+ else
+ llvm::consumeError(serial_options.takeError());
+ return eConnectionStatusError;
+ }
+
+ int fd = llvm::sys::RetryAfterSignal(-1, ::open, path.str().c_str(), O_RDWR);
+ if (fd == -1) {
+ if (error_ptr)
+ error_ptr->SetErrorToErrno();
+ return eConnectionStatusError;
+ }
+
+ int flags = ::fcntl(fd, F_GETFL, 0);
+ if (flags >= 0) {
+ if ((flags & O_NONBLOCK) == 0) {
+ flags |= O_NONBLOCK;
+ ::fcntl(fd, F_SETFL, flags);
+ }
+ }
+
+ llvm::Error error = llvm::Error::success();
+ m_io_sp = std::make_shared<SerialPort>(fd, File::eOpenOptionReadWrite,
+ serial_options.get(), true, error);
+ if (error) {
+ m_io_sp.reset();
+ if (error_ptr)
+ *error_ptr = std::move(error);
+ else
+ llvm::consumeError(std::move(error));
+ return eConnectionStatusError;
+ }
+
+ return eConnectionStatusSuccess;
+#endif // LLDB_ENABLE_POSIX
+ llvm_unreachable("this function should be only called w/ LLDB_ENABLE_POSIX");
+}
+
uint16_t
ConnectionFileDescriptor::GetListeningPort(const Timeout<std::micro> &timeout) {
auto Result = m_port_predicate.WaitForValueNotEqualTo(0, timeout);
Index: lldb/source/Host/common/File.cpp
===================================================================
--- lldb/source/Host/common/File.cpp
+++ lldb/source/Host/common/File.cpp
@@ -769,5 +769,79 @@
return mode;
}
+llvm::Expected<SerialPort::Options>
+SerialPort::OptionsFromURL(llvm::StringRef urlqs) {
+ SerialPort::Options serial_options;
+ for (llvm::StringRef x : llvm::Split(urlqs, '&')) {
+ if (x.consume_front("baud=")) {
+ unsigned int baud_rate;
+ if (!llvm::to_integer(x, baud_rate, 10))
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "Invalid baud rate: %s",
+ x.str().c_str());
+ serial_options.BaudRate = baud_rate;
+ } else if (x.consume_front("parity=")) {
+ serial_options.Parity =
+ llvm::StringSwitch<llvm::Optional<Terminal::Parity>>(x)
+ .Case("no", Terminal::Parity::No)
+ .Case("even", Terminal::Parity::Even)
+ .Case("odd", Terminal::Parity::Odd)
+ .Case("mark", Terminal::Parity::Mark)
+ .Case("space", Terminal::Parity::Space)
+ .Default(llvm::None);
+ if (!serial_options.Parity)
+ return llvm::createStringError(
+ llvm::inconvertibleErrorCode(),
+ "Invalid parity (must be no, even, odd, mark or space): %s",
+ x.str().c_str());
+ } else if (x.consume_front("stop-bits=")) {
+ unsigned int stop_bits;
+ if (!llvm::to_integer(x, stop_bits, 10) ||
+ (stop_bits != 1 && stop_bits != 2))
+ return llvm::createStringError(
+ llvm::inconvertibleErrorCode(),
+ "Invalid stop bit number (must be 1 or 2): %s", x.str().c_str());
+ serial_options.StopBits = stop_bits;
+ } else
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "Unknown parameter: %s", x.str().c_str());
+ }
+ return serial_options;
+}
+
+SerialPort::SerialPort(int fd, OpenOptions options,
+ SerialPort::Options serial_options,
+ bool transfer_ownership, llvm::Error &error)
+ : NativeFile(fd, options, transfer_ownership), m_state(fd) {
+ llvm::ErrorAsOutParameter EAO(&error);
+ if (!GetIsInteractive()) {
+ error = llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "the specified file is not a teletype");
+ return;
+ }
+
+ Terminal term{fd};
+ if ((error = term.SetRaw()))
+ return;
+ if (serial_options.BaudRate) {
+ if ((error = term.SetBaudRate(serial_options.BaudRate.getValue())))
+ return;
+ }
+ if (serial_options.Parity) {
+ if ((error = term.SetParity(serial_options.Parity.getValue())))
+ return;
+ }
+ if (serial_options.StopBits) {
+ if ((error = term.SetStopBits(serial_options.StopBits.getValue())))
+ return;
+ }
+}
+
+Status SerialPort::Close() {
+ m_state.Restore();
+ return NativeFile::Close();
+}
+
char File::ID = 0;
char NativeFile::ID = 0;
+char SerialPort::ID = 0;
Index: lldb/include/lldb/Host/posix/ConnectionFileDescriptorPosix.h
===================================================================
--- lldb/include/lldb/Host/posix/ConnectionFileDescriptorPosix.h
+++ lldb/include/lldb/Host/posix/ConnectionFileDescriptorPosix.h
@@ -88,6 +88,9 @@
lldb::ConnectionStatus ConnectFile(llvm::StringRef args, Status *error_ptr);
+ lldb::ConnectionStatus ConnectSerialPort(llvm::StringRef args,
+ Status *error_ptr);
+
lldb::IOObjectSP m_io_sp;
Predicate<uint16_t>
Index: lldb/include/lldb/Host/File.h
===================================================================
--- lldb/include/lldb/Host/File.h
+++ lldb/include/lldb/Host/File.h
@@ -10,6 +10,7 @@
#define LLDB_HOST_FILE_H
#include "lldb/Host/PosixApi.h"
+#include "lldb/Host/Terminal.h"
#include "lldb/Utility/IOObject.h"
#include "lldb/Utility/Status.h"
#include "lldb/lldb-private.h"
@@ -433,6 +434,40 @@
const NativeFile &operator=(const NativeFile &) = delete;
};
+class SerialPort : public NativeFile {
+public:
+ struct Options {
+ llvm::Optional<unsigned int> BaudRate = llvm::None;
+ llvm::Optional<Terminal::Parity> Parity = llvm::None;
+ llvm::Optional<unsigned int> StopBits = llvm::None;
+ };
+
+ // Obtain Options corresponding to the passed URL query string
+ // (i.e. the part after '?').
+ static llvm::Expected<Options> OptionsFromURL(llvm::StringRef urlqs);
+
+ SerialPort(int fd, OpenOptions options, Options serial_options,
+ bool transfer_ownership, llvm::Error &error);
+
+ bool IsValid() const override {
+ return NativeFile::IsValid() && m_is_interactive == eLazyBoolYes;
+ }
+
+ Status Close() override;
+
+ static char ID;
+ virtual bool isA(const void *classID) const override {
+ return classID == &ID || File::isA(classID);
+ }
+ static bool classof(const File *file) { return file->isA(&ID); }
+
+private:
+ SerialPort(const SerialPort &) = delete;
+ const SerialPort &operator=(const SerialPort &) = delete;
+
+ TerminalState m_state;
+};
+
} // namespace lldb_private
#endif // LLDB_HOST_FILE_H
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits