diff --git a/README.md b/README.md index 0b8103b0..bd9418da 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ iwasm VM core - [Multiple modules as dependencies](./doc/multi_module.md), ref to [sample](samples/multi-module) - [Thread management and pthread library](./doc/pthread_library.md), ref to [sample](samples/multi-thread) - [Linux SGX (Intel Software Guard Extension) support](./doc/linux_sgx.md) +- [Source debugging](./doc/source_debugging.md) ### post-MVP features - [Non-trapping float-to-int conversions](https://github.com/WebAssembly/nontrapping-float-to-int-conversions) diff --git a/build-scripts/config_common.cmake b/build-scripts/config_common.cmake index 15d47a00..6fbdb82d 100644 --- a/build-scripts/config_common.cmake +++ b/build-scripts/config_common.cmake @@ -239,3 +239,9 @@ endif () if (WAMR_DISABLE_APP_ENTRY EQUAL 1) message (" WAMR application entry functions excluded") endif () +if (WAMR_BUILD_DEBUG_INTERP EQUAL 1) + message (" Debug Interpreter enabled") +endif () +if (WAMR_BUILD_DEBUG_AOT EQUAL 1) + message (" Debug AOT enabled") +endif () diff --git a/build-scripts/lldb-wasm.patch b/build-scripts/lldb-wasm.patch new file mode 100644 index 00000000..1570418b --- /dev/null +++ b/build-scripts/lldb-wasm.patch @@ -0,0 +1,5779 @@ +diff --git a/lldb/include/lldb/Core/Module.h b/lldb/include/lldb/Core/Module.h +index dd7100c4616c..97d70daadbdc 100644 +--- a/lldb/include/lldb/Core/Module.h ++++ b/lldb/include/lldb/Core/Module.h +@@ -41,6 +41,7 @@ + + namespace lldb_private { + class CompilerDeclContext; ++class DWARFEvaluatorFactory; + class Function; + class Log; + class ObjectFile; +@@ -859,6 +860,8 @@ public: + /// Update the ArchSpec to a more specific variant. + bool MergeArchitecture(const ArchSpec &arch_spec); + ++ DWARFEvaluatorFactory *GetDWARFExpressionEvaluatorFactory(); ++ + /// \class LookupInfo Module.h "lldb/Core/Module.h" + /// A class that encapsulates name lookup information. + /// +@@ -985,6 +988,8 @@ protected: + m_first_file_changed_log : 1; /// See if the module was modified after it + /// was initially opened. + ++ std::unique_ptr m_dwarf_evaluator_factory; ++ + /// Resolve a file or load virtual address. + /// + /// Tries to resolve \a vm_addr as a file address (if \a +diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h +index be91929c62e1..8d876fc1fa2f 100644 +--- a/lldb/include/lldb/Core/PluginManager.h ++++ b/lldb/include/lldb/Core/PluginManager.h +@@ -508,6 +508,17 @@ public: + static bool CreateSettingForStructuredDataPlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + ConstString description, bool is_global_property); ++ ++ // DWARFEvaluatorFactory ++ static bool ++ RegisterPlugin(ConstString name, const char *description, ++ DWARFEvaluatorFactoryCreateInstance create_callback); ++ ++ static bool ++ UnregisterPlugin(DWARFEvaluatorFactoryCreateInstance create_callback); ++ ++ static DWARFEvaluatorFactoryCreateInstance ++ GetDWARFEvaluatorFactoryCreateCallbackAtIndex(uint32_t idx); + }; + + } // namespace lldb_private +diff --git a/lldb/include/lldb/Expression/DWARFEvaluator.h b/lldb/include/lldb/Expression/DWARFEvaluator.h +new file mode 100644 +index 000000000000..6811cbeae3d3 +--- /dev/null ++++ b/lldb/include/lldb/Expression/DWARFEvaluator.h +@@ -0,0 +1,110 @@ ++//===-- DWARFEvaluator.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 ++// ++//===----------------------------------------------------------------------===// ++ ++#ifndef LLDB_EXPRESSION_DWARFEVALUATOR_H ++#define LLDB_EXPRESSION_DWARFEVALUATOR_H ++ ++#include "lldb/lldb-private.h" ++#include ++ ++namespace lldb_private { ++ ++class DWARFExpression; ++ ++/// \class DWARFEvaluator DWARFEvaluator.h ++/// "lldb/Expression/DWARFEvaluator.h" Evaluates DWARF opcodes. ++/// ++class DWARFEvaluator { ++public: ++ /// Crates a DWARF location expression evaluator ++ /// ++ /// \param[in] dwarf_expression ++ /// The DWARF expression to evaluate. ++ /// ++ /// \param[in] exe_ctx ++ /// The execution context in which to evaluate the location ++ /// expression. The location expression may access the target's ++ /// memory, especially if it comes from the expression parser. ++ /// ++ /// \param[in] reg_ctx ++ /// An optional parameter which provides a RegisterContext for use ++ /// when evaluating the expression (i.e. for fetching register values). ++ /// Normally this will come from the ExecutionContext's StackFrame but ++ /// in the case where an expression needs to be evaluated while building ++ /// the stack frame list, this short-cut is available. ++ /// ++ /// \param[in] initial_value_ptr ++ /// A value to put on top of the interpreter stack before evaluating ++ /// the expression, if the expression is parametrized. Can be NULL. ++ /// ++ /// \param[in] object_address_ptr ++ /// ++ DWARFEvaluator(const DWARFExpression &dwarf_expression, ++ ExecutionContext *exe_ctx, RegisterContext *reg_ctx, ++ const Value *initial_value_ptr, ++ const Value *object_address_ptr); ++ ++ /// DWARFEvaluator protocol. ++ /// \{ ++ ++ /// Evaluate the DWARF location expression ++ /// ++ /// \param[in] result ++ /// A value into which the result of evaluating the expression is ++ /// to be placed. ++ /// ++ /// \param[in] error_ptr ++ /// If non-NULL, used to report errors in expression evaluation. ++ /// ++ /// \return ++ /// True on success; false otherwise. If error_ptr is non-NULL, ++ /// details of the failure are provided through it. ++ virtual bool Evaluate(Value &result, Status *error_ptr); ++ ++ /// Evaluate the DWARF location expression with the opcodes specified. ++ /// ++ /// \param[in] opcodes ++ /// The DWARF opcodes to evaluate. ++ /// ++ /// \param[in] result ++ /// A value into which the result of evaluating the expression is ++ /// to be placed. ++ /// ++ /// \param[in] error_ptr ++ /// If non-NULL, used to report errors in expression evaluation. ++ /// ++ /// \return ++ /// True on success; false otherwise. If error_ptr is non-NULL, ++ /// details of the failure are provided through it. ++ virtual bool Evaluate(const DataExtractor &opcodes, Value &result, ++ Status *error_ptr); ++ ++ /// Evaluates a specific DWARF opcode in the context of a DWARF expression ++ virtual bool Evaluate(const uint8_t op, Process *process, StackFrame *frame, ++ std::vector &stack, const DataExtractor &opcodes, ++ lldb::offset_t &offset, Value &pieces, ++ uint64_t &op_piece_offset, Log *log, Status *error_ptr); ++ ++ /// \} ++ ++protected: ++ const DWARFExpression &m_dwarf_expression; ++ ExecutionContext *m_exe_ctx; ++ RegisterContext *m_reg_ctx; ++ const Value *m_initial_value_ptr; ++ const Value *m_object_address_ptr; ++ ++private: ++ DWARFEvaluator(const DWARFEvaluator &); ++ const DWARFEvaluator &operator=(const DWARFEvaluator &) = delete; ++ ++}; ++ ++} // namespace lldb_private ++ ++#endif // LLDB_EXPRESSION_DWARFEVALUATOR_H +diff --git a/lldb/include/lldb/Expression/DWARFEvaluatorFactory.h b/lldb/include/lldb/Expression/DWARFEvaluatorFactory.h +new file mode 100644 +index 000000000000..f3b496c580e4 +--- /dev/null ++++ b/lldb/include/lldb/Expression/DWARFEvaluatorFactory.h +@@ -0,0 +1,56 @@ ++//===-- DWARFEvaluatorFactory.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 ++// ++//===----------------------------------------------------------------------===// ++ ++#ifndef LLDB_EXPRESSION_DWARFEVALUATORFACTORY_H ++#define LLDB_EXPRESSION_DWARFEVALUATORFACTORY_H ++ ++#include "lldb/Core/PluginInterface.h" ++#include "lldb/Utility/ConstString.h" ++#include "lldb/lldb-private.h" ++ ++class DWARFUnit; ++ ++namespace lldb_private { ++ ++class DWARFEvaluator; ++class DWARFExpression; ++ ++/// \class DWARFEvaluatorFactory DWARFEvaluatorFactory.h ++/// "lldb/Expression/DWARFEvaluatorFactory.h" Factory class that allows the ++/// registration of platform-specific DWARF expression evaluators, used to ++/// handle platform-specific DWARF opcodes. ++class DWARFEvaluatorFactory : public PluginInterface { ++public: ++ static std::unique_ptr FindPlugin(Module *module); ++ ++ /// PluginInterface protocol. ++ /// \{ ++ ConstString GetPluginName() override; ++ ++ uint32_t GetPluginVersion() override { return 1; } ++ /// \} ++ ++ DWARFEvaluatorFactory() {} ++ ++ /// DWARFEvaluatorFactory protocol. ++ /// \{ ++ virtual std::unique_ptr ++ CreateDWARFEvaluator(const DWARFExpression &dwarf_expression, ++ ExecutionContext *exe_ctx, RegisterContext *reg_ctx, ++ const Value *initial_value_ptr, ++ const Value *object_address_ptr); ++ /// \} ++ ++private: ++ DWARFEvaluatorFactory(const DWARFEvaluatorFactory &); ++ const DWARFEvaluatorFactory &operator=(const DWARFEvaluatorFactory &) = delete; ++}; ++ ++} // namespace lldb_private ++ ++#endif // LLDB_EXPRESSION_DWARFEVALUATORFACTORY_H +diff --git a/lldb/include/lldb/Expression/DWARFExpression.h b/lldb/include/lldb/Expression/DWARFExpression.h +index 1490ac2d614a..35c741d4e6ba 100644 +--- a/lldb/include/lldb/Expression/DWARFExpression.h ++++ b/lldb/include/lldb/Expression/DWARFExpression.h +@@ -120,6 +120,10 @@ public: + + void SetModule(const lldb::ModuleSP &module) { m_module_wp = module; } + ++ lldb::ModuleSP GetModule() const { return m_module_wp.lock(); } ++ ++ const DWARFUnit *GetDWARFCompileUnit() const { return m_dwarf_cu; } ++ + bool ContainsThreadLocalStorage() const; + + bool LinkThreadLocalStorage( +@@ -140,7 +144,7 @@ public: + lldb::addr_t func_file_addr); + + /// Return the call-frame-info style register kind +- int GetRegisterKind(); ++ lldb::RegisterKind GetRegisterKind() const; + + /// Set the call-frame-info style register kind + /// +@@ -219,6 +223,9 @@ public: + + bool MatchesOperand(StackFrame &frame, const Instruction::Operand &op); + ++ static lldb::addr_t ReadAddressFromDebugAddrSection(const DWARFUnit *dwarf_cu, ++ uint32_t index); ++ + llvm::Optional + GetLocationExpression(lldb::addr_t load_function_start, + lldb::addr_t addr) const; +diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h +index aaa2470d2931..c15f2db52fbc 100644 +--- a/lldb/include/lldb/Target/Process.h ++++ b/lldb/include/lldb/Target/Process.h +@@ -1434,7 +1434,7 @@ public: + /// vm_addr, \a buf, and \a size updated appropriately. Zero is + /// returned in the case of an error. + virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size, +- Status &error); ++ Status &error, ExecutionContext *exe_ctx = nullptr); + + /// Read of memory from a process. + /// +diff --git a/lldb/include/lldb/Target/ProcessTrace.h b/lldb/include/lldb/Target/ProcessTrace.h +index 7b9d6b13dd6f..9525fc9750fd 100644 +--- a/lldb/include/lldb/Target/ProcessTrace.h ++++ b/lldb/include/lldb/Target/ProcessTrace.h +@@ -59,7 +59,7 @@ public: + bool WarnBeforeDetach() const override { return false; } + + size_t ReadMemory(lldb::addr_t addr, void *buf, size_t size, +- Status &error) override; ++ Status &error, ExecutionContext *exe_ctx = nullptr) override; + + size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) override; +diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h +index ad5298151e4a..5a3c0b27a738 100644 +--- a/lldb/include/lldb/lldb-forward.h ++++ b/lldb/include/lldb/lldb-forward.h +@@ -74,6 +74,7 @@ class Disassembler; + class DumpValueObjectOptions; + class DynamicCheckerFunctions; + class DynamicLoader; ++class DWARFEvaluatorFactory; + class Editline; + class EmulateInstruction; + class Environment; +diff --git a/lldb/include/lldb/lldb-private-interfaces.h b/lldb/include/lldb/lldb-private-interfaces.h +index 2ed083ec8ae9..f4d500d198e8 100644 +--- a/lldb/include/lldb/lldb-private-interfaces.h ++++ b/lldb/include/lldb/lldb-private-interfaces.h +@@ -113,6 +113,8 @@ typedef lldb::REPLSP (*REPLCreateInstance)(Status &error, + const char *repl_options); + typedef int (*ComparisonFunction)(const void *, const void *); + typedef void (*DebuggerInitializeCallback)(Debugger &debugger); ++typedef DWARFEvaluatorFactory *(*DWARFEvaluatorFactoryCreateInstance)( ++ Module *module); + /// Trace + /// \{ + typedef llvm::Expected (*TraceCreateInstanceForSessionFile)( +diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp +index 19c97be15066..1647f93ec4f3 100644 +--- a/lldb/source/Core/Module.cpp ++++ b/lldb/source/Core/Module.cpp +@@ -16,6 +16,7 @@ + #include "lldb/Core/ModuleSpec.h" + #include "lldb/Core/SearchFilter.h" + #include "lldb/Core/Section.h" ++#include "lldb/Expression/DWARFEvaluatorFactory.h" + #include "lldb/Host/FileSystem.h" + #include "lldb/Host/Host.h" + #include "lldb/Host/HostInfo.h" +@@ -1659,3 +1660,9 @@ bool Module::GetIsDynamicLinkEditor() { + + return false; + } ++ ++DWARFEvaluatorFactory *Module::GetDWARFExpressionEvaluatorFactory() { ++ if (!m_dwarf_evaluator_factory) ++ m_dwarf_evaluator_factory = DWARFEvaluatorFactory::FindPlugin(this); ++ return m_dwarf_evaluator_factory.get(); ++} +diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp +index fcaa868b083e..59a404d4a7e1 100644 +--- a/lldb/source/Core/PluginManager.cpp ++++ b/lldb/source/Core/PluginManager.cpp +@@ -1597,3 +1597,32 @@ bool PluginManager::CreateSettingForStructuredDataPlugin( + ConstString("Settings for structured data plug-ins"), properties_sp, + description, is_global_property); + } ++ ++#pragma mark DWARFEvaluator ++ ++typedef PluginInstance ++ DWARFEvaluatorFactoryInstance; ++typedef PluginInstances ++ DWARFEvaluatorFactoryInstances; ++ ++static DWARFEvaluatorFactoryInstances &GetDWARFEvaluatorFactoryInstances() { ++ static DWARFEvaluatorFactoryInstances g_instances; ++ return g_instances; ++} ++ ++bool PluginManager::RegisterPlugin( ++ ConstString name, const char *description, ++ DWARFEvaluatorFactoryCreateInstance create_callback) { ++ return GetDWARFEvaluatorFactoryInstances().RegisterPlugin(name, description, ++ create_callback); ++} ++ ++bool PluginManager::UnregisterPlugin( ++ DWARFEvaluatorFactoryCreateInstance create_callback) { ++ return GetDWARFEvaluatorFactoryInstances().UnregisterPlugin(create_callback); ++} ++ ++DWARFEvaluatorFactoryCreateInstance ++PluginManager::GetDWARFEvaluatorFactoryCreateCallbackAtIndex(uint32_t idx) { ++ return GetDWARFEvaluatorFactoryInstances().GetCallbackAtIndex(idx); ++} +diff --git a/lldb/source/Core/Value.cpp b/lldb/source/Core/Value.cpp +index fb57c0fedf04..f92d6a54de94 100644 +--- a/lldb/source/Core/Value.cpp ++++ b/lldb/source/Core/Value.cpp +@@ -538,7 +538,7 @@ Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data, + + if (process) { + const size_t bytes_read = +- process->ReadMemory(address, dst, byte_size, error); ++ process->ReadMemory(address, dst, byte_size, error, exe_ctx); + if (bytes_read != byte_size) + error.SetErrorStringWithFormat( + "read memory from 0x%" PRIx64 " failed (%u of %u bytes read)", +diff --git a/lldb/source/Core/ValueObject.cpp b/lldb/source/Core/ValueObject.cpp +index 9c1ba99da1d0..b15b214b2a2f 100644 +--- a/lldb/source/Core/ValueObject.cpp ++++ b/lldb/source/Core/ValueObject.cpp +@@ -735,7 +735,7 @@ size_t ValueObject::GetPointeeData(DataExtractor &data, uint32_t item_idx, + if (process) { + heap_buf_ptr->SetByteSize(bytes); + size_t bytes_read = process->ReadMemory( +- addr + offset, heap_buf_ptr->GetBytes(), bytes, error); ++ addr + offset, heap_buf_ptr->GetBytes(), bytes, error, &exe_ctx); + if (error.Success() || bytes_read > 0) { + data.SetData(data_sp); + return bytes_read; +diff --git a/lldb/source/Expression/CMakeLists.txt b/lldb/source/Expression/CMakeLists.txt +index bf94361dd6c1..4e76d547aeaf 100644 +--- a/lldb/source/Expression/CMakeLists.txt ++++ b/lldb/source/Expression/CMakeLists.txt +@@ -1,5 +1,7 @@ + add_lldb_library(lldbExpression + DiagnosticManager.cpp ++ DWARFEvaluator.cpp ++ DWARFEvaluatorFactory.cpp + DWARFExpression.cpp + Expression.cpp + ExpressionVariable.cpp +diff --git a/lldb/source/Expression/DWARFEvaluator.cpp b/lldb/source/Expression/DWARFEvaluator.cpp +new file mode 100644 +index 000000000000..06107e136197 +--- /dev/null ++++ b/lldb/source/Expression/DWARFEvaluator.cpp +@@ -0,0 +1,1952 @@ ++//===-- DWARFEvaluator.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 "lldb/Expression/DWARFEvaluator.h" ++#include "lldb/Expression/DWARFExpression.h" ++ ++#include "lldb/Core/Module.h" ++#include "lldb/Core/Value.h" ++#include "lldb/Core/dwarf.h" ++ ++#include "lldb/Utility/Log.h" ++#include "lldb/Utility/RegisterValue.h" ++ ++#include "lldb/Target/Process.h" ++#include "lldb/Target/RegisterContext.h" ++#include "lldb/Target/StackFrame.h" ++ ++#include "Plugins/SymbolFile/DWARF/DWARFUnit.h" ++ ++using namespace lldb; ++using namespace lldb_private; ++ ++DWARFEvaluator::DWARFEvaluator(const DWARFExpression &dwarf_expression, ++ ExecutionContext *exe_ctx, ++ RegisterContext *reg_ctx, ++ const Value *initial_value_ptr, ++ const Value *object_address_ptr) ++ : m_dwarf_expression(dwarf_expression), m_exe_ctx(exe_ctx), ++ m_reg_ctx(reg_ctx), m_initial_value_ptr(initial_value_ptr), ++ m_object_address_ptr(object_address_ptr) {} ++ ++static bool ReadRegisterValueAsScalar(RegisterContext *reg_ctx, ++ lldb::RegisterKind reg_kind, ++ uint32_t reg_num, Status *error_ptr, ++ Value &value) { ++ if (reg_ctx == nullptr) { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat("No register context in frame.\n"); ++ } else { ++ uint32_t native_reg = ++ reg_ctx->ConvertRegisterKindToRegisterNumber(reg_kind, reg_num); ++ if (native_reg == LLDB_INVALID_REGNUM) { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat("Unable to convert register " ++ "kind=%u reg_num=%u to a native " ++ "register number.\n", ++ reg_kind, reg_num); ++ } else { ++ const RegisterInfo *reg_info = ++ reg_ctx->GetRegisterInfoAtIndex(native_reg); ++ RegisterValue reg_value; ++ if (reg_ctx->ReadRegister(reg_info, reg_value)) { ++ if (reg_value.GetScalarValue(value.GetScalar())) { ++ value.SetValueType(Value::ValueType::Scalar); ++ value.SetContext(Value::ContextType::RegisterInfo, ++ const_cast(reg_info)); ++ if (error_ptr) ++ error_ptr->Clear(); ++ return true; ++ } else { ++ // If we get this error, then we need to implement a value buffer in ++ // the dwarf expression evaluation function... ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "register %s can't be converted to a scalar value", ++ reg_info->name); ++ } ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat("register %s is not available", ++ reg_info->name); ++ } ++ } ++ } ++ return false; ++} ++ ++static bool Evaluate_DW_OP_entry_value(std::vector &stack, ++ ExecutionContext *exe_ctx, ++ RegisterContext *reg_ctx, ++ const DataExtractor &opcodes, ++ lldb::offset_t &opcode_offset, ++ Status *error_ptr, Log *log) { ++ // DW_OP_entry_value(sub-expr) describes the location a variable had upon ++ // function entry: this variable location is presumed to be optimized out at ++ // the current PC value. The caller of the function may have call site ++ // information that describes an alternate location for the variable (e.g. a ++ // constant literal, or a spilled stack value) in the parent frame. ++ // ++ // Example (this is pseudo-code & pseudo-DWARF, but hopefully illustrative): ++ // ++ // void child(int &sink, int x) { ++ // ... ++ // /* "x" gets optimized out. */ ++ // ++ // /* The location of "x" here is: DW_OP_entry_value($reg2). */ ++ // ++sink; ++ // } ++ // ++ // void parent() { ++ // int sink; ++ // ++ // /* ++ // * The callsite information emitted here is: ++ // * ++ // * DW_TAG_call_site ++ // * DW_AT_return_pc ... (for "child(sink, 123);") ++ // * DW_TAG_call_site_parameter (for "sink") ++ // * DW_AT_location ($reg1) ++ // * DW_AT_call_value ($SP - 8) ++ // * DW_TAG_call_site_parameter (for "x") ++ // * DW_AT_location ($reg2) ++ // * DW_AT_call_value ($literal 123) ++ // * ++ // * DW_TAG_call_site ++ // * DW_AT_return_pc ... (for "child(sink, 456);") ++ // * ... ++ // */ ++ // child(sink, 123); ++ // child(sink, 456); ++ // } ++ // ++ // When the program stops at "++sink" within `child`, the debugger determines ++ // the call site by analyzing the return address. Once the call site is found, ++ // the debugger determines which parameter is referenced by DW_OP_entry_value ++ // and evaluates the corresponding location for that parameter in `parent`. ++ ++ // 1. Find the function which pushed the current frame onto the stack. ++ if ((!exe_ctx || !exe_ctx->HasTargetScope()) || !reg_ctx) { ++ LLDB_LOG(log, "Evaluate_DW_OP_entry_value: no exe/reg context"); ++ return false; ++ } ++ ++ StackFrame *current_frame = exe_ctx->GetFramePtr(); ++ Thread *thread = exe_ctx->GetThreadPtr(); ++ if (!current_frame || !thread) { ++ LLDB_LOG(log, "Evaluate_DW_OP_entry_value: no current frame/thread"); ++ return false; ++ } ++ ++ Target &target = exe_ctx->GetTargetRef(); ++ StackFrameSP parent_frame = nullptr; ++ addr_t return_pc = LLDB_INVALID_ADDRESS; ++ uint32_t current_frame_idx = current_frame->GetFrameIndex(); ++ uint32_t num_frames = thread->GetStackFrameCount(); ++ for (uint32_t parent_frame_idx = current_frame_idx + 1; ++ parent_frame_idx < num_frames; ++parent_frame_idx) { ++ parent_frame = thread->GetStackFrameAtIndex(parent_frame_idx); ++ // Require a valid sequence of frames. ++ if (!parent_frame) ++ break; ++ ++ // Record the first valid return address, even if this is an inlined frame, ++ // in order to look up the associated call edge in the first non-inlined ++ // parent frame. ++ if (return_pc == LLDB_INVALID_ADDRESS) { ++ return_pc = parent_frame->GetFrameCodeAddress().GetLoadAddress(&target); ++ LLDB_LOG(log, ++ "Evaluate_DW_OP_entry_value: immediate ancestor with pc = {0:x}", ++ return_pc); ++ } ++ ++ // If we've found an inlined frame, skip it (these have no call site ++ // parameters). ++ if (parent_frame->IsInlined()) ++ continue; ++ ++ // We've found the first non-inlined parent frame. ++ break; ++ } ++ if (!parent_frame || !parent_frame->GetRegisterContext()) { ++ LLDB_LOG(log, "Evaluate_DW_OP_entry_value: no parent frame with reg ctx"); ++ return false; ++ } ++ ++ Function *parent_func = ++ parent_frame->GetSymbolContext(eSymbolContextFunction).function; ++ if (!parent_func) { ++ LLDB_LOG(log, "Evaluate_DW_OP_entry_value: no parent function"); ++ return false; ++ } ++ ++ // 2. Find the call edge in the parent function responsible for creating the ++ // current activation. ++ Function *current_func = ++ current_frame->GetSymbolContext(eSymbolContextFunction).function; ++ if (!current_func) { ++ LLDB_LOG(log, "Evaluate_DW_OP_entry_value: no current function"); ++ return false; ++ } ++ ++ CallEdge *call_edge = nullptr; ++ ModuleList &modlist = target.GetImages(); ++ ExecutionContext parent_exe_ctx = *exe_ctx; ++ parent_exe_ctx.SetFrameSP(parent_frame); ++ if (!parent_frame->IsArtificial()) { ++ // If the parent frame is not artificial, the current activation may be ++ // produced by an ambiguous tail call. In this case, refuse to proceed. ++ call_edge = parent_func->GetCallEdgeForReturnAddress(return_pc, target); ++ if (!call_edge) { ++ LLDB_LOG(log, ++ "Evaluate_DW_OP_entry_value: no call edge for retn-pc = {0:x} " ++ "in parent frame {1}", ++ return_pc, parent_func->GetName()); ++ return false; ++ } ++ Function *callee_func = call_edge->GetCallee(modlist, parent_exe_ctx); ++ if (callee_func != current_func) { ++ LLDB_LOG(log, "Evaluate_DW_OP_entry_value: ambiguous call sequence, " ++ "can't find real parent frame"); ++ return false; ++ } ++ } else { ++ // The StackFrameList solver machinery has deduced that an unambiguous tail ++ // call sequence that produced the current activation. The first edge in ++ // the parent that points to the current function must be valid. ++ for (auto &edge : parent_func->GetTailCallingEdges()) { ++ if (edge->GetCallee(modlist, parent_exe_ctx) == current_func) { ++ call_edge = edge.get(); ++ break; ++ } ++ } ++ } ++ if (!call_edge) { ++ LLDB_LOG(log, "Evaluate_DW_OP_entry_value: no unambiguous edge from parent " ++ "to current function"); ++ return false; ++ } ++ ++ // 3. Attempt to locate the DW_OP_entry_value expression in the set of ++ // available call site parameters. If found, evaluate the corresponding ++ // parameter in the context of the parent frame. ++ const uint32_t subexpr_len = opcodes.GetULEB128(&opcode_offset); ++ const void *subexpr_data = opcodes.GetData(&opcode_offset, subexpr_len); ++ if (!subexpr_data) { ++ LLDB_LOG(log, "Evaluate_DW_OP_entry_value: subexpr could not be read"); ++ return false; ++ } ++ ++ const CallSiteParameter *matched_param = nullptr; ++ for (const CallSiteParameter ¶m : call_edge->GetCallSiteParameters()) { ++ DataExtractor param_subexpr_extractor; ++ if (!param.LocationInCallee.GetExpressionData(param_subexpr_extractor)) ++ continue; ++ lldb::offset_t param_subexpr_offset = 0; ++ const void *param_subexpr_data = ++ param_subexpr_extractor.GetData(¶m_subexpr_offset, subexpr_len); ++ if (!param_subexpr_data || ++ param_subexpr_extractor.BytesLeft(param_subexpr_offset) != 0) ++ continue; ++ ++ // At this point, the DW_OP_entry_value sub-expression and the callee-side ++ // expression in the call site parameter are known to have the same length. ++ // Check whether they are equal. ++ // ++ // Note that an equality check is sufficient: the contents of the ++ // DW_OP_entry_value subexpression are only used to identify the right call ++ // site parameter in the parent, and do not require any special handling. ++ if (memcmp(subexpr_data, param_subexpr_data, subexpr_len) == 0) { ++ matched_param = ¶m; ++ break; ++ } ++ } ++ if (!matched_param) { ++ LLDB_LOG(log, ++ "Evaluate_DW_OP_entry_value: no matching call site param found"); ++ return false; ++ } ++ ++ // TODO: Add support for DW_OP_push_object_address within a DW_OP_entry_value ++ // subexpresion whenever llvm does. ++ Value result; ++ const DWARFExpression ¶m_expr = matched_param->LocationInCaller; ++ if (!param_expr.Evaluate(&parent_exe_ctx, ++ parent_frame->GetRegisterContext().get(), ++ /*loclist_base_addr=*/LLDB_INVALID_ADDRESS, ++ /*initial_value_ptr=*/nullptr, ++ /*object_address_ptr=*/nullptr, result, error_ptr)) { ++ LLDB_LOG(log, ++ "Evaluate_DW_OP_entry_value: call site param evaluation failed"); ++ return false; ++ } ++ ++ stack.push_back(result); ++ return true; ++} ++ ++bool DWARFEvaluator::Evaluate(Value &result, Status *error_ptr) { ++ DataExtractor opcodes; ++ if (!m_dwarf_expression.GetExpressionData(opcodes)) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "no location, value may have been optimized out"); ++ return false; ++ } ++ return Evaluate(opcodes, result, error_ptr); ++} ++ ++bool DWARFEvaluator::Evaluate(const DataExtractor &opcodes, Value &result, ++ Status *error_ptr) { ++ if (opcodes.GetByteSize() == 0) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "no location, value may have been optimized out"); ++ return false; ++ } ++ std::vector stack; ++ ++ Process *process = nullptr; ++ StackFrame *frame = nullptr; ++ ++ if (m_exe_ctx) { ++ process = m_exe_ctx->GetProcessPtr(); ++ frame = m_exe_ctx->GetFramePtr(); ++ } ++ if (m_reg_ctx == nullptr && frame) ++ m_reg_ctx = frame->GetRegisterContext().get(); ++ ++ if (m_initial_value_ptr) ++ stack.push_back(*m_initial_value_ptr); ++ ++ lldb::offset_t offset = 0; ++ ++ /// Insertion point for evaluating multi-piece expression. ++ uint64_t op_piece_offset = 0; ++ Value pieces; // Used for DW_OP_piece ++ ++ Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); ++ ++ uint8_t _opcode = 0; ++ ++ while (opcodes.ValidOffset(offset)) { ++ const lldb::offset_t op_offset = offset; ++ const uint8_t op = opcodes.GetU8(&offset); ++ _opcode = op; ++ ++ if (log && log->GetVerbose()) { ++ size_t count = stack.size(); ++ LLDB_LOGF(log, "Stack before operation has %" PRIu64 " values:", ++ (uint64_t)count); ++ for (size_t i = 0; i < count; ++i) { ++ StreamString new_value; ++ new_value.Printf("[%" PRIu64 "]", (uint64_t)i); ++ stack[i].Dump(&new_value); ++ LLDB_LOGF(log, " %s", new_value.GetData()); ++ } ++ LLDB_LOGF(log, "0x%8.8" PRIx64 ": %s", op_offset, ++ DW_OP_value_to_name(op)); ++ } ++ ++ if (!Evaluate(op, process, frame, stack, opcodes, offset, pieces, ++ op_piece_offset, log, error_ptr)) ++ return false; ++ } ++ ++ if (stack.empty()) { ++ // Nothing on the stack, check if we created a piece value from DW_OP_piece ++ // or DW_OP_bit_piece opcodes ++ if (pieces.GetBuffer().GetByteSize()) ++ result = pieces; ++ else { ++ if (error_ptr) ++ error_ptr->SetErrorString("Stack empty after evaluation."); ++ return false; ++ } ++ } else { ++ if (log && log->GetVerbose()) { ++ size_t count = stack.size(); ++ LLDB_LOGF(log, "Stack after operation has %" PRIu64 " values:", ++ (uint64_t)count); ++ for (size_t i = 0; i < count; ++i) { ++ StreamString new_value; ++ new_value.Printf("[%" PRIu64 "]", (uint64_t)i); ++ stack[i].Dump(&new_value); ++ LLDB_LOGF(log, " %s", new_value.GetData()); ++ } ++ } ++ result = stack.back(); ++ } ++ return true; // Return true on success ++} ++ ++bool DWARFEvaluator::Evaluate(const uint8_t op, Process *process, ++ StackFrame *frame, std::vector &stack, ++ const DataExtractor &opcodes, ++ lldb::offset_t &offset, Value &pieces, ++ uint64_t &op_piece_offset, Log *log, ++ Status *error_ptr) { ++ Value tmp; ++ uint32_t reg_num; ++ ++ lldb::ModuleSP module_sp = m_dwarf_expression.GetModule(); ++ const DWARFUnit *dwarf_cu = m_dwarf_expression.GetDWARFCompileUnit(); ++ const lldb::RegisterKind reg_kind = m_dwarf_expression.GetRegisterKind(); ++ ++ switch (op) { ++ // The DW_OP_addr operation has a single operand that encodes a machine ++ // address and whose size is the size of an address on the target machine. ++ case DW_OP_addr: ++ stack.push_back(Scalar(opcodes.GetAddress(&offset))); ++ stack.back().SetValueType(Value::ValueType::FileAddress); ++ // Convert the file address to a load address, so subsequent ++ // DWARF operators can operate on it. ++ if (frame) ++ stack.back().ConvertToLoadAddress(module_sp.get(), ++ frame->CalculateTarget().get()); ++ break; ++ ++ // The DW_OP_addr_sect_offset4 is used for any location expressions in ++ // shared libraries that have a location like: ++ // DW_OP_addr(0x1000) ++ // If this address resides in a shared library, then this virtual address ++ // won't make sense when it is evaluated in the context of a running ++ // process where shared libraries have been slid. To account for this, this ++ // new address type where we can store the section pointer and a 4 byte ++ // offset. ++ // case DW_OP_addr_sect_offset4: ++ // { ++ // result_type = eResultTypeFileAddress; ++ // lldb::Section *sect = (lldb::Section ++ // *)opcodes.GetMaxU64(&offset, sizeof(void *)); ++ // lldb::addr_t sect_offset = opcodes.GetU32(&offset); ++ // ++ // Address so_addr (sect, sect_offset); ++ // lldb::addr_t load_addr = so_addr.GetLoadAddress(); ++ // if (load_addr != LLDB_INVALID_ADDRESS) ++ // { ++ // // We successfully resolve a file address to a load ++ // // address. ++ // stack.push_back(load_addr); ++ // break; ++ // } ++ // else ++ // { ++ // // We were able ++ // if (error_ptr) ++ // error_ptr->SetErrorStringWithFormat ("Section %s in ++ // %s is not currently loaded.\n", ++ // sect->GetName().AsCString(), ++ // sect->GetModule()->GetFileSpec().GetFilename().AsCString()); ++ // return false; ++ // } ++ // } ++ // break; ++ ++ // OPCODE: DW_OP_deref ++ // OPERANDS: none ++ // DESCRIPTION: Pops the top stack entry and treats it as an address. ++ // The value retrieved from that address is pushed. The size of the data ++ // retrieved from the dereferenced address is the size of an address on the ++ // target machine. ++ case DW_OP_deref: { ++ if (stack.empty()) { ++ if (error_ptr) ++ error_ptr->SetErrorString("Expression stack empty for DW_OP_deref."); ++ return false; ++ } ++ Value::ValueType value_type = stack.back().GetValueType(); ++ switch (value_type) { ++ case Value::ValueType::HostAddress: { ++ void *src = (void *)stack.back().GetScalar().ULongLong(); ++ intptr_t ptr; ++ ::memcpy(&ptr, src, sizeof(void *)); ++ stack.back().GetScalar() = ptr; ++ stack.back().ClearContext(); ++ } break; ++ case Value::ValueType::FileAddress: { ++ auto file_addr = stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS); ++ if (!module_sp) { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "need module to resolve file address for DW_OP_deref"); ++ return false; ++ } ++ Address so_addr; ++ if (!module_sp->ResolveFileAddress(file_addr, so_addr)) { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "failed to resolve file address in module"); ++ return false; ++ } ++ addr_t load_Addr = so_addr.GetLoadAddress(m_exe_ctx->GetTargetPtr()); ++ if (load_Addr == LLDB_INVALID_ADDRESS) { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat("failed to resolve load address"); ++ return false; ++ } ++ stack.back().GetScalar() = load_Addr; ++ stack.back().SetValueType(Value::ValueType::LoadAddress); ++ // Fall through to load address code below... ++ } ++ LLVM_FALLTHROUGH; ++ case Value::ValueType::LoadAddress: ++ if (m_exe_ctx) { ++ if (process) { ++ lldb::addr_t pointer_addr = ++ stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS); ++ Status error; ++ lldb::addr_t pointer_value = ++ process->ReadPointerFromMemory(pointer_addr, error); ++ if (pointer_value != LLDB_INVALID_ADDRESS) { ++ stack.back().GetScalar() = pointer_value; ++ stack.back().ClearContext(); ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "Failed to dereference pointer from 0x%" PRIx64 ++ " for DW_OP_deref: %s\n", ++ pointer_addr, error.AsCString()); ++ return false; ++ } ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "NULL process for DW_OP_deref.\n"); ++ return false; ++ } ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "NULL execution context for DW_OP_deref.\n"); ++ return false; ++ } ++ break; ++ ++ default: ++ break; ++ } ++ ++ } break; ++ ++ // OPCODE: DW_OP_deref_size ++ // OPERANDS: 1 ++ // 1 - uint8_t that specifies the size of the data to dereference. ++ // DESCRIPTION: Behaves like the DW_OP_deref operation: it pops the top ++ // stack entry and treats it as an address. The value retrieved from that ++ // address is pushed. In the DW_OP_deref_size operation, however, the size ++ // in bytes of the data retrieved from the dereferenced address is ++ // specified by the single operand. This operand is a 1-byte unsigned ++ // integral constant whose value may not be larger than the size of an ++ // address on the target machine. The data retrieved is zero extended to ++ // the size of an address on the target machine before being pushed on the ++ // expression stack. ++ case DW_OP_deref_size: { ++ if (stack.empty()) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack empty for DW_OP_deref_size."); ++ return false; ++ } ++ uint8_t size = opcodes.GetU8(&offset); ++ Value::ValueType value_type = stack.back().GetValueType(); ++ switch (value_type) { ++ case Value::ValueType::HostAddress: { ++ void *src = (void *)stack.back().GetScalar().ULongLong(); ++ intptr_t ptr; ++ ::memcpy(&ptr, src, sizeof(void *)); ++ // I can't decide whether the size operand should apply to the bytes in ++ // their ++ // lldb-host endianness or the target endianness.. I doubt this'll ever ++ // come up but I'll opt for assuming big endian regardless. ++ switch (size) { ++ case 1: ++ ptr = ptr & 0xff; ++ break; ++ case 2: ++ ptr = ptr & 0xffff; ++ break; ++ case 3: ++ ptr = ptr & 0xffffff; ++ break; ++ case 4: ++ ptr = ptr & 0xffffffff; ++ break; ++ // the casts are added to work around the case where intptr_t is a 32 ++ // bit quantity; ++ // presumably we won't hit the 5..7 cases if (void*) is 32-bits in this ++ // program. ++ case 5: ++ ptr = (intptr_t)ptr & 0xffffffffffULL; ++ break; ++ case 6: ++ ptr = (intptr_t)ptr & 0xffffffffffffULL; ++ break; ++ case 7: ++ ptr = (intptr_t)ptr & 0xffffffffffffffULL; ++ break; ++ default: ++ break; ++ } ++ stack.back().GetScalar() = ptr; ++ stack.back().ClearContext(); ++ } break; ++ case Value::ValueType::LoadAddress: ++ if (m_exe_ctx) { ++ if (process) { ++ lldb::addr_t pointer_addr = ++ stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS); ++ uint8_t addr_bytes[sizeof(lldb::addr_t)]; ++ Status error; ++ if (process->ReadMemory(pointer_addr, &addr_bytes, size, error) == ++ size) { ++ DataExtractor addr_data(addr_bytes, sizeof(addr_bytes), ++ process->GetByteOrder(), size); ++ lldb::offset_t addr_data_offset = 0; ++ switch (size) { ++ case 1: ++ stack.back().GetScalar() = addr_data.GetU8(&addr_data_offset); ++ break; ++ case 2: ++ stack.back().GetScalar() = addr_data.GetU16(&addr_data_offset); ++ break; ++ case 4: ++ stack.back().GetScalar() = addr_data.GetU32(&addr_data_offset); ++ break; ++ case 8: ++ stack.back().GetScalar() = addr_data.GetU64(&addr_data_offset); ++ break; ++ default: ++ stack.back().GetScalar() = ++ addr_data.GetAddress(&addr_data_offset); ++ } ++ stack.back().ClearContext(); ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "Failed to dereference pointer from 0x%" PRIx64 ++ " for DW_OP_deref: %s\n", ++ pointer_addr, error.AsCString()); ++ return false; ++ } ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "NULL process for DW_OP_deref.\n"); ++ return false; ++ } ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "NULL execution context for DW_OP_deref.\n"); ++ return false; ++ } ++ break; ++ ++ default: ++ break; ++ } ++ ++ } break; ++ ++ // OPCODE: DW_OP_xderef_size ++ // OPERANDS: 1 ++ // 1 - uint8_t that specifies the size of the data to dereference. ++ // DESCRIPTION: Behaves like the DW_OP_xderef operation: the entry at ++ // the top of the stack is treated as an address. The second stack entry is ++ // treated as an "address space identifier" for those architectures that ++ // support multiple address spaces. The top two stack elements are popped, ++ // a data item is retrieved through an implementation-defined address ++ // calculation and pushed as the new stack top. In the DW_OP_xderef_size ++ // operation, however, the size in bytes of the data retrieved from the ++ // dereferenced address is specified by the single operand. This operand is ++ // a 1-byte unsigned integral constant whose value may not be larger than ++ // the size of an address on the target machine. The data retrieved is zero ++ // extended to the size of an address on the target machine before being ++ // pushed on the expression stack. ++ case DW_OP_xderef_size: ++ if (error_ptr) ++ error_ptr->SetErrorString("Unimplemented opcode: DW_OP_xderef_size."); ++ return false; ++ // OPCODE: DW_OP_xderef ++ // OPERANDS: none ++ // DESCRIPTION: Provides an extended dereference mechanism. The entry at ++ // the top of the stack is treated as an address. The second stack entry is ++ // treated as an "address space identifier" for those architectures that ++ // support multiple address spaces. The top two stack elements are popped, ++ // a data item is retrieved through an implementation-defined address ++ // calculation and pushed as the new stack top. The size of the data ++ // retrieved from the dereferenced address is the size of an address on the ++ // target machine. ++ case DW_OP_xderef: ++ if (error_ptr) ++ error_ptr->SetErrorString("Unimplemented opcode: DW_OP_xderef."); ++ return false; ++ ++ // All DW_OP_constXXX opcodes have a single operand as noted below: ++ // ++ // Opcode Operand 1 ++ // DW_OP_const1u 1-byte unsigned integer constant DW_OP_const1s ++ // 1-byte signed integer constant DW_OP_const2u 2-byte unsigned integer ++ // constant DW_OP_const2s 2-byte signed integer constant DW_OP_const4u ++ // 4-byte unsigned integer constant DW_OP_const4s 4-byte signed integer ++ // constant DW_OP_const8u 8-byte unsigned integer constant DW_OP_const8s ++ // 8-byte signed integer constant DW_OP_constu unsigned LEB128 integer ++ // constant DW_OP_consts signed LEB128 integer constant ++ case DW_OP_const1u: ++ stack.push_back(Scalar((uint8_t)opcodes.GetU8(&offset))); ++ break; ++ case DW_OP_const1s: ++ stack.push_back(Scalar((int8_t)opcodes.GetU8(&offset))); ++ break; ++ case DW_OP_const2u: ++ stack.push_back(Scalar((uint16_t)opcodes.GetU16(&offset))); ++ break; ++ case DW_OP_const2s: ++ stack.push_back(Scalar((int16_t)opcodes.GetU16(&offset))); ++ break; ++ case DW_OP_const4u: ++ stack.push_back(Scalar((uint32_t)opcodes.GetU32(&offset))); ++ break; ++ case DW_OP_const4s: ++ stack.push_back(Scalar((int32_t)opcodes.GetU32(&offset))); ++ break; ++ case DW_OP_const8u: ++ stack.push_back(Scalar((uint64_t)opcodes.GetU64(&offset))); ++ break; ++ case DW_OP_const8s: ++ stack.push_back(Scalar((int64_t)opcodes.GetU64(&offset))); ++ break; ++ case DW_OP_constu: ++ stack.push_back(Scalar(opcodes.GetULEB128(&offset))); ++ break; ++ case DW_OP_consts: ++ stack.push_back(Scalar(opcodes.GetSLEB128(&offset))); ++ break; ++ ++ // OPCODE: DW_OP_dup ++ // OPERANDS: none ++ // DESCRIPTION: duplicates the value at the top of the stack ++ case DW_OP_dup: ++ if (stack.empty()) { ++ if (error_ptr) ++ error_ptr->SetErrorString("Expression stack empty for DW_OP_dup."); ++ return false; ++ } else ++ stack.push_back(stack.back()); ++ break; ++ ++ // OPCODE: DW_OP_drop ++ // OPERANDS: none ++ // DESCRIPTION: pops the value at the top of the stack ++ case DW_OP_drop: ++ if (stack.empty()) { ++ if (error_ptr) ++ error_ptr->SetErrorString("Expression stack empty for DW_OP_drop."); ++ return false; ++ } else ++ stack.pop_back(); ++ break; ++ ++ // OPCODE: DW_OP_over ++ // OPERANDS: none ++ // DESCRIPTION: Duplicates the entry currently second in the stack at ++ // the top of the stack. ++ case DW_OP_over: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_over."); ++ return false; ++ } else ++ stack.push_back(stack[stack.size() - 2]); ++ break; ++ ++ // OPCODE: DW_OP_pick ++ // OPERANDS: uint8_t index into the current stack ++ // DESCRIPTION: The stack entry with the specified index (0 through 255, ++ // inclusive) is pushed on the stack ++ case DW_OP_pick: { ++ uint8_t pick_idx = opcodes.GetU8(&offset); ++ if (pick_idx < stack.size()) ++ stack.push_back(stack[stack.size() - 1 - pick_idx]); ++ else { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "Index %u out of range for DW_OP_pick.\n", pick_idx); ++ return false; ++ } ++ } break; ++ ++ // OPCODE: DW_OP_swap ++ // OPERANDS: none ++ // DESCRIPTION: swaps the top two stack entries. The entry at the top ++ // of the stack becomes the second stack entry, and the second entry ++ // becomes the top of the stack ++ case DW_OP_swap: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_swap."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.back() = stack[stack.size() - 2]; ++ stack[stack.size() - 2] = tmp; ++ } ++ break; ++ ++ // OPCODE: DW_OP_rot ++ // OPERANDS: none ++ // DESCRIPTION: Rotates the first three stack entries. The entry at ++ // the top of the stack becomes the third stack entry, the second entry ++ // becomes the top of the stack, and the third entry becomes the second ++ // entry. ++ case DW_OP_rot: ++ if (stack.size() < 3) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 3 items for DW_OP_rot."); ++ return false; ++ } else { ++ size_t last_idx = stack.size() - 1; ++ Value old_top = stack[last_idx]; ++ stack[last_idx] = stack[last_idx - 1]; ++ stack[last_idx - 1] = stack[last_idx - 2]; ++ stack[last_idx - 2] = old_top; ++ } ++ break; ++ ++ // OPCODE: DW_OP_abs ++ // OPERANDS: none ++ // DESCRIPTION: pops the top stack entry, interprets it as a signed ++ // value and pushes its absolute value. If the absolute value can not be ++ // represented, the result is undefined. ++ case DW_OP_abs: ++ if (stack.empty()) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 1 item for DW_OP_abs."); ++ return false; ++ } else if (!stack.back().ResolveValue(m_exe_ctx).AbsoluteValue()) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Failed to take the absolute value of the first stack item."); ++ return false; ++ } ++ break; ++ ++ // OPCODE: DW_OP_and ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack values, performs a bitwise and ++ // operation on the two, and pushes the result. ++ case DW_OP_and: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_and."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) = ++ stack.back().ResolveValue(m_exe_ctx) & tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_div ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack values, divides the former second ++ // entry by the former top of the stack using signed division, and pushes ++ // the result. ++ case DW_OP_div: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_div."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ if (tmp.ResolveValue(m_exe_ctx).IsZero()) { ++ if (error_ptr) ++ error_ptr->SetErrorString("Divide by zero."); ++ return false; ++ } else { ++ stack.pop_back(); ++ stack.back() = ++ stack.back().ResolveValue(m_exe_ctx) / tmp.ResolveValue(m_exe_ctx); ++ if (!stack.back().ResolveValue(m_exe_ctx).IsValid()) { ++ if (error_ptr) ++ error_ptr->SetErrorString("Divide failed."); ++ return false; ++ } ++ } ++ } ++ break; ++ ++ // OPCODE: DW_OP_minus ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack values, subtracts the former top ++ // of the stack from the former second entry, and pushes the result. ++ case DW_OP_minus: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_minus."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) = ++ stack.back().ResolveValue(m_exe_ctx) - tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_mod ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack values and pushes the result of ++ // the calculation: former second stack entry modulo the former top of the ++ // stack. ++ case DW_OP_mod: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_mod."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) = ++ stack.back().ResolveValue(m_exe_ctx) % tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_mul ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack entries, multiplies them ++ // together, and pushes the result. ++ case DW_OP_mul: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_mul."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) = ++ stack.back().ResolveValue(m_exe_ctx) * tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_neg ++ // OPERANDS: none ++ // DESCRIPTION: pops the top stack entry, and pushes its negation. ++ case DW_OP_neg: ++ if (stack.empty()) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 1 item for DW_OP_neg."); ++ return false; ++ } else { ++ if (!stack.back().ResolveValue(m_exe_ctx).UnaryNegate()) { ++ if (error_ptr) ++ error_ptr->SetErrorString("Unary negate failed."); ++ return false; ++ } ++ } ++ break; ++ ++ // OPCODE: DW_OP_not ++ // OPERANDS: none ++ // DESCRIPTION: pops the top stack entry, and pushes its bitwise ++ // complement ++ case DW_OP_not: ++ if (stack.empty()) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 1 item for DW_OP_not."); ++ return false; ++ } else { ++ if (!stack.back().ResolveValue(m_exe_ctx).OnesComplement()) { ++ if (error_ptr) ++ error_ptr->SetErrorString("Logical NOT failed."); ++ return false; ++ } ++ } ++ break; ++ ++ // OPCODE: DW_OP_or ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack entries, performs a bitwise or ++ // operation on the two, and pushes the result. ++ case DW_OP_or: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_or."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) = ++ stack.back().ResolveValue(m_exe_ctx) | tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_plus ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack entries, adds them together, and ++ // pushes the result. ++ case DW_OP_plus: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_plus."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().GetScalar() += tmp.GetScalar(); ++ } ++ break; ++ ++ // OPCODE: DW_OP_plus_uconst ++ // OPERANDS: none ++ // DESCRIPTION: pops the top stack entry, adds it to the unsigned LEB128 ++ // constant operand and pushes the result. ++ case DW_OP_plus_uconst: ++ if (stack.empty()) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 1 item for DW_OP_plus_uconst."); ++ return false; ++ } else { ++ const uint64_t uconst_value = opcodes.GetULEB128(&offset); ++ // Implicit conversion from a UINT to a Scalar... ++ stack.back().GetScalar() += uconst_value; ++ if (!stack.back().GetScalar().IsValid()) { ++ if (error_ptr) ++ error_ptr->SetErrorString("DW_OP_plus_uconst failed."); ++ return false; ++ } ++ } ++ break; ++ ++ // OPCODE: DW_OP_shl ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack entries, shifts the former ++ // second entry left by the number of bits specified by the former top of ++ // the stack, and pushes the result. ++ case DW_OP_shl: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_shl."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) <<= tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_shr ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack entries, shifts the former second ++ // entry right logically (filling with zero bits) by the number of bits ++ // specified by the former top of the stack, and pushes the result. ++ case DW_OP_shr: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_shr."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ if (!stack.back().ResolveValue(m_exe_ctx).ShiftRightLogical( ++ tmp.ResolveValue(m_exe_ctx))) { ++ if (error_ptr) ++ error_ptr->SetErrorString("DW_OP_shr failed."); ++ return false; ++ } ++ } ++ break; ++ ++ // OPCODE: DW_OP_shra ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack entries, shifts the former second ++ // entry right arithmetically (divide the magnitude by 2, keep the same ++ // sign for the result) by the number of bits specified by the former top ++ // of the stack, and pushes the result. ++ case DW_OP_shra: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_shra."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) >>= tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_xor ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack entries, performs the bitwise ++ // exclusive-or operation on the two, and pushes the result. ++ case DW_OP_xor: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_xor."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) = ++ stack.back().ResolveValue(m_exe_ctx) ^ tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_skip ++ // OPERANDS: int16_t ++ // DESCRIPTION: An unconditional branch. Its single operand is a 2-byte ++ // signed integer constant. The 2-byte constant is the number of bytes of ++ // the DWARF expression to skip forward or backward from the current ++ // operation, beginning after the 2-byte constant. ++ case DW_OP_skip: { ++ int16_t skip_offset = (int16_t)opcodes.GetU16(&offset); ++ lldb::offset_t new_offset = offset + skip_offset; ++ if (opcodes.ValidOffset(new_offset)) ++ offset = new_offset; ++ else { ++ if (error_ptr) ++ error_ptr->SetErrorString("Invalid opcode offset in DW_OP_skip."); ++ return false; ++ } ++ } break; ++ ++ // OPCODE: DW_OP_bra ++ // OPERANDS: int16_t ++ // DESCRIPTION: A conditional branch. Its single operand is a 2-byte ++ // signed integer constant. This operation pops the top of stack. If the ++ // value popped is not the constant 0, the 2-byte constant operand is the ++ // number of bytes of the DWARF expression to skip forward or backward from ++ // the current operation, beginning after the 2-byte constant. ++ case DW_OP_bra: ++ if (stack.empty()) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 1 item for DW_OP_bra."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ int16_t bra_offset = (int16_t)opcodes.GetU16(&offset); ++ Scalar zero(0); ++ if (tmp.ResolveValue(m_exe_ctx) != zero) { ++ lldb::offset_t new_offset = offset + bra_offset; ++ if (opcodes.ValidOffset(new_offset)) ++ offset = new_offset; ++ else { ++ if (error_ptr) ++ error_ptr->SetErrorString("Invalid opcode offset in DW_OP_bra."); ++ return false; ++ } ++ } ++ } ++ break; ++ ++ // OPCODE: DW_OP_eq ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack values, compares using the ++ // equals (==) operator. ++ // STACK RESULT: push the constant value 1 onto the stack if the result ++ // of the operation is true or the constant value 0 if the result of the ++ // operation is false. ++ case DW_OP_eq: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_eq."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) = ++ stack.back().ResolveValue(m_exe_ctx) == tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_ge ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack values, compares using the ++ // greater than or equal to (>=) operator. ++ // STACK RESULT: push the constant value 1 onto the stack if the result ++ // of the operation is true or the constant value 0 if the result of the ++ // operation is false. ++ case DW_OP_ge: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_ge."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) = ++ stack.back().ResolveValue(m_exe_ctx) >= tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_gt ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack values, compares using the ++ // greater than (>) operator. ++ // STACK RESULT: push the constant value 1 onto the stack if the result ++ // of the operation is true or the constant value 0 if the result of the ++ // operation is false. ++ case DW_OP_gt: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_gt."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) = ++ stack.back().ResolveValue(m_exe_ctx) > tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_le ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack values, compares using the ++ // less than or equal to (<=) operator. ++ // STACK RESULT: push the constant value 1 onto the stack if the result ++ // of the operation is true or the constant value 0 if the result of the ++ // operation is false. ++ case DW_OP_le: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_le."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) = ++ stack.back().ResolveValue(m_exe_ctx) <= tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_lt ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack values, compares using the ++ // less than (<) operator. ++ // STACK RESULT: push the constant value 1 onto the stack if the result ++ // of the operation is true or the constant value 0 if the result of the ++ // operation is false. ++ case DW_OP_lt: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_lt."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) = ++ stack.back().ResolveValue(m_exe_ctx) < tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_ne ++ // OPERANDS: none ++ // DESCRIPTION: pops the top two stack values, compares using the ++ // not equal (!=) operator. ++ // STACK RESULT: push the constant value 1 onto the stack if the result ++ // of the operation is true or the constant value 0 if the result of the ++ // operation is false. ++ case DW_OP_ne: ++ if (stack.size() < 2) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 2 items for DW_OP_ne."); ++ return false; ++ } else { ++ tmp = stack.back(); ++ stack.pop_back(); ++ stack.back().ResolveValue(m_exe_ctx) = ++ stack.back().ResolveValue(m_exe_ctx) != tmp.ResolveValue(m_exe_ctx); ++ } ++ break; ++ ++ // OPCODE: DW_OP_litn ++ // OPERANDS: none ++ // DESCRIPTION: encode the unsigned literal values from 0 through 31. ++ // STACK RESULT: push the unsigned literal constant value onto the top ++ // of the stack. ++ case DW_OP_lit0: ++ case DW_OP_lit1: ++ case DW_OP_lit2: ++ case DW_OP_lit3: ++ case DW_OP_lit4: ++ case DW_OP_lit5: ++ case DW_OP_lit6: ++ case DW_OP_lit7: ++ case DW_OP_lit8: ++ case DW_OP_lit9: ++ case DW_OP_lit10: ++ case DW_OP_lit11: ++ case DW_OP_lit12: ++ case DW_OP_lit13: ++ case DW_OP_lit14: ++ case DW_OP_lit15: ++ case DW_OP_lit16: ++ case DW_OP_lit17: ++ case DW_OP_lit18: ++ case DW_OP_lit19: ++ case DW_OP_lit20: ++ case DW_OP_lit21: ++ case DW_OP_lit22: ++ case DW_OP_lit23: ++ case DW_OP_lit24: ++ case DW_OP_lit25: ++ case DW_OP_lit26: ++ case DW_OP_lit27: ++ case DW_OP_lit28: ++ case DW_OP_lit29: ++ case DW_OP_lit30: ++ case DW_OP_lit31: ++ stack.push_back(Scalar((uint64_t)(op - DW_OP_lit0))); ++ break; ++ ++ // OPCODE: DW_OP_regN ++ // OPERANDS: none ++ // DESCRIPTION: Push the value in register n on the top of the stack. ++ case DW_OP_reg0: ++ case DW_OP_reg1: ++ case DW_OP_reg2: ++ case DW_OP_reg3: ++ case DW_OP_reg4: ++ case DW_OP_reg5: ++ case DW_OP_reg6: ++ case DW_OP_reg7: ++ case DW_OP_reg8: ++ case DW_OP_reg9: ++ case DW_OP_reg10: ++ case DW_OP_reg11: ++ case DW_OP_reg12: ++ case DW_OP_reg13: ++ case DW_OP_reg14: ++ case DW_OP_reg15: ++ case DW_OP_reg16: ++ case DW_OP_reg17: ++ case DW_OP_reg18: ++ case DW_OP_reg19: ++ case DW_OP_reg20: ++ case DW_OP_reg21: ++ case DW_OP_reg22: ++ case DW_OP_reg23: ++ case DW_OP_reg24: ++ case DW_OP_reg25: ++ case DW_OP_reg26: ++ case DW_OP_reg27: ++ case DW_OP_reg28: ++ case DW_OP_reg29: ++ case DW_OP_reg30: ++ case DW_OP_reg31: { ++ reg_num = op - DW_OP_reg0; ++ ++ if (ReadRegisterValueAsScalar(m_reg_ctx, reg_kind, reg_num, error_ptr, tmp)) ++ stack.push_back(tmp); ++ else ++ return false; ++ } break; ++ // OPCODE: DW_OP_regx ++ // OPERANDS: ++ // ULEB128 literal operand that encodes the register. ++ // DESCRIPTION: Push the value in register on the top of the stack. ++ case DW_OP_regx: { ++ reg_num = opcodes.GetULEB128(&offset); ++ if (ReadRegisterValueAsScalar(m_reg_ctx, reg_kind, reg_num, error_ptr, tmp)) ++ stack.push_back(tmp); ++ else ++ return false; ++ } break; ++ ++ // OPCODE: DW_OP_bregN ++ // OPERANDS: ++ // SLEB128 offset from register N ++ // DESCRIPTION: Value is in memory at the address specified by register ++ // N plus an offset. ++ case DW_OP_breg0: ++ case DW_OP_breg1: ++ case DW_OP_breg2: ++ case DW_OP_breg3: ++ case DW_OP_breg4: ++ case DW_OP_breg5: ++ case DW_OP_breg6: ++ case DW_OP_breg7: ++ case DW_OP_breg8: ++ case DW_OP_breg9: ++ case DW_OP_breg10: ++ case DW_OP_breg11: ++ case DW_OP_breg12: ++ case DW_OP_breg13: ++ case DW_OP_breg14: ++ case DW_OP_breg15: ++ case DW_OP_breg16: ++ case DW_OP_breg17: ++ case DW_OP_breg18: ++ case DW_OP_breg19: ++ case DW_OP_breg20: ++ case DW_OP_breg21: ++ case DW_OP_breg22: ++ case DW_OP_breg23: ++ case DW_OP_breg24: ++ case DW_OP_breg25: ++ case DW_OP_breg26: ++ case DW_OP_breg27: ++ case DW_OP_breg28: ++ case DW_OP_breg29: ++ case DW_OP_breg30: ++ case DW_OP_breg31: { ++ reg_num = op - DW_OP_breg0; ++ ++ if (ReadRegisterValueAsScalar(m_reg_ctx, reg_kind, reg_num, error_ptr, ++ tmp)) { ++ int64_t breg_offset = opcodes.GetSLEB128(&offset); ++ tmp.ResolveValue(m_exe_ctx) += (uint64_t)breg_offset; ++ tmp.ClearContext(); ++ stack.push_back(tmp); ++ stack.back().SetValueType(Value::ValueType::LoadAddress); ++ } else ++ return false; ++ } break; ++ // OPCODE: DW_OP_bregx ++ // OPERANDS: 2 ++ // ULEB128 literal operand that encodes the register. ++ // SLEB128 offset from register N ++ // DESCRIPTION: Value is in memory at the address specified by register ++ // N plus an offset. ++ case DW_OP_bregx: { ++ reg_num = opcodes.GetULEB128(&offset); ++ ++ if (ReadRegisterValueAsScalar(m_reg_ctx, reg_kind, reg_num, error_ptr, ++ tmp)) { ++ int64_t breg_offset = opcodes.GetSLEB128(&offset); ++ tmp.ResolveValue(m_exe_ctx) += (uint64_t)breg_offset; ++ tmp.ClearContext(); ++ stack.push_back(tmp); ++ stack.back().SetValueType(Value::ValueType::LoadAddress); ++ } else ++ return false; ++ } break; ++ ++ case DW_OP_fbreg: ++ if (m_exe_ctx) { ++ if (frame) { ++ Scalar value; ++ if (frame->GetFrameBaseValue(value, error_ptr)) { ++ int64_t fbreg_offset = opcodes.GetSLEB128(&offset); ++ value += fbreg_offset; ++ stack.push_back(value); ++ stack.back().SetValueType(Value::ValueType::LoadAddress); ++ } else ++ return false; ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Invalid stack frame in context for DW_OP_fbreg opcode."); ++ return false; ++ } ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "NULL execution context for DW_OP_fbreg.\n"); ++ return false; ++ } ++ ++ break; ++ ++ // OPCODE: DW_OP_nop ++ // OPERANDS: none ++ // DESCRIPTION: A place holder. It has no effect on the location stack ++ // or any of its values. ++ case DW_OP_nop: ++ break; ++ ++ // OPCODE: DW_OP_piece ++ // OPERANDS: 1 ++ // ULEB128: byte size of the piece ++ // DESCRIPTION: The operand describes the size in bytes of the piece of ++ // the object referenced by the DWARF expression whose result is at the top ++ // of the stack. If the piece is located in a register, but does not occupy ++ // the entire register, the placement of the piece within that register is ++ // defined by the ABI. ++ // ++ // Many compilers store a single variable in sets of registers, or store a ++ // variable partially in memory and partially in registers. DW_OP_piece ++ // provides a way of describing how large a part of a variable a particular ++ // DWARF expression refers to. ++ case DW_OP_piece: { ++ const uint64_t piece_byte_size = opcodes.GetULEB128(&offset); ++ ++ if (piece_byte_size > 0) { ++ Value curr_piece; ++ ++ if (stack.empty()) { ++ // In a multi-piece expression, this means that the current piece is ++ // not available. Fill with zeros for now by resizing the data and ++ // appending it ++ curr_piece.ResizeData(piece_byte_size); ++ // Note that "0" is not a correct value for the unknown bits. ++ // It would be better to also return a mask of valid bits together ++ // with the expression result, so the debugger can print missing ++ // members as "" or something. ++ ::memset(curr_piece.GetBuffer().GetBytes(), 0, piece_byte_size); ++ pieces.AppendDataToHostBuffer(curr_piece); ++ } else { ++ Status error; ++ // Extract the current piece into "curr_piece" ++ Value curr_piece_source_value(stack.back()); ++ stack.pop_back(); ++ ++ const Value::ValueType curr_piece_source_value_type = ++ curr_piece_source_value.GetValueType(); ++ switch (curr_piece_source_value_type) { ++ case Value::ValueType::LoadAddress: ++ if (process) { ++ if (curr_piece.ResizeData(piece_byte_size) == piece_byte_size) { ++ lldb::addr_t load_addr = ++ curr_piece_source_value.GetScalar().ULongLong( ++ LLDB_INVALID_ADDRESS); ++ if (process->ReadMemory( ++ load_addr, curr_piece.GetBuffer().GetBytes(), ++ piece_byte_size, error) != piece_byte_size) { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "failed to read memory DW_OP_piece(%" PRIu64 ++ ") from 0x%" PRIx64, ++ piece_byte_size, load_addr); ++ return false; ++ } ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "failed to resize the piece memory buffer for " ++ "DW_OP_piece(%" PRIu64 ")", ++ piece_byte_size); ++ return false; ++ } ++ } ++ break; ++ ++ case Value::ValueType::FileAddress: ++ case Value::ValueType::HostAddress: ++ if (error_ptr) { ++ lldb::addr_t addr = curr_piece_source_value.GetScalar().ULongLong( ++ LLDB_INVALID_ADDRESS); ++ error_ptr->SetErrorStringWithFormat( ++ "failed to read memory DW_OP_piece(%" PRIu64 ++ ") from %s address 0x%" PRIx64, ++ piece_byte_size, ++ curr_piece_source_value.GetValueType() == ++ Value::ValueType::FileAddress ++ ? "file" ++ : "host", ++ addr); ++ } ++ return false; ++ ++ case Value::ValueType::Scalar: { ++ uint32_t bit_size = piece_byte_size * 8; ++ uint32_t bit_offset = 0; ++ Scalar &scalar = curr_piece_source_value.GetScalar(); ++ if (!scalar.ExtractBitfield(bit_size, bit_offset)) { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "unable to extract %" PRIu64 " bytes from a %" PRIu64 ++ " byte scalar value.", ++ piece_byte_size, ++ (uint64_t)curr_piece_source_value.GetScalar().GetByteSize()); ++ return false; ++ } ++ // Create curr_piece with bit_size. By default Scalar ++ // grows to the nearest host integer type. ++ llvm::APInt fail_value(1, 0, false); ++ llvm::APInt ap_int = scalar.UInt128(fail_value); ++ assert(ap_int.getBitWidth() >= bit_size); ++ llvm::ArrayRef buf{ap_int.getRawData(), ++ ap_int.getNumWords()}; ++ curr_piece.GetScalar() = Scalar(llvm::APInt(bit_size, buf)); ++ } break; ++ } ++ ++ // Check if this is the first piece? ++ if (op_piece_offset == 0) { ++ // This is the first piece, we should push it back onto the stack ++ // so subsequent pieces will be able to access this piece and add ++ // to it. ++ if (pieces.AppendDataToHostBuffer(curr_piece) == 0) { ++ if (error_ptr) ++ error_ptr->SetErrorString("failed to append piece data"); ++ return false; ++ } ++ } else { ++ // If this is the second or later piece there should be a value on ++ // the stack. ++ if (pieces.GetBuffer().GetByteSize() != op_piece_offset) { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "DW_OP_piece for offset %" PRIu64 ++ " but top of stack is of size %" PRIu64, ++ op_piece_offset, pieces.GetBuffer().GetByteSize()); ++ return false; ++ } ++ ++ if (pieces.AppendDataToHostBuffer(curr_piece) == 0) { ++ if (error_ptr) ++ error_ptr->SetErrorString("failed to append piece data"); ++ return false; ++ } ++ } ++ } ++ op_piece_offset += piece_byte_size; ++ } ++ } break; ++ ++ case DW_OP_bit_piece: // 0x9d ULEB128 bit size, ULEB128 bit offset (DWARF3); ++ if (stack.size() < 1) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 1 item for DW_OP_bit_piece."); ++ return false; ++ } else { ++ const uint64_t piece_bit_size = opcodes.GetULEB128(&offset); ++ const uint64_t piece_bit_offset = opcodes.GetULEB128(&offset); ++ switch (stack.back().GetValueType()) { ++ case Value::ValueType::Scalar: { ++ if (!stack.back().GetScalar().ExtractBitfield(piece_bit_size, ++ piece_bit_offset)) { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "unable to extract %" PRIu64 " bit value with %" PRIu64 ++ " bit offset from a %" PRIu64 " bit scalar value.", ++ piece_bit_size, piece_bit_offset, ++ (uint64_t)(stack.back().GetScalar().GetByteSize() * 8)); ++ return false; ++ } ++ } break; ++ ++ case Value::ValueType::FileAddress: ++ case Value::ValueType::LoadAddress: ++ case Value::ValueType::HostAddress: ++ if (error_ptr) { ++ error_ptr->SetErrorStringWithFormat( ++ "unable to extract DW_OP_bit_piece(bit_size = %" PRIu64 ++ ", bit_offset = %" PRIu64 ") from an address value.", ++ piece_bit_size, piece_bit_offset); ++ } ++ return false; ++ } ++ } ++ break; ++ ++ // OPCODE: DW_OP_push_object_address ++ // OPERANDS: none ++ // DESCRIPTION: Pushes the address of the object currently being ++ // evaluated as part of evaluation of a user presented expression. This ++ // object may correspond to an independent variable described by its own ++ // DIE or it may be a component of an array, structure, or class whose ++ // address has been dynamically determined by an earlier step during user ++ // expression evaluation. ++ case DW_OP_push_object_address: ++ if (m_object_address_ptr) ++ stack.push_back(*m_object_address_ptr); ++ else { ++ if (error_ptr) ++ error_ptr->SetErrorString("DW_OP_push_object_address used without " ++ "specifying an object address"); ++ return false; ++ } ++ break; ++ ++ // OPCODE: DW_OP_call2 ++ // OPERANDS: ++ // uint16_t compile unit relative offset of a DIE ++ // DESCRIPTION: Performs subroutine calls during evaluation ++ // of a DWARF expression. The operand is the 2-byte unsigned offset of a ++ // debugging information entry in the current compilation unit. ++ // ++ // Operand interpretation is exactly like that for DW_FORM_ref2. ++ // ++ // This operation transfers control of DWARF expression evaluation to the ++ // DW_AT_location attribute of the referenced DIE. If there is no such ++ // attribute, then there is no effect. Execution of the DWARF expression of ++ // a DW_AT_location attribute may add to and/or remove from values on the ++ // stack. Execution returns to the point following the call when the end of ++ // the attribute is reached. Values on the stack at the time of the call ++ // may be used as parameters by the called expression and values left on ++ // the stack by the called expression may be used as return values by prior ++ // agreement between the calling and called expressions. ++ case DW_OP_call2: ++ if (error_ptr) ++ error_ptr->SetErrorString("Unimplemented opcode DW_OP_call2."); ++ return false; ++ // OPCODE: DW_OP_call4 ++ // OPERANDS: 1 ++ // uint32_t compile unit relative offset of a DIE ++ // DESCRIPTION: Performs a subroutine call during evaluation of a DWARF ++ // expression. For DW_OP_call4, the operand is a 4-byte unsigned offset of ++ // a debugging information entry in the current compilation unit. ++ // ++ // Operand interpretation DW_OP_call4 is exactly like that for ++ // DW_FORM_ref4. ++ // ++ // This operation transfers control of DWARF expression evaluation to the ++ // DW_AT_location attribute of the referenced DIE. If there is no such ++ // attribute, then there is no effect. Execution of the DWARF expression of ++ // a DW_AT_location attribute may add to and/or remove from values on the ++ // stack. Execution returns to the point following the call when the end of ++ // the attribute is reached. Values on the stack at the time of the call ++ // may be used as parameters by the called expression and values left on ++ // the stack by the called expression may be used as return values by prior ++ // agreement between the calling and called expressions. ++ case DW_OP_call4: ++ if (error_ptr) ++ error_ptr->SetErrorString("Unimplemented opcode DW_OP_call4."); ++ return false; ++ ++ // OPCODE: DW_OP_stack_value ++ // OPERANDS: None ++ // DESCRIPTION: Specifies that the object does not exist in memory but ++ // rather is a constant value. The value from the top of the stack is the ++ // value to be used. This is the actual object value and not the location. ++ case DW_OP_stack_value: ++ if (stack.empty()) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 1 item for DW_OP_stack_value."); ++ return false; ++ } ++ stack.back().SetValueType(Value::ValueType::Scalar); ++ break; ++ ++ // OPCODE: DW_OP_convert ++ // OPERANDS: 1 ++ // A ULEB128 that is either a DIE offset of a ++ // DW_TAG_base_type or 0 for the generic (pointer-sized) type. ++ // ++ // DESCRIPTION: Pop the top stack element, convert it to a ++ // different type, and push the result. ++ case DW_OP_convert: { ++ if (stack.size() < 1) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Expression stack needs at least 1 item for DW_OP_convert."); ++ return false; ++ } ++ const uint64_t die_offset = opcodes.GetULEB128(&offset); ++ uint64_t bit_size; ++ bool sign; ++ if (die_offset == 0) { ++ // The generic type has the size of an address on the target ++ // machine and an unspecified signedness. Scalar has no ++ // "unspecified signedness", so we use unsigned types. ++ if (!module_sp) { ++ if (error_ptr) ++ error_ptr->SetErrorString("No module"); ++ return false; ++ } ++ sign = false; ++ bit_size = module_sp->GetArchitecture().GetAddressByteSize() * 8; ++ if (!bit_size) { ++ if (error_ptr) ++ error_ptr->SetErrorString("unspecified architecture"); ++ return false; ++ } ++ } else { ++ // Retrieve the type DIE that the value is being converted to. ++ // FIXME: the constness has annoying ripple effects. ++ DWARFDIE die = const_cast(dwarf_cu)->GetDIE(die_offset); ++ if (!die) { ++ if (error_ptr) ++ error_ptr->SetErrorString("Cannot resolve DW_OP_convert type DIE"); ++ return false; ++ } ++ uint64_t encoding = ++ die.GetAttributeValueAsUnsigned(DW_AT_encoding, DW_ATE_hi_user); ++ bit_size = die.GetAttributeValueAsUnsigned(DW_AT_byte_size, 0) * 8; ++ if (!bit_size) ++ bit_size = die.GetAttributeValueAsUnsigned(DW_AT_bit_size, 0); ++ if (!bit_size) { ++ if (error_ptr) ++ error_ptr->SetErrorString("Unsupported type size in DW_OP_convert"); ++ return false; ++ } ++ switch (encoding) { ++ case DW_ATE_signed: ++ case DW_ATE_signed_char: ++ sign = true; ++ break; ++ case DW_ATE_unsigned: ++ case DW_ATE_unsigned_char: ++ sign = false; ++ break; ++ default: ++ if (error_ptr) ++ error_ptr->SetErrorString("Unsupported encoding in DW_OP_convert"); ++ return false; ++ } ++ } ++ Scalar &top = stack.back().ResolveValue(m_exe_ctx); ++ top.TruncOrExtendTo(bit_size, sign); ++ break; ++ } ++ ++ // OPCODE: DW_OP_call_frame_cfa ++ // OPERANDS: None ++ // DESCRIPTION: Specifies a DWARF expression that pushes the value of ++ // the canonical frame address consistent with the call frame information ++ // located in .debug_frame (or in the FDEs of the eh_frame section). ++ case DW_OP_call_frame_cfa: ++ if (frame) { ++ // Note that we don't have to parse FDEs because this DWARF expression ++ // is commonly evaluated with a valid stack frame. ++ StackID id = frame->GetStackID(); ++ addr_t cfa = id.GetCallFrameAddress(); ++ if (cfa != LLDB_INVALID_ADDRESS) { ++ stack.push_back(Scalar(cfa)); ++ stack.back().SetValueType(Value::ValueType::LoadAddress); ++ } else if (error_ptr) ++ error_ptr->SetErrorString("Stack frame does not include a canonical " ++ "frame address for DW_OP_call_frame_cfa " ++ "opcode."); ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorString("Invalid stack frame in context for " ++ "DW_OP_call_frame_cfa opcode."); ++ return false; ++ } ++ break; ++ ++ // OPCODE: DW_OP_form_tls_address (or the old pre-DWARFv3 vendor extension ++ // opcode, DW_OP_GNU_push_tls_address) ++ // OPERANDS: none ++ // DESCRIPTION: Pops a TLS offset from the stack, converts it to ++ // an address in the current thread's thread-local storage block, and ++ // pushes it on the stack. ++ case DW_OP_form_tls_address: ++ case DW_OP_GNU_push_tls_address: { ++ if (stack.size() < 1) { ++ if (error_ptr) { ++ if (op == DW_OP_form_tls_address) ++ error_ptr->SetErrorString( ++ "DW_OP_form_tls_address needs an argument."); ++ else ++ error_ptr->SetErrorString( ++ "DW_OP_GNU_push_tls_address needs an argument."); ++ } ++ return false; ++ } ++ ++ if (!m_exe_ctx || !module_sp) { ++ if (error_ptr) ++ error_ptr->SetErrorString("No context to evaluate TLS within."); ++ return false; ++ } ++ ++ Thread *thread = m_exe_ctx->GetThreadPtr(); ++ if (!thread) { ++ if (error_ptr) ++ error_ptr->SetErrorString("No thread to evaluate TLS within."); ++ return false; ++ } ++ ++ // Lookup the TLS block address for this thread and module. ++ const addr_t tls_file_addr = ++ stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS); ++ const addr_t tls_load_addr = ++ thread->GetThreadLocalData(module_sp, tls_file_addr); ++ ++ if (tls_load_addr == LLDB_INVALID_ADDRESS) { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "No TLS data currently exists for this thread."); ++ return false; ++ } ++ ++ stack.back().GetScalar() = tls_load_addr; ++ stack.back().SetValueType(Value::ValueType::LoadAddress); ++ } break; ++ ++ // OPCODE: DW_OP_addrx (DW_OP_GNU_addr_index is the legacy name.) ++ // OPERANDS: 1 ++ // ULEB128: index to the .debug_addr section ++ // DESCRIPTION: Pushes an address to the stack from the .debug_addr ++ // section with the base address specified by the DW_AT_addr_base attribute ++ // and the 0 based index is the ULEB128 encoded index. ++ case DW_OP_addrx: ++ case DW_OP_GNU_addr_index: { ++ if (!dwarf_cu) { ++ if (error_ptr) ++ error_ptr->SetErrorString("DW_OP_GNU_addr_index found without a " ++ "compile unit being specified"); ++ return false; ++ } ++ uint64_t index = opcodes.GetULEB128(&offset); ++ lldb::addr_t value = ++ DWARFExpression::ReadAddressFromDebugAddrSection(dwarf_cu, index); ++ stack.push_back(Scalar(value)); ++ stack.back().SetValueType(Value::ValueType::FileAddress); ++ } break; ++ ++ // OPCODE: DW_OP_GNU_const_index ++ // OPERANDS: 1 ++ // ULEB128: index to the .debug_addr section ++ // DESCRIPTION: Pushes an constant with the size of a machine address to ++ // the stack from the .debug_addr section with the base address specified ++ // by the DW_AT_addr_base attribute and the 0 based index is the ULEB128 ++ // encoded index. ++ case DW_OP_GNU_const_index: { ++ if (!dwarf_cu) { ++ if (error_ptr) ++ error_ptr->SetErrorString("DW_OP_GNU_const_index found without a " ++ "compile unit being specified"); ++ return false; ++ } ++ uint64_t index = opcodes.GetULEB128(&offset); ++ lldb::addr_t value = ++ DWARFExpression::ReadAddressFromDebugAddrSection(dwarf_cu, index); ++ stack.push_back(Scalar(value)); ++ } break; ++ ++ case DW_OP_entry_value: { ++ if (!Evaluate_DW_OP_entry_value(stack, m_exe_ctx, m_reg_ctx, opcodes, ++ offset, error_ptr, log)) { ++ LLDB_ERRORF(error_ptr, "Could not evaluate %s.", DW_OP_value_to_name(op)); ++ return false; ++ } ++ break; ++ } ++ ++ default: ++ LLDB_LOGF(log, "Unhandled opcode %s in DWARFExpression.", ++ DW_OP_value_to_name(op)); ++ break; ++ } ++ ++ return true; ++} +diff --git a/lldb/source/Expression/DWARFEvaluatorFactory.cpp b/lldb/source/Expression/DWARFEvaluatorFactory.cpp +new file mode 100644 +index 000000000000..c0612641204a +--- /dev/null ++++ b/lldb/source/Expression/DWARFEvaluatorFactory.cpp +@@ -0,0 +1,57 @@ ++//===-- DWARFEvaluatorFactory.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 "lldb/Expression/DWARFEvaluatorFactory.h" ++#include "lldb/Expression/DWARFEvaluator.h" ++ ++#include "lldb/Core/PluginManager.h" ++#include "lldb/Core/Value.h" ++#include "lldb/Target/RegisterContext.h" ++ ++using namespace lldb; ++using namespace lldb_private; ++ ++// PluginInterface protocol ++lldb_private::ConstString DWARFEvaluatorFactory::GetPluginName() { ++ static ConstString g_name("vendor-default"); ++ return g_name; ++} ++ ++// FindPlugin ++// ++// Platforms can register a callback to use when creating DWARF expression ++// evaluators to allow handling platform-specific DWARF codes. ++std::unique_ptr ++DWARFEvaluatorFactory::FindPlugin(Module *module) { ++ std::unique_ptr instance_up; ++ DWARFEvaluatorFactoryCreateInstance create_callback; ++ ++ for (size_t idx = 0; ++ (create_callback = ++ PluginManager::GetDWARFEvaluatorFactoryCreateCallbackAtIndex( ++ idx)) != nullptr; ++ ++idx) { ++ instance_up.reset(create_callback(module)); ++ ++ if (instance_up) { ++ return instance_up; ++ } ++ } ++ ++ instance_up.reset(new DWARFEvaluatorFactory()); ++ return instance_up; ++} ++ ++std::unique_ptr DWARFEvaluatorFactory::CreateDWARFEvaluator( ++ const DWARFExpression &dwarf_expression, ExecutionContext *exe_ctx, ++ RegisterContext *reg_ctx, const Value *initial_value_ptr, ++ const Value *object_address_ptr) { ++ return std::make_unique(dwarf_expression, exe_ctx, reg_ctx, ++ initial_value_ptr, ++ object_address_ptr); ++} +diff --git a/lldb/source/Expression/DWARFExpression.cpp b/lldb/source/Expression/DWARFExpression.cpp +index a10546c1deae..4d13e4642af3 100644 +--- a/lldb/source/Expression/DWARFExpression.cpp ++++ b/lldb/source/Expression/DWARFExpression.cpp +@@ -15,6 +15,8 @@ + #include "lldb/Core/Module.h" + #include "lldb/Core/Value.h" + #include "lldb/Core/dwarf.h" ++#include "lldb/Expression/DWARFEvaluator.h" ++#include "lldb/Expression/DWARFEvaluatorFactory.h" + #include "lldb/Utility/DataEncoder.h" + #include "lldb/Utility/Log.h" + #include "lldb/Utility/RegisterValue.h" +@@ -41,8 +43,8 @@ + using namespace lldb; + using namespace lldb_private; + +-static lldb::addr_t +-ReadAddressFromDebugAddrSection(const DWARFUnit *dwarf_cu, ++lldb::addr_t ++DWARFExpression::ReadAddressFromDebugAddrSection(const DWARFUnit *dwarf_cu, + uint32_t index) { + uint32_t index_size = dwarf_cu->GetAddressByteSize(); + dw_offset_t addr_base = dwarf_cu->GetAddrBase(); +@@ -96,7 +98,7 @@ void DWARFExpression::SetLocationListAddresses(addr_t cu_file_addr, + m_loclist_addresses = LoclistAddresses{cu_file_addr, func_file_addr}; + } + +-int DWARFExpression::GetRegisterKind() { return m_reg_kind; } ++RegisterKind DWARFExpression::GetRegisterKind() const { return m_reg_kind; } + + void DWARFExpression::SetRegisterKind(RegisterKind reg_kind) { + m_reg_kind = reg_kind; +@@ -150,52 +152,6 @@ void DWARFExpression::GetDescription(Stream *s, lldb::DescriptionLevel level, + } + } + +-static bool ReadRegisterValueAsScalar(RegisterContext *reg_ctx, +- lldb::RegisterKind reg_kind, +- uint32_t reg_num, Status *error_ptr, +- Value &value) { +- if (reg_ctx == nullptr) { +- if (error_ptr) +- error_ptr->SetErrorString("No register context in frame.\n"); +- } else { +- uint32_t native_reg = +- reg_ctx->ConvertRegisterKindToRegisterNumber(reg_kind, reg_num); +- if (native_reg == LLDB_INVALID_REGNUM) { +- if (error_ptr) +- error_ptr->SetErrorStringWithFormat("Unable to convert register " +- "kind=%u reg_num=%u to a native " +- "register number.\n", +- reg_kind, reg_num); +- } else { +- const RegisterInfo *reg_info = +- reg_ctx->GetRegisterInfoAtIndex(native_reg); +- RegisterValue reg_value; +- if (reg_ctx->ReadRegister(reg_info, reg_value)) { +- if (reg_value.GetScalarValue(value.GetScalar())) { +- value.SetValueType(Value::ValueType::Scalar); +- value.SetContext(Value::ContextType::RegisterInfo, +- const_cast(reg_info)); +- if (error_ptr) +- error_ptr->Clear(); +- return true; +- } else { +- // If we get this error, then we need to implement a value buffer in +- // the dwarf expression evaluation function... +- if (error_ptr) +- error_ptr->SetErrorStringWithFormat( +- "register %s can't be converted to a scalar value", +- reg_info->name); +- } +- } else { +- if (error_ptr) +- error_ptr->SetErrorStringWithFormat("register %s is not available", +- reg_info->name); +- } +- } +- } +- return false; +-} +- + /// Return the length in bytes of the set of operands for \p op. No guarantees + /// are made on the state of \p data after this call. + static offset_t GetOpcodeDataSize(const DataExtractor &data, +@@ -955,1719 +911,17 @@ bool DWARFExpression::Evaluate( + const Value *initial_value_ptr, const Value *object_address_ptr, + Value &result, Status *error_ptr) { + +- if (opcodes.GetByteSize() == 0) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "no location, value may have been optimized out"); +- return false; +- } +- std::vector stack; +- +- Process *process = nullptr; +- StackFrame *frame = nullptr; +- +- if (exe_ctx) { +- process = exe_ctx->GetProcessPtr(); +- frame = exe_ctx->GetFramePtr(); +- } +- if (reg_ctx == nullptr && frame) +- reg_ctx = frame->GetRegisterContext().get(); +- +- if (initial_value_ptr) +- stack.push_back(*initial_value_ptr); +- +- lldb::offset_t offset = 0; +- Value tmp; +- uint32_t reg_num; +- +- /// Insertion point for evaluating multi-piece expression. +- uint64_t op_piece_offset = 0; +- Value pieces; // Used for DW_OP_piece +- +- Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); +- // A generic type is "an integral type that has the size of an address and an +- // unspecified signedness". For now, just use the signedness of the operand. +- // TODO: Implement a real typed stack, and store the genericness of the value +- // there. +- auto to_generic = [&](auto v) { +- bool is_signed = std::is_signed::value; +- return Scalar(llvm::APSInt( +- llvm::APInt(8 * opcodes.GetAddressByteSize(), v, is_signed), +- !is_signed)); +- }; +- +- // The default kind is a memory location. This is updated by any +- // operation that changes this, such as DW_OP_stack_value, and reset +- // by composition operations like DW_OP_piece. +- LocationDescriptionKind dwarf4_location_description_kind = Memory; +- +- while (opcodes.ValidOffset(offset)) { +- const lldb::offset_t op_offset = offset; +- const uint8_t op = opcodes.GetU8(&offset); +- +- if (log && log->GetVerbose()) { +- size_t count = stack.size(); +- LLDB_LOGF(log, "Stack before operation has %" PRIu64 " values:", +- (uint64_t)count); +- for (size_t i = 0; i < count; ++i) { +- StreamString new_value; +- new_value.Printf("[%" PRIu64 "]", (uint64_t)i); +- stack[i].Dump(&new_value); +- LLDB_LOGF(log, " %s", new_value.GetData()); +- } +- LLDB_LOGF(log, "0x%8.8" PRIx64 ": %s", op_offset, +- DW_OP_value_to_name(op)); +- } +- +- switch (op) { +- // The DW_OP_addr operation has a single operand that encodes a machine +- // address and whose size is the size of an address on the target machine. +- case DW_OP_addr: +- stack.push_back(Scalar(opcodes.GetAddress(&offset))); +- stack.back().SetValueType(Value::ValueType::FileAddress); +- // Convert the file address to a load address, so subsequent +- // DWARF operators can operate on it. +- if (frame) +- stack.back().ConvertToLoadAddress(module_sp.get(), +- frame->CalculateTarget().get()); +- break; +- +- // The DW_OP_addr_sect_offset4 is used for any location expressions in +- // shared libraries that have a location like: +- // DW_OP_addr(0x1000) +- // If this address resides in a shared library, then this virtual address +- // won't make sense when it is evaluated in the context of a running +- // process where shared libraries have been slid. To account for this, this +- // new address type where we can store the section pointer and a 4 byte +- // offset. +- // case DW_OP_addr_sect_offset4: +- // { +- // result_type = eResultTypeFileAddress; +- // lldb::Section *sect = (lldb::Section +- // *)opcodes.GetMaxU64(&offset, sizeof(void *)); +- // lldb::addr_t sect_offset = opcodes.GetU32(&offset); +- // +- // Address so_addr (sect, sect_offset); +- // lldb::addr_t load_addr = so_addr.GetLoadAddress(); +- // if (load_addr != LLDB_INVALID_ADDRESS) +- // { +- // // We successfully resolve a file address to a load +- // // address. +- // stack.push_back(load_addr); +- // break; +- // } +- // else +- // { +- // // We were able +- // if (error_ptr) +- // error_ptr->SetErrorStringWithFormat ("Section %s in +- // %s is not currently loaded.\n", +- // sect->GetName().AsCString(), +- // sect->GetModule()->GetFileSpec().GetFilename().AsCString()); +- // return false; +- // } +- // } +- // break; +- +- // OPCODE: DW_OP_deref +- // OPERANDS: none +- // DESCRIPTION: Pops the top stack entry and treats it as an address. +- // The value retrieved from that address is pushed. The size of the data +- // retrieved from the dereferenced address is the size of an address on the +- // target machine. +- case DW_OP_deref: { +- if (stack.empty()) { +- if (error_ptr) +- error_ptr->SetErrorString("Expression stack empty for DW_OP_deref."); +- return false; +- } +- Value::ValueType value_type = stack.back().GetValueType(); +- switch (value_type) { +- case Value::ValueType::HostAddress: { +- void *src = (void *)stack.back().GetScalar().ULongLong(); +- intptr_t ptr; +- ::memcpy(&ptr, src, sizeof(void *)); +- stack.back().GetScalar() = ptr; +- stack.back().ClearContext(); +- } break; +- case Value::ValueType::FileAddress: { +- auto file_addr = stack.back().GetScalar().ULongLong( +- LLDB_INVALID_ADDRESS); +- if (!module_sp) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "need module to resolve file address for DW_OP_deref"); +- return false; +- } +- Address so_addr; +- if (!module_sp->ResolveFileAddress(file_addr, so_addr)) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "failed to resolve file address in module"); +- return false; +- } +- addr_t load_Addr = so_addr.GetLoadAddress(exe_ctx->GetTargetPtr()); +- if (load_Addr == LLDB_INVALID_ADDRESS) { +- if (error_ptr) +- error_ptr->SetErrorString("failed to resolve load address"); +- return false; +- } +- stack.back().GetScalar() = load_Addr; +- // Fall through to load address promotion code below. +- } LLVM_FALLTHROUGH; +- case Value::ValueType::Scalar: +- // Promote Scalar to LoadAddress and fall through. +- stack.back().SetValueType(Value::ValueType::LoadAddress); +- LLVM_FALLTHROUGH; +- case Value::ValueType::LoadAddress: +- if (exe_ctx) { +- if (process) { +- lldb::addr_t pointer_addr = +- stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS); +- Status error; +- lldb::addr_t pointer_value = +- process->ReadPointerFromMemory(pointer_addr, error); +- if (pointer_value != LLDB_INVALID_ADDRESS) { +- if (ABISP abi_sp = process->GetABI()) +- pointer_value = abi_sp->FixCodeAddress(pointer_value); +- stack.back().GetScalar() = pointer_value; +- stack.back().ClearContext(); +- } else { +- if (error_ptr) +- error_ptr->SetErrorStringWithFormat( +- "Failed to dereference pointer from 0x%" PRIx64 +- " for DW_OP_deref: %s\n", +- pointer_addr, error.AsCString()); +- return false; +- } +- } else { +- if (error_ptr) +- error_ptr->SetErrorString("NULL process for DW_OP_deref.\n"); +- return false; +- } +- } else { +- if (error_ptr) +- error_ptr->SetErrorString( +- "NULL execution context for DW_OP_deref.\n"); +- return false; +- } +- break; +- +- case Value::ValueType::Invalid: +- if (error_ptr) +- error_ptr->SetErrorString("Invalid value type for DW_OP_deref.\n"); +- return false; +- } +- +- } break; +- +- // OPCODE: DW_OP_deref_size +- // OPERANDS: 1 +- // 1 - uint8_t that specifies the size of the data to dereference. +- // DESCRIPTION: Behaves like the DW_OP_deref operation: it pops the top +- // stack entry and treats it as an address. The value retrieved from that +- // address is pushed. In the DW_OP_deref_size operation, however, the size +- // in bytes of the data retrieved from the dereferenced address is +- // specified by the single operand. This operand is a 1-byte unsigned +- // integral constant whose value may not be larger than the size of an +- // address on the target machine. The data retrieved is zero extended to +- // the size of an address on the target machine before being pushed on the +- // expression stack. +- case DW_OP_deref_size: { +- if (stack.empty()) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack empty for DW_OP_deref_size."); +- return false; +- } +- uint8_t size = opcodes.GetU8(&offset); +- Value::ValueType value_type = stack.back().GetValueType(); +- switch (value_type) { +- case Value::ValueType::HostAddress: { +- void *src = (void *)stack.back().GetScalar().ULongLong(); +- intptr_t ptr; +- ::memcpy(&ptr, src, sizeof(void *)); +- // I can't decide whether the size operand should apply to the bytes in +- // their +- // lldb-host endianness or the target endianness.. I doubt this'll ever +- // come up but I'll opt for assuming big endian regardless. +- switch (size) { +- case 1: +- ptr = ptr & 0xff; +- break; +- case 2: +- ptr = ptr & 0xffff; +- break; +- case 3: +- ptr = ptr & 0xffffff; +- break; +- case 4: +- ptr = ptr & 0xffffffff; +- break; +- // the casts are added to work around the case where intptr_t is a 32 +- // bit quantity; +- // presumably we won't hit the 5..7 cases if (void*) is 32-bits in this +- // program. +- case 5: +- ptr = (intptr_t)ptr & 0xffffffffffULL; +- break; +- case 6: +- ptr = (intptr_t)ptr & 0xffffffffffffULL; +- break; +- case 7: +- ptr = (intptr_t)ptr & 0xffffffffffffffULL; +- break; +- default: +- break; +- } +- stack.back().GetScalar() = ptr; +- stack.back().ClearContext(); +- } break; +- case Value::ValueType::Scalar: +- case Value::ValueType::LoadAddress: +- if (exe_ctx) { +- if (process) { +- lldb::addr_t pointer_addr = +- stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS); +- uint8_t addr_bytes[sizeof(lldb::addr_t)]; +- Status error; +- if (process->ReadMemory(pointer_addr, &addr_bytes, size, error) == +- size) { +- DataExtractor addr_data(addr_bytes, sizeof(addr_bytes), +- process->GetByteOrder(), size); +- lldb::offset_t addr_data_offset = 0; +- switch (size) { +- case 1: +- stack.back().GetScalar() = addr_data.GetU8(&addr_data_offset); +- break; +- case 2: +- stack.back().GetScalar() = addr_data.GetU16(&addr_data_offset); +- break; +- case 4: +- stack.back().GetScalar() = addr_data.GetU32(&addr_data_offset); +- break; +- case 8: +- stack.back().GetScalar() = addr_data.GetU64(&addr_data_offset); +- break; +- default: +- stack.back().GetScalar() = +- addr_data.GetAddress(&addr_data_offset); +- } +- stack.back().ClearContext(); +- } else { +- if (error_ptr) +- error_ptr->SetErrorStringWithFormat( +- "Failed to dereference pointer from 0x%" PRIx64 +- " for DW_OP_deref: %s\n", +- pointer_addr, error.AsCString()); +- return false; +- } +- } else { +- if (error_ptr) +- error_ptr->SetErrorString("NULL process for DW_OP_deref_size.\n"); +- return false; +- } +- } else { +- if (error_ptr) +- error_ptr->SetErrorString( +- "NULL execution context for DW_OP_deref_size.\n"); +- return false; +- } +- break; +- +- case Value::ValueType::FileAddress: +- case Value::ValueType::Invalid: +- if (error_ptr) +- error_ptr->SetErrorString("Invalid value for DW_OP_deref_size.\n"); +- return false; +- } +- +- } break; +- +- // OPCODE: DW_OP_xderef_size +- // OPERANDS: 1 +- // 1 - uint8_t that specifies the size of the data to dereference. +- // DESCRIPTION: Behaves like the DW_OP_xderef operation: the entry at +- // the top of the stack is treated as an address. The second stack entry is +- // treated as an "address space identifier" for those architectures that +- // support multiple address spaces. The top two stack elements are popped, +- // a data item is retrieved through an implementation-defined address +- // calculation and pushed as the new stack top. In the DW_OP_xderef_size +- // operation, however, the size in bytes of the data retrieved from the +- // dereferenced address is specified by the single operand. This operand is +- // a 1-byte unsigned integral constant whose value may not be larger than +- // the size of an address on the target machine. The data retrieved is zero +- // extended to the size of an address on the target machine before being +- // pushed on the expression stack. +- case DW_OP_xderef_size: +- if (error_ptr) +- error_ptr->SetErrorString("Unimplemented opcode: DW_OP_xderef_size."); +- return false; +- // OPCODE: DW_OP_xderef +- // OPERANDS: none +- // DESCRIPTION: Provides an extended dereference mechanism. The entry at +- // the top of the stack is treated as an address. The second stack entry is +- // treated as an "address space identifier" for those architectures that +- // support multiple address spaces. The top two stack elements are popped, +- // a data item is retrieved through an implementation-defined address +- // calculation and pushed as the new stack top. The size of the data +- // retrieved from the dereferenced address is the size of an address on the +- // target machine. +- case DW_OP_xderef: +- if (error_ptr) +- error_ptr->SetErrorString("Unimplemented opcode: DW_OP_xderef."); +- return false; +- +- // All DW_OP_constXXX opcodes have a single operand as noted below: +- // +- // Opcode Operand 1 +- // DW_OP_const1u 1-byte unsigned integer constant +- // DW_OP_const1s 1-byte signed integer constant +- // DW_OP_const2u 2-byte unsigned integer constant +- // DW_OP_const2s 2-byte signed integer constant +- // DW_OP_const4u 4-byte unsigned integer constant +- // DW_OP_const4s 4-byte signed integer constant +- // DW_OP_const8u 8-byte unsigned integer constant +- // DW_OP_const8s 8-byte signed integer constant +- // DW_OP_constu unsigned LEB128 integer constant +- // DW_OP_consts signed LEB128 integer constant +- case DW_OP_const1u: +- stack.push_back(to_generic(opcodes.GetU8(&offset))); +- break; +- case DW_OP_const1s: +- stack.push_back(to_generic((int8_t)opcodes.GetU8(&offset))); +- break; +- case DW_OP_const2u: +- stack.push_back(to_generic(opcodes.GetU16(&offset))); +- break; +- case DW_OP_const2s: +- stack.push_back(to_generic((int16_t)opcodes.GetU16(&offset))); +- break; +- case DW_OP_const4u: +- stack.push_back(to_generic(opcodes.GetU32(&offset))); +- break; +- case DW_OP_const4s: +- stack.push_back(to_generic((int32_t)opcodes.GetU32(&offset))); +- break; +- case DW_OP_const8u: +- stack.push_back(to_generic(opcodes.GetU64(&offset))); +- break; +- case DW_OP_const8s: +- stack.push_back(to_generic((int64_t)opcodes.GetU64(&offset))); +- break; +- // These should also use to_generic, but we can't do that due to a +- // producer-side bug in llvm. See llvm.org/pr48087. +- case DW_OP_constu: +- stack.push_back(Scalar(opcodes.GetULEB128(&offset))); +- break; +- case DW_OP_consts: +- stack.push_back(Scalar(opcodes.GetSLEB128(&offset))); +- break; +- +- // OPCODE: DW_OP_dup +- // OPERANDS: none +- // DESCRIPTION: duplicates the value at the top of the stack +- case DW_OP_dup: +- if (stack.empty()) { +- if (error_ptr) +- error_ptr->SetErrorString("Expression stack empty for DW_OP_dup."); +- return false; +- } else +- stack.push_back(stack.back()); +- break; +- +- // OPCODE: DW_OP_drop +- // OPERANDS: none +- // DESCRIPTION: pops the value at the top of the stack +- case DW_OP_drop: +- if (stack.empty()) { +- if (error_ptr) +- error_ptr->SetErrorString("Expression stack empty for DW_OP_drop."); +- return false; +- } else +- stack.pop_back(); +- break; +- +- // OPCODE: DW_OP_over +- // OPERANDS: none +- // DESCRIPTION: Duplicates the entry currently second in the stack at +- // the top of the stack. +- case DW_OP_over: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_over."); +- return false; +- } else +- stack.push_back(stack[stack.size() - 2]); +- break; +- +- // OPCODE: DW_OP_pick +- // OPERANDS: uint8_t index into the current stack +- // DESCRIPTION: The stack entry with the specified index (0 through 255, +- // inclusive) is pushed on the stack +- case DW_OP_pick: { +- uint8_t pick_idx = opcodes.GetU8(&offset); +- if (pick_idx < stack.size()) +- stack.push_back(stack[stack.size() - 1 - pick_idx]); +- else { +- if (error_ptr) +- error_ptr->SetErrorStringWithFormat( +- "Index %u out of range for DW_OP_pick.\n", pick_idx); +- return false; +- } +- } break; +- +- // OPCODE: DW_OP_swap +- // OPERANDS: none +- // DESCRIPTION: swaps the top two stack entries. The entry at the top +- // of the stack becomes the second stack entry, and the second entry +- // becomes the top of the stack +- case DW_OP_swap: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_swap."); +- return false; +- } else { +- tmp = stack.back(); +- stack.back() = stack[stack.size() - 2]; +- stack[stack.size() - 2] = tmp; +- } +- break; +- +- // OPCODE: DW_OP_rot +- // OPERANDS: none +- // DESCRIPTION: Rotates the first three stack entries. The entry at +- // the top of the stack becomes the third stack entry, the second entry +- // becomes the top of the stack, and the third entry becomes the second +- // entry. +- case DW_OP_rot: +- if (stack.size() < 3) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 3 items for DW_OP_rot."); +- return false; +- } else { +- size_t last_idx = stack.size() - 1; +- Value old_top = stack[last_idx]; +- stack[last_idx] = stack[last_idx - 1]; +- stack[last_idx - 1] = stack[last_idx - 2]; +- stack[last_idx - 2] = old_top; +- } +- break; +- +- // OPCODE: DW_OP_abs +- // OPERANDS: none +- // DESCRIPTION: pops the top stack entry, interprets it as a signed +- // value and pushes its absolute value. If the absolute value can not be +- // represented, the result is undefined. +- case DW_OP_abs: +- if (stack.empty()) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 1 item for DW_OP_abs."); +- return false; +- } else if (!stack.back().ResolveValue(exe_ctx).AbsoluteValue()) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Failed to take the absolute value of the first stack item."); +- return false; +- } +- break; +- +- // OPCODE: DW_OP_and +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack values, performs a bitwise and +- // operation on the two, and pushes the result. +- case DW_OP_and: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_and."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) = +- stack.back().ResolveValue(exe_ctx) & tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_div +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack values, divides the former second +- // entry by the former top of the stack using signed division, and pushes +- // the result. +- case DW_OP_div: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_div."); +- return false; +- } else { +- tmp = stack.back(); +- if (tmp.ResolveValue(exe_ctx).IsZero()) { +- if (error_ptr) +- error_ptr->SetErrorString("Divide by zero."); +- return false; +- } else { +- stack.pop_back(); +- stack.back() = +- stack.back().ResolveValue(exe_ctx) / tmp.ResolveValue(exe_ctx); +- if (!stack.back().ResolveValue(exe_ctx).IsValid()) { +- if (error_ptr) +- error_ptr->SetErrorString("Divide failed."); +- return false; +- } +- } +- } +- break; +- +- // OPCODE: DW_OP_minus +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack values, subtracts the former top +- // of the stack from the former second entry, and pushes the result. +- case DW_OP_minus: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_minus."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) = +- stack.back().ResolveValue(exe_ctx) - tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_mod +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack values and pushes the result of +- // the calculation: former second stack entry modulo the former top of the +- // stack. +- case DW_OP_mod: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_mod."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) = +- stack.back().ResolveValue(exe_ctx) % tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_mul +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack entries, multiplies them +- // together, and pushes the result. +- case DW_OP_mul: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_mul."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) = +- stack.back().ResolveValue(exe_ctx) * tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_neg +- // OPERANDS: none +- // DESCRIPTION: pops the top stack entry, and pushes its negation. +- case DW_OP_neg: +- if (stack.empty()) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 1 item for DW_OP_neg."); +- return false; +- } else { +- if (!stack.back().ResolveValue(exe_ctx).UnaryNegate()) { +- if (error_ptr) +- error_ptr->SetErrorString("Unary negate failed."); +- return false; +- } +- } +- break; +- +- // OPCODE: DW_OP_not +- // OPERANDS: none +- // DESCRIPTION: pops the top stack entry, and pushes its bitwise +- // complement +- case DW_OP_not: +- if (stack.empty()) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 1 item for DW_OP_not."); +- return false; +- } else { +- if (!stack.back().ResolveValue(exe_ctx).OnesComplement()) { +- if (error_ptr) +- error_ptr->SetErrorString("Logical NOT failed."); +- return false; +- } +- } +- break; +- +- // OPCODE: DW_OP_or +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack entries, performs a bitwise or +- // operation on the two, and pushes the result. +- case DW_OP_or: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_or."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) = +- stack.back().ResolveValue(exe_ctx) | tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_plus +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack entries, adds them together, and +- // pushes the result. +- case DW_OP_plus: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_plus."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().GetScalar() += tmp.GetScalar(); +- } +- break; +- +- // OPCODE: DW_OP_plus_uconst +- // OPERANDS: none +- // DESCRIPTION: pops the top stack entry, adds it to the unsigned LEB128 +- // constant operand and pushes the result. +- case DW_OP_plus_uconst: +- if (stack.empty()) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 1 item for DW_OP_plus_uconst."); +- return false; +- } else { +- const uint64_t uconst_value = opcodes.GetULEB128(&offset); +- // Implicit conversion from a UINT to a Scalar... +- stack.back().GetScalar() += uconst_value; +- if (!stack.back().GetScalar().IsValid()) { +- if (error_ptr) +- error_ptr->SetErrorString("DW_OP_plus_uconst failed."); +- return false; +- } +- } +- break; +- +- // OPCODE: DW_OP_shl +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack entries, shifts the former +- // second entry left by the number of bits specified by the former top of +- // the stack, and pushes the result. +- case DW_OP_shl: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_shl."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) <<= tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_shr +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack entries, shifts the former second +- // entry right logically (filling with zero bits) by the number of bits +- // specified by the former top of the stack, and pushes the result. +- case DW_OP_shr: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_shr."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- if (!stack.back().ResolveValue(exe_ctx).ShiftRightLogical( +- tmp.ResolveValue(exe_ctx))) { +- if (error_ptr) +- error_ptr->SetErrorString("DW_OP_shr failed."); +- return false; +- } +- } +- break; +- +- // OPCODE: DW_OP_shra +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack entries, shifts the former second +- // entry right arithmetically (divide the magnitude by 2, keep the same +- // sign for the result) by the number of bits specified by the former top +- // of the stack, and pushes the result. +- case DW_OP_shra: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_shra."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) >>= tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_xor +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack entries, performs the bitwise +- // exclusive-or operation on the two, and pushes the result. +- case DW_OP_xor: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_xor."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) = +- stack.back().ResolveValue(exe_ctx) ^ tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_skip +- // OPERANDS: int16_t +- // DESCRIPTION: An unconditional branch. Its single operand is a 2-byte +- // signed integer constant. The 2-byte constant is the number of bytes of +- // the DWARF expression to skip forward or backward from the current +- // operation, beginning after the 2-byte constant. +- case DW_OP_skip: { +- int16_t skip_offset = (int16_t)opcodes.GetU16(&offset); +- lldb::offset_t new_offset = offset + skip_offset; +- if (opcodes.ValidOffset(new_offset)) +- offset = new_offset; +- else { +- if (error_ptr) +- error_ptr->SetErrorString("Invalid opcode offset in DW_OP_skip."); +- return false; +- } +- } break; +- +- // OPCODE: DW_OP_bra +- // OPERANDS: int16_t +- // DESCRIPTION: A conditional branch. Its single operand is a 2-byte +- // signed integer constant. This operation pops the top of stack. If the +- // value popped is not the constant 0, the 2-byte constant operand is the +- // number of bytes of the DWARF expression to skip forward or backward from +- // the current operation, beginning after the 2-byte constant. +- case DW_OP_bra: +- if (stack.empty()) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 1 item for DW_OP_bra."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- int16_t bra_offset = (int16_t)opcodes.GetU16(&offset); +- Scalar zero(0); +- if (tmp.ResolveValue(exe_ctx) != zero) { +- lldb::offset_t new_offset = offset + bra_offset; +- if (opcodes.ValidOffset(new_offset)) +- offset = new_offset; +- else { +- if (error_ptr) +- error_ptr->SetErrorString("Invalid opcode offset in DW_OP_bra."); +- return false; +- } +- } +- } +- break; +- +- // OPCODE: DW_OP_eq +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack values, compares using the +- // equals (==) operator. +- // STACK RESULT: push the constant value 1 onto the stack if the result +- // of the operation is true or the constant value 0 if the result of the +- // operation is false. +- case DW_OP_eq: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_eq."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) = +- stack.back().ResolveValue(exe_ctx) == tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_ge +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack values, compares using the +- // greater than or equal to (>=) operator. +- // STACK RESULT: push the constant value 1 onto the stack if the result +- // of the operation is true or the constant value 0 if the result of the +- // operation is false. +- case DW_OP_ge: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_ge."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) = +- stack.back().ResolveValue(exe_ctx) >= tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_gt +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack values, compares using the +- // greater than (>) operator. +- // STACK RESULT: push the constant value 1 onto the stack if the result +- // of the operation is true or the constant value 0 if the result of the +- // operation is false. +- case DW_OP_gt: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_gt."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) = +- stack.back().ResolveValue(exe_ctx) > tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_le +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack values, compares using the +- // less than or equal to (<=) operator. +- // STACK RESULT: push the constant value 1 onto the stack if the result +- // of the operation is true or the constant value 0 if the result of the +- // operation is false. +- case DW_OP_le: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_le."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) = +- stack.back().ResolveValue(exe_ctx) <= tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_lt +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack values, compares using the +- // less than (<) operator. +- // STACK RESULT: push the constant value 1 onto the stack if the result +- // of the operation is true or the constant value 0 if the result of the +- // operation is false. +- case DW_OP_lt: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_lt."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) = +- stack.back().ResolveValue(exe_ctx) < tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_ne +- // OPERANDS: none +- // DESCRIPTION: pops the top two stack values, compares using the +- // not equal (!=) operator. +- // STACK RESULT: push the constant value 1 onto the stack if the result +- // of the operation is true or the constant value 0 if the result of the +- // operation is false. +- case DW_OP_ne: +- if (stack.size() < 2) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 2 items for DW_OP_ne."); +- return false; +- } else { +- tmp = stack.back(); +- stack.pop_back(); +- stack.back().ResolveValue(exe_ctx) = +- stack.back().ResolveValue(exe_ctx) != tmp.ResolveValue(exe_ctx); +- } +- break; +- +- // OPCODE: DW_OP_litn +- // OPERANDS: none +- // DESCRIPTION: encode the unsigned literal values from 0 through 31. +- // STACK RESULT: push the unsigned literal constant value onto the top +- // of the stack. +- case DW_OP_lit0: +- case DW_OP_lit1: +- case DW_OP_lit2: +- case DW_OP_lit3: +- case DW_OP_lit4: +- case DW_OP_lit5: +- case DW_OP_lit6: +- case DW_OP_lit7: +- case DW_OP_lit8: +- case DW_OP_lit9: +- case DW_OP_lit10: +- case DW_OP_lit11: +- case DW_OP_lit12: +- case DW_OP_lit13: +- case DW_OP_lit14: +- case DW_OP_lit15: +- case DW_OP_lit16: +- case DW_OP_lit17: +- case DW_OP_lit18: +- case DW_OP_lit19: +- case DW_OP_lit20: +- case DW_OP_lit21: +- case DW_OP_lit22: +- case DW_OP_lit23: +- case DW_OP_lit24: +- case DW_OP_lit25: +- case DW_OP_lit26: +- case DW_OP_lit27: +- case DW_OP_lit28: +- case DW_OP_lit29: +- case DW_OP_lit30: +- case DW_OP_lit31: +- stack.push_back(to_generic(op - DW_OP_lit0)); +- break; +- +- // OPCODE: DW_OP_regN +- // OPERANDS: none +- // DESCRIPTION: Push the value in register n on the top of the stack. +- case DW_OP_reg0: +- case DW_OP_reg1: +- case DW_OP_reg2: +- case DW_OP_reg3: +- case DW_OP_reg4: +- case DW_OP_reg5: +- case DW_OP_reg6: +- case DW_OP_reg7: +- case DW_OP_reg8: +- case DW_OP_reg9: +- case DW_OP_reg10: +- case DW_OP_reg11: +- case DW_OP_reg12: +- case DW_OP_reg13: +- case DW_OP_reg14: +- case DW_OP_reg15: +- case DW_OP_reg16: +- case DW_OP_reg17: +- case DW_OP_reg18: +- case DW_OP_reg19: +- case DW_OP_reg20: +- case DW_OP_reg21: +- case DW_OP_reg22: +- case DW_OP_reg23: +- case DW_OP_reg24: +- case DW_OP_reg25: +- case DW_OP_reg26: +- case DW_OP_reg27: +- case DW_OP_reg28: +- case DW_OP_reg29: +- case DW_OP_reg30: +- case DW_OP_reg31: { +- dwarf4_location_description_kind = Register; +- reg_num = op - DW_OP_reg0; +- +- if (ReadRegisterValueAsScalar(reg_ctx, reg_kind, reg_num, error_ptr, tmp)) +- stack.push_back(tmp); +- else +- return false; +- } break; +- // OPCODE: DW_OP_regx +- // OPERANDS: +- // ULEB128 literal operand that encodes the register. +- // DESCRIPTION: Push the value in register on the top of the stack. +- case DW_OP_regx: { +- dwarf4_location_description_kind = Register; +- reg_num = opcodes.GetULEB128(&offset); +- if (ReadRegisterValueAsScalar(reg_ctx, reg_kind, reg_num, error_ptr, tmp)) +- stack.push_back(tmp); +- else +- return false; +- } break; +- +- // OPCODE: DW_OP_bregN +- // OPERANDS: +- // SLEB128 offset from register N +- // DESCRIPTION: Value is in memory at the address specified by register +- // N plus an offset. +- case DW_OP_breg0: +- case DW_OP_breg1: +- case DW_OP_breg2: +- case DW_OP_breg3: +- case DW_OP_breg4: +- case DW_OP_breg5: +- case DW_OP_breg6: +- case DW_OP_breg7: +- case DW_OP_breg8: +- case DW_OP_breg9: +- case DW_OP_breg10: +- case DW_OP_breg11: +- case DW_OP_breg12: +- case DW_OP_breg13: +- case DW_OP_breg14: +- case DW_OP_breg15: +- case DW_OP_breg16: +- case DW_OP_breg17: +- case DW_OP_breg18: +- case DW_OP_breg19: +- case DW_OP_breg20: +- case DW_OP_breg21: +- case DW_OP_breg22: +- case DW_OP_breg23: +- case DW_OP_breg24: +- case DW_OP_breg25: +- case DW_OP_breg26: +- case DW_OP_breg27: +- case DW_OP_breg28: +- case DW_OP_breg29: +- case DW_OP_breg30: +- case DW_OP_breg31: { +- reg_num = op - DW_OP_breg0; +- +- if (ReadRegisterValueAsScalar(reg_ctx, reg_kind, reg_num, error_ptr, +- tmp)) { +- int64_t breg_offset = opcodes.GetSLEB128(&offset); +- tmp.ResolveValue(exe_ctx) += (uint64_t)breg_offset; +- tmp.ClearContext(); +- stack.push_back(tmp); +- stack.back().SetValueType(Value::ValueType::LoadAddress); +- } else +- return false; +- } break; +- // OPCODE: DW_OP_bregx +- // OPERANDS: 2 +- // ULEB128 literal operand that encodes the register. +- // SLEB128 offset from register N +- // DESCRIPTION: Value is in memory at the address specified by register +- // N plus an offset. +- case DW_OP_bregx: { +- reg_num = opcodes.GetULEB128(&offset); +- +- if (ReadRegisterValueAsScalar(reg_ctx, reg_kind, reg_num, error_ptr, +- tmp)) { +- int64_t breg_offset = opcodes.GetSLEB128(&offset); +- tmp.ResolveValue(exe_ctx) += (uint64_t)breg_offset; +- tmp.ClearContext(); +- stack.push_back(tmp); +- stack.back().SetValueType(Value::ValueType::LoadAddress); +- } else +- return false; +- } break; +- +- case DW_OP_fbreg: +- if (exe_ctx) { +- if (frame) { +- Scalar value; +- if (frame->GetFrameBaseValue(value, error_ptr)) { +- int64_t fbreg_offset = opcodes.GetSLEB128(&offset); +- value += fbreg_offset; +- stack.push_back(value); +- stack.back().SetValueType(Value::ValueType::LoadAddress); +- } else +- return false; +- } else { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Invalid stack frame in context for DW_OP_fbreg opcode."); +- return false; +- } +- } else { +- if (error_ptr) +- error_ptr->SetErrorString( +- "NULL execution context for DW_OP_fbreg.\n"); +- return false; +- } +- +- break; +- +- // OPCODE: DW_OP_nop +- // OPERANDS: none +- // DESCRIPTION: A place holder. It has no effect on the location stack +- // or any of its values. +- case DW_OP_nop: +- break; +- +- // OPCODE: DW_OP_piece +- // OPERANDS: 1 +- // ULEB128: byte size of the piece +- // DESCRIPTION: The operand describes the size in bytes of the piece of +- // the object referenced by the DWARF expression whose result is at the top +- // of the stack. If the piece is located in a register, but does not occupy +- // the entire register, the placement of the piece within that register is +- // defined by the ABI. +- // +- // Many compilers store a single variable in sets of registers, or store a +- // variable partially in memory and partially in registers. DW_OP_piece +- // provides a way of describing how large a part of a variable a particular +- // DWARF expression refers to. +- case DW_OP_piece: { +- LocationDescriptionKind piece_locdesc = dwarf4_location_description_kind; +- // Reset for the next piece. +- dwarf4_location_description_kind = Memory; +- +- const uint64_t piece_byte_size = opcodes.GetULEB128(&offset); +- +- if (piece_byte_size > 0) { +- Value curr_piece; +- +- if (stack.empty()) { +- UpdateValueTypeFromLocationDescription( +- log, dwarf_cu, LocationDescriptionKind::Empty); +- // In a multi-piece expression, this means that the current piece is +- // not available. Fill with zeros for now by resizing the data and +- // appending it +- curr_piece.ResizeData(piece_byte_size); +- // Note that "0" is not a correct value for the unknown bits. +- // It would be better to also return a mask of valid bits together +- // with the expression result, so the debugger can print missing +- // members as "" or something. +- ::memset(curr_piece.GetBuffer().GetBytes(), 0, piece_byte_size); +- pieces.AppendDataToHostBuffer(curr_piece); +- } else { +- Status error; +- // Extract the current piece into "curr_piece" +- Value curr_piece_source_value(stack.back()); +- stack.pop_back(); +- UpdateValueTypeFromLocationDescription(log, dwarf_cu, piece_locdesc, +- &curr_piece_source_value); +- +- const Value::ValueType curr_piece_source_value_type = +- curr_piece_source_value.GetValueType(); +- switch (curr_piece_source_value_type) { +- case Value::ValueType::Invalid: +- return false; +- case Value::ValueType::LoadAddress: +- if (process) { +- if (curr_piece.ResizeData(piece_byte_size) == piece_byte_size) { +- lldb::addr_t load_addr = +- curr_piece_source_value.GetScalar().ULongLong( +- LLDB_INVALID_ADDRESS); +- if (process->ReadMemory( +- load_addr, curr_piece.GetBuffer().GetBytes(), +- piece_byte_size, error) != piece_byte_size) { +- if (error_ptr) +- error_ptr->SetErrorStringWithFormat( +- "failed to read memory DW_OP_piece(%" PRIu64 +- ") from 0x%" PRIx64, +- piece_byte_size, load_addr); +- return false; +- } +- } else { +- if (error_ptr) +- error_ptr->SetErrorStringWithFormat( +- "failed to resize the piece memory buffer for " +- "DW_OP_piece(%" PRIu64 ")", +- piece_byte_size); +- return false; +- } +- } +- break; +- +- case Value::ValueType::FileAddress: +- case Value::ValueType::HostAddress: +- if (error_ptr) { +- lldb::addr_t addr = curr_piece_source_value.GetScalar().ULongLong( +- LLDB_INVALID_ADDRESS); +- error_ptr->SetErrorStringWithFormat( +- "failed to read memory DW_OP_piece(%" PRIu64 +- ") from %s address 0x%" PRIx64, +- piece_byte_size, curr_piece_source_value.GetValueType() == +- Value::ValueType::FileAddress +- ? "file" +- : "host", +- addr); +- } +- return false; +- +- case Value::ValueType::Scalar: { +- uint32_t bit_size = piece_byte_size * 8; +- uint32_t bit_offset = 0; +- Scalar &scalar = curr_piece_source_value.GetScalar(); +- if (!scalar.ExtractBitfield( +- bit_size, bit_offset)) { +- if (error_ptr) +- error_ptr->SetErrorStringWithFormat( +- "unable to extract %" PRIu64 " bytes from a %" PRIu64 +- " byte scalar value.", +- piece_byte_size, +- (uint64_t)curr_piece_source_value.GetScalar() +- .GetByteSize()); +- return false; +- } +- // Create curr_piece with bit_size. By default Scalar +- // grows to the nearest host integer type. +- llvm::APInt fail_value(1, 0, false); +- llvm::APInt ap_int = scalar.UInt128(fail_value); +- assert(ap_int.getBitWidth() >= bit_size); +- llvm::ArrayRef buf{ap_int.getRawData(), +- ap_int.getNumWords()}; +- curr_piece.GetScalar() = Scalar(llvm::APInt(bit_size, buf)); +- } break; +- } +- +- // Check if this is the first piece? +- if (op_piece_offset == 0) { +- // This is the first piece, we should push it back onto the stack +- // so subsequent pieces will be able to access this piece and add +- // to it. +- if (pieces.AppendDataToHostBuffer(curr_piece) == 0) { +- if (error_ptr) +- error_ptr->SetErrorString("failed to append piece data"); +- return false; +- } +- } else { +- // If this is the second or later piece there should be a value on +- // the stack. +- if (pieces.GetBuffer().GetByteSize() != op_piece_offset) { +- if (error_ptr) +- error_ptr->SetErrorStringWithFormat( +- "DW_OP_piece for offset %" PRIu64 +- " but top of stack is of size %" PRIu64, +- op_piece_offset, pieces.GetBuffer().GetByteSize()); +- return false; +- } +- +- if (pieces.AppendDataToHostBuffer(curr_piece) == 0) { +- if (error_ptr) +- error_ptr->SetErrorString("failed to append piece data"); +- return false; +- } +- } +- } +- op_piece_offset += piece_byte_size; +- } +- } break; +- +- case DW_OP_bit_piece: // 0x9d ULEB128 bit size, ULEB128 bit offset (DWARF3); +- if (stack.size() < 1) { +- UpdateValueTypeFromLocationDescription(log, dwarf_cu, +- LocationDescriptionKind::Empty); +- // Reset for the next piece. +- dwarf4_location_description_kind = Memory; +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 1 item for DW_OP_bit_piece."); +- return false; +- } else { +- UpdateValueTypeFromLocationDescription( +- log, dwarf_cu, dwarf4_location_description_kind, &stack.back()); +- // Reset for the next piece. +- dwarf4_location_description_kind = Memory; +- const uint64_t piece_bit_size = opcodes.GetULEB128(&offset); +- const uint64_t piece_bit_offset = opcodes.GetULEB128(&offset); +- switch (stack.back().GetValueType()) { +- case Value::ValueType::Invalid: +- return false; +- case Value::ValueType::Scalar: { +- if (!stack.back().GetScalar().ExtractBitfield(piece_bit_size, +- piece_bit_offset)) { +- if (error_ptr) +- error_ptr->SetErrorStringWithFormat( +- "unable to extract %" PRIu64 " bit value with %" PRIu64 +- " bit offset from a %" PRIu64 " bit scalar value.", +- piece_bit_size, piece_bit_offset, +- (uint64_t)(stack.back().GetScalar().GetByteSize() * 8)); +- return false; +- } +- } break; +- +- case Value::ValueType::FileAddress: +- case Value::ValueType::LoadAddress: +- case Value::ValueType::HostAddress: +- if (error_ptr) { +- error_ptr->SetErrorStringWithFormat( +- "unable to extract DW_OP_bit_piece(bit_size = %" PRIu64 +- ", bit_offset = %" PRIu64 ") from an address value.", +- piece_bit_size, piece_bit_offset); +- } +- return false; +- } +- } +- break; +- +- // OPCODE: DW_OP_implicit_value +- // OPERANDS: 2 +- // ULEB128 size of the value block in bytes +- // uint8_t* block bytes encoding value in target's memory +- // representation +- // DESCRIPTION: Value is immediately stored in block in the debug info with +- // the memory representation of the target. +- case DW_OP_implicit_value: { +- dwarf4_location_description_kind = Implicit; +- +- const uint32_t len = opcodes.GetULEB128(&offset); +- const void *data = opcodes.GetData(&offset, len); +- +- if (!data) { +- LLDB_LOG(log, "Evaluate_DW_OP_implicit_value: could not be read data"); +- LLDB_ERRORF(error_ptr, "Could not evaluate %s.", +- DW_OP_value_to_name(op)); +- return false; +- } +- +- Value result(data, len); +- stack.push_back(result); +- break; +- } +- +- case DW_OP_implicit_pointer: { +- dwarf4_location_description_kind = Implicit; +- LLDB_ERRORF(error_ptr, "Could not evaluate %s.", DW_OP_value_to_name(op)); +- return false; +- } +- +- // OPCODE: DW_OP_push_object_address +- // OPERANDS: none +- // DESCRIPTION: Pushes the address of the object currently being +- // evaluated as part of evaluation of a user presented expression. This +- // object may correspond to an independent variable described by its own +- // DIE or it may be a component of an array, structure, or class whose +- // address has been dynamically determined by an earlier step during user +- // expression evaluation. +- case DW_OP_push_object_address: +- if (object_address_ptr) +- stack.push_back(*object_address_ptr); +- else { +- if (error_ptr) +- error_ptr->SetErrorString("DW_OP_push_object_address used without " +- "specifying an object address"); +- return false; +- } +- break; +- +- // OPCODE: DW_OP_call2 +- // OPERANDS: +- // uint16_t compile unit relative offset of a DIE +- // DESCRIPTION: Performs subroutine calls during evaluation +- // of a DWARF expression. The operand is the 2-byte unsigned offset of a +- // debugging information entry in the current compilation unit. +- // +- // Operand interpretation is exactly like that for DW_FORM_ref2. +- // +- // This operation transfers control of DWARF expression evaluation to the +- // DW_AT_location attribute of the referenced DIE. If there is no such +- // attribute, then there is no effect. Execution of the DWARF expression of +- // a DW_AT_location attribute may add to and/or remove from values on the +- // stack. Execution returns to the point following the call when the end of +- // the attribute is reached. Values on the stack at the time of the call +- // may be used as parameters by the called expression and values left on +- // the stack by the called expression may be used as return values by prior +- // agreement between the calling and called expressions. +- case DW_OP_call2: +- if (error_ptr) +- error_ptr->SetErrorString("Unimplemented opcode DW_OP_call2."); +- return false; +- // OPCODE: DW_OP_call4 +- // OPERANDS: 1 +- // uint32_t compile unit relative offset of a DIE +- // DESCRIPTION: Performs a subroutine call during evaluation of a DWARF +- // expression. For DW_OP_call4, the operand is a 4-byte unsigned offset of +- // a debugging information entry in the current compilation unit. +- // +- // Operand interpretation DW_OP_call4 is exactly like that for +- // DW_FORM_ref4. +- // +- // This operation transfers control of DWARF expression evaluation to the +- // DW_AT_location attribute of the referenced DIE. If there is no such +- // attribute, then there is no effect. Execution of the DWARF expression of +- // a DW_AT_location attribute may add to and/or remove from values on the +- // stack. Execution returns to the point following the call when the end of +- // the attribute is reached. Values on the stack at the time of the call +- // may be used as parameters by the called expression and values left on +- // the stack by the called expression may be used as return values by prior +- // agreement between the calling and called expressions. +- case DW_OP_call4: +- if (error_ptr) +- error_ptr->SetErrorString("Unimplemented opcode DW_OP_call4."); +- return false; +- +- // OPCODE: DW_OP_stack_value +- // OPERANDS: None +- // DESCRIPTION: Specifies that the object does not exist in memory but +- // rather is a constant value. The value from the top of the stack is the +- // value to be used. This is the actual object value and not the location. +- case DW_OP_stack_value: +- dwarf4_location_description_kind = Implicit; +- if (stack.empty()) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 1 item for DW_OP_stack_value."); +- return false; +- } +- stack.back().SetValueType(Value::ValueType::Scalar); +- break; +- +- // OPCODE: DW_OP_convert +- // OPERANDS: 1 +- // A ULEB128 that is either a DIE offset of a +- // DW_TAG_base_type or 0 for the generic (pointer-sized) type. +- // +- // DESCRIPTION: Pop the top stack element, convert it to a +- // different type, and push the result. +- case DW_OP_convert: { +- if (stack.size() < 1) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "Expression stack needs at least 1 item for DW_OP_convert."); +- return false; +- } +- const uint64_t die_offset = opcodes.GetULEB128(&offset); +- uint64_t bit_size; +- bool sign; +- if (die_offset == 0) { +- // The generic type has the size of an address on the target +- // machine and an unspecified signedness. Scalar has no +- // "unspecified signedness", so we use unsigned types. +- if (!module_sp) { +- if (error_ptr) +- error_ptr->SetErrorString("No module"); +- return false; +- } +- sign = false; +- bit_size = module_sp->GetArchitecture().GetAddressByteSize() * 8; +- if (!bit_size) { +- if (error_ptr) +- error_ptr->SetErrorString("unspecified architecture"); +- return false; +- } +- } else { +- // Retrieve the type DIE that the value is being converted to. +- // FIXME: the constness has annoying ripple effects. +- DWARFDIE die = const_cast(dwarf_cu)->GetDIE(die_offset); +- if (!die) { +- if (error_ptr) +- error_ptr->SetErrorString("Cannot resolve DW_OP_convert type DIE"); +- return false; +- } +- uint64_t encoding = +- die.GetAttributeValueAsUnsigned(DW_AT_encoding, DW_ATE_hi_user); +- bit_size = die.GetAttributeValueAsUnsigned(DW_AT_byte_size, 0) * 8; +- if (!bit_size) +- bit_size = die.GetAttributeValueAsUnsigned(DW_AT_bit_size, 0); +- if (!bit_size) { +- if (error_ptr) +- error_ptr->SetErrorString("Unsupported type size in DW_OP_convert"); +- return false; +- } +- switch (encoding) { +- case DW_ATE_signed: +- case DW_ATE_signed_char: +- sign = true; +- break; +- case DW_ATE_unsigned: +- case DW_ATE_unsigned_char: +- sign = false; +- break; +- default: +- if (error_ptr) +- error_ptr->SetErrorString("Unsupported encoding in DW_OP_convert"); +- return false; +- } +- } +- Scalar &top = stack.back().ResolveValue(exe_ctx); +- top.TruncOrExtendTo(bit_size, sign); +- break; +- } +- +- // OPCODE: DW_OP_call_frame_cfa +- // OPERANDS: None +- // DESCRIPTION: Specifies a DWARF expression that pushes the value of +- // the canonical frame address consistent with the call frame information +- // located in .debug_frame (or in the FDEs of the eh_frame section). +- case DW_OP_call_frame_cfa: +- if (frame) { +- // Note that we don't have to parse FDEs because this DWARF expression +- // is commonly evaluated with a valid stack frame. +- StackID id = frame->GetStackID(); +- addr_t cfa = id.GetCallFrameAddress(); +- if (cfa != LLDB_INVALID_ADDRESS) { +- stack.push_back(Scalar(cfa)); +- stack.back().SetValueType(Value::ValueType::LoadAddress); +- } else if (error_ptr) +- error_ptr->SetErrorString("Stack frame does not include a canonical " +- "frame address for DW_OP_call_frame_cfa " +- "opcode."); +- } else { +- if (error_ptr) +- error_ptr->SetErrorString("Invalid stack frame in context for " +- "DW_OP_call_frame_cfa opcode."); +- return false; +- } +- break; +- +- // OPCODE: DW_OP_form_tls_address (or the old pre-DWARFv3 vendor extension +- // opcode, DW_OP_GNU_push_tls_address) +- // OPERANDS: none +- // DESCRIPTION: Pops a TLS offset from the stack, converts it to +- // an address in the current thread's thread-local storage block, and +- // pushes it on the stack. +- case DW_OP_form_tls_address: +- case DW_OP_GNU_push_tls_address: { +- if (stack.size() < 1) { +- if (error_ptr) { +- if (op == DW_OP_form_tls_address) +- error_ptr->SetErrorString( +- "DW_OP_form_tls_address needs an argument."); +- else +- error_ptr->SetErrorString( +- "DW_OP_GNU_push_tls_address needs an argument."); +- } +- return false; +- } +- +- if (!exe_ctx || !module_sp) { +- if (error_ptr) +- error_ptr->SetErrorString("No context to evaluate TLS within."); +- return false; +- } +- +- Thread *thread = exe_ctx->GetThreadPtr(); +- if (!thread) { +- if (error_ptr) +- error_ptr->SetErrorString("No thread to evaluate TLS within."); +- return false; +- } +- +- // Lookup the TLS block address for this thread and module. +- const addr_t tls_file_addr = +- stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS); +- const addr_t tls_load_addr = +- thread->GetThreadLocalData(module_sp, tls_file_addr); +- +- if (tls_load_addr == LLDB_INVALID_ADDRESS) { +- if (error_ptr) +- error_ptr->SetErrorString( +- "No TLS data currently exists for this thread."); +- return false; +- } +- +- stack.back().GetScalar() = tls_load_addr; +- stack.back().SetValueType(Value::ValueType::LoadAddress); +- } break; +- +- // OPCODE: DW_OP_addrx (DW_OP_GNU_addr_index is the legacy name.) +- // OPERANDS: 1 +- // ULEB128: index to the .debug_addr section +- // DESCRIPTION: Pushes an address to the stack from the .debug_addr +- // section with the base address specified by the DW_AT_addr_base attribute +- // and the 0 based index is the ULEB128 encoded index. +- case DW_OP_addrx: +- case DW_OP_GNU_addr_index: { +- if (!dwarf_cu) { +- if (error_ptr) +- error_ptr->SetErrorString("DW_OP_GNU_addr_index found without a " +- "compile unit being specified"); +- return false; +- } +- uint64_t index = opcodes.GetULEB128(&offset); +- lldb::addr_t value = ReadAddressFromDebugAddrSection(dwarf_cu, index); +- stack.push_back(Scalar(value)); +- stack.back().SetValueType(Value::ValueType::FileAddress); +- } break; +- +- // OPCODE: DW_OP_GNU_const_index +- // OPERANDS: 1 +- // ULEB128: index to the .debug_addr section +- // DESCRIPTION: Pushes an constant with the size of a machine address to +- // the stack from the .debug_addr section with the base address specified +- // by the DW_AT_addr_base attribute and the 0 based index is the ULEB128 +- // encoded index. +- case DW_OP_GNU_const_index: { +- if (!dwarf_cu) { +- if (error_ptr) +- error_ptr->SetErrorString("DW_OP_GNU_const_index found without a " +- "compile unit being specified"); +- return false; +- } +- uint64_t index = opcodes.GetULEB128(&offset); +- lldb::addr_t value = ReadAddressFromDebugAddrSection(dwarf_cu, index); +- stack.push_back(Scalar(value)); +- } break; +- +- case DW_OP_GNU_entry_value: +- case DW_OP_entry_value: { +- if (!Evaluate_DW_OP_entry_value(stack, exe_ctx, reg_ctx, opcodes, offset, +- error_ptr, log)) { +- LLDB_ERRORF(error_ptr, "Could not evaluate %s.", +- DW_OP_value_to_name(op)); +- return false; +- } +- break; +- } +- +- default: +- if (error_ptr) +- error_ptr->SetErrorStringWithFormatv( +- "Unhandled opcode {0} in DWARFExpression", LocationAtom(op)); +- return false; +- } +- } +- +- if (stack.empty()) { +- // Nothing on the stack, check if we created a piece value from DW_OP_piece +- // or DW_OP_bit_piece opcodes +- if (pieces.GetBuffer().GetByteSize()) { +- result = pieces; +- return true; +- } +- if (error_ptr) +- error_ptr->SetErrorString("Stack empty after evaluation."); +- return false; +- } +- +- UpdateValueTypeFromLocationDescription( +- log, dwarf_cu, dwarf4_location_description_kind, &stack.back()); +- +- if (log && log->GetVerbose()) { +- size_t count = stack.size(); +- LLDB_LOGF(log, +- "Stack after operation has %" PRIu64 " values:", (uint64_t)count); +- for (size_t i = 0; i < count; ++i) { +- StreamString new_value; +- new_value.Printf("[%" PRIu64 "]", (uint64_t)i); +- stack[i].Dump(&new_value); +- LLDB_LOGF(log, " %s", new_value.GetData()); +- } +- } +- result = stack.back(); +- return true; // Return true on success ++ DWARFExpression expr(module_sp, opcodes, dwarf_cu); ++ expr.SetRegisterKind(reg_kind); ++ ++ // Use the DWARF expression evaluator registered for this module (or ++ // DWARFEvaluator by default). ++ DWARFEvaluatorFactory *evaluator_factory = ++ module_sp->GetDWARFExpressionEvaluatorFactory(); ++ std::unique_ptr evaluator = ++ evaluator_factory->CreateDWARFEvaluator( ++ expr, exe_ctx, reg_ctx, initial_value_ptr, object_address_ptr); ++ return evaluator->Evaluate(result, error_ptr); + } + + static DataExtractor ToDataExtractor(const llvm::DWARFLocationExpression &loc, +diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp +index 00e9ccb762c3..2137a1ac8324 100644 +--- a/lldb/source/Interpreter/CommandInterpreter.cpp ++++ b/lldb/source/Interpreter/CommandInterpreter.cpp +@@ -759,6 +759,24 @@ void CommandInterpreter::LoadCommandDictionary() { + } + } + ++ std::unique_ptr connect_wasm_cmd_up( ++ new CommandObjectRegexCommand( ++ *this, "wasm", ++ "Connect to a WebAssembly process via remote GDB server. " ++ "If no host is specifed, localhost is assumed.", ++ "wasm [:]", 2, 0, false)); ++ if (connect_wasm_cmd_up) { ++ if (connect_wasm_cmd_up->AddRegexCommand( ++ "^([^:]+|\\[[0-9a-fA-F:]+.*\\]):([0-9]+)$", ++ "process connect --plugin wasm connect://%1:%2") && ++ connect_wasm_cmd_up->AddRegexCommand( ++ "^([[:digit:]]+)$", ++ "process connect --plugin wasm connect://localhost:%1")) { ++ CommandObjectSP command_sp(connect_wasm_cmd_up.release()); ++ m_command_dict[std::string(command_sp->GetCommandName())] = command_sp; ++ } ++ } ++ + std::unique_ptr connect_kdp_remote_cmd_up( + new CommandObjectRegexCommand( + *this, "kdp-remote", +diff --git a/lldb/source/Plugins/CMakeLists.txt b/lldb/source/Plugins/CMakeLists.txt +index 9181a4e47675..2be6ec3657c0 100644 +--- a/lldb/source/Plugins/CMakeLists.txt ++++ b/lldb/source/Plugins/CMakeLists.txt +@@ -2,6 +2,7 @@ add_subdirectory(ABI) + add_subdirectory(Architecture) + add_subdirectory(Disassembler) + add_subdirectory(DynamicLoader) ++add_subdirectory(DWARFEvaluator) + add_subdirectory(ExpressionParser) + add_subdirectory(Instruction) + add_subdirectory(InstrumentationRuntime) +@@ -32,6 +33,7 @@ set(LLDB_ENUM_PLUGINS "") + # FIXME: ProcessWindowsCommon needs to be initialized after all other process + # plugins but before ProcessGDBRemote. + set(LLDB_PROCESS_WINDOWS_PLUGIN "") ++set(LLDB_PROCESS_WASM_PLUGIN "") + set(LLDB_PROCESS_GDB_PLUGIN "") + + foreach(p ${LLDB_ALL_PLUGINS}) +@@ -43,6 +45,8 @@ foreach(p ${LLDB_ALL_PLUGINS}) + set(LLDB_PROCESS_WINDOWS_PLUGIN "LLDB_PLUGIN(${pStripped})\n") + elseif(${pStripped} STREQUAL "ProcessGDBRemote") + set(LLDB_PROCESS_GDB_PLUGIN "LLDB_PLUGIN(${pStripped})\n") ++ elseif(${pStripped} STREQUAL "ProcessWasm") ++ set(LLDB_PROCESS_WASM_PLUGIN "LLDB_PLUGIN(${pStripped})\n") + else() + set(LLDB_ENUM_PLUGINS "${LLDB_ENUM_PLUGINS}LLDB_PLUGIN(${pStripped})\n") + endif() +diff --git a/lldb/source/Plugins/DWARFEvaluator/CMakeLists.txt b/lldb/source/Plugins/DWARFEvaluator/CMakeLists.txt +new file mode 100644 +index 000000000000..73fad41e1a72 +--- /dev/null ++++ b/lldb/source/Plugins/DWARFEvaluator/CMakeLists.txt +@@ -0,0 +1 @@ ++add_subdirectory(wasm) +diff --git a/lldb/source/Plugins/DWARFEvaluator/wasm/CMakeLists.txt b/lldb/source/Plugins/DWARFEvaluator/wasm/CMakeLists.txt +new file mode 100644 +index 000000000000..e50b1bef7e69 +--- /dev/null ++++ b/lldb/source/Plugins/DWARFEvaluator/wasm/CMakeLists.txt +@@ -0,0 +1,10 @@ ++add_lldb_library(lldbPluginWasmDWARFEvaluatorFactory PLUGIN ++ WasmDWARFEvaluator.cpp ++ WasmDWARFEvaluatorFactory.cpp ++ ++ LINK_LIBS ++ lldbCore ++ lldbHost ++ lldbSymbol ++ lldbPluginObjectFileWasm ++ ) +diff --git a/lldb/source/Plugins/DWARFEvaluator/wasm/WasmDWARFEvaluator.cpp b/lldb/source/Plugins/DWARFEvaluator/wasm/WasmDWARFEvaluator.cpp +new file mode 100644 +index 000000000000..fdda1991d19f +--- /dev/null ++++ b/lldb/source/Plugins/DWARFEvaluator/wasm/WasmDWARFEvaluator.cpp +@@ -0,0 +1,126 @@ ++//===-- WasmDWARFEvaluator.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 "WasmDWARFEvaluator.h" ++ ++#include "Plugins/ObjectFile/wasm/ObjectFileWasm.h" ++#include "Plugins/Process/wasm/ProcessWasm.h" ++#include "lldb/Core/Module.h" ++#include "lldb/Core/PluginManager.h" ++#include "lldb/Core/Value.h" ++#include "lldb/Core/dwarf.h" ++#include "lldb/Expression/DWARFExpression.h" ++ ++using namespace lldb; ++using namespace lldb_private; ++using namespace lldb_private::wasm; ++ ++bool WasmDWARFEvaluator::Evaluate(const uint8_t op, Process *process, ++ StackFrame *frame, std::vector &stack, ++ const DataExtractor &opcodes, ++ lldb::offset_t &offset, Value &pieces, ++ uint64_t &op_piece_offset, Log *log, ++ Status *error_ptr) { ++ lldb::ModuleSP module_sp = m_dwarf_expression.GetModule(); ++ ++ switch (op) { ++ case DW_OP_WASM_location: { ++ if (frame) { ++ const llvm::Triple::ArchType machine = ++ frame->CalculateTarget()->GetArchitecture().GetMachine(); ++ if (machine != llvm::Triple::wasm32) { ++ if (error_ptr) ++ error_ptr->SetErrorString("Invalid target architecture for " ++ "DW_OP_WASM_location opcode."); ++ return false; ++ } ++ ++ ProcessWasm *wasm_process = ++ static_cast(frame->CalculateProcess().get()); ++ int frame_index = frame->GetConcreteFrameIndex(); ++ uint64_t wasm_op = opcodes.GetULEB128(&offset); ++ uint64_t index = opcodes.GetULEB128(&offset); ++ uint8_t buf[16]; ++ size_t size = 0; ++ switch (wasm_op) { ++ case 0: // Local ++ if (!wasm_process->GetWasmLocal(frame_index, index, buf, 16, size)) { ++ return false; ++ } ++ break; ++ case 1: // Global ++ if (!wasm_process->GetWasmGlobal(frame_index, index, buf, 16, size)) { ++ return false; ++ } ++ break; ++ case 2: // Operand Stack ++ if (!wasm_process->GetWasmStackValue(frame_index, index, buf, 16, ++ size)) { ++ return false; ++ } ++ break; ++ default: ++ return false; ++ } ++ ++ if (size == sizeof(uint32_t)) { ++ uint32_t value; ++ memcpy(&value, buf, size); ++ stack.push_back(Scalar(value)); ++ } else if (size == sizeof(uint64_t)) { ++ uint64_t value; ++ memcpy(&value, buf, size); ++ stack.push_back(Scalar(value)); ++ } else ++ return false; ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorString("Invalid stack frame in context for " ++ "DW_OP_WASM_location opcode."); ++ return false; ++ } ++ } break; ++ ++ case DW_OP_addr: { ++ /// {addr} is an offset in the module Data section. ++ lldb::addr_t addr = opcodes.GetAddress(&offset); ++ stack.push_back(Scalar(addr)); ++ stack.back().SetValueType(Value::ValueType::LoadAddress); ++ } break; ++ ++ case DW_OP_fbreg: ++ if (m_exe_ctx) { ++ if (frame) { ++ Scalar value; ++ if (frame->GetFrameBaseValue(value, error_ptr)) { ++ // The value is an address in the Wasm Memory space. ++ int64_t fbreg_offset = opcodes.GetSLEB128(&offset); ++ stack.push_back(Scalar(value.ULong() + fbreg_offset)); ++ stack.back().SetValueType(Value::ValueType::LoadAddress); ++ } else ++ return false; ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorString( ++ "Invalid stack frame in context for DW_OP_fbreg opcode."); ++ return false; ++ } ++ } else { ++ if (error_ptr) ++ error_ptr->SetErrorStringWithFormat( ++ "NULL execution context for DW_OP_fbreg.\n"); ++ return false; ++ } ++ break; ++ ++ default: ++ return DWARFEvaluator::Evaluate(op, process, frame, stack, opcodes, offset, ++ pieces, op_piece_offset, log, error_ptr); ++ } ++ return true; ++} +diff --git a/lldb/source/Plugins/DWARFEvaluator/wasm/WasmDWARFEvaluator.h b/lldb/source/Plugins/DWARFEvaluator/wasm/WasmDWARFEvaluator.h +new file mode 100644 +index 000000000000..a01159064a39 +--- /dev/null ++++ b/lldb/source/Plugins/DWARFEvaluator/wasm/WasmDWARFEvaluator.h +@@ -0,0 +1,47 @@ ++//===-- WasmDWARFEvaluator.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 ++// ++//===----------------------------------------------------------------------===// ++ ++#ifndef LLDB_SOURCE_PLUGINS_DWARFEVALUATOR_WASM_WASMDWARFEVALUATOR_H ++#define LLDB_SOURCE_PLUGINS_DWARFEVALUATOR_WASM_WASMDWARFEVALUATOR_H ++ ++#include "lldb/Expression/DWARFEvaluator.h" ++#include "lldb/lldb-private.h" ++ ++namespace lldb_private { ++namespace wasm { ++ ++/// \class WasmDWARFEvaluator evaluates DWARF expressions in the context of a ++/// WebAssembly process. ++/// ++class WasmDWARFEvaluator : public DWARFEvaluator { ++public: ++ WasmDWARFEvaluator(const DWARFExpression &dwarf_expression, ++ ExecutionContext *exe_ctx, RegisterContext *reg_ctx, ++ const Value *initial_value_ptr, ++ const Value *object_address_ptr) ++ : DWARFEvaluator(dwarf_expression, exe_ctx, reg_ctx, initial_value_ptr, ++ object_address_ptr) {} ++ ++ /// DWARFEvaluator protocol. ++ /// \{ ++ bool Evaluate(const uint8_t op, Process *process, StackFrame *frame, ++ std::vector &stack, const DataExtractor &opcodes, ++ lldb::offset_t &offset, Value &pieces, ++ uint64_t &op_piece_offset, Log *log, ++ Status *error_ptr) override; ++ /// \} ++ ++private: ++ WasmDWARFEvaluator(const WasmDWARFEvaluator &); ++ const WasmDWARFEvaluator &operator=(const WasmDWARFEvaluator &) = delete; ++}; ++ ++} // namespace wasm ++} // namespace lldb_private ++ ++#endif // LLDB_SOURCE_PLUGINS_DWARFEVALUATOR_WASM_WASMDWARFEVALUATOR_H +diff --git a/lldb/source/Plugins/DWARFEvaluator/wasm/WasmDWARFEvaluatorFactory.cpp b/lldb/source/Plugins/DWARFEvaluator/wasm/WasmDWARFEvaluatorFactory.cpp +new file mode 100644 +index 000000000000..d43e96a34d37 +--- /dev/null ++++ b/lldb/source/Plugins/DWARFEvaluator/wasm/WasmDWARFEvaluatorFactory.cpp +@@ -0,0 +1,64 @@ ++//===-- WasmDWARFEvaluatorFactory.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 "WasmDWARFEvaluatorFactory.h" ++#include "WasmDWARFEvaluator.h" ++ ++#include "Plugins/ObjectFile/wasm/ObjectFileWasm.h" ++#include "lldb/Core/Module.h" ++#include "lldb/Core/PluginManager.h" ++ ++using namespace lldb; ++using namespace lldb_private; ++using namespace lldb_private::wasm; ++ ++LLDB_PLUGIN_DEFINE(WasmDWARFEvaluatorFactory) ++ ++void WasmDWARFEvaluatorFactory::Initialize() { ++ PluginManager::RegisterPlugin(GetPluginNameStatic(), ++ GetPluginDescriptionStatic(), CreateInstance); ++} ++ ++void WasmDWARFEvaluatorFactory::Terminate() { ++ PluginManager::UnregisterPlugin(CreateInstance); ++} ++ ++lldb_private::ConstString WasmDWARFEvaluatorFactory::GetPluginNameStatic() { ++ static ConstString g_name("WASM"); ++ return g_name; ++} ++ ++const char *WasmDWARFEvaluatorFactory::GetPluginDescriptionStatic() { ++ return "DWARF expression evaluator factory for WASM."; ++} ++ ++// CreateInstance ++// ++// Platforms can register a callback to use when creating DWARF expression ++// evaluators to allow handling platform-specific DWARF codes. ++DWARFEvaluatorFactory * ++WasmDWARFEvaluatorFactory::CreateInstance(Module *module) { ++ if (!module) ++ return nullptr; ++ ++ ObjectFileWasm *obj_file = ++ llvm::dyn_cast_or_null(module->GetObjectFile()); ++ if (!obj_file) ++ return nullptr; ++ ++ return new WasmDWARFEvaluatorFactory(); ++} ++ ++std::unique_ptr WasmDWARFEvaluatorFactory::CreateDWARFEvaluator( ++ const DWARFExpression &dwarf_expression, ExecutionContext *exe_ctx, ++ RegisterContext *reg_ctx, const Value *initial_value_ptr, ++ const Value *object_address_ptr) { ++ return std::make_unique(dwarf_expression, exe_ctx, ++ reg_ctx, initial_value_ptr, ++ object_address_ptr); ++} +diff --git a/lldb/source/Plugins/DWARFEvaluator/wasm/WasmDWARFEvaluatorFactory.h b/lldb/source/Plugins/DWARFEvaluator/wasm/WasmDWARFEvaluatorFactory.h +new file mode 100644 +index 000000000000..8a946592a09a +--- /dev/null ++++ b/lldb/source/Plugins/DWARFEvaluator/wasm/WasmDWARFEvaluatorFactory.h +@@ -0,0 +1,55 @@ ++//===-- WasmDWARFEvaluatorFactory.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 ++// ++//===----------------------------------------------------------------------===// ++ ++#ifndef LLDB_SOURCE_PLUGINS_DWARFEVALUATOR_WASM_WASMDWARFEVALUATORFACTORY_H ++#define LLDB_SOURCE_PLUGINS_DWARFEVALUATOR_WASM_WASMDWARFEVALUATORFACTORY_H ++ ++#include "lldb/Expression/DWARFEvaluatorFactory.h" ++ ++namespace lldb_private { ++namespace wasm { ++ ++/// \class WasmDWARFEvaluatorFactory creates DWARF evaluators specialized to ++/// manage DWARF-specific opcodes. ++class WasmDWARFEvaluatorFactory : public DWARFEvaluatorFactory { ++public: ++ static void Initialize(); ++ static void Terminate(); ++ static lldb_private::ConstString GetPluginNameStatic(); ++ static const char *GetPluginDescriptionStatic(); ++ ++ static lldb_private::DWARFEvaluatorFactory *CreateInstance(Module *module); ++ ++ /// PluginInterface protocol. ++ /// \{ ++ lldb_private::ConstString GetPluginName() override { ++ return GetPluginNameStatic(); ++ } ++ uint32_t GetPluginVersion() override { return 1; } ++ /// \} ++ ++ WasmDWARFEvaluatorFactory() {} ++ ++ /// DWARFEvaluatorFactory protocol. ++ /// \{ ++ std::unique_ptr ++ CreateDWARFEvaluator(const DWARFExpression &dwarf_expression, ++ ExecutionContext *exe_ctx, RegisterContext *reg_ctx, ++ const Value *initial_value_ptr, ++ const Value *object_address_ptr) override; ++ /// \} ++ ++private: ++ WasmDWARFEvaluatorFactory(const WasmDWARFEvaluatorFactory &); ++ const WasmDWARFEvaluatorFactory &operator=(const WasmDWARFEvaluatorFactory &) = delete; ++}; ++ ++} // namespace wasm ++} // namespace lldb_private ++ ++#endif // LLDB_SOURCE_PLUGINS_DWARFEVALUATOR_WASM_WASMDWARFEVALUATORFACTORY_H +diff --git a/lldb/source/Plugins/DynamicLoader/wasm-DYLD/DynamicLoaderWasmDYLD.cpp b/lldb/source/Plugins/DynamicLoader/wasm-DYLD/DynamicLoaderWasmDYLD.cpp +index ae7e011eaa52..24ea75d1971c 100644 +--- a/lldb/source/Plugins/DynamicLoader/wasm-DYLD/DynamicLoaderWasmDYLD.cpp ++++ b/lldb/source/Plugins/DynamicLoader/wasm-DYLD/DynamicLoaderWasmDYLD.cpp +@@ -62,6 +62,15 @@ void DynamicLoaderWasmDYLD::DidAttach() { + // Ask the process for the list of loaded WebAssembly modules. + auto error = m_process->LoadModules(); + LLDB_LOG_ERROR(log, std::move(error), "Couldn't load modules: {0}"); ++ ++ // TODO: multi-modules support ? ++ Target &target = m_process->GetTarget(); ++ const ModuleList &modules = target.GetImages(); ++ ModuleSP module_sp(modules.GetModuleAtIndex(0)); ++ // module_sp is nullptr if without libxml2 ++ if(module_sp) { ++ module_sp->PreloadSymbols(); ++ } + } + + ThreadPlanSP DynamicLoaderWasmDYLD::GetStepThroughTrampolinePlan(Thread &thread, +diff --git a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp +index 5272da9ab33a..abc5523bfd70 100644 +--- a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp ++++ b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp +@@ -23,6 +23,7 @@ + #include "llvm/BinaryFormat/Wasm.h" + #include "llvm/Support/Endian.h" + #include "llvm/Support/Format.h" ++#include "Plugins/Process/wasm/ProcessWasm.h" + + using namespace lldb; + using namespace lldb_private; +@@ -341,6 +342,8 @@ void ObjectFileWasm::CreateSections(SectionList &unified_section_list) { + 0, // Alignment of the section + 0, // Flags for this section. + 1)); // Number of host bytes per target byte ++ if (section_type == eSectionTypeCode) ++ section_sp->SetPermissions(ePermissionsReadable|ePermissionsExecutable); + m_sections_up->AddSection(section_sp); + unified_section_list.AddSection(section_sp); + } +@@ -367,6 +370,7 @@ bool ObjectFileWasm::SetLoadAddress(Target &target, lldb::addr_t load_address, + assert(m_memory_addr == LLDB_INVALID_ADDRESS || + m_memory_addr == load_address); + ++ lldb::addr_t adjust_addr; + ModuleSP module_sp = GetModule(); + if (!module_sp) + return false; +@@ -381,8 +385,9 @@ bool ObjectFileWasm::SetLoadAddress(Target &target, lldb::addr_t load_address, + const size_t num_sections = section_list->GetSize(); + for (size_t sect_idx = 0; sect_idx < num_sections; ++sect_idx) { + SectionSP section_sp(section_list->GetSectionAtIndex(sect_idx)); ++ adjust_addr = load_address; + if (target.SetSectionLoadAddress( +- section_sp, load_address | section_sp->GetFileOffset())) { ++ section_sp, adjust_addr | section_sp->GetFileOffset())) { + ++num_loaded_sections; + } + } +diff --git a/lldb/source/Plugins/Platform/CMakeLists.txt b/lldb/source/Plugins/Platform/CMakeLists.txt +index 5f284e517dca..6084cbc9378d 100644 +--- a/lldb/source/Plugins/Platform/CMakeLists.txt ++++ b/lldb/source/Plugins/Platform/CMakeLists.txt +@@ -15,3 +15,4 @@ + add_subdirectory(POSIX) + add_subdirectory(gdb-server) + add_subdirectory(Android) ++add_subdirectory(wasm-remote) +diff --git a/lldb/source/Plugins/Platform/wasm-remote/CMakeLists.txt b/lldb/source/Plugins/Platform/wasm-remote/CMakeLists.txt +new file mode 100644 +index 000000000000..4a65765a5659 +--- /dev/null ++++ b/lldb/source/Plugins/Platform/wasm-remote/CMakeLists.txt +@@ -0,0 +1,10 @@ ++add_lldb_library(lldbPluginPlatformWasm PLUGIN ++ PlatformRemoteWasmServer.cpp ++ ++ LINK_LIBS ++ lldbBreakpoint ++ lldbCore ++ lldbHost ++ lldbTarget ++ lldbPluginProcessUtility ++ ) +diff --git a/lldb/source/Plugins/Platform/wasm-remote/PlatformRemoteWasmServer.cpp b/lldb/source/Plugins/Platform/wasm-remote/PlatformRemoteWasmServer.cpp +new file mode 100644 +index 000000000000..f26d11f00e5c +--- /dev/null ++++ b/lldb/source/Plugins/Platform/wasm-remote/PlatformRemoteWasmServer.cpp +@@ -0,0 +1,139 @@ ++#include "PlatformRemoteWasmServer.h" ++#include "lldb/Host/Config.h" ++ ++#include "lldb/Breakpoint/BreakpointLocation.h" ++#include "lldb/Core/Debugger.h" ++#include "lldb/Core/Module.h" ++#include "lldb/Core/ModuleList.h" ++#include "lldb/Core/ModuleSpec.h" ++#include "lldb/Core/PluginManager.h" ++#include "lldb/Core/StreamFile.h" ++#include "lldb/Host/ConnectionFileDescriptor.h" ++#include "lldb/Host/Host.h" ++#include "lldb/Host/HostInfo.h" ++#include "lldb/Host/PosixApi.h" ++#include "lldb/Target/Process.h" ++#include "lldb/Target/Target.h" ++#include "lldb/Utility/FileSpec.h" ++#include "lldb/Utility/Log.h" ++#include "lldb/Utility/ProcessInfo.h" ++#include "lldb/Utility/Status.h" ++#include "lldb/Utility/StreamString.h" ++#include "lldb/Utility/UriParser.h" ++ ++#include "Plugins/Process/Utility/GDBRemoteSignals.h" ++ ++using namespace lldb; ++using namespace lldb_private; ++using namespace lldb_private::platform_wasm_server; ++ ++LLDB_PLUGIN_DEFINE_ADV(PlatformRemoteWASMServer, PlatformWasm) ++ ++static bool g_initialized = false; ++ ++void PlatformRemoteWASMServer::Initialize() { ++ Platform::Initialize(); ++ ++ if (!g_initialized) { ++ g_initialized = true; ++ PluginManager::RegisterPlugin( ++ PlatformRemoteWASMServer::GetPluginNameStatic(), ++ PlatformRemoteWASMServer::GetDescriptionStatic(), ++ PlatformRemoteWASMServer::CreateInstance); ++ } ++} ++ ++void PlatformRemoteWASMServer::Terminate() { ++ if (g_initialized) { ++ g_initialized = false; ++ PluginManager::UnregisterPlugin(PlatformRemoteWASMServer::CreateInstance); ++ } ++ ++ Platform::Terminate(); ++} ++ ++PlatformSP PlatformRemoteWASMServer::CreateInstance(bool force, ++ const ArchSpec *arch) { ++ Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PLATFORM)); ++ if (log) { ++ const char *arch_name; ++ if (arch && arch->GetArchitectureName()) ++ arch_name = arch->GetArchitectureName(); ++ else ++ arch_name = ""; ++ ++ const char *triple_cstr = ++ arch ? arch->GetTriple().getTriple().c_str() : ""; ++ ++ LLDB_LOGF(log, "PlatformRemoteWASMServer::%s(force=%s, arch={%s,%s})", ++ __FUNCTION__, force ? "true" : "false", arch_name, triple_cstr); ++ } ++ ++ bool create = force; ++ if (!create && arch && arch->IsValid()) { ++ const llvm::Triple &triple = arch->GetTriple(); ++ if (arch->GetMachine() == llvm::Triple::wasm32 && ++ triple.getOS() == llvm::Triple::WASI) { ++ create = true; ++ } ++ } ++ ++ if (create) { ++ if (log) ++ LLDB_LOGF(log, "PlatformRemoteWASMServer::%s() creating platform", ++ __FUNCTION__); ++ return PlatformSP(new PlatformRemoteWASMServer()); ++ } ++ ++ if (log) ++ LLDB_LOGF(log, ++ "PlatformRemoteWASMServer::%s() aborting creation of platform", ++ __FUNCTION__); ++ return PlatformSP(); ++} ++ ++ConstString PlatformRemoteWASMServer::GetPluginNameStatic() { ++ static ConstString g_name("remote-wasm-server"); ++ return g_name; ++} ++ ++ConstString PlatformRemoteWASMServer::GetPluginName() { ++ return GetPluginNameStatic(); ++} ++ ++const char *PlatformRemoteWASMServer::GetDescriptionStatic() { ++ return "A platform that uses the GDB remote protocol as the communication " ++ "transport for Wasm Runtime"; ++} ++ ++size_t PlatformRemoteWASMServer::ConnectToWaitingProcesses(Debugger &debugger, ++ Status &error) { ++ std::vector connection_urls; ++ GetPendingGdbServerList(connection_urls); ++ ++ for (size_t i = 0; i < connection_urls.size(); ++i) { ++ ConnectProcess(connection_urls[i].c_str(), "wasm", debugger, nullptr, error); ++ if (error.Fail()) ++ return i; // We already connected to i process succsessfully ++ } ++ return connection_urls.size(); ++} ++ ++bool PlatformRemoteWASMServer::GetSupportedArchitectureAtIndex(uint32_t idx, ++ ArchSpec &arch) { ++ ArchSpec remote_arch = m_gdb_client.GetSystemArchitecture(); ++ if (idx == 0) { ++ arch = remote_arch; ++ return arch.IsValid(); ++ } else if (idx == 1 && remote_arch.IsValid() && ++ remote_arch.GetTriple().getOS() == llvm::Triple::WASI) { ++ return arch.IsValid(); ++ } ++ return false; ++} ++ ++/// Default Constructor ++PlatformRemoteWASMServer::PlatformRemoteWASMServer() ++ : PlatformRemoteGDBServer() ++ { ++ } +\ No newline at end of file +diff --git a/lldb/source/Plugins/Platform/wasm-remote/PlatformRemoteWasmServer.h b/lldb/source/Plugins/Platform/wasm-remote/PlatformRemoteWasmServer.h +new file mode 100644 +index 000000000000..f306a79d3f4f +--- /dev/null ++++ b/lldb/source/Plugins/Platform/wasm-remote/PlatformRemoteWasmServer.h +@@ -0,0 +1,37 @@ ++#ifndef LLDB_SOURCE_PLUGINS_PLATFORM_GDB_SERVER_PLATFORMREMOTEWASMSERVER_H ++#define LLDB_SOURCE_PLUGINS_PLATFORM_GDB_SERVER_PLATFORMREMOTEWASMSERVER_H ++ ++#include "Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h" ++#include "lldb/Target/Platform.h" ++ ++namespace lldb_private { ++namespace platform_wasm_server { ++ ++class PlatformRemoteWASMServer : public lldb_private::platform_gdb_server::PlatformRemoteGDBServer{ ++ ++public: ++ static void Initialize(); ++ ++ static void Terminate(); ++ ++ static lldb::PlatformSP CreateInstance(bool force, const ArchSpec *arch); ++ ++ static ConstString GetPluginNameStatic(); ++ ++ static const char *GetDescriptionStatic(); ++ ++ size_t ConnectToWaitingProcesses(lldb_private::Debugger &debugger, ++ lldb_private::Status &error) override; ++ ++ bool GetSupportedArchitectureAtIndex(uint32_t idx, ArchSpec &arch) override; ++ ++ ConstString GetPluginName() override; ++ ++ PlatformRemoteWASMServer(); ++ ++}; ++ ++} // namespace platform_wasm_server ++} // namespace lldb_private ++ ++#endif +\ No newline at end of file +diff --git a/lldb/source/Plugins/Plugins.def.in b/lldb/source/Plugins/Plugins.def.in +index bf54598fb2f3..b0bd7b9965fe 100644 +--- a/lldb/source/Plugins/Plugins.def.in ++++ b/lldb/source/Plugins/Plugins.def.in +@@ -31,6 +31,7 @@ + + @LLDB_ENUM_PLUGINS@ + @LLDB_PROCESS_WINDOWS_PLUGIN@ ++@LLDB_PROCESS_WASM_PLUGIN@ + @LLDB_PROCESS_GDB_PLUGIN@ + + #undef LLDB_PLUGIN +diff --git a/lldb/source/Plugins/Process/CMakeLists.txt b/lldb/source/Plugins/Process/CMakeLists.txt +index bea5bac9eb21..7a0855e02ca2 100644 +--- a/lldb/source/Plugins/Process/CMakeLists.txt ++++ b/lldb/source/Plugins/Process/CMakeLists.txt +@@ -18,3 +18,4 @@ add_subdirectory(Utility) + add_subdirectory(elf-core) + add_subdirectory(mach-core) + add_subdirectory(minidump) ++add_subdirectory(wasm) +diff --git a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp +index 12bc7390c729..707ab85e5615 100644 +--- a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp ++++ b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp +@@ -285,7 +285,7 @@ bool ProcessElfCore::IsAlive() { return true; } + + // Process Memory + size_t ProcessElfCore::ReadMemory(lldb::addr_t addr, void *buf, size_t size, +- Status &error) { ++ Status &error, ExecutionContext *exe_ctx) { + // Don't allow the caching that lldb_private::Process::ReadMemory does since + // in core files we have it all cached our our core file anyway. + return DoReadMemory(addr, buf, size, error); +diff --git a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.h b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.h +index d8e3cc9ae3e1..f0bf9c4d3b00 100644 +--- a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.h ++++ b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.h +@@ -84,7 +84,8 @@ public: + + // Process Memory + size_t ReadMemory(lldb::addr_t addr, void *buf, size_t size, +- lldb_private::Status &error) override; ++ lldb_private::Status &error, ++ lldb_private::ExecutionContext *exe_ctx = nullptr) override; + + size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size, + lldb_private::Status &error) override; +diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +index 6914b37348ea..bb8a056049f3 100644 +--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp ++++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +@@ -334,6 +334,11 @@ ConstString ProcessGDBRemote::GetPluginName() { return GetPluginNameStatic(); } + + uint32_t ProcessGDBRemote::GetPluginVersion() { return 1; } + ++std::shared_ptr ++ProcessGDBRemote::CreateThread(lldb::tid_t tid) { ++ return std::make_shared(*this, tid); ++} ++ + bool ProcessGDBRemote::ParsePythonTargetDefinition( + const FileSpec &target_definition_fspec) { + ScriptInterpreter *interpreter = +@@ -1626,7 +1631,7 @@ bool ProcessGDBRemote::DoUpdateThreadList(ThreadList &old_thread_list, + ThreadSP thread_sp( + old_thread_list_copy.RemoveThreadByProtocolID(tid, false)); + if (!thread_sp) { +- thread_sp = std::make_shared(*this, tid); ++ thread_sp = CreateThread(tid); + LLDB_LOGV(log, "Making new thread: {0} for thread ID: {1:x}.", + thread_sp.get(), thread_sp->GetID()); + } else { +@@ -1742,7 +1747,7 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo( + + if (!thread_sp) { + // Create the thread if we need to +- thread_sp = std::make_shared(*this, tid); ++ thread_sp = CreateThread(tid); + m_thread_list_real.AddThread(thread_sp); + } + } +diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +index fe04cdddd0f5..e4a14c64579a 100644 +--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h ++++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +@@ -237,6 +237,8 @@ protected: + + bool SupportsMemoryTagging() override; + ++ virtual std::shared_ptr CreateThread(lldb::tid_t tid); ++ + /// Broadcaster event bits definitions. + enum { + eBroadcastBitAsyncContinue = (1 << 0), +diff --git a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp +index 84548edb5caa..0ae6f7e4a177 100644 +--- a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp ++++ b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp +@@ -596,7 +596,7 @@ bool ProcessMachCore::WarnBeforeDetach() const { return false; } + + // Process Memory + size_t ProcessMachCore::ReadMemory(addr_t addr, void *buf, size_t size, +- Status &error) { ++ Status &error, ExecutionContext *exe_ctx) { + // Don't allow the caching that lldb_private::Process::ReadMemory does since + // in core files we have it all cached our our core file anyway. + return DoReadMemory(addr, buf, size, error); +diff --git a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.h b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.h +index db77e96f1072..1c930896c743 100644 +--- a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.h ++++ b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.h +@@ -65,7 +65,8 @@ public: + + // Process Memory + size_t ReadMemory(lldb::addr_t addr, void *buf, size_t size, +- lldb_private::Status &error) override; ++ lldb_private::Status &error, ++ lldb_private::ExecutionContext *exe_ctx = nullptr) override; + + size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size, + lldb_private::Status &error) override; +diff --git a/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp b/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp +index 385557422758..d8bb21581086 100644 +--- a/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp ++++ b/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp +@@ -374,7 +374,7 @@ bool ProcessMinidump::IsAlive() { return true; } + bool ProcessMinidump::WarnBeforeDetach() const { return false; } + + size_t ProcessMinidump::ReadMemory(lldb::addr_t addr, void *buf, size_t size, +- Status &error) { ++ Status &error, ExecutionContext *exe_ctx) { + // Don't allow the caching that lldb_private::Process::ReadMemory does since + // we have it all cached in our dump file anyway. + return DoReadMemory(addr, buf, size, error); +diff --git a/lldb/source/Plugins/Process/minidump/ProcessMinidump.h b/lldb/source/Plugins/Process/minidump/ProcessMinidump.h +index 27b0da0047a5..e94ecab430c1 100644 +--- a/lldb/source/Plugins/Process/minidump/ProcessMinidump.h ++++ b/lldb/source/Plugins/Process/minidump/ProcessMinidump.h +@@ -69,8 +69,8 @@ public: + + bool WarnBeforeDetach() const override; + +- size_t ReadMemory(lldb::addr_t addr, void *buf, size_t size, +- Status &error) override; ++ size_t ReadMemory(lldb::addr_t addr, void *buf, size_t size, Status &error, ++ ExecutionContext *exe_ctx = nullptr) override; + + size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) override; +diff --git a/lldb/source/Plugins/Process/wasm/CMakeLists.txt b/lldb/source/Plugins/Process/wasm/CMakeLists.txt +new file mode 100644 +index 000000000000..61efb933fa62 +--- /dev/null ++++ b/lldb/source/Plugins/Process/wasm/CMakeLists.txt +@@ -0,0 +1,12 @@ ++ ++add_lldb_library(lldbPluginProcessWasm PLUGIN ++ ProcessWasm.cpp ++ ThreadWasm.cpp ++ UnwindWasm.cpp ++ ++ LINK_LIBS ++ lldbCore ++ ${LLDB_PLUGINS} ++ LINK_COMPONENTS ++ Support ++ ) +diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp +new file mode 100644 +index 000000000000..b8ffcac12df2 +--- /dev/null ++++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp +@@ -0,0 +1,256 @@ ++//===-- ProcessWasm.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 "ProcessWasm.h" ++#include "ThreadWasm.h" ++#include "lldb/Core/Module.h" ++#include "lldb/Core/PluginManager.h" ++#include "lldb/Utility/DataBufferHeap.h" ++ ++using namespace lldb; ++using namespace lldb_private; ++using namespace lldb_private::process_gdb_remote; ++using namespace lldb_private::wasm; ++ ++LLDB_PLUGIN_DEFINE(ProcessWasm) ++ ++// ProcessGDBRemote constructor ++ProcessWasm::ProcessWasm(lldb::TargetSP target_sp, ListenerSP listener_sp) ++ : ProcessGDBRemote(target_sp, listener_sp) {} ++ ++void ProcessWasm::Initialize() { ++ static llvm::once_flag g_once_flag; ++ ++ llvm::call_once(g_once_flag, []() { ++ PluginManager::RegisterPlugin(GetPluginNameStatic(), ++ GetPluginDescriptionStatic(), CreateInstance, ++ DebuggerInitialize); ++ }); ++} ++ ++void ProcessWasm::DebuggerInitialize(Debugger &debugger) { ++ ProcessGDBRemote::DebuggerInitialize(debugger); ++} ++ ++// PluginInterface ++ConstString ProcessWasm::GetPluginName() { return GetPluginNameStatic(); } ++ ++uint32_t ProcessWasm::GetPluginVersion() { return 1; } ++ ++ConstString ProcessWasm::GetPluginNameStatic() { ++ static ConstString g_name("wasm"); ++ return g_name; ++} ++ ++const char *ProcessWasm::GetPluginDescriptionStatic() { ++ return "GDB Remote protocol based WebAssembly debugging plug-in."; ++} ++ ++void ProcessWasm::Terminate() { ++ PluginManager::UnregisterPlugin(ProcessWasm::CreateInstance); ++} ++ ++lldb::ProcessSP ProcessWasm::CreateInstance(lldb::TargetSP target_sp, ++ ListenerSP listener_sp, ++ const FileSpec *crash_file_path, ++ bool can_connect) { ++ lldb::ProcessSP process_sp; ++ if (crash_file_path == nullptr) ++ process_sp = std::make_shared(target_sp, listener_sp); ++ return process_sp; ++} ++ ++bool ProcessWasm::CanDebug(lldb::TargetSP target_sp, ++ bool plugin_specified_by_name) { ++ if (plugin_specified_by_name) ++ return true; ++ ++ Module *exe_module = target_sp->GetExecutableModulePointer(); ++ if (exe_module) { ++ ObjectFile *exe_objfile = exe_module->GetObjectFile(); ++ return exe_objfile->GetArchitecture().GetMachine() == llvm::Triple::wasm32; ++ } ++ // However, if there is no wasm module, we return false, otherwise, ++ // we might use ProcessWasm to attach gdb remote. ++ return false; ++} ++ ++ ++ ++std::shared_ptr ProcessWasm::CreateThread(lldb::tid_t tid) { ++ return std::make_shared(*this, tid); ++} ++ ++size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size, ++ Status &error, ExecutionContext *exe_ctx) { ++ wasm_addr_t wasm_addr(vm_addr); ++ size_t nread = 0; ++ ++ switch (wasm_addr.GetType()) { ++ case WasmAddressType::Memory: ++ case WasmAddressType::Object: ++ return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error); ++ case WasmAddressType::Invalid: ++ default: ++ error.SetErrorStringWithFormat( ++ "Wasm read failed for invalid address 0x%" PRIx64, vm_addr); ++ return 0; ++ } ++} ++ ++size_t ProcessWasm::WasmReadMemory(uint32_t wasm_module_id, lldb::addr_t addr, ++ void *buf, size_t buffer_size) { ++ char packet[64]; ++ int packet_len = ++ ::snprintf(packet, sizeof(packet), "qWasmMem:%d;%" PRIx64 ";%" PRIx64, ++ wasm_module_id, static_cast(addr), ++ static_cast(buffer_size)); ++ assert(packet_len + 1 < (int)sizeof(packet)); ++ UNUSED_IF_ASSERT_DISABLED(packet_len); ++ StringExtractorGDBRemote response; ++ if (m_gdb_comm.SendPacketAndWaitForResponse(packet, response, GetInterruptTimeout()) == ++ GDBRemoteCommunication::PacketResult::Success) { ++ if (response.IsNormalResponse()) { ++ return response.GetHexBytes(llvm::MutableArrayRef( ++ static_cast(buf), buffer_size), ++ '\xdd'); ++ } ++ } ++ return 0; ++} ++ ++size_t ProcessWasm::WasmReadData(uint32_t wasm_module_id, lldb::addr_t addr, ++ void *buf, size_t buffer_size) { ++ char packet[64]; ++ int packet_len = ++ ::snprintf(packet, sizeof(packet), "qWasmData:%d;%" PRIx64 ";%" PRIx64, ++ wasm_module_id, static_cast(addr), ++ static_cast(buffer_size)); ++ assert(packet_len + 1 < (int)sizeof(packet)); ++ UNUSED_IF_ASSERT_DISABLED(packet_len); ++ StringExtractorGDBRemote response; ++ if (m_gdb_comm.SendPacketAndWaitForResponse(packet, response, GetInterruptTimeout()) == ++ GDBRemoteCommunication::PacketResult::Success) { ++ if (response.IsNormalResponse()) { ++ return response.GetHexBytes(llvm::MutableArrayRef( ++ static_cast(buf), buffer_size), ++ '\xdd'); ++ } ++ } ++ return 0; ++} ++ ++bool ProcessWasm::GetWasmLocal(int frame_index, int index, void *buf, ++ size_t buffer_size, size_t &size) { ++ StreamString packet; ++ packet.Printf("qWasmLocal:"); ++ packet.Printf("%d;%d", frame_index, index); ++ StringExtractorGDBRemote response; ++ if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) != ++ GDBRemoteCommunication::PacketResult::Success) { ++ return false; ++ } ++ ++ if (!response.IsNormalResponse()) { ++ return false; ++ } ++ ++ DataBufferSP buffer_sp( ++ new DataBufferHeap(response.GetStringRef().size() / 2, 0)); ++ response.GetHexBytes(buffer_sp->GetData(), '\xcc'); ++ size = buffer_sp->GetByteSize(); ++ if (size <= buffer_size) { ++ memcpy(buf, buffer_sp->GetBytes(), size); ++ return true; ++ } ++ ++ return false; ++} ++ ++bool ProcessWasm::GetWasmGlobal(int frame_index, int index, void *buf, ++ size_t buffer_size, size_t &size) { ++ StreamString packet; ++ packet.PutCString("qWasmGlobal:"); ++ packet.Printf("%d;%d", frame_index, index); ++ StringExtractorGDBRemote response; ++ if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) != ++ GDBRemoteCommunication::PacketResult::Success) { ++ return false; ++ } ++ ++ if (!response.IsNormalResponse()) { ++ return false; ++ } ++ ++ DataBufferSP buffer_sp( ++ new DataBufferHeap(response.GetStringRef().size() / 2, 0)); ++ response.GetHexBytes(buffer_sp->GetData(), '\xcc'); ++ size = buffer_sp->GetByteSize(); ++ if (size <= buffer_size) { ++ memcpy(buf, buffer_sp->GetBytes(), size); ++ return true; ++ } ++ ++ return false; ++} ++ ++bool ProcessWasm::GetWasmStackValue(int frame_index, int index, void *buf, ++ size_t buffer_size, size_t &size) { ++ StreamString packet; ++ packet.PutCString("qWasmStackValue:"); ++ packet.Printf("%d;%d", frame_index, index); ++ StringExtractorGDBRemote response; ++ if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) != ++ GDBRemoteCommunication::PacketResult::Success) { ++ return false; ++ } ++ ++ if (!response.IsNormalResponse()) { ++ return false; ++ } ++ ++ DataBufferSP buffer_sp( ++ new DataBufferHeap(response.GetStringRef().size() / 2, 0)); ++ response.GetHexBytes(buffer_sp->GetData(), '\xcc'); ++ size = buffer_sp->GetByteSize(); ++ if (size <= buffer_size) { ++ memcpy(buf, buffer_sp->GetBytes(), size); ++ return true; ++ } ++ ++ return false; ++} ++ ++bool ProcessWasm::GetWasmCallStack(lldb::tid_t tid, ++ std::vector &call_stack_pcs) { ++ call_stack_pcs.clear(); ++ StreamString packet; ++ packet.Printf("qWasmCallStack:"); ++ packet.Printf("%lx", tid); ++ StringExtractorGDBRemote response; ++ if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) != ++ GDBRemoteCommunication::PacketResult::Success) { ++ return false; ++ } ++ ++ if (!response.IsNormalResponse()) { ++ return false; ++ } ++ ++ addr_t buf[1024 / sizeof(addr_t)]; ++ size_t bytes = response.GetHexBytes( ++ llvm::MutableArrayRef((uint8_t *)buf, sizeof(buf)), '\xdd'); ++ if (bytes == 0) { ++ return false; ++ } ++ ++ for (size_t i = 0; i < bytes / sizeof(addr_t); i++) { ++ call_stack_pcs.push_back(buf[i]); ++ } ++ return true; ++} +diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.h b/lldb/source/Plugins/Process/wasm/ProcessWasm.h +new file mode 100644 +index 000000000000..d3aece7a6554 +--- /dev/null ++++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h +@@ -0,0 +1,128 @@ ++//===-- ProcessWasm.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 ++// ++//===----------------------------------------------------------------------===// ++ ++#ifndef LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H ++#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H ++ ++#include "Plugins/Process/gdb-remote/ProcessGDBRemote.h" ++#include "lldb/Target/RegisterContext.h" ++ ++namespace lldb_private { ++namespace wasm { ++ ++// Each WebAssembly module has separated address spaces for Code and Memory. ++// A WebAssembly module also has a Data section which, when the module is ++// loaded, gets mapped into a region in the module Memory. ++// For the purpose of debugging, we can represent all these separated 32-bit ++// address spaces with a single virtual 64-bit address space. ++// ++// Struct wasm_addr_t provides this encoding using bitfields ++// ++enum WasmAddressType { ++ Memory = 0x00, ++ Object = 0x01, ++ Invalid = 0x03 ++}; ++struct wasm_addr_t { ++ uint64_t offset : 32; ++ uint64_t module_id : 30; ++ uint64_t type : 2; ++ ++ wasm_addr_t(lldb::addr_t addr) ++ : type(addr >> 62), module_id((addr & 0x00ffffff00000000) >> 32), ++ offset(addr & 0x00000000ffffffff) {} ++ ++ wasm_addr_t(WasmAddressType type_, uint32_t module_id_, uint32_t offset_) ++ : type(type_), module_id(module_id_), offset(offset_) {} ++ ++ WasmAddressType GetType() { return static_cast(type); } ++ operator lldb::addr_t() { return *(uint64_t *)this; } ++}; ++ ++/// ProcessWasm provides the access to the Wasm program state ++/// retrieved from the Wasm engine. ++class ProcessWasm : public process_gdb_remote::ProcessGDBRemote { ++public: ++ ProcessWasm(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp); ++ ~ProcessWasm() override = default; ++ ++ static lldb::ProcessSP CreateInstance(lldb::TargetSP target_sp, ++ lldb::ListenerSP listener_sp, ++ const FileSpec *crash_file_path, ++ bool can_connect); ++ ++ static void Initialize(); ++ static void DebuggerInitialize(Debugger &debugger); ++ static void Terminate(); ++ static ConstString GetPluginNameStatic(); ++ static const char *GetPluginDescriptionStatic(); ++ ++ /// PluginInterface protocol. ++ /// \{ ++ ConstString GetPluginName() override; ++ uint32_t GetPluginVersion() override; ++ /// \} ++ ++ /// Process protocol. ++ /// \{ ++ size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size, Status &error, ++ ExecutionContext *exe_ctx = nullptr) override; ++ /// \} ++ ++ /// Query the value of a WebAssembly local variable from the WebAssembly ++ /// remote process. ++ bool GetWasmLocal(int frame_index, int index, void *buf, size_t buffer_size, ++ size_t &size); ++ ++ /// Query the value of a WebAssembly global variable from the WebAssembly ++ /// remote process. ++ bool GetWasmGlobal(int frame_index, int index, void *buf, size_t buffer_size, ++ size_t &size); ++ ++ /// Query the value of an item in the WebAssembly operand stack from the ++ /// WebAssembly remote process. ++ bool GetWasmStackValue(int frame_index, int index, void *buf, ++ size_t buffer_size, size_t &size); ++ ++ /// Read from the WebAssembly Memory space. ++ size_t WasmReadMemory(uint32_t wasm_module_id, lldb::addr_t addr, void *buf, ++ size_t buffer_size); ++ ++ /// Read from the WebAssembly Data space. ++ size_t WasmReadData(uint32_t wasm_module_id, lldb::addr_t addr, void *buf, ++ size_t buffer_size); ++ ++ /// Retrieve the current call stack from the WebAssembly remote process. ++ bool GetWasmCallStack(lldb::tid_t tid, ++ std::vector &call_stack_pcs); ++ ++ // Check if a given Process ++ bool CanDebug(lldb::TargetSP target_sp, ++ bool plugin_specified_by_name) override; ++ ++protected: ++ /// ProcessGDBRemote protocol. ++ /// \{ ++ std::shared_ptr ++ CreateThread(lldb::tid_t tid) override; ++ /// \} ++ ++private: ++ friend class UnwindWasm; ++ process_gdb_remote::GDBRemoteDynamicRegisterInfoSP &GetRegisterInfo() { ++ return m_register_info_sp; ++ } ++ ++ ProcessWasm(const ProcessWasm &); ++ const ProcessWasm &operator=(const ProcessWasm &) = delete; ++}; ++ ++} // namespace wasm ++} // namespace lldb_private ++ ++#endif // LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H +diff --git a/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp b/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp +new file mode 100644 +index 000000000000..fa02073e7a52 +--- /dev/null ++++ b/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp +@@ -0,0 +1,35 @@ ++//===-- ThreadWasm.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 "ThreadWasm.h" ++ ++#include "ProcessWasm.h" ++#include "UnwindWasm.h" ++#include "lldb/Target/Target.h" ++ ++using namespace lldb; ++using namespace lldb_private; ++using namespace lldb_private::wasm; ++ ++Unwind &ThreadWasm::GetUnwinder() { ++ if (!m_unwinder_up) { ++ assert(CalculateTarget()->GetArchitecture().GetMachine() == ++ llvm::Triple::wasm32); ++ m_unwinder_up.reset(new wasm::UnwindWasm(*this)); ++ } ++ return *m_unwinder_up; ++} ++ ++bool ThreadWasm::GetWasmCallStack(std::vector &call_stack_pcs) { ++ ProcessSP process_sp(GetProcess()); ++ if (process_sp) { ++ ProcessWasm *wasm_process = static_cast(process_sp.get()); ++ return wasm_process->GetWasmCallStack(GetID(), call_stack_pcs); ++ } ++ return false; ++} +diff --git a/lldb/source/Plugins/Process/wasm/ThreadWasm.h b/lldb/source/Plugins/Process/wasm/ThreadWasm.h +new file mode 100644 +index 000000000000..0a33c07de994 +--- /dev/null ++++ b/lldb/source/Plugins/Process/wasm/ThreadWasm.h +@@ -0,0 +1,41 @@ ++//===-- ThreadWasm.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 ++// ++//===----------------------------------------------------------------------===// ++ ++#ifndef LLDB_SOURCE_PLUGINS_PROCESS_WASM_THREADWASM_H ++#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_THREADWASM_H ++ ++#include "Plugins/Process/gdb-remote/ThreadGDBRemote.h" ++ ++namespace lldb_private { ++namespace wasm { ++ ++/// ProcessWasm provides the access to the Wasm program state ++/// retrieved from the Wasm engine. ++class ThreadWasm : public process_gdb_remote::ThreadGDBRemote { ++public: ++ ThreadWasm(Process &process, lldb::tid_t tid) ++ : process_gdb_remote::ThreadGDBRemote(process, tid) {} ++ ~ThreadWasm() override = default; ++ ++ /// Retrieve the current call stack from the WebAssembly remote process. ++ bool GetWasmCallStack(std::vector &call_stack_pcs); ++ ++protected: ++ /// Thread protocol. ++ /// \{ ++ Unwind &GetUnwinder() override; ++ /// \} ++ ++ ThreadWasm(const ThreadWasm &); ++ const ThreadWasm &operator=(const ThreadWasm &) = delete; ++}; ++ ++} // namespace wasm ++} // namespace lldb_private ++ ++#endif // LLDB_SOURCE_PLUGINS_PROCESS_WASM_THREADWASM_H +diff --git a/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp b/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp +new file mode 100644 +index 000000000000..1a195cb9361a +--- /dev/null ++++ b/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp +@@ -0,0 +1,74 @@ ++//===-- UnwindWasm.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 "UnwindWasm.h" ++#include "Plugins/Process/gdb-remote/ThreadGDBRemote.h" ++#include "Plugins/Process/wasm/ProcessWasm.h" ++#include "Plugins/Process/wasm/ThreadWasm.h" ++ ++using namespace lldb; ++using namespace lldb_private; ++using namespace process_gdb_remote; ++using namespace wasm; ++ ++class WasmGDBRemoteRegisterContext : public GDBRemoteRegisterContext { ++public: ++ WasmGDBRemoteRegisterContext(ThreadGDBRemote &thread, ++ uint32_t concrete_frame_idx, ++ GDBRemoteDynamicRegisterInfoSP ®_info_sp, ++ uint64_t pc) ++ : GDBRemoteRegisterContext(thread, concrete_frame_idx, reg_info_sp, false, ++ false) { ++ PrivateSetRegisterValue(0, pc); ++ } ++}; ++ ++lldb::RegisterContextSP ++UnwindWasm::DoCreateRegisterContextForFrame(lldb_private::StackFrame *frame) { ++ if (m_frames.size() <= frame->GetFrameIndex()) { ++ return lldb::RegisterContextSP(); ++ } ++ ++ ThreadSP thread = frame->GetThread(); ++ ThreadGDBRemote *gdb_thread = static_cast(thread.get()); ++ ProcessWasm *wasm_process = ++ static_cast(thread->GetProcess().get()); ++ std::shared_ptr reg_ctx_sp = ++ std::make_shared( ++ *gdb_thread, frame->GetConcreteFrameIndex(), ++ wasm_process->GetRegisterInfo(), m_frames[frame->GetFrameIndex()]); ++ return reg_ctx_sp; ++} ++ ++uint32_t UnwindWasm::DoGetFrameCount() { ++ if (!m_unwind_complete) { ++ m_unwind_complete = true; ++ m_frames.clear(); ++ ++ ThreadWasm &wasm_thread = static_cast(GetThread()); ++ if (!wasm_thread.GetWasmCallStack(m_frames)) ++ m_frames.clear(); ++ } ++ return m_frames.size(); ++} ++ ++bool UnwindWasm::DoGetFrameInfoAtIndex(uint32_t frame_idx, lldb::addr_t &cfa, ++ lldb::addr_t &pc, ++ bool &behaves_like_zeroth_frame) { ++ if (m_frames.size() == 0) { ++ DoGetFrameCount(); ++ } ++ ++ if (frame_idx < m_frames.size()) { ++ behaves_like_zeroth_frame = (frame_idx == 0); ++ cfa = 0; ++ pc = m_frames[frame_idx]; ++ return true; ++ } ++ return false; ++} +\ No newline at end of file +diff --git a/lldb/source/Plugins/Process/wasm/UnwindWasm.h b/lldb/source/Plugins/Process/wasm/UnwindWasm.h +new file mode 100644 +index 000000000000..9bd1dac9a98a +--- /dev/null ++++ b/lldb/source/Plugins/Process/wasm/UnwindWasm.h +@@ -0,0 +1,55 @@ ++//===-- UnwindWasm.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 ++// ++//===----------------------------------------------------------------------===// ++ ++#ifndef lldb_UnwindWasm_h_ ++#define lldb_UnwindWasm_h_ ++ ++#include "lldb/Target/RegisterContext.h" ++#include "lldb/Target/Unwind.h" ++#include ++ ++namespace lldb_private { ++namespace wasm { ++ ++/// UnwindWasm manages stack unwinding for a WebAssembly process. ++class UnwindWasm : public lldb_private::Unwind { ++public: ++ UnwindWasm(lldb_private::Thread &thread) ++ : Unwind(thread), m_frames(), m_unwind_complete(false) {} ++ ~UnwindWasm() override = default; ++ ++protected: ++ /// Unwind protocol. ++ /// \{ ++ void DoClear() override { ++ m_frames.clear(); ++ m_unwind_complete = false; ++ } ++ ++ uint32_t DoGetFrameCount() override; ++ ++ bool DoGetFrameInfoAtIndex(uint32_t frame_idx, lldb::addr_t &cfa, ++ lldb::addr_t &pc, ++ bool &behaves_like_zeroth_frame) override; ++ ++ lldb::RegisterContextSP ++ DoCreateRegisterContextForFrame(lldb_private::StackFrame *frame) override; ++ /// \} ++ ++private: ++ std::vector m_frames; ++ bool m_unwind_complete; ++ ++ UnwindWasm(const UnwindWasm &); ++ const UnwindWasm &operator=(const UnwindWasm &) = delete; ++}; ++ ++} // namespace wasm ++} // namespace lldb_private ++ ++#endif // lldb_UnwindWasm_h_ +diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp +index ccaf31317d75..c3ef5aebd46d 100644 +--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp ++++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp +@@ -3212,8 +3212,13 @@ VariableSP SymbolFileDWARF::ParseVariableDIE(const SymbolContext &sc, + GetDWARFDeclContext(die).GetQualifiedNameAsConstString().GetCString(); + } + +- if (tag == DW_TAG_formal_parameter) ++ if (tag == DW_TAG_formal_parameter) { + scope = eValueTypeVariableArgument; ++ // For Wasm dwarft, pamameter may don't have location attr, ++ // so set module here ++ if (!location.GetModule()) ++ location.SetModule(module); ++ } + else { + // DWARF doesn't specify if a DW_TAG_variable is a local, global + // or static variable, so we have to do a little digging: +diff --git a/lldb/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp +index a77ecddfbab6..e257f93508f6 100644 +--- a/lldb/source/Target/Platform.cpp ++++ b/lldb/source/Target/Platform.cpp +@@ -1970,6 +1970,12 @@ size_t Platform::GetSoftwareBreakpointTrapOpcode(Target &target, + trap_opcode_size = sizeof(g_i386_opcode); + } break; + ++ case llvm::Triple::wasm32: { ++ static const uint8_t g_wasm_opcode[] = {0x00}; // unreachable ++ trap_opcode = g_wasm_opcode; ++ trap_opcode_size = sizeof(g_wasm_opcode); ++ } break; ++ + default: + return 0; + } +diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp +index 8ecc66b592ea..f148987915de 100644 +--- a/lldb/source/Target/Process.cpp ++++ b/lldb/source/Target/Process.cpp +@@ -1892,7 +1892,8 @@ Status Process::DisableSoftwareBreakpoint(BreakpointSite *bp_site) { + // code + //#define VERIFY_MEMORY_READS + +-size_t Process::ReadMemory(addr_t addr, void *buf, size_t size, Status &error) { ++size_t Process::ReadMemory(addr_t addr, void *buf, size_t size, Status &error, ++ ExecutionContext *exe_ctx) { + error.Clear(); + if (!GetDisableMemoryCache()) { + #if defined(VERIFY_MEMORY_READS) +diff --git a/lldb/source/Target/ProcessTrace.cpp b/lldb/source/Target/ProcessTrace.cpp +index c878a2ac4eb9..ad5945b0ad1f 100644 +--- a/lldb/source/Target/ProcessTrace.cpp ++++ b/lldb/source/Target/ProcessTrace.cpp +@@ -88,7 +88,7 @@ void ProcessTrace::RefreshStateAfterStop() {} + Status ProcessTrace::DoDestroy() { return Status(); } + + size_t ProcessTrace::ReadMemory(addr_t addr, void *buf, size_t size, +- Status &error) { ++ Status &error, ExecutionContext *exe_ctx) { + // Don't allow the caching that lldb_private::Process::ReadMemory does since + // we have it all cached in the trace files. + return DoReadMemory(addr, buf, size, error); diff --git a/build-scripts/runtime_lib.cmake b/build-scripts/runtime_lib.cmake index d6dce67f..2ef197fc 100644 --- a/build-scripts/runtime_lib.cmake +++ b/build-scripts/runtime_lib.cmake @@ -84,6 +84,11 @@ if (WAMR_BUILD_LIB_PTHREAD EQUAL 1) set (WAMR_BUILD_SHARED_MEMORY 1) endif () +if (WAMR_BUILD_DEBUG_INTERP EQUAL 1) + set (WAMR_BUILD_THREAD_MGR 1) + include (${IWASM_DIR}/libraries/debug-engine/debug_engine.cmake) +endif () + if (WAMR_BUILD_THREAD_MGR EQUAL 1) include (${IWASM_DIR}/libraries/thread-mgr/thread_mgr.cmake) endif () @@ -132,6 +137,7 @@ set (source_all ${LIB_PTHREAD_SOURCE} ${THREAD_MGR_SOURCE} ${LIBC_EMCC_SOURCE} + ${DEBUG_ENGINE_SOURCE} ) set (WAMR_RUNTIME_LIB_SOURCE ${source_all}) diff --git a/core/iwasm/aot/aot_loader.c b/core/iwasm/aot/aot_loader.c index 10907e85..8c296f0e 100644 --- a/core/iwasm/aot/aot_loader.c +++ b/core/iwasm/aot/aot_loader.c @@ -14,6 +14,10 @@ #include "../compilation/aot_llvm.h" #include "../interpreter/wasm_loader.h" #endif +#if WASM_ENABLE_DEBUG_AOT != 0 +#include "debug/elf_parser.h" +#include "debug/jit_debug.h" +#endif #define XMM_PLT_PREFIX "__xmm@" #define REAL_PLT_PREFIX "__real@" @@ -1231,7 +1235,6 @@ load_init_data_section(const uint8 *buf, const uint8 *buf_end, } return true; - fail: return false; } @@ -1257,12 +1260,30 @@ load_text_section(const uint8 *buf, const uint8 *buf_end, module->code = (void*)(buf + module->literal_size); module->code_size = (uint32)(buf_end - (uint8*)module->code); +#if WASM_ENABLE_DEBUG_AOT != 0 + module->elf_size = module->code_size; + + if (is_ELF(module->code)) { + /* Now code points to an ELF object, we pull it down to .text section */ + uint64 offset; + uint64 size; + char *buf = module->code; + module->elf_hdr = buf; + if (!get_text_section(buf, &offset, &size)) { + set_error_buf(error_buf, error_buf_size, + "get text section of ELF failed"); + return false; + } + module->code = buf + offset; + module->code_size -= (uint32)offset; + } +#endif + if ((module->code_size > 0) && (module->native_symbol_count == 0)) { plt_base = (uint8 *)buf_end - get_plt_table_size(); init_plt_table(plt_base); } return true; - fail: return false; } @@ -1451,7 +1472,6 @@ load_export_section(const uint8 *buf, const uint8 *buf_end, } return true; - fail: return false; } @@ -2247,6 +2267,13 @@ load_from_sections(AOTModule *module, AOTSection *sections, #if WASM_ENABLE_MEMORY_TRACING != 0 wasm_runtime_dump_module_mem_consumption((WASMModuleCommon*)module); #endif + +#if WASM_ENABLE_DEBUG_AOT != 0 + if (!jit_code_entry_create(module->elf_hdr, module->elf_size)) { + set_error_buf(error_buf, error_buf_size, "create jit code entry failed"); + return false; + } +#endif return true; } @@ -2893,6 +2920,9 @@ aot_unload(AOTModule *module) if (module->data_sections) destroy_object_data_sections(module->data_sections, module->data_section_count); +#if WASM_ENABLE_DEBUG_AOT != 0 + jit_code_entry_destroy(module->elf_hdr); +#endif wasm_runtime_free(module); } diff --git a/core/iwasm/aot/aot_runtime.h b/core/iwasm/aot/aot_runtime.h index 159aac24..70c0697e 100644 --- a/core/iwasm/aot/aot_runtime.h +++ b/core/iwasm/aot/aot_runtime.h @@ -254,6 +254,10 @@ typedef struct AOTModule { WASIArguments wasi_args; bool is_wasi_module; #endif +#if WASM_ENABLE_DEBUG_AOT != 0 + void *elf_hdr; + uint32 elf_size; +#endif } AOTModule; typedef union { diff --git a/core/iwasm/aot/debug/elf_parser.c b/core/iwasm/aot/debug/elf_parser.c new file mode 100644 index 00000000..57714ce6 --- /dev/null +++ b/core/iwasm/aot/debug/elf_parser.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "aot_runtime.h" +#include "bh_log.h" +#include "elf_parser.h" + +bool +is_ELF(void *buf) +{ + Elf32_Ehdr *eh = (Elf32_Ehdr *)buf; + if (!strncmp((char *)eh->e_ident, "\177ELF", 4)) { + LOG_VERBOSE("the buffer is ELF entry!"); + return true; + } + LOG_VERBOSE("the buffer is not ELF entry!"); + return false; +} + +static bool +is64Bit(Elf32_Ehdr *eh) +{ + if (eh->e_ident[EI_CLASS] == ELFCLASS64) + return true; + else + return false; +} + +static bool +is32Bit(Elf32_Ehdr *eh) +{ + if (eh->e_ident[EI_CLASS] == ELFCLASS32) + return true; + else + return false; +} + +bool +is_ELF64(void *buf) +{ + Elf64_Ehdr *eh = (Elf64_Ehdr *)buf; + if (!strncmp((char *)eh->e_ident, "\177ELF", 4)) { + LOG_VERBOSE("the buffer is ELF entry!"); + return true; + } + LOG_VERBOSE("the buffer is not ELF entry!"); + return false; +} + +static void +read_section_header_table(Elf32_Ehdr *eh, Elf32_Shdr *sh_table[]) +{ + uint32_t i; + char *buf = (char *)eh; + buf += eh->e_shoff; + LOG_VERBOSE("str index = %d count=%d", eh->e_shstrndx, eh->e_shnum); + for (i = 0; i < eh->e_shnum; i++) { + sh_table[i] = (Elf32_Shdr *)buf; + buf += eh->e_shentsize; + } +} + +static void +read_section_header_table64(Elf64_Ehdr *eh, Elf64_Shdr *sh_table[]) +{ + uint32_t i; + char *buf = (char *)eh; + buf += eh->e_shoff; + + for (i = 0; i < eh->e_shnum; i++) { + sh_table[i] = (Elf64_Shdr *)buf; + buf += eh->e_shentsize; + } +} + +static char * +get_section(Elf32_Ehdr *eh, Elf32_Shdr *section_header) +{ + char *buf = (char *)eh; + return buf + section_header->sh_offset; +} + +static char * +get_section64(Elf64_Ehdr *eh, Elf64_Shdr *section_header) +{ + char *buf = (char *)eh; + return buf + section_header->sh_offset; +} + +bool +get_text_section(void *buf, uint64_t *offset, uint64_t *size) +{ + bool ret = false; + uint32 i; + char *sh_str; + + if (is64Bit(buf)) { + Elf64_Ehdr *eh = (Elf64_Ehdr *)buf; + Elf64_Shdr **sh_table = + wasm_runtime_malloc(eh->e_shnum * sizeof(Elf64_Shdr *)); + if (sh_table) { + read_section_header_table64(eh, sh_table); + sh_str = get_section64(eh, sh_table[eh->e_shstrndx]); + for (i= 0; i < eh->e_shnum; i++) { + if (!strcmp(sh_str + sh_table[i]->sh_name, ".text")) { + *offset = sh_table[i]->sh_offset; + *size = sh_table[i]->sh_size; + sh_table[i]->sh_addr = (Elf64_Addr)(uintptr_t) + ((char *)buf + sh_table[i]->sh_offset); + ret = true; + break; + } + } + wasm_runtime_free(sh_table); + } + } + else if (is32Bit(buf)) { + Elf32_Ehdr *eh = (Elf32_Ehdr *)buf; + Elf32_Shdr **sh_table = + wasm_runtime_malloc(eh->e_shnum * sizeof(Elf32_Shdr *)); + if (sh_table) { + read_section_header_table(eh, sh_table); + sh_str = get_section(eh, sh_table[eh->e_shstrndx]); + for (i= 0; i < eh->e_shnum; i++) { + if (!strcmp(sh_str + sh_table[i]->sh_name, ".text")) { + *offset = sh_table[i]->sh_offset; + *size = sh_table[i]->sh_size; + sh_table[i]->sh_addr = (Elf32_Addr)(uintptr_t) + ((char *)buf + sh_table[i]->sh_offset); + ret = true; + break; + } + } + wasm_runtime_free(sh_table); + } + } + + return ret; +} diff --git a/core/iwasm/aot/debug/elf_parser.h b/core/iwasm/aot/debug/elf_parser.h new file mode 100644 index 00000000..4e4b9b21 --- /dev/null +++ b/core/iwasm/aot/debug/elf_parser.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifndef _ELF_PARSERE_H_ +#define _ELF_PARSER_H_ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool +is_ELF(void *buf); + +bool +is_ELF64(void *buf); + +bool +get_text_section(void *buf, uint64_t *offset, uint64_t *size); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/core/iwasm/aot/debug/jit_debug.c b/core/iwasm/aot/debug/jit_debug.c new file mode 100644 index 00000000..8d79ff03 --- /dev/null +++ b/core/iwasm/aot/debug/jit_debug.c @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "bh_log.h" +#include "bh_platform.h" +#include "wasm_runtime.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* This must be kept in sync with gdb/gdb/jit.h */ +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + JIT_NOACTION = 0, + JIT_REGISTER_FN, + JIT_UNREGISTER_FN +} JITAction; + +typedef struct JITCodeEntry { + struct JITCodeEntry *next_; + struct JITCodeEntry *prev_; + const uint8 *symfile_addr_; + uint64 symfile_size_; +} JITCodeEntry; + +typedef struct JITDescriptor { + uint32 version_; + uint32 action_flag_; + JITCodeEntry *relevant_entry_; + JITCodeEntry *first_entry_; +} JITDescriptor; + +/* LLVM has already define this */ +#if (WASM_ENABLE_WAMR_COMPILER == 0) && (WASM_ENABLE_JIT == 0) +/** + * GDB will place breakpoint into this function. + * To prevent GCC from inlining or removing it we place noinline attribute + * and inline assembler statement inside. + */ +void __attribute__((noinline)) __jit_debug_register_code(); + +void __attribute__((noinline)) __jit_debug_register_code() +{ + int x; + *(char *)&x = '\0'; +} + +/** + * GDB will inspect contents of this descriptor. + * Static initialization is necessary to prevent GDB from seeing + * uninitialized descriptor. + */ + +JITDescriptor __jit_debug_descriptor = { 1, JIT_NOACTION, NULL, NULL }; +#else +extern void __jit_debug_register_code(); +extern JITDescriptor __jit_debug_descriptor; +#endif + +/** + * Call __jit_debug_register_code indirectly via global variable. + * This gives the debugger an easy way to inject custom code to + * handle the events. + */ +void (*__jit_debug_register_code_ptr)() = __jit_debug_register_code; + +#ifdef __cplusplus +} +#endif + +typedef struct WASMJITDebugEngine { + korp_mutex jit_entry_lock; + bh_list jit_entry_list; +} WASMJITDebugEngine; + +typedef struct WASMJITEntryNode { + struct WASMJITEntryNode *next; + JITCodeEntry *entry; +} WASMJITEntryNode; + +static WASMJITDebugEngine *jit_debug_engine; + +static JITCodeEntry * +CreateJITCodeEntryInternal(const uint8 *symfile_addr, uint64 symfile_size) +{ + JITCodeEntry *entry; + + os_mutex_lock(&jit_debug_engine->jit_entry_lock); + + if (!(entry = wasm_runtime_malloc(sizeof(JITCodeEntry)))) { + LOG_ERROR("WASM JIT Debug Engine error: failed to allocate memory"); + os_mutex_unlock(&jit_debug_engine->jit_entry_lock); + return NULL; + } + entry->symfile_addr_ = symfile_addr; + entry->symfile_size_ = symfile_size; + entry->prev_ = NULL; + + entry->next_ = __jit_debug_descriptor.first_entry_; + if (entry->next_ != NULL) { + entry->next_->prev_ = entry; + } + __jit_debug_descriptor.first_entry_ = entry; + __jit_debug_descriptor.relevant_entry_ = entry; + + __jit_debug_descriptor.action_flag_ = JIT_REGISTER_FN; + + (*__jit_debug_register_code_ptr)(); + + os_mutex_unlock(&jit_debug_engine->jit_entry_lock); + return entry; +} + +static void +DestroyJITCodeEntryInternal(JITCodeEntry *entry) +{ + os_mutex_lock(&jit_debug_engine->jit_entry_lock); + + if (entry->prev_ != NULL) { + entry->prev_->next_ = entry->next_; + } + else { + __jit_debug_descriptor.first_entry_ = entry->next_; + } + + if (entry->next_ != NULL) { + entry->next_->prev_ = entry->prev_; + } + + __jit_debug_descriptor.relevant_entry_ = entry; + __jit_debug_descriptor.action_flag_ = JIT_UNREGISTER_FN; + (*__jit_debug_register_code_ptr)(); + + wasm_runtime_free(entry); + + os_mutex_unlock(&jit_debug_engine->jit_entry_lock); +} + +bool +jit_debug_engine_init() +{ + if (jit_debug_engine) { + return true; + } + + if (!(jit_debug_engine = + wasm_runtime_malloc(sizeof(WASMJITDebugEngine)))) { + LOG_ERROR("WASM JIT Debug Engine error: failed to allocate memory"); + return false; + } + memset(jit_debug_engine, 0, sizeof(WASMJITDebugEngine)); + + if (os_mutex_init(&jit_debug_engine->jit_entry_lock) != 0) { + wasm_runtime_free(jit_debug_engine); + jit_debug_engine = NULL; + return false; + } + + bh_list_init(&jit_debug_engine->jit_entry_list); + return true; +} + +void +jit_debug_engine_destroy() +{ + if (jit_debug_engine) { + WASMJITEntryNode *node, *node_next; + + /* Destroy all nodes */ + node = bh_list_first_elem(&jit_debug_engine->jit_entry_list); + while (node) { + node_next = bh_list_elem_next(node); + DestroyJITCodeEntryInternal(node->entry); + bh_list_remove(&jit_debug_engine->jit_entry_list, node); + wasm_runtime_free(node); + node = node_next; + } + + /* Destroy JIT Debug Engine */ + os_mutex_destroy(&jit_debug_engine->jit_entry_lock); + wasm_runtime_free(jit_debug_engine); + jit_debug_engine = NULL; + } +} + +bool +jit_code_entry_create(const uint8 *symfile_addr, uint64 symfile_size) +{ + JITCodeEntry *entry; + WASMJITEntryNode *node; + + if (!(node = wasm_runtime_malloc(sizeof(WASMJITEntryNode)))) { + LOG_ERROR("WASM JIT Debug Engine error: failed to allocate memory"); + return false; + } + + entry = CreateJITCodeEntryInternal(symfile_addr, symfile_size); + + if (!entry) { + wasm_runtime_free(node); + return false; + } + + node->entry = entry; + os_mutex_lock(&jit_debug_engine->jit_entry_lock); + bh_list_insert(&jit_debug_engine->jit_entry_list, node); + os_mutex_unlock(&jit_debug_engine->jit_entry_lock); + return true; +} + +void +jit_code_entry_destroy(const uint8 *symfile_addr) +{ + WASMJITEntryNode *node; + + node = bh_list_first_elem(&jit_debug_engine->jit_entry_list); + while (node) { + WASMJITEntryNode *next_node = bh_list_elem_next(node); + if (node->entry->symfile_addr_ == symfile_addr) { + DestroyJITCodeEntryInternal(node->entry); + os_mutex_lock(&jit_debug_engine->jit_entry_lock); + bh_list_remove(&jit_debug_engine->jit_entry_list, node); + os_mutex_unlock(&jit_debug_engine->jit_entry_lock); + wasm_runtime_free(node); + } + node = next_node; + } +} diff --git a/core/iwasm/aot/debug/jit_debug.h b/core/iwasm/aot/debug/jit_debug.h new file mode 100644 index 00000000..5e3e3651 --- /dev/null +++ b/core/iwasm/aot/debug/jit_debug.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifndef _JIT_DEBUG_H_ +#define _JIT_DEBUG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +bool +jit_debug_engine_init(); + +void +jit_debug_engine_destroy(); + +bool +jit_code_entry_create(const uint8 *symfile_addr, uint64 symfile_size); + +void +jit_code_entry_destroy(const uint8 *symfile_addr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/iwasm/aot/iwasm_aot.cmake b/core/iwasm/aot/iwasm_aot.cmake index 2a685687..8014f237 100644 --- a/core/iwasm/aot/iwasm_aot.cmake +++ b/core/iwasm/aot/iwasm_aot.cmake @@ -31,5 +31,10 @@ else () message (FATAL_ERROR "Build target isn't set") endif () -set (IWASM_AOT_SOURCE ${c_source_all} ${arch_source}) +if (WAMR_BUILD_DEBUG_AOT EQUAL 1) + add_definitions(-DWASM_ENABLE_DEBUG_AOT=1) + file(GLOB debug_source ${IWASM_AOT_DIR}/debug/*.c) +endif() + +set (IWASM_AOT_SOURCE ${c_source_all} ${arch_source} ${debug_source}) diff --git a/core/iwasm/common/wasm_exec_env.c b/core/iwasm/common/wasm_exec_env.c index f54f0931..c9e2bf5a 100644 --- a/core/iwasm/common/wasm_exec_env.c +++ b/core/iwasm/common/wasm_exec_env.c @@ -18,6 +18,9 @@ #if WASM_ENABLE_THREAD_MGR != 0 #include "../libraries/thread-mgr/thread_manager.h" +#if WASM_ENABLE_DEBUG_INTERP != 0 +#include "../libraries/debug-engine/debug_engine.h" +#endif #endif WASMExecEnv * @@ -46,6 +49,12 @@ wasm_exec_env_create_internal(struct WASMModuleInstanceCommon *module_inst, if (os_cond_init(&exec_env->wait_cond) != 0) goto fail3; + +#if WASM_ENABLE_DEBUG_INTERP != 0 + if (!(exec_env->current_status = wasm_cluster_create_exenv_status())) + goto fail4; +#endif + #endif exec_env->module_inst = module_inst; @@ -68,6 +77,10 @@ wasm_exec_env_create_internal(struct WASMModuleInstanceCommon *module_inst, return exec_env; #if WASM_ENABLE_THREAD_MGR != 0 +#if WASM_ENABLE_DEBUG_INTERP != 0 +fail4: + os_cond_destroy(&exec_env->wait_cond); +#endif fail3: os_mutex_destroy(&exec_env->wait_lock); fail2: @@ -86,6 +99,9 @@ wasm_exec_env_destroy_internal(WASMExecEnv *exec_env) #if WASM_ENABLE_THREAD_MGR != 0 os_mutex_destroy(&exec_env->wait_lock); os_cond_destroy(&exec_env->wait_cond); +#if WASM_ENABLE_DEBUG_INTERP != 0 + wasm_cluster_destroy_exenv_status(exec_env->current_status); +#endif #endif #if WASM_ENABLE_AOT != 0 wasm_runtime_free(exec_env->argv_buf); @@ -131,6 +147,10 @@ wasm_exec_env_create(struct WASMModuleInstanceCommon *module_inst, wasm_exec_env_destroy_internal(exec_env); return NULL; } +#if WASM_ENABLE_DEBUG_INTERP != 0 + wasm_debug_instance_create(cluster); +#endif + #endif return exec_env; } @@ -142,6 +162,12 @@ wasm_exec_env_destroy(WASMExecEnv *exec_env) /* Terminate all sub-threads */ WASMCluster *cluster = wasm_exec_env_get_cluster(exec_env); if (cluster) { +#if WASM_ENABLE_THREAD_MGR != 0 +#if WASM_ENABLE_DEBUG_INTERP != 0 + wasm_cluster_thread_exited(exec_env); + wasm_debug_instance_destroy(cluster); +#endif +#endif wasm_cluster_terminate_all_except_self(cluster, exec_env); wasm_cluster_del_exec_env(cluster, exec_env); } diff --git a/core/iwasm/common/wasm_exec_env.h b/core/iwasm/common/wasm_exec_env.h index f101504d..dc651445 100644 --- a/core/iwasm/common/wasm_exec_env.h +++ b/core/iwasm/common/wasm_exec_env.h @@ -20,6 +20,9 @@ struct WASMInterpFrame; #if WASM_ENABLE_THREAD_MGR != 0 typedef struct WASMCluster WASMCluster; +#if WASM_ENABLE_DEBUG_INTERP != 0 +typedef struct WASMCurrentEnvStatus WASMCurrentEnvStatus; +#endif #endif #ifdef OS_ENABLE_HW_BOUND_CHECK @@ -97,6 +100,10 @@ typedef struct WASMExecEnv { korp_cond wait_cond; #endif +#if WASM_ENABLE_DEBUG_INTERP != 0 + WASMCurrentEnvStatus *current_status; +#endif + /* attachment for native function */ void *attachment; diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c index f739ff63..6ddd28e6 100644 --- a/core/iwasm/common/wasm_runtime_common.c +++ b/core/iwasm/common/wasm_runtime_common.c @@ -14,9 +14,15 @@ #endif #if WASM_ENABLE_AOT != 0 #include "../aot/aot_runtime.h" +#if WASM_ENABLE_DEBUG_AOT != 0 +#include "../aot/debug/jit_debug.h" +#endif #endif #if WASM_ENABLE_THREAD_MGR != 0 #include "../libraries/thread-mgr/thread_manager.h" +#if WASM_ENABLE_DEBUG_INTERP != 0 +#include "../libraries/debug-engine/debug_engine.h" +#endif #endif #if WASM_ENABLE_SHARED_MEMORY != 0 #include "wasm_shared_memory.h" @@ -128,20 +134,29 @@ wasm_runtime_env_init() goto fail6; } #endif +#if WASM_ENABLE_DEBUG_AOT != 0 + if (!jit_debug_engine_init()) { + goto fail7; + } +#endif #endif #if WASM_ENABLE_REF_TYPES != 0 if (!wasm_externref_map_init()) { - goto fail7; + goto fail8; } #endif return true; #if WASM_ENABLE_REF_TYPES != 0 -fail7: +fail8: #endif #if WASM_ENABLE_AOT != 0 +#if WASM_ENABLE_DEBUG_AOT != 0 + jit_debug_engine_destroy(); +fail7: +#endif #ifdef OS_ENABLE_HW_BOUND_CHECK aot_signal_destroy(); fail6: @@ -201,6 +216,9 @@ wasm_runtime_destroy() #endif #if WASM_ENABLE_AOT != 0 +#if WASM_ENABLE_DEBUG_AOT != 0 + jit_debug_engine_destroy(); +#endif #ifdef OS_ENABLE_HW_BOUND_CHECK aot_signal_destroy(); #endif @@ -220,6 +238,9 @@ wasm_runtime_destroy() #endif #if (WASM_ENABLE_WAMR_COMPILER == 0) && (WASM_ENABLE_THREAD_MGR != 0) +#if WASM_ENABLE_DEBUG_INTERP != 0 + wasm_debug_engine_destroy(); +#endif thread_manager_destroy(); #endif @@ -241,6 +262,16 @@ wasm_runtime_full_init(RuntimeInitArgs *init_args) return false; } +#if WASM_ENABLE_DEBUG_INTERP != 0 + if (strlen(init_args->ip_addr)) + if (!wasm_debug_engine_init(init_args->ip_addr, + init_args->platform_port, + init_args->instance_port)) { + wasm_runtime_destroy(); + return false; + } +#endif + if (init_args->n_native_symbols > 0 && !wasm_runtime_register_natives(init_args->native_module_name, init_args->native_symbols, diff --git a/core/iwasm/common/wasm_runtime_common.h b/core/iwasm/common/wasm_runtime_common.h index 328cdfac..baaf3b9d 100644 --- a/core/iwasm/common/wasm_runtime_common.h +++ b/core/iwasm/common/wasm_runtime_common.h @@ -774,24 +774,24 @@ wasm_runtime_finalize_call_function(WASMExecEnv *exec_env, bool wasm_runtime_get_export_func_type(const WASMModuleCommon *module_comm, - const WASMExport *export_func_type, + const WASMExport *export_, WASMType **out); bool wasm_runtime_get_export_global_type(const WASMModuleCommon *module_comm, - const WASMExport *export_global_type, + const WASMExport *export_, uint8 *out_val_type, bool *out_mutability); bool wasm_runtime_get_export_memory_type(const WASMModuleCommon *module_comm, - const WASMExport *export_memory_type, + const WASMExport *export_, uint32 *out_min_page, uint32 *out_max_page); bool wasm_runtime_get_export_table_type(const WASMModuleCommon *module_comm, - const WASMExport *export_table_type, + const WASMExport *export_, uint8 *out_elem_type, uint32 *out_min_size, uint32 *out_max_size); diff --git a/core/iwasm/compilation/aot.h b/core/iwasm/compilation/aot.h index d6d3127e..8902f9d0 100644 --- a/core/iwasm/compilation/aot.h +++ b/core/iwasm/compilation/aot.h @@ -21,6 +21,10 @@ typedef InitializerExpression AOTInitExpr; typedef WASMType AOTFuncType; typedef WASMExport AOTExport; +#if WASM_ENABLE_DEBUG_AOT != 0 +typedef void * dwar_extractor_handle_t; +#endif + typedef enum AOTIntCond { INT_EQZ = 0, INT_EQ, @@ -251,6 +255,9 @@ typedef struct AOTCompData { uint32 aux_stack_size; WASMModule *wasm_module; +#if WASM_ENABLE_DEBUG_AOT != 0 + dwar_extractor_handle_t extractor; +#endif } AOTCompData; typedef struct AOTNativeSymbol { diff --git a/core/iwasm/compilation/aot_compiler.c b/core/iwasm/compilation/aot_compiler.c index 695752ee..9a222ab1 100644 --- a/core/iwasm/compilation/aot_compiler.c +++ b/core/iwasm/compilation/aot_compiler.c @@ -32,6 +32,9 @@ #include "../interpreter/wasm_opcode.h" #include +#if WASM_ENABLE_DEBUG_AOT != 0 +#include "debug/dwarf_extractor.h" +#endif #define CHECK_BUF(buf, buf_end, length) do { \ if (buf + length > buf_end) { \ @@ -153,6 +156,9 @@ aot_compile_func(AOTCompContext *comp_ctx, uint32 func_index) float32 f32_const; float64 f64_const; AOTFuncType *func_type = NULL; +#if WASM_ENABLE_DEBUG_AOT != 0 + LLVMMetadataRef location; +#endif /* Start to translate the opcodes */ LLVMPositionBuilderAtEnd(comp_ctx->builder, @@ -160,6 +166,15 @@ aot_compile_func(AOTCompContext *comp_ctx, uint32 func_index) ->llvm_entry_block); while (frame_ip < frame_ip_end) { opcode = *frame_ip++; + +#if WASM_ENABLE_DEBUG_AOT != 0 + location = dwarf_gen_location( + comp_ctx, func_ctx, + (frame_ip - 1) - comp_ctx->comp_data->wasm_module->buf_code + ); + LLVMSetCurrentDebugLocation2(comp_ctx->builder, location); +#endif + switch (opcode) { case WASM_OP_UNREACHABLE: if (!aot_compile_op_unreachable(comp_ctx, func_ctx, &frame_ip)) @@ -2440,6 +2455,10 @@ aot_compile_wasm(AOTCompContext *comp_ctx) errno = 0; #endif +#if WASM_ENABLE_DEBUG_AOT != 0 + LLVMDIBuilderFinalize(comp_ctx->debug_builder); +#endif + bh_print_time("Begin to verify LLVM module"); ret = LLVMVerifyModule(comp_ctx->module, LLVMPrintMessageAction, &msg); diff --git a/core/iwasm/compilation/aot_emit_aot_file.c b/core/iwasm/compilation/aot_emit_aot_file.c index 333401eb..6c2f1432 100644 --- a/core/iwasm/compilation/aot_emit_aot_file.c +++ b/core/iwasm/compilation/aot_emit_aot_file.c @@ -1757,22 +1757,34 @@ aot_resolve_target_info(AOTCompContext *comp_ctx, AOTObjectData *obj_data) static bool aot_resolve_text(AOTObjectData *obj_data) { - LLVMSectionIteratorRef sec_itr; - char *name; +#if WASM_ENABLE_DEBUG_AOT != 0 + LLVMBinaryType bin_type = LLVMBinaryGetType(obj_data->binary); + if (bin_type == LLVMBinaryTypeELF32L || bin_type == LLVMBinaryTypeELF64L) { + obj_data->text = (char *)LLVMGetBufferStart(obj_data->mem_buf); + obj_data->text_size = (uint32)LLVMGetBufferSize(obj_data->mem_buf); + } + else +#endif + { + LLVMSectionIteratorRef sec_itr; + char *name; - if (!(sec_itr = LLVMObjectFileCopySectionIterator(obj_data->binary))) { - aot_set_last_error("llvm get section iterator failed."); - return false; - } - while (!LLVMObjectFileIsSectionIteratorAtEnd(obj_data->binary, sec_itr)) { - if ((name = (char *)LLVMGetSectionName(sec_itr)) && !strcmp(name, ".text")) { - obj_data->text = (char *)LLVMGetSectionContents(sec_itr); - obj_data->text_size = (uint32)LLVMGetSectionSize(sec_itr); - break; + if (!(sec_itr = LLVMObjectFileCopySectionIterator(obj_data->binary))) { + aot_set_last_error("llvm get section iterator failed."); + return false; } - LLVMMoveToNextSection(sec_itr); + while ( + !LLVMObjectFileIsSectionIteratorAtEnd(obj_data->binary, sec_itr)) { + if ((name = (char *)LLVMGetSectionName(sec_itr)) + && !strcmp(name, ".text")) { + obj_data->text = (char *)LLVMGetSectionContents(sec_itr); + obj_data->text_size = (uint32)LLVMGetSectionSize(sec_itr); + break; + } + LLVMMoveToNextSection(sec_itr); + } + LLVMDisposeSectionIterator(sec_itr); } - LLVMDisposeSectionIterator(sec_itr); return true; } diff --git a/core/iwasm/compilation/aot_emit_control.c b/core/iwasm/compilation/aot_emit_control.c index 51a5648d..17755234 100644 --- a/core/iwasm/compilation/aot_emit_control.c +++ b/core/iwasm/compilation/aot_emit_control.c @@ -8,6 +8,10 @@ #include "../aot/aot_runtime.h" #include "../interpreter/wasm_loader.h" +#if WASM_ENABLE_DEBUG_AOT != 0 +#include "debug/dwarf_extractor.h" +#endif + static char *block_name_prefix[] = { "block", "loop", "if" }; static char *block_name_suffix[] = { "begin", "else", "end" }; @@ -158,14 +162,22 @@ handle_next_reachable_block(AOTCompContext *comp_ctx, uint8 *frame_ip = NULL; uint32 i; AOTFuncType *func_type; + LLVMValueRef ret; +#if WASM_ENABLE_DEBUG_AOT != 0 + LLVMMetadataRef return_location; +#endif aot_checked_addr_list_destroy(func_ctx); - bh_assert(block); +#if WASM_ENABLE_DEBUG_AOT != 0 + return_location = dwarf_gen_location( + comp_ctx, func_ctx, + (*p_frame_ip - 1) - comp_ctx->comp_data->wasm_module->buf_code + ); +#endif if (block->label_type == LABEL_TYPE_IF && block->llvm_else_block - && !block->skip_wasm_code_else && *p_frame_ip <= block->wasm_code_else) { /* Clear value stack and start to translate else branch */ aot_value_stack_destroy(&block->value_stack); @@ -237,16 +249,23 @@ handle_next_reachable_block(AOTCompContext *comp_ctx, if (block->label_type == LABEL_TYPE_FUNCTION) { if (block->result_count) { /* Return the first return value */ - if (!LLVMBuildRet(comp_ctx->builder, block->result_phis[0])) { + if (!(ret = + LLVMBuildRet(comp_ctx->builder, block->result_phis[0]))) { aot_set_last_error("llvm build return failed."); goto fail; } +#if WASM_ENABLE_DEBUG_AOT != 0 + LLVMInstructionSetDebugLoc(ret, return_location); +#endif } else { - if (!LLVMBuildRetVoid(comp_ctx->builder)) { + if (!(ret = LLVMBuildRetVoid(comp_ctx->builder))) { aot_set_last_error("llvm build return void failed."); goto fail; } +#if WASM_ENABLE_DEBUG_AOT != 0 + LLVMInstructionSetDebugLoc(ret, return_location); +#endif } } aot_block_destroy(block); @@ -381,7 +400,7 @@ aot_compile_op_block(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx, memset(block_addr_cache, 0, sizeof(block_addr_cache)); /* Get block info */ - if (!(wasm_loader_find_block_addr((BlockAddr*)block_addr_cache, + if (!(wasm_loader_find_block_addr(NULL, (BlockAddr*)block_addr_cache, *p_frame_ip, frame_ip_end, (uint8)label_type, &else_addr, &end_addr))) { aot_set_last_error("find block end addr failed."); @@ -709,7 +728,7 @@ check_suspend_flags(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx) /* Move builder to terminate block */ SET_BUILDER_POS(terminate_block); - if (!aot_build_zero_function_ret(comp_ctx, aot_func_type)) { + if (!aot_build_zero_function_ret(comp_ctx, func_ctx, aot_func_type)) { goto fail; } @@ -1070,12 +1089,22 @@ aot_compile_op_return(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx, { AOTBlock *block_func = func_ctx->block_stack.block_list_head; LLVMValueRef value; + LLVMValueRef ret; AOTFuncType *func_type; uint32 i, param_index, result_index; +#if WASM_ENABLE_DEBUG_AOT != 0 + LLVMMetadataRef return_location; +#endif bh_assert(block_func); func_type = func_ctx->aot_func->func_type; +#if WASM_ENABLE_DEBUG_AOT != 0 + return_location = dwarf_gen_location( + comp_ctx, func_ctx, + (*p_frame_ip - 1) - comp_ctx->comp_data->wasm_module->buf_code + ); +#endif if (block_func->result_count) { /* Store extra result values to function parameters */ for (i = 0; i < block_func->result_count - 1; i++) { @@ -1091,16 +1120,22 @@ aot_compile_op_return(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx, } /* Return the first result value */ POP(value, block_func->result_types[0]); - if (!LLVMBuildRet(comp_ctx->builder, value)) { + if (!(ret = LLVMBuildRet(comp_ctx->builder, value))) { aot_set_last_error("llvm build return failed."); goto fail; } +#if WASM_ENABLE_DEBUG_AOT != 0 + LLVMInstructionSetDebugLoc(ret, return_location); +#endif } else { - if (!LLVMBuildRetVoid(comp_ctx->builder)) { + if (!(ret = LLVMBuildRetVoid(comp_ctx->builder))) { aot_set_last_error("llvm build return void failed."); goto fail; } +#if WASM_ENABLE_DEBUG_AOT != 0 + LLVMInstructionSetDebugLoc(ret, return_location); +#endif } return handle_next_reachable_block(comp_ctx, func_ctx, p_frame_ip); diff --git a/core/iwasm/compilation/aot_emit_exception.c b/core/iwasm/compilation/aot_emit_exception.c index 5671a12e..af96196a 100644 --- a/core/iwasm/compilation/aot_emit_exception.c +++ b/core/iwasm/compilation/aot_emit_exception.c @@ -109,7 +109,7 @@ aot_emit_exception(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx, /* Create return IR */ AOTFuncType *aot_func_type = func_ctx->aot_func->func_type; - if (!aot_build_zero_function_ret(comp_ctx, aot_func_type)) { + if (!aot_build_zero_function_ret(comp_ctx, func_ctx, aot_func_type)) { return false; } diff --git a/core/iwasm/compilation/aot_emit_function.c b/core/iwasm/compilation/aot_emit_function.c index 92647918..f8c9d1bb 100644 --- a/core/iwasm/compilation/aot_emit_function.c +++ b/core/iwasm/compilation/aot_emit_function.c @@ -35,7 +35,7 @@ create_func_return_block(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx) /* Create return IR */ LLVMPositionBuilderAtEnd(comp_ctx->builder, func_ctx->func_return_block); - if (!aot_build_zero_function_ret(comp_ctx, aot_func_type)) { + if (!aot_build_zero_function_ret(comp_ctx, func_ctx, aot_func_type)) { return false; } } @@ -313,7 +313,7 @@ call_aot_alloc_frame_func(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx, /* If frame alloc failed, return this function so the runtime can catch the exception */ LLVMPositionBuilderAtEnd(comp_ctx->builder, frame_alloc_fail); - if (!aot_build_zero_function_ret(comp_ctx, aot_func_type)) { + if (!aot_build_zero_function_ret(comp_ctx, func_ctx, aot_func_type)) { return false; } diff --git a/core/iwasm/compilation/aot_emit_memory.c b/core/iwasm/compilation/aot_emit_memory.c index 20618353..820a7477 100644 --- a/core/iwasm/compilation/aot_emit_memory.c +++ b/core/iwasm/compilation/aot_emit_memory.c @@ -897,7 +897,7 @@ aot_compile_op_memory_init(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx, /* If memory.init failed, return this function so the runtime can catch the exception */ LLVMPositionBuilderAtEnd(comp_ctx->builder, mem_init_fail); - if (!aot_build_zero_function_ret(comp_ctx, aot_func_type)) { + if (!aot_build_zero_function_ret(comp_ctx, func_ctx, aot_func_type)) { goto fail; } @@ -1231,7 +1231,7 @@ aot_compile_op_atomic_wait(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx, /* If atomic wait failed, return this function so the runtime can catch the exception */ LLVMPositionBuilderAtEnd(comp_ctx->builder, wait_fail); - if (!aot_build_zero_function_ret(comp_ctx, aot_func_type)) { + if (!aot_build_zero_function_ret(comp_ctx, func_ctx, aot_func_type)) { goto fail; } diff --git a/core/iwasm/compilation/aot_llvm.c b/core/iwasm/compilation/aot_llvm.c index 780c45dd..843e76ba 100644 --- a/core/iwasm/compilation/aot_llvm.c +++ b/core/iwasm/compilation/aot_llvm.c @@ -9,6 +9,10 @@ #include "../aot/aot_runtime.h" #include "../aot/aot_intrinsic.h" +#if WASM_ENABLE_DEBUG_AOT != 0 +#include "debug/dwarf_extractor.h" +#endif + LLVMTypeRef wasm_type_to_llvm_type(AOTLLVMTypes *llvm_types, uint8 wasm_type) { @@ -634,6 +638,10 @@ aot_create_func_context(AOTCompData *comp_data, AOTCompContext *comp_ctx, func, aot_func_type))) goto fail; +#if WASM_ENABLE_DEBUG_AOT != 0 + func_ctx->debug_func = dwarf_gen_func_info(comp_ctx, func_ctx); +#endif + aot_block_stack_push(&func_ctx->block_stack, aot_block); /* Add local variables */ @@ -1462,6 +1470,29 @@ aot_create_comp_context(AOTCompData *comp_data, goto fail; } +#if WASM_ENABLE_DEBUG_AOT != 0 + if (!(comp_ctx->debug_builder = LLVMCreateDIBuilder(comp_ctx->module))) { + aot_set_last_error("create LLVM Debug Infor builder failed."); + goto fail; + } + + LLVMAddModuleFlag( + comp_ctx->module, LLVMModuleFlagBehaviorWarning, "Debug Info Version", + strlen("Debug Info Version"), + LLVMValueAsMetadata(LLVMConstInt(LLVMInt32Type(), 3, false))); + + comp_ctx->debug_file = dwarf_gen_file_info(comp_ctx); + if (!comp_ctx->debug_file) { + aot_set_last_error("dwarf generate file info failed"); + goto fail; + } + comp_ctx->debug_comp_unit = dwarf_gen_comp_unit_info(comp_ctx); + if (!comp_ctx->debug_comp_unit) { + aot_set_last_error("dwarf generate compile unit info failed"); + goto fail; + } +#endif + if (option->enable_bulk_memory) comp_ctx->enable_bulk_memory = true; @@ -2213,6 +2244,7 @@ aot_checked_addr_list_destroy(AOTFuncContext *func_ctx) bool aot_build_zero_function_ret(AOTCompContext *comp_ctx, + AOTFuncContext *func_ctx, AOTFuncType *func_type) { LLVMValueRef ret = NULL; @@ -2251,6 +2283,10 @@ aot_build_zero_function_ret(AOTCompContext *comp_ctx, aot_set_last_error("llvm build ret failed."); return false; } +#if WASM_ENABLE_DEBUG_AOT != 0 + LLVMMetadataRef return_location = dwarf_gen_func_ret_location(comp_ctx, func_ctx); + LLVMInstructionSetDebugLoc(ret, return_location); +#endif return true; } diff --git a/core/iwasm/compilation/aot_llvm.h b/core/iwasm/compilation/aot_llvm.h index 85cf40c7..1ef03c73 100644 --- a/core/iwasm/compilation/aot_llvm.h +++ b/core/iwasm/compilation/aot_llvm.h @@ -25,6 +25,9 @@ #include "llvm-c/Initialization.h" #include "llvm-c/Support.h" #endif +#if WASM_ENABLE_DEBUG_AOT != 0 +#include "llvm-c/DebugInfo.h" +#endif #ifdef __cplusplus extern "C" { @@ -148,6 +151,9 @@ typedef struct AOTFuncContext { LLVMBasicBlockRef func_return_block; LLVMValueRef exception_id_phi; LLVMValueRef func_type_indexes; +#if WASM_ENABLE_DEBUG_AOT != 0 + LLVMMetadataRef debug_func; +#endif LLVMValueRef locals[1]; } AOTFuncContext; @@ -245,6 +251,11 @@ typedef struct AOTCompContext { LLVMContextRef context; LLVMModuleRef module; LLVMBuilderRef builder; +#if WASM_ENABLE_DEBUG_AOT + LLVMDIBuilderRef debug_builder; + LLVMMetadataRef debug_file; + LLVMMetadataRef debug_comp_unit; +#endif LLVMTargetMachineRef target_machine; char *target_cpu; char target_arch[16]; @@ -407,6 +418,7 @@ aot_checked_addr_list_destroy(AOTFuncContext *func_ctx); bool aot_build_zero_function_ret(AOTCompContext *comp_ctx, + AOTFuncContext *func_ctx, AOTFuncType *func_type); LLVMValueRef diff --git a/core/iwasm/compilation/debug/dwarf_extractor.cpp b/core/iwasm/compilation/debug/dwarf_extractor.cpp new file mode 100644 index 00000000..a98846b0 --- /dev/null +++ b/core/iwasm/compilation/debug/dwarf_extractor.cpp @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "lldb/API/SBBlock.h" +#include "lldb/API/SBCompileUnit.h" +#include "lldb/API/SBCommandReturnObject.h" +#include "lldb/API/SBCommandInterpreter.h" +#include "lldb/API/SBBreakpointLocation.h" +#include "lldb/API/SBDebugger.h" +#include "lldb/API//SBFunction.h" +#include "lldb/API//SBModule.h" +#include "lldb/API//SBProcess.h" +#include "lldb/API//SBStream.h" +#include "lldb/API//SBSymbol.h" +#include "lldb/API//SBTarget.h" +#include "lldb/API//SBThread.h" +#include "lldb/API/SBDeclaration.h" + +#include "dwarf_extractor.h" +#include "../aot_llvm.h" + +#include "bh_log.h" +#include "../../aot/aot_runtime.h" + +#include "llvm/BinaryFormat/Dwarf.h" + +using namespace lldb; + +typedef struct dwar_extractor +{ + SBDebugger debugger; + SBTarget target; + SBModule module; + +} dwar_extractor; + +#define TO_HANDLE(extractor) (dwar_extractor_handle_t)(extractor) + +#define TO_EXTACTOR(handle) (dwar_extractor *)(handle) + +static bool is_debugger_initialized; + +dwar_extractor_handle_t +create_dwarf_extractor(AOTCompData *comp_data, char * file_name) +{ + char *arch = NULL; + char *platform = NULL; + dwar_extractor * extractor = NULL; + + //__attribute__((constructor)) may be better? + if (!is_debugger_initialized) { + SBError error = SBDebugger::InitializeWithErrorHandling(); + if(error.Fail()) { + LOG_ERROR("Init Dwarf Debugger failed"); + return TO_HANDLE(NULL); + } + is_debugger_initialized = true; + } + + SBError error; + SBFileSpec exe_file_spec(file_name, true); + + if (!(extractor = new dwar_extractor()) ) { + LOG_ERROR("Create Dwarf Extractor error: failed to allocate memory"); + goto fail3; + } + + extractor->debugger = SBDebugger::Create(); + if (!extractor->debugger.IsValid()) { + LOG_ERROR("Create Dwarf Debugger failed"); + goto fail2; + } + + extractor->target = extractor->debugger.CreateTarget( + file_name, arch, platform, false, error); + + if (!error.Success()) { + LOG_ERROR("Create Dwarf target failed:%s", error.GetCString()); + goto fail1; + } + + if (!extractor->target.IsValid()) { + LOG_ERROR("Create Dwarf target not valid"); + goto fail1; + } + + extractor->module = extractor->target.FindModule(exe_file_spec); + comp_data->extractor = TO_HANDLE(extractor); + + return TO_HANDLE(extractor); + +fail1: + SBDebugger::Destroy(extractor->debugger); + +fail2: + wasm_runtime_free(extractor); + +fail3: + return TO_HANDLE(NULL); +} + +void +destroy_dwarf_extractor(dwar_extractor_handle_t handle) +{ + dwar_extractor * extractor = TO_EXTACTOR(handle); + if (!extractor) + return; + extractor->debugger.DeleteTarget(extractor->target); + SBDebugger::Destroy(extractor->debugger); + delete extractor; + SBDebugger::Terminate(); + is_debugger_initialized = false; +} + +LLVMMetadataRef +dwarf_gen_file_info(AOTCompContext *comp_ctx) +{ + dwar_extractor *extractor; + int units_number; + LLVMMetadataRef file_info = NULL; + const char *file_name; + const char *dir_name; + + if (!(extractor = TO_EXTACTOR(comp_ctx->comp_data->extractor))) + return NULL; + + units_number = extractor->module.GetNumCompileUnits(); + + if (units_number > 0) { + SBCompileUnit compile_unit = + extractor->module.GetCompileUnitAtIndex(0); + auto filespec = compile_unit.GetFileSpec(); + file_name = filespec.GetFilename(); + dir_name = filespec.GetDirectory(); + if (file_name || dir_name) { + file_info = LLVMDIBuilderCreateFile(comp_ctx->debug_builder, + file_name, strlen(file_name), + dir_name, strlen(dir_name)); + } + } + return file_info; +} + +#if 0 +void +dwarf_gen_mock_vm_info(AOTCompContext *comp_ctx) +{ + LLVMMetadataRef file_info = NULL; + LLVMMetadataRef comp_unit = NULL; + file_info = LLVMDIBuilderCreateFile(comp_ctx->debug_builder, + "ant_runtime_mock.c", 18, ".", 1); + + comp_unit = LLVMDIBuilderCreateCompileUnit( + comp_ctx->debug_builder, LLVMDWARFSourceLanguageC, file_info, + "ant compiler", 12, 0, NULL, 0, 1, NULL, 0, LLVMDWARFEmissionFull, 0, 0, + 0, "/", 1, "", 0); + + LLVMTypeRef ParamTys[] = { + LLVMVoidType(), + }; + + LLVMTypeRef FuncTy = LLVMFunctionType(LLVMVoidType(), ParamTys, 0, 0); + + LLVMValueRef Function = + LLVMAddFunction(comp_ctx->module, "ant_runtime_mock", FuncTy); + + LLVMMetadataRef ParamTypes[0]; + LLVMMetadataRef FunctionTy = LLVMDIBuilderCreateSubroutineType( + comp_ctx->debug_builder, file_info, ParamTypes, 0, LLVMDIFlagZero); + + /* 0x0015 is subroutine_type */ + LLVMMetadataRef ReplaceableFunctionMetadata = + LLVMDIBuilderCreateReplaceableCompositeType( + comp_ctx->debug_builder, 0x15, "ant_runtime_mock", 16, file_info, + file_info, 2, 0, 0, 0, LLVMDIFlagFwdDecl, "", 0); + + LLVMMetadataRef FunctionMetadata = LLVMDIBuilderCreateFunction( + comp_ctx->debug_builder, file_info, "ant_runtime_mock", 16, + "ant_runtime_mock", 16, file_info, 2, FunctionTy, true, true, 2, LLVMDIFlagZero, + false); + + LLVMMetadataReplaceAllUsesWith(ReplaceableFunctionMetadata, + FunctionMetadata); + + LLVMSetSubprogram(Function, FunctionMetadata); + + comp_ctx->vm_debug_comp_unit = comp_unit; + comp_ctx->vm_debug_file = file_info; + comp_ctx->vm_debug_func = FunctionMetadata; +} +#endif + +LLVMMetadataRef +dwarf_gen_comp_unit_info(AOTCompContext *comp_ctx) +{ + dwar_extractor *extractor; + int units_number; + LLVMMetadataRef comp_unit = NULL; + + if (!(extractor = TO_EXTACTOR(comp_ctx->comp_data->extractor))) + return NULL; + + units_number = extractor->module.GetNumCompileUnits(); + + if (units_number > 0) { + SBCompileUnit compile_unit = + extractor->module.GetCompileUnitAtIndex(0); + auto lang_type = compile_unit.GetLanguage(); + + comp_unit = LLVMDIBuilderCreateCompileUnit( + comp_ctx->debug_builder, LLDB_TO_LLVM_LANG_TYPE(lang_type), + comp_ctx->debug_file, "ant compiler", 12, 0, NULL, 0, 1, NULL, 0, + LLVMDWARFEmissionFull, 0, 0, 0, "/", 1, "", 0); + } + return comp_unit; +} + + +bool +dwarf_get_func_info(dwar_extractor_handle_t handle, uint64_t offset) +{ + dwar_extractor *extractor = TO_EXTACTOR(handle); + auto sbaddr = extractor->target.ResolveFileAddress(offset); + SBSymbolContext sc( + sbaddr.GetSymbolContext(eSymbolContextFunction)); + if (sc.IsValid()) { + SBFunction function(sc.GetFunction()); + if (function.IsValid()) { + + } + } +} + +static LLVMDWARFTypeEncoding +lldb_get_basic_type_encoding(BasicType basic_type) +{ + LLVMDWARFTypeEncoding encoding = 0; + switch (basic_type) + { + case eBasicTypeUnsignedChar: + encoding = llvm::dwarf::DW_ATE_unsigned_char; + break; + case eBasicTypeSignedChar: + encoding = llvm::dwarf::DW_ATE_signed_char; + break; + case eBasicTypeUnsignedInt: + case eBasicTypeUnsignedLong: + case eBasicTypeUnsignedLongLong: + case eBasicTypeUnsignedWChar: + case eBasicTypeUnsignedInt128: + case eBasicTypeUnsignedShort: + encoding = llvm::dwarf::DW_ATE_unsigned; + break; + case eBasicTypeInt: + case eBasicTypeLong: + case eBasicTypeLongLong: + case eBasicTypeWChar: + case eBasicTypeInt128: + case eBasicTypeShort: + encoding = llvm::dwarf::DW_ATE_signed; + break; + case eBasicTypeBool: + encoding = llvm::dwarf::DW_ATE_boolean; + break; + case eBasicTypeHalf: + case eBasicTypeFloat: + case eBasicTypeDouble: + case eBasicTypeLongDouble: + encoding = llvm::dwarf::DW_ATE_float; + break; + default: + break; + } + return encoding; +} + +static LLVMMetadataRef +lldb_type_to_type_dbi(AOTCompContext *comp_ctx, SBType &type) +{ + LLVMMetadataRef type_info = NULL; + BasicType basic_type = type.GetBasicType(); + uint64_t bit_size = type.GetByteSize() * 8; + LLVMDIBuilderRef DIB = comp_ctx->debug_builder; + LLVMDWARFTypeEncoding encoding; + + if (basic_type != eBasicTypeInvalid) { + encoding = lldb_get_basic_type_encoding(basic_type); + type_info = LLVMDIBuilderCreateBasicType( + DIB, type.GetName(), strlen(type.GetName()), bit_size, encoding, + LLVMDIFlagZero); + } + else if (type.IsPointerType()) { + SBType pointee_type = type.GetPointeeType(); + type_info = LLVMDIBuilderCreatePointerType( + DIB, lldb_type_to_type_dbi(comp_ctx, pointee_type), bit_size, 0, 0, + "", 0); + } + + return type_info; +} + +static LLVMMetadataRef +lldb_function_to_function_dbi(AOTCompContext *comp_ctx, SBSymbolContext &sc, AOTFuncContext *func_ctx) +{ + SBFunction function(sc.GetFunction()); + const char *function_name = function.GetName(); + const char *link_name = function.GetName(); + SBTypeList function_args = function.GetType().GetFunctionArgumentTypes(); + SBType return_type = function.GetType().GetFunctionReturnType(); + const size_t num_function_args = function_args.GetSize(); + dwar_extractor *extractor; + + if (!(extractor = TO_EXTACTOR(comp_ctx->comp_data->extractor))) + return NULL; + + + LLVMDIBuilderRef DIB = comp_ctx->debug_builder; + LLVMMetadataRef File = comp_ctx->debug_file; + + LLVMMetadataRef ParamTypes[num_function_args + 1]; + + + ParamTypes[0] = lldb_type_to_type_dbi(comp_ctx, return_type); + + for (uint32_t function_arg_idx = 0; function_arg_idx < num_function_args; + ++function_arg_idx) { + SBType function_arg_type = + function_args.GetTypeAtIndex(function_arg_idx); + + if (function_arg_type.IsValid()) { + ParamTypes[function_arg_idx + 1] = lldb_type_to_type_dbi(comp_ctx, function_arg_type); + } + } + + LLVMMetadataRef FunctionTy = + LLVMDIBuilderCreateSubroutineType(DIB, File, ParamTypes, num_function_args + 1, LLVMDIFlagZero); + + auto line_entry = sc.GetLineEntry(); + LLVMMetadataRef ReplaceableFunctionMetadata = + LLVMDIBuilderCreateReplaceableCompositeType( + DIB, 0x15, function_name, strlen(function_name), File, File, + line_entry.GetLine(), 0, 0, 0, LLVMDIFlagFwdDecl, "", 0); + + LLVMMetadataRef FunctionMetadata = + LLVMDIBuilderCreateFunction(DIB, File, function_name, strlen(function_name), link_name, strlen(link_name), + File, line_entry.GetLine(), FunctionTy, true, true, line_entry.GetLine(), LLVMDIFlagZero, false); + + LLVMMetadataReplaceAllUsesWith(ReplaceableFunctionMetadata, FunctionMetadata); + + LLVMSetSubprogram(func_ctx->func, FunctionMetadata); + + LLVMMetadataRef ParamExpression = LLVMDIBuilderCreateExpression(DIB, NULL, 0); + auto variable_list = function.GetBlock().GetVariables(extractor->target, true, false,false); + if (num_function_args != variable_list.GetSize()) + { + LOG_ERROR("function args number dismatch!:value number=%d, function args=%d", variable_list.GetSize(), num_function_args); + } + + LLVMMetadataRef ParamLocation = LLVMDIBuilderCreateDebugLocation( + comp_ctx->context, line_entry.GetLine(), 0, FunctionMetadata, NULL); + + //TODO:change to void * or WasmExenv * ? + LLVMMetadataRef voidtype = LLVMDIBuilderCreateBasicType(DIB, "void", 4, 0, 0, LLVMDIFlagZero); + LLVMMetadataRef voidpionter = LLVMDIBuilderCreatePointerType(DIB, voidtype, 64, 0, 0, "void *", 6); + + LLVMMetadataRef ParamVar = LLVMDIBuilderCreateParameterVariable( + DIB, FunctionMetadata, "exenv", + 5, 1, + File, //starts form 1, and 1 is exenv, + line_entry.GetLine(), voidpionter, true, + LLVMDIFlagZero); + LLVMValueRef Param = + LLVMGetParam(func_ctx->func, 0); + LLVMBasicBlockRef block_curr = + LLVMGetEntryBasicBlock(func_ctx->func); + LLVMDIBuilderInsertDbgValueAtEnd(DIB, Param, ParamVar, + ParamExpression, ParamLocation, + block_curr); + + for (uint32_t function_arg_idx = 0; function_arg_idx < variable_list.GetSize(); + ++function_arg_idx) { + SBValue variable(variable_list.GetValueAtIndex(function_arg_idx)); + if (variable.IsValid()) { + SBDeclaration dec(variable.GetDeclaration()); + auto valtype = variable.GetType(); + LLVMMetadataRef ParamLocation = LLVMDIBuilderCreateDebugLocation( + comp_ctx->context, dec.GetLine(), dec.GetColumn(), + FunctionMetadata, NULL); + LLVMMetadataRef ParamVar = LLVMDIBuilderCreateParameterVariable( + DIB, FunctionMetadata, variable.GetName(), + strlen(variable.GetName()), function_arg_idx + 1 + 1, + File, //starts form 1, and 1 is exenv, + dec.GetLine(), ParamTypes[function_arg_idx + 1], true, + LLVMDIFlagZero); + LLVMValueRef Param = + LLVMGetParam(func_ctx->func, function_arg_idx + 1); + LLVMDIBuilderInsertDbgValueAtEnd(DIB, Param, ParamVar, + ParamExpression, ParamLocation, + block_curr); + } + } + + return FunctionMetadata; +} + +LLVMMetadataRef +dwarf_gen_func_info(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx) +{ + LLVMMetadataRef func_info = NULL; + dwar_extractor *extractor; + uint64_t vm_offset; + AOTFunc *func = func_ctx->aot_func; + + + if (!(extractor = TO_EXTACTOR(comp_ctx->comp_data->extractor))) + return NULL; + + // A code address in DWARF for WebAssembly is the offset of an + // instruction relative within the Code section of the WebAssembly file. + // For this reason Section::GetFileAddress() must return zero for the + // Code section. (refert to ObjectFileWasm.cpp) + vm_offset = func->code - comp_ctx->comp_data->wasm_module->buf_code; + + auto sbaddr = extractor->target.ResolveFileAddress(vm_offset); + SBSymbolContext sc( + sbaddr.GetSymbolContext(eSymbolContextFunction | eSymbolContextLineEntry)); + if (sc.IsValid()) { + SBFunction function(sc.GetFunction()); + if (function.IsValid()) { + func_info = lldb_function_to_function_dbi(comp_ctx, sc, func_ctx); + } + } + return func_info; +} + +void +dwarf_get_func_name(AOTCompContext *comp_ctx, + AOTFuncContext *func_ctx, + char *name, + int len) +{ + LLVMMetadataRef func_info = NULL; + dwar_extractor *extractor; + uint64_t vm_offset; + AOTFunc *func = func_ctx->aot_func; + + name[0] = '\0'; + + if (!(extractor = TO_EXTACTOR(comp_ctx->comp_data->extractor))) + return ; + + // A code address in DWARF for WebAssembly is the offset of an + // instruction relative within the Code section of the WebAssembly file. + // For this reason Section::GetFileAddress() must return zero for the + // Code section. (refert to ObjectFileWasm.cpp) + vm_offset = func->code - comp_ctx->comp_data->wasm_module->buf_code; + + auto sbaddr = extractor->target.ResolveFileAddress(vm_offset); + SBSymbolContext sc(sbaddr.GetSymbolContext(eSymbolContextFunction + | eSymbolContextLineEntry)); + if (sc.IsValid()) { + SBFunction function(sc.GetFunction()); + if (function.IsValid()) { + bh_strcpy_s(name, len, function.GetName()); + } + } +} + +LLVMMetadataRef +dwarf_gen_location(AOTCompContext *comp_ctx, + AOTFuncContext *func_ctx, + uint64_t vm_offset) +{ + LLVMMetadataRef location_info = NULL; + dwar_extractor *extractor; + AOTFunc *func = func_ctx->aot_func; + + if (!(extractor = TO_EXTACTOR(comp_ctx->comp_data->extractor))) + return NULL; + + auto sbaddr = extractor->target.ResolveFileAddress(vm_offset); + SBSymbolContext sc(sbaddr.GetSymbolContext(eSymbolContextFunction + | eSymbolContextLineEntry)); + if (sc.IsValid()) { + //TODO:need to check if the vm_offset is belong to + SBFunction function(sc.GetFunction()); + if (function.IsValid()) { + uint64_t start = func_ctx->aot_func->code + - comp_ctx->comp_data->wasm_module->buf_code; + uint64_t end = func_ctx->aot_func->code + - comp_ctx->comp_data->wasm_module->buf_code + + func_ctx->aot_func->code_size; + if (function.GetStartAddress().GetOffset() <= start + && end <= function.GetEndAddress().GetOffset()) { + auto line_entry = sc.GetLineEntry(); + location_info = + LLVMDIBuilderCreateDebugLocation( + comp_ctx->context, line_entry.GetLine(), + line_entry.GetColumn(), func_ctx->debug_func, NULL); + //LOG_VERBOSE("Gen the location l:%d, c:%d at %lx", line_entry.GetLine(), line_entry.GetColumn(), vm_offset); + } else + LOG_WARNING("the offset and function is not matched"); + } + } + return location_info; +} + +LLVMMetadataRef +dwarf_gen_func_ret_location(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx) +{ + LLVMMetadataRef func_info = NULL; + dwar_extractor *extractor; + uint64_t vm_offset; + AOTFunc *func = func_ctx->aot_func; + LLVMMetadataRef location_info = NULL; + + if (!(extractor = TO_EXTACTOR(comp_ctx->comp_data->extractor))) + return NULL; + + // A code address in DWARF for WebAssembly is the offset of an + // instruction relative within the Code section of the WebAssembly file. + // For this reason Section::GetFileAddress() must return zero for the + // Code section. (refert to ObjectFileWasm.cpp) + vm_offset = (func->code + func->code_size -1) - comp_ctx->comp_data->wasm_module->buf_code; + location_info = dwarf_gen_location(comp_ctx, func_ctx, vm_offset); + + return location_info; +} \ No newline at end of file diff --git a/core/iwasm/compilation/debug/dwarf_extractor.h b/core/iwasm/compilation/debug/dwarf_extractor.h new file mode 100644 index 00000000..288230c8 --- /dev/null +++ b/core/iwasm/compilation/debug/dwarf_extractor.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifndef _DWARF_EXTRACTOR_H_ +#define _DWARF_EXTRACTOR_H_ + +#include "llvm-c/DebugInfo.h" + +#ifdef __cplusplus +extern "C" { +#endif +typedef unsigned int LLDBLangType; +#define LLDB_TO_LLVM_LANG_TYPE(lldb_lang_type) \ + (LLVMDWARFSourceLanguage)(((lldb_lang_type) > 0 ? (lldb_lang_type)-1 : 1)) + +struct AOTCompData; +typedef struct AOTCompData *aot_comp_data_t; +typedef void *dwar_extractor_handle_t; + +struct AOTCompContext; +typedef struct AOTCompContext AOTCompContext; + +struct AOTFuncContext; + +typedef struct AOTFuncContext AOTFuncContext; +dwar_extractor_handle_t +create_dwarf_extractor(aot_comp_data_t comp_data, char *file_name); + +LLVMMetadataRef +dwarf_gen_file_info(AOTCompContext *comp_ctx); + +LLVMMetadataRef +dwarf_gen_comp_unit_info(AOTCompContext *comp_ctx); + +LLVMMetadataRef +dwarf_gen_func_info(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx); + +LLVMMetadataRef +dwarf_gen_location(AOTCompContext *comp_ctx, + AOTFuncContext *func_ctx, + uint64_t vm_offset); + +LLVMMetadataRef +dwarf_gen_func_ret_location(AOTCompContext *comp_ctx, + AOTFuncContext *func_ctx); + +void +dwarf_get_func_name(AOTCompContext *comp_ctx, + AOTFuncContext *func_ctx, + char *name, + int len); +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/core/iwasm/compilation/iwasm_compl.cmake b/core/iwasm/compilation/iwasm_compl.cmake index 09b3b9ec..59455de9 100644 --- a/core/iwasm/compilation/iwasm_compl.cmake +++ b/core/iwasm/compilation/iwasm_compl.cmake @@ -2,9 +2,17 @@ set (IWASM_COMPL_DIR ${CMAKE_CURRENT_LIST_DIR}) include_directories(${IWASM_COMPL_DIR}) -file (GLOB_RECURSE source_all - ${IWASM_COMPL_DIR}/*.c - ${IWASM_COMPL_DIR}/*.cpp) +if (WAMR_BUILD_DEBUG_AOT EQUAL 1) + file (GLOB_RECURSE source_all + ${IWASM_COMPL_DIR}/*.c + ${IWASM_COMPL_DIR}/*.cpp) +else() + file (GLOB source_all + ${IWASM_COMPL_DIR}/simd/*.c + ${IWASM_COMPL_DIR}/simd/*.cpp + ${IWASM_COMPL_DIR}/*.c + ${IWASM_COMPL_DIR}/*.cpp) +endif() set (IWASM_COMPL_SOURCE ${source_all}) diff --git a/core/iwasm/include/aot_export.h b/core/iwasm/include/aot_export.h index cc7355dc..476e2cb3 100644 --- a/core/iwasm/include/aot_export.h +++ b/core/iwasm/include/aot_export.h @@ -26,6 +26,12 @@ aot_create_comp_data(void *wasm_module); void aot_destroy_comp_data(aot_comp_data_t comp_data); +#if WASM_ENABLE_DEBUG_AOT != 0 +typedef void * dwar_extractor_handle_t; +dwar_extractor_handle_t +create_dwarf_extractor(aot_comp_data_t comp_data, char * file_name); +#endif + enum { AOT_FORMAT_FILE, AOT_OBJECT_FILE, diff --git a/core/iwasm/include/wasm_export.h b/core/iwasm/include/wasm_export.h index 85eb2003..e23a8ebd 100644 --- a/core/iwasm/include/wasm_export.h +++ b/core/iwasm/include/wasm_export.h @@ -133,6 +133,11 @@ typedef struct RuntimeInitArgs { /* maximum thread number, only used when WASM_ENABLE_THREAD_MGR is defined */ uint32_t max_thread_num; +#if WASM_ENABLE_DEBUG_INTERP != 0 + char ip_addr[128]; + int platform_port; + int instance_port; +#endif } RuntimeInitArgs; #ifndef WASM_VALKIND_T_DEFINED diff --git a/core/iwasm/interpreter/wasm.h b/core/iwasm/interpreter/wasm.h index fedf3dd4..5bdf65c6 100644 --- a/core/iwasm/interpreter/wasm.h +++ b/core/iwasm/interpreter/wasm.h @@ -316,6 +316,13 @@ typedef struct StringNode { char *str; } StringNode, *StringList; +#if WASM_ENABLE_DEBUG_INTERP != 0 +typedef struct WASMFastOPCodeNode { + struct WASMFastOPCodeNode *next; + uint64 offset; + uint8 orig_op; +} WASMFastOPCodeNode; +#endif struct WASMModule { /* Module type, for module loaded from WASM bytecode binary, this field is Wasm_Module_Bytecode; @@ -404,6 +411,13 @@ struct WASMModule { bh_list import_module_list_head; bh_list *import_module_list; #endif +#if WASM_ENABLE_DEBUG_INTERP != 0 || WASM_ENABLE_DEBUG_AOT != 0 + bh_list fast_opcode_list; + uint8 *buf_code; + uint8 *load_addr; + uint64 load_size; + uint64 buf_code_size; +#endif }; typedef struct BlockType { diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c index c14355f7..1f9ddf3f 100644 --- a/core/iwasm/interpreter/wasm_interp_classic.c +++ b/core/iwasm/interpreter/wasm_interp_classic.c @@ -12,6 +12,9 @@ #if WASM_ENABLE_SHARED_MEMORY != 0 #include "../common/wasm_shared_memory.h" #endif +#if WASM_ENABLE_THREAD_MGR != 0 && WASM_ENABLE_DEBUG_INTERP != 0 +#include "../libraries/thread-mgr/thread_manager.h" +#endif typedef int32 CellType_I32; typedef int64 CellType_I64; @@ -848,27 +851,70 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst, #endif #if WASM_ENABLE_THREAD_MGR != 0 +#if WASM_ENABLE_DEBUG_INTERP != 0 +#define CHECK_SUSPEND_FLAGS() do { \ + if (IS_WAMR_TERM_SIG(exec_env->current_status->signal_flag)) { \ + return; \ + } \ + if (IS_WAMR_STOP_SIG(exec_env->current_status->signal_flag)) { \ + SYNC_ALL_TO_FRAME(); \ + wasm_cluster_thread_stopped(exec_env); \ + wasm_cluster_thread_waiting_run(exec_env); \ + } \ + } while (0) +#else #define CHECK_SUSPEND_FLAGS() do { \ if (exec_env->suspend_flags.flags != 0) { \ if (exec_env->suspend_flags.flags & 0x01) { \ /* terminate current thread */ \ return; \ } \ - /* TODO: support suspend and breakpoint */ \ + while (exec_env->suspend_flags.flags & 0x02){ \ + /* suspend current thread */ \ + os_cond_wait(&exec_env->wait_cond, \ + &exec_env->wait_lock); \ + } \ } \ } while (0) #endif +#endif #if WASM_ENABLE_LABELS_AS_VALUES != 0 #define HANDLE_OP(opcode) HANDLE_##opcode #define FETCH_OPCODE_AND_DISPATCH() goto *handle_table[*frame_ip++] + +#if WASM_ENABLE_THREAD_MGR != 0 && WASM_ENABLE_DEBUG_INTERP != 0 +#define HANDLE_OP_END() \ + do { \ + while (exec_env->current_status->signal_flag == WAMR_SIG_SINGSTEP \ + && exec_env->current_status->step_count++ == 1) { \ + exec_env->current_status->step_count = 0; \ + SYNC_ALL_TO_FRAME(); \ + wasm_cluster_thread_stopped(exec_env); \ + wasm_cluster_thread_waiting_run(exec_env); \ + } \ + goto *handle_table[*frame_ip++]; \ + } while (0) +#else #define HANDLE_OP_END() FETCH_OPCODE_AND_DISPATCH() +#endif #else /* else of WASM_ENABLE_LABELS_AS_VALUES */ - #define HANDLE_OP(opcode) case opcode +#if WASM_ENABLE_THREAD_MGR != 0 && WASM_ENABLE_DEBUG_INTERP != 0 +#define HANDLE_OP_END() \ + if (exec_env->current_status->signal_flag == WAMR_SIG_SINGSTEP \ + && exec_env->current_status->step_count++ == 2) { \ + exec_env->current_status->step_count = 0; \ + SYNC_ALL_TO_FRAME(); \ + wasm_cluster_thread_stopped(exec_env); \ + wasm_cluster_thread_waiting_run(exec_env); \ + } \ + continue +#else #define HANDLE_OP_END() continue +#endif #endif /* end of WASM_ENABLE_LABELS_AS_VALUES */ @@ -941,6 +987,16 @@ handle_op_block: else if (cache_items[1].start_addr == frame_ip) { end_addr = cache_items[1].end_addr; } +#if WASM_ENABLE_DEBUG_INTERP != 0 + else if (!wasm_loader_find_block_addr(exec_env, + (BlockAddr*)exec_env->block_addr_cache, + frame_ip, (uint8*)-1, + LABEL_TYPE_BLOCK, + &else_addr, &end_addr)) { + wasm_set_exception(module, "find block address failed"); + goto got_exception; + } +#endif else { end_addr = NULL; } @@ -978,7 +1034,8 @@ handle_op_if: else_addr = cache_items[1].else_addr; end_addr = cache_items[1].end_addr; } - else if (!wasm_loader_find_block_addr((BlockAddr*)exec_env->block_addr_cache, + else if (!wasm_loader_find_block_addr(exec_env, + (BlockAddr*)exec_env->block_addr_cache, frame_ip, (uint8*)-1, LABEL_TYPE_IF, &else_addr, &end_addr)) { @@ -1030,7 +1087,8 @@ handle_op_if: label_pop_csp_n: POP_CSP_N(depth); if (!frame_ip) { /* must be label pushed by WASM_OP_BLOCK */ - if (!wasm_loader_find_block_addr((BlockAddr*)exec_env->block_addr_cache, + if (!wasm_loader_find_block_addr(exec_env, + (BlockAddr*)exec_env->block_addr_cache, (frame_csp - 1)->begin_addr, (uint8*)-1, LABEL_TYPE_BLOCK, &else_addr, &end_addr)) { @@ -3178,7 +3236,17 @@ label_pop_csp_n: frame_sp = frame->sp; frame_csp = frame->csp; goto call_func_from_entry; - +#if WASM_ENABLE_DEBUG_INTERP != 0 + HANDLE_OP (DEBUG_OP_BREAK): + { + wasm_cluster_thread_send_signal(exec_env, WAMR_SIG_TRAP); + exec_env->suspend_flags.flags |= 2; + frame_ip--; + SYNC_ALL_TO_FRAME(); + CHECK_SUSPEND_FLAGS(); + HANDLE_OP_END (); + } +#endif #if WASM_ENABLE_LABELS_AS_VALUES == 0 default: wasm_set_exception(module, "unsupported opcode"); @@ -3320,6 +3388,9 @@ label_pop_csp_n: PUSH_CSP(LABEL_TYPE_FUNCTION, cell_num, frame_ip_end - 1); wasm_exec_env_set_cur_frame(exec_env, (WASMRuntimeFrame*)frame); +#if WASM_ENABLE_THREAD_MGR != 0 + CHECK_SUSPEND_FLAGS(); +#endif } HANDLE_OP_END (); } diff --git a/core/iwasm/interpreter/wasm_loader.c b/core/iwasm/interpreter/wasm_loader.c index 06909a1c..ea0d9cfe 100644 --- a/core/iwasm/interpreter/wasm_loader.c +++ b/core/iwasm/interpreter/wasm_loader.c @@ -10,6 +10,9 @@ #include "wasm_opcode.h" #include "wasm_runtime.h" #include "../common/wasm_native.h" +#if WASM_ENABLE_DEBUG_INTERP != 0 +#include "../libraries/debug-engine/debug_engine.h" +#endif /* Read a value of given type from the address pointed to by the given pointer and increase the pointer to the position just after the @@ -2934,6 +2937,10 @@ load_from_sections(WASMModule *module, WASMSection *sections, if (section->section_type == SECTION_TYPE_CODE) { buf_code = section->section_body; buf_code_end = buf_code + section->section_body_size; +#if WASM_ENABLE_DEBUG_INTERP != 0 || WASM_ENABLE_DEBUG_AOT != 0 + module->buf_code = (uint8 *)buf_code; + module->buf_code_size = section->section_body_size; +#endif } else if (section->section_type == SECTION_TYPE_FUNC) { buf_func = section->section_body; @@ -3296,10 +3303,26 @@ create_module(char *error_buf, uint32 error_buf_size) #if WASM_ENABLE_MULTI_MODULE != 0 module->import_module_list = &module->import_module_list_head; +#endif +#if WASM_ENABLE_DEBUG_INTERP != 0 + bh_list_init(&module->fast_opcode_list); #endif return module; } +#if WASM_ENABLE_DEBUG_INTERP != 0 +static void +record_fast_op(WASMModule *module, uint8 * pos, uint8 orig_op) +{ + WASMFastOPCodeNode *fast_op = wasm_runtime_malloc(sizeof(WASMFastOPCodeNode)); + if (fast_op) { + fast_op->offset = pos - module->load_addr; + fast_op->orig_op = orig_op; + bh_list_insert(&module->fast_opcode_list, fast_op); + } +} +#endif + WASMModule * wasm_loader_load_from_sections(WASMSection *section_list, char *error_buf, uint32 error_buf_size) @@ -3493,6 +3516,11 @@ wasm_loader_load(const uint8 *buf, uint32 size, char *error_buf, uint32 error_bu return NULL; } +#if WASM_ENABLE_DEBUG_INTERP != 0 + module->load_addr = (uint8 *)buf; + module->load_size = size; +#endif + if (!load(buf, size, module, error_buf, error_buf_size)) { goto fail; } @@ -3593,7 +3621,6 @@ wasm_loader_unload(WASMModule *module) */ wasm_runtime_free(node); /* - * * the module file reading buffer will be released * in runtime_destroy() */ @@ -3601,12 +3628,21 @@ wasm_loader_unload(WASMModule *module) } } #endif - +#if WASM_ENABLE_DEBUG_INTERP != 0 + WASMFastOPCodeNode *fast_opcode = + bh_list_first_elem(&module->fast_opcode_list); + while(fast_opcode) { + WASMFastOPCodeNode * next = bh_list_elem_next(fast_opcode); + wasm_runtime_free(fast_opcode); + fast_opcode = next; + } +#endif wasm_runtime_free(module); } bool -wasm_loader_find_block_addr(BlockAddr *block_addr_cache, +wasm_loader_find_block_addr(WASMExecEnv *exec_env, + BlockAddr *block_addr_cache, const uint8 *start_addr, const uint8 *code_end_addr, uint8 label_type, @@ -3638,7 +3674,9 @@ wasm_loader_find_block_addr(BlockAddr *block_addr_cache, while (p < code_end_addr) { opcode = *p++; - +#if WASM_ENABLE_DEBUG_INTERP != 0 +op_break_retry: +#endif switch (opcode) { case WASM_OP_UNREACHABLE: case WASM_OP_NOP: @@ -4156,6 +4194,31 @@ wasm_loader_find_block_addr(BlockAddr *block_addr_cache, break; } #endif +#if WASM_ENABLE_DEBUG_INTERP != 0 + case DEBUG_OP_BREAK: { + WASMDebugInstance *debug_instance = + wasm_exec_env_get_instance(exec_env); + char orignal_opcode[1]; + uint64 size = 1; + WASMModuleInstance *module_inst = + (WASMModuleInstance *)exec_env->module_inst; + uint64 offset = (p - 1) >= module_inst->module->load_addr + ? (p - 1) - module_inst->module->load_addr + : ~0; + if (debug_instance) { + if (wasm_debug_instance_get_obj_mem( + debug_instance, offset, orignal_opcode, &size) + && size == 1) { + LOG_VERBOSE("WASM loader find OP_BREAK , recover it " + "with %02x: ", + orignal_opcode[0]); + opcode = orignal_opcode[0]; + goto op_break_retry; + } + } + break; + } +#endif default: return false; @@ -6317,6 +6380,9 @@ handle_op_block_and_loop: * to new extended opcode so that interpreter can resolve the * block quickly. */ +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p - 2, *(p - 2)); +#endif *(p - 2) = EXT_OP_BLOCK + (opcode - WASM_OP_BLOCK); #endif } @@ -7196,13 +7262,28 @@ handle_op_block_and_loop: #else #if (WASM_ENABLE_WAMR_COMPILER == 0) && (WASM_ENABLE_JIT == 0) if (local_offset < 0x80) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org++ = EXT_OP_GET_LOCAL_FAST; - if (is_32bit_type(local_type)) + if (is_32bit_type(local_type)) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org++ = (uint8)local_offset; - else + } + else { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org++ = (uint8)(local_offset | 0x80); - while (p_org < p) + } + while (p_org < p) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org++ = WASM_OP_NOP; + } } #endif #endif @@ -7254,13 +7335,28 @@ handle_op_block_and_loop: #else #if (WASM_ENABLE_WAMR_COMPILER == 0) && (WASM_ENABLE_JIT == 0) if (local_offset < 0x80) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org++ = EXT_OP_SET_LOCAL_FAST; - if (is_32bit_type(local_type)) + if (is_32bit_type(local_type)) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org++ = (uint8)local_offset; - else + } + else { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org++ = (uint8)(local_offset | 0x80); - while (p_org < p) + } + while (p_org < p) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org++ = WASM_OP_NOP; + } } #endif #endif @@ -7309,13 +7405,28 @@ handle_op_block_and_loop: #else #if (WASM_ENABLE_WAMR_COMPILER == 0) && (WASM_ENABLE_JIT == 0) if (local_offset < 0x80) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org++ = EXT_OP_TEE_LOCAL_FAST; - if (is_32bit_type(local_type)) + if (is_32bit_type(local_type)) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org++ = (uint8)local_offset; - else + } + else { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org++ = (uint8)(local_offset | 0x80); - while (p_org < p) + } + while (p_org < p) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org++ = WASM_OP_NOP; + } } #endif #endif @@ -7344,6 +7455,9 @@ handle_op_block_and_loop: #if (WASM_ENABLE_WAMR_COMPILER == 0) && (WASM_ENABLE_JIT == 0) if (global_type == VALUE_TYPE_I64 || global_type == VALUE_TYPE_F64) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org = WASM_OP_GET_GLOBAL_64; } #endif @@ -7393,10 +7507,16 @@ handle_op_block_and_loop: #if WASM_ENABLE_FAST_INTERP == 0 if (global_type == VALUE_TYPE_I64 || global_type == VALUE_TYPE_F64) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org = WASM_OP_SET_GLOBAL_64; } else if (module->aux_stack_size > 0 && global_idx == module->aux_stack_top_global_index) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + record_fast_op(module, p_org, *p_org); +#endif *p_org = WASM_OP_SET_GLOBAL_AUX_STACK; } #else /* else of WASM_ENABLE_FAST_INTERP */ diff --git a/core/iwasm/interpreter/wasm_loader.h b/core/iwasm/interpreter/wasm_loader.h index 13daeaf6..03b3b8bd 100644 --- a/core/iwasm/interpreter/wasm_loader.h +++ b/core/iwasm/interpreter/wasm_loader.h @@ -62,8 +62,10 @@ wasm_loader_unload(WASMModule *module); * * @return true if success, false otherwise */ + bool -wasm_loader_find_block_addr(BlockAddr *block_addr_cache, +wasm_loader_find_block_addr(WASMExecEnv *exec_env, + BlockAddr *block_addr_cache, const uint8 *start_addr, const uint8 *code_end_addr, uint8 block_type, diff --git a/core/iwasm/interpreter/wasm_opcode.h b/core/iwasm/interpreter/wasm_opcode.h index 46fe5da8..294a87a8 100644 --- a/core/iwasm/interpreter/wasm_opcode.h +++ b/core/iwasm/interpreter/wasm_opcode.h @@ -267,6 +267,10 @@ typedef enum WASMOpcode { EXT_OP_LOOP = 0xd4, /* loop with blocktype */ EXT_OP_IF = 0xd5, /* if with blocktype */ +#if WASM_ENABLE_DEBUG_INTERP != 0 + DEBUG_OP_BREAK = 0xd6, /* debug break point */ +#endif + /* Post-MVP extend op prefix */ WASM_OP_MISC_PREFIX = 0xfc, WASM_OP_SIMD_PREFIX = 0xfd, @@ -673,6 +677,14 @@ typedef enum WASMAtomicEXTOpcode { } #endif +#if WASM_ENABLE_DEBUG_INTERP != 0 +#define DEF_DEBUG_BREAK_HANDLE(_name) \ + _name[DEBUG_OP_BREAK] = \ + HANDLE_OPCODE (DEBUG_OP_BREAK); /* 0xd6 */ +#else +#define DEF_DEBUG_BREAK_HANDLE(_name) +#endif + /* * Macro used to generate computed goto tables for the C interpreter. */ @@ -900,6 +912,7 @@ do { \ HANDLE_OPCODE (WASM_OP_MISC_PREFIX); /* 0xfc */ \ _name[WASM_OP_ATOMIC_PREFIX] = \ HANDLE_OPCODE (WASM_OP_ATOMIC_PREFIX); /* 0xfe */ \ + DEF_DEBUG_BREAK_HANDLE(_name) \ } while (0) #endif /* end of _WASM_OPCODE_H */ diff --git a/core/iwasm/interpreter/wasm_runtime.c b/core/iwasm/interpreter/wasm_runtime.c index 0d1cfad3..e219ccf2 100644 --- a/core/iwasm/interpreter/wasm_runtime.c +++ b/core/iwasm/interpreter/wasm_runtime.c @@ -16,6 +16,9 @@ #if WASM_ENABLE_THREAD_MGR != 0 #include "../libraries/thread-mgr/thread_manager.h" #endif +#if WASM_ENABLE_DEBUG_INTERP != 0 +#include "../libraries/debug-engine/debug_engine.h" +#endif static void set_error_buf(char *error_buf, uint32 error_buf_size, const char *string) @@ -1826,14 +1829,30 @@ wasm_module_malloc(WASMModuleInstance *module_inst, uint32 size, } else if (module_inst->malloc_function && module_inst->free_function) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + /* TODO: obviously, we can not create debug instance for + * module malloc here, so, just disable the engine here, + * it is strange, but we now are lack of ways to indicate + * which calls should not be debugged. And we have other + * execute_xxx_function may need to be taken care of + */ + bool active = wasm_debug_get_engine_active(); + wasm_debug_set_engine_active(false); +#endif if (!execute_malloc_function(module_inst, module_inst->malloc_function, module_inst->retain_function, size, &offset)) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + wasm_debug_set_engine_active(active); +#endif return 0; } +#if WASM_ENABLE_DEBUG_INTERP != 0 + wasm_debug_set_engine_active(active); +#endif /* If we use app's malloc function, - the default memory may be changed while memory growing */ + the default memory may be changed while memory growing */ memory = module_inst->default_memory; addr = offset ? memory->memory_data + offset : NULL; } @@ -1915,9 +1934,19 @@ wasm_module_free(WASMModuleInstance *module_inst, uint32 ptr) && module_inst->free_function && memory->memory_data <= addr && addr < memory->memory_data_end) { +#if WASM_ENABLE_DEBUG_INTERP != 0 + /*TODO: obviously, we can not create debug instance for module malloc here, + so, just disable the engine here, it is strange. the wasm's call should be + marshed to its own thread */ + bool active = wasm_debug_get_engine_active(); + wasm_debug_set_engine_active(false); +#endif execute_free_function(module_inst, module_inst->free_function, ptr); +#if WASM_ENABLE_DEBUG_INTERP != 0 + wasm_debug_set_engine_active(active); +#endif } } } diff --git a/core/iwasm/libraries/debug-engine/debug_engine.c b/core/iwasm/libraries/debug-engine/debug_engine.c new file mode 100644 index 00000000..99c5ddf1 --- /dev/null +++ b/core/iwasm/libraries/debug-engine/debug_engine.c @@ -0,0 +1,1011 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "debug_engine.h" + +#include "bh_log.h" +#include "gdbserver.h" +#include "platform_api_extension.h" +#include "wasm_interp.h" +#include "wasm_opcode.h" +#include "wasm_runtime.h" + +static uint8 break_instr[] = { DEBUG_OP_BREAK }; + +typedef struct WASMDebugEngine { + struct WASMDebugEngine *next; + WASMDebugControlThread *control_thread; + char ip_addr[128]; + int platform_port; + int process_base_port; + bh_list debug_instance_list; + bool active; +} WASMDebugEngine; + +static bool +should_stop(WASMDebugControlThread *control_thread) +{ + return control_thread->status != RUNNING; +} + +static void * +control_thread_routine(void *arg) +{ + WASMDebugObject *debug_object = (WASMDebugObject *)arg; + + LOG_WARNING("control thread of debug object %p start at %s:%d\n", + debug_object, debug_object->control_thread->ip_addr, + debug_object->control_thread->port); + + debug_object->control_thread->server = + wasm_launch_gdbserver(debug_object->control_thread->ip_addr, + debug_object->control_thread->port); + if (!debug_object->control_thread->server) { + LOG_ERROR("Failed to create debug server\n"); + return NULL; + } + + debug_object->control_thread->server->thread = + debug_object->control_thread; + + while (true) { + os_mutex_lock(&debug_object->control_thread->wait_lock); + if (!should_stop(debug_object->control_thread)) { + if (!wasm_gdbserver_handle_packet( + debug_object->control_thread->server)) + debug_object->control_thread->status = STOPPED; + } + else { + os_mutex_unlock(&debug_object->control_thread->wait_lock); + break; + } + os_mutex_unlock(&debug_object->control_thread->wait_lock); + } + + LOG_VERBOSE("control thread of debug object %p stop\n", debug_object); + return NULL; +} + +static WASMDebugControlThread * +wasm_debug_control_thread_create(WASMDebugObject *debug_object) +{ + WASMDebugControlThread *control_thread; + + if (!(control_thread = + wasm_runtime_malloc(sizeof(WASMDebugControlThread)))) { + LOG_ERROR("WASM Debug Engine error: failed to allocate memory"); + return NULL; + } + + if (os_mutex_init(&control_thread->wait_lock) != 0) + goto fail; + + if (os_cond_init(&control_thread->wait_cond) != 0) + goto fail1; + + control_thread->status = RUNNING; + + if (0 != os_thread_create(&control_thread->tid, control_thread_routine, + debug_object, APP_THREAD_STACK_SIZE_MAX)) { + goto fail2; + } + + return control_thread; + +fail2: + os_cond_destroy(&control_thread->wait_cond); +fail1: + os_mutex_destroy(&control_thread->wait_lock); +fail: + wasm_runtime_free(control_thread); + return NULL; +} + +static void +wasm_debug_control_thread_destroy(WASMDebugObject *debug_object) +{ + WASMDebugControlThread *control_thread = debug_object->control_thread; + LOG_VERBOSE("control thread of debug object %p stop at %s:%d\n", + debug_object, debug_object->control_thread->ip_addr, + debug_object->control_thread->port); + os_mutex_lock(&control_thread->wait_lock); + control_thread->status = STOPPED; + wasm_close_gdbserver(control_thread->server); + os_mutex_unlock(&control_thread->wait_lock); + os_cond_signal(&control_thread->wait_cond); + os_thread_join(control_thread->tid, NULL); + wasm_runtime_free(control_thread->server); + + os_mutex_destroy(&control_thread->wait_lock); + os_cond_destroy(&control_thread->wait_cond); + wasm_runtime_free(control_thread); +} + +static WASMDebugEngine *g_debug_engine; + +static WASMDebugEngine * +wasm_debug_engine_create() +{ + WASMDebugEngine *engine; + + if (!(engine = wasm_runtime_malloc(sizeof(WASMDebugEngine)))) { + LOG_ERROR("WASM Debug Engine error: failed to allocate memory"); + return NULL; + } + memset(engine, 0, sizeof(WASMDebugEngine)); + + /* TODO: support Wasm platform in LLDB */ + /* + engine->control_thread = + wasm_debug_control_thread_create((WASMDebugObject *)engine); + engine->control_thread->debug_engine = (WASMDebugObject *)engine; + engine->control_thread->debug_instance = NULL; + sprintf(engine->control_thread->ip_addr, "127.0.0.1"); + engine->control_thread->port = 1234; + */ + + bh_list_init(&engine->debug_instance_list); + return engine; +} + +bool +wasm_debug_engine_init(char *ip_addr, int platform_port, int process_port) +{ + if (g_debug_engine == NULL) + g_debug_engine = wasm_debug_engine_create(); + + if (g_debug_engine) { + process_port -= 1; + g_debug_engine->platform_port = + platform_port > 0 ? platform_port : 1234; + g_debug_engine->process_base_port = + process_port > 0 ? process_port : 6169; + if (ip_addr) + sprintf(g_debug_engine->ip_addr, "%s", ip_addr); + else + sprintf(g_debug_engine->ip_addr, "%s", "127.0.0.1"); + g_debug_engine->active = true; + } + + return g_debug_engine != NULL ? true : false; +} + +void +wasm_debug_set_engine_active(bool active) +{ + if (g_debug_engine) { + g_debug_engine->active = active; + } +} + +bool +wasm_debug_get_engine_active(void) +{ + if (g_debug_engine) { + return g_debug_engine->active; + } + return false; +} + +void +wasm_debug_engine_destroy() +{ + if (g_debug_engine) { + wasm_runtime_free(g_debug_engine); + g_debug_engine = NULL; + } +} + +/* A debug Instance is a debug "process" in gdb remote protocol + and bound to a runtime cluster */ +WASMDebugInstance * +wasm_debug_instance_create(WASMCluster *cluster) +{ + WASMDebugInstance *instance; + WASMExecEnv *exec_env; + + if (!g_debug_engine || !g_debug_engine->active) { + return NULL; + } + + if (!(instance = wasm_runtime_malloc(sizeof(WASMDebugInstance)))) { + LOG_ERROR("WASM Debug Engine error: failed to allocate memory"); + return NULL; + } + memset(instance, 0, sizeof(WASMDebugInstance)); + instance->cluster = cluster; + instance->control_thread = + wasm_debug_control_thread_create((WASMDebugObject *)instance); + instance->control_thread->debug_engine = (WASMDebugObject *)g_debug_engine; + instance->control_thread->debug_instance = (WASMDebugObject *)instance; + strcpy(instance->control_thread->ip_addr, g_debug_engine->ip_addr); + instance->id = g_debug_engine->debug_instance_list.len + 1; + + exec_env = bh_list_first_elem(&cluster->exec_env_list); + + /* exec_evn is created, but handle may not be set yet. */ + instance->current_tid = exec_env ? exec_env->handle : 0; + + instance->control_thread->port = + g_debug_engine->process_base_port + instance->id; + bh_list_init(&instance->break_point_list); + bh_list_insert(&g_debug_engine->debug_instance_list, instance); + wasm_cluster_send_signal_all(instance->cluster, WAMR_SIG_STOP); + return instance; +} + +static WASMDebugInstance * +wasm_cluster_get_debug_instance(WASMDebugEngine *engine, WASMCluster *cluster) +{ + WASMDebugInstance *instance = + bh_list_first_elem(&engine->debug_instance_list); + while (instance) { + if (instance->cluster == cluster) + return instance; + instance = bh_list_elem_next(instance); + } + return instance; +} + +void +wasm_debug_instance_destroy(WASMCluster *cluster) +{ + WASMDebugInstance *instance = NULL; + + if (!g_debug_engine) { + return; + } + + instance = wasm_cluster_get_debug_instance(g_debug_engine, cluster); + if (instance) { + wasm_debug_control_thread_destroy((WASMDebugObject *)instance); + bh_list_remove(&g_debug_engine->debug_instance_list, instance); + wasm_runtime_free(instance); + } +} + +static WASMExecEnv * +wasm_debug_instance_get_current_env(WASMDebugInstance *instance) +{ + WASMExecEnv *exec_env = NULL; + + if (instance) { + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + while (exec_env) { + if (exec_env->handle == instance->current_tid) + break; + exec_env = bh_list_elem_next(exec_env); + } + } + return exec_env; +} + +#if WASM_ENABLE_LIBC_WASI != 0 +bool +wasm_debug_instance_get_current_object_name(WASMDebugInstance *instance, + char name_buffer[], + int len) +{ + WASMExecEnv *exec_env; + WASIArguments *wasi_args; + WASMModuleInstance *module_inst; + + if (!instance) + return false; + + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + if (!exec_env) + return false; + + module_inst = (WASMModuleInstance *)exec_env->module_inst; + wasi_args = &module_inst->module->wasi_args; + if (wasi_args && wasi_args->argc > 0) { + char *argv_name = wasi_args->argv[0]; + int name_len = strlen(argv_name); + printf("the module name is %s\n", argv_name); + if (len - 1 >= name_len) + strcpy(name_buffer, argv_name); + else + strcpy(name_buffer, argv_name + (name_len + 1 - len)); + return true; + } + return false; +} +#endif + +uint64 +wasm_debug_instance_get_pid(WASMDebugInstance *instance) +{ + if (instance != NULL) { + return (uint64)instance->id; + } + return (uint64)0; +} + +uint64 +wasm_debug_instance_get_tid(WASMDebugInstance *instance) +{ + if (instance != NULL) { + return (uint64)instance->current_tid; + } + return (uint64)0; +} + +int +wasm_debug_instance_get_tids(WASMDebugInstance *instance, + uint64 tids[], int len) +{ + WASMExecEnv *exec_env; + int i = 0; + + if (!instance) + return 0; + + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + while (exec_env && i < len) { + tids[i++] = exec_env->handle; + exec_env = bh_list_elem_next(exec_env); + } + LOG_VERBOSE("find %d tids\n", i); + return i; +} + +uint64 +wasm_debug_instance_wait_thread(WASMDebugInstance *instance, + uint64 tid, uint32 *status) +{ + WASMExecEnv *exec_env; + WASMExecEnv *last_exec_env = NULL; + + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + while (exec_env) { + last_exec_env = exec_env; + if (instance->current_tid != 0 + && last_exec_env->handle == instance->current_tid) { + break; + } + exec_env = bh_list_elem_next(exec_env); + } + + if (last_exec_env) { + wasm_cluster_wait_thread_status(last_exec_env, status); + if (instance->current_tid == 0) + instance->current_tid = last_exec_env->handle; + return last_exec_env->handle; + } + else { + *status = ~0; + return 0; + } +} + +void +wasm_debug_instance_set_cur_thread(WASMDebugInstance *instance, uint64 tid) +{ + instance->current_tid = tid; +} + +uint64 +wasm_debug_instance_get_pc(WASMDebugInstance *instance) +{ + WASMExecEnv *exec_env; + + if (!instance) + return 0; + + exec_env = wasm_debug_instance_get_current_env(instance); + if ((exec_env->cur_frame != NULL) + && (exec_env->cur_frame->ip != NULL)) { + WASMModuleInstance *module_inst = + (WASMModuleInstance *)exec_env->module_inst; + return WASM_ADDR( + WasmObj, instance->id, + (exec_env->cur_frame->ip - module_inst->module->load_addr)); + } + return 0; +} + +uint64 +wasm_debug_instance_get_load_addr(WASMDebugInstance *instance) +{ + WASMExecEnv *exec_env; + + if (!instance) + return WASM_ADDR(WasmInvalid, 0, 0); + + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + if (exec_env) { + return WASM_ADDR(WasmObj, instance->id, 0); + } + + return WASM_ADDR(WasmInvalid, 0, 0); +} + +WASMDebugMemoryInfo * +wasm_debug_instance_get_memregion(WASMDebugInstance *instance, uint64 addr) +{ + WASMDebugMemoryInfo *mem_info; + WASMExecEnv *exec_env; + WASMModuleInstance *module_inst; + WASMMemoryInstance *memory; + uint32 num_bytes_per_page; + uint32 linear_mem_size = 0; + + if (!instance) + return NULL; + + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + if (!exec_env) + return NULL; + + if (!(mem_info = wasm_runtime_malloc(sizeof(WASMDebugMemoryInfo)))) { + LOG_ERROR("WASM Debug Engine error: failed to allocate memory"); + return NULL; + } + mem_info->start = WASM_ADDR(WasmInvalid, 0, 0); + mem_info->size = 0; + mem_info->name[0] = '\0'; + mem_info->permisson[0] = '\0'; + + module_inst = (WASMModuleInstance *)exec_env->module_inst; + + switch (WASM_ADDR_TYPE(addr)) { + case WasmObj: + if (WASM_ADDR_OFFSET(addr) < module_inst->module->load_size) { + mem_info->start = WASM_ADDR(WasmObj, instance->id, 0); + mem_info->size = module_inst->module->load_size; + sprintf(mem_info->name, "%s", "module"); + sprintf(mem_info->permisson, "%s", "rx"); + } + break; + case WasmMemory: { + memory = module_inst->default_memory; + + if (memory) { + num_bytes_per_page = memory->num_bytes_per_page; + linear_mem_size = num_bytes_per_page * memory->cur_page_count; + } + if (WASM_ADDR_OFFSET(addr) < linear_mem_size) { + mem_info->start = WASM_ADDR(WasmMemory, instance->id, 0); + mem_info->size = linear_mem_size; + sprintf(mem_info->name, "%s", "memory"); + sprintf(mem_info->permisson, "%s", "rw"); + } + break; + } + default: + mem_info->start = WASM_ADDR(WasmInvalid, 0, 0); + mem_info->size = 0; + } + return mem_info; +} + +void +wasm_debug_instance_destroy_memregion(WASMDebugInstance *instance, + WASMDebugMemoryInfo *mem_info) +{ + wasm_runtime_free(mem_info); +} + +bool +wasm_debug_instance_get_obj_mem(WASMDebugInstance *instance, + uint64 offset, char *buf, uint64 *size) +{ + WASMExecEnv *exec_env; + WASMModuleInstance *module_inst; + + if (!instance) + return false; + + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + if (!exec_env) + return false; + + module_inst = (WASMModuleInstance *)exec_env->module_inst; + + if (offset + *size > module_inst->module->load_size) { + LOG_VERBOSE("wasm_debug_instance_get_data_mem size over flow!\n"); + *size = module_inst->module->load_size >= offset + ? module_inst->module->load_size - offset + : 0; + } + + bh_memcpy_s(buf, *size, module_inst->module->load_addr + offset, *size); + + WASMDebugBreakPoint *breakpoint = + bh_list_first_elem(&instance->break_point_list); + + while (breakpoint) { + if (offset <= breakpoint->addr && breakpoint->addr < offset + *size) { + bh_memcpy_s(buf + (breakpoint->addr - offset), sizeof(break_instr), + &breakpoint->orignal_data, sizeof(break_instr)); + } + breakpoint = bh_list_elem_next(breakpoint); + } + + WASMFastOPCodeNode *fast_opcode = + bh_list_first_elem(&module_inst->module->fast_opcode_list); + while (fast_opcode) { + if (offset <= fast_opcode->offset + && fast_opcode->offset < offset + *size) { + *(uint8 *)(buf + (fast_opcode->offset - offset)) = + fast_opcode->orig_op; + } + fast_opcode = bh_list_elem_next(fast_opcode); + } + + return true; +} + +bool +wasm_debug_instance_get_linear_mem(WASMDebugInstance *instance, + uint64 offset, char *buf, uint64 *size) +{ + WASMExecEnv *exec_env; + WASMModuleInstance *module_inst; + WASMMemoryInstance *memory; + uint32 num_bytes_per_page; + uint32 linear_mem_size; + + if (!instance) + return false; + + exec_env = wasm_debug_instance_get_current_env(instance); + if (!exec_env) + return false; + + module_inst = (WASMModuleInstance *)exec_env->module_inst; + memory = module_inst->default_memory; + if (memory) { + num_bytes_per_page = memory->num_bytes_per_page; + linear_mem_size = num_bytes_per_page * memory->cur_page_count; + if (offset + *size > linear_mem_size) { + LOG_VERBOSE( + "wasm_debug_instance_get_linear_mem size over flow!\n"); + *size = linear_mem_size >= offset ? linear_mem_size - offset : 0; + } + bh_memcpy_s(buf, *size, memory->memory_data + offset, *size); + return true; + } + return false; +} + +bool +wasm_debug_instance_set_linear_mem(WASMDebugInstance *instance, + uint64 offset, char *buf, uint64 *size) +{ + WASMExecEnv *exec_env; + WASMModuleInstance *module_inst; + WASMMemoryInstance *memory; + uint32 num_bytes_per_page; + uint32 linear_mem_size; + + if (!instance) + return false; + + exec_env = wasm_debug_instance_get_current_env(instance); + if (!exec_env) + return false; + + module_inst = (WASMModuleInstance *)exec_env->module_inst; + memory = module_inst->default_memory; + if (memory) { + num_bytes_per_page = memory->num_bytes_per_page; + linear_mem_size = num_bytes_per_page * memory->cur_page_count; + if (offset + *size > linear_mem_size) { + LOG_VERBOSE( + "wasm_debug_instance_get_linear_mem size over flow!\n"); + *size = linear_mem_size >= offset ? linear_mem_size - offset : 0; + } + bh_memcpy_s(memory->memory_data + offset, *size, buf, *size); + return true; + } + return false; +} + +bool +wasm_debug_instance_get_mem(WASMDebugInstance *instance, + uint64 addr, char *buf, uint64 *size) +{ + switch (WASM_ADDR_TYPE(addr)) { + case WasmMemory: + return wasm_debug_instance_get_linear_mem( + instance, WASM_ADDR_OFFSET(addr), buf, size); + break; + case WasmObj: + return wasm_debug_instance_get_obj_mem( + instance, WASM_ADDR_OFFSET(addr), buf, size); + break; + default: + return false; + } +} + +bool +wasm_debug_instance_set_mem(WASMDebugInstance *instance, + uint64 addr, char *buf, uint64 *size) +{ + switch (WASM_ADDR_TYPE(addr)) { + case WasmMemory: + return wasm_debug_instance_set_linear_mem( + instance, WASM_ADDR_OFFSET(addr), buf, size); + break; + case WasmObj: + default: + return false; + } +} + +WASMDebugInstance * +wasm_exec_env_get_instance(WASMExecEnv *exec_env) +{ + WASMDebugInstance *instance = NULL; + bh_assert(g_debug_engine); + + instance = bh_list_first_elem(&g_debug_engine->debug_instance_list); + while (instance) { + if (instance->cluster == exec_env->cluster) + break; + instance = bh_list_elem_next(instance); + } + return instance; +} + +int +wasm_debug_instance_get_call_stack_pcs(WASMDebugInstance *instance, + uint64 tid, uint64 buf[], uint64 size) +{ + WASMExecEnv *exec_env; + struct WASMInterpFrame *frame; + uint64 i = 0; + + if (!instance) + return 0; + + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + while (exec_env) { + if (exec_env->handle == tid) { + WASMModuleInstance *module_inst = + (WASMModuleInstance *)exec_env->module_inst; + frame = exec_env->cur_frame; + while (frame && i < size) { + if (frame->ip != NULL) { + buf[i++] = + WASM_ADDR(WasmObj, instance->id, + (frame->ip - module_inst->module->load_addr)); + } + frame = frame->prev_frame; + } + return i; + } + exec_env = bh_list_elem_next(exec_env); + } + return 0; +} + +bool +wasm_debug_instance_add_breakpoint(WASMDebugInstance *instance, + uint64 addr, uint64 length) +{ + WASMExecEnv *exec_env; + WASMModuleInstance *module_inst; + uint64 offset; + + if (!instance) + return false; + + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + if (!exec_env) + return false; + + module_inst = (WASMModuleInstance *)exec_env->module_inst; + if (WASM_ADDR_TYPE(addr) != WasmObj) + return false; + + offset = WASM_ADDR_OFFSET(addr); + + if (length >= sizeof(break_instr)) { + if (offset + sizeof(break_instr) <= module_inst->module->load_size) { + WASMDebugBreakPoint *breakpoint; + if (!(breakpoint = + wasm_runtime_malloc(sizeof(WASMDebugBreakPoint)))) { + LOG_ERROR( + "WASM Debug Engine error: failed to allocate memory"); + return false; + } + breakpoint->addr = offset; + /* TODO: how to if more than one breakpoints are set + at the same addr? */ + bh_memcpy_s(&breakpoint->orignal_data, + (uint32)sizeof(break_instr), + module_inst->module->load_addr + offset, + (uint32)sizeof(break_instr)); + + bh_memcpy_s(module_inst->module->load_addr + offset, + (uint32)sizeof(break_instr), + break_instr, + (uint32)sizeof(break_instr)); + + bh_list_insert(&instance->break_point_list, breakpoint); + return true; + } + } + return false; +} + +bool +wasm_debug_instance_remove_breakpoint(WASMDebugInstance *instance, + uint64 addr, uint64 length) +{ + WASMExecEnv *exec_env; + WASMModuleInstance *module_inst; + uint64 offset; + + if (!instance) + return false; + + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + if (!exec_env) + return false; + + module_inst = (WASMModuleInstance *)exec_env->module_inst; + + if (WASM_ADDR_TYPE(addr) != WasmObj) + return false; + offset = WASM_ADDR_OFFSET(addr); + + if (length >= sizeof(break_instr)) { + if (offset + sizeof(break_instr) <= module_inst->module->load_size) { + WASMDebugBreakPoint *breakpoint = + bh_list_first_elem(&instance->break_point_list); + while (breakpoint) { + WASMDebugBreakPoint *next_break = + bh_list_elem_next(breakpoint); + if (breakpoint->addr == offset) { + /* TODO: how to if more than one breakpoints are set + at the same addr? */ + bh_memcpy_s(module_inst->module->load_addr + offset, + (uint32)sizeof(break_instr), + &breakpoint->orignal_data, + (uint32)sizeof(break_instr)); + bh_list_remove(&instance->break_point_list, breakpoint); + wasm_runtime_free(breakpoint); + } + breakpoint = next_break; + } + } + } + return true; +} + +bool +wasm_debug_instance_continue(WASMDebugInstance *instance) +{ + WASMExecEnv *exec_env; + + if (!instance) + return false; + + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + if (!exec_env) + return false; + + while (exec_env) { + wasm_cluster_thread_continue(exec_env); + exec_env = bh_list_elem_next(exec_env); + } + return true; +} + +bool +wasm_debug_instance_kill(WASMDebugInstance *instance) +{ + WASMExecEnv *exec_env; + + if (!instance) + return false; + + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + if (!exec_env) + return false; + + while (exec_env) { + wasm_cluster_thread_send_signal(exec_env, WAMR_SIG_TERM); + exec_env = bh_list_elem_next(exec_env); + } + return true; +} + +bool +wasm_debug_instance_singlestep(WASMDebugInstance *instance, uint64 tid) +{ + WASMExecEnv *exec_env; + + if (!instance) + return false; + + exec_env = bh_list_first_elem(&instance->cluster->exec_env_list); + if (!exec_env) + return false; + + while (exec_env) { + if (exec_env->handle == tid || tid == (uint64)~0) { + wasm_cluster_thread_send_signal(exec_env, WAMR_SIG_SINGSTEP); + wasm_cluster_thread_step(exec_env); + } + exec_env = bh_list_elem_next(exec_env); + } + return true; +} + +bool +wasm_debug_instance_get_local(WASMDebugInstance *instance, + int frame_index, int local_index, + char buf[], int *size) +{ + WASMExecEnv *exec_env; + struct WASMInterpFrame *frame; + WASMFunctionInstance *cur_func; + uint8 local_type = 0xFF; + uint32 local_offset; + int param_count; + int fi = 0; + + if (!instance) + return false; + + exec_env = wasm_debug_instance_get_current_env(instance); + if (!exec_env) + return false; + + frame = exec_env->cur_frame; + while (frame && fi++ != frame_index) { + frame = frame->prev_frame; + } + + if (!frame) + return false; + cur_func = frame->function; + if (!cur_func) + return false; + + param_count = cur_func->param_count; + local_offset = cur_func->local_offsets[local_index]; + if (local_index < param_count) + local_type = cur_func->param_types[local_index]; + else if (local_index < cur_func->local_count + param_count) + local_type = cur_func->local_types[local_index - param_count]; + + switch (local_type) { + case VALUE_TYPE_I32: + case VALUE_TYPE_F32: + *size = 4; + bh_memcpy_s(buf, 4, (char *)(frame->lp + local_offset), 4); + break; + case VALUE_TYPE_I64: + case VALUE_TYPE_F64: + *size = 8; + bh_memcpy_s(buf, 8, (char *)(frame->lp + local_offset), 8); + break; + default: + *size = 0; + break; + } + return true; +} + +bool +wasm_debug_instance_get_global(WASMDebugInstance *instance, + int frame_index, int global_index, + char buf[], int *size) +{ + WASMExecEnv *exec_env; + struct WASMInterpFrame *frame; + WASMModuleInstance *module_inst; + WASMGlobalInstance *globals, *global; + uint8 *global_addr; + uint8 global_type = 0xFF; + uint8 *global_data; + int fi = 0; + + if (!instance) + return false; + + exec_env = wasm_debug_instance_get_current_env(instance); + if (!exec_env) + return false; + + frame = exec_env->cur_frame; + while (frame && fi++ != frame_index) { + frame = frame->prev_frame; + } + + if (!frame) + return false; + + module_inst = (WASMModuleInstance *)exec_env->module_inst; + global_data = module_inst->global_data; + globals = module_inst->globals; + global = globals + global_index; + +#if WASM_ENABLE_MULTI_MODULE == 0 + global_addr = global_data + global->data_offset; +#else + global_addr = global->import_global_inst + ? global->import_module_inst->global_data + + global->import_global_inst->data_offset + : global_data + global->data_offset; +#endif + global_type = global->type; + + switch (global_type) { + case VALUE_TYPE_I32: + case VALUE_TYPE_F32: + *size = 4; + bh_memcpy_s(buf, 4, (char *)(global_addr), 4); + break; + case VALUE_TYPE_I64: + case VALUE_TYPE_F64: + *size = 8; + bh_memcpy_s(buf, 8, (char *)(global_addr), 8); + break; + default: + *size = 0; + break; + } + return true; +} + +uint64 +wasm_debug_instance_mmap(WASMDebugInstance *instance, + uint32 size, int map_port) +{ + WASMExecEnv *exec_env; + WASMModuleInstance *module_inst; + uint32 offset; + void *native_addr; + (void)map_port; + + if (!instance) + return 0; + + exec_env = wasm_debug_instance_get_current_env(instance); + if (!exec_env) + return 0; + + module_inst = (WASMModuleInstance *)exec_env->module_inst; + + /* TODO: malloc in wasi libc maybe not be thread safe, we hope LLDB will + always ask for memory when threads stopped */ + offset = wasm_runtime_module_malloc((wasm_module_inst_t)module_inst, size, + &native_addr); + if (!offset) + LOG_WARNING("the memory may be not enough for debug, try use larger " + "--heap-size"); + return WASM_ADDR(WasmMemory, 0, offset); +} + +bool +wasm_debug_instance_ummap(WASMDebugInstance *instance, uint64 addr) +{ + WASMExecEnv *exec_env; + WASMModuleInstance *module_inst; + uint32 offset; + + if (!instance) + return false; + + exec_env = wasm_debug_instance_get_current_env(instance); + if (!exec_env) + return false; + + module_inst = (WASMModuleInstance *)exec_env->module_inst; + if (WASM_ADDR_TYPE(addr) == WasmMemory) { + offset = WASM_ADDR_OFFSET(addr); + wasm_runtime_module_free((wasm_module_inst_t)module_inst, offset); + return true; + } + return false; +} diff --git a/core/iwasm/libraries/debug-engine/debug_engine.cmake b/core/iwasm/libraries/debug-engine/debug_engine.cmake new file mode 100644 index 00000000..914ddd63 --- /dev/null +++ b/core/iwasm/libraries/debug-engine/debug_engine.cmake @@ -0,0 +1,12 @@ +# Copyright (C) 2021 Ant Group. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +set (DEBUG_ENGINE_DIR ${CMAKE_CURRENT_LIST_DIR}) + +add_definitions (-DWASM_ENABLE_DEBUG_INTERP=1) + +include_directories(${DEBUG_ENGINE_DIR}) + +file (GLOB source_all ${DEBUG_ENGINE_DIR}/*.c) + +set (DEBUG_ENGINE_SOURCE ${source_all}) diff --git a/core/iwasm/libraries/debug-engine/debug_engine.h b/core/iwasm/libraries/debug-engine/debug_engine.h new file mode 100644 index 00000000..1fc89604 --- /dev/null +++ b/core/iwasm/libraries/debug-engine/debug_engine.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifndef _DEBUG_ENGINE_H +#define _DEBUG_ENGINE_H + +#include "bh_list.h" +#include "gdbserver.h" +#include "thread_manager.h" + +typedef enum WASMDebugControlThreadStatus { + RUNNING, + STOPPED, +} WASMDebugControlThreadStatus; + +struct WASMDebugObject; +typedef struct WASMDebugControlThread { + WASMGDBServer *server; + korp_tid tid; + korp_mutex wait_lock; + korp_cond wait_cond; + char ip_addr[128]; + int port; + WASMDebugControlThreadStatus status; + struct WASMDebugObject *debug_engine; + struct WASMDebugObject *debug_instance; +} WASMDebugControlThread; + +typedef struct WASMDebugObject { + struct WASMDebugObject *next; + WASMDebugControlThread *control_thread; +} WASMDebugObject; + +typedef struct WASMDebugBreakPoint { + struct WASMDebugBreakPoint *next; + uint64 addr; + uint64 orignal_data; +} WASMDebugBreakPoint; + +typedef struct WASMDebugInstance { + struct WASMDebugInstance *next; + WASMDebugControlThread *control_thread; + bh_list break_point_list; + WASMCluster *cluster; + uint32 id; + korp_tid current_tid; +} WASMDebugInstance; + +typedef enum WASMDebugEventKind { + BREAK_POINT_ADD, + BREAK_POINT_REMOVE +} WASMDebugEventKind; + +typedef struct WASMDebugEvent { + WASMDebugEventKind kind; + unsigned char metadata[0]; +} WASMDebugEvent; + +typedef struct WASMDebugMemoryInfo { + uint64 start; + uint64 size; + char name[128]; + char permisson[4]; +} WASMDebugMemoryInfo; + +typedef enum WasmAddressType { + WasmMemory = 0x00, + WasmObj = 0x01, + WasmInvalid = 0x03 +} WasmAddressType; + +#define WASM_ADDR(type, id, offset) \ + (((uint64)type << 62) | ((uint64)0 << 32) | ((uint64)offset << 0)) + +#define WASM_ADDR_TYPE(addr) (((addr)&0xC000000000000000) >> 62) +#define WASM_ADDR_OFFSET(addr) (((addr)&0x00000000FFFFFFFF)) + +#define INVALIED_ADDR (0xFFFFFFFFFFFFFFFF) + +WASMDebugInstance * +wasm_debug_instance_create(WASMCluster *cluster); + +void +wasm_debug_instance_destroy(WASMCluster *cluster); + +WASMDebugInstance * +wasm_exec_env_get_instance(WASMExecEnv *exec_env); + +bool +wasm_debug_engine_init(char *ip_addr, int platform_port, int process_port); + +void +wasm_debug_engine_destroy(); + +void +wasm_debug_set_engine_active(bool active); + +bool +wasm_debug_get_engine_active(void); + + +uint64 +wasm_debug_instance_get_pid(WASMDebugInstance *instance); + +uint64 +wasm_debug_instance_get_tid(WASMDebugInstance *instance); + +int +wasm_debug_instance_get_tids(WASMDebugInstance *instance, + uint64 tids[], int len); + +void +wasm_debug_instance_set_cur_thread(WASMDebugInstance *instance, uint64 tid); + +uint64 +wasm_debug_instance_get_pc(WASMDebugInstance *instance); + +uint64 +wasm_debug_instance_get_load_addr(WASMDebugInstance *instance); + +WASMDebugMemoryInfo * +wasm_debug_instance_get_memregion(WASMDebugInstance *instance, uint64 addr); + +void +wasm_debug_instance_destroy_memregion(WASMDebugInstance *instance, + WASMDebugMemoryInfo *mem_info); + +bool +wasm_debug_instance_get_obj_mem(WASMDebugInstance *instance, + uint64 addr, char *buf, uint64 *size); + +bool +wasm_debug_instance_get_linear_mem(WASMDebugInstance *instance, + uint64 addr, char *buf, uint64 *size); + +bool +wasm_debug_instance_get_mem(WASMDebugInstance *instance, + uint64 addr, char *buf, uint64 *size); + +bool +wasm_debug_instance_set_mem(WASMDebugInstance *instance, + uint64 addr, char *buf, uint64 *size); + +int +wasm_debug_instance_get_call_stack_pcs(WASMDebugInstance *instance, + uint64 tid, uint64 buf[], uint64 size); + +bool +wasm_debug_instance_add_breakpoint(WASMDebugInstance *instance, + uint64 addr, uint64 length); + +bool +wasm_debug_instance_remove_breakpoint(WASMDebugInstance *instance, + uint64 addr, uint64 length); + +bool +wasm_debug_instance_continue(WASMDebugInstance *instance); + +bool +wasm_debug_instance_kill(WASMDebugInstance *instance); + +uint64 +wasm_debug_instance_wait_thread(WASMDebugInstance *instance, + uint64 tid, uint32 *status); + +bool +wasm_debug_instance_singlestep(WASMDebugInstance *instance, uint64 tid); + +bool +wasm_debug_instance_get_local(WASMDebugInstance *instance, + int frame_index, int local_index, + char buf[], int *size); + +bool +wasm_debug_instance_get_global(WASMDebugInstance *instance, + int frame_index, int global_index, + char buf[], int *size); + +#if WASM_ENABLE_LIBC_WASI != 0 +bool +wasm_debug_instance_get_current_object_name(WASMDebugInstance *instance, + char name_buffer[], int len); +#endif + +uint64 +wasm_debug_instance_mmap(WASMDebugInstance *instance, + uint32 size, int map_port); + +bool +wasm_debug_instance_ummap(WASMDebugInstance *instance, uint64 addr); +#endif diff --git a/core/iwasm/libraries/debug-engine/gdbserver.c b/core/iwasm/libraries/debug-engine/gdbserver.c new file mode 100644 index 00000000..23aae849 --- /dev/null +++ b/core/iwasm/libraries/debug-engine/gdbserver.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "gdbserver.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bh_log.h" +#include "handler.h" +#include "packets.h" +#include "utils.h" + +typedef void (*PacketHandler)(WASMGDBServer *server, char *payload); + +struct packet_handler_elem { + char request; + PacketHandler handler; +}; + +#define DEL_HANDLER(r, h) [r] = { .request = r, .handler = h } + +static struct packet_handler_elem packet_handler_table[255] = { + DEL_HANDLER('Q', handle_generay_set), + DEL_HANDLER('q', handle_generay_query), + DEL_HANDLER('v', handle_v_packet), + DEL_HANDLER('?', handle_threadstop_request), + DEL_HANDLER('H', handle_set_current_thread), + DEL_HANDLER('p', handle_get_register), + DEL_HANDLER('j', handle_get_json_request), + DEL_HANDLER('m', handle_get_read_memory), + DEL_HANDLER('M', handle_get_write_memory), + DEL_HANDLER('x', handle_get_read_binary_memory), + DEL_HANDLER('Z', handle_add_break), + DEL_HANDLER('z', handle_remove_break), + DEL_HANDLER('c', handle_continue_request), + DEL_HANDLER('k', handle_kill_request), + DEL_HANDLER('_', handle____request), +}; + +WASMGDBServer * +wasm_launch_gdbserver(char *host, int port) +{ + int listen_fd = -1; + const int one = 1; + struct sockaddr_in addr; + int ret; + int sockt_fd = 0; + + WASMGDBServer *server; + + if (!(server = wasm_runtime_malloc(sizeof(WASMGDBServer)))) { + LOG_ERROR("wasm gdb server error: failed to allocate memory"); + return NULL; + } + + listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listen_fd < 0) { + LOG_ERROR("wasm gdb server error: socket() failed"); + goto fail; + } + + ret = fcntl(listen_fd, F_SETFD, FD_CLOEXEC); + if(ret < 0) { + LOG_ERROR("wasm gdb server error: fcntl() failed on setting FD_CLOEXEC"); + goto fail; + } + + ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (ret < 0) { + LOG_ERROR("wasm gdb server error: setsockopt() failed"); + goto fail; + } + + LOG_VERBOSE("Listening on %s:%d\n", host, port); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(host); + addr.sin_port = htons(port); + + ret = bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + LOG_ERROR("wasm gdb server error: bind() failed"); + goto fail; + } + + ret = listen(listen_fd, 1); + if (ret < 0) { + LOG_ERROR("wasm gdb server error: listen() failed"); + goto fail; + } + + server->listen_fd = listen_fd; + + sockt_fd = accept(listen_fd, NULL, NULL); + if (sockt_fd < 0) { + LOG_ERROR("wasm gdb server error: accept() failed"); + goto fail; + } + LOG_VERBOSE("accept gdb client"); + server->socket_fd = sockt_fd; + server->noack = false; + return server; + +fail: + if (listen_fd > 0) { + shutdown(listen_fd, SHUT_RDWR); + close(listen_fd); + } + if (server) + wasm_runtime_free(server); + return NULL; +} + +void +wasm_close_gdbserver(WASMGDBServer *server) +{ + if (server->socket_fd > 0) { + shutdown(server->socket_fd, SHUT_RDWR); + close(server->socket_fd); + } + if (server->listen_fd > 0) { + shutdown(server->listen_fd, SHUT_RDWR); + close(server->listen_fd); + } +} + +static inline void +handler_packet(WASMGDBServer *server, char request, char *payload) +{ + if (packet_handler_table[(int)request].handler != NULL) + packet_handler_table[(int)request].handler(server, payload); +} + +static void +process_packet(WASMGDBServer *server) +{ + uint8_t *inbuf = server->pkt.buf; + int inbuf_size = server->pkt.end; + uint8_t *packetend_ptr = (uint8_t *)memchr(inbuf, '#', inbuf_size); + int packetend = packetend_ptr - inbuf; + bh_assert('$' == inbuf[0]); + char request = inbuf[1]; + char *payload = (char *)&inbuf[2]; + inbuf[packetend] = '\0'; + + uint8_t checksum = 0; + for (int i = 1; i < packetend; i++) + checksum += inbuf[i]; + bh_assert(checksum + == (hex(inbuf[packetend + 1]) << 4 | hex(inbuf[packetend + 2]))); + + LOG_VERBOSE("receive request:%c %s\n", request, payload); + handler_packet(server, request, payload); + inbuf_erase_head(server, packetend + 3); +} + +bool +wasm_gdbserver_handle_packet(WASMGDBServer *server) +{ + bool ret; + ret = read_packet(server); + if (ret) + process_packet(server); + return ret; +} diff --git a/core/iwasm/libraries/debug-engine/gdbserver.h b/core/iwasm/libraries/debug-engine/gdbserver.h new file mode 100644 index 00000000..f452e48e --- /dev/null +++ b/core/iwasm/libraries/debug-engine/gdbserver.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifndef _GDB_SERVER_H +#define _GDB_SERVER_H + +#include + +#define PACKET_BUF_SIZE 0x8000 + +enum GDBStoppointType { + eStoppointInvalid = -1, + eBreakpointSoftware = 0, + eBreakpointHardware, + eWatchpointWrite, + eWatchpointRead, + eWatchpointReadWrite +}; +typedef struct WasmDebugPacket { + unsigned char buf[PACKET_BUF_SIZE]; + unsigned int end; +} WasmDebugPacket; + +struct WASMDebugControlThread; +typedef struct WASMGDBServer { + int listen_fd; + int socket_fd; + WasmDebugPacket pkt; + bool noack; + struct WASMDebugControlThread *thread; +} WASMGDBServer; + +WASMGDBServer * +wasm_launch_gdbserver(char *addr, int port); + +void +wasm_close_gdbserver(WASMGDBServer *server); + +bool +wasm_gdbserver_handle_packet(WASMGDBServer *server); +#endif diff --git a/core/iwasm/libraries/debug-engine/handler.c b/core/iwasm/libraries/debug-engine/handler.c new file mode 100644 index 00000000..8387f051 --- /dev/null +++ b/core/iwasm/libraries/debug-engine/handler.c @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include +#include + +#include "debug_engine.h" +#include "packets.h" +#include "utils.h" +#include "wasm_runtime.h" + +#define MAX_PACKET_SIZE (0x20000) +static char tmpbuf[MAX_PACKET_SIZE]; + +void +handle_generay_set(WASMGDBServer *server, char *payload) +{ + const char *name; + char *args; + + args = strchr(payload, ':'); + if (args) + *args++ = '\0'; + + name = payload; + LOG_VERBOSE("%s:%s\n", __FUNCTION__, payload); + + if (!strcmp(name, "StartNoAckMode")) { + server->noack = true; + write_packet(server, "OK"); + } + if (!strcmp(name, "ThreadSuffixSupported")) { + write_packet(server, ""); + } + if (!strcmp(name, "ListThreadsInStopReply")) { + write_packet(server, ""); + } + if (!strcmp(name, "EnableErrorStrings")) { + write_packet(server, "OK"); + } +} + +static void +process_xfer(WASMGDBServer *server, const char *name, char *args) +{ + const char *mode = args; + + args = strchr(args, ':'); + *args++ = '\0'; + + if (!strcmp(name, "libraries") && !strcmp(mode, "read")) { + //TODO: how to get current wasm file name? + uint64_t addr = wasm_debug_instance_get_load_addr( + (WASMDebugInstance *)server->thread->debug_instance); +#if WASM_ENABLE_LIBC_WASI != 0 + char objname[128]; + wasm_debug_instance_get_current_object_name( + (WASMDebugInstance *)server->thread->debug_instance, objname, 128); + sprintf(tmpbuf, + "l
", + objname, addr); +#else + sprintf(tmpbuf, + "l
", + "nobody.wasm", addr); +#endif + write_packet(server, tmpbuf); + } +} + +void +porcess_wasm_local(WASMGDBServer *server, char *args) +{ + int frame_index; + int local_index; + char buf[16]; + int size = 16; + bool ret; + + sprintf(tmpbuf, "E01"); + if (sscanf(args, "%d;%d", &frame_index, &local_index) == 2) { + ret = wasm_debug_instance_get_local( + (WASMDebugInstance *)server->thread->debug_instance, frame_index, + local_index, buf, &size); + if (ret && size > 0) { + mem2hex(buf, tmpbuf, size); + } + } + write_packet(server, tmpbuf); +} + +void +porcess_wasm_global(WASMGDBServer *server, char *args) +{ + int frame_index; + int global_index; + char buf[16]; + int size = 16; + bool ret; + + sprintf(tmpbuf, "E01"); + if (sscanf(args, "%d;%d", &frame_index, &global_index) == 2) { + ret = wasm_debug_instance_get_global( + (WASMDebugInstance *)server->thread->debug_instance, frame_index, + global_index, buf, &size); + if (ret && size > 0) { + mem2hex(buf, tmpbuf, size); + } + } + write_packet(server, tmpbuf); +} + +void +handle_generay_query(WASMGDBServer *server, char *payload) +{ + const char *name; + char *args; + + args = strchr(payload, ':'); + if (args) + *args++ = '\0'; + name = payload; + LOG_VERBOSE("%s:%s\n", __FUNCTION__, payload); + + if (!strcmp(name, "C")) { + uint64_t pid, tid; + pid = wasm_debug_instance_get_pid( + (WASMDebugInstance *)server->thread->debug_instance); + tid = wasm_debug_instance_get_tid( + (WASMDebugInstance *)server->thread->debug_instance); + snprintf(tmpbuf, sizeof(tmpbuf), "QCp%lx.%lx", pid, tid); + write_packet(server, tmpbuf); + } + if (!strcmp(name, "Supported")) { + sprintf(tmpbuf, "qXfer:libraries:read+;PacketSize=%x;", MAX_PACKET_SIZE); + write_packet(server, tmpbuf); + } + + if (!strcmp(name, "Xfer")) { + name = args; + args = strchr(args, ':'); + *args++ = '\0'; + process_xfer(server, name, args); + } + + if (!strcmp(name, "HostInfo")) { + //Todo: change vendor to Intel for outside tree? + char triple[256]; + mem2hex("wasm32-Ant-wasi-wasm", triple, + strlen("wasm32-Ant-wasi-wasm")); + sprintf(tmpbuf, + "vendor:Ant;ostype:wasi;arch:wasm32;" + "triple:%s;endian:little;ptrsize:4;", + triple); + + write_packet(server, tmpbuf); + } + if (!strcmp(name, "GetWorkingDir")) { + if (getcwd(tmpbuf, PATH_MAX)) + write_packet(server, tmpbuf); + } + if (!strcmp(name, "QueryGDBServer")) { + write_packet(server, ""); + } + if (!strcmp(name, "VAttachOrWaitSupported")) { + write_packet(server, ""); + } + if (!strcmp(name, "ProcessInfo")) { + //Todo: process id parent-pid + uint64_t pid; + pid = wasm_debug_instance_get_pid( + (WASMDebugInstance *)server->thread->debug_instance); + char triple[256]; + //arch-vendor-os-env(format) + mem2hex("wasm32-Ant-wasi-wasm", triple, + strlen("wasm32-Ant-wasi-wasm")); + sprintf(tmpbuf, + "pid:%lx;parent-pid:%lx;vendor:Ant;ostype:wasi;arch:wasm32;" + "triple:%s;endian:little;ptrsize:4;", + pid, pid, triple); + + write_packet(server, tmpbuf); + } + if (!strcmp(name, "RegisterInfo0")) { + sprintf( + tmpbuf, + "name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;" + "set:General Purpose Registers;gcc:16;dwarf:16;generic:pc;"); + write_packet(server, tmpbuf); + } + else if (!strncmp(name, "RegisterInfo", strlen("RegisterInfo"))) { + write_packet(server, "E45"); + } + if (!strcmp(name, "StructuredDataPlugins")) { + write_packet(server, ""); + } + + if (!strcmp(name, "MemoryRegionInfo")) { + uint64_t addr = strtol(args, NULL, 16); + WASMDebugMemoryInfo *mem_info = wasm_debug_instance_get_memregion( + (WASMDebugInstance *)server->thread->debug_instance, addr); + if (mem_info) { + char name[256]; + mem2hex(mem_info->name, name, strlen(mem_info->name)); + sprintf(tmpbuf, "start:%lx;size:%lx;permissions:%s;name:%s;", + (uint64)mem_info->start, mem_info->size, mem_info->permisson, name); + write_packet(server, tmpbuf); + wasm_debug_instance_destroy_memregion( + (WASMDebugInstance *)server->thread->debug_instance, mem_info); + } + } + + if (!strcmp(name, "WasmData")) { + + } + + if (!strcmp(name, "WasmMem")) { + + } + + if (!strcmp(name, "Symbol")) { + write_packet(server, ""); + } + + if (!strcmp(name, "WasmCallStack")) { + uint64_t tid = strtol(args, NULL, 16); + uint64_t buf[1024 / sizeof(uint64_t)]; + uint64_t count = wasm_debug_instance_get_call_stack_pcs( + (WASMDebugInstance *)server->thread->debug_instance, tid, buf, + 1024 / sizeof(uint64_t)); + if (count > 0) { + mem2hex((char *)buf, tmpbuf, count * sizeof(uint64_t)); + write_packet(server, tmpbuf); + } + else + write_packet(server, ""); + } + + if (!strcmp(name, "WasmLocal")) { + porcess_wasm_local(server, args); + } + + if (!strcmp(name, "WasmGlobal")) { + porcess_wasm_global(server, args); + } +} + +static void +send_thread_stop_status(WASMGDBServer *server, uint32_t status, uint64_t tid) +{ + int tids_number, len = 0, i = 0; + uint64_t tids[20]; + char pc_string[17]; + uint32_t gdb_status = status; + + if (status == 0) { + sprintf(tmpbuf, "W%02x", status); + write_packet(server, tmpbuf); + return; + } + tids_number = wasm_debug_instance_get_tids( + (WASMDebugInstance *)server->thread->debug_instance, tids, 20); + uint64_t pc = wasm_debug_instance_get_pc( + (WASMDebugInstance *)server->thread->debug_instance); + + if (status == WAMR_SIG_SINGSTEP) { + gdb_status = WAMR_SIG_TRAP; + } + + //TODO: how name a wasm thread? + len += sprintf(tmpbuf, "T%02xthread:%lx;name:%s;", gdb_status, tid, "nobody"); + if (tids_number > 0) { + len += sprintf(tmpbuf + len, "threads:"); + while (i < tids_number) { + if (i == tids_number - 1) + len += sprintf(tmpbuf + len, "%lx;", tids[i]); + else + len += sprintf(tmpbuf + len, "%lx,", tids[i]); + i++; + } + } + mem2hex((void *)&pc, pc_string, 8); + pc_string[8 * 2] = '\0'; + + if (status == WAMR_SIG_TRAP) { + len += sprintf(tmpbuf + len, "thread-pcs:%lx;00:%s,reason:%s;", pc, + pc_string, "breakpoint"); + } + else if (status == WAMR_SIG_SINGSTEP) { + len += sprintf(tmpbuf + len, "thread-pcs:%lx;00:%s,reason:%s;", pc, + pc_string, "trace"); + } + else if (status > 0) { + len += sprintf(tmpbuf + len, "thread-pcs:%lx;00:%s,reason:%s;", pc, + pc_string, "signal"); + } + write_packet(server, tmpbuf); +} + +void +handle_v_packet(WASMGDBServer *server, char *payload) +{ + const char *name; + char *args; + uint32_t status; + args = strchr(payload, ';'); + if (args) + *args++ = '\0'; + name = payload; + LOG_VERBOSE("%s:%s\n", __FUNCTION__, payload); + + if (!strcmp("Cont?", name)) + write_packet(server, "vCont;c;C;s;S;"); + + if (!strcmp("Cont", name)) { + if (args[0] == 's') { + char *numstring = strchr(args, ':'); + if (numstring) { + *numstring++ = '\0'; + uint64_t tid = strtol(numstring, NULL, 16); + wasm_debug_instance_set_cur_thread( + (WASMDebugInstance *)server->thread->debug_instance, tid); + wasm_debug_instance_singlestep( + (WASMDebugInstance *)server->thread->debug_instance, tid); + tid = wasm_debug_instance_wait_thread( + (WASMDebugInstance *)server->thread->debug_instance, tid, + &status); + send_thread_stop_status(server, status, tid); + } + } + } +} + +void +handle_threadstop_request(WASMGDBServer *server, char *payload) +{ + uint64_t tid = wasm_debug_instance_get_tid( + (WASMDebugInstance *)server->thread->debug_instance); + uint32_t status; + + tid = wasm_debug_instance_wait_thread( + (WASMDebugInstance *)server->thread->debug_instance, tid, &status); + + send_thread_stop_status(server, status, tid); +} + +void +handle_set_current_thread(WASMGDBServer *server, char *payload) +{ + LOG_VERBOSE("%s:%s\n", __FUNCTION__, payload, payload); + if ('g' == *payload++) { + uint64_t tid; + tid = strtol(payload, NULL, 16); + if (tid > 0) + wasm_debug_instance_set_cur_thread( + (WASMDebugInstance *)server->thread->debug_instance, tid); + } + write_packet(server, "OK"); +} + +void +handle_get_register(WASMGDBServer *server, char *payload) +{ + int i = strtol(payload, NULL, 16); + + if (i != 0) { + write_packet(server, "E01"); + return; + } + uint64_t regdata = wasm_debug_instance_get_pc( + (WASMDebugInstance *)server->thread->debug_instance); + mem2hex((void *)®data, tmpbuf, 8); + tmpbuf[8 * 2] = '\0'; + write_packet(server, tmpbuf); +} + +void +handle_get_json_request(WASMGDBServer *server, char *payload) +{ + char *args; + + args = strchr(payload, ':'); + if (args) + *args++ = '\0'; + write_packet(server, ""); +} + +void +handle_get_read_binary_memory(WASMGDBServer *server, char *payload) +{ + write_packet(server, ""); +} + +void +handle_get_read_memory(WASMGDBServer *server, char *payload) +{ + size_t maddr, mlen; + bool ret; + + sprintf(tmpbuf, "%s", ""); + if (sscanf(payload, "%zx,%zx", &maddr, &mlen) == 2) { + if (mlen * 2 > MAX_PACKET_SIZE) { + LOG_ERROR("Buffer overflow!"); + mlen = MAX_PACKET_SIZE / 2; + } + char *buff = wasm_runtime_malloc(mlen); + if (buff) { + ret = wasm_debug_instance_get_mem( + (WASMDebugInstance *)server->thread->debug_instance, maddr, buff, + &mlen); + if (ret) { + mem2hex(buff, tmpbuf, mlen); + } + wasm_runtime_free(buff); + } + } + write_packet(server, tmpbuf); +} + +void +handle_get_write_memory(WASMGDBServer *server, char *payload) +{ + size_t maddr, mlen, hex_len; + int offset, act_len; + char *buff; + bool ret; + + sprintf(tmpbuf, "%s", ""); + if (sscanf(payload, "%zx,%zx:%n", &maddr, &mlen, &offset) == 2) { + payload += offset; + hex_len = strlen(payload); + act_len = hex_len / 2 < mlen ? hex_len / 2 : mlen; + buff = wasm_runtime_malloc(act_len); + if (buff) { + hex2mem(payload, buff, act_len); + ret = wasm_debug_instance_set_mem( + (WASMDebugInstance *)server->thread->debug_instance, maddr, buff, + &mlen); + if (ret) { + sprintf(tmpbuf, "%s", "OK"); + } + wasm_runtime_free(buff); + } + } + write_packet(server, tmpbuf); +} + +void +handle_add_break(WASMGDBServer *server, char *payload) +{ + size_t type, addr, length; + + if (sscanf(payload, "%zx,%zx,%zx", &type, &addr, &length) == 3) { + if (type == eBreakpointSoftware) { + bool ret = wasm_debug_instance_add_breakpoint( + (WASMDebugInstance *)server->thread->debug_instance, addr, + length); + if (ret) + write_packet(server, "OK"); + else + write_packet(server, "E01"); + return; + } + } + write_packet(server, ""); +} + +void +handle_remove_break(WASMGDBServer *server, char *payload) +{ + size_t type, addr, length; + + if (sscanf(payload, "%zx,%zx,%zx", &type, &addr, &length) == 3) { + if (type == eBreakpointSoftware) { + bool ret = wasm_debug_instance_remove_breakpoint( + (WASMDebugInstance *)server->thread->debug_instance, addr, + length); + if (ret) + write_packet(server, "OK"); + else + write_packet(server, "E01"); + return; + } + } + write_packet(server, ""); +} + +void +handle_continue_request(WASMGDBServer *server, char *payload) +{ + uint64_t tid; + uint32_t status; + + wasm_debug_instance_continue( + (WASMDebugInstance *)server->thread->debug_instance); + + tid = wasm_debug_instance_get_tid( + (WASMDebugInstance *)server->thread->debug_instance); + + tid = wasm_debug_instance_wait_thread( + (WASMDebugInstance *)server->thread->debug_instance, tid, &status); + + send_thread_stop_status(server, status, tid); +} + +void +handle_kill_request(WASMGDBServer *server, char *payload) +{ + uint64_t tid; + uint32_t status; + + wasm_debug_instance_kill( + (WASMDebugInstance *)server->thread->debug_instance); + + tid = wasm_debug_instance_get_tid( + (WASMDebugInstance *)server->thread->debug_instance); + + tid = wasm_debug_instance_wait_thread( + (WASMDebugInstance *)server->thread->debug_instance, tid, &status); + + send_thread_stop_status(server, status, tid); +} + +static void +handle_malloc(WASMGDBServer *server, char *payload) +{ + char *args; + uint64_t size; + int map_port = MMAP_PROT_NONE; + uint64_t addr; + + sprintf(tmpbuf, "%s", "E03"); + + args = strstr(payload, ","); + if (args) + *args++ = '\0'; + + size = strtol(payload, NULL, 16); + if (size > 0) { + while (*args) { + if (*args == 'r') { + map_port |= MMAP_PROT_READ; + } + if (*args == 'w') { + map_port |= MMAP_PROT_WRITE; + } + if (*args == 'x') { + map_port |= MMAP_PROT_EXEC; + } + args++; + } + addr = wasm_debug_instance_mmap( + (WASMDebugInstance *)server->thread->debug_instance, size, map_port); + if (addr) { + sprintf(tmpbuf, "%lx", addr); + } + } + write_packet(server, tmpbuf); +} + +static void +handle_free(WASMGDBServer *server, char *payload) +{ + uint64_t addr; + bool ret; + + sprintf(tmpbuf, "%s", "E03"); + addr = strtol(payload, NULL, 16); + + ret = wasm_debug_instance_ummap( + (WASMDebugInstance *)server->thread->debug_instance, addr); + if (ret) { + sprintf(tmpbuf, "%s", "OK"); + } + write_packet(server, tmpbuf); +} + +void +handle____request(WASMGDBServer *server, char *payload) +{ + char *args; + + if (payload[0] == 'M') { + args = payload + 1; + handle_malloc(server, args); + } + if (payload[0] == 'm') { + args = payload + 1; + handle_free(server, args); + } +} diff --git a/core/iwasm/libraries/debug-engine/handler.h b/core/iwasm/libraries/debug-engine/handler.h new file mode 100644 index 00000000..90525a98 --- /dev/null +++ b/core/iwasm/libraries/debug-engine/handler.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifndef HANDLER_H +#define HANDLER_H + +#include "gdbserver.h" + +void +handle_generay_set(WASMGDBServer *server, char *payload); + +void +handle_generay_query(WASMGDBServer *server, char *payload); + +void +handle_v_packet(WASMGDBServer *server, char *payload); + +void +handle_threadstop_request(WASMGDBServer *server, char *payload); + +void +handle_set_current_thread(WASMGDBServer *server, char *payload); + +void +handle_get_register(WASMGDBServer *server, char *payload); + +void +handle_get_json_request(WASMGDBServer *server, char *payload); + +void +handle_get_read_binary_memory(WASMGDBServer *server, char *payload); + +void +handle_get_read_memory(WASMGDBServer *server, char *payload); + +void +handle_get_write_memory(WASMGDBServer *server, char *payload); + +void +handle_add_break(WASMGDBServer *server, char *payload); + +void +handle_remove_break(WASMGDBServer *server, char *payload); + +void +handle_continue_request(WASMGDBServer *server, char *payload); + +void +handle_kill_request(WASMGDBServer *server, char *payload); + +void +handle____request(WASMGDBServer *server, char *payload); +#endif diff --git a/core/iwasm/libraries/debug-engine/packets.c b/core/iwasm/libraries/debug-engine/packets.c new file mode 100644 index 00000000..ca7d3c37 --- /dev/null +++ b/core/iwasm/libraries/debug-engine/packets.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "packets.h" + +#include + +#include "bh_log.h" +#include "gdbserver.h" + +void +pktbuf_insert(WASMGDBServer *gdbserver, const uint8_t *buf, ssize_t len) +{ + WasmDebugPacket *pkt = &gdbserver->pkt; + + if ((unsigned long)(pkt->end + len) >= sizeof(pkt->buf)) { + LOG_ERROR("Packet buffer overflow"); + exit(-2); + } + memcpy(pkt->buf + pkt->end, buf, len); + pkt->end += len; +} + +void +pktbuf_erase_head(WASMGDBServer *gdbserver, ssize_t end) +{ + WasmDebugPacket *pkt = &gdbserver->pkt; + memmove(pkt->buf, pkt->buf + end, pkt->end - end); + pkt->end -= end; +} + +void +inbuf_erase_head(WASMGDBServer *gdbserver, ssize_t end) +{ + pktbuf_erase_head(gdbserver, end); +} + +void +pktbuf_clear(WASMGDBServer *gdbserver) +{ + WasmDebugPacket *pkt = &gdbserver->pkt; + pkt->end = 0; +} + +int +read_data_once(WASMGDBServer *gdbserver) +{ + ssize_t nread; + uint8_t buf[4096]; + + nread = read(gdbserver->socket_fd, buf, sizeof(buf)); + if (nread <= 0) { + LOG_ERROR("Connection closed"); + return -1; + } + pktbuf_insert(gdbserver, buf, nread); + return nread; +} + +void +write_data_raw(WASMGDBServer *gdbserver, const uint8_t *data, ssize_t len) +{ + ssize_t nwritten; + + nwritten = write(gdbserver->socket_fd, data, len); + if (nwritten < 0) { + LOG_ERROR("Write error\n"); + exit(-2); + } +} + +void +write_hex(WASMGDBServer *gdbserver, unsigned long hex) +{ + char buf[32]; + size_t len; + + len = snprintf(buf, sizeof(buf) - 1, "%02lx", hex); + write_data_raw(gdbserver, (uint8_t *)buf, len); +} + +void +write_packet_bytes(WASMGDBServer *gdbserver, + const uint8_t *data, + size_t num_bytes) +{ + uint8_t checksum; + size_t i; + + write_data_raw(gdbserver, (uint8_t *)"$", 1); + for (i = 0, checksum = 0; i < num_bytes; ++i) + checksum += data[i]; + write_data_raw(gdbserver, (uint8_t *)data, num_bytes); + write_data_raw(gdbserver, (uint8_t *)"#", 1); + write_hex(gdbserver, checksum); +} + +void +write_packet(WASMGDBServer *gdbserver, const char *data) +{ + LOG_VERBOSE("send replay:%s", data); + write_packet_bytes(gdbserver, (const uint8_t *)data, strlen(data)); +} + +void +write_binary_packet(WASMGDBServer *gdbserver, + const char *pfx, + const uint8_t *data, + ssize_t num_bytes) +{ + uint8_t *buf; + ssize_t pfx_num_chars = strlen(pfx); + ssize_t buf_num_bytes = 0; + int i; + + buf = malloc(2 * num_bytes + pfx_num_chars); + memcpy(buf, pfx, pfx_num_chars); + buf_num_bytes += pfx_num_chars; + + for (i = 0; i < num_bytes; ++i) { + uint8_t b = data[i]; + switch (b) { + case '#': + case '$': + case '}': + case '*': + buf[buf_num_bytes++] = '}'; + buf[buf_num_bytes++] = b ^ 0x20; + break; + default: + buf[buf_num_bytes++] = b; + break; + } + } + write_packet_bytes(gdbserver, buf, buf_num_bytes); + free(buf); +} + +bool +skip_to_packet_start(WASMGDBServer *gdbserver) +{ + ssize_t end = -1; + + for (size_t i = 0; i < gdbserver->pkt.end; ++i) + if (gdbserver->pkt.buf[i] == '$') { + end = i; + break; + } + + if (end < 0) { + pktbuf_clear(gdbserver); + return false; + } + + pktbuf_erase_head(gdbserver, end); + bh_assert(1 <= gdbserver->pkt.end); + bh_assert('$' == gdbserver->pkt.buf[0]); + return true; +} + +bool +read_packet(WASMGDBServer *gdbserver) +{ + while (!skip_to_packet_start(gdbserver)) { + if(read_data_once(gdbserver) < 0) + return false; + } + if (!gdbserver->noack) + write_data_raw(gdbserver, (uint8_t *)"+", 1); + return true; +} diff --git a/core/iwasm/libraries/debug-engine/packets.h b/core/iwasm/libraries/debug-engine/packets.h new file mode 100644 index 00000000..ecc6978c --- /dev/null +++ b/core/iwasm/libraries/debug-engine/packets.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifndef PACKETS_H +#define PACKETS_H + +#include +#include +#include "gdbserver.h" + +bool +read_packet(WASMGDBServer *gdbserver); + +void +write_packet(WASMGDBServer *gdbserver, const char *data); + +void +inbuf_erase_head(WASMGDBServer *gdbserver, ssize_t end); + +#endif diff --git a/core/iwasm/libraries/debug-engine/utils.c b/core/iwasm/libraries/debug-engine/utils.c new file mode 100644 index 00000000..497dbd2d --- /dev/null +++ b/core/iwasm/libraries/debug-engine/utils.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "utils.h" + +int +hex(char ch) +{ + if ((ch >= 'a') && (ch <= 'f')) + return (ch - 'a' + 10); + if ((ch >= '0') && (ch <= '9')) + return (ch - '0'); + if ((ch >= 'A') && (ch <= 'F')) + return (ch - 'A' + 10); + return (-1); +} + +char * +mem2hex(char *mem, char *buf, int count) +{ + unsigned char ch; + + for (int i = 0; i < count; i++) { + ch = *(mem++); + *buf++ = hexchars[ch >> 4]; + *buf++ = hexchars[ch % 16]; + } + *buf = 0; + return (buf); +} + +char * +hex2mem(char *buf, char *mem, int count) +{ + unsigned char ch; + + for (int i = 0; i < count; i++) { + ch = hex(*buf++) << 4; + ch = ch + hex(*buf++); + *(mem++) = ch; + } + return (mem); +} diff --git a/core/iwasm/libraries/debug-engine/utils.h b/core/iwasm/libraries/debug-engine/utils.h new file mode 100644 index 00000000..cbf5819e --- /dev/null +++ b/core/iwasm/libraries/debug-engine/utils.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 Ant Group. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifndef UTILS_H +#define UTILS_H + +static const char hexchars[] = "0123456789abcdef"; + +int +hex(char ch); + +char * +mem2hex(char *mem, char *buf, int count); + +char * +hex2mem(char *buf, char *mem, int count); + +int +unescape(char *msg, int len); + +#endif /* UTILS_H */ diff --git a/core/iwasm/libraries/thread-mgr/thread_manager.c b/core/iwasm/libraries/thread-mgr/thread_manager.c index 4ee917a8..6a8b8fd3 100644 --- a/core/iwasm/libraries/thread-mgr/thread_manager.c +++ b/core/iwasm/libraries/thread-mgr/thread_manager.c @@ -484,6 +484,119 @@ fail1: return -1; } +#if WASM_ENABLE_DEBUG_INTERP != 0 +WASMCurrentEnvStatus * +wasm_cluster_create_exenv_status() +{ + WASMCurrentEnvStatus *status; + + if (!(status = wasm_runtime_malloc(sizeof(WASMCurrentEnvStatus)))) { + goto fail; + } + if (os_mutex_init(&status->wait_lock) != 0) + goto fail1; + + if (os_cond_init(&status->wait_cond) != 0) + goto fail2; + status->step_count = 0; + status->signal_flag = 0; + status->running_status = 0; + return status; + +fail2: + os_mutex_destroy(&status->wait_lock); +fail1: + wasm_runtime_free(status); +fail: + return NULL; +} + +void +wasm_cluster_destroy_exenv_status(WASMCurrentEnvStatus *status) +{ + os_mutex_destroy(&status->wait_lock); + os_cond_destroy(&status->wait_cond); + wasm_runtime_free(status); +} + +inline static bool +wasm_cluster_thread_is_running(WASMExecEnv *exec_env) { + return exec_env->current_status->running_status == STATUS_RUNNING + || exec_env->current_status->running_status == STATUS_STEP; +} + +void +wasm_cluster_clear_thread_signal(WASMExecEnv *exec_env) +{ + exec_env->current_status->signal_flag = 0; +} + +void +wasm_cluster_wait_thread_status(WASMExecEnv *exec_env, uint32 * status) +{ + os_mutex_lock(&exec_env->current_status->wait_lock); + while (wasm_cluster_thread_is_running(exec_env)) { + os_cond_wait(&exec_env->current_status->wait_cond, + &exec_env->current_status->wait_lock); + } + *status = exec_env->current_status->signal_flag; + os_mutex_unlock(&exec_env->current_status->wait_lock); +} + +void +wasm_cluster_thread_send_signal(WASMExecEnv *exec_env, uint32 signo) +{ + exec_env->current_status->signal_flag = signo; +} + +void +wasm_cluster_thread_stopped(WASMExecEnv *exec_env) +{ + exec_env->current_status->running_status = STATUS_STOP; + os_cond_signal(&exec_env->current_status->wait_cond); +} + +void +wasm_cluster_thread_waiting_run(WASMExecEnv *exec_env) +{ + os_mutex_lock(&exec_env->wait_lock); + while (!wasm_cluster_thread_is_running(exec_env)) { + os_cond_wait(&exec_env->wait_cond, &exec_env->wait_lock); + } + os_mutex_unlock(&exec_env->wait_lock); +} + +void +wasm_cluster_send_signal_all(WASMCluster *cluster, uint32 signo) +{ + WASMExecEnv *exec_env = bh_list_first_elem(&cluster->exec_env_list); + while (exec_env) { + wasm_cluster_thread_send_signal(exec_env, signo); + exec_env = bh_list_elem_next(exec_env); + } +} + +void wasm_cluster_thread_exited(WASMExecEnv *exec_env) +{ + exec_env->current_status->running_status = STATUS_EXIT; + os_cond_signal(&exec_env->current_status->wait_cond); + +} + +void wasm_cluster_thread_continue(WASMExecEnv *exec_env) +{ + wasm_cluster_clear_thread_signal(exec_env); + exec_env->current_status->running_status = STATUS_RUNNING; + os_cond_signal(&exec_env->wait_cond); +} + +void wasm_cluster_thread_step(WASMExecEnv *exec_env) +{ + exec_env->current_status->running_status = STATUS_STEP; + os_cond_signal(&exec_env->wait_cond); +} +#endif + int32 wasm_cluster_join_thread(WASMExecEnv *exec_env, void **ret_val) { @@ -520,7 +633,10 @@ wasm_cluster_exit_thread(WASMExecEnv *exec_env, void *retval) cluster = wasm_exec_env_get_cluster(exec_env); bh_assert(cluster); - +#if WASM_ENABLE_DEBUG_INTERP != 0 + wasm_cluster_clear_thread_signal(exec_env); + wasm_cluster_thread_exited(exec_env); +#endif /* App exit the thread, free the resources before exit native thread */ /* Free aux stack space */ free_aux_stack(cluster, exec_env->aux_stack_bottom.bottom); @@ -537,8 +653,13 @@ int32 wasm_cluster_cancel_thread(WASMExecEnv *exec_env) { /* Set the termination flag */ +#if WASM_ENABLE_DEBUG_INTERP != 0 + wasm_cluster_thread_send_signal(exec_env, WAMR_SIG_TERM); + wasm_cluster_thread_exited(exec_env); +#else exec_env->suspend_flags.flags |= 0x01; - return 0; +#endif + return 0; } static void @@ -621,6 +742,7 @@ void wasm_cluster_resume_thread(WASMExecEnv *exec_env) { exec_env->suspend_flags.flags &= ~0x02; + os_cond_signal(&exec_env->wait_cond); } static void diff --git a/core/iwasm/libraries/thread-mgr/thread_manager.h b/core/iwasm/libraries/thread-mgr/thread_manager.h index 392ebe4a..64e05c5e 100644 --- a/core/iwasm/libraries/thread-mgr/thread_manager.h +++ b/core/iwasm/libraries/thread-mgr/thread_manager.h @@ -15,7 +15,63 @@ #ifdef __cplusplus extern "C" { #endif +#if WASM_ENABLE_DEBUG_INTERP != 0 +#define WAMR_SIG_TRAP (5) +#define WAMR_SIG_STOP (19) +#define WAMR_SIG_TERM (15) +#define WAMR_SIG_SINGSTEP (0x1ff) +#define STATUS_RUNNING (0) +#define STATUS_STOP (1) +#define STATUS_EXIT (2) +#define STATUS_STEP (3) + +#define IS_WAMR_TERM_SIG(signo) \ + ((signo) == WAMR_SIG_TERM) + +#define IS_WAMR_STOP_SIG(signo) \ + ((signo) == WAMR_SIG_STOP || (signo) == WAMR_SIG_TRAP) + +typedef struct WASMCurrentEnvStatus +{ + uint64 signal_flag:32; + uint64 step_count:16; + uint64 running_status:16; + korp_mutex wait_lock; + korp_cond wait_cond; +}WASMCurrentEnvStatus; + +WASMCurrentEnvStatus * +wasm_cluster_create_exenv_status(); + +void +wasm_cluster_destroy_exenv_status(WASMCurrentEnvStatus *status); + +void +wasm_cluster_send_signal_all(WASMCluster *cluster, uint32 signo); + +void +wasm_cluster_thread_stopped(WASMExecEnv *exec_env); + +void +wasm_cluster_thread_waiting_run(WASMExecEnv *exec_env); + +void +wasm_cluster_wait_thread_status(WASMExecEnv *exec_env, uint32 * status); + +void +wasm_cluster_thread_exited(WASMExecEnv *exec_env); + +void +wasm_cluster_thread_continue(WASMExecEnv *exec_env); + +void +wasm_cluster_thread_send_signal(WASMExecEnv *exec_env, uint32 signo); + +void +wasm_cluster_thread_step(WASMExecEnv *exec_env); + +#endif typedef struct WASMCluster { struct WASMCluster *next; diff --git a/doc/source_debugging.md b/doc/source_debugging.md new file mode 100644 index 00000000..1543ad5d --- /dev/null +++ b/doc/source_debugging.md @@ -0,0 +1,86 @@ +# WAMR source debugging + +WAMR supports source level debugging based on DWARF (normally used in C/C++/Rust), source map (normally used in AssemblyScript) is not supported. + +## Build wasm application with debug information +To debug your application, you need to compile them with debug information. You can use `-g` option when compiling the source code if you are using wasi-sdk (also work for emcc and rustc): +``` bash +/opt/wasi-sdk/bin/clang -g test.c -o test.wasm +``` + +Then you will get `test.wasm` which is a WebAssembly module with embedded DWARF sections. Further, you can use `llvm-dwarfdump` to check if the generated wasm file contains DWARF information: +``` bash +llvm-dwarfdump-12 test.wasm +``` + +## Debugging with interpreter + +1. Build iwasm with source debugging feature +``` bash +cd ${WAMR_ROOT}/product-mini/platforms/linux +mkdir build && cd build +cmake .. -DWAMR_BUILD_DEBUG_INTERP=1 +make +``` + +2. Execute iwasm with debug engine enabled +``` bash +iwasm -g=127.0.0.1:1234 test.wasm +``` + +3. Build customized lldb (assume you have already built llvm) +``` bash +cd ${WAMR_ROOT}/core/deps/llvm +git apply ../../../../build-scripts/lldb-wasm.patch +mkdir build && cd build +cmake ../llvm -DLLVM_ENABLE_PROJECTS="clang,lldb" -DLLVM_TARGETS_TO_BUILD:STRING="X86;WebAssembly" +make -j $(nproc) +``` + +4. Launch customized lldb and connect to iwasm +``` bash +lldb +(lldb) process connect -p wasm connect://127.0.0.1:1234 +``` +Then you can use lldb commands to debug your applications. Please refer to [lldb document](https://lldb.llvm.org/use/tutorial.html) for command usage. + +> Known issue: `step over` on some function may be treated as `step in`, it will be fixed later. + +## Debugging with AoT + +> Note: AoT debugging is experimental and only a few debugging capabilities are supported. + +1. Build lldb (assume you have already built llvm) +``` bash +cd ${WAMR_ROOT}/core/deps/llvm/build +cmake . -DLLVM_ENABLE_PROJECTS="clang;lldb" +make -j $(nproc) +``` + +2. Build wamrc with debugging feature +``` bash +cd ${WAMR_ROOT}/wamr-compiler +mkdir build && cd build +cmake .. -DWAMR_BUILD_DEBUG_AOT=1 +make -j $(nproc) +``` + +3. Build iwasm with debugging feature +``` bash +cd ${WAMR_ROOT}/product-mini/platforms/linux +mkdir build && cd build +cmake .. -DWAMR_BUILD_DEBUG_AOT=1 +make +``` + +4. Compile wasm module to AoT module +``` bash +wamrc -o test.aot test.wasm +``` + +5. Execute iwasm using lldb +``` bash +lldb-12 iwasm -- test.aot +``` + +Then you can use lldb commands to debug both wamr runtime and your wasm application in ***current terminal*** \ No newline at end of file diff --git a/product-mini/platforms/darwin/CMakeLists.txt b/product-mini/platforms/darwin/CMakeLists.txt index 6c384563..d4148a07 100644 --- a/product-mini/platforms/darwin/CMakeLists.txt +++ b/product-mini/platforms/darwin/CMakeLists.txt @@ -82,6 +82,17 @@ if (NOT DEFINED WAMR_BUILD_SIMD) set (WAMR_BUILD_SIMD 1) endif () +if (NOT DEFINED WAMR_BUILD_DEBUG_INTERP) + # Disable Debug feature by default + set (WAMR_BUILD_DEBUG_INTERP 0) +endif () + +if (WAMR_BUILD_DEBUG_INTERP EQUAL 1) + set (WAMR_BUILD_FAST_INTERP 0) + set (WAMR_BUILD_MINI_LOADER 0) + set (WAMR_BUILD_SIMD 0) +endif () + set (CMAKE_SHARED_LINKER_FLAGS "-Wl,-U,_get_ext_lib_export_apis") set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") diff --git a/product-mini/platforms/linux/CMakeLists.txt b/product-mini/platforms/linux/CMakeLists.txt index bfc01be1..7692d610 100644 --- a/product-mini/platforms/linux/CMakeLists.txt +++ b/product-mini/platforms/linux/CMakeLists.txt @@ -90,6 +90,17 @@ if (NOT DEFINED WAMR_BUILD_REF_TYPES) set (WAMR_BUILD_REF_TYPES 0) endif () +if (NOT DEFINED WAMR_BUILD_DEBUG_INTERP) + # Disable Debug feature by default + set (WAMR_BUILD_DEBUG_INTERP 0) +endif () + +if (WAMR_BUILD_DEBUG_INTERP EQUAL 1) + set (WAMR_BUILD_FAST_INTERP 0) + set (WAMR_BUILD_MINI_LOADER 0) + set (WAMR_BUILD_SIMD 0) +endif () + if (COLLECT_CODE_COVERAGE EQUAL 1) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage") endif () diff --git a/product-mini/platforms/posix/main.c b/product-mini/platforms/posix/main.c index ccc053c7..de241751 100644 --- a/product-mini/platforms/posix/main.c +++ b/product-mini/platforms/posix/main.c @@ -49,6 +49,9 @@ print_help() #endif #if WASM_ENABLE_LIB_PTHREAD != 0 printf(" --max-threads=n Set maximum thread number per cluster, default is 4\n"); +#endif +#if WASM_ENABLE_DEBUG_INTERP != 0 + printf(" -g=ip:port Set the debug sever address, default is debug disabled\n"); #endif return 1; } @@ -241,6 +244,11 @@ main(int argc, char *argv[]) const char *env_list[8] = { NULL }; uint32 env_list_size = 0; #endif +#if WASM_ENABLE_DEBUG_INTERP != 0 + char * ip_addr = NULL; + //int platform_port = 0; + int instance_port = 0; +#endif /* Process options. */ for (argc--, argv++; argc > 0 && argv[0][0] == '-'; argc--, argv++) { @@ -321,6 +329,19 @@ main(int argc, char *argv[]) return print_help(); wasm_runtime_set_max_thread_num(atoi(argv[0] + 14)); } +#endif +#if WASM_ENABLE_DEBUG_INTERP != 0 + else if (!strncmp(argv[0], "-g=", 3)) { + char * port_str = strchr(argv[0] + 3, ':'); + char *port_end; + if (port_str == NULL) + return print_help(); + *port_str = '\0'; + instance_port = strtoul(port_str + 1, &port_end, 10); + if (port_str[1] == '\0' || *port_end != '\0') + return print_help(); + ip_addr = argv[0] + 3; + } #endif else return print_help(); @@ -346,6 +367,13 @@ main(int argc, char *argv[]) init_args.mem_alloc_option.allocator.free_func = free; #endif +#if WASM_ENABLE_DEBUG_INTERP != 0 + init_args.platform_port = 0; + init_args.instance_port = instance_port; + if (ip_addr) + strcpy(init_args.ip_addr, ip_addr); +#endif + /* initialize runtime environment */ if (!wasm_runtime_full_init(&init_args)) { printf("Init runtime environment failed.\n"); diff --git a/product-mini/platforms/vxworks/CMakeLists.txt b/product-mini/platforms/vxworks/CMakeLists.txt index 37e71d81..d681e921 100644 --- a/product-mini/platforms/vxworks/CMakeLists.txt +++ b/product-mini/platforms/vxworks/CMakeLists.txt @@ -59,6 +59,17 @@ if (NOT DEFINED WAMR_BUILD_LIBC_WASI) set (WAMR_BUILD_LIBC_WASI 0) endif () +if (NOT DEFINED WAMR_BUILD_DEBUG_INTERP) + # Disable Debug feature by default + set (WAMR_BUILD_DEBUG_INTERP 0) +endif () + +if (WAMR_BUILD_DEBUG_INTERP EQUAL 1) + set (WAMR_BUILD_FAST_INTERP 0) + set (WAMR_BUILD_MINI_LOADER 0) + set (WAMR_BUILD_SIMD 0) +endif () + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") set (WAMR_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..) diff --git a/wamr-compiler/CMakeLists.txt b/wamr-compiler/CMakeLists.txt index ada45cf7..70e59ed5 100644 --- a/wamr-compiler/CMakeLists.txt +++ b/wamr-compiler/CMakeLists.txt @@ -109,6 +109,9 @@ message ("-- CMAKE_BUILD_TYPE = " ${CMAKE_BUILD_TYPE}) if (CMAKE_BUILD_TYPE STREQUAL "Debug") add_definitions(-DBH_DEBUG=1) endif () +if (WAMR_BUILD_DEBUG_AOT EQUAL 1) + add_definitions(-DWASM_ENABLE_DEBUG_AOT=1) +endif() # Enable LLVM if (NOT WAMR_BUILD_WITH_CUSTOM_LLVM) @@ -133,6 +136,16 @@ add_definitions(${LLVM_DEFINITIONS}) message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") +if (WAMR_BUILD_DEBUG_AOT EQUAL 1) + if(LLVM_BUILD_MAIN_SRC_DIR) + include_directories(${LLVM_BUILD_MAIN_SRC_DIR}/../lldb/include) + include_directories(${LLVM_BUILD_BINARY_DIR}/tools/lldb/include) + endif() + link_directories(${LLVM_LIBRARY_DIRS}) + find_library(lib_lldb NAMES lldb HINTS ${LLVM_LIBRARY_DIRS}) + message(STATUS "find lldb ${LLDB_ALL_PLUGINS} in: ${LLVM_LIBRARY_DIRS}") +endif() + if (NOT (CMAKE_C_COMPILER MATCHES ".*clang.*" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang")) if(NOT MSVC) set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") @@ -212,7 +225,7 @@ add_library (aotclib ${IWASM_COMPL_SOURCE}) add_executable (wamrc main.c) if (NOT MSVC) - target_link_libraries (wamrc aotclib vmlib LLVMDemangle ${LLVM_AVAILABLE_LIBS} -lm -ldl -lpthread) + target_link_libraries (wamrc aotclib vmlib LLVMDemangle ${LLVM_AVAILABLE_LIBS} -lm -ldl -lpthread ${lib_lldb}) else() - target_link_libraries (wamrc aotclib vmlib ${LLVM_AVAILABLE_LIBS}) + target_link_libraries (wamrc aotclib vmlib ${lib_lldb} ${LLVM_AVAILABLE_LIBS}) endif() diff --git a/wamr-compiler/main.c b/wamr-compiler/main.c index 465151a2..c57eff2d 100644 --- a/wamr-compiler/main.c +++ b/wamr-compiler/main.c @@ -248,6 +248,12 @@ main(int argc, char *argv[]) goto fail3; } +#if WASM_ENABLE_DEBUG_AOT != 0 + if (!create_dwarf_extractor(comp_data, wasm_file_name)) { + printf("%s:create dwarf extractor failed\n", wasm_file_name); + } +#endif + bh_print_time("Begin to create compile context"); if (!(comp_ctx = aot_create_comp_context(comp_data,