Jonas Devlieghere 3c4c2fada2
[lldb] Expose debuggers and target as resources through MCP (#148075)
Expose debuggers and target as resources through MCP. This has two
advantages:

1. Enables returning data in a structured way. Although tools can return
structured data with the latest revision of the protocol, we might not
be able to update before the majority of clients has adopted it.
2. Enables the user to specify a resource themselves, rather than
letting the model guess which debugger instance it should use.

This PR exposes a resource for debuggers and targets.

The following URI returns information about a given debugger instance:

```
lldb://debugger/<debugger id>
```

For example:

```
{
  uri: "lldb://debugger/0"
  mimeType: "application/json"
  text: "{"debugger_id":0,"num_targets":2}"
}
```

The following URI returns information about a given target:

```
lldb://debugger/<debugger id>/target/<target id>
```

For example:

```
{
  uri: "lldb://debugger/0/target/0"
  mimeType: "application/json"
  text: "{"arch":"arm64-apple-macosx26.0.0","debugger_id":0,"path":"/Users/jonas/llvm/build-ra/bin/count","target_id":0}"
}
```
2025-07-11 15:49:27 -07:00

218 lines
7.2 KiB
C++

// 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 "Resource.h"
#include "MCPError.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
#include "lldb/Target/Platform.h"
using namespace lldb_private::mcp;
namespace {
struct DebuggerResource {
uint64_t debugger_id = 0;
std::string name;
uint64_t num_targets = 0;
};
llvm::json::Value toJSON(const DebuggerResource &DR) {
llvm::json::Object Result{{"debugger_id", DR.debugger_id},
{"num_targets", DR.num_targets}};
if (!DR.name.empty())
Result.insert({"name", DR.name});
return Result;
}
struct TargetResource {
size_t debugger_id = 0;
size_t target_idx = 0;
bool selected = false;
bool dummy = false;
std::string arch;
std::string path;
std::string platform;
};
llvm::json::Value toJSON(const TargetResource &TR) {
llvm::json::Object Result{{"debugger_id", TR.debugger_id},
{"target_idx", TR.target_idx},
{"selected", TR.selected},
{"dummy", TR.dummy}};
if (!TR.arch.empty())
Result.insert({"arch", TR.arch});
if (!TR.path.empty())
Result.insert({"path", TR.path});
if (!TR.platform.empty())
Result.insert({"platform", TR.platform});
return Result;
}
} // namespace
static constexpr llvm::StringLiteral kMimeTypeJSON = "application/json";
template <typename... Args>
static llvm::Error createStringError(const char *format, Args &&...args) {
return llvm::createStringError(
llvm::formatv(format, std::forward<Args>(args)...).str());
}
static llvm::Error createUnsupportedURIError(llvm::StringRef uri) {
return llvm::make_error<UnsupportedURI>(uri.str());
}
protocol::Resource
DebuggerResourceProvider::GetDebuggerResource(Debugger &debugger) {
const lldb::user_id_t debugger_id = debugger.GetID();
protocol::Resource resource;
resource.uri = llvm::formatv("lldb://debugger/{0}", debugger_id);
resource.name = debugger.GetInstanceName();
resource.description =
llvm::formatv("Information about debugger instance {0}: {1}", debugger_id,
debugger.GetInstanceName());
resource.mimeType = kMimeTypeJSON;
return resource;
}
protocol::Resource
DebuggerResourceProvider::GetTargetResource(size_t target_idx, Target &target) {
const size_t debugger_id = target.GetDebugger().GetID();
std::string target_name = llvm::formatv("target {0}", target_idx);
if (Module *exe_module = target.GetExecutableModulePointer())
target_name = exe_module->GetFileSpec().GetFilename().GetString();
protocol::Resource resource;
resource.uri =
llvm::formatv("lldb://debugger/{0}/target/{1}", debugger_id, target_idx);
resource.name = target_name;
resource.description =
llvm::formatv("Information about target {0} in debugger instance {1}",
target_idx, debugger_id);
resource.mimeType = kMimeTypeJSON;
return resource;
}
std::vector<protocol::Resource> DebuggerResourceProvider::GetResources() const {
std::vector<protocol::Resource> resources;
const size_t num_debuggers = Debugger::GetNumDebuggers();
for (size_t i = 0; i < num_debuggers; ++i) {
lldb::DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(i);
if (!debugger_sp)
continue;
resources.emplace_back(GetDebuggerResource(*debugger_sp));
TargetList &target_list = debugger_sp->GetTargetList();
const size_t num_targets = target_list.GetNumTargets();
for (size_t j = 0; j < num_targets; ++j) {
lldb::TargetSP target_sp = target_list.GetTargetAtIndex(j);
if (!target_sp)
continue;
resources.emplace_back(GetTargetResource(j, *target_sp));
}
}
return resources;
}
llvm::Expected<protocol::ResourceResult>
DebuggerResourceProvider::ReadResource(llvm::StringRef uri) const {
auto [protocol, path] = uri.split("://");
if (protocol != "lldb")
return createUnsupportedURIError(uri);
llvm::SmallVector<llvm::StringRef, 4> components;
path.split(components, '/');
if (components.size() < 2)
return createUnsupportedURIError(uri);
if (components[0] != "debugger")
return createUnsupportedURIError(uri);
size_t debugger_idx;
if (components[1].getAsInteger(0, debugger_idx))
return createStringError("invalid debugger id '{0}': {1}", components[1],
path);
if (components.size() > 3) {
if (components[2] != "target")
return createUnsupportedURIError(uri);
size_t target_idx;
if (components[3].getAsInteger(0, target_idx))
return createStringError("invalid target id '{0}': {1}", components[3],
path);
return ReadTargetResource(uri, debugger_idx, target_idx);
}
return ReadDebuggerResource(uri, debugger_idx);
}
llvm::Expected<protocol::ResourceResult>
DebuggerResourceProvider::ReadDebuggerResource(llvm::StringRef uri,
lldb::user_id_t debugger_id) {
lldb::DebuggerSP debugger_sp = Debugger::FindDebuggerWithID(debugger_id);
if (!debugger_sp)
return createStringError("invalid debugger id: {0}", debugger_id);
DebuggerResource debugger_resource;
debugger_resource.debugger_id = debugger_id;
debugger_resource.name = debugger_sp->GetInstanceName();
debugger_resource.num_targets = debugger_sp->GetTargetList().GetNumTargets();
protocol::ResourceContents contents;
contents.uri = uri;
contents.mimeType = kMimeTypeJSON;
contents.text = llvm::formatv("{0}", toJSON(debugger_resource));
protocol::ResourceResult result;
result.contents.push_back(contents);
return result;
}
llvm::Expected<protocol::ResourceResult>
DebuggerResourceProvider::ReadTargetResource(llvm::StringRef uri,
lldb::user_id_t debugger_id,
size_t target_idx) {
lldb::DebuggerSP debugger_sp = Debugger::FindDebuggerWithID(debugger_id);
if (!debugger_sp)
return createStringError("invalid debugger id: {0}", debugger_id);
TargetList &target_list = debugger_sp->GetTargetList();
lldb::TargetSP target_sp = target_list.GetTargetAtIndex(target_idx);
if (!target_sp)
return createStringError("invalid target idx: {0}", target_idx);
TargetResource target_resource;
target_resource.debugger_id = debugger_id;
target_resource.target_idx = target_idx;
target_resource.arch = target_sp->GetArchitecture().GetTriple().str();
target_resource.dummy = target_sp->IsDummyTarget();
target_resource.selected = target_sp == debugger_sp->GetSelectedTarget();
if (Module *exe_module = target_sp->GetExecutableModulePointer())
target_resource.path = exe_module->GetFileSpec().GetPath();
if (lldb::PlatformSP platform_sp = target_sp->GetPlatform())
target_resource.platform = platform_sp->GetName();
protocol::ResourceContents contents;
contents.uri = uri;
contents.mimeType = kMimeTypeJSON;
contents.text = llvm::formatv("{0}", toJSON(target_resource));
protocol::ResourceResult result;
result.contents.push_back(contents);
return result;
}