Summary: The NativePDB plugin contains code to convert "programs" describing the layout of function frames into dwarf (for easier interaction with the rest of lldb). This functionality is useful for the Breakpad plugin too, as it contains the same kind of expressions (because breakpad info is generated from pdb files). In this patch, I move the core classes of this code into a common place, where it can be used from both files. Previously, these were the details of the implementation, but here I am exposing them (instead of just a single "string->string" conversion function), as breakpad will need to use these in a slightly different way. The reason for that is that breakpad files generated from dwarf expressions use a slightly different syntax, although most of the core code can be reused with a bit of thought. This is also the reason why I am not moving the parsing or dwarf generation bits, as they will need to be generalized a bit before they're usable for both scenarios. This patch should be NFC, modulo renaming the moved entities to more neutral names. The reason I am moving this to the "Symbol" library, is because both customers will be "Symbol"Files, and also the unwinding code lives in the Symbol library. From a purely dependency standpoint this code will probably be standalone, and so it could be moved all the way to Utility, but that seems too low for this kind of functionality. Reviewers: jasonmolenda, amccarth, clayborg, JDevlieghere, aleksandr.urakov Subscribers: aprantl, markmentovai, lldb-commits Differential Revision: https://reviews.llvm.org/D60599 llvm-svn: 358976
378 lines
11 KiB
C++
378 lines
11 KiB
C++
//===-- PDBFPOProgramToDWARFExpression.cpp ----------------------*- 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 "PdbFPOProgramToDWARFExpression.h"
|
|
#include "CodeViewRegisterMapping.h"
|
|
|
|
#include "lldb/Core/StreamBuffer.h"
|
|
#include "lldb/Core/dwarf.h"
|
|
#include "lldb/Symbol/PostfixExpression.h"
|
|
#include "lldb/Utility/LLDBAssert.h"
|
|
#include "lldb/Utility/Stream.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/DebugInfo/CodeView/CodeView.h"
|
|
#include "llvm/DebugInfo/CodeView/EnumTables.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
using namespace lldb_private::postfix;
|
|
|
|
namespace {
|
|
|
|
class NodeAllocator {
|
|
public:
|
|
template <typename T, typename... Args> T *makeNode(Args &&... args) {
|
|
static_assert(std::is_trivially_destructible<T>::value,
|
|
"This object will not be destroyed!");
|
|
void *new_node_mem = m_alloc.Allocate(sizeof(T), alignof(T));
|
|
return new (new_node_mem) T(std::forward<Args>(args)...);
|
|
}
|
|
|
|
private:
|
|
llvm::BumpPtrAllocator m_alloc;
|
|
};
|
|
|
|
class FPOProgramASTVisitorMergeDependent : public Visitor<> {
|
|
public:
|
|
void Visit(BinaryOpNode &binary, Node *&) override {
|
|
Dispatch(binary.Left());
|
|
Dispatch(binary.Right());
|
|
}
|
|
|
|
void Visit(UnaryOpNode &unary, Node *&) override {
|
|
Dispatch(unary.Operand());
|
|
}
|
|
|
|
void Visit(RegisterNode &, Node *&) override {}
|
|
void Visit(IntegerNode &, Node *&) override {}
|
|
void Visit(SymbolNode &symbol, Node *&ref) override;
|
|
|
|
static void
|
|
Merge(const llvm::DenseMap<llvm::StringRef, Node *> &dependent_programs,
|
|
Node *&ast) {
|
|
FPOProgramASTVisitorMergeDependent(dependent_programs).Dispatch(ast);
|
|
}
|
|
|
|
private:
|
|
FPOProgramASTVisitorMergeDependent(
|
|
const llvm::DenseMap<llvm::StringRef, Node *> &dependent_programs)
|
|
: m_dependent_programs(dependent_programs) {}
|
|
|
|
const llvm::DenseMap<llvm::StringRef, Node *> &m_dependent_programs;
|
|
};
|
|
|
|
void FPOProgramASTVisitorMergeDependent::Visit(SymbolNode &symbol, Node *&ref) {
|
|
auto it = m_dependent_programs.find(symbol.GetName());
|
|
if (it == m_dependent_programs.end())
|
|
return;
|
|
|
|
ref = it->second;
|
|
Dispatch(ref);
|
|
}
|
|
|
|
class FPOProgramASTVisitorResolveRegisterRefs : public Visitor<bool> {
|
|
public:
|
|
static bool
|
|
Resolve(const llvm::DenseMap<llvm::StringRef, Node *> &dependent_programs,
|
|
llvm::Triple::ArchType arch_type, NodeAllocator &alloc, Node *&ast) {
|
|
return FPOProgramASTVisitorResolveRegisterRefs(dependent_programs,
|
|
arch_type, alloc)
|
|
.Dispatch(ast);
|
|
}
|
|
|
|
bool Visit(BinaryOpNode &binary, Node *&) override {
|
|
return Dispatch(binary.Left()) && Dispatch(binary.Right());
|
|
}
|
|
|
|
bool Visit(UnaryOpNode &unary, Node *&) override {
|
|
return Dispatch(unary.Operand());
|
|
}
|
|
|
|
bool Visit(RegisterNode &, Node *&) override { return true; }
|
|
|
|
bool Visit(IntegerNode &, Node *&) override { return true; }
|
|
|
|
bool Visit(SymbolNode &symbol, Node *&ref) override;
|
|
|
|
private:
|
|
FPOProgramASTVisitorResolveRegisterRefs(
|
|
const llvm::DenseMap<llvm::StringRef, Node *> &dependent_programs,
|
|
llvm::Triple::ArchType arch_type, NodeAllocator &alloc)
|
|
: m_dependent_programs(dependent_programs), m_arch_type(arch_type),
|
|
m_alloc(alloc) {}
|
|
|
|
const llvm::DenseMap<llvm::StringRef, Node *> &m_dependent_programs;
|
|
llvm::Triple::ArchType m_arch_type;
|
|
NodeAllocator &m_alloc;
|
|
};
|
|
|
|
static uint32_t ResolveLLDBRegisterNum(llvm::StringRef reg_name, llvm::Triple::ArchType arch_type) {
|
|
// lookup register name to get lldb register number
|
|
llvm::ArrayRef<llvm::EnumEntry<uint16_t>> register_names =
|
|
llvm::codeview::getRegisterNames();
|
|
auto it = llvm::find_if(
|
|
register_names,
|
|
[®_name](const llvm::EnumEntry<uint16_t> ®ister_entry) {
|
|
return reg_name.compare_lower(register_entry.Name) == 0;
|
|
});
|
|
|
|
if (it == register_names.end())
|
|
return LLDB_INVALID_REGNUM;
|
|
|
|
auto reg_id = static_cast<llvm::codeview::RegisterId>(it->Value);
|
|
return npdb::GetLLDBRegisterNumber(arch_type, reg_id);
|
|
}
|
|
|
|
bool FPOProgramASTVisitorResolveRegisterRefs::Visit(SymbolNode &symbol,
|
|
Node *&ref) {
|
|
// Look up register reference as lvalue in preceding assignments.
|
|
auto it = m_dependent_programs.find(symbol.GetName());
|
|
if (it != m_dependent_programs.end()) {
|
|
// Dependent programs are handled elsewhere.
|
|
return true;
|
|
}
|
|
|
|
uint32_t reg_num =
|
|
ResolveLLDBRegisterNum(symbol.GetName().drop_front(1), m_arch_type);
|
|
|
|
if (reg_num == LLDB_INVALID_REGNUM)
|
|
return false;
|
|
|
|
ref = m_alloc.makeNode<RegisterNode>(reg_num);
|
|
return true;
|
|
}
|
|
|
|
class FPOProgramASTVisitorDWARFCodegen : public Visitor<> {
|
|
public:
|
|
static void Emit(Stream &stream, Node *&ast) {
|
|
FPOProgramASTVisitorDWARFCodegen(stream).Dispatch(ast);
|
|
}
|
|
|
|
void Visit(RegisterNode ®, Node *&);
|
|
void Visit(BinaryOpNode &binary, Node *&);
|
|
void Visit(UnaryOpNode &unary, Node *&);
|
|
void Visit(SymbolNode &symbol, Node *&) {
|
|
llvm_unreachable("Symbols should have been resolved by now!");
|
|
}
|
|
void Visit(IntegerNode &integer, Node *&);
|
|
|
|
private:
|
|
FPOProgramASTVisitorDWARFCodegen(Stream &stream) : m_out_stream(stream) {}
|
|
|
|
Stream &m_out_stream;
|
|
};
|
|
|
|
void FPOProgramASTVisitorDWARFCodegen::Visit(RegisterNode ®, Node *&) {
|
|
uint32_t reg_num = reg.GetRegNum();
|
|
lldbassert(reg_num != LLDB_INVALID_REGNUM);
|
|
|
|
if (reg_num > 31) {
|
|
m_out_stream.PutHex8(DW_OP_bregx);
|
|
m_out_stream.PutULEB128(reg_num);
|
|
} else
|
|
m_out_stream.PutHex8(DW_OP_breg0 + reg_num);
|
|
|
|
m_out_stream.PutSLEB128(0);
|
|
}
|
|
|
|
void FPOProgramASTVisitorDWARFCodegen::Visit(IntegerNode &integer, Node *&) {
|
|
uint32_t value = integer.GetValue();
|
|
m_out_stream.PutHex8(DW_OP_constu);
|
|
m_out_stream.PutULEB128(value);
|
|
}
|
|
|
|
void FPOProgramASTVisitorDWARFCodegen::Visit(BinaryOpNode &binary, Node *&) {
|
|
Dispatch(binary.Left());
|
|
Dispatch(binary.Right());
|
|
|
|
switch (binary.GetOpType()) {
|
|
case BinaryOpNode::Plus:
|
|
m_out_stream.PutHex8(DW_OP_plus);
|
|
// NOTE: can be optimized by using DW_OP_plus_uconst opcpode
|
|
// if right child node is constant value
|
|
break;
|
|
case BinaryOpNode::Minus:
|
|
m_out_stream.PutHex8(DW_OP_minus);
|
|
break;
|
|
case BinaryOpNode::Align:
|
|
// emit align operator a @ b as
|
|
// a & ~(b - 1)
|
|
// NOTE: implicitly assuming that b is power of 2
|
|
m_out_stream.PutHex8(DW_OP_lit1);
|
|
m_out_stream.PutHex8(DW_OP_minus);
|
|
m_out_stream.PutHex8(DW_OP_not);
|
|
|
|
m_out_stream.PutHex8(DW_OP_and);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FPOProgramASTVisitorDWARFCodegen::Visit(UnaryOpNode &unary, Node *&) {
|
|
Dispatch(unary.Operand());
|
|
|
|
switch (unary.GetOpType()) {
|
|
case UnaryOpNode::Deref:
|
|
m_out_stream.PutHex8(DW_OP_deref);
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
static bool ParseFPOSingleAssignmentProgram(llvm::StringRef program,
|
|
NodeAllocator &alloc,
|
|
llvm::StringRef ®ister_name,
|
|
Node *&ast) {
|
|
llvm::SmallVector<llvm::StringRef, 16> tokens;
|
|
llvm::SplitString(program, tokens, " ");
|
|
|
|
if (tokens.empty())
|
|
return false;
|
|
|
|
llvm::SmallVector<Node *, 4> eval_stack;
|
|
|
|
llvm::DenseMap<llvm::StringRef, BinaryOpNode::OpType> ops_binary = {
|
|
{"+", BinaryOpNode::Plus},
|
|
{"-", BinaryOpNode::Minus},
|
|
{"@", BinaryOpNode::Align},
|
|
};
|
|
|
|
llvm::DenseMap<llvm::StringRef, UnaryOpNode::OpType> ops_unary = {
|
|
{"^", UnaryOpNode::Deref},
|
|
};
|
|
|
|
constexpr llvm::StringLiteral ra_search_keyword = ".raSearch";
|
|
|
|
// lvalue of assignment is always first token
|
|
// rvalue program goes next
|
|
for (size_t i = 1; i < tokens.size(); ++i) {
|
|
llvm::StringRef cur = tokens[i];
|
|
|
|
auto ops_binary_it = ops_binary.find(cur);
|
|
if (ops_binary_it != ops_binary.end()) {
|
|
// token is binary operator
|
|
if (eval_stack.size() < 2) {
|
|
return false;
|
|
}
|
|
Node *right = eval_stack.pop_back_val();
|
|
Node *left = eval_stack.pop_back_val();
|
|
Node *node =
|
|
alloc.makeNode<BinaryOpNode>(ops_binary_it->second, *left, *right);
|
|
eval_stack.push_back(node);
|
|
continue;
|
|
}
|
|
|
|
auto ops_unary_it = ops_unary.find(cur);
|
|
if (ops_unary_it != ops_unary.end()) {
|
|
// token is unary operator
|
|
if (eval_stack.empty()) {
|
|
return false;
|
|
}
|
|
Node *operand = eval_stack.pop_back_val();
|
|
Node *node = alloc.makeNode<UnaryOpNode>(ops_unary_it->second, *operand);
|
|
eval_stack.push_back(node);
|
|
continue;
|
|
}
|
|
|
|
if (cur.startswith("$")) {
|
|
eval_stack.push_back(alloc.makeNode<SymbolNode>(cur));
|
|
continue;
|
|
}
|
|
|
|
if (cur == ra_search_keyword) {
|
|
// TODO: .raSearch is unsupported
|
|
return false;
|
|
}
|
|
|
|
uint32_t value;
|
|
if (!cur.getAsInteger(10, value)) {
|
|
// token is integer literal
|
|
eval_stack.push_back(alloc.makeNode<IntegerNode>(value));
|
|
continue;
|
|
}
|
|
|
|
// unexpected token
|
|
return false;
|
|
}
|
|
|
|
if (eval_stack.size() != 1) {
|
|
return false;
|
|
}
|
|
|
|
register_name = tokens[0];
|
|
ast = eval_stack.pop_back_val();
|
|
|
|
return true;
|
|
}
|
|
|
|
static Node *ParseFPOProgram(llvm::StringRef program,
|
|
llvm::StringRef register_name,
|
|
llvm::Triple::ArchType arch_type,
|
|
NodeAllocator &alloc) {
|
|
llvm::DenseMap<llvm::StringRef, Node *> dependent_programs;
|
|
|
|
size_t cur = 0;
|
|
while (true) {
|
|
size_t assign_index = program.find('=', cur);
|
|
if (assign_index == llvm::StringRef::npos) {
|
|
llvm::StringRef tail = program.slice(cur, llvm::StringRef::npos);
|
|
if (!tail.trim().empty()) {
|
|
// missing assign operator
|
|
return nullptr;
|
|
}
|
|
break;
|
|
}
|
|
llvm::StringRef assignment_program = program.slice(cur, assign_index);
|
|
|
|
llvm::StringRef lvalue_name;
|
|
Node *rvalue_ast = nullptr;
|
|
if (!ParseFPOSingleAssignmentProgram(assignment_program, alloc, lvalue_name,
|
|
rvalue_ast)) {
|
|
return nullptr;
|
|
}
|
|
|
|
lldbassert(rvalue_ast);
|
|
|
|
// check & resolve assignment program
|
|
if (!FPOProgramASTVisitorResolveRegisterRefs::Resolve(
|
|
dependent_programs, arch_type, alloc, rvalue_ast))
|
|
return nullptr;
|
|
|
|
if (lvalue_name == register_name) {
|
|
// found target assignment program - no need to parse further
|
|
|
|
// emplace valid dependent subtrees to make target assignment independent
|
|
// from predecessors
|
|
FPOProgramASTVisitorMergeDependent::Merge(dependent_programs, rvalue_ast);
|
|
|
|
return rvalue_ast;
|
|
}
|
|
|
|
dependent_programs[lvalue_name] = rvalue_ast;
|
|
cur = assign_index + 1;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool lldb_private::npdb::TranslateFPOProgramToDWARFExpression(
|
|
llvm::StringRef program, llvm::StringRef register_name,
|
|
llvm::Triple::ArchType arch_type, Stream &stream) {
|
|
NodeAllocator node_alloc;
|
|
Node *target_program =
|
|
ParseFPOProgram(program, register_name, arch_type, node_alloc);
|
|
if (target_program == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
FPOProgramASTVisitorDWARFCodegen::Emit(stream, target_program);
|
|
return true;
|
|
}
|