1 // Copyright 2020 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/debug/wasm/gdb-server/target.h"
6
7 #include <inttypes.h>
8 #include "src/base/platform/time.h"
9 #include "src/debug/wasm/gdb-server/gdb-remote-util.h"
10 #include "src/debug/wasm/gdb-server/gdb-server.h"
11 #include "src/debug/wasm/gdb-server/packet.h"
12 #include "src/debug/wasm/gdb-server/session.h"
13 #include "src/debug/wasm/gdb-server/transport.h"
14
15 namespace v8 {
16 namespace internal {
17 namespace wasm {
18 namespace gdb_server {
19
20 static const int kThreadId = 1;
21
22 // Signals.
23 static const int kSigTrace = 5;
24 static const int kSigSegv = 11;
25
Target(GdbServer * gdb_server)26 Target::Target(GdbServer* gdb_server)
27 : gdb_server_(gdb_server),
28 status_(Status::Running),
29 cur_signal_(0),
30 session_(nullptr),
31 debugger_initial_suspension_(true),
32 semaphore_(0),
33 current_isolate_(nullptr) {
34 InitQueryPropertyMap();
35 }
36
InitQueryPropertyMap()37 void Target::InitQueryPropertyMap() {
38 // Request LLDB to send packets up to 4000 bytes for bulk transfers.
39 query_properties_["Supported"] =
40 "PacketSize=1000;vContSupported-;qXfer:libraries:read+;";
41
42 query_properties_["Attached"] = "1";
43
44 // There is only one register, named 'pc', in this architecture
45 query_properties_["RegisterInfo0"] =
46 "name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;set:"
47 "General Purpose Registers;gcc:16;dwarf:16;generic:pc;";
48 query_properties_["RegisterInfo1"] = "E45";
49
50 // ProcessInfo for wasm32
51 query_properties_["ProcessInfo"] =
52 "pid:1;ppid:1;uid:1;gid:1;euid:1;egid:1;name:6c6c6462;triple:" +
53 Mem2Hex("wasm32-unknown-unknown-wasm") + ";ptrsize:4;";
54 query_properties_["Symbol"] = "OK";
55
56 // Current thread info
57 char buff[16];
58 snprintf(buff, sizeof(buff), "QC%x", kThreadId);
59 query_properties_["C"] = buff;
60 }
61
Terminate()62 void Target::Terminate() {
63 // Executed in the Isolate thread, when the process shuts down.
64 SetStatus(Status::Terminated);
65 }
66
OnProgramBreak(Isolate * isolate,const std::vector<wasm_addr_t> & call_frames)67 void Target::OnProgramBreak(Isolate* isolate,
68 const std::vector<wasm_addr_t>& call_frames) {
69 OnSuspended(isolate, kSigTrace, call_frames);
70 }
OnException(Isolate * isolate,const std::vector<wasm_addr_t> & call_frames)71 void Target::OnException(Isolate* isolate,
72 const std::vector<wasm_addr_t>& call_frames) {
73 OnSuspended(isolate, kSigSegv, call_frames);
74 }
OnSuspended(Isolate * isolate,int signal,const std::vector<wasm_addr_t> & call_frames)75 void Target::OnSuspended(Isolate* isolate, int signal,
76 const std::vector<wasm_addr_t>& call_frames) {
77 // This function will be called in the isolate thread, when the wasm
78 // interpreter gets suspended.
79
80 bool isWaitingForSuspension = (status_ == Status::WaitingForSuspension);
81 SetStatus(Status::Suspended, signal, call_frames, isolate);
82 if (isWaitingForSuspension) {
83 // Wake the GdbServer thread that was blocked waiting for the Target
84 // to suspend.
85 semaphore_.Signal();
86 } else if (session_) {
87 session_->SignalThreadEvent();
88 }
89 }
90
Run(Session * session)91 void Target::Run(Session* session) {
92 // Executed in the GdbServer thread.
93 session_ = session;
94 do {
95 WaitForDebugEvent();
96 ProcessDebugEvent();
97 ProcessCommands();
98 } while (!IsTerminated() && session_->IsConnected());
99 session_ = nullptr;
100 }
101
WaitForDebugEvent()102 void Target::WaitForDebugEvent() {
103 // Executed in the GdbServer thread.
104
105 if (status_ == Status::Running) {
106 // Wait for either:
107 // * the thread to fault (or single-step)
108 // * an interrupt from LLDB
109 session_->WaitForDebugStubEvent();
110 }
111 }
112
ProcessDebugEvent()113 void Target::ProcessDebugEvent() {
114 // Executed in the GdbServer thread
115
116 if (status_ == Status::Running) {
117 // Blocks, waiting for the engine to suspend.
118 Suspend();
119 }
120
121 // Here, the wasm interpreter has suspended and we have updated the current
122 // thread info.
123
124 if (debugger_initial_suspension_) {
125 // First time on a connection, we don't send the signal.
126 // All other times, send the signal that triggered us.
127 debugger_initial_suspension_ = false;
128 } else {
129 Packet pktOut;
130 SetStopReply(&pktOut);
131 session_->SendPacket(&pktOut, false);
132 }
133 }
134
Suspend()135 void Target::Suspend() {
136 // Executed in the GdbServer thread
137 if (status_ == Status::Running) {
138 // TODO(paolosev) - this only suspends the wasm interpreter.
139 gdb_server_->Suspend();
140
141 status_ = Status::WaitingForSuspension;
142 }
143
144 while (status_ == Status::WaitingForSuspension) {
145 if (semaphore_.WaitFor(base::TimeDelta::FromMilliseconds(500))) {
146 // Here the wasm interpreter is suspended.
147 return;
148 }
149 }
150 }
151
ProcessCommands()152 void Target::ProcessCommands() {
153 // GDB-remote messages are processed in the GDBServer thread.
154
155 if (IsTerminated()) {
156 return;
157 } else if (status_ != Status::Suspended) {
158 // Don't process commands if we haven't stopped.
159 return;
160 }
161
162 // Now we are ready to process commands.
163 // Loop through packets until we process a continue packet or a detach.
164 Packet recv, reply;
165 while (session_->IsConnected()) {
166 if (!session_->GetPacket(&recv)) {
167 continue;
168 }
169
170 reply.Clear();
171 ProcessPacketResult result = ProcessPacket(&recv, &reply);
172 switch (result) {
173 case ProcessPacketResult::Paused:
174 session_->SendPacket(&reply);
175 break;
176
177 case ProcessPacketResult::Continue:
178 DCHECK_EQ(status_, Status::Running);
179 // If this is a continue type command, break out of this loop.
180 gdb_server_->QuitMessageLoopOnPause();
181 return;
182
183 case ProcessPacketResult::Detach:
184 SetStatus(Status::Running);
185 session_->SendPacket(&reply);
186 session_->Disconnect();
187 gdb_server_->QuitMessageLoopOnPause();
188 return;
189
190 case ProcessPacketResult::Kill:
191 session_->SendPacket(&reply);
192 exit(-9);
193
194 default:
195 UNREACHABLE();
196 }
197 }
198
199 if (!session_->IsConnected()) {
200 debugger_initial_suspension_ = true;
201 }
202 }
203
ProcessPacket(Packet * pkt_in,Packet * pkt_out)204 Target::ProcessPacketResult Target::ProcessPacket(Packet* pkt_in,
205 Packet* pkt_out) {
206 ErrorCode err = ErrorCode::None;
207
208 // Clear the outbound message.
209 pkt_out->Clear();
210
211 // Set the sequence number, if present.
212 int32_t seq = -1;
213 if (pkt_in->GetSequence(&seq)) {
214 pkt_out->SetSequence(seq);
215 }
216
217 // A GDB-remote packet begins with an upper- or lower-case letter, which
218 // generally represents a single command.
219 // The letters 'q' and 'Q' introduce a "General query packets" and are used
220 // to extend the protocol with custom commands.
221 // The format of GDB-remote commands is documented here:
222 // https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview.
223 char cmd;
224 pkt_in->GetRawChar(&cmd);
225
226 switch (cmd) {
227 // Queries the reason the target halted.
228 // IN : $?
229 // OUT: A Stop-reply packet
230 case '?':
231 SetStopReply(pkt_out);
232 break;
233
234 // Resumes execution
235 // IN : $c
236 // OUT: A Stop-reply packet is sent later, when the execution halts.
237 case 'c':
238 SetStatus(Status::Running);
239 return ProcessPacketResult::Continue;
240
241 // Detaches the debugger from this target
242 // IN : $D
243 // OUT: $OK
244 case 'D':
245 TRACE_GDB_REMOTE("Requested Detach.\n");
246 pkt_out->AddString("OK");
247 return ProcessPacketResult::Detach;
248
249 // Read general registers (We only support register 'pc' that contains
250 // the current instruction pointer).
251 // IN : $g
252 // OUT: $xx...xx
253 case 'g': {
254 uint64_t pc = GetCurrentPc();
255 pkt_out->AddBlock(&pc, sizeof(pc));
256 break;
257 }
258
259 // Write general registers - NOT SUPPORTED
260 // IN : $Gxx..xx
261 // OUT: $ (empty string)
262 case 'G': {
263 break;
264 }
265
266 // Set thread for subsequent operations. For Wasm targets, we currently
267 // assume that there is only one thread with id = kThreadId (= 1).
268 // IN : $H(c/g)(-1,0,xxxx)
269 // OUT: $OK
270 case 'H': {
271 // Type of the operation (‘m’, ‘M’, ‘g’, ‘G’, ...)
272 char operation;
273 if (!pkt_in->GetRawChar(&operation)) {
274 err = ErrorCode::BadFormat;
275 break;
276 }
277
278 uint64_t thread_id;
279 if (!pkt_in->GetNumberSep(&thread_id, 0)) {
280 err = ErrorCode::BadFormat;
281 break;
282 }
283
284 // Ignore, only one thread supported for now.
285 pkt_out->AddString("OK");
286 break;
287 }
288
289 // Kills the debuggee.
290 // IN : $k
291 // OUT: $OK
292 case 'k':
293 TRACE_GDB_REMOTE("Requested Kill.\n");
294 pkt_out->AddString("OK");
295 return ProcessPacketResult::Kill;
296
297 // Reads {llll} addressable memory units starting at address {aaaa}.
298 // IN : $maaaa,llll
299 // OUT: $xx..xx
300 case 'm': {
301 uint64_t address;
302 if (!pkt_in->GetNumberSep(&address, 0)) {
303 err = ErrorCode::BadFormat;
304 break;
305 }
306 wasm_addr_t wasm_addr(address);
307
308 uint64_t len;
309 if (!pkt_in->GetNumberSep(&len, 0)) {
310 err = ErrorCode::BadFormat;
311 break;
312 }
313
314 if (len > Transport::kBufSize / 2) {
315 err = ErrorCode::BadArgs;
316 break;
317 }
318
319 uint32_t length = static_cast<uint32_t>(len);
320 uint8_t buff[Transport::kBufSize];
321 if (wasm_addr.ModuleId() > 0) {
322 uint32_t read =
323 gdb_server_->GetWasmModuleBytes(wasm_addr, buff, length);
324 if (read > 0) {
325 pkt_out->AddBlock(buff, read);
326 } else {
327 err = ErrorCode::Failed;
328 }
329 } else {
330 err = ErrorCode::BadArgs;
331 }
332 break;
333 }
334
335 // Writes {llll} addressable memory units starting at address {aaaa}.
336 // IN : $Maaaa,llll:xx..xx
337 // OUT: $OK
338 case 'M': {
339 // Writing to memory not supported for Wasm.
340 err = ErrorCode::Failed;
341 break;
342 }
343
344 // pN: Reads the value of register N.
345 // IN : $pxx
346 // OUT: $xx..xx
347 case 'p': {
348 uint64_t pc = GetCurrentPc();
349 pkt_out->AddBlock(&pc, sizeof(pc));
350 } break;
351
352 case 'q': {
353 err = ProcessQueryPacket(pkt_in, pkt_out);
354 break;
355 }
356
357 // Single step
358 // IN : $s
359 // OUT: A Stop-reply packet is sent later, when the execution halts.
360 case 's': {
361 if (status_ == Status::Suspended) {
362 gdb_server_->PrepareStep();
363 SetStatus(Status::Running);
364 }
365 return ProcessPacketResult::Continue;
366 }
367
368 // Find out if the thread 'id' is alive.
369 // IN : $T
370 // OUT: $OK if alive, $Enn if thread is dead.
371 case 'T': {
372 uint64_t id;
373 if (!pkt_in->GetNumberSep(&id, 0)) {
374 err = ErrorCode::BadFormat;
375 break;
376 }
377 if (id != kThreadId) {
378 err = ErrorCode::BadArgs;
379 break;
380 }
381 pkt_out->AddString("OK");
382 break;
383 }
384
385 // Z: Adds a breakpoint
386 // IN : $Z<type>,<addr>,<kind>
387 // <type>: 0: sw breakpoint, 1: hw breakpoint, 2: watchpoint
388 // OUT: $OK (success) or $Enn (error)
389 case 'Z': {
390 uint64_t breakpoint_type;
391 uint64_t breakpoint_address;
392 uint64_t breakpoint_kind;
393 // Only software breakpoints are supported.
394 if (!pkt_in->GetNumberSep(&breakpoint_type, 0) || breakpoint_type != 0 ||
395 !pkt_in->GetNumberSep(&breakpoint_address, 0) ||
396 !pkt_in->GetNumberSep(&breakpoint_kind, 0)) {
397 err = ErrorCode::BadFormat;
398 break;
399 }
400
401 wasm_addr_t wasm_breakpoint_addr(breakpoint_address);
402 if (!gdb_server_->AddBreakpoint(wasm_breakpoint_addr.ModuleId(),
403 wasm_breakpoint_addr.Offset())) {
404 err = ErrorCode::Failed;
405 break;
406 }
407
408 pkt_out->AddString("OK");
409 break;
410 }
411
412 // z: Removes a breakpoint
413 // IN : $z<type>,<addr>,<kind>
414 // <type>: 0: sw breakpoint, 1: hw breakpoint, 2: watchpoint
415 // OUT: $OK (success) or $Enn (error)
416 case 'z': {
417 uint64_t breakpoint_type;
418 uint64_t breakpoint_address;
419 uint64_t breakpoint_kind;
420 if (!pkt_in->GetNumberSep(&breakpoint_type, 0) || breakpoint_type != 0 ||
421 !pkt_in->GetNumberSep(&breakpoint_address, 0) ||
422 !pkt_in->GetNumberSep(&breakpoint_kind, 0)) {
423 err = ErrorCode::BadFormat;
424 break;
425 }
426
427 wasm_addr_t wasm_breakpoint_addr(breakpoint_address);
428 if (!gdb_server_->RemoveBreakpoint(wasm_breakpoint_addr.ModuleId(),
429 wasm_breakpoint_addr.Offset())) {
430 err = ErrorCode::Failed;
431 break;
432 }
433
434 pkt_out->AddString("OK");
435 break;
436 }
437
438 // If the command is not recognized, ignore it by sending an empty reply.
439 default: {
440 TRACE_GDB_REMOTE("Unknown command: %s\n", pkt_in->GetPayload());
441 }
442 }
443
444 // If there is an error, return the error code instead of a payload
445 if (err != ErrorCode::None) {
446 pkt_out->Clear();
447 pkt_out->AddRawChar('E');
448 pkt_out->AddWord8(static_cast<uint8_t>(err));
449 }
450 return ProcessPacketResult::Paused;
451 }
452
ProcessQueryPacket(const Packet * pkt_in,Packet * pkt_out)453 Target::ErrorCode Target::ProcessQueryPacket(const Packet* pkt_in,
454 Packet* pkt_out) {
455 const char* str = &pkt_in->GetPayload()[1];
456
457 // Get first thread query
458 // IN : $qfThreadInfo
459 // OUT: $m<tid>
460 //
461 // Get next thread query
462 // IN : $qsThreadInfo
463 // OUT: $m<tid> or l to denote end of list.
464 if (!strcmp(str, "fThreadInfo") || !strcmp(str, "sThreadInfo")) {
465 if (str[0] == 'f') {
466 pkt_out->AddString("m");
467 pkt_out->AddNumberSep(kThreadId, 0);
468 } else {
469 pkt_out->AddString("l");
470 }
471 return ErrorCode::None;
472 }
473
474 // Get a list of loaded libraries
475 // IN : $qXfer:libraries:read
476 // OUT: an XML document which lists loaded libraries, with this format:
477 // <library-list>
478 // <library name="foo.wasm">
479 // <section address="0x100000000"/>
480 // </library>
481 // <library name="bar.wasm">
482 // <section address="0x200000000"/>
483 // </library>
484 // </library-list>
485 // Note that LLDB must be compiled with libxml2 support to handle this packet.
486 std::string tmp = "Xfer:libraries:read";
487 if (!strncmp(str, tmp.data(), tmp.length())) {
488 std::vector<GdbServer::WasmModuleInfo> modules =
489 gdb_server_->GetLoadedModules();
490 std::string result("l<library-list>");
491 for (const auto& module : modules) {
492 wasm_addr_t address(module.module_id, 0);
493 char address_string[32];
494 snprintf(address_string, sizeof(address_string), "%" PRIu64,
495 static_cast<uint64_t>(address));
496 result += "<library name=\"";
497 result += module.module_name;
498 result += "\"><section address=\"";
499 result += address_string;
500 result += "\"/></library>";
501 }
502 result += "</library-list>";
503 pkt_out->AddString(result.c_str());
504 return ErrorCode::None;
505 }
506
507 // Get the current call stack.
508 // IN : $qWasmCallStack
509 // OUT: $xx..xxyy..yyzz..zz (A sequence of uint64_t values represented as
510 // consecutive 8-bytes blocks).
511 std::vector<std::string> toks = StringSplit(str, ":;");
512 if (toks[0] == "WasmCallStack") {
513 std::vector<wasm_addr_t> call_stack_pcs = gdb_server_->GetWasmCallStack();
514 std::vector<uint64_t> buffer;
515 for (wasm_addr_t pc : call_stack_pcs) {
516 buffer.push_back(pc);
517 }
518 pkt_out->AddBlock(buffer.data(),
519 static_cast<uint32_t>(sizeof(uint64_t) * buffer.size()));
520 return ErrorCode::None;
521 }
522
523 // Get a Wasm global value in the Wasm module specified.
524 // IN : $qWasmGlobal:frame_index;index
525 // OUT: $xx..xx
526 if (toks[0] == "WasmGlobal") {
527 if (toks.size() == 3) {
528 uint32_t frame_index =
529 static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
530 uint32_t index =
531 static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
532 uint8_t buff[16];
533 uint32_t size = 0;
534 if (gdb_server_->GetWasmGlobal(frame_index, index, buff, 16, &size)) {
535 pkt_out->AddBlock(buff, size);
536 return ErrorCode::None;
537 } else {
538 return ErrorCode::Failed;
539 }
540 }
541 return ErrorCode::BadFormat;
542 }
543
544 // Get a Wasm local value in the stack frame specified.
545 // IN : $qWasmLocal:frame_index;index
546 // OUT: $xx..xx
547 if (toks[0] == "WasmLocal") {
548 if (toks.size() == 3) {
549 uint32_t frame_index =
550 static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
551 uint32_t index =
552 static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
553 uint8_t buff[16];
554 uint32_t size = 0;
555 if (gdb_server_->GetWasmLocal(frame_index, index, buff, 16, &size)) {
556 pkt_out->AddBlock(buff, size);
557 return ErrorCode::None;
558 } else {
559 return ErrorCode::Failed;
560 }
561 }
562 return ErrorCode::BadFormat;
563 }
564
565 // Get a Wasm local from the operand stack at the index specified.
566 // IN : qWasmStackValue:frame_index;index
567 // OUT: $xx..xx
568 if (toks[0] == "WasmStackValue") {
569 if (toks.size() == 3) {
570 uint32_t frame_index =
571 static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
572 uint32_t index =
573 static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
574 uint8_t buff[16];
575 uint32_t size = 0;
576 if (gdb_server_->GetWasmStackValue(frame_index, index, buff, 16, &size)) {
577 pkt_out->AddBlock(buff, size);
578 return ErrorCode::None;
579 } else {
580 return ErrorCode::Failed;
581 }
582 }
583 return ErrorCode::BadFormat;
584 }
585
586 // Read Wasm memory.
587 // IN : $qWasmMem:frame_index;addr;len
588 // OUT: $xx..xx
589 if (toks[0] == "WasmMem") {
590 if (toks.size() == 4) {
591 uint32_t frame_index =
592 static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
593 uint32_t address =
594 static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 16));
595 uint32_t length =
596 static_cast<uint32_t>(strtol(toks[3].data(), nullptr, 16));
597 if (length > Transport::kBufSize / 2) {
598 return ErrorCode::BadArgs;
599 }
600 uint8_t buff[Transport::kBufSize];
601 uint32_t read =
602 gdb_server_->GetWasmMemory(frame_index, address, buff, length);
603 if (read > 0) {
604 pkt_out->AddBlock(buff, read);
605 return ErrorCode::None;
606 } else {
607 return ErrorCode::Failed;
608 }
609 }
610 return ErrorCode::BadFormat;
611 }
612
613 // No match so far, check the property cache.
614 QueryPropertyMap::const_iterator it = query_properties_.find(toks[0]);
615 if (it != query_properties_.end()) {
616 pkt_out->AddString(it->second.data());
617 }
618 // If not found, just send an empty response.
619 return ErrorCode::None;
620 }
621
622 // A Stop-reply packet has the format:
623 // Sxx
624 // or:
625 // Txx<name1>:<value1>;...;<nameN>:<valueN>
626 // where 'xx' is a two-digit hex number that represents the stop signal
627 // and the <name>:<value> pairs are used to report additional information,
628 // like the thread id.
SetStopReply(Packet * pkt_out) const629 void Target::SetStopReply(Packet* pkt_out) const {
630 pkt_out->AddRawChar('T');
631 pkt_out->AddWord8(cur_signal_);
632
633 // Adds 'thread-pcs:<pc1>,...,<pcN>;' A list of pc values for all threads that
634 // currently exist in the process.
635 char buff[64];
636 snprintf(buff, sizeof(buff), "thread-pcs:%" PRIx64 ";",
637 static_cast<uint64_t>(GetCurrentPc()));
638 pkt_out->AddString(buff);
639
640 // Adds 'thread:<tid>;' pair. Note that a terminating ';' is required.
641 pkt_out->AddString("thread:");
642 pkt_out->AddNumberSep(kThreadId, ';');
643 }
644
SetStatus(Status status,int8_t signal,std::vector<wasm_addr_t> call_frames,Isolate * isolate)645 void Target::SetStatus(Status status, int8_t signal,
646 std::vector<wasm_addr_t> call_frames, Isolate* isolate) {
647 v8::base::MutexGuard guard(&mutex_);
648
649 DCHECK((status == Status::Suspended && signal != 0 &&
650 call_frames.size() > 0 && isolate != nullptr) ||
651 (status != Status::Suspended && signal == 0 &&
652 call_frames.size() == 0 && isolate == nullptr));
653
654 current_isolate_ = isolate;
655 status_ = status;
656 cur_signal_ = signal;
657 call_frames_ = call_frames;
658 }
659
GetCallStack() const660 const std::vector<wasm_addr_t> Target::GetCallStack() const {
661 v8::base::MutexGuard guard(&mutex_);
662
663 return call_frames_;
664 }
665
GetCurrentPc() const666 wasm_addr_t Target::GetCurrentPc() const {
667 v8::base::MutexGuard guard(&mutex_);
668
669 wasm_addr_t pc{0};
670 if (call_frames_.size() > 0) {
671 pc = call_frames_[0];
672 }
673 return pc;
674 }
675
676 } // namespace gdb_server
677 } // namespace wasm
678 } // namespace internal
679 } // namespace v8
680