blob: 09ea4cc9cc169c498f56a80b8dd48607c83f23ed [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ApiContext.h"
#include "Expressions.h"
#include "Variables.h"
#include "WasmModule.h"
#include "api.h"
#include "lldb/Symbol/CompilerType.h"
#include "lldb/Utility/ConstString.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-types.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/None.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/BinaryFormat/Wasm.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#define DEBUG_TYPE "symbols-backend"
namespace symbols_backend {
namespace api {
namespace {
Error MakeError(Error::Code ec, llvm::Twine message) {
Error e;
e.SetCode(ec);
e.SetMessage(message.str());
return e;
}
Error MakeError(Error::Code ec, llvm::Error error) {
Error e;
e.SetCode(ec);
llvm::handleAllErrors(std::move(error),
[&e](const llvm::StringError& string_error) {
e.SetMessage(string_error.getMessage());
});
return e;
}
Error MakeNotFoundError(llvm::StringRef raw_module_id) {
return MakeError(Error::Code::kModuleNotFoundError,
"Module with id '" + raw_module_id + "' not found");
}
Error MakeEvalError(llvm::Error error) {
Error e;
e.SetCode(Error::Code::kEvalError);
llvm::handleAllErrors(std::move(error),
[&e](const llvm::StringError& string_error) {
e.SetMessage(string_error.getMessage() +
" (Not all C++ expressions supported)");
});
return e;
}
Variable::Scope ToApiScope(lldb::ValueType scope) {
switch (scope) {
case lldb::eValueTypeVariableGlobal:
case lldb::eValueTypeVariableStatic:
return Variable::Scope::kGlobal;
case lldb::eValueTypeVariableArgument:
return Variable::Scope::kParameter;
case lldb::eValueTypeVariableLocal:
return Variable::Scope::kLocal;
default:
llvm::errs() << "Got variable scope " << scope << "\n";
llvm_unreachable("Unhandled variable scope");
}
}
llvm::SmallVector<RawLocationRange, 1> ToRawLocationRanges(
llvm::SmallSet<std::pair<lldb::addr_t, lldb::addr_t>, 1>&& ranges,
std::string raw_module_id) {
llvm::SmallVector<RawLocationRange, 1> locations;
for (auto offset_pair : ranges) {
RawLocationRange& location = locations.emplace_back();
location.SetRawModuleId(raw_module_id);
location.SetStartOffset(offset_pair.first);
location.SetEndOffset(offset_pair.first + offset_pair.second);
}
return locations;
}
} // namespace
// Notify the plugin about a new script
AddRawModuleResponse ApiContext::AddRawModule(
std::string raw_module_id, // A raw module identifier
std::string path // The path to the file with the wasm bytes
) {
AddRawModuleResponse response;
if (FindModule(raw_module_id)) {
response.SetError(
MakeError(Error::Code::kInternalError,
"Duplicate module with id '" + raw_module_id + "'"));
} else if (auto m = AddModule(raw_module_id, path)) {
auto source_info = m->GetSourceScripts();
response.SetSources(
{source_info.sources.begin(), source_info.sources.end()});
response.SetDwos({source_info.dwos.begin(), source_info.dwos.end()});
} else {
response.SetError(MakeError(Error::Code::kInternalError,
"Unable to load file '" + path + "'"));
}
return response;
};
void ApiContext::RemoveRawModule(
std::string raw_module_id // A raw module identifier
) {
DeleteModule(raw_module_id);
}
// Find locations in raw modules from a location in a source file
SourceLocationToRawLocationResponse ApiContext::SourceLocationToRawLocation(
std::string raw_module_id,
std::string source_file,
int32_t line_number,
int32_t column_number) {
SourceLocationToRawLocationResponse response;
auto module = FindModule(raw_module_id);
if (!module) {
response.SetError(MakeNotFoundError(raw_module_id));
return response;
}
llvm::SmallVector<RawLocationRange, 1> locations = ToRawLocationRanges(
module->GetOffsetFromSourceLocation(
{source_file, static_cast<uint32_t>(line_number + 1),
static_cast<uint16_t>(column_number + 1)}),
raw_module_id);
response.SetRawLocationRanges({locations.begin(), locations.end()});
return response;
};
// Find locations in source files from a location in a raw module
RawLocationToSourceLocationResponse ApiContext::RawLocationToSourceLocation(
std::string raw_module_id,
int32_t code_offset,
int32_t inline_frame_index) {
RawLocationToSourceLocationResponse response;
auto module = FindModule(raw_module_id);
if (!module) {
response.SetError(MakeNotFoundError(raw_module_id));
return response;
}
llvm::SmallVector<SourceLocation, 1> locations;
for (auto& source_loc :
module->GetSourceLocationFromOffset(code_offset, inline_frame_index)) {
SourceLocation& location = locations.emplace_back();
location.SetSourceFile(source_loc.file);
location.SetRawModuleId(raw_module_id);
location.SetLineNumber(source_loc.line - 1);
location.SetColumnNumber(source_loc.column - 1);
}
response.SetSourceLocation({locations.begin(), locations.end()});
return response;
}
// List all variables in lexical scope at a location in a raw module
ListVariablesInScopeResponse ApiContext::ListVariablesInScope(
std::string raw_module_id,
int32_t code_offset,
int32_t inline_frame_index) {
ListVariablesInScopeResponse response;
auto module = FindModule(raw_module_id);
if (!module) {
response.SetError(MakeNotFoundError(raw_module_id));
return response;
}
llvm::SmallVector<Variable, 1> variables;
for (auto& var :
module->GetVariablesInScope(code_offset, inline_frame_index)) {
if (var.name.empty()) {
// Skip anonymous objects, since we can't get their value through
// `evaluate`.
continue;
}
Variable& variable = variables.emplace_back();
variable.SetScope(ToApiScope(var.scope));
variable.SetName(var.name);
variable.SetType(var.type);
}
response.SetVariable({variables.begin(), variables.end()});
return response;
}
GetFunctionInfoResponse ApiContext::GetFunctionInfo(std::string raw_module_id,
int32_t code_offset) {
GetFunctionInfoResponse response;
auto module = FindModule(raw_module_id);
if (!module) {
response.SetError(MakeNotFoundError(raw_module_id));
return response;
}
FunctionInfo fi = module->GetFunctionInfo(code_offset);
response.SetFunctionNames({fi.names.begin(), fi.names.end()});
response.SetMissingSymbolFiles(
{fi.missing_symbols.begin(), fi.missing_symbols.end()});
return response;
}
namespace {
std::vector<std::string> GetTypeNames(lldb_private::CompilerType type) {
std::vector<std::string> names;
lldb_private::CompilerType original_type = type;
while (type.IsValid()) {
if (const char* type_name = type.GetDisplayTypeName().GetCString()) {
names.emplace_back(type_name);
}
if (const char* type_name = type.GetTypeName().GetCString()) {
if (names.back() != type_name) {
names.emplace_back(type_name);
}
}
type = type.GetTypedefedType();
}
// If the original input type is a fixed-size integer or enum type, append a
// matching c++ fixed-width integer type name to the list to simplify integer
// handling.
bool is_signed = false;
if (original_type.IsIntegerOrEnumerationType(is_signed)) {
if (original_type.IsEnumerationType(is_signed)) {
original_type = original_type.GetEnumerationIntegerType();
}
if (auto byte_size = original_type.GetByteSize(nullptr)) {
switch (*byte_size) {
case 8:
names.push_back(is_signed ? "int64_t" : "uint64_t");
break;
case 4:
names.push_back(is_signed ? "int32_t" : "uint32_t");
break;
case 2:
names.push_back(is_signed ? "int16_t" : "uint16_t");
break;
case 1:
names.push_back(is_signed ? "int8_t" : "uint8_t");
break;
}
}
}
return names;
}
} // namespace
GetInlinedFunctionRangesResponse ApiContext::GetInlinedFunctionRanges(
std::string raw_module_id,
int32_t code_offset) {
GetInlinedFunctionRangesResponse response;
auto module = FindModule(raw_module_id);
if (!module) {
response.SetError(MakeNotFoundError(raw_module_id));
return response;
}
llvm::SmallVector<RawLocationRange, 1> locations = ToRawLocationRanges(
module->GetInlineFunctionAddressRanges(code_offset), raw_module_id);
response.SetRawLocationRanges({locations.begin(), locations.end()});
return response;
}
GetInlinedCalleesRangesResponse ApiContext::GetInlinedCalleesRanges(
std::string raw_module_id,
int32_t code_offset) {
GetInlinedCalleesRangesResponse response;
auto module = FindModule(raw_module_id);
if (!module) {
response.SetError(MakeNotFoundError(raw_module_id));
return response;
}
llvm::SmallVector<RawLocationRange, 1> locations = ToRawLocationRanges(
module->GetChildInlineFunctionAddressRanges(code_offset), raw_module_id);
response.SetRawLocationRanges({locations.begin(), locations.end()});
return response;
}
GetMappedLinesResponse ApiContext::GetMappedLines(std::string raw_module_id,
std::string source_file_url) {
GetMappedLinesResponse response;
auto module = FindModule(raw_module_id);
if (!module) {
response.SetError(MakeNotFoundError(raw_module_id));
return response;
}
std::vector<int32_t> lines = module->GetMappedLines(source_file_url);
for (int32_t& line : lines) {
--line;
}
response.SetMappedLines(std::move(lines));
return response;
}
std::shared_ptr<WasmModule> ApiContext::AddModule(llvm::StringRef id,
llvm::StringRef path) {
if (!llvm::sys::fs::exists(path)) {
LLVM_DEBUG(llvm::dbgs()
<< "Module '" << id << "' at " << path << " not found\n");
return nullptr;
}
auto maybe_module = WasmModule::CreateFromFile(path);
if (!maybe_module) {
llvm::errs() << llvm::toString(maybe_module.takeError());
return nullptr;
}
const auto& module =
modules_.insert({id, std::move(*maybe_module)}).first->second;
if (module) {
LLVM_DEBUG(llvm::dbgs() << "Loaded module " << id << " with "
<< module->GetSourceScripts().sources.size()
<< " source files\n");
}
return module;
}
std::shared_ptr<WasmModule> ApiContext::FindModule(llvm::StringRef id) const {
auto it = modules_.find(id);
if (it == modules_.end()) {
return nullptr;
}
return it->second;
}
void ApiContext::DeleteModule(llvm::StringRef id) {
modules_.erase(id);
}
api::TypeInfo ApiContext::GetApiTypeInfo(
lldb_private::CompilerType type,
const llvm::SmallVectorImpl<SubObjectInfo>& member_info) {
std::vector<api::Enumerator> enumerators;
type.ForEachEnumerator([&](const lldb_private::CompilerType& type,
lldb_private::ConstString label,
const llvm::APSInt& value) {
enumerators.push_back(api::Enumerator()
.SetName(label.AsCString(""))
.SetValue(value.getExtValue())
.SetTypeId(GetTypeId(type)));
return true;
});
auto size = type.GetByteSize(nullptr);
uint64_t array_size = 0;
size_t alignment = type.GetTypeBitAlign(nullptr).value_or(0) / 8;
bool has_elements = type.IsPointerOrReferenceType() ||
type.IsArrayType(nullptr, &array_size, nullptr) ||
type.IsVectorType(nullptr, &array_size);
bool can_expand = type.IsAggregateType() || has_elements;
bool is_signed = false;
bool has_value =
type.IsScalarType() || type.IsEnumerationType(is_signed) || has_elements;
std::vector<api::FieldInfo> members;
for (const auto& member : member_info) {
members.push_back(FieldInfo()
.SetName(member.MemberName().str())
.SetOffset(member.OffsetInParent())
.SetTypeId(GetTypeId(member.Type())));
}
return api::TypeInfo()
.SetCanExpand(can_expand)
.SetArraySize(array_size)
.SetHasValue(has_value)
.SetTypeId(GetTypeId(type))
.SetTypeNames(GetTypeNames(type))
.SetSize(size ? *size : 0)
.SetIsPointer(type.IsPointerOrReferenceType(nullptr))
.SetAlignment(alignment)
.SetMembers(std::move(members))
.SetEnumerators(std::move(enumerators));
}
llvm::Expected<std::vector<api::TypeInfo>> ApiContext::GetApiTypeInfos(
lldb_private::CompilerType type,
int32_t required_type_depth) {
std::deque<std::pair<lldb_private::CompilerType, int32_t>> queue{{type, 0}};
std::vector<api::TypeInfo> type_infos;
llvm::DenseSet<lldb::opaque_compiler_type_t> visited_types;
while (!queue.empty()) {
lldb_private::CompilerType type;
int32_t depth;
std::tie(type, depth) = queue.front();
queue.pop_front();
if (required_type_depth > 0 && depth > required_type_depth) {
continue;
}
if (!visited_types.insert(type.GetOpaqueQualType()).second) {
continue;
}
auto member_info = SubObjectInfo::GetMembers(type);
for (const auto& member : member_info) {
if (!visited_types.contains(member.Type().GetOpaqueQualType())) {
queue.push_back({member.Type(), depth + 1});
}
}
type_infos.push_back(GetApiTypeInfo(type, member_info));
}
return type_infos;
}
std::string ApiContext::GetTypeId(lldb_private::CompilerType type) {
std::string key =
std::to_string(reinterpret_cast<uintptr_t>(type.GetOpaqueQualType()));
types_[key] = type;
return key;
}
llvm::Optional<lldb_private::CompilerType> ApiContext::GetTypeFromId(
llvm::StringRef type_id) {
auto it = types_.find(type_id.str());
if (it == types_.end()) {
return llvm::None;
}
return it->getValue();
}
struct EvalVisitor {
ApiContext& context;
lldb_private::CompilerType type;
llvm::Optional<size_t> address;
EvalVisitor(ApiContext& context,
lldb_private::CompilerType type,
llvm::Optional<size_t> address)
: context(context), type(type), address(address) {}
api::EvaluateExpressionResponse MakeResponse() {
auto member_type_infos = context.GetApiTypeInfos(type, 0);
if (!member_type_infos) {
return api::EvaluateExpressionResponse().SetError(
MakeEvalError(member_type_infos.takeError()));
}
api::TypeInfo root_type = member_type_infos->front();
return api::EvaluateExpressionResponse()
.SetTypeInfos(std::move(*member_type_infos))
.SetRoot(root_type)
.SetMemoryAddress(address ? llvm::Optional<int32_t>(*address)
: llvm::None);
}
// Most types are stringified in the JS wrapper, including most primitive
// types as implemented below. There's two exceptions that we're handling
// here because they're impossible/hard to do in JS, which is enum labels and
// floating point values. Stringifying floats and doubles in JS is subtly
// different than in C++ (or rather lldb), in particular for floats which
// have different level of precision than JS's number.
template <typename T>
api::EvaluateExpressionResponse MakeResponse(T v) {
auto begin = reinterpret_cast<unsigned char*>(&v);
auto end = begin + sizeof(T);
std::vector<int32_t> data = {begin, end};
bool is_signed = false;
llvm::Optional<std::string> enum_label;
if (type.IsEnumerationType(is_signed)) {
type.ForEachEnumerator([&enum_label, v](auto t, auto label, auto value) {
if (value == v) {
enum_label = std::string(label.GetStringRef());
return false;
}
return true;
});
}
return MakeResponse().SetData(data).SetDisplayValue(std::move(enum_label));
}
auto operator()(std::monostate v) { return MakeResponse(); }
auto operator()(bool v) { return MakeResponse(v); }
auto operator()(int8_t v) { return MakeResponse(v); }
auto operator()(uint8_t v) { return MakeResponse(v); }
auto operator()(int16_t v) { return MakeResponse(v); }
auto operator()(uint16_t v) { return MakeResponse(v); }
auto operator()(int32_t v) { return MakeResponse(v); }
auto operator()(uint32_t v) { return MakeResponse(v); }
auto operator()(int64_t v) { return MakeResponse(v); }
auto operator()(uint64_t v) { return MakeResponse(v); }
auto operator()(float v) {
llvm::APFloat f(v);
llvm::SmallVector<char, 256> str;
f.toString(str, 0, 6);
return MakeResponse(v).SetDisplayValue(std::string(str.data(), str.size()));
}
auto operator()(double v) {
llvm::APFloat f(v);
llvm::SmallVector<char, 256> str;
f.toString(str, 0, 6);
return MakeResponse(v).SetDisplayValue(std::string(str.data(), str.size()));
}
auto operator()(void* v) {
return MakeResponse().SetLocation(reinterpret_cast<uint64_t>(v));
}
auto operator()(std::nullptr_t v) { return MakeResponse<intptr_t>(0); }
};
llvm::Expected<size_t> DebuggerProxy::ReadMemory(lldb::addr_t address,
void* buffer,
size_t size) const {
return proxy_.call<size_t>("readMemory", static_cast<size_t>(address),
reinterpret_cast<size_t>(buffer), size);
}
static llvm::Expected<DebuggerProxy::WasmValue> readWasmValue(
const emscripten::val& value) {
std::string type = value["type"].as<std::string>();
if (type == "i32") {
return DebuggerProxy::WasmValue{llvm::wasm::ValType::I32,
value["value"].as<int32_t>()};
}
if (type == "i64") {
return DebuggerProxy::WasmValue{llvm::wasm::ValType::I64,
value["value"].as<int64_t>()};
}
if (type == "f32") {
return DebuggerProxy::WasmValue{llvm::wasm::ValType::F32,
value["value"].as<float>()};
}
if (type == "f64") {
return DebuggerProxy::WasmValue{llvm::wasm::ValType::F64,
value["value"].as<double>()};
}
if (type == "reftype") {
// Only a scalar value can cross the DWARF interpreter, so we
// encode the value into a 64-bit integer.
int64_t encodedValue = value["index"].as<uint32_t>();
std::string valueClass = value["valueClass"].as<std::string>();
if (valueClass == "local") {
encodedValue |= (int64_t)1 << 32;
} else if (valueClass == "operand") {
encodedValue |= (int64_t)2 << 32;
} else if (valueClass != "global") {
llvm::errs() << "Got value class " << encodedValue << "\n";
llvm_unreachable("Unhandled value class");
}
return DebuggerProxy::WasmValue{llvm::wasm::ValType::I64, encodedValue};
}
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"Invalid value type %s", type.c_str());
}
llvm::Expected<DebuggerProxy::WasmValue> DebuggerProxy::GetGlobal(
size_t index) const {
return readWasmValue(proxy_.call<emscripten::val>("getGlobal", index));
}
llvm::Expected<DebuggerProxy::WasmValue> DebuggerProxy::GetLocal(
size_t index) const {
return readWasmValue(proxy_.call<emscripten::val>("getLocal", index));
}
llvm::Expected<DebuggerProxy::WasmValue> DebuggerProxy::GetOperand(
size_t index) const {
return readWasmValue(proxy_.call<emscripten::val>("getOperand", index));
}
api::EvaluateExpressionResponse ApiContext::EvaluateExpression(
RawLocation location,
std::string expression,
emscripten::val debug_proxy) {
std::string raw_module_id = location.GetRawModuleId();
auto module = FindModule(raw_module_id);
if (!module) {
return api::EvaluateExpressionResponse().SetError(
MakeNotFoundError(raw_module_id));
}
auto result = module->InterpretExpression(
location.GetCodeOffset(), location.GetInlineFrameIndex(), expression,
DebuggerProxy{debug_proxy});
if (!result) {
return api::EvaluateExpressionResponse().SetError(
MakeError(Error::Code::kEvalError, result.takeError()));
}
return std::visit(EvalVisitor(*this, result->type, result->address),
result->value);
}
} // namespace api
} // namespace symbols_backend
#ifndef NDEBUG
namespace {
struct Logging {
Logging() {
lldb_private::Log::Initialize();
lldb_private::Log::ListAllLogChannels(llvm::errs());
auto stream = std::make_shared<llvm::raw_fd_ostream>(2, false, true);
lldb_private::Log::EnableLogChannel(stream, 0, "lldb", {"default"},
llvm::errs());
}
};
static Logging l;
} // namespace
#endif