Jonas Devlieghere 074653aa53
[lldb] Fix llvm_unreachable for invalid Wasm address (#176464)
We had an llvm_unreachable following a switch on the WasmAddress's type.
However, the type is encoded in a larger 64 bit address, and therefore
it's possible to create an invalid value that doesn't map back on one of
the enum types.

We could try to diagnose that in the wrapper, or treat all invalid types
the same. I took the latter approach because it makes it easier to show
the invalid type after the fact in an error message.

rdar://168314695
2026-01-16 14:17:50 -08:00

172 lines
5.8 KiB
C++

//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "ProcessWasm.h"
#include "ThreadWasm.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/Value.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Target/UnixSignals.h"
using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::process_gdb_remote;
using namespace lldb_private::wasm;
LLDB_PLUGIN_DEFINE(ProcessWasm)
ProcessWasm::ProcessWasm(lldb::TargetSP target_sp, ListenerSP listener_sp)
: ProcessGDBRemote(target_sp, listener_sp) {
assert(target_sp);
// Wasm doesn't have any Unix-like signals as a platform concept, but pretend
// like it does to appease LLDB.
m_unix_signals_sp = UnixSignals::Create(target_sp->GetArchitecture());
}
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);
}
llvm::StringRef ProcessWasm::GetPluginName() { return GetPluginNameStatic(); }
llvm::StringRef ProcessWasm::GetPluginNameStatic() { return "wasm"; }
llvm::StringRef 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) {
if (crash_file_path == nullptr)
return std::make_shared<ProcessWasm>(target_sp, listener_sp);
return {};
}
bool ProcessWasm::CanDebug(lldb::TargetSP target_sp,
bool plugin_specified_by_name) {
if (plugin_specified_by_name)
return true;
if (Module *exe_module = target_sp->GetExecutableModulePointer()) {
if (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<ThreadGDBRemote> ProcessWasm::CreateThread(lldb::tid_t tid) {
return std::make_shared<ThreadWasm>(*this, tid);
}
size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
Status &error) {
wasm_addr_t wasm_addr(vm_addr);
switch (wasm_addr.GetType()) {
case WasmAddressType::Memory:
case WasmAddressType::Object:
return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error);
case WasmAddressType::Invalid:
break;
}
error.FromErrorStringWithFormatv(
"Wasm read failed for invalid address {0:x} (type = {1:x}, module = "
"{2:x}, offset = {3:x})",
vm_addr, wasm_addr.GetType(), wasm_addr.GetModuleID(),
wasm_addr.GetOffset());
return 0;
}
llvm::Expected<std::vector<lldb::addr_t>>
ProcessWasm::GetWasmCallStack(lldb::tid_t tid) {
StreamString packet;
packet.Printf("qWasmCallStack:");
packet.Printf("%" PRIx64, tid);
StringExtractorGDBRemote response;
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
GDBRemoteCommunication::PacketResult::Success)
return llvm::createStringError("failed to send qWasmCallStack");
if (!response.IsNormalResponse())
return llvm::createStringError("failed to get response for qWasmCallStack");
WritableDataBufferSP data_buffer_sp =
std::make_shared<DataBufferHeap>(response.GetStringRef().size() / 2, 0);
const size_t bytes = response.GetHexBytes(data_buffer_sp->GetData(), '\xcc');
if (bytes == 0 || bytes % sizeof(uint64_t) != 0)
return llvm::createStringError("invalid response for qWasmCallStack");
// To match the Wasm specification, the addresses are encoded in little endian
// byte order.
DataExtractor data(data_buffer_sp, lldb::eByteOrderLittle,
GetAddressByteSize());
lldb::offset_t offset = 0;
std::vector<lldb::addr_t> call_stack_pcs;
while (offset < bytes)
call_stack_pcs.push_back(data.GetU64(&offset));
return call_stack_pcs;
}
llvm::Expected<lldb::DataBufferSP>
ProcessWasm::GetWasmVariable(WasmVirtualRegisterKinds kind, int frame_index,
int index) {
StreamString packet;
switch (kind) {
case eWasmTagLocal:
packet.Printf("qWasmLocal:");
break;
case eWasmTagGlobal:
packet.Printf("qWasmGlobal:");
break;
case eWasmTagOperandStack:
packet.PutCString("qWasmStackValue:");
break;
case eWasmTagNotAWasmLocation:
return llvm::createStringError("not a Wasm location");
}
packet.Printf("%d;%d", frame_index, index);
StringExtractorGDBRemote response;
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
GDBRemoteCommunication::PacketResult::Success)
return llvm::createStringError("failed to send Wasm variable");
if (!response.IsNormalResponse())
return llvm::createStringError("failed to get response for Wasm variable");
WritableDataBufferSP buffer_sp(
new DataBufferHeap(response.GetStringRef().size() / 2, 0));
response.GetHexBytes(buffer_sp->GetData(), '\xcc');
return buffer_sp;
}