Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 28 additions & 7 deletions .github/workflows/sbpf-dexter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 2G
steps:
- uses: actions/checkout@v6
- name: Check out the LLVM and LLDB changes
uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/checkout@v6
- name: Check out the coordinated SBPF runtime
uses: actions/checkout@v6
with:
repository: anza-xyz/sbpf
ref: main
repository: walnuthq/sbpf
ref: feat/improve-bt
path: test-runtime
persist-credentials: false
- name: Install build dependencies
Expand Down Expand Up @@ -59,12 +61,30 @@ jobs:
-DLLVM_PARALLEL_LINK_JOBS=1 \
-DLLDB_ENABLE_PYTHON=ON -DPython3_EXECUTABLE=/usr/bin/python3 \
-DLLDB_ENABLE_LUA=OFF -DLLDB_ENABLE_CURSES=OFF \
-DLLDB_INCLUDE_TESTS=OFF
cmake --build build --target lld lldb --parallel 2
- name: Build the SBPF main interpreter with its debug server
-DLLDB_INCLUDE_TESTS=ON
cmake --build build --target lld lldb TargetTests ProcessGdbRemoteTests --parallel 2
- name: Test frame identity and remote stack parsing
run: |
build/tools/lldb/unittests/Target/TargetTests --gtest_filter='StackIDTest.*'
build/tools/lldb/unittests/Process/gdb-remote/ProcessGdbRemoteTests --gtest_filter='SBPFCallStackTest.*'
- name: Test SBPF remote frames through the LLDB API
run: |
/usr/bin/python3 lldb/test/API/dotest.py \
--executable "$PWD/build/bin/lldb" \
--compiler /usr/bin/clang --cmake-build-type Release \
--build-dir "$PWD/build/lldb-api-sbpf" \
-p TestSBPFCallStack.py \
lldb/test/API/functionalities/gdb_remote_client
- name: Verify the coordinated gdbstub branch
run: |
grep --fixed-strings \
'git+https://github.com/walnuthq/gdbstub?branch=feat%2Fsbpf-call-stack#' \
test-runtime/cli/Cargo.lock
- name: Build the SBPF interpreter with its debug server
run: |
rustup toolchain install 1.98.1 --profile minimal
cargo +1.98.1 build --locked --manifest-path test-runtime/cli/Cargo.toml
cargo +1.98.1 test --locked --manifest-path test-runtime/Cargo.toml --features debugger --test vm
- name: Install the pinned SBPF Rust producer
run: |
curl --fail --location --retry 3 \
Expand All @@ -78,6 +98,7 @@ jobs:
{
git rev-parse HEAD
git -C test-runtime rev-parse HEAD
sed -n '/name = "gdbstub"/,+3p' test-runtime/cli/Cargo.lock
build/bin/ld.lld --version
build/bin/lldb --version
platform-tools/rust/bin/rustc -vV
Expand Down
15 changes: 15 additions & 0 deletions cross-project-tests/debuginfo-tests/sbpf/Inputs/recursive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#![no_std]
#[inline(never)]
#[no_mangle]
pub extern "C" fn recurse(n: u64) -> u64 {
let n = unsafe { core::ptr::read_volatile(&n) };
if n == 0 {
return 1;
}
let result = recurse(n - 1);
unsafe { core::ptr::read_volatile(&result) }.wrapping_add(n)
}
#[no_mangle]
pub extern "C" fn entrypoint() -> u64 {
recurse(3)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DexDeclareFile('recursive.rs')
DexExpectProgramState({'frames': [{'location': {'lineno': 6}}, {'location': {'lineno': 9}}, {'location': {'lineno': 9}}, {'location': {'lineno': 9}}, {'location': {'lineno': 14}}]})
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
DexDeclareFile('calls.rs')
DexExpectStepOrder(1, 5, on_line=14)
DexExpectStepOrder(2, 4, on_line=10)
DexExpectStepOrder(3, on_line=5)
# The two columns on line 5 are the volatile read and wrapping_add.
DexExpectStepOrder(1, 6, on_line=14)
DexExpectStepOrder(2, 5, on_line=10)
DexExpectStepOrder(3, 4, on_line=5)
2 changes: 1 addition & 1 deletion cross-project-tests/debuginfo-tests/sbpf/rust-callers.test
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"source": "calls.rs", "expectations": "rust-callers.dex", "xfail": "SBPF-UNWIND: saved interpreter caller frames are not exposed to LLDB"}
{"source": "calls.rs", "expectations": "rust-callers.dex"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"source": "recursive.rs", "expectations": "rust-recursive.dex", "return_value": 7}
25 changes: 25 additions & 0 deletions lldb/docs/resources/lldbgdbremote.md
Original file line number Diff line number Diff line change
Expand Up @@ -2549,6 +2549,31 @@ read packet: $e0030100#b9
**Priority to Implement:** Only required for Wasm support. Necessary to show
variables.

## SBPF Packets

### qSBPFCallStack

Get the SBPF interpreter call stack for the given thread id. The response is a
hex-encoded list without delimiters. Each frame contains a 64-bit PC followed
by the 64-bit value of SBPF register r10, both in little-endian byte order.
Frames are ordered from the active frame to the oldest caller. The active PC is
the current instruction; caller PCs are return addresses.

```
send packet: $qSBPFCallStack:1#79
read packet: $2001000000000000002000000200000068010000000000000010000002000000#19
```

**Priority to Implement:** Only required for interpreted SBPF support.
Necessary to show stack traces because return PCs are stored in host memory.

The stack must include every physical frame through the oldest caller. LLDB
uses each frame's depth from that oldest caller as its identity across stops.
The frame pointer is the guest r10 register, not a DWARF CFA: dynamic-stack
versions can change it within a function or share it between recursive calls.
LLDB reports the CFA as unavailable when using this packet; register-based
DWARF expressions still use the actual guest registers.

## "x" - Binary memory read

> **Warning:** The format of this packet was decided before GDB 16
Expand Down
19 changes: 16 additions & 3 deletions lldb/include/lldb/Target/StackID.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define LLDB_TARGET_STACKID_H

#include "lldb/Core/AddressRange.h"
#include <optional>

namespace lldb_private {

Expand All @@ -20,7 +21,8 @@ class StackID {
StackID() = default;

explicit StackID(lldb::addr_t pc, lldb::addr_t cfa,
SymbolContextScope *symbol_scope, Process *process);
SymbolContextScope *symbol_scope, Process *process,
std::optional<uint32_t> call_frame_depth = std::nullopt);

~StackID() = default;

Expand All @@ -32,6 +34,10 @@ class StackID {

lldb::addr_t GetCallFrameAddressWithoutMetadata() const { return m_cfa; }

std::optional<uint32_t> GetCallFrameDepth() const {
return m_call_frame_depth;
}

SymbolContextScope *GetSymbolContextScope() const { return m_symbol_scope; }

void SetSymbolContextScope(SymbolContextScope *symbol_scope) {
Expand All @@ -41,11 +47,14 @@ class StackID {
void Clear() {
m_pc = LLDB_INVALID_ADDRESS;
m_cfa = LLDB_INVALID_ADDRESS;
m_cfa_with_metadata = LLDB_INVALID_ADDRESS;
m_call_frame_depth.reset();
m_symbol_scope = nullptr;
}

bool IsValid() const {
return m_pc != LLDB_INVALID_ADDRESS || m_cfa != LLDB_INVALID_ADDRESS;
return m_call_frame_depth.has_value() || m_pc != LLDB_INVALID_ADDRESS ||
m_cfa != LLDB_INVALID_ADDRESS;
}

void Dump(Stream *s);
Expand All @@ -64,12 +73,16 @@ class StackID {

/// The call frame address (stack pointer) value at the beginning of the
/// function that uniquely identifies this frame (along with m_symbol_scope
/// below)
/// below), when no explicit call frame depth is available.
lldb::addr_t m_cfa = LLDB_INVALID_ADDRESS;

/// The cfa with metadata (i.e. prior to Process::FixAddress).
lldb::addr_t m_cfa_with_metadata = LLDB_INVALID_ADDRESS;

/// Optional VM frame identity, counted from the oldest physical frame.
/// Greater depths are younger. This does not change CFA address semantics.
std::optional<uint32_t> m_call_frame_depth;

/// If nullptr, there is no block or symbol for this frame. If not nullptr,
/// this will either be the scope for the lexical block for the frame, or the
/// scope for the symbol. Symbol context scopes are always be unique pointers
Expand Down
14 changes: 14 additions & 0 deletions lldb/include/lldb/Target/Unwind.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define LLDB_TARGET_UNWIND_H

#include <mutex>
#include <optional>

#include "lldb/lldb-private.h"

Expand Down Expand Up @@ -58,10 +59,23 @@ class Unwind {
return DoCreateRegisterContextForFrame(frame);
}

/// An optional zero-based depth from the oldest physical frame. Unlike an
/// index from the current frame, this stays unchanged when callees are pushed
/// or popped. Virtual machines may use it to identify frames independently
/// of their guest stack addresses. It is not a canonical frame address.
std::optional<uint32_t> GetCallFrameDepth(uint32_t frame_idx) {
std::lock_guard<std::recursive_mutex> guard(m_unwind_mutex);
return DoGetCallFrameDepth(frame_idx);
}

Thread &GetThread() { return m_thread; }

protected:
// Classes that inherit from Unwind can see and modify these
virtual std::optional<uint32_t> DoGetCallFrameDepth(uint32_t frame_idx) {
return std::nullopt;
}

virtual void DoClear() = 0;

virtual uint32_t DoGetFrameCount() = 0;
Expand Down
3 changes: 3 additions & 0 deletions lldb/source/Plugins/Process/gdb-remote/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ add_lldb_library(lldbPluginProcessGDBRemote PLUGIN
GDBRemoteRegisterFallback.cpp
ProcessGDBRemote.cpp
ProcessGDBRemoteLog.cpp
RegisterContextSBPF.cpp
SBPFCallStack.cpp
ThreadGDBRemote.cpp
UnwindSBPF.cpp

LINK_COMPONENTS
Support
Expand Down
15 changes: 15 additions & 0 deletions lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
#include "Plugins/Process/Utility/StopInfoMachException.h"
#include "ProcessGDBRemote.h"
#include "ProcessGDBRemoteLog.h"
#include "SBPFCallStack.h"
#include "ThreadGDBRemote.h"
#include "lldb/Host/Host.h"
#include "lldb/Utility/StringExtractorGDBRemote.h"
Expand Down Expand Up @@ -220,6 +221,20 @@ void ProcessGDBRemote::DumpPluginHistory(Stream &s) {
gdb_comm.DumpHistory(s);
}

llvm::Expected<std::vector<SBPFCallFrame>>
ProcessGDBRemote::GetSBPFCallStack(lldb::tid_t tid) {
StreamString packet;
packet.Printf("qSBPFCallStack:%" PRIx64, tid);

StringExtractorGDBRemote response;
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
GDBRemoteCommunication::PacketResult::Success)
return llvm::createStringError("failed to send qSBPFCallStack");
if (!response.IsNormalResponse())
return llvm::createStringError("failed to get qSBPFCallStack response");
return ParseSBPFCallStack(response.GetStringRef());
}

std::chrono::seconds ProcessGDBRemote::GetPacketTimeout() {
return std::chrono::seconds(GetGlobalPluginProperties().GetPacketTimeout());
}
Expand Down
3 changes: 3 additions & 0 deletions lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

#include "GDBRemoteCommunicationClient.h"
#include "GDBRemoteRegisterContext.h"
#include "SBPFCallStack.h"

#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringMap.h"
Expand Down Expand Up @@ -200,6 +201,8 @@ class ProcessGDBRemote : public Process,

GDBRemoteCommunicationClient &GetGDBRemote() { return m_gdb_comm; }

llvm::Expected<std::vector<SBPFCallFrame>> GetSBPFCallStack(lldb::tid_t tid);

Status SendEventData(const char *data) override;

// Override DidExit so we can disconnect from the remote GDB server
Expand Down
74 changes: 74 additions & 0 deletions lldb/source/Plugins/Process/gdb-remote/RegisterContextSBPF.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//===-- RegisterContextSBPF.cpp ------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "RegisterContextSBPF.h"
#include "lldb/Utility/RegisterValue.h"
#include <utility>

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::process_gdb_remote;

RegisterContextSBPF::RegisterContextSBPF(
Thread &thread, uint32_t concrete_frame_idx,
GDBRemoteDynamicRegisterInfoSP register_info, lldb::addr_t pc,
lldb::addr_t frame_pointer)
: RegisterContext(thread, concrete_frame_idx),
m_register_info(std::move(register_info)), m_pc(pc),
m_frame_pointer(frame_pointer) {}

void RegisterContextSBPF::InvalidateAllRegisters() {}

size_t RegisterContextSBPF::GetRegisterCount() {
return m_register_info->GetNumRegisters();
}

const RegisterInfo *RegisterContextSBPF::GetRegisterInfoAtIndex(size_t reg) {
return m_register_info->GetRegisterInfoAtIndex(reg);
}

size_t RegisterContextSBPF::GetRegisterSetCount() {
return m_register_info->GetNumRegisterSets();
}

const RegisterSet *RegisterContextSBPF::GetRegisterSet(size_t reg_set) {
return m_register_info->GetRegisterSet(reg_set);
}

bool RegisterContextSBPF::ReadRegister(const RegisterInfo *reg_info,
RegisterValue &value) {
if (!reg_info)
return false;

switch (reg_info->kinds[eRegisterKindGeneric]) {
case LLDB_REGNUM_GENERIC_PC:
return value.SetUInt(m_pc, reg_info->byte_size);
case LLDB_REGNUM_GENERIC_FP:
return value.SetUInt(m_frame_pointer, reg_info->byte_size);
default:
return false;
}
}

bool RegisterContextSBPF::WriteRegister(const RegisterInfo *,
const RegisterValue &) {
return false;
}

bool RegisterContextSBPF::ReadAllRegisterValues(WritableDataBufferSP &) {
return false;
}

bool RegisterContextSBPF::WriteAllRegisterValues(const DataBufferSP &) {
return false;
}

uint32_t RegisterContextSBPF::ConvertRegisterKindToRegisterNumber(
lldb::RegisterKind kind, uint32_t num) {
return m_register_info->ConvertRegisterKindToRegisterNumber(kind, num);
}
Loading