diff --git a/lldb/include/lldb/Symbol/PostfixExpression.h b/lldb/include/lldb/Symbol/PostfixExpression.h index a5c2fc573669..2cf9dee6859c 100644 --- a/lldb/include/lldb/Symbol/PostfixExpression.h +++ b/lldb/include/lldb/Symbol/PostfixExpression.h @@ -1,209 +1,226 @@ //===-- PostfixExpression.h -------------------------------------*- 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 // //===----------------------------------------------------------------------===// // // This file implements support for postfix expressions found in several symbol // file formats, and their conversion to DWARF. // //===----------------------------------------------------------------------===// #ifndef LLDB_SYMBOL_POSTFIXEXPRESSION_H #define LLDB_SYMBOL_POSTFIXEXPRESSION_H #include "llvm/ADT/StringRef.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/Casting.h" namespace lldb_private { class Stream; namespace postfix { /// The base class for all nodes in the parsed postfix tree. class Node { public: enum Kind { BinaryOp, + InitialValue, Integer, Register, Symbol, UnaryOp, }; protected: Node(Kind kind) : m_kind(kind) {} public: Kind GetKind() const { return m_kind; } private: Kind m_kind; }; /// A node representing a binary expression. class BinaryOpNode : public Node { public: enum OpType { Align, // alignDown(a, b) Minus, // a - b Plus, // a + b }; BinaryOpNode(OpType op_type, Node &left, Node &right) : Node(BinaryOp), m_op_type(op_type), m_left(&left), m_right(&right) {} OpType GetOpType() const { return m_op_type; } const Node *Left() const { return m_left; } Node *&Left() { return m_left; } const Node *Right() const { return m_right; } Node *&Right() { return m_right; } static bool classof(const Node *node) { return node->GetKind() == BinaryOp; } private: OpType m_op_type; Node *m_left; Node *m_right; }; +/// A node representing the canonical frame address. +class InitialValueNode: public Node { +public: + InitialValueNode() : Node(InitialValue) {} + + static bool classof(const Node *node) { + return node->GetKind() == InitialValue; + } +}; + /// A node representing an integer literal. class IntegerNode : public Node { public: IntegerNode(uint32_t value) : Node(Integer), m_value(value) {} uint32_t GetValue() const { return m_value; } static bool classof(const Node *node) { return node->GetKind() == Integer; } private: uint32_t m_value; }; /// A node representing the value of a register with the given register number. /// The register kind (RegisterKind enum) used for the specifying the register /// number is implicit and assumed to be the same for all Register nodes in a /// given tree. class RegisterNode : public Node { public: RegisterNode(uint32_t reg_num) : Node(Register), m_reg_num(reg_num) {} uint32_t GetRegNum() const { return m_reg_num; } static bool classof(const Node *node) { return node->GetKind() == Register; } private: uint32_t m_reg_num; }; /// A node representing a symbolic reference to a named entity. This may be a /// register, which hasn't yet been resolved to a RegisterNode. class SymbolNode : public Node { public: SymbolNode(llvm::StringRef name) : Node(Symbol), m_name(name) {} llvm::StringRef GetName() const { return m_name; } static bool classof(const Node *node) { return node->GetKind() == Symbol; } private: llvm::StringRef m_name; }; /// A node representing a unary operation. class UnaryOpNode : public Node { public: enum OpType { Deref, // *a }; UnaryOpNode(OpType op_type, Node &operand) : Node(UnaryOp), m_op_type(op_type), m_operand(&operand) {} OpType GetOpType() const { return m_op_type; } const Node *Operand() const { return m_operand; } Node *&Operand() { return m_operand; } static bool classof(const Node *node) { return node->GetKind() == UnaryOp; } private: OpType m_op_type; Node *m_operand; }; /// A template class implementing a visitor pattern, but with a couple of /// twists: /// - It uses type switch instead of virtual double dispatch. This allows the // node classes to be vtable-free and trivially destructible. /// - The Visit functions get an extra Node *& parameter, which refers to the /// child pointer of the parent of the node we are currently visiting. This /// allows mutating algorithms, which replace the currently visited node with /// a different one. /// - The class is templatized on the return type of the Visit functions, which /// means it's possible to return values from them. template class Visitor { protected: virtual ~Visitor() = default; virtual ResultT Visit(BinaryOpNode &binary, Node *&ref) = 0; + virtual ResultT Visit(InitialValueNode &val, Node *&ref) = 0; virtual ResultT Visit(IntegerNode &integer, Node *&) = 0; virtual ResultT Visit(RegisterNode ®, Node *&) = 0; virtual ResultT Visit(SymbolNode &symbol, Node *&ref) = 0; virtual ResultT Visit(UnaryOpNode &unary, Node *&ref) = 0; /// Invoke the correct Visit function based on the dynamic type of the given /// node. ResultT Dispatch(Node *&node) { switch (node->GetKind()) { case Node::BinaryOp: return Visit(llvm::cast(*node), node); + case Node::InitialValue: + return Visit(llvm::cast(*node), node); case Node::Integer: return Visit(llvm::cast(*node), node); case Node::Register: return Visit(llvm::cast(*node), node); case Node::Symbol: return Visit(llvm::cast(*node), node); case Node::UnaryOp: return Visit(llvm::cast(*node), node); } llvm_unreachable("Fully covered switch!"); } }; /// A utility function for "resolving" SymbolNodes. It traverses a tree and /// calls the callback function for all SymbolNodes it encountered. The /// replacement function should return the node it wished to replace the current /// SymbolNode with (this can also be the original node), or nullptr in case of /// an error. The nodes returned by the callback are inspected and replaced /// recursively, *except* for the case when the function returns the exact same /// node as the input one. It returns true if all SymbolNodes were replaced /// successfully. bool ResolveSymbols(Node *&node, llvm::function_ref replacer); template inline T *MakeNode(llvm::BumpPtrAllocator &alloc, Args &&... args) { static_assert(std::is_trivially_destructible::value, "This object will not be destroyed!"); return new (alloc.Allocate()) T(std::forward(args)...); } /// Parse the given postfix expression. The parsed nodes are placed into the /// provided allocator. Node *Parse(llvm::StringRef expr, llvm::BumpPtrAllocator &alloc); /// Serialize the given expression tree as DWARF. The result is written into the -/// given stream. The AST should not contain any SymbolNodes. +/// given stream. The AST should not contain any SymbolNodes. If the expression +/// contains InitialValueNodes, the generated expression will assume that their +/// value will be provided as the top value of the initial evaluation stack (as +/// is the case with the CFA value in register eh_unwind rules). void ToDWARF(Node &node, Stream &stream); } // namespace postfix } // namespace lldb_private #endif // LLDB_SYMBOL_POSTFIXEXPRESSION_H diff --git a/lldb/source/Symbol/PostfixExpression.cpp b/lldb/source/Symbol/PostfixExpression.cpp index 8be242a850f5..df51288ec7b1 100644 --- a/lldb/source/Symbol/PostfixExpression.cpp +++ b/lldb/source/Symbol/PostfixExpression.cpp @@ -1,202 +1,227 @@ //===-- PostfixExpression.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 // //===----------------------------------------------------------------------===// // // This file implements support for postfix expressions found in several symbol // file formats, and their conversion to DWARF. // //===----------------------------------------------------------------------===// #include "lldb/Symbol/PostfixExpression.h" #include "lldb/Core/dwarf.h" #include "lldb/Utility/Stream.h" #include "llvm/ADT/StringExtras.h" using namespace lldb_private; using namespace lldb_private::postfix; static llvm::Optional GetBinaryOpType(llvm::StringRef token) { if (token.size() != 1) return llvm::None; switch (token[0]) { case '@': return BinaryOpNode::Align; case '-': return BinaryOpNode::Minus; case '+': return BinaryOpNode::Plus; } return llvm::None; } static llvm::Optional GetUnaryOpType(llvm::StringRef token) { if (token == "^") return UnaryOpNode::Deref; return llvm::None; } Node *postfix::Parse(llvm::StringRef expr, llvm::BumpPtrAllocator &alloc) { llvm::SmallVector stack; llvm::StringRef token; while (std::tie(token, expr) = getToken(expr), !token.empty()) { if (auto op_type = GetBinaryOpType(token)) { // token is binary operator if (stack.size() < 2) return nullptr; Node *right = stack.pop_back_val(); Node *left = stack.pop_back_val(); stack.push_back(MakeNode(alloc, *op_type, *left, *right)); continue; } if (auto op_type = GetUnaryOpType(token)) { // token is unary operator if (stack.empty()) return nullptr; Node *operand = stack.pop_back_val(); stack.push_back(MakeNode(alloc, *op_type, *operand)); continue; } uint32_t value; if (to_integer(token, value, 10)) { // token is integer literal stack.push_back(MakeNode(alloc, value)); continue; } stack.push_back(MakeNode(alloc, token)); } if (stack.size() != 1) return nullptr; return stack.back(); } namespace { class SymbolResolver : public Visitor { public: SymbolResolver(llvm::function_ref replacer) : m_replacer(replacer) {} using Visitor::Dispatch; private: bool Visit(BinaryOpNode &binary, Node *&) override { return Dispatch(binary.Left()) && Dispatch(binary.Right()); } - bool Visit(IntegerNode &integer, Node *&) override { return true; } - bool Visit(RegisterNode ®, Node *&) override { return true; } + bool Visit(InitialValueNode &, Node *&) override { return true; } + bool Visit(IntegerNode &, Node *&) override { return true; } + bool Visit(RegisterNode &, Node *&) override { return true; } bool Visit(SymbolNode &symbol, Node *&ref) override { if (Node *replacement = m_replacer(symbol)) { ref = replacement; if (replacement != &symbol) return Dispatch(ref); return true; } return false; } bool Visit(UnaryOpNode &unary, Node *&) override { return Dispatch(unary.Operand()); } llvm::function_ref m_replacer; }; class DWARFCodegen : public Visitor<> { public: DWARFCodegen(Stream &stream) : m_out_stream(stream) {} using Visitor<>::Dispatch; private: void Visit(BinaryOpNode &binary, Node *&); + void Visit(InitialValueNode &val, Node *&); + void Visit(IntegerNode &integer, Node *&) { m_out_stream.PutHex8(DW_OP_constu); m_out_stream.PutULEB128(integer.GetValue()); + ++m_stack_depth; } void Visit(RegisterNode ®, Node *&); void Visit(SymbolNode &symbol, Node *&) { llvm_unreachable("Symbols should have been resolved by now!"); } void Visit(UnaryOpNode &unary, Node *&); Stream &m_out_stream; + + /// The number keeping track of the evaluation stack depth at any given + /// moment. Used for implementing InitialValueNodes. We start with + /// m_stack_depth = 1, assuming that the initial value is already on the + /// stack. This initial value will be the value of all InitialValueNodes. If + /// the expression does not contain InitialValueNodes, then m_stack_depth is + /// not used, and the generated expression will run correctly even without an + /// initial value. + size_t m_stack_depth = 1; }; } // namespace void DWARFCodegen::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; } + --m_stack_depth; // Two pops, one push. +} + +void DWARFCodegen::Visit(InitialValueNode &, Node *&) { + // We never go below the initial stack, so we can pick the initial value from + // the bottom of the stack at any moment. + assert(m_stack_depth >= 1); + m_out_stream.PutHex8(DW_OP_pick); + m_out_stream.PutHex8(m_stack_depth - 1); + ++m_stack_depth; } void DWARFCodegen::Visit(RegisterNode ®, Node *&) { uint32_t reg_num = reg.GetRegNum(); assert(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); + ++m_stack_depth; } void DWARFCodegen::Visit(UnaryOpNode &unary, Node *&) { Dispatch(unary.Operand()); switch (unary.GetOpType()) { case UnaryOpNode::Deref: m_out_stream.PutHex8(DW_OP_deref); break; } + // Stack depth unchanged. } bool postfix::ResolveSymbols( Node *&node, llvm::function_ref replacer) { return SymbolResolver(replacer).Dispatch(node); } void postfix::ToDWARF(Node &node, Stream &stream) { Node *ptr = &node; DWARFCodegen(stream).Dispatch(ptr); } diff --git a/lldb/unittests/Symbol/PostfixExpressionTest.cpp b/lldb/unittests/Symbol/PostfixExpressionTest.cpp index f919a944b0e0..b7fef83af7fc 100644 --- a/lldb/unittests/Symbol/PostfixExpressionTest.cpp +++ b/lldb/unittests/Symbol/PostfixExpressionTest.cpp @@ -1,152 +1,168 @@ //===-- PostfixExpressionTest.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 "lldb/Symbol/PostfixExpression.h" #include "lldb/Expression/DWARFExpression.h" #include "lldb/Utility/DataExtractor.h" #include "lldb/Utility/StreamString.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/raw_ostream.h" #include "gtest/gtest.h" using namespace lldb_private; using namespace lldb_private::postfix; static std::string ToString(BinaryOpNode::OpType type) { switch (type) { case BinaryOpNode::Align: return "@"; case BinaryOpNode::Minus: return "-"; case BinaryOpNode::Plus: return "+"; } llvm_unreachable("Fully covered switch!"); } static std::string ToString(UnaryOpNode::OpType type) { switch (type) { case UnaryOpNode::Deref: return "^"; } llvm_unreachable("Fully covered switch!"); } struct ASTPrinter : public Visitor { protected: std::string Visit(BinaryOpNode &binary, Node *&) override { return llvm::formatv("{0}({1}, {2})", ToString(binary.GetOpType()), Dispatch(binary.Left()), Dispatch(binary.Right())); } + std::string Visit(InitialValueNode &, Node *&) override { return "InitialValue"; } + std::string Visit(IntegerNode &integer, Node *&) override { return llvm::formatv("int({0})", integer.GetValue()); } std::string Visit(RegisterNode ®, Node *&) override { return llvm::formatv("reg({0})", reg.GetRegNum()); } std::string Visit(SymbolNode &symbol, Node *&) override { return symbol.GetName(); } std::string Visit(UnaryOpNode &unary, Node *&) override { return llvm::formatv("{0}({1})", ToString(unary.GetOpType()), Dispatch(unary.Operand())); } public: static std::string Print(Node *node) { if (node) return ASTPrinter().Dispatch(node); return "nullptr"; } }; static std::string ParseAndStringify(llvm::StringRef expr) { llvm::BumpPtrAllocator alloc; return ASTPrinter::Print(Parse(expr, alloc)); } TEST(PostfixExpression, Parse) { EXPECT_EQ("int(47)", ParseAndStringify("47")); EXPECT_EQ("$foo", ParseAndStringify("$foo")); EXPECT_EQ("+(int(1), int(2))", ParseAndStringify("1 2 +")); EXPECT_EQ("-(int(1), int(2))", ParseAndStringify("1 2 -")); EXPECT_EQ("@(int(1), int(2))", ParseAndStringify("1 2 @")); EXPECT_EQ("+(int(1), +(int(2), int(3)))", ParseAndStringify("1 2 3 + +")); EXPECT_EQ("+(+(int(1), int(2)), int(3))", ParseAndStringify("1 2 + 3 +")); EXPECT_EQ("^(int(1))", ParseAndStringify("1 ^")); EXPECT_EQ("^(^(int(1)))", ParseAndStringify("1 ^ ^")); EXPECT_EQ("^(+(int(1), ^(int(2))))", ParseAndStringify("1 2 ^ + ^")); EXPECT_EQ("-($foo, int(47))", ParseAndStringify("$foo 47 -")); EXPECT_EQ("nullptr", ParseAndStringify("+")); EXPECT_EQ("nullptr", ParseAndStringify("^")); EXPECT_EQ("nullptr", ParseAndStringify("1 +")); EXPECT_EQ("nullptr", ParseAndStringify("1 2 ^")); EXPECT_EQ("nullptr", ParseAndStringify("1 2 3 +")); EXPECT_EQ("nullptr", ParseAndStringify("^ 1")); EXPECT_EQ("nullptr", ParseAndStringify("+ 1 2")); EXPECT_EQ("nullptr", ParseAndStringify("1 + 2")); EXPECT_EQ("nullptr", ParseAndStringify("1 2")); EXPECT_EQ("nullptr", ParseAndStringify("")); } static std::string ParseAndGenerateDWARF(llvm::StringRef expr) { llvm::BumpPtrAllocator alloc; Node *ast = Parse(expr, alloc); if (!ast) return "Parse failed."; if (!ResolveSymbols(ast, [&](SymbolNode &symbol) -> Node * { + if (symbol.GetName() == "INIT") + return MakeNode(alloc); + uint32_t num; if (to_integer(symbol.GetName().drop_front(), num)) return MakeNode(alloc, num); return nullptr; })) { return "Resolution failed."; } const size_t addr_size = 4; StreamString dwarf(Stream::eBinary, addr_size, lldb::eByteOrderLittle); ToDWARF(*ast, dwarf); // print dwarf expression to comparable textual representation DataExtractor extractor(dwarf.GetData(), dwarf.GetSize(), lldb::eByteOrderLittle, addr_size); StreamString result; if (!DWARFExpression::PrintDWARFExpression(result, extractor, addr_size, /*dwarf_ref_size*/ 4, /*location_expression*/ false)) { return "DWARF printing failed."; } return result.GetString(); } TEST(PostfixExpression, ToDWARF) { EXPECT_EQ("DW_OP_constu 0x0", ParseAndGenerateDWARF("0")); EXPECT_EQ("DW_OP_breg1 +0", ParseAndGenerateDWARF("R1")); EXPECT_EQ("DW_OP_bregx 65 0", ParseAndGenerateDWARF("R65")); + EXPECT_EQ("DW_OP_pick 0x00", ParseAndGenerateDWARF("INIT")); + + EXPECT_EQ("DW_OP_pick 0x00, DW_OP_pick 0x01, DW_OP_plus ", + ParseAndGenerateDWARF("INIT INIT +")); + + EXPECT_EQ("DW_OP_breg1 +0, DW_OP_pick 0x01, DW_OP_plus ", + ParseAndGenerateDWARF("R1 INIT +")); + + EXPECT_EQ("DW_OP_constu 0x1, DW_OP_pick 0x01, DW_OP_deref , DW_OP_plus ", + ParseAndGenerateDWARF("1 INIT ^ +")); + EXPECT_EQ("DW_OP_constu 0x4, DW_OP_constu 0x5, DW_OP_plus ", ParseAndGenerateDWARF("4 5 +")); EXPECT_EQ("DW_OP_constu 0x4, DW_OP_constu 0x5, DW_OP_minus ", ParseAndGenerateDWARF("4 5 -")); EXPECT_EQ("DW_OP_constu 0x4, DW_OP_deref ", ParseAndGenerateDWARF("4 ^")); EXPECT_EQ("DW_OP_breg6 +0, DW_OP_constu 0x80, DW_OP_lit1 " ", DW_OP_minus , DW_OP_not , DW_OP_and ", ParseAndGenerateDWARF("R6 128 @")); }