| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file LICENSE.rst or https://cmake.org/licensing for details. */
- #include "cmConfigure.h" // IWYU pragma: keep
- #include "cmDebuggerAdapter.h"
- #include <algorithm>
- #include <climits>
- #include <condition_variable>
- #include <cstdint>
- #include <functional>
- #include <iostream>
- #include <stdexcept>
- #include <utility>
- #include <cm/memory>
- #include <cm/optional>
- #include <cm3p/cppdap/io.h> // IWYU pragma: keep
- #include <cm3p/cppdap/protocol.h>
- #include <cm3p/cppdap/session.h>
- #include "cmDebuggerBreakpointManager.h"
- #include "cmDebuggerExceptionManager.h"
- #include "cmDebuggerProtocol.h"
- #include "cmDebuggerSourceBreakpoint.h" // IWYU pragma: keep
- #include "cmDebuggerStackFrame.h"
- #include "cmDebuggerThread.h"
- #include "cmDebuggerThreadManager.h"
- #include "cmListFileCache.h"
- #include "cmMakefile.h"
- #include "cmValue.h"
- #include "cmVersionConfig.h"
- #include <cmcppdap/include/dap/optional.h>
- #include <cmcppdap/include/dap/types.h>
- namespace cmDebugger {
- // Event provides a basic wait and signal synchronization primitive.
- class SyncEvent
- {
- public:
- // Wait() blocks until the event is fired.
- void Wait()
- {
- std::unique_lock<std::mutex> lock(Mutex);
- Cv.wait(lock, [&] { return Fired; });
- }
- // Fire() sets signals the event, and unblocks any calls to Wait().
- void Fire()
- {
- std::unique_lock<std::mutex> lock(Mutex);
- Fired = true;
- Cv.notify_all();
- }
- private:
- std::mutex Mutex;
- std::condition_variable Cv;
- bool Fired = false;
- };
- class Semaphore
- {
- public:
- Semaphore(int count_ = 0)
- : Count(count_)
- {
- }
- void Notify()
- {
- std::unique_lock<std::mutex> lock(Mutex);
- Count++;
- // notify the waiting thread
- Cv.notify_one();
- }
- void Wait()
- {
- std::unique_lock<std::mutex> lock(Mutex);
- while (Count == 0) {
- // wait on the mutex until notify is called
- Cv.wait(lock);
- }
- Count--;
- }
- private:
- std::mutex Mutex;
- std::condition_variable Cv;
- int Count;
- };
- cmDebuggerAdapter::cmDebuggerAdapter(
- std::shared_ptr<cmDebuggerConnection> connection,
- std::string const& dapLogPath)
- : cmDebuggerAdapter(std::move(connection),
- dapLogPath.empty()
- ? cm::nullopt
- : cm::optional<std::shared_ptr<dap::Writer>>(
- dap::file(dapLogPath.c_str())))
- {
- }
- cmDebuggerAdapter::cmDebuggerAdapter(
- std::shared_ptr<cmDebuggerConnection> connection,
- cm::optional<std::shared_ptr<dap::Writer>> logger)
- : Connection(std::move(connection))
- , SessionActive(true)
- , DisconnectEvent(cm::make_unique<SyncEvent>())
- , ConfigurationDoneEvent(cm::make_unique<SyncEvent>())
- , ContinueSem(cm::make_unique<Semaphore>())
- , ThreadManager(cm::make_unique<cmDebuggerThreadManager>())
- {
- if (logger.has_value()) {
- SessionLog = std::move(logger.value());
- }
- ClearStepRequests();
- Session = dap::Session::create();
- BreakpointManager =
- cm::make_unique<cmDebuggerBreakpointManager>(Session.get());
- ExceptionManager =
- cm::make_unique<cmDebuggerExceptionManager>(Session.get());
- // Handle errors reported by the Session. These errors include protocol
- // parsing errors and receiving messages with no handler.
- Session->onError([this](char const* msg) {
- if (SessionLog) {
- dap::writef(SessionLog, "dap::Session error: %s\n", msg);
- }
- std::cout << "[CMake Debugger] DAP session error: " << msg << std::endl;
- BreakpointManager->ClearAll();
- ExceptionManager->ClearAll();
- ClearStepRequests();
- ContinueSem->Notify();
- DisconnectEvent->Fire();
- SessionActive.store(false);
- });
- // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
- Session->registerHandler([this](dap::CMakeInitializeRequest const& req) {
- SupportsVariableType = req.supportsVariableType.value(false);
- dap::CMakeInitializeResponse response;
- response.supportsConfigurationDoneRequest = true;
- response.supportsValueFormattingOptions = true;
- response.cmakeVersion.major = CMake_VERSION_MAJOR;
- response.cmakeVersion.minor = CMake_VERSION_MINOR;
- response.cmakeVersion.patch = CMake_VERSION_PATCH;
- response.cmakeVersion.full = CMake_VERSION;
- ExceptionManager->HandleInitializeRequest(response);
- return response;
- });
- // https://microsoft.github.io/debug-adapter-protocol/specification#Events_Initialized
- Session->registerSentHandler(
- [&](dap::ResponseOrError<dap::CMakeInitializeResponse> const&) {
- Session->send(dap::InitializedEvent());
- });
- // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Threads
- Session->registerHandler([this](dap::ThreadsRequest /*unused*/) {
- std::unique_lock<std::mutex> lock(Mutex);
- dap::ThreadsResponse response;
- // If a client requests threads during shutdown (like after receiving the
- // thread exited event), DefaultThread won't be set.
- if (DefaultThread) {
- dap::Thread thread;
- thread.id = DefaultThread->GetId();
- thread.name = DefaultThread->GetName();
- response.threads.push_back(thread);
- }
- return response;
- });
- // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StackTrace
- Session->registerHandler([this](dap::StackTraceRequest const& request)
- -> dap::ResponseOrError<dap::StackTraceResponse> {
- std::unique_lock<std::mutex> lock(Mutex);
- cm::optional<dap::StackTraceResponse> response =
- ThreadManager->GetThreadStackTraceResponse(request);
- if (response.has_value()) {
- return response.value();
- }
- return dap::Error("Unknown threadId '%d'", int(request.threadId));
- });
- // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Scopes
- Session->registerHandler([this](dap::ScopesRequest request)
- -> dap::ResponseOrError<dap::ScopesResponse> {
- std::unique_lock<std::mutex> lock(Mutex);
- return DefaultThread->GetScopesResponse(request.frameId,
- SupportsVariableType);
- });
- // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Variables
- Session->registerHandler([this](dap::VariablesRequest const& request)
- -> dap::ResponseOrError<dap::VariablesResponse> {
- return DefaultThread->GetVariablesResponse(request);
- });
- // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Pause
- Session->registerHandler([this](dap::PauseRequest /*unused*/) {
- PauseRequest.store(true);
- return dap::PauseResponse();
- });
- // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Continue
- Session->registerHandler([this](dap::ContinueRequest const& req) {
- (void)req;
- ContinueSem->Notify();
- return dap::ContinueResponse();
- });
- // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Next
- Session->registerHandler([this](dap::NextRequest const& req) {
- (void)req;
- NextStepFrom.store(DefaultThread->GetStackFrameSize());
- ContinueSem->Notify();
- return dap::NextResponse();
- });
- // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepIn
- Session->registerHandler([this](dap::StepInRequest const& req) {
- (void)req;
- // This would stop after stepped in, single line stepped or stepped out.
- StepInRequest.store(true);
- ContinueSem->Notify();
- return dap::StepInResponse();
- });
- // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepOut
- Session->registerHandler([this](dap::StepOutRequest const& req) {
- (void)req;
- StepOutDepth.store(DefaultThread->GetStackFrameSize() - 1);
- ContinueSem->Notify();
- return dap::StepOutResponse();
- });
- // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Launch
- Session->registerHandler([](dap::LaunchRequest const& req) {
- (void)req;
- return dap::LaunchResponse();
- });
- // Handler for disconnect requests
- Session->registerHandler([this](dap::DisconnectRequest const& request) {
- (void)request;
- BreakpointManager->ClearAll();
- ExceptionManager->ClearAll();
- ClearStepRequests();
- ContinueSem->Notify();
- DisconnectEvent->Fire();
- SessionActive.store(false);
- return dap::DisconnectResponse();
- });
- Session->registerHandler([this](dap::EvaluateRequest const& request) {
- dap::EvaluateResponse response;
- if (request.frameId.has_value()) {
- std::shared_ptr<cmDebuggerStackFrame> frame =
- DefaultThread->GetStackFrame(request.frameId.value());
- auto var = frame->GetMakefile()->GetDefinition(request.expression);
- if (var) {
- response.type = "string";
- response.result = var;
- return response;
- }
- }
- return response;
- });
- // The ConfigurationDone request is made by the client once all configuration
- // requests have been made.
- // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_ConfigurationDone
- Session->registerHandler([this](dap::ConfigurationDoneRequest /*unused*/) {
- ConfigurationDoneEvent->Fire();
- return dap::ConfigurationDoneResponse();
- });
- std::string errorMessage;
- if (!Connection->StartListening(errorMessage)) {
- throw std::runtime_error(errorMessage);
- }
- // Connect to the client. Write a well-known message to stdout so that
- // clients know it is safe to attempt to connect.
- std::cout << "Waiting for debugger client to connect..." << std::endl;
- Connection->WaitForConnection();
- std::cout << "Debugger client connected." << std::endl;
- if (SessionLog) {
- Session->connect(spy(Connection->GetReader(), SessionLog),
- spy(Connection->GetWriter(), SessionLog));
- } else {
- Session->connect(Connection->GetReader(), Connection->GetWriter());
- }
- // Start the processing thread.
- SessionThread = std::thread([this] {
- while (SessionActive.load()) {
- if (auto payload = Session->getPayload()) {
- payload();
- }
- }
- });
- ConfigurationDoneEvent->Wait();
- DefaultThread = ThreadManager->StartThread("CMake script");
- dap::ThreadEvent threadEvent;
- threadEvent.reason = "started";
- threadEvent.threadId = DefaultThread->GetId();
- Session->send(threadEvent);
- }
- cmDebuggerAdapter::~cmDebuggerAdapter()
- {
- if (SessionThread.joinable()) {
- SessionThread.join();
- }
- Session.reset(nullptr);
- if (SessionLog) {
- SessionLog->close();
- }
- }
- void cmDebuggerAdapter::ReportExitCode(int exitCode)
- {
- ThreadManager->EndThread(DefaultThread);
- dap::ThreadEvent threadEvent;
- threadEvent.reason = "exited";
- threadEvent.threadId = DefaultThread->GetId();
- DefaultThread.reset();
- dap::ExitedEvent exitEvent;
- exitEvent.exitCode = exitCode;
- dap::TerminatedEvent terminatedEvent;
- if (SessionActive.load()) {
- Session->send(threadEvent);
- Session->send(exitEvent);
- Session->send(terminatedEvent);
- }
- // Wait until disconnected or error.
- DisconnectEvent->Wait();
- }
- void cmDebuggerAdapter::OnFileParsedSuccessfully(
- std::string const& sourcePath,
- std::vector<cmListFileFunction> const& functions)
- {
- BreakpointManager->SourceFileLoaded(sourcePath, functions);
- }
- void cmDebuggerAdapter::OnBeginFunctionCall(cmMakefile* mf,
- std::string const& sourcePath,
- cmListFileFunction const& lff)
- {
- std::unique_lock<std::mutex> lock(Mutex);
- DefaultThread->PushStackFrame(mf, sourcePath, lff);
- if (lff.Line() == 0) {
- // File just loaded, continue to first valid function call.
- return;
- }
- auto hits = BreakpointManager->GetBreakpoints(sourcePath, lff.Line());
- lock.unlock();
- bool waitSem = false;
- dap::StoppedEvent stoppedEvent;
- stoppedEvent.allThreadsStopped = true;
- stoppedEvent.threadId = DefaultThread->GetId();
- if (!hits.empty()) {
- ClearStepRequests();
- waitSem = true;
- dap::array<dap::integer> hitBreakpoints;
- hitBreakpoints.resize(hits.size());
- std::transform(hits.begin(), hits.end(), hitBreakpoints.begin(),
- [&](int64_t id) { return dap::integer(id); });
- stoppedEvent.reason = "breakpoint";
- stoppedEvent.hitBreakpointIds = hitBreakpoints;
- }
- if (long(DefaultThread->GetStackFrameSize()) <= NextStepFrom.load() ||
- StepInRequest.load() ||
- long(DefaultThread->GetStackFrameSize()) <= StepOutDepth.load()) {
- ClearStepRequests();
- waitSem = true;
- stoppedEvent.reason = "step";
- }
- if (PauseRequest.load()) {
- ClearStepRequests();
- waitSem = true;
- stoppedEvent.reason = "pause";
- }
- if (waitSem) {
- Session->send(stoppedEvent);
- ContinueSem->Wait();
- }
- }
- void cmDebuggerAdapter::OnEndFunctionCall()
- {
- DefaultThread->PopStackFrame();
- }
- static std::shared_ptr<cmListFileFunction> listFileFunction;
- void cmDebuggerAdapter::OnBeginFileParse(cmMakefile* mf,
- std::string const& sourcePath)
- {
- std::unique_lock<std::mutex> lock(Mutex);
- listFileFunction = std::make_shared<cmListFileFunction>(
- sourcePath, 0, 0, std::vector<cmListFileArgument>());
- DefaultThread->PushStackFrame(mf, sourcePath, *listFileFunction);
- }
- void cmDebuggerAdapter::OnEndFileParse()
- {
- DefaultThread->PopStackFrame();
- listFileFunction = nullptr;
- }
- void cmDebuggerAdapter::OnMessageOutput(MessageType t, std::string const& text)
- {
- cm::optional<dap::StoppedEvent> stoppedEvent =
- ExceptionManager->RaiseExceptionIfAny(t, text);
- if (stoppedEvent.has_value()) {
- stoppedEvent->threadId = DefaultThread->GetId();
- Session->send(*stoppedEvent);
- ContinueSem->Wait();
- }
- }
- void cmDebuggerAdapter::ClearStepRequests()
- {
- NextStepFrom.store(INT_MIN);
- StepInRequest.store(false);
- StepOutDepth.store(INT_MIN);
- PauseRequest.store(false);
- }
- } // namespace cmDebugger
|