|
| 1 | +LLDB RPC Upstreaming Design Doc |
| 2 | +=============================== |
| 3 | + |
| 4 | +This document aims to explain the general structure of the upstreaming patches for adding LLDB RPC. The 2 primary concepts explained here will be: |
| 5 | + |
| 6 | +* How LLDB RPC is used |
| 7 | +* How the ``lldb-rpc-gen`` works and what it outputs |
| 8 | + |
| 9 | +LLDB RPC |
| 10 | +********* |
| 11 | + |
| 12 | +LLDB RPC is a framework by which processes can communicate with LLDB out of process while maintaining compatibility with the SB API. More details are explained in the `RFC<https://discourse.llvm.org/t/rfc-upstreaming-lldb-rpc/85804>`_ for upstreaming LLDB RPC, but the main focus in this doc for this section will be how exactly the code is structured for the PRs that will upstream this code. |
| 13 | + |
| 14 | +The ``lldb-rpc-gen`` tool |
| 15 | +************************* |
| 16 | + |
| 17 | +``lldb-rpc-gen`` is the tool that generates the main client and server interfaces for LLDB RPC. It is a ``ClangTool`` that reads all SB API header files and their functions and outputs the client/server interfaces and certain other pieces of code, such as RPC-specfic versions of Python bindings used for the test suite. There's 3 main components behind ``lldb-rpc-gen``: |
| 18 | + |
| 19 | +1. The ``lldb-rpc-gen`` tool itself, which contains the main driver that uses the ``ClangTool``. |
| 20 | +2. The code that generates all interfaces, which we call "emitters". All generated code for the interfaces are in C++, so the server side has one emitter for its generated source code and another for its generated header code. The client side has the same. |
| 21 | +3. All common code shared between all emitters, such as helper methods and information about exceptions to take when emitting. |
| 22 | + |
| 23 | +The `current PR<https://github.com/llvm/llvm-project/pull/136748>`_ up for upstreaming LLDB RPC upstreams a subset of the code used for the tool. It upstreams the ``lldb-rpc-gen`` tool and all code needed for the server side emitters. Here's an example of what ``lldb-rpc-gen`` will output for the server side interface: |
| 24 | + |
| 25 | +Input |
| 26 | +----- |
| 27 | + |
| 28 | +We'll use ``SBDebugger::CreateTarget(const char *filename)`` as an example. ``lldb-rpc-gen`` will read this method from ``SBDebugger.h``. The output is as follows. |
| 29 | + |
| 30 | +Source Code Output |
| 31 | +------------------ |
| 32 | + |
| 33 | +:: |
| 34 | + |
| 35 | + bool rpc_server::_ZN4lldb10SBDebugger12CreateTargetEPKc::HandleRPCCall(rpc_common::Connection &connection, RPCStream &send, RPCStream &response) { |
| 36 | + // 1) Make local storage for incoming function arguments |
| 37 | + lldb::SBDebugger *this_ptr = nullptr; |
| 38 | + rpc_common::ConstCharPointer filename; |
| 39 | + // 2) Decode all function arguments |
| 40 | + this_ptr = RPCServerObjectDecoder<lldb::SBDebugger>(send, rpc_common::RPCPacket::ValueType::Argument); |
| 41 | + if (!this_ptr) |
| 42 | + return false; |
| 43 | + if (!RPCValueDecoder(send, rpc_common::RPCPacket::ValueType::Argument, filename)) |
| 44 | + return false; |
| 45 | + // 3) Call the method and encode the return value |
| 46 | + lldb::SBTarget && __result = this_ptr->CreateTarget(filename.c_str()); |
| 47 | + RPCServerObjectEncoder(response, rpc_common::RPCPacket::ValueType::ReturnValue, std::move(__result)); |
| 48 | + return true; |
| 49 | + } |
| 50 | + |
| 51 | +Function signature |
| 52 | +~~~~~~~~~~~~~~~~~~ |
| 53 | + |
| 54 | +All server-side source code functions have a function signature that take the format ``bool rpc_server::<mangled-function-name>::HandleRPCCall(rpc_common::Connection &connection, RPCStream &send, RPCStream &response)``. Here the ``connection`` is what's maintained between the client and server. The ``send`` variable is a byte stream that carries information sent from the client. ``response`` is also a byte stream that will be populated with the return value obtained from the call into the SB API function that will be sent back to the client. |
| 55 | + |
| 56 | +Local variable storage |
| 57 | +~~~~~~~~~~~~~~~~~~~~~~ |
| 58 | + |
| 59 | +First, variables are created to hold all arguments coming in from the client side. These variables will be a pointer for the SB API class in question, and corresponding variables for all parameters that the function has. Since this signature for ``SBDebugger::CreateTarget()`` only has one parameter, a ``const char *``, 2 local variables get created. A pointer for an ``SBDebugger`` object, and an ``RPCCommon::ConstCharPointer`` for the ``const char * filename`` parameter. The ``ConstCharPointer`` is a typedef over ``const char *`` in the main RPC core code. |
| 60 | + |
| 61 | +Incoming stream decoding |
| 62 | +~~~~~~~~~~~~~~~~~~~~~~~~ |
| 63 | + |
| 64 | +Following this, ``RPCServerObjectDecoder`` is used to decode the ``send`` byte stream. In this case, we're decoding this stream into the ``SBDebugger`` pointer we created earlier. We then decode the ``send`` stream again to obtain the ``const char * filename`` sent by the client. Each decoded argument from the client is checked for validity and the function will exit early if any are invalid. |
| 65 | + |
| 66 | +SB API function call |
| 67 | +~~~~~~~~~~~~~~~~~~~~ |
| 68 | + |
| 69 | +Once all arguments have been decoded, the underlying SB API function called with the decoded arguments. ``RPCServerObjectEncoder`` is then used to encode the return value from the SB API call into the ``response`` stream, and this is then sent back to the client. |
| 70 | + |
| 71 | +Header Code Output |
| 72 | +------------------ |
| 73 | +:: |
| 74 | + |
| 75 | + class _ZN4lldb10SBDebugger12CreateTargetEPKc : public rpc_common::RPCFunctionInstance { |
| 76 | + public: |
| 77 | + _ZN4lldb10SBDebugger12CreateTargetEPKc() : RPCFunctionInstance("_ZN4lldb10SBDebugger12CreateTargetEPKc") {} |
| 78 | + ~_ZN4lldb10SBDebugger12CreateTargetEPKc() override {} |
| 79 | + bool HandleRPCCall(rpc_common::Connection &connection, rpc_common::RPCStream &send, rpc_common::RPCStream &response) override; |
| 80 | + }; |
| 81 | + |
| 82 | +Class definition and ``HandleRPCCall`` |
| 83 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 84 | + |
| 85 | +ALL RPC server side functions are subclasses of ``RPCFunctionInstance``. Each class will then define their ``HandleRPCCall`` function that is seen in the source code above. This subclassing and ``HandleRPCCall`` definition is what is emitted in the header code for server. |
| 86 | + |
| 87 | +The ``lldb-rpc-gen`` emitters |
| 88 | +***************************** |
| 89 | + |
| 90 | +The bulk of the code is generated using the emitters. For the server side, we have ``RPCServerSourceEmitter`` and ``RPCServerHeaderEmitter``. The former handles generation of the source code and the latter handles generation of the header code seen above. |
| 91 | + |
| 92 | +Emitters largely have similar structure. Constituent sections of code, such as function headers, function bodies and others are typically given their own method. As an example, the function to emit a function header is ``EmitFunctionHeader()`` and the function to emit a function body is ``EmitFunctionBody()``. Information that will be written to the output file is written using ``EmitLine()``, which uses ``llvm::raw_string_ostreams`` and is defined in ``RPCCommon.h``. |
| 93 | + |
| 94 | +Since this is a ``ClangTool``, information about each method is obtained from Clang itself and stored in the ``Method`` struct located in ``RPCCommon.h`` in ``lldb-rpc-gen``'s directory. ``Method`` is used for simplicity and abstraction, and other information that would be needed from the SB API is obtained from Clang directly. |
0 commit comments