microsoft / MIEngine

The Visual Studio MI Debug Engine ("MIEngine") provides an open-source Visual Studio Debugger extension that works with MI-enabled debuggers such as gdb and lldb.
MIT License
818 stars 218 forks source link

Guidance needed on "evaluate" Request #1355

Closed haneefdm closed 2 years ago

haneefdm commented 2 years ago

I am trying to figure out how best to evaluate expressions as a client and what to do with frameId during an evaluate

            const arg: DebugProtocol.EvaluateArguments = {
                // frameId: undefined,
                expression: expr,
                context: 'hover'
            };
            session.customRequest('evaluate', arg).then((result) => {

According to the DAP, if frameId is undefined, then means global scope

interface EvaluateArguments {
  /**
   * The expression to evaluate.
   */
  expression: string;

  /**
   * Evaluate the expression in the scope of this stack frame. If not specified,
   * the expression is evaluated in the global scope.
   */
  frameId?: number;

If frameId is undefined, I get this and the debug session actually ends/hangs (as in not much works afterward and the only thing that works is Ending the session) -- that should be a separate issue.

<--   C (evaluate-17): {"command":"evaluate","arguments":{"expression":"&buf","context":"hover"},"type":"request","seq":17}
Stopping due to fatal error: ProtocolException: Cannot evaluate expression on the specified stack frame.

Yes, I can make a request for a stackTrace and then use any one of the frameId's, but that is extra, unnecessary work for clients.

Note that I am using the context as hover as I wasn't sure which one would be more appropriate. Should I be using something else? I am using this for memory views and RTOS views discussed here

We ourselves end up using the current scope when no frameId is specified. But with a warning which we are thinking of removing. I do not know how to specify global scope in gdb machine interface. We know current-scope could be anything depending on what transpired before.

I want us to provide some compatible interface on how to deal with outliers. Please let me know what we should do and I can duplicate that in my DA

Cc: @benmcmorran

gregg-miskelly commented 2 years ago

What frame do you want your expression evaluated in? Ideally VS Code would expose a way to obtain the current frame. I opened an issue on them long ago, but it is still active: https://github.com/microsoft/vscode/issues/63943

haneefdm commented 2 years ago

Hello, @gregg-miskelly I am looking for answers/guidance myself. No one right answer given I have gdb as the debugger.

The letter/spirit of the DAP says, to evaluate in the global scope. Not sure how to do that in gdb though (maybe I missed that)

  1. Best I can do is evaluate it in the context of the thread that caused a stop and use the top frame
  2. Lazy (that's me right now) thing to do is to use whatever the current thread/frame is. gdb seems to be stateful. So let's say if the user interacted with the stack trace and moved on to another frame/thread, that will become the current one. But not so simple. Since these things are cached, it can really be the last thing fetched and not the last thing user clicked on.

Ideally VS Code would expose a way to obtain the current frame

Wouldn't that be nice :-) I wanted an event that clients could subscribe to. The Watch/Variable windows always evaluate according to the selected frame. I just wanted the same luxury for other clients

In the end, I want to see if we can figure out together what frameId === undefined means. Maybe we can get that documented in the DAP in cases where there is no definition of global scope?

gregg-miskelly commented 2 years ago

I don't think there is a way to both make this work in VS Code (until https://github.com/microsoft/vscode/issues/63943 is fixed) and also strictly adhere to the DAP.

I think there are two possible paths forward:

  1. Someone does the work in VS Code -or-
  2. Debug adapter that want to allow evaluate requests from extensions must hack around the VS Code limitation by guessing that if frame id is missing, this indicates a request to evaluate in the 'current' frame, where a debug adapter can guess (possibly incorrectly) what the current frame is by keeping an internal variable which is set thusly:
    • When a stopped event is sent: remember the top frame id
    • When a scopes request is received: remember the requested frame
haneefdm commented 2 years ago

When a stopped event is sent: remember the top frame id When a scopes request is received: remember the requested frame

AFAIK, this would be inaccurate and seem random as the scopes request is not repeated and users can jump around, so not clear which frame it would be after a while. I was thinking that if the frameId is missing, we could always apply the request to top frame of the stopped-thread-id. At least that would be consistent.

I can volunteer to try and submit a PR for https://github.com/microsoft/vscode/issues/63943 but its likelihood of getting accepted is pretty low don't you think? It's been there since 2018. We always get referred back to using the tracker mechanism.

gregg-miskelly commented 2 years ago

If they don't repeat scopes then I don't see how we can really make this work at all. I don't know how open VS Code would be to accepting a PR. But I think it is the only way to accomplish what you are trying to do.

haneefdm commented 2 years ago

@gregg-miskelly I can positively confirm that the scopes is cached by VSCode and only called once. So, there is not much to hang our hat on.

Neither of us are honouring the DAP spec for an undefined frameId and it will remain so.

  /**
   * Evaluate the expression in the scope of this stack frame. If not specified,
   * the expression is evaluated in the global scope.
   */
  frameId?: number;

I will close this Issue and and get it out of your queue.