1 // Copyright 2019 The SwiftShader Authors. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "Server.hpp"
16
17 #include "Context.hpp"
18 #include "EventListener.hpp"
19 #include "File.hpp"
20 #include "Thread.hpp"
21 #include "Variable.hpp"
22
23 #include "dap/network.h"
24 #include "dap/protocol.h"
25 #include "dap/session.h"
26 #include "marl/waitgroup.h"
27
28 #include <thread>
29 #include <unordered_set>
30
31 // Switch for controlling DAP debug logging
32 #define ENABLE_DAP_LOGGING 0
33
34 #if ENABLE_DAP_LOGGING
35 # define DAP_LOG(msg, ...) printf(msg "\n", ##__VA_ARGS__)
36 #else
37 # define DAP_LOG(...) \
38 do \
39 { \
40 } while(false)
41 #endif
42
43 namespace vk {
44 namespace dbg {
45
46 class Server::Impl : public Server, public ServerEventListener
47 {
48 public:
49 Impl(const std::shared_ptr<Context> &ctx, int port);
50 ~Impl();
51
52 // EventListener
53 void onThreadStarted(ID<Thread>) override;
54 void onThreadStepped(ID<Thread>) override;
55 void onLineBreakpointHit(ID<Thread>) override;
56 void onFunctionBreakpointHit(ID<Thread>) override;
57
58 dap::Scope scope(Context::Lock &lock, const char *type, Scope *);
59 dap::Source source(File *);
60 std::shared_ptr<File> file(const dap::Source &source);
61
62 const std::shared_ptr<Context> ctx;
63 const std::unique_ptr<dap::net::Server> server;
64 const std::unique_ptr<dap::Session> session;
65 std::atomic<bool> clientIsVisualStudio = { false };
66 };
67
Impl(const std::shared_ptr<Context> & context,int port)68 Server::Impl::Impl(const std::shared_ptr<Context> &context, int port)
69 : ctx(context)
70 , server(dap::net::Server::create())
71 , session(dap::Session::create())
72 {
73 session->registerHandler([](const dap::DisconnectRequest &req) {
74 DAP_LOG("DisconnectRequest receieved");
75 return dap::DisconnectResponse();
76 });
77
78 session->registerHandler([&](const dap::InitializeRequest &req) {
79 DAP_LOG("InitializeRequest receieved");
80 dap::InitializeResponse response;
81 response.supportsFunctionBreakpoints = true;
82 response.supportsConfigurationDoneRequest = true;
83 response.supportsEvaluateForHovers = true;
84 clientIsVisualStudio = (req.clientID.value("") == "visualstudio");
85 return response;
86 });
87
88 session->registerSentHandler(
89 [&](const dap::ResponseOrError<dap::InitializeResponse> &response) {
90 DAP_LOG("InitializeResponse sent");
91 session->send(dap::InitializedEvent());
92 });
93
94 session->registerHandler([](const dap::SetExceptionBreakpointsRequest &req) {
95 DAP_LOG("SetExceptionBreakpointsRequest receieved");
96 dap::SetExceptionBreakpointsResponse response;
97 return response;
98 });
99
100 session->registerHandler(
101 [this](const dap::SetFunctionBreakpointsRequest &req) {
102 DAP_LOG("SetFunctionBreakpointsRequest receieved");
103
104 dap::SetFunctionBreakpointsResponse response;
105 for(const auto &reqBP : req.breakpoints)
106 {
107 DAP_LOG("Setting breakpoint for function '%s'", reqBP.name.c_str());
108
109 bool verified = false;
110 ctx->clientEventBroadcast()->onSetBreakpoint(reqBP.name, verified);
111
112 dap::Breakpoint resBP{};
113 resBP.verified = verified;
114 response.breakpoints.emplace_back(std::move(resBP));
115 }
116 {
117 auto lock = ctx->lock();
118 lock.clearFunctionBreakpoints();
119 for(const auto &reqBP : req.breakpoints)
120 {
121 lock.addFunctionBreakpoint(reqBP.name.c_str());
122 }
123 }
124 ctx->clientEventBroadcast()->onBreakpointsChanged();
125 return response;
126 });
127
128 session->registerHandler(
129 [this](const dap::SetBreakpointsRequest &req)
130 -> dap::ResponseOrError<dap::SetBreakpointsResponse> {
131 DAP_LOG("SetBreakpointsRequest receieved");
132
133 size_t numBreakpoints = 0;
134 if(req.breakpoints.has_value())
135 {
136 const auto &breakpoints = req.breakpoints.value();
137 numBreakpoints = breakpoints.size();
138 if(auto file = this->file(req.source))
139 {
140 dap::SetBreakpointsResponse response;
141 file->clearBreakpoints();
142 for(size_t i = 0; i < numBreakpoints; i++)
143 {
144 auto &reqBP = breakpoints[i];
145 Location location{ file, static_cast<int>(reqBP.line) };
146 file->addBreakpoint(location.line);
147
148 bool verified = false;
149 ctx->clientEventBroadcast()->onSetBreakpoint(location, verified);
150
151 dap::Breakpoint respBP;
152 respBP.verified = verified;
153 respBP.source = req.source;
154 response.breakpoints.push_back(respBP);
155 }
156 ctx->clientEventBroadcast()->onBreakpointsChanged();
157 return response;
158 }
159
160 if(req.source.name.has_value())
161 {
162 std::vector<int> lines;
163 lines.reserve(breakpoints.size());
164 for(const auto &bp : breakpoints)
165 {
166 lines.push_back(bp.line);
167 }
168 ctx->lock().addPendingBreakpoints(req.source.name.value(),
169 lines);
170 }
171 }
172
173 // Generic response.
174 dap::SetBreakpointsResponse response;
175 for(size_t i = 0; i < numBreakpoints; i++)
176 {
177 dap::Breakpoint bp;
178 bp.verified = false;
179 bp.source = req.source;
180 response.breakpoints.push_back(bp);
181 }
182 ctx->clientEventBroadcast()->onBreakpointsChanged();
183 return response;
184 });
185
186 session->registerHandler([this](const dap::ThreadsRequest &req) {
187 DAP_LOG("ThreadsRequest receieved");
188 auto lock = ctx->lock();
189 dap::ThreadsResponse response;
190 for(auto thread : lock.threads())
191 {
192 std::string name = thread->name();
193 if(clientIsVisualStudio)
194 {
195 // WORKAROUND: https://github.com/microsoft/VSDebugAdapterHost/issues/15
196 for(size_t i = 0; i < name.size(); i++)
197 {
198 if(name[i] == '.')
199 {
200 name[i] = '_';
201 }
202 }
203 }
204
205 dap::Thread out;
206 out.id = thread->id.value();
207 out.name = name;
208 response.threads.push_back(out);
209 };
210 return response;
211 });
212
213 session->registerHandler(
214 [this](const dap::StackTraceRequest &req)
215 -> dap::ResponseOrError<dap::StackTraceResponse> {
216 DAP_LOG("StackTraceRequest receieved");
217
218 auto lock = ctx->lock();
219 auto thread = lock.get(Thread::ID(req.threadId));
220 if(!thread)
221 {
222 return dap::Error("Thread %d not found", req.threadId);
223 }
224
225 auto stack = thread->stack();
226
227 dap::StackTraceResponse response;
228 response.totalFrames = stack.size();
229 response.stackFrames.reserve(stack.size());
230 for(int i = static_cast<int>(stack.size()) - 1; i >= 0; i--)
231 {
232 const auto &frame = stack[i];
233 const auto &loc = frame.location;
234 dap::StackFrame sf;
235 sf.column = 0;
236 sf.id = frame.id.value();
237 sf.name = frame.function;
238 sf.line = loc.line;
239 if(loc.file)
240 {
241 sf.source = source(loc.file.get());
242 }
243 response.stackFrames.emplace_back(std::move(sf));
244 }
245 return response;
246 });
247
248 session->registerHandler([this](const dap::ScopesRequest &req)
249 -> dap::ResponseOrError<dap::ScopesResponse> {
250 DAP_LOG("ScopesRequest receieved");
251
252 auto lock = ctx->lock();
253 auto frame = lock.get(Frame::ID(req.frameId));
254 if(!frame)
255 {
256 return dap::Error("Frame %d not found", req.frameId);
257 }
258
259 dap::ScopesResponse response;
260 response.scopes = {
261 scope(lock, "locals", frame->locals.get()),
262 scope(lock, "arguments", frame->arguments.get()),
263 scope(lock, "registers", frame->registers.get()),
264 };
265 return response;
266 });
267
268 session->registerHandler([this](const dap::VariablesRequest &req)
269 -> dap::ResponseOrError<dap::VariablesResponse> {
270 DAP_LOG("VariablesRequest receieved");
271
272 auto lock = ctx->lock();
273 auto vars = lock.get(Variables::ID(req.variablesReference));
274 if(!vars)
275 {
276 return dap::Error("VariablesReference %d not found",
277 int(req.variablesReference));
278 }
279
280 dap::VariablesResponse response;
281 vars->foreach(req.start.value(0), req.count.value(~0), [&](const Variable &v) {
282 dap::Variable out;
283 out.evaluateName = v.name;
284 out.name = v.name;
285 out.type = v.value->type();
286 out.value = v.value->get();
287 if(auto children = v.value->children())
288 {
289 out.variablesReference = children->id.value();
290 lock.track(children);
291 }
292 response.variables.push_back(out);
293 return true;
294 });
295 return response;
296 });
297
298 session->registerHandler([this](const dap::SourceRequest &req)
299 -> dap::ResponseOrError<dap::SourceResponse> {
300 DAP_LOG("SourceRequest receieved");
301
302 dap::SourceResponse response;
303 uint64_t id = req.sourceReference;
304
305 auto lock = ctx->lock();
306 auto file = lock.get(File::ID(id));
307 if(!file)
308 {
309 return dap::Error("Source %d not found", id);
310 }
311 response.content = file->source;
312 return response;
313 });
314
315 session->registerHandler([this](const dap::PauseRequest &req)
316 -> dap::ResponseOrError<dap::PauseResponse> {
317 DAP_LOG("PauseRequest receieved");
318
319 dap::StoppedEvent event;
320 event.reason = "pause";
321
322 auto lock = ctx->lock();
323 if(auto thread = lock.get(Thread::ID(req.threadId)))
324 {
325 thread->pause();
326 event.threadId = req.threadId;
327 }
328 else
329 {
330 auto threads = lock.threads();
331 for(auto thread : threads)
332 {
333 thread->pause();
334 }
335 event.allThreadsStopped = true;
336
337 // Workaround for
338 // https://github.com/microsoft/VSDebugAdapterHost/issues/11
339 if(clientIsVisualStudio && !threads.empty())
340 {
341 event.threadId = threads.front()->id.value();
342 }
343 }
344
345 session->send(event);
346
347 dap::PauseResponse response;
348 return response;
349 });
350
351 session->registerHandler([this](const dap::ContinueRequest &req)
352 -> dap::ResponseOrError<dap::ContinueResponse> {
353 DAP_LOG("ContinueRequest receieved");
354
355 dap::ContinueResponse response;
356
357 auto lock = ctx->lock();
358 if(auto thread = lock.get(Thread::ID(req.threadId)))
359 {
360 thread->resume();
361 response.allThreadsContinued = false;
362 }
363 else
364 {
365 for(auto it : lock.threads())
366 {
367 thread->resume();
368 }
369 response.allThreadsContinued = true;
370 }
371
372 return response;
373 });
374
375 session->registerHandler([this](const dap::NextRequest &req)
376 -> dap::ResponseOrError<dap::NextResponse> {
377 DAP_LOG("NextRequest receieved");
378
379 auto lock = ctx->lock();
380 auto thread = lock.get(Thread::ID(req.threadId));
381 if(!thread)
382 {
383 return dap::Error("Unknown thread %d", int(req.threadId));
384 }
385
386 thread->stepOver();
387 return dap::NextResponse();
388 });
389
390 session->registerHandler([this](const dap::StepInRequest &req)
391 -> dap::ResponseOrError<dap::StepInResponse> {
392 DAP_LOG("StepInRequest receieved");
393
394 auto lock = ctx->lock();
395 auto thread = lock.get(Thread::ID(req.threadId));
396 if(!thread)
397 {
398 return dap::Error("Unknown thread %d", int(req.threadId));
399 }
400
401 thread->stepIn();
402 return dap::StepInResponse();
403 });
404
405 session->registerHandler([this](const dap::StepOutRequest &req)
406 -> dap::ResponseOrError<dap::StepOutResponse> {
407 DAP_LOG("StepOutRequest receieved");
408
409 auto lock = ctx->lock();
410 auto thread = lock.get(Thread::ID(req.threadId));
411 if(!thread)
412 {
413 return dap::Error("Unknown thread %d", int(req.threadId));
414 }
415
416 thread->stepOut();
417 return dap::StepOutResponse();
418 });
419
420 session->registerHandler([this](const dap::EvaluateRequest &req)
421 -> dap::ResponseOrError<dap::EvaluateResponse> {
422 DAP_LOG("EvaluateRequest receieved");
423
424 auto lock = ctx->lock();
425 if(req.frameId.has_value())
426 {
427 auto frame = lock.get(Frame::ID(req.frameId.value(0)));
428 if(!frame)
429 {
430 return dap::Error("Unknown frame %d", int(req.frameId.value()));
431 }
432
433 auto fmt = FormatFlags::Default;
434 auto subfmt = FormatFlags::Default;
435
436 if(req.context.value("") == "hover")
437 {
438 subfmt.listPrefix = "\n";
439 subfmt.listSuffix = "";
440 subfmt.listDelimiter = "\n";
441 subfmt.listIndent = " ";
442 fmt.listPrefix = "";
443 fmt.listSuffix = "";
444 fmt.listDelimiter = "\n";
445 fmt.listIndent = "";
446 fmt.subListFmt = &subfmt;
447 }
448
449 dap::EvaluateResponse response;
450
451 std::vector<std::shared_ptr<vk::dbg::Variables>> variables = {
452 frame->locals->variables,
453 frame->arguments->variables,
454 frame->registers->variables,
455 frame->hovers->variables,
456 };
457
458 for(const auto &vars : variables)
459 {
460 if(auto val = vars->get(req.expression))
461 {
462 response.result = val->get(fmt);
463 response.type = val->type();
464 return response;
465 }
466 }
467
468 // HACK: VSCode does not appear to include the % in %123 tokens
469 // TODO: This might be a configuration problem of the SPIRV-Tools
470 // spirv-ls plugin. Investigate.
471 auto withPercent = "%" + req.expression;
472 for(const auto &vars : variables)
473 {
474 if(auto val = vars->get(withPercent))
475 {
476 response.result = val->get(fmt);
477 response.type = val->type();
478 return response;
479 }
480 }
481 }
482
483 return dap::Error("Could not evaluate expression");
484 });
485
486 session->registerHandler([](const dap::LaunchRequest &req) {
487 DAP_LOG("LaunchRequest receieved");
488 return dap::LaunchResponse();
489 });
490
491 marl::WaitGroup configurationDone(1);
492 session->registerHandler([=](const dap::ConfigurationDoneRequest &req) {
493 DAP_LOG("ConfigurationDoneRequest receieved");
494 configurationDone.done();
495 return dap::ConfigurationDoneResponse();
496 });
497
498 server->start(port, [&](const std::shared_ptr<dap::ReaderWriter> &rw) {
499 session->bind(rw);
500 ctx->addListener(this);
501 });
502
503 static bool waitForDebugger = getenv("VK_WAIT_FOR_DEBUGGER") != nullptr;
504 if(waitForDebugger)
505 {
506 printf("Waiting for debugger connection...\n");
507 configurationDone.wait();
508 printf("Debugger connection established\n");
509 }
510 }
511
~Impl()512 Server::Impl::~Impl()
513 {
514 ctx->removeListener(this);
515 server->stop();
516 }
517
onThreadStarted(ID<Thread> id)518 void Server::Impl::onThreadStarted(ID<Thread> id)
519 {
520 dap::ThreadEvent event;
521 event.reason = "started";
522 event.threadId = id.value();
523 session->send(event);
524 }
525
onThreadStepped(ID<Thread> id)526 void Server::Impl::onThreadStepped(ID<Thread> id)
527 {
528 dap::StoppedEvent event;
529 event.threadId = id.value();
530 event.reason = "step";
531 session->send(event);
532 }
533
onLineBreakpointHit(ID<Thread> id)534 void Server::Impl::onLineBreakpointHit(ID<Thread> id)
535 {
536 dap::StoppedEvent event;
537 event.threadId = id.value();
538 event.reason = "breakpoint";
539 session->send(event);
540 }
541
onFunctionBreakpointHit(ID<Thread> id)542 void Server::Impl::onFunctionBreakpointHit(ID<Thread> id)
543 {
544 dap::StoppedEvent event;
545 event.threadId = id.value();
546 event.reason = "function breakpoint";
547 session->send(event);
548 }
549
scope(Context::Lock & lock,const char * type,Scope * s)550 dap::Scope Server::Impl::scope(Context::Lock &lock, const char *type, Scope *s)
551 {
552 dap::Scope out;
553 // out.line = s->startLine;
554 // out.endLine = s->endLine;
555 out.source = source(s->file.get());
556 out.name = type;
557 out.presentationHint = type;
558 out.variablesReference = s->variables->id.value();
559 lock.track(s->variables);
560 return out;
561 }
562
source(File * file)563 dap::Source Server::Impl::source(File *file)
564 {
565 dap::Source out;
566 out.name = file->name;
567 if(file->isVirtual())
568 {
569 out.sourceReference = file->id.value();
570 }
571 out.path = file->path();
572 return out;
573 }
574
file(const dap::Source & source)575 std::shared_ptr<File> Server::Impl::file(const dap::Source &source)
576 {
577 auto lock = ctx->lock();
578 if(source.sourceReference.has_value())
579 {
580 auto id = source.sourceReference.value();
581 if(auto file = lock.get(File::ID(id)))
582 {
583 return file;
584 }
585 }
586
587 auto files = lock.files();
588 if(source.path.has_value())
589 {
590 auto path = source.path.value();
591 std::shared_ptr<File> out;
592 for(auto file : files)
593 {
594 if(file->path() == path)
595 {
596 out = file;
597 break;
598 }
599 }
600 return out;
601 }
602
603 if(source.name.has_value())
604 {
605 auto name = source.name.value();
606 std::shared_ptr<File> out;
607 for(auto file : files)
608 {
609 if(file->name == name)
610 {
611 out = file;
612 break;
613 }
614 }
615 return out;
616 }
617
618 return nullptr;
619 }
620
create(const std::shared_ptr<Context> & ctx,int port)621 std::shared_ptr<Server> Server::create(const std::shared_ptr<Context> &ctx, int port)
622 {
623 return std::make_shared<Server::Impl>(ctx, port);
624 }
625
626 } // namespace dbg
627 } // namespace vk
628