Skip to content

Commit f623702

Browse files
authored
[lldb] Implement RegisterContextWasm (#151056)
This PR implements a register context for Wasm, which uses virtual registers to resolve Wasm local, globals and stack values. The registers are used to implement support for `DW_OP_WASM_location` in the DWARF expression evaluator (#151010). This also adds a more comprehensive test, showing that we can use this to show local variables.
1 parent ef96275 commit f623702

File tree

11 files changed

+703
-83
lines changed

11 files changed

+703
-83
lines changed

lldb/docs/resources/lldbgdbremote.md

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1998,22 +1998,6 @@ threads (live system debug) / cores (JTAG) in your program have
19981998
stopped and allows LLDB to display and control your program
19991999
correctly.
20002000
2001-
## qWasmCallStack
2002-
2003-
Get the Wasm call stack for the given thread id. This returns a hex-encoded
2004-
list of PC values, one for each frame of the call stack. To match the Wasm
2005-
specification, the addresses are encoded in little endian byte order, even if
2006-
the endian of the Wasm runtime's host is not little endian.
2007-
2008-
```
2009-
send packet: $qWasmCallStack:202dbe040#08
2010-
read packet: $9c01000000000040e501000000000040fe01000000000040#
2011-
```
2012-
2013-
**Priority to Implement:** Only required for Wasm support. This packed is
2014-
supported by the [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime)
2015-
and [V8](https://v8.dev) Wasm runtimes.
2016-
20172001
## qWatchpointSupportInfo
20182002
20192003
Get the number of hardware watchpoints available on the remote target.
@@ -2479,3 +2463,70 @@ omitting them will work fine; these numbers are always base 16.
24792463
24802464
The length of the payload is not provided. A reliable, 8-bit clean,
24812465
transport layer is assumed.
2466+
2467+
## Wasm Packets
2468+
2469+
The packet below are supported by the
2470+
[WAMR](https://github.com/bytecodealliance/wasm-micro-runtime) and
2471+
[V8](https://v8.dev) Wasm runtimes.
2472+
2473+
2474+
### qWasmCallStack
2475+
2476+
Get the Wasm call stack for the given thread id. This returns a hex-encoded
2477+
list of PC values, one for each frame of the call stack. To match the Wasm
2478+
specification, the addresses are encoded in little endian byte order, even if
2479+
the endian of the Wasm runtime's host is not little endian.
2480+
2481+
```
2482+
send packet: $qWasmCallStack:202dbe040#08
2483+
read packet: $9c01000000000040e501000000000040fe01000000000040#
2484+
```
2485+
2486+
**Priority to Implement:** Only required for Wasm support. Necessary to show
2487+
stack traces.
2488+
2489+
### qWasmGlobal
2490+
2491+
Get the value of a Wasm global variable for the given frame index at the given
2492+
variable index. The indexes are encoded as base 10. The result is a hex-encoded
2493+
address from where to read the value.
2494+
2495+
```
2496+
send packet: $qWasmGlobal:0;2#cb
2497+
read packet: $e0030100#b9
2498+
```
2499+
2500+
**Priority to Implement:** Only required for Wasm support. Necessary to show
2501+
variables.
2502+
2503+
2504+
### qWasmLocal
2505+
2506+
Get the value of a Wasm function argument or local variable for the given frame
2507+
index at the given variable index. The indexes are encoded as base 10. The
2508+
result is a hex-encoded address from where to read the value.
2509+
2510+
2511+
```
2512+
send packet: $qWasmLocal:0;2#cb
2513+
read packet: $e0030100#b9
2514+
```
2515+
2516+
**Priority to Implement:** Only required for Wasm support. Necessary to show
2517+
variables.
2518+
2519+
2520+
### qWasmStackValue
2521+
2522+
Get the value of a Wasm local variable from the Wasm operand stack, for the
2523+
given frame index at the given variable index. The indexes are encoded as base
2524+
10. The result is a hex-encoded address from where to read value.
2525+
2526+
```
2527+
send packet: $qWasmStackValue:0;2#cb
2528+
read packet: $e0030100#b9
2529+
```
2530+
2531+
**Priority to Implement:** Only required for Wasm support. Necessary to show
2532+
variables.

lldb/source/Plugins/Process/wasm/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
add_lldb_library(lldbPluginProcessWasm PLUGIN
22
ProcessWasm.cpp
3+
RegisterContextWasm.cpp
34
ThreadWasm.cpp
45
UnwindWasm.cpp
56

lldb/source/Plugins/Process/wasm/ProcessWasm.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,36 @@ ProcessWasm::GetWasmCallStack(lldb::tid_t tid) {
131131

132132
return call_stack_pcs;
133133
}
134+
135+
llvm::Expected<lldb::DataBufferSP>
136+
ProcessWasm::GetWasmVariable(WasmVirtualRegisterKinds kind, int frame_index,
137+
int index) {
138+
StreamString packet;
139+
switch (kind) {
140+
case eWasmTagLocal:
141+
packet.Printf("qWasmLocal:");
142+
break;
143+
case eWasmTagGlobal:
144+
packet.Printf("qWasmGlobal:");
145+
break;
146+
case eWasmTagOperandStack:
147+
packet.PutCString("qWasmStackValue:");
148+
break;
149+
case eWasmTagNotAWasmLocation:
150+
return llvm::createStringError("not a Wasm ___location");
151+
}
152+
packet.Printf("%d;%d", frame_index, index);
153+
154+
StringExtractorGDBRemote response;
155+
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
156+
GDBRemoteCommunication::PacketResult::Success)
157+
return llvm::createStringError("failed to send Wasm variable");
158+
159+
if (!response.IsNormalResponse())
160+
return llvm::createStringError("failed to get response for Wasm variable");
161+
162+
WritableDataBufferSP buffer_sp(
163+
new DataBufferHeap(response.GetStringRef().size() / 2, 0));
164+
response.GetHexBytes(buffer_sp->GetData(), '\xcc');
165+
return buffer_sp;
166+
}

lldb/source/Plugins/Process/wasm/ProcessWasm.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H
1111

1212
#include "Plugins/Process/gdb-remote/ProcessGDBRemote.h"
13+
#include "Utility/WasmVirtualRegisters.h"
1314

1415
namespace lldb_private {
1516
namespace wasm {
@@ -71,12 +72,19 @@ class ProcessWasm : public process_gdb_remote::ProcessGDBRemote {
7172
/// Retrieve the current call stack from the WebAssembly remote process.
7273
llvm::Expected<std::vector<lldb::addr_t>> GetWasmCallStack(lldb::tid_t tid);
7374

75+
/// Query the value of a WebAssembly variable from the WebAssembly
76+
/// remote process.
77+
llvm::Expected<lldb::DataBufferSP>
78+
GetWasmVariable(WasmVirtualRegisterKinds kind, int frame_index, int index);
79+
7480
protected:
7581
std::shared_ptr<process_gdb_remote::ThreadGDBRemote>
7682
CreateThread(lldb::tid_t tid) override;
7783

7884
private:
7985
friend class UnwindWasm;
86+
friend class ThreadWasm;
87+
8088
process_gdb_remote::GDBRemoteDynamicRegisterInfoSP &GetRegisterInfo() {
8189
return m_register_info_sp;
8290
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "RegisterContextWasm.h"
10+
#include "Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h"
11+
#include "ProcessWasm.h"
12+
#include "ThreadWasm.h"
13+
#include "lldb/Utility/LLDBLog.h"
14+
#include "lldb/Utility/Log.h"
15+
#include "lldb/Utility/RegisterValue.h"
16+
#include "llvm/Support/Error.h"
17+
#include <memory>
18+
19+
using namespace lldb;
20+
using namespace lldb_private;
21+
using namespace lldb_private::process_gdb_remote;
22+
using namespace lldb_private::wasm;
23+
24+
RegisterContextWasm::RegisterContextWasm(
25+
wasm::ThreadWasm &thread, uint32_t concrete_frame_idx,
26+
GDBRemoteDynamicRegisterInfoSP reg_info_sp)
27+
: GDBRemoteRegisterContext(thread, concrete_frame_idx, reg_info_sp, false,
28+
false) {}
29+
30+
RegisterContextWasm::~RegisterContextWasm() = default;
31+
32+
uint32_t RegisterContextWasm::ConvertRegisterKindToRegisterNumber(
33+
lldb::RegisterKind kind, uint32_t num) {
34+
return num;
35+
}
36+
37+
size_t RegisterContextWasm::GetRegisterCount() {
38+
// Wasm has no registers.
39+
return 0;
40+
}
41+
42+
const RegisterInfo *RegisterContextWasm::GetRegisterInfoAtIndex(size_t reg) {
43+
uint32_t tag = GetWasmVirtualRegisterTag(reg);
44+
if (tag == eWasmTagNotAWasmLocation)
45+
return m_reg_info_sp->GetRegisterInfoAtIndex(
46+
GetWasmVirtualRegisterIndex(reg));
47+
48+
auto it = m_register_map.find(reg);
49+
if (it == m_register_map.end()) {
50+
WasmVirtualRegisterKinds kind = static_cast<WasmVirtualRegisterKinds>(tag);
51+
std::tie(it, std::ignore) = m_register_map.insert(
52+
{reg, std::make_unique<WasmVirtualRegisterInfo>(
53+
kind, GetWasmVirtualRegisterIndex(reg))});
54+
}
55+
return it->second.get();
56+
}
57+
58+
size_t RegisterContextWasm::GetRegisterSetCount() { return 0; }
59+
60+
const RegisterSet *RegisterContextWasm::GetRegisterSet(size_t reg_set) {
61+
// Wasm has no registers.
62+
return nullptr;
63+
}
64+
65+
bool RegisterContextWasm::ReadRegister(const RegisterInfo *reg_info,
66+
RegisterValue &value) {
67+
// The only real registers is the PC.
68+
if (reg_info->name)
69+
return GDBRemoteRegisterContext::ReadRegister(reg_info, value);
70+
71+
// Read the virtual registers.
72+
ThreadWasm *thread = static_cast<ThreadWasm *>(&GetThread());
73+
ProcessWasm *process = static_cast<ProcessWasm *>(thread->GetProcess().get());
74+
if (!thread)
75+
return false;
76+
77+
uint32_t frame_index = m_concrete_frame_idx;
78+
WasmVirtualRegisterInfo *wasm_reg_info =
79+
static_cast<WasmVirtualRegisterInfo *>(
80+
const_cast<RegisterInfo *>(reg_info));
81+
82+
llvm::Expected<DataBufferSP> maybe_buffer = process->GetWasmVariable(
83+
wasm_reg_info->kind, frame_index, wasm_reg_info->index);
84+
if (!maybe_buffer) {
85+
LLDB_LOG_ERROR(GetLog(LLDBLog::Process), maybe_buffer.takeError(),
86+
"Failed to read Wasm local: {0}");
87+
return false;
88+
}
89+
90+
DataBufferSP buffer_sp = *maybe_buffer;
91+
DataExtractor reg_data(buffer_sp, process->GetByteOrder(),
92+
process->GetAddressByteSize());
93+
wasm_reg_info->byte_size = buffer_sp->GetByteSize();
94+
wasm_reg_info->encoding = lldb::eEncodingUint;
95+
96+
Status error = value.SetValueFromData(
97+
*reg_info, reg_data, reg_info->byte_offset, /*partial_data_ok=*/false);
98+
return error.Success();
99+
}
100+
101+
void RegisterContextWasm::InvalidateAllRegisters() {}
102+
103+
bool RegisterContextWasm::WriteRegister(const RegisterInfo *reg_info,
104+
const RegisterValue &value) {
105+
// The only real registers is the PC.
106+
if (reg_info->name)
107+
return GDBRemoteRegisterContext::WriteRegister(reg_info, value);
108+
return false;
109+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_SOURCE_PLUGINS_PROCESS_WASM_REGISTERCONTEXTWASM_H
10+
#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_REGISTERCONTEXTWASM_H
11+
12+
#include "Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h"
13+
#include "ThreadWasm.h"
14+
#include "Utility/WasmVirtualRegisters.h"
15+
#include "lldb/lldb-private-types.h"
16+
#include <unordered_map>
17+
18+
namespace lldb_private {
19+
namespace wasm {
20+
21+
class RegisterContextWasm;
22+
23+
typedef std::shared_ptr<RegisterContextWasm> RegisterContextWasmSP;
24+
25+
struct WasmVirtualRegisterInfo : public RegisterInfo {
26+
WasmVirtualRegisterKinds kind;
27+
uint32_t index;
28+
29+
WasmVirtualRegisterInfo(WasmVirtualRegisterKinds kind, uint32_t index)
30+
: RegisterInfo(), kind(kind), index(index) {}
31+
};
32+
33+
class RegisterContextWasm
34+
: public process_gdb_remote::GDBRemoteRegisterContext {
35+
public:
36+
RegisterContextWasm(
37+
wasm::ThreadWasm &thread, uint32_t concrete_frame_idx,
38+
process_gdb_remote::GDBRemoteDynamicRegisterInfoSP reg_info_sp);
39+
40+
~RegisterContextWasm() override;
41+
42+
uint32_t ConvertRegisterKindToRegisterNumber(lldb::RegisterKind kind,
43+
uint32_t num) override;
44+
45+
void InvalidateAllRegisters() override;
46+
47+
size_t GetRegisterCount() override;
48+
49+
const RegisterInfo *GetRegisterInfoAtIndex(size_t reg) override;
50+
51+
size_t GetRegisterSetCount() override;
52+
53+
const RegisterSet *GetRegisterSet(size_t reg_set) override;
54+
55+
bool ReadRegister(const RegisterInfo *reg_info,
56+
RegisterValue &value) override;
57+
58+
bool WriteRegister(const RegisterInfo *reg_info,
59+
const RegisterValue &value) override;
60+
61+
private:
62+
std::unordered_map<size_t, std::unique_ptr<WasmVirtualRegisterInfo>>
63+
m_register_map;
64+
};
65+
66+
} // namespace wasm
67+
} // namespace lldb_private
68+
69+
#endif

lldb/source/Plugins/Process/wasm/ThreadWasm.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "ThreadWasm.h"
1010

1111
#include "ProcessWasm.h"
12+
#include "RegisterContextWasm.h"
1213
#include "UnwindWasm.h"
1314
#include "lldb/Target/Target.h"
1415

@@ -32,3 +33,19 @@ llvm::Expected<std::vector<lldb::addr_t>> ThreadWasm::GetWasmCallStack() {
3233
}
3334
return llvm::createStringError("no process");
3435
}
36+
37+
lldb::RegisterContextSP
38+
ThreadWasm::CreateRegisterContextForFrame(StackFrame *frame) {
39+
uint32_t concrete_frame_idx = 0;
40+
ProcessSP process_sp(GetProcess());
41+
ProcessWasm *wasm_process = static_cast<ProcessWasm *>(process_sp.get());
42+
43+
if (frame)
44+
concrete_frame_idx = frame->GetConcreteFrameIndex();
45+
46+
if (concrete_frame_idx == 0)
47+
return std::make_shared<RegisterContextWasm>(
48+
*this, concrete_frame_idx, wasm_process->GetRegisterInfo());
49+
50+
return GetUnwinder().CreateRegisterContextForFrame(frame);
51+
}

lldb/source/Plugins/Process/wasm/ThreadWasm.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ class ThreadWasm : public process_gdb_remote::ThreadGDBRemote {
2525
/// Retrieve the current call stack from the WebAssembly remote process.
2626
llvm::Expected<std::vector<lldb::addr_t>> GetWasmCallStack();
2727

28+
lldb::RegisterContextSP
29+
CreateRegisterContextForFrame(StackFrame *frame) override;
30+
2831
protected:
2932
Unwind &GetUnwinder() override;
3033

0 commit comments

Comments
 (0)