Serial port support in LLDB

Hi, everyone.

I'm working on improving LLDB's feature parity with GDB. As part of
this, I'm working on bettering LLDB's serial port support. Since serial
ports are not that common these days, I've been asked to explain a bit
what I'd like to do.

At this point, LLDB (client) has minimal support for serial port
debugging. You can use a command like:

    process connect file:///dev/ttyS0

to connect via the GDB Remote Protocol over a serial port. However,
the client hardcodes serial transmission parameters (I'll explain
below). I haven't been able to find an option to bind lldb-server to a
serial port.

I'd like to fix the both limitations, i.e. make it possible to configure
serial port parameters and add support for serial port in lldb-server.

The RS-232 standard is quite open ended, so I'm going to focus on 8250-
compatible serial port with DB-9 connector below (i.e. the kind found
in home PCs). However, I'm going to skip the gory details and just
focus on the high-level problem.

The exact protocol used to transmit data over the serial port is
configurable to some degree. However, there is no support for
autoconfiguration, so both ends have to be configured the same.
The synchronization support is also minimal.

The important hardware parameters that can be configured are:

- baud rate, i.e. data transmission speed that implies the sampling
  rate. The higher the baud rate, the shorter individual bits are
  in the transmitted signal. If baud rate is missynced, then
  the receiver will get wrong bit sequences.

- number of data bits (5-8) in the frame, lower values meaning that
  the characters sent are being truncated. For binary data transfers,
  8 data bits must be used.

- parity used to verify frame correctness. The parity bit is optional,
  and can be configured to use odd or even parity. Additionally, Linux
  supports sending constant 0 or 1 as parity bit.

- number of stop bits (1 or 1.5/2) in the frame. The use of more than
  one stop bit is apparently a relict that was supposed to give
  the receiver more time for processing. I think this one isn't
  strictly necessary nowadays.

- flow control (none, software, hardware). This is basically used by
  the receiver to inform the sender that it's got its buffer full
  and the sender must stop transmitting. Software flow control used
  in-band signaling, so it's not suitable for binary protocols.
  Hardware flow control uses control lines.

Of course, there is more to serial ports than just that but for LLDB's
purposes, this should be sufficient.

The POSIX and win32 API for serial ports are quite similar in design.
In the POSIX API, you have to open a character device corresponding to
the serial port, while in win32 API a special path \\.\COMn. In both
cases, reads and writes effect the transmission. Both systems also have
a dedicated API to configure the serial transmission parameters
(ioctl/termios on POSIX [1], DCB on win32 [2]). Note that I haven't
tried the win32 API, just looked it up.

The POSIX serial ports are a teletype (tty) devices just like virtual
consoles used by terminal emulators. This makes it easy to use a serial
port as a remote terminal for other system. This also adds a bunch of
configuration options related to input/output processing and special
behavior. When a serial port is going to be used for non-console
purposes, these flags have to be disabled (i.e. the tty is set to 'raw'
mode).

The rough idea is that after opening the serial port, we need to set its
parameters to match the other end. For this to work, I need to replace
LLDB's hardwired parameters with some way of specifying this. I think
the cleanest way of doing this (and following GDB's example) would be to
add a new set of configuration variables to specify:

a. the baud rate used

b. the parity kind used

c. the number of stop bits

d. whether to use hardware flow control

I'm thinking of creating a new setting group for this, maybe
'host.serial'. When connecting to a serial port, LLDB would set its
parameters based on the settings from this group.

That said, I can't think of a really clean way of making this
configurable on lldb-server's end but I guess adding more command-line
parameters should suffice.

WDYT?

[1] https://man7.org/linux/man-pages/man0/termios.h.0p.html
[2] https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-dcb

This looks pretty sensible. I spent a lot of time fighting serial ports on early PCs and Apple IIs back in the eighties, and you seem to be covering most of the usual problems. You should probably:

Make hardware flow control compulsory, since software flow control will not be practical, and with no flow control things are unlikely to work in any useful way.

Decide how high a baud rate you want to permit: 115.2Kbps is often the practical limit on genuine serial cables.

Create default settings for client and server that are compatible, so that most people don't have to fiddle with them.

I have not used LLDB over any kind of wire, but I have used GDB that way, over USB between Linux and Android. That sometimes needs to download debug data or binaries from the device to the Linux machine. Over USB 2.0 or faster, this isn't a problem, but over 115.2kbps serial, it could take quite a while. You should consider giving the user a chance to avoid locking up their session while that happens.

Best,

John

Thanks for the nice summary Michał. I've found it very helpful.

The thing I am missing from this proposal is how would those settings translate into actual termios calls? Like, who would be responsible reading those settings and acting on them? Currently we have some tty code in ConnectionFileDescriptorPosix (in the Host library) but I think a bit too low-level for that (I don't think the host library knows anything about "settings"). I am also not sure if a class called "ConnectionFileDescriptor" is really the best place for this.

Hi, everyone.

I'm working on improving LLDB's feature parity with GDB. As part of
this, I'm working on bettering LLDB's serial port support. Since serial
ports are not that common these days, I've been asked to explain a bit
what I'd like to do.

At this point, LLDB (client) has minimal support for serial port
debugging. You can use a command like:

     process connect file:///dev/ttyS0

to connect via the GDB Remote Protocol over a serial port. However,
the client hardcodes serial transmission parameters (I'll explain
below). I haven't been able to find an option to bind lldb-server to a
serial port.

I'd like to fix the both limitations, i.e. make it possible to configure
serial port parameters and add support for serial port in lldb-server.

The RS-232 standard is quite open ended, so I'm going to focus on 8250-
compatible serial port with DB-9 connector below (i.e. the kind found
in home PCs). However, I'm going to skip the gory details and just
focus on the high-level problem.

The exact protocol used to transmit data over the serial port is
configurable to some degree. However, there is no support for
autoconfiguration, so both ends have to be configured the same.
The synchronization support is also minimal.

The important hardware parameters that can be configured are:

- baud rate, i.e. data transmission speed that implies the sampling
   rate. The higher the baud rate, the shorter individual bits are
   in the transmitted signal. If baud rate is missynced, then
   the receiver will get wrong bit sequences.

- number of data bits (5-8) in the frame, lower values meaning that
   the characters sent are being truncated. For binary data transfers,
   8 data bits must be used.

I believe gdb-remote protocol is compatible with 7-bit connections, though we would need to make sure lldb refrains from using some packets. Should I take it this is not an avenue you wish to pursue?

- parity used to verify frame correctness. The parity bit is optional,
   and can be configured to use odd or even parity. Additionally, Linux
   supports sending constant 0 or 1 as parity bit.

- number of stop bits (1 or 1.5/2) in the frame. The use of more than
   one stop bit is apparently a relict that was supposed to give
   the receiver more time for processing. I think this one isn't
   strictly necessary nowadays.

Gotta love those half-bits.

- flow control (none, software, hardware). This is basically used by
   the receiver to inform the sender that it's got its buffer full
   and the sender must stop transmitting. Software flow control used
   in-band signaling, so it's not suitable for binary protocols.
   Hardware flow control uses control lines.

Of course, there is more to serial ports than just that but for LLDB's
purposes, this should be sufficient.

The POSIX and win32 API for serial ports are quite similar in design.
In the POSIX API, you have to open a character device corresponding to
the serial port, while in win32 API a special path \\.\COMn. In both
cases, reads and writes effect the transmission. Both systems also have
a dedicated API to configure the serial transmission parameters
(ioctl/termios on POSIX [1], DCB on win32 [2]). Note that I haven't
tried the win32 API, just looked it up.

The POSIX serial ports are a teletype (tty) devices just like virtual
consoles used by terminal emulators. This makes it easy to use a serial
port as a remote terminal for other system. This also adds a bunch of
configuration options related to input/output processing and special
behavior. When a serial port is going to be used for non-console
purposes, these flags have to be disabled (i.e. the tty is set to 'raw'
mode).

The rough idea is that after opening the serial port, we need to set its
parameters to match the other end. For this to work, I need to replace
LLDB's hardwired parameters with some way of specifying this. I think
the cleanest way of doing this (and following GDB's example) would be to
add a new set of configuration variables to specify:

a. the baud rate used

b. the parity kind used

c. the number of stop bits

d. whether to use hardware flow control

I'm thinking of creating a new setting group for this, maybe
'host.serial'. When connecting to a serial port, LLDB would set its
parameters based on the settings from this group.

That said, I can't think of a really clean way of making this
configurable on lldb-server's end but I guess adding more command-line
parameters should suffice.

WDYT?

Let me try to make a counterproposal.

Since the serial parameters are a property of a specific connection, and one could theoretically have be debugging multiple processes with different connection parameters, having a (global) setting for them does not seem ideal. And since lldb already has a history of using made up urls (unix-connect://, tcp://), I am wondering if we couldn't/shouldn't invent a new url scheme for this. So like instead of file:///dev/ttyS0, one would use a new scheme (say serial:// to connect), and we would somehow encode the connection parameters into the url. For example, this could look something like
   serial://[PARODD,STOP1,B19200]/dev/ttyS0
This connection string could be passed to both lldb and lldb-server without any new arguments. Implementation-wise this url could be detected at a fairly high level, and would cause us to instantiate a new class which would handle the serial connection.

Would this be too ugly?

pl

Thanks for the nice summary Michał. I've found it very helpful.

The thing I am missing from this proposal is how would those settings
translate into actual termios calls? Like, who would be responsible
reading those settings and acting on them? Currently we have some tty
code in ConnectionFileDescriptorPosix (in the Host library) but I think
a bit too low-level for that (I don't think the host library knows
anything about "settings"). I am also not sure if a class called
"ConnectionFileDescriptor" is really the best place for this.

> Hi, everyone.
>
> I'm working on improving LLDB's feature parity with GDB. As part of
> this, I'm working on bettering LLDB's serial port support. Since serial
> ports are not that common these days, I've been asked to explain a bit
> what I'd like to do.
>
>
> At this point, LLDB (client) has minimal support for serial port
> debugging. You can use a command like:
>
> process connect file:///dev/ttyS0
>
> to connect via the GDB Remote Protocol over a serial port. However,
> the client hardcodes serial transmission parameters (I'll explain
> below). I haven't been able to find an option to bind lldb-server to a
> serial port.
>
> I'd like to fix the both limitations, i.e. make it possible to configure
> serial port parameters and add support for serial port in lldb-server.
>
>
> The RS-232 standard is quite open ended, so I'm going to focus on 8250-
> compatible serial port with DB-9 connector below (i.e. the kind found
> in home PCs). However, I'm going to skip the gory details and just
> focus on the high-level problem.
>
> The exact protocol used to transmit data over the serial port is
> configurable to some degree. However, there is no support for
> autoconfiguration, so both ends have to be configured the same.
> The synchronization support is also minimal.
>
> The important hardware parameters that can be configured are:
>
> - baud rate, i.e. data transmission speed that implies the sampling
> rate. The higher the baud rate, the shorter individual bits are
> in the transmitted signal. If baud rate is missynced, then
> the receiver will get wrong bit sequences.
>
> - number of data bits (5-8) in the frame, lower values meaning that
> the characters sent are being truncated. For binary data transfers,
> 8 data bits must be used.
I believe gdb-remote protocol is compatible with 7-bit connections,
though we would need to make sure lldb refrains from using some packets.
Should I take it this is not an avenue you wish to pursue?

No, I don't think that there's a good reason to pursue it. GDB itself
doesn't support 7-bit connections (i.e. forces 8 bits unconditionally),
so I doubt that there's a point in doing that.

>
> - parity used to verify frame correctness. The parity bit is optional,
> and can be configured to use odd or even parity. Additionally, Linux
> supports sending constant 0 or 1 as parity bit.
>
> - number of stop bits (1 or 1.5/2) in the frame. The use of more than
> one stop bit is apparently a relict that was supposed to give
> the receiver more time for processing. I think this one isn't
> strictly necessary nowadays.
Gotta love those half-bits.

Fortunately, 8250 support half-bits only with 5-bit data frames, so we
can forget about them entirely ;-).

>
> - flow control (none, software, hardware). This is basically used by
> the receiver to inform the sender that it's got its buffer full
> and the sender must stop transmitting. Software flow control used
> in-band signaling, so it's not suitable for binary protocols.
> Hardware flow control uses control lines.
>
> Of course, there is more to serial ports than just that but for LLDB's
> purposes, this should be sufficient.
>
>
> The POSIX and win32 API for serial ports are quite similar in design.
> In the POSIX API, you have to open a character device corresponding to
> the serial port, while in win32 API a special path \\.\COMn. In both
> cases, reads and writes effect the transmission. Both systems also have
> a dedicated API to configure the serial transmission parameters
> (ioctl/termios on POSIX [1], DCB on win32 [2]). Note that I haven't
> tried the win32 API, just looked it up.
>
> The POSIX serial ports are a teletype (tty) devices just like virtual
> consoles used by terminal emulators. This makes it easy to use a serial
> port as a remote terminal for other system. This also adds a bunch of
> configuration options related to input/output processing and special
> behavior. When a serial port is going to be used for non-console
> purposes, these flags have to be disabled (i.e. the tty is set to 'raw'
> mode).
>
>
> The rough idea is that after opening the serial port, we need to set its
> parameters to match the other end. For this to work, I need to replace
> LLDB's hardwired parameters with some way of specifying this. I think
> the cleanest way of doing this (and following GDB's example) would be to
> add a new set of configuration variables to specify:
>
> a. the baud rate used
>
> b. the parity kind used
>
> c. the number of stop bits
>
> d. whether to use hardware flow control
>
> I'm thinking of creating a new setting group for this, maybe
> 'host.serial'. When connecting to a serial port, LLDB would set its
> parameters based on the settings from this group.
>
> That said, I can't think of a really clean way of making this
> configurable on lldb-server's end but I guess adding more command-line
> parameters should suffice.
>
> WDYT?

Let me try to make a counterproposal.

Since the serial parameters are a property of a specific connection, and
one could theoretically have be debugging multiple processes with
different connection parameters, having a (global) setting for them does
not seem ideal. And since lldb already has a history of using made up
urls (unix-connect://, tcp://), I am wondering if we couldn't/shouldn't
invent a new url scheme for this. So like instead of file:///dev/ttyS0,
one would use a new scheme (say serial:// to connect), and we would
somehow encode the connection parameters into the url. For example, this
could look something like
   serial://[PARODD,STOP1,B19200]/dev/ttyS0
This connection string could be passed to both lldb and lldb-server
without any new arguments. Implementation-wise this url could be
detected at a fairly high level, and would cause us to instantiate a new
class which would handle the serial connection.

I don't have a strong opinion either way. I suppose this would be
a little easier to implement, though I'd prefer using something more
classically URL-ish, i.e.:

  serial:///dev/ttyS0?baud=115200&parity=odd...

or:

  serial:///dev/ttyS0,baud=115200,parity=odd...

I suppose it would prevent weird paths but I don't think we need to
account for weird paths for ttys (and if we did, we should probably
urlencode them anyway).

The the full-url version (with ? and &). I'm sure you've noticed this already, but for the sake of others, let me just mention that we already have a TODO to support a syntax like this.

(I meant to say I like the full url version)