Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion docs/debugger.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,18 @@ Within each `THREAD` command block, you may use any of the following commands to
* `EXPECT LOCATION` \<file name\> \<line number\> [\<line source\>]

Verifies that the debugger is currently paused at the given line location.
The [\<line source\>] is an additional, optional check that verifies the line of the file reported by the debuggeer is as expected.
The [\<line source\>] is an additional, optional check that verifies the line of the file reported by the debugger is as expected.

* `EXPECT CALLSTACK`

Verifies that the debugger is currently paused with the given complete stack frame.
Each frame must be declared on a separate line, starting with the most nested call, and has the form:

\<function name\> [\<file name\> [\<line number\>]]

The [\<file name\>] and [\<line number\>] fields are additional, optionals checks that verify the file and line numbers reported by the debugger for the frame are as expected.

The list of stack frames is terminated with `END`.

* `EXPECT LOCAL` \<name\> `EQ` [\<value\>]

Expand Down
31 changes: 30 additions & 1 deletion src/amberscript/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1932,7 +1932,6 @@ Result Parser::ParseDebugThreadBody(debug::Thread* thread) {
}

thread->ExpectLocation(location, line_source);

} else if (token->AsString() == "LOCAL") {
auto name = tokenizer_->NextToken();
if (!name->IsString()) {
Expand All @@ -1953,7 +1952,37 @@ Result Parser::ParseDebugThreadBody(debug::Thread* thread) {
} else {
return Result("expected variable value");
}
} else if (token->AsString() == "CALLSTACK") {
std::vector<debug::StackFrame> stack;
for (auto tok = tokenizer_->NextToken(); tok->AsString() != "END";
tok = tokenizer_->NextToken()) {
if (tok->IsEOL()) {
continue;
}
debug::StackFrame frame;
if (!tok->IsString()) {
return Result("expected stack frame name");
}
frame.name = tok->AsString();

tok = tokenizer_->NextToken();
if (tok->IsString()) {
frame.location.file = tok->AsString();
tok = tokenizer_->NextToken();
if (tok->IsInteger()) {
frame.location.line = tok->AsUint32();
} else if (!tok->IsEOL()) {
return Result(
"expected end of line or stack frame location number");
}
} else if (!tok->IsEOL()) {
return Result(
"expected end of line or stack frame location file name");
}

stack.emplace_back(frame);
}
thread->ExpectCallstack(stack);
} else {
return Result("expected LOCATION or LOCAL");
}
Expand Down
4 changes: 4 additions & 0 deletions src/debug.cc
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ class ThreadScriptImpl : public ThreadScript {
[=](Thread* t) { t->ExpectLocation(location, line); });
}

void ExpectCallstack(const std::vector<StackFrame>& callstack) override {
sequence_.emplace_back([=](Thread* t) { t->ExpectCallstack(callstack); });
}

void ExpectLocal(const std::string& name, int64_t value) override {
sequence_.emplace_back([=](Thread* t) { t->ExpectLocal(name, value); });
}
Expand Down
17 changes: 15 additions & 2 deletions src/debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <functional>
#include <memory>
#include <string>
#include <vector>

#include "amber/result.h"

Expand All @@ -32,8 +33,14 @@ class ThreadScript;

/// Location holds a file path and a 1-based line number.
struct Location {
std::string file;
uint32_t line;
std::string file; // empty represents unspecified.
uint32_t line = 0; // 0 represents unspecified.
};

// StackFrame holds name and location of a stack frame.
struct StackFrame {
std::string name;
Location location;
};

/// Thread is the interface used to control a single debugger thread of
Expand Down Expand Up @@ -66,6 +73,12 @@ class Thread {
virtual void ExpectLocation(const Location& location,
const std::string& line) = 0;

/// ExpectCallstack verifies that the debugger is currently suspended for the
/// given thread of execution with the specified callstack.
/// callstack is ordered with the 0th element representing the most nested
/// call.
virtual void ExpectCallstack(const std::vector<StackFrame>& callstack) = 0;

/// ExpectLocal verifies that the local variable with the given name has the
/// expected value. |name| may contain `.` delimiters to index structure or
/// array types.
Expand Down
101 changes: 90 additions & 11 deletions src/vulkan/engine_vulkan_debugger.cc
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,20 @@ class Client {

// TopStackFrame retrieves the frame at the top of the thread's call stack.
// Returns true on success, false on error.
bool TopStackFrame(dap::integer threadId, dap::StackFrame* frame) {
bool TopStackFrame(dap::integer thread_id, dap::StackFrame* frame) {
std::vector<dap::StackFrame> stack;
if (!Callstack(thread_id, &stack)) {
return false;
}
*frame = stack.front();
return true;
}

// Callstack retrieves the thread's full call stack.
// Returns true on success, false on error.
bool Callstack(dap::integer thread_id, std::vector<dap::StackFrame>* stack) {
dap::StackTraceRequest request;
request.threadId = threadId;
request.threadId = thread_id;
auto response = session_->send(request).get();
if (response.error) {
onerror_(response.error.message);
Expand All @@ -230,7 +241,7 @@ class Client {
onerror_("Stack frame is empty");
return false;
}
*frame = response.response.stackFrames.front();
*stack = response.response.stackFrames;
return true;
}

Expand Down Expand Up @@ -519,7 +530,7 @@ class Thread : public debug::Thread {
int threadId,
int lane,
std::shared_ptr<const debug::ThreadScript> script)
: threadId_(threadId),
: thread_id_(threadId),
lane_(lane),
client_(session, [this](const std::string& err) { OnError(err); }) {
// The thread script runs concurrently with other debugger thread scripts.
Expand Down Expand Up @@ -549,28 +560,28 @@ class Thread : public debug::Thread {
void StepOver() override {
DEBUGGER_LOG("StepOver()");
dap::NextRequest request;
request.threadId = threadId_;
request.threadId = thread_id_;
client_.Send(request);
}

void StepIn() override {
DEBUGGER_LOG("StepIn()");
dap::StepInRequest request;
request.threadId = threadId_;
request.threadId = thread_id_;
client_.Send(request);
}

void StepOut() override {
DEBUGGER_LOG("StepOut()");
dap::StepOutRequest request;
request.threadId = threadId_;
request.threadId = thread_id_;
client_.Send(request);
}

void Continue() override {
DEBUGGER_LOG("Continue()");
dap::ContinueRequest request;
request.threadId = threadId_;
request.threadId = thread_id_;
client_.Send(request);
}

Expand All @@ -580,7 +591,7 @@ class Thread : public debug::Thread {
location.line);

dap::StackFrame frame;
if (!client_.TopStackFrame(threadId_, &frame)) {
if (!client_.TopStackFrame(thread_id_, &frame)) {
return;
}

Expand Down Expand Up @@ -608,6 +619,53 @@ class Thread : public debug::Thread {
}
}

void ExpectCallstack(
const std::vector<debug::StackFrame>& callstack) override {
DEBUGGER_LOG("ExpectCallstack()");

std::vector<dap::StackFrame> got_stack;
if (!client_.Callstack(thread_id_, &got_stack)) {
return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be an error? We expected a callstack but didn't receive on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callstack already records the error messages before returning false:

bool Callstack(dap::integer thread_id, std::vector<dap::StackFrame>* stack) {
dap::StackTraceRequest request;
request.threadId = thread_id;
auto response = session_->send(request).get();
if (response.error) {
onerror_(response.error.message);
return false;
}
if (response.response.stackFrames.size() == 0) {
onerror_("Stack frame is empty");
return false;
}
*stack = response.response.stackFrames;
return true;
}

}

std::stringstream ss;

size_t count = std::min(callstack.size(), got_stack.size());
for (size_t i = 0; i < count; i++) {
auto const& got_frame = got_stack[i];
auto const& want_frame = callstack[i];
bool ok = got_frame.name == want_frame.name;
if (ok && want_frame.location.file != "") {
ok = got_frame.source.has_value() &&
got_frame.source->name.value("") == want_frame.location.file;
}
if (ok && want_frame.location.line != 0) {
ok = got_frame.line == static_cast<int>(want_frame.location.line);
}
if (!ok) {
ss << "Unexpected stackframe at frame " << i
<< "\nGot: " << FrameString(got_frame)
<< "\nExpected: " << FrameString(want_frame) << "\n";
}
}

if (got_stack.size() > callstack.size()) {
ss << "Callstack has an additional "
<< (got_stack.size() - callstack.size()) << " unexpected frames\n";
} else if (callstack.size() > got_stack.size()) {
ss << "Callstack is missing " << (callstack.size() - got_stack.size())
<< " frames\n";
}

if (ss.str().size() > 0) {
ss << "Full callstack:\n";
for (auto& frame : got_stack) {
ss << " " << FrameString(frame) << "\n";
}
OnError(ss.str());
}
}

void ExpectLocal(const std::string& name, int64_t value) override {
DEBUGGER_LOG("ExpectLocal('%s', %d)", name.c_str(), (int)value);
ExpectLocalT(name, value);
Expand All @@ -626,7 +684,7 @@ class Thread : public debug::Thread {
template <typename T>
void ExpectLocalT(const std::string& name, const T& expect) {
dap::StackFrame frame;
if (!client_.TopStackFrame(threadId_, &frame)) {
if (!client_.TopStackFrame(thread_id_, &frame)) {
return;
}

Expand Down Expand Up @@ -677,7 +735,28 @@ class Thread : public debug::Thread {
error_ += err;
}

const dap::integer threadId_;
std::string FrameString(const dap::StackFrame& frame) {
std::stringstream ss;
ss << frame.name;
if (frame.source.has_value() && frame.source->name.has_value()) {
ss << " " << frame.source->name.value() << ":" << frame.line;
}
return ss.str();
}

std::string FrameString(const debug::StackFrame& frame) {
std::stringstream ss;
ss << frame.name;
if (frame.location.file != "") {
ss << " " << frame.location.file;
if (frame.location.line != 0) {
ss << ":" << frame.location.line;
}
}
return ss.str();
}

const dap::integer thread_id_;
const int lane_;
Client client_;
std::thread thread_;
Expand Down
4 changes: 4 additions & 0 deletions tests/cases/debugger_hlsl_line_stepping.amber
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,10 @@ CLEAR pipeline

DEBUG pipeline DRAW_ARRAY AS TRIANGLE_LIST START_IDX 0 COUNT 6
THREAD VERTEX_INDEX 2
EXPECT CALLSTACK
"main" "simple_vs.hlsl" 9
"VertexShader"
END
EXPECT LOCATION "simple_vs.hlsl" 9 " VS_OUTPUT vout;"
EXPECT LOCAL "pos.x" EQ -1.007874
EXPECT LOCAL "pos.y" EQ 1.000000
Expand Down
6 changes: 6 additions & 0 deletions tests/cases/debugger_spirv_line_stepping.amber
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ END
# there are no race conditions.
DEBUG pipeline 1 1 1
THREAD GLOBAL_INVOCATION_ID 2 0 0
EXPECT CALLSTACK
"ComputeShader" "ComputeShader0.spvasm" 20
END
EXPECT LOCATION "ComputeShader0.spvasm" 20 "%5 = OpVariable %11 Uniform"
STEP_IN
EXPECT LOCATION "ComputeShader0.spvasm" 25 "%2 = OpVariable %15 Input"
Expand All @@ -109,6 +112,9 @@ DEBUG pipeline 1 1 1
STEP_IN
EXPECT LOCATION "ComputeShader0.spvasm" 36 "OpStore %23 %22"
STEP_IN
EXPECT CALLSTACK
"ComputeShader" "ComputeShader0.spvasm" 37
END
EXPECT LOCATION "ComputeShader0.spvasm" 37 "OpReturn"
CONTINUE
END
Expand Down