Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Behavior Cleanup #14

Merged
merged 10 commits into from
Nov 14, 2023
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ jobs:
- run: npm run lint
- run: npm run check-format
- run: npm run compile
- run: npm run build
- run: npm test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
out
dist
extension/dist
node_modules
.vscode-test/
npm-debug.log
Expand Down
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionDevelopmentPath=${workspaceFolder}/extension",
"${workspaceFolder}/sampleWorkspace"
],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
Expand Down
4 changes: 3 additions & 1 deletion .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.travis.yml
appveyor.yml
src/**/*
extension/src/**/*
out/tests/**/*
**/*.js.map
build
Expand All @@ -11,4 +12,5 @@ node_modules
tsconfig.json
out
sampleWebWorkerWorkspace
sampleWorkspace
sampleWorkspace
images
75 changes: 75 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Features

This document describes features that the AVM debugger supports.

Screenshots and the exact features are based on the VS Code client.

## View transaction groups being debugged

Every execution starts with a top level transaction group.

![A transaction group being debugged](images/transaction%20group.png)

## Step into programs executions

Both LogicSig and application programs associated with transactions can be stepped into. Source maps
are used to show the original source code.

![A program being debugged](images/app%20call.png)

## Step into inner transactions

If an application spawns an inner transaction, the debugger can step into it as well.

![An inner transaction group being debugged](images/inner%20transaction%20group.png)

Additionally, the entire call stack can be seen, showing the depth of the inner transaction or inner
application being executed. Each frame of the call stack can be inspected to view the higher level
state.

![Call stack](images/call%20stack.png)

## Step-by-step debugging

The debugger supports step into, over, out, and back. Yes, you can step back!

## Breakpoint support

Breakpoints can be set in program source files. The debugger will pause when code corresponding to a
breakpoint is about to be executed. Since multiple opcodes can be in a single line of source code,
breakpoints can be set on specific columns.

![Breakpoints in program code](images/breakpoints.png)

## Error reporting

Execution errors will be reported by the debugger. Since any error stops the execution of a
transaction group, the debugger will not allow you to advance after an error. You can however step
backwards to inspect what happened prior to the error.

![An error in the debugger](images/error.png)

## Inspect program state

The debugger allows you to inspect the state of the program being debugged. This includes the PC
(program counter), stack, and scratch space.

![Inspecting program state](images/program%20state%20variables.png)

Byte arrays can be displayed in a variety of formats, including base64, hex, and UTF-8.

![Inspecting byte arrays in program state](images/program%20state%20variables%20bytes%20expanded.png)

Additionally, specific values can be added to the watch list.

Since values relative to the top of the stack are often important, negative indexing is supported to
look up values relative to the top of the stack.

![Watched values](images/watch%20values.png)

## Inspect application state

The debugger also allows you to inspect and watch any available application state from the
execution. Such state includes application boxes, global, and local state.

![Inspecting application state variables](images/app%20state%20variables%20expanded.png)
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# AVM Debugger

## Summary

This repo contains an AVM debugger which adheres to the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/).
This protocol is used by a variety of clients, and this repo additionally
implements a basic client for VS Code.

Unlike traditional debuggers, which typically execute code as it is being
debugged, this debugger operates on an execution trace. The trace is created by
the [algod simulate API](https://developer.algorand.org/docs/rest-apis/algod/#post-v2transactionssimulate).
This debugger is not responsible for compiling programs, assembling transaction groups, or executing
transactions/programs. It is only responsible for replaying the execution trace, which must already
exist.

This code is based on the [`vscode-mock-debug`](https://github.com/microsoft/vscode-mock-debug) repo.

## Features

See [FEATURES.md](FEATURES.md) for a list of features this debugger supports.

## Build and Run

1. Clone the repo.
2. `npm i` to install dependencies.
3. Open the project folder in VS Code.
4. From the Run and Debug menu, run the `Extension` configuration to open the AVM Debug extension in another VS Code window.
5. In the new window, go to its Run and Debug menu to select and launch one of the existing configurations.
6. You are now in a debugging session of a transaction group. You can step through the transaction group, inspect variables, set breakpoints and more. See [FEATURES.md](FEATURES.md) for more details.
118 changes: 118 additions & 0 deletions extension/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
{
"name": "avm-debug-vscode-extension",
"displayName": "AVM Debug",
"version": "1.0.0",
"publisher": "algorand-vscode",
"description": "Debug extension for developing AVM transactions and smart contracts.",
"author": {
"name": "Algorand, llc"
},
"license": "MIT",
"keywords": [
"multi-root ready"
],
"engines": {
"vscode": "^1.80.0"
},
"categories": [
"Debuggers"
],
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/algorand/avm-debugger.git"
},
"bugs": {
"url": "https://github.com/algorand/avm-debugger/issues"
},
"main": "dist/extension.js",
"browser": "dist/web-extension.js",
"activationEvents": [
"workspaceContains:**/*.teal",
"onDebug",
"onDebugResolve:avm",
"onDebugDynamicConfigurations:avm"
],
"workspaceTrust": {
"request": "never"
},
"contributes": {
"languages": [
{
"id": "teal",
"extensions": [
".teal"
],
"configuration": "teal-language-configuration.json"
}
],
"grammars": [
{
"language": "teal",
"scopeName": "source.teal",
"path": "syntaxes/teal.tmLanguage.json"
}
],
"breakpoints": [
{
"language": "teal"
}
],
"debuggers": [
{
"type": "avm",
"languages": [
"teal"
],
"label": "AVM Debug",
"program": "../out/src/cli.js",
"runtime": "node",
"configurationAttributes": {
"launch": {
"properties": {
"simulateTraceFile": {
"type": "string",
"description": "Transaction group simulation response with execution trace.",
"default": "${workspaceFolder}/path/to/simulateTraceFile.json"
},
"programSourcesDescriptionFile": {
"type": "string",
"description": "Description file for sources of programs appearing in transaction group.",
"default": "${workspaceFolder}/path/to/programSourcesDescriptionFile.json"
},
"stopOnEntry": {
"type": "boolean",
"description": "Automatically stop after launch.",
"default": true
}
}
}
},
"initialConfigurations": [
{
"type": "avm",
"request": "launch",
"name": "Debug AVM Transactions",
"simulateTraceFile": "${workspaceFolder}/path/to/simulateTraceFile.json",
"programSourcesDescriptionFile": "${workspaceFolder}/path/to/programSourcesDescriptionFile.json",
"stopOnEntry": true
}
],
"configurationSnippets": [
{
"label": "AVM Debug",
"description": "A new configuration for replaying and debugging a Algorand transactions.",
"body": {
"type": "avm",
"request": "launch",
"name": "Debug AVM Transactions",
"simulateTraceFile": "^\"\\${workspaceFolder}/path/to/simulateTraceFile.json\"",
"programSourcesDescriptionFile": "^\"\\${workspaceFolder}/path/to/programSourcesDescriptionFile.json\"",
"stopOnEntry": true
}
}
]
}
]
}
}
29 changes: 29 additions & 0 deletions extension/src/activateAvmDebug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

import * as vscode from 'vscode';
import { AvmDebugConfigProvider } from './configuration';

export function activateAvmDebug(
context: vscode.ExtensionContext,
factory: vscode.DebugAdapterDescriptorFactory,
) {
// register a configuration provider for 'avm' debug type
const provider = new AvmDebugConfigProvider();
context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider('avm', provider),
);

context.subscriptions.push(
vscode.debug.registerDebugAdapterDescriptorFactory('avm', factory),
);

if (isDisposable(factory)) {
// https://vscode-docs.readthedocs.io/en/stable/extensions/patterns-and-principles/#events
// see events, by the end of subscription, call `dispose()` to release resource.
context.subscriptions.push(factory);
}
}

function isDisposable(value: object): value is { dispose(): unknown } {
return 'dispose' in value;
}
2 changes: 1 addition & 1 deletion src/configuration.ts → extension/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
CancellationToken,
} from 'vscode';

export class TealDebugConfigProvider
export class AvmDebugConfigProvider
implements vscode.DebugConfigurationProvider
{
/**
Expand Down
25 changes: 25 additions & 0 deletions extension/src/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

import * as vscode from 'vscode';
import { activateAvmDebug } from './activateAvmDebug';
import { ServerDebugAdapterFactory } from './serverDescriptorFactory';
import { InlineDebugAdapterFactory } from './internalDescriptorFactory';

const runMode: 'server' | 'inline' = 'inline';

export function activate(context: vscode.ExtensionContext) {
switch (runMode) {
case 'server':
// run the debug adapter as a server inside the extension and communicate via a socket
activateAvmDebug(context, new ServerDebugAdapterFactory());
break;

case 'inline':
// run the debug adapter inside the extension and directly talk to it
activateAvmDebug(context, new InlineDebugAdapterFactory());
break;
}
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
export function deactivate() {}
38 changes: 38 additions & 0 deletions extension/src/fileAccessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';

import * as vscode from 'vscode';
import { FileAccessor } from '../../src';

export const workspaceFileAccessor: FileAccessor = {
isWindows: typeof process !== 'undefined' && process.platform === 'win32',
readFile(path: string): Promise<Uint8Array> {
const uri = pathToUri(path);
return thenableToPromise(vscode.workspace.fs.readFile(uri));
},
writeFile(path: string, contents: Uint8Array): Promise<void> {
const uri = pathToUri(path);
return thenableToPromise(vscode.workspace.fs.writeFile(uri, contents));
},
basename(path: string): string {
const uri = pathToUri(path);
const lastSlash = uri.path.lastIndexOf('/');
if (lastSlash === -1) {
return path;
}
return uri.path.substring(lastSlash + 1);
},
};

function pathToUri(path: string) {
try {
return vscode.Uri.file(path);
} catch (e) {
return vscode.Uri.parse(path, true);
}
}

function thenableToPromise<T>(t: Thenable<T>): Promise<T> {
return new Promise((resolve, reject) => {
t.then(resolve, reject);
});
}
18 changes: 18 additions & 0 deletions extension/src/internalDescriptorFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

import * as vscode from 'vscode';
import { ProviderResult } from 'vscode';
import { AvmDebugSession } from '../../src';
import { workspaceFileAccessor } from './fileAccessor';

export class InlineDebugAdapterFactory
implements vscode.DebugAdapterDescriptorFactory
{
createDebugAdapterDescriptor(
_session: vscode.DebugSession,
): ProviderResult<vscode.DebugAdapterDescriptor> {
return new vscode.DebugAdapterInlineImplementation(
new AvmDebugSession(workspaceFileAccessor),
);
}
}
Loading