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 EventListener
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(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 auto lock = ctx->lock();
104 dap::SetFunctionBreakpointsResponse response;
105 for(auto const &bp : req.breakpoints)
106 {
107 DAP_LOG("Setting breakpoint for function '%s'", bp.name.c_str());
108 lock.addFunctionBreakpoint(bp.name.c_str());
109 response.breakpoints.push_back({});
110 }
111 return response;
112 });
113
114 session->registerHandler(
115 [this](const dap::SetBreakpointsRequest &req)
116 -> dap::ResponseOrError<dap::SetBreakpointsResponse> {
117 DAP_LOG("SetBreakpointsRequest receieved");
118 bool verified = false;
119
120 size_t numBreakpoints = 0;
121 if(req.breakpoints.has_value())
122 {
123 auto const &breakpoints = req.breakpoints.value();
124 numBreakpoints = breakpoints.size();
125 if(auto file = this->file(req.source))
126 {
127 file->clearBreakpoints();
128 for(auto const &bp : breakpoints)
129 {
130 file->addBreakpoint(bp.line);
131 }
132 verified = true;
133 }
134 else if(req.source.name.has_value())
135 {
136 std::vector<int> lines;
137 lines.reserve(breakpoints.size());
138 for(auto const &bp : breakpoints)
139 {
140 lines.push_back(bp.line);
141 }
142 ctx->lock().addPendingBreakpoints(req.source.name.value(),
143 lines);
144 }
145 }
146
147 dap::SetBreakpointsResponse response;
148 for(size_t i = 0; i < numBreakpoints; i++)
149 {
150 dap::Breakpoint bp;
151 bp.verified = verified;
152 bp.source = req.source;
153 response.breakpoints.push_back(bp);
154 }
155 return response;
156 });
157
158 session->registerHandler([this](const dap::ThreadsRequest &req) {
159 DAP_LOG("ThreadsRequest receieved");
160 auto lock = ctx->lock();
161 dap::ThreadsResponse response;
162 for(auto thread : lock.threads())
163 {
164 std::string name = thread->name();
165 if(clientIsVisualStudio)
166 {
167 // WORKAROUND: https://github.com/microsoft/VSDebugAdapterHost/issues/15
168 for(size_t i = 0; i < name.size(); i++)
169 {
170 if(name[i] == '.')
171 {
172 name[i] = '_';
173 }
174 }
175 }
176
177 dap::Thread out;
178 out.id = thread->id.value();
179 out.name = name;
180 response.threads.push_back(out);
181 };
182 return response;
183 });
184
185 session->registerHandler(
186 [this](const dap::StackTraceRequest &req)
187 -> dap::ResponseOrError<dap::StackTraceResponse> {
188 DAP_LOG("StackTraceRequest receieved");
189
190 auto lock = ctx->lock();
191 auto thread = lock.get(Thread::ID(req.threadId));
192 if(!thread)
193 {
194 return dap::Error("Thread %d not found", req.threadId);
195 }
196
197 auto stack = thread->stack();
198
199 dap::StackTraceResponse response;
200 response.totalFrames = stack.size();
201 response.stackFrames.reserve(stack.size());
202 for(int i = static_cast<int>(stack.size()) - 1; i >= 0; i--)
203 {
204 auto const &frame = stack[i];
205 auto const &loc = frame.location;
206 dap::StackFrame sf;
207 sf.column = 0;
208 sf.id = frame.id.value();
209 sf.name = frame.function;
210 sf.line = loc.line;
211 if(loc.file)
212 {
213 sf.source = source(loc.file.get());
214 }
215 response.stackFrames.emplace_back(std::move(sf));
216 }
217 return response;
218 });
219
220 session->registerHandler([this](const dap::ScopesRequest &req)
221 -> dap::ResponseOrError<dap::ScopesResponse> {
222 DAP_LOG("ScopesRequest receieved");
223
224 auto lock = ctx->lock();
225 auto frame = lock.get(Frame::ID(req.frameId));
226 if(!frame)
227 {
228 return dap::Error("Frame %d not found", req.frameId);
229 }
230
231 dap::ScopesResponse response;
232 response.scopes = {
233 scope("locals", frame->locals.get()),
234 scope("arguments", frame->arguments.get()),
235 scope("registers", frame->registers.get()),
236 };
237 return response;
238 });
239
240 session->registerHandler([this](const dap::VariablesRequest &req)
241 -> dap::ResponseOrError<dap::VariablesResponse> {
242 DAP_LOG("VariablesRequest receieved");
243
244 auto lock = ctx->lock();
245 auto vars = lock.get(VariableContainer::ID(req.variablesReference));
246 if(!vars)
247 {
248 return dap::Error("VariablesReference %d not found",
249 int(req.variablesReference));
250 }
251
252 dap::VariablesResponse response;
253 vars->foreach(req.start.value(0), req.count.value(~0), [&](const Variable &v) {
254 dap::Variable out;
255 out.evaluateName = v.name;
256 out.name = v.name;
257 out.type = v.value->type()->string();
258 out.value = v.value->string();
259 if(v.value->type()->kind == Kind::VariableContainer)
260 {
261 auto const vc = static_cast<const VariableContainer *>(v.value.get());
262 out.variablesReference = vc->id.value();
263 }
264 response.variables.push_back(out);
265 });
266 return response;
267 });
268
269 session->registerHandler([this](const dap::SourceRequest &req)
270 -> dap::ResponseOrError<dap::SourceResponse> {
271 DAP_LOG("SourceRequest receieved");
272
273 dap::SourceResponse response;
274 uint64_t id = req.sourceReference;
275
276 auto lock = ctx->lock();
277 auto file = lock.get(File::ID(id));
278 if(!file)
279 {
280 return dap::Error("Source %d not found", id);
281 }
282 response.content = file->source;
283 return response;
284 });
285
286 session->registerHandler([this](const dap::PauseRequest &req)
287 -> dap::ResponseOrError<dap::PauseResponse> {
288 DAP_LOG("PauseRequest receieved");
289
290 dap::StoppedEvent event;
291 event.reason = "pause";
292
293 auto lock = ctx->lock();
294 if(auto thread = lock.get(Thread::ID(req.threadId)))
295 {
296 thread->pause();
297 event.threadId = req.threadId;
298 }
299 else
300 {
301 auto threads = lock.threads();
302 for(auto thread : threads)
303 {
304 thread->pause();
305 }
306 event.allThreadsStopped = true;
307
308 // Workaround for
309 // https://github.com/microsoft/VSDebugAdapterHost/issues/11
310 if(clientIsVisualStudio)
311 {
312 for(auto thread : threads)
313 {
314 event.threadId = thread->id.value();
315 break;
316 }
317 }
318 }
319
320 session->send(event);
321
322 dap::PauseResponse response;
323 return response;
324 });
325
326 session->registerHandler([this](const dap::ContinueRequest &req)
327 -> dap::ResponseOrError<dap::ContinueResponse> {
328 DAP_LOG("ContinueRequest receieved");
329
330 dap::ContinueResponse response;
331
332 auto lock = ctx->lock();
333 if(auto thread = lock.get(Thread::ID(req.threadId)))
334 {
335 thread->resume();
336 response.allThreadsContinued = false;
337 }
338 else
339 {
340 for(auto it : lock.threads())
341 {
342 thread->resume();
343 }
344 response.allThreadsContinued = true;
345 }
346
347 return response;
348 });
349
350 session->registerHandler([this](const dap::NextRequest &req)
351 -> dap::ResponseOrError<dap::NextResponse> {
352 DAP_LOG("NextRequest receieved");
353
354 auto lock = ctx->lock();
355 auto thread = lock.get(Thread::ID(req.threadId));
356 if(!thread)
357 {
358 return dap::Error("Unknown thread %d", int(req.threadId));
359 }
360
361 thread->stepOver();
362 return dap::NextResponse();
363 });
364
365 session->registerHandler([this](const dap::StepInRequest &req)
366 -> dap::ResponseOrError<dap::StepInResponse> {
367 DAP_LOG("StepInRequest receieved");
368
369 auto lock = ctx->lock();
370 auto thread = lock.get(Thread::ID(req.threadId));
371 if(!thread)
372 {
373 return dap::Error("Unknown thread %d", int(req.threadId));
374 }
375
376 thread->stepIn();
377 return dap::StepInResponse();
378 });
379
380 session->registerHandler([this](const dap::StepOutRequest &req)
381 -> dap::ResponseOrError<dap::StepOutResponse> {
382 DAP_LOG("StepOutRequest receieved");
383
384 auto lock = ctx->lock();
385 auto thread = lock.get(Thread::ID(req.threadId));
386 if(!thread)
387 {
388 return dap::Error("Unknown thread %d", int(req.threadId));
389 }
390
391 thread->stepOut();
392 return dap::StepOutResponse();
393 });
394
395 session->registerHandler([this](const dap::EvaluateRequest &req)
396 -> dap::ResponseOrError<dap::EvaluateResponse> {
397 DAP_LOG("EvaluateRequest receieved");
398
399 auto lock = ctx->lock();
400 if(req.frameId.has_value())
401 {
402 auto frame = lock.get(Frame::ID(req.frameId.value(0)));
403 if(!frame)
404 {
405 return dap::Error("Unknown frame %d", int(req.frameId.value()));
406 }
407
408 auto fmt = FormatFlags::Default;
409 auto subfmt = FormatFlags::Default;
410
411 if(req.context.value("") == "hover")
412 {
413 subfmt.listPrefix = "\n";
414 subfmt.listSuffix = "";
415 subfmt.listDelimiter = "\n";
416 subfmt.listIndent = " ";
417 fmt.listPrefix = "";
418 fmt.listSuffix = "";
419 fmt.listDelimiter = "\n";
420 fmt.listIndent = "";
421 fmt.subListFmt = &subfmt;
422 }
423
424 dap::EvaluateResponse response;
425 auto findHandler = [&](const Variable &var) {
426 response.result = var.value->string(fmt);
427 response.type = var.value->type()->string();
428 };
429
430 std::vector<std::shared_ptr<vk::dbg::VariableContainer>> variables = {
431 frame->locals->variables,
432 frame->arguments->variables,
433 frame->registers->variables,
434 frame->hovers->variables,
435 };
436
437 for(auto const &vars : variables)
438 {
439 if(vars->find(req.expression, findHandler)) { return response; }
440 }
441
442 // HACK: VSCode does not appear to include the % in %123 tokens
443 // TODO: This might be a configuration problem of the SPIRV-Tools
444 // spirv-ls plugin. Investigate.
445 auto withPercent = "%" + req.expression;
446 for(auto const &vars : variables)
447 {
448 if(vars->find(withPercent, findHandler)) { return response; }
449 }
450 }
451
452 return dap::Error("Could not evaluate expression");
453 });
454
455 session->registerHandler([](const dap::LaunchRequest &req) {
456 DAP_LOG("LaunchRequest receieved");
457 return dap::LaunchResponse();
458 });
459
460 marl::WaitGroup configurationDone(1);
461 session->registerHandler([=](const dap::ConfigurationDoneRequest &req) {
462 DAP_LOG("ConfigurationDoneRequest receieved");
463 configurationDone.done();
464 return dap::ConfigurationDoneResponse();
465 });
466
467 server->start(port, [&](const std::shared_ptr<dap::ReaderWriter> &rw) {
468 session->bind(rw);
469 ctx->addListener(this);
470 });
471
472 static bool waitForDebugger = getenv("VK_WAIT_FOR_DEBUGGER") != nullptr;
473 if(waitForDebugger)
474 {
475 printf("Waiting for debugger connection...\n");
476 configurationDone.wait();
477 printf("Debugger connection established\n");
478 }
479 }
480
~Impl()481 Server::Impl::~Impl()
482 {
483 ctx->removeListener(this);
484 server->stop();
485 }
486
onThreadStarted(ID<Thread> id)487 void Server::Impl::onThreadStarted(ID<Thread> id)
488 {
489 dap::ThreadEvent event;
490 event.reason = "started";
491 event.threadId = id.value();
492 session->send(event);
493 }
494
onThreadStepped(ID<Thread> id)495 void Server::Impl::onThreadStepped(ID<Thread> id)
496 {
497 dap::StoppedEvent event;
498 event.threadId = id.value();
499 event.reason = "step";
500 session->send(event);
501 }
502
onLineBreakpointHit(ID<Thread> id)503 void Server::Impl::onLineBreakpointHit(ID<Thread> id)
504 {
505 dap::StoppedEvent event;
506 event.threadId = id.value();
507 event.reason = "breakpoint";
508 session->send(event);
509 }
510
onFunctionBreakpointHit(ID<Thread> id)511 void Server::Impl::onFunctionBreakpointHit(ID<Thread> id)
512 {
513 dap::StoppedEvent event;
514 event.threadId = id.value();
515 event.reason = "function breakpoint";
516 session->send(event);
517 }
518
scope(const char * type,Scope * s)519 dap::Scope Server::Impl::scope(const char *type, Scope *s)
520 {
521 dap::Scope out;
522 // out.line = s->startLine;
523 // out.endLine = s->endLine;
524 out.source = source(s->file.get());
525 out.name = type;
526 out.presentationHint = type;
527 out.variablesReference = s->variables->id.value();
528 return out;
529 }
530
source(File * file)531 dap::Source Server::Impl::source(File *file)
532 {
533 dap::Source out;
534 out.name = file->name;
535 if(file->isVirtual())
536 {
537 out.sourceReference = file->id.value();
538 }
539 else
540 {
541 out.path = file->path();
542 }
543 return out;
544 }
545
file(const dap::Source & source)546 std::shared_ptr<File> Server::Impl::file(const dap::Source &source)
547 {
548 auto lock = ctx->lock();
549 if(source.sourceReference.has_value())
550 {
551 auto id = source.sourceReference.value();
552 if(auto file = lock.get(File::ID(id)))
553 {
554 return file;
555 }
556 }
557
558 auto files = lock.files();
559 if(source.path.has_value())
560 {
561 auto path = source.path.value();
562 std::shared_ptr<File> out;
563 for(auto file : files)
564 {
565 if(file->path() == path)
566 {
567 out = file;
568 break;
569 }
570 }
571 return out;
572 }
573
574 if(source.name.has_value())
575 {
576 auto name = source.name.value();
577 std::shared_ptr<File> out;
578 for(auto file : files)
579 {
580 if(file->name == name)
581 {
582 out = file;
583 break;
584 }
585 }
586 return out;
587 }
588
589 return nullptr;
590 }
591
create(const std::shared_ptr<Context> & ctx,int port)592 std::shared_ptr<Server> Server::create(const std::shared_ptr<Context> &ctx, int port)
593 {
594 return std::make_shared<Server::Impl>(ctx, port);
595 }
596
597 } // namespace dbg
598 } // namespace vk