diff --git a/lldb/include/lldb/Target/UnwindWasm.h b/lldb/include/lldb/Target/UnwindWasm.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Target/UnwindWasm.h @@ -0,0 +1,47 @@ +//===-- 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_TARGET_UNWINDWASM_H +#define LLDB_TARGET_UNWINDWASM_H + +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Unwind.h" +#include + +namespace lldb_private { + +class UnwindWasm : public Unwind { +public: + UnwindWasm(lldb_private::Thread &thread) + : Unwind(thread), m_frames(), m_unwind_complete(false) {} + ~UnwindWasm() override = default; + +protected: + 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; + + DISALLOW_COPY_AND_ASSIGN(UnwindWasm); +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_UNWINDWASM_H diff --git a/lldb/source/Core/Value.cpp b/lldb/source/Core/Value.cpp --- a/lldb/source/Core/Value.cpp +++ b/lldb/source/Core/Value.cpp @@ -30,6 +30,8 @@ #include "lldb/lldb-forward.h" #include "lldb/lldb-types.h" +#include "Plugins/Process/gdb-remote/ProcessGDBRemote.h" + #include #include @@ -563,8 +565,27 @@ Process *process = exe_ctx->GetProcessPtr(); if (process) { - const size_t bytes_read = - process->ReadMemory(address, dst, byte_size, error); + StackFrame *frame = exe_ctx->GetFramePtr(); + size_t bytes_read = 0; + + bool isWasm = + frame + ? (frame->CalculateTarget()->GetArchitecture().GetMachine() == + llvm::Triple::wasm32) + : false; + if (isWasm) { + int frame_index = frame->GetConcreteFrameIndex(); + process_gdb_remote::ProcessGDBRemote *wasm_process = + static_cast( + frame->CalculateProcess().get()); + if (wasm_process->WasmReadMemory(frame_index, address, dst, + byte_size)) { + bytes_read = byte_size; + } + } else { + bytes_read = process->ReadMemory(address, dst, byte_size, error); + } + if (bytes_read != byte_size) error.SetErrorStringWithFormat( "read memory from 0x%" PRIx64 " failed (%u of %u bytes read)", diff --git a/lldb/source/Expression/DWARFExpression.cpp b/lldb/source/Expression/DWARFExpression.cpp --- a/lldb/source/Expression/DWARFExpression.cpp +++ b/lldb/source/Expression/DWARFExpression.cpp @@ -36,6 +36,7 @@ #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" +#include "Plugins/Process/gdb-remote/ProcessGDBRemote.h" #include "Plugins/SymbolFile/DWARF/DWARFUnit.h" using namespace lldb; @@ -2483,6 +2484,66 @@ stack.back().SetValueType(Value::eValueTypeLoadAddress); } break; + case DW_OP_WASM_location: { + if (frame) { + const llvm::Triple::ArchType machine = + frame->CalculateTarget()->GetArchitecture().GetMachine(); + if (machine == llvm::Triple::wasm32) { + process_gdb_remote::ProcessGDBRemote *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 target architecture for " + "DW_OP_WASM_location opcode."); + return false; + } + } else { + if (error_ptr) + error_ptr->SetErrorString("Invalid stack frame in context for " + "DW_OP_WASM_location opcode."); + return false; + } + } break; + // OPCODE: DW_OP_addrx (DW_OP_GNU_addr_index is the legacy name.) // OPERANDS: 1 // ULEB128: index to the .debug_addr section diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -297,6 +297,17 @@ bool GetThreadStopInfo(lldb::tid_t tid, StringExtractorGDBRemote &response); + // WebAssembly-specific commands + bool GetWasmGlobal(int frame_index, int index, void *buf, size_t buffer_size, + size_t &size); + bool GetWasmLocal(int frame_index, int index, void *buf, size_t buffer_size, + size_t &size); + bool GetWasmStackValue(int frame_index, int index, void *buf, + size_t buffer_size, size_t &size); + bool WasmReadMemory(int frame_index, lldb::addr_t vm_addr, void *buf, + size_t buffer_size); + bool GetWasmCallStack(std::vector &call_stack_pcs); + bool SupportsGDBStoppointPacket(GDBStoppointType type) { switch (type) { case eBreakpointSoftware: @@ -357,6 +368,8 @@ bool GetQXferMemoryMapReadSupported(); + bool GetWasmFeaturesSupported(); + LazyBool SupportsAllocDeallocMemory() // const { // Uncomment this to have lldb pretend the debug server doesn't respond to @@ -552,6 +565,7 @@ LazyBool m_supports_jGetSharedCacheInfo; LazyBool m_supports_QPassSignals; LazyBool m_supports_error_string_reply; + LazyBool m_supports_wasm; bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1, m_supports_qUserName : 1, m_supports_qGroupName : 1, diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -199,6 +199,13 @@ return m_supports_qXfer_memory_map_read == eLazyBoolYes; } +bool GDBRemoteCommunicationClient::GetWasmFeaturesSupported() { + if (m_supports_wasm == eLazyBoolCalculate) { + GetRemoteQSupported(); + } + return m_supports_wasm == eLazyBoolYes; +} + uint64_t GDBRemoteCommunicationClient::GetRemoteMaxPacketSize() { if (m_max_packet_size == 0) { GetRemoteQSupported(); @@ -342,6 +349,7 @@ m_supports_augmented_libraries_svr4_read = eLazyBoolNo; m_supports_qXfer_features_read = eLazyBoolNo; m_supports_qXfer_memory_map_read = eLazyBoolNo; + m_supports_wasm = eLazyBoolNo; m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if // not, we assume no limit @@ -433,6 +441,11 @@ else m_supports_QPassSignals = eLazyBoolNo; + if (::strstr(response_cstr, "wasm+")) + m_supports_wasm = eLazyBoolYes; + else + m_supports_wasm = eLazyBoolNo; + const char *packet_size_str = ::strstr(response_cstr, "PacketSize="); if (packet_size_str) { StringExtractorGDBRemote packet_response(packet_size_str + @@ -2694,6 +2707,141 @@ return false; } +bool GDBRemoteCommunicationClient::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 (SendPacketAndWaitForResponse(packet.GetString(), response, false) != + 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 GDBRemoteCommunicationClient::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 (SendPacketAndWaitForResponse(packet.GetString(), response, false) != + 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 GDBRemoteCommunicationClient::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 (SendPacketAndWaitForResponse(packet.GetString(), response, false) != + 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 GDBRemoteCommunicationClient::WasmReadMemory(int frame_index, + lldb::addr_t addr, void *buf, + size_t buffer_size) { + char packet[64]; + int packet_len = ::snprintf( + packet, sizeof(packet), "qWasmMem:%d;%" PRIx64 ";%" PRIx64, frame_index, + static_cast(addr), static_cast(buffer_size)); + assert(packet_len + 1 < (int)sizeof(packet)); + UNUSED_IF_ASSERT_DISABLED(packet_len); + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse(packet, response, true) == + PacketResult::Success) { + if (response.IsNormalResponse()) { + return buffer_size == + response.GetHexBytes(llvm::MutableArrayRef( + static_cast(buf), buffer_size), + '\xdd'); + } + } + return false; +} + +bool GDBRemoteCommunicationClient::GetWasmCallStack( + std::vector &call_stack_pcs) { + call_stack_pcs.clear(); + StreamString packet; + packet.Printf("qWasmCallStack"); + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse(packet.GetString(), response, false) != + 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; +} + uint8_t GDBRemoteCommunicationClient::SendGDBStoppointTypePacket( GDBStoppointType type, bool insert, addr_t addr, uint32_t length) { Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_BREAKPOINTS)); diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -231,7 +231,26 @@ std::string HarmonizeThreadIdsForProfileData( StringExtractorGDBRemote &inputStringExtractor); + // Query remote GDBServer for Wasm information + + bool IsWasmProcess() const; + + bool GetWasmLocal(int frame_index, int index, void *buf, size_t buffer_size, + size_t &size); + + bool GetWasmGlobal(int frame_index, int index, void *buf, size_t buffer_size, + size_t &size); + + bool GetWasmStackValue(int frame_index, int index, void *buf, + size_t buffer_size, size_t &size); + + bool WasmReadMemory(int frame_index, lldb::addr_t addr, void *buf, + size_t buffer_size); + + bool GetWasmCallStack(std::vector &call_stack_pcs); + protected: + friend class UnwindWasm; friend class ThreadGDBRemote; friend class GDBRemoteCommunicationClient; friend class GDBRemoteRegisterContext; diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -2689,9 +2689,61 @@ } } +// WebAssembly features + +bool ProcessGDBRemote::IsWasmProcess() const { + return GetTarget().GetArchitecture().GetMachine() == llvm::Triple::wasm32; +} + +bool ProcessGDBRemote::GetWasmLocal(int frame_index, int index, void *buf, + size_t buffer_size, size_t &size) { + if (!m_gdb_comm.GetWasmFeaturesSupported()) + return false; + return GetGDBRemote().GetWasmLocal(frame_index, index, buf, buffer_size, + size); +} + +bool ProcessGDBRemote::GetWasmGlobal(int frame_index, int index, void *buf, + size_t buffer_size, size_t &size) { + if (!m_gdb_comm.GetWasmFeaturesSupported()) + return false; + return GetGDBRemote().GetWasmGlobal(frame_index, index, buf, buffer_size, + size); +} + +bool ProcessGDBRemote::GetWasmStackValue(int frame_index, int index, void *buf, + size_t buffer_size, size_t &size) { + if (!m_gdb_comm.GetWasmFeaturesSupported()) + return false; + return GetGDBRemote().GetWasmStackValue(frame_index, index, buf, buffer_size, + size); +} + +bool ProcessGDBRemote::WasmReadMemory(int frame_index, lldb::addr_t addr, + void *buf, size_t buffer_size) { + if (!m_gdb_comm.GetWasmFeaturesSupported()) + return false; + return GetGDBRemote().WasmReadMemory(frame_index, addr, buf, buffer_size); +} + +bool ProcessGDBRemote::GetWasmCallStack( + std::vector &call_stack_pcs) { + if (!m_gdb_comm.GetWasmFeaturesSupported()) + return false; + return GetGDBRemote().GetWasmCallStack(call_stack_pcs); +} + // Process Memory size_t ProcessGDBRemote::DoReadMemory(addr_t addr, void *buf, size_t size, Status &error) { + if (addr < 0x100000000 && IsWasmProcess() && + m_gdb_comm.GetWasmFeaturesSupported()) { + if (WasmReadMemory(0 /*frame_index*/, addr, buf, size)) { + return size; + } + return 0; + } + GetMaxMemorySize(); bool binary_memory_read = m_gdb_comm.GetxPacketSupported(); // M and m packets take 2 bytes for 1 byte of memory diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -68,6 +68,7 @@ UnixSignals.cpp UnwindAssembly.cpp UnwindLLDB.cpp + UnwindWasm.cpp LINK_LIBS lldbBreakpoint diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -43,6 +43,7 @@ #include "lldb/Target/ThreadPlanStepUntil.h" #include "lldb/Target/ThreadSpec.h" #include "lldb/Target/UnwindLLDB.h" +#include "lldb/Target/UnwindWasm.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/RegularExpression.h" #include "lldb/Utility/State.h" @@ -1853,8 +1854,13 @@ } Unwind &Thread::GetUnwinder() { - if (!m_unwinder_up) - m_unwinder_up.reset(new UnwindLLDB(*this)); + if (!m_unwinder_up) { + if (CalculateTarget()->GetArchitecture().GetMachine() == + llvm::Triple::wasm32) + m_unwinder_up.reset(new UnwindWasm(*this)); + else + m_unwinder_up.reset(new UnwindLLDB(*this)); + } return *m_unwinder_up; } diff --git a/lldb/source/Target/UnwindWasm.cpp b/lldb/source/Target/UnwindWasm.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Target/UnwindWasm.cpp @@ -0,0 +1,75 @@ +//===-- 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 "lldb/Target/UnwindWasm.h" +#include "lldb/Target/Thread.h" + +#include "Plugins/Process/gdb-remote/ProcessGDBRemote.h" +#include "Plugins/Process/gdb-remote/ThreadGDBRemote.h" + +using namespace lldb; +using namespace lldb_private; +using namespace process_gdb_remote; + +class WasmGDBRemoteRegisterContext : public GDBRemoteRegisterContext { +public: + WasmGDBRemoteRegisterContext(ThreadGDBRemote &thread, + uint32_t concrete_frame_idx, + GDBRemoteDynamicRegisterInfo ®_info, + uint64_t pc) + : GDBRemoteRegisterContext(thread, concrete_frame_idx, reg_info, 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()); + ProcessGDBRemote *gdb_process = + static_cast(thread->GetProcess().get()); + std::shared_ptr reg_ctx_sp = + std::make_shared( + *gdb_thread, frame->GetConcreteFrameIndex(), + gdb_process->m_register_info, m_frames[frame->GetFrameIndex()]); + return reg_ctx_sp; +} + +uint32_t UnwindWasm::DoGetFrameCount() { + if (!m_unwind_complete) { + m_unwind_complete = true; + m_frames.clear(); + + ProcessGDBRemote *gdb_process = + static_cast(GetThread().GetProcess().get()); + if (!gdb_process->GetWasmCallStack(m_frames)) + return 0; + } + 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