# How does vscode debugger extension work?
###### tags: `VSCode` 'trace code`
## [Debug adapter protocol](https://microsoft.github.io/debug-adapter-protocol/)

### [coretex-debug](https://github.com/Marus/cortex-debug)
這個 project 的 wiki 裡有很好的解說。

Extension 必須用 Javascript 寫(總之就是要跑在 nodejs 上),而 debug adapter 可以用任意語言來實現。
### Extension 有哪些方式來跟 debug adapter 溝通?
* Socket
* NamedPipe
* Supported from [v1.49](https://code.visualstudio.com/updates/v1_49)
* Stdin/stdout
* 就是 create 一個 subprocess
* Inline
* 必須用 Javascript 寫,直接呼叫 API
### Extension 需要做什麼?
* Launch debugger(optional)
* e.g. gdb-server
* Communicate with debug adapter
### Debug adapter
* 把 DAP 的 protocol 轉成真正 debugger 的 protocol
### Example
官方 [library](https://github.com/microsoft/vscode-debugadapter-node) 提供了很多基礎功能,沒什麼理由不用。
#### [vscode-mock-debug](https://github.com/microsoft/vscode-mock-debug)
官方給的 debug extension,用 markdown 文件來模擬 debug 的行為。並沒有真的 debugger 在執行。

```typescript
// extension.ts
// 用來說明四種跟 debug adapter 溝通的方式。
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
activateMockDebug(context, new MockDebugAdapterServerDescriptorFactory());
break;
case 'namedPipeServer':
// run the debug adapter as a server inside the extension and communicate via a named pipe (Windows) or UNIX domain socket (non-Windows)
activateMockDebug(context, new MockDebugAdapterNamedPipeServerDescriptorFactory());
break;
case 'external': default:
// run the debug adapter as a separate process
activateMockDebug(context, new DebugAdapterExecutableFactory());
break;
case 'inline':
// run the debug adapter inside the extension and directly talk to it
activateMockDebug(context);
break;
}
}
```
```typescript
export function activateMockDebug(context: vscode.ExtensionContext, factory?: vscode.DebugAdapterDescriptorFactory) {
if (!factory) {
factory = new InlineDebugAdapterFactory();
}
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('mock', factory));
}
class DebugAdapterExecutableFactory implements vscode.DebugAdapterDescriptorFactory {
createDebugAdapterDescriptor(_session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable | undefined): ProviderResult<vscode.DebugAdapterDescriptor> {
if (!executable) {
const command = "absolute path to my DA executable";
const args = [
"some args",
"another arg"
];
const options = {
cwd: "working directory for executable",
env: { "envVariable": "some value" }
};
executable = new vscode.DebugAdapterExecutable(command, args, options);
}
return executable;
}
}
class MockDebugAdapterServerDescriptorFactory implements vscode.DebugAdapterDescriptorFactory {
createDebugAdapterDescriptor(session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable | undefined): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
if (!this.server) {
this.server = Net.createServer(socket => {
const session = new MockDebugSession(workspaceFileAccessor);
session.setRunAsServer(true);
session.start(socket as NodeJS.ReadableStream, socket);
}).listen(0);
}
return new vscode.DebugAdapterServer((this.server.address() as Net.AddressInfo).port);
}
}
class MockDebugAdapterNamedPipeServerDescriptorFactory implements vscode.DebugAdapterDescriptorFactory {
createDebugAdapterDescriptor(session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable | undefined): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
if (!this.server) {
const pipeName = randomBytes(10).toString('utf8');
const pipePath = platform === "win32" ? join('\\\\.\\pipe\\', pipeName) : join(tmpdir(), pipeName);
this.server = Net.createServer(socket => {
const session = new MockDebugSession(workspaceFileAccessor);
session.setRunAsServer(true);
session.start(<NodeJS.ReadableStream>socket, socket);
}).listen(pipePath);
}
return new vscode.DebugAdapterNamedPipeServer(this.server.address() as string);
}
}
class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory {
createDebugAdapterDescriptor(_session: vscode.DebugSession): ProviderResult<vscode.DebugAdapterDescriptor> {
return new vscode.DebugAdapterInlineImplementation(new MockDebugSession(workspaceFileAccessor));
}
}
```
四種方式都需要去繼承 `DebugAdapterDescriptorFactory` 不過也只是初始化並傳些參數給 library 的實作,最後透過 `registerDebugAdapterDescriptorFactory` 來註冊。
`MockDebugSession` 的 base class [`DebugSession`](https://sourcegraph.com/github.com/microsoft/vscode-debugadapter-node@main/-/blob/adapter/src/debugSession.ts) 定義了 `dispatchRequest` 來處理 DAP 的 protocol,`MockDebugSession` 要做的就是 override 這些 methods。真實的例子需要在這些 method 把 DAP 轉成 debugger 的 protocol。
```typescript
// DebugSession.ts
protected dispatchRequest(request: DebugProtocol.Request): void {
const response = new Response(request);
if (request.command === 'initialize') {
// ...
} else if (request.command === 'launch') {
this.launchRequest(<DebugProtocol.LaunchResponse> response, request.arguments, request);
} else if (request.command === 'attach') {
this.attachRequest(<DebugProtocol.AttachResponse> response, request.arguments, request);
} else if (request.command === 'disconnect') {
this.disconnectRequest(<DebugProtocol.DisconnectResponse> response, request.arguments, request);
} else if (request.command === 'setInstructionBreakpoints') {
this.setInstructionBreakpointsRequest(<DebugProtocol.SetInstructionBreakpointsResponse> response, request.arguments, request);
} else {
this.customRequest(request.command, <DebugProtocol.Response> response, request.arguments, request);
}
}
// MockDebugSession.ts, take one method as an example
protected async setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): Promise<void> {
const path = args.source.path as string;
const clientLines = args.lines || [];
// clear all breakpoints for this file
this._runtime.clearBreakpoints(path);
// set and verify breakpoint locations
const actualBreakpoints0 = clientLines.map(async l => {
const { verified, line, id } = await this._runtime.setBreakPoint(path, this.convertClientLineToDebugger(l));
const bp = new Breakpoint(verified, this.convertDebuggerLineToClient(line)) as DebugProtocol.Breakpoint;
bp.id = id;
return bp;
});
const actualBreakpoints = await Promise.all<DebugProtocol.Breakpoint>(actualBreakpoints0);
// send back the actual breakpoint positions
response.body = {
breakpoints: actualBreakpoints
};
this.sendResponse(response);
}
```
#### [code-debug](https://github.com/WebFreak001/code-debug)
支援 gdb & lldb(我沒用過), 一開始有點疑惑,因為微軟的 c++ extension 就可以用 gdb 來 debug 了,後來才想明白因為 gdb 支援多種程式語言,而這個 adapter 就可以用 gdb 來 debug 各種語言了。
Debug adapter 跟 gdb/lldb 之間是用 [Mahcine interface](https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI.html) 這個 protocol,之前完全沒聽過這個東西,概念跟 DAP 差不多,都是想用一個 interface 去接多種 debugger。
#### [cortex-debug](https://github.com/Marus/cortex-debug)
用來 debug ARM cortex-M 系列的裝置。做的事情是 launch openOCD(這裡面會實作 gdb server) 和一些 config。有了 gdb server 其實 adapter 也就不需要知道這個 debugger 其實是 openOCD,具體對裝置的操作也是由 openOCD 來完成。
## Murmur
看完沒寫個紀錄真的忘得很快。