vadimcn / codelldb

A native debugger extension for VSCode based on LLDB
https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb
MIT License
2.52k stars 245 forks source link

Add debugger command posting API to WebView JavaScript #914

Closed bitsawer closed 1 year ago

bitsawer commented 1 year ago

Thanks for the great extension, I hope this is a good place to leave enhancement requests.

Currently, I'm using CodeLLDB's Python API to visualize Godot's and other similar C++ apps internals during debugging, including memory views (see the screenshot) and Image data/mipmap visualization. I'm writing these in Python and using debugger.display_html() to dump data in WebViews. Things work great, but there is one small issue which prevents things from being perfect. Currently, WebViews only accept data, they can't send it back or communicate with the debugger. For example, I can't make an interactive HTML memory view where you can edit the values and then ask the debugger to perform those edits. Yes, there is also a builtin VSCode memory view, but this issue is more general and applies to all custom tools like that.

I would suggest implementing a simple WebView command listener. See the Passing messages from a webview to an extension section and onDidReceiveMessage() for an example implementation. It seems to be a pretty simple thing to add, although I'm not very familiar with VSCode extension development. For example, we could then do this in our WebView JavaScript code:

const vscode = acquireVsCodeApi();

var address = 312504976390; // Raw memory address, coming from UI etc.
const debugger_command = "?/nat *((int*)" + address + ") = 0"; // Create debugger commands dynamically.

vscode.postMessage({
    command: 'codelldb_run', // Or whatever command name we want.
    text: debugger_command
})

That code would send a command string which CodeLLDB should then pass to active LLDB debugger (not sure if we would need that ? at the beginning of a command here, but this is just an example). Preferably, this command should run silently so that it doesn't clutter the actual Debug console or its history. Naturally, this would also allow us to call our custom Python functions etc.:

vscode.postMessage({
    command: 'codelldb_run',
    text: '?/py mytool.set_value(0x25345245, 4, 10)'
})

To enable this functionality, we could add a new, optional argument to debugger.display_html(). For example named enable_api, which would default to False. When enabled, CodeLLDB adds the onDidReceiveMessage() listener and performs the commands sent from the WebView. The user is then expected to make sure they don't load untrusted html content to avoid security issues when this setting is on.

Here's a screenshot from one of my custom HTML views (Memory View, panel on the right):

codelldb

vadimcn commented 1 year ago

Seems hacky TBH. And won't you immediately ask for the ability to send messages back from CodeLLDB to the webview, in order to preserve UI state on refresh?
I think scenarios like this require exposing pretty much the entire WebView API in CodeLLDB. But not sure this is worth it: creating interactive WebViews seems pretty labor-intensive for a one-off debug visualization, so I don't expect this feature to become popular.

Speaking of memory visualization specifically, another viable approach would be to do what this extension does, i.e. a normal VSCode extension, which talks to debug adapters via evaluate or readMemory requests. This, at least, would be reusable across many debug adapters.

Another idea I've been toying with, is hosting a Jupyter kernel in the LLDB's Python interpreter. This would allow using the full richness of VSCode Jupyter Notebooks, including interactive controls. What do you think?

bitsawer commented 1 year ago

Seems hacky TBH. And won't you immediately ask for the ability to send messages back from CodeLLDB to the webview, in order to preserve UI state on refresh?

It might be a bit hacky, but then again the whole display_html() is kind of a (very) useful hack to show information fast without having to implement more complex API's or controls. Also no need to implement an ability to send messages back from CodeLLDB to WebView, simply refreshing the WebView with new data (using display_html()) is enough as it remembers the old scrolling position. Most of the persistent data lives in Python code anyway and can be written inline in the HTML code and dumped into a WebView.

I was just making a simple API suggestion for power users with minimal development effort, implementing something better would of course be nice as well.

I think scenarios like this require exposing pretty much the entire WebView API in CodeLLDB. But not sure this is worth it: creating interactive WebViews seems pretty labor-intensive for a one-off debug visualization, so I don't expect this feature to become popular.

I agree it's a niche feature for power users, but I think the implementation cost of my basic suggestion might be pretty low, too. Again, I don't have much experience in VSCode extension development, so I might be wrong. And many of my HTML visualization tools are not really one-off, they are general purpose that work with any C/C++ project.

Speaking of memory visualization specifically, another viable approach would be to do what this extension does, i.e. a normal VSCode extension, which talks to debug adapters via evaluate or readMemory requests. This, at least, would be reusable across many debug adapters.

Another idea I've been toying with, is hosting a Jupyter kernel in the LLDB's Python interpreter. This would allow using the full richness of VSCode Jupyter Notebooks, including interactive controls. What do you think?

I think anything that makes it easier to write custom views/controls that can interact with the debugger would be great. Some of those links look pretty promising. Allowing Jupyter code to interact with LLDB would be very interesting and could allow creating some pretty powerful tools.

vadimcn commented 1 year ago

Okay, I've implemented this in v1.9.1:

  vscode = acquireVsCodeApi();
  vscode.postMessage({ command: 'execute', text: 'script print("Hello")' });