LLDB Platform Refactor RFC
Authors: Alex Langford, Jonas Devlieghere, Ismail Bennani, Jim Ingham
This RFC proposes breaking up LLDB’s Platform into different abstractions: PlatformInfo and Device. It is motivated by specific weaknesses of Platform, both functional and ergonomic. Additionally, it proposes a new abstraction (DeviceProvider) for device discovery.
Motivation
To make it easier to debug devices from the command line, LLDB in Xcode 16 now integrates with CoreDevice. CoreDevice is a framework for interacting with remote devices. Its features include listing connected devices, listing processes on a device, and attaching to a process with debugserver. While integrating CoreDevice, we hit a few limitations of the current Platform abstraction. Specifically, Platform represents both how to connect to a device (i.e. over gdb-remote) and how to debug on particular device (e.g. what OS version it’s running). Adding a new way of talking to a device (i.e. using CoreDevice) would require duplicating all existing supported platforms. This leads to an unsustainable combinatorial explosion of implementations and makes it harder to add new platforms in the future.
In addition to the problem of Platform representing two concepts, it has additional shortcomings:
- Talking to more than one device in platform mode is not really supported. Each platform owns its own connection information, so one Platform instance cannot connect to multiple devices at the same time. To talk to two Linux machines with Platforms, you would therefore need two platform objects. From the command line, this is not possible today. Running
platform select remote-linux
andplatform connect
will certainly connect you to a remote linux machine, but any sequentialplatform connect
commands would terminate the previous connection. Creating additionalremote-linux
platforms is also not possible, because the debugger creates one Platform instance for each kind of platform. The only possible way is through the SBAPI: Create multipleSBPlatform
instances and switch between them withSBDebugger::SetSelectedPlatform
. - Platform implementations are more complex than they need to be. The Platform class has a large number of methods, some of which are confusing or have unclear overlap. There are functions that are duplicated to support remote variants, e.g.
GetOSBuildString
vsGetRemoteOSBuildString
. There is also a Host platform which does not behave like a remote platform. Instead of having methods overridden like the other platforms, there is bespoke logic in the base class to check if the current platform is considered the host platform. - Some platforms support device discovery, e.g. Adb for Android (
adb devices
) or CoreDevice for Apple devices (devicectl list devices
). Regardless of how this functionality is implemented, Platforms are a natural integration point. This functionality would be very valuable for workflows that involve connecting to and debugging on devices repeatedly. Existing workflows frequently include setting up tunnels manually and copying around files individually. Platform has support for moving files around over SSH and Rsync, but those also may require involved setups.
The Proposal
Internal LLDB Architecture
The responsibilities of Platform can be broken down into two distinct categories:
- How do we debug a given platform? The answer to this question doesn’t require you to have a concrete device running that platform: it is known statically. An example of this is the library file extension on that platform. We call this PlatformInfo.
- How do we debug a particular platform? The answer to this question does require you to have a concrete device. You need to know how to talk to it and how to ask it query it for information: it is known dynamically. An example of this information is the version of the operating system that’s running on the device. We call this Device.
PlatformInfo
PlatformInfo
provides static information about the platform such as Platform::GetFullNameForDylib
which depending on the platform returns libfoo.dylib
or libfoo.so
or foo.dll
.
Some of this static information might be host-specific. For example: Platform::GetSDKDirectory
which attempts to find a directory on the host machine containing SDK information for said platform. Moving that complexity into the host platform is outside the scope of this RFC.
Device
Device
represents a specific device, like a remote iOS device or a remote Windows machine. It will own its own connection information and supports the device and connection-specific information that Platform currently owns, such as interacting with the file system or processes. Devices will be responsible for dynamic knowledge, such as retrieving an OS version or Build ID for a remote device.
A useful consequence of a device owning its own connection means that LLDB can talk to devices using things other than the gdb-remote protocol. For example, CoreDevice can perform many of the operations that the gdb-remote platform packets can do.
DeviceProvider
DeviceProvider
is the abstraction that allows LLDB to discover and communicate with devices. It will use LLDB’s plugin mechanism to provide this functionality conditionally. Concretely, on Apple platforms, this will be powered by CoreDevice. Some other platforms, such as Android, could use technologies like ADB. Some platforms will have no DeviceProvider and connections to devices can only be formed using a URL.
For example, our fork currently supports a device list
command that shows all devices that CoreDevice knows about.
(lldb) device list
Name Identifier State Configuration
------------- ------------------------------------ ----------- ------------------
Alex’s iPhone 57D7F4E1-3B3D-4CEE-8A5F-4E6287A4BC2C available iOS 18.0.1
iPad D41398E1-841E-420D-AB7F-72726923A601 unavailable iOS 18.0
Changes to existing architecture
Platforms will no longer represent both the knowledge of a platform and abilities to interact with a device. The device-specific functionality will live in the new Device class. To obtain a device for a given platform, Platform will have a new overload of Platform::Connect
that takes some identifier for a device and returns a handle for that device. To illustrate:
lldb_private::DeviceSP Platform::Connect(lldb_private::DeviceIdentifier device_id);
How the identifier is discovered will be addressed later. Internally, the platform will talk to DeviceProviders to form that device connection. For platforms with no existing DeviceProviders, a connection URL will need to be supplied instead.
Targets currently hold onto a Platform instance and will continue to do so. They will also optionally contain a handle to a Device. Where Target uses Platform today, it will now use Platform, Device, or a combination of the two to accomplish the same task.
The existing host platform would be treated a little differently. Specifically, the host device would be created automatically at start up and always exist. From an interface perspective, the host device will be like any other device, but implementation-wise it will talk to the local filesystem instead of talking over the network.
SBAPI
The existing SBPlatform functionality will continue to work as-is to preserve API compatibility. This will be achieved by associating each SBPlatform instance with a single Device internally.
In addition, we propose introducing a new SBDevice class to correspond to the new Device abstraction. SBDevices would be created by a new method on SBPlatform. New clients would be encouraged to use a combination of SBPlatform and SBDevice to debug instead of the existing SBPlatform functionality.
Commands
Similar to the SBAPI, we propose maintaining existing command-line functionality without breaking compatibility. To support distinguishing between different devices, we also propose adding a new device
subcommand to the top-level platform
command. Examples of new commands:
platform device list
would list the devices that a platform is aware of. For example, if your selected platform wasremote-android
, this command would list every device that the ADB DeviceProvider can find as well as any devices formed from URL connections.platform device connect
would connect to a device with some kind of identifier.
After that, interacting with specific devices would involve a top-level device
command. Some examples:
device list
would list all connected devices, regardless of platform.device select $ID
would select a specific device ID.device [--device-id $ID] process list
would list every process on the selected device or with from the device with id$ID
.device --device-id 2 get-file /tmp/some-file.txt
would transfer the file/tmp/some-file.txt
off device with ID2
.