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

Debug Support for Any Exceptions and Uncaught Exceptions #298

Open
sgtoj opened this issue Sep 20, 2016 · 40 comments
Open

Debug Support for Any Exceptions and Uncaught Exceptions #298

sgtoj opened this issue Sep 20, 2016 · 40 comments
Labels
Area-Debugging Issue-Enhancement A feature request (enhancement).

Comments

@sgtoj
Copy link

sgtoj commented Sep 20, 2016

Is it possible to debug support to enable break points for: Any Exceptions or Uncaught Exceptions?

VS Code Debug Breakpoints

@daviwil daviwil added the Issue-Enhancement A feature request (enhancement). label Sep 20, 2016
@daviwil daviwil added this to the Backlog milestone Sep 20, 2016
@daviwil
Copy link
Contributor

daviwil commented Sep 20, 2016

I'm not sure how this would work in the case of PowerShell. What do you think, @rkeithhill?

@rkeithhill
Copy link
Collaborator

I've wanted this for a while but I think PowerShell would need to be modified to support this. Who's the debugger expert on the team? Paul H?

FYI, I filed a UserVoice suggestion on this back in March - please vote it up: https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/12843021-enhance-set-psbreakpoint-to-allow-us-to-set-except

@daviwil
Copy link
Contributor

daviwil commented Sep 20, 2016

Yep, @PaulHigin is the PowerShell debugging expert.

@PaulHigin
Copy link

This is a fairly common request and something we have in our backlog, but is impossible to implement with our current script debugger. The reason is that the script debugger runs on the script execution thread and requires running script to work. But once an exception is thrown on the script execution thread the script is no longer running and the script debugger no longer works. At this point you need a managed/native debugger. We could have script debugger break for non-terminating errors since script execution continues and the script debugger is working. But I am not sure how useful that would be.

Jason, @lzybkr, has experimented with a mixed managed code/script debugger and it would be ideal for this kind of thing. When the managed exception is thrown this debugger could show both managed and PowerShell script stacks.

@daviwil
Copy link
Contributor

daviwil commented Sep 20, 2016

The mixed code/script debugger would be extremely nice to have and would be pretty easy to surface in VS Code.

@lzybkr
Copy link
Member

lzybkr commented Sep 20, 2016

That prototype generated a pdb and code that the C# debugger could consume - it was useful, but not a PowerShell experience.

@rkeithhill
Copy link
Collaborator

@PaulHigin What do you think about this workaround by @nightroman? https://github.com/nightroman/PowerShelf/blob/master/Debug-Error.ps1

@PaulHigin
Copy link

Very clever. I like it. But I believe this just handles the case where PowerShell is throwing the exception (either through throw keyword or -ErrorAction Stop). @lzybkr can you confirm? I was thinking in terms of a general exception being thrown in managed code. Still this looks to be very useful and something we could support internally.

@nightroman
Copy link

nightroman commented Sep 23, 2016

Unfortunately, Debug-Error.ps1 does not show the error itself, it just breaks into the debugger. The error has not been written yet (i.e. added to $Error) by PowerShell. So you have to guess what has happened. Sometimes this is clear, sometimes not. The tool is useful in special cases, rather rare.

@andrewducker
Copy link

Any movement on this? At the moment I'm reduced to stepping through my module (and all of its dependencies) line by line trying to find where the exception is being thrown. Or adding a load of Write-Host statements to see which ones get hit.

@SeeminglyScience
Copy link
Collaborator

There is a way to break before the terminating error is thrown, but after it's been written to $Error. It isn't very clean though.

Set-PSBreakpoint -Variable StackTrace -Mode Write -Action {
    $null = Set-PSBreakpoint -Variable ErrorActionPreference -Mode Read
}

throw 'test'
# In debugger:
$Error[0]
# Returns:
# test
# At line:1 char:1
# + throw 'test'
# + ~~~~~~~~~~~~
#     + CategoryInfo          : OperationStopped: (test:String) [], RuntimeException
#     + FullyQualifiedErrorId : test

After $StackTrace is written to, $ErrorActionPreference is checked to see if the error should be terminating. When it checks $ErrorActionPreference it has already written to $Error regardless of the preference value.

For this to be reliable you would need to store state between the two breakpoints to ensure they fire on the same sequence point (in case the script later reads the preference directly). It would also need to handle situations where the break point already exists and be able to remove itself after the first hit.

I'll start looking into the feasibility of adding support for this.

@JustinGrote
Copy link
Collaborator

JustinGrote commented Jul 21, 2019

To follow up on @SeeminglyScience's work:

  • Capturing $Error by breaking on erroractionpreference only works reliably on the "throw" keyword, other exceptions don't seem to call it.
  • The breakpoints don't get cleaned up after a session and can trigger again if you look at erroractionpreference, etc. so I'm looking into having a remove-breakpoint somewhere maybe, via delayed runspace or something, because you cant remove the breakpoint within itself as far as I can tell.
  • Foreach Loops get weird, some additional workarounds added there
  • The only way to capture $Error for non-throw items as far as I've found is to try-catch the failed line again to capture the error. This obviously can have issues if the command wasn't idempotent so I made it a toggleable variable.
  • You can't break on a write to $Error because its marked as a readonly constant, so it doesn't technically "change" from the perspective of the breakpoint watcher even though PS updates it in the background.

Effective Workaround
EDIT: See next post

@JustinGrote
Copy link
Collaborator

JustinGrote commented Jul 23, 2019

OK, I've come up with a pretty effective function to break on exceptions in lieu of a proper implementation (PowerShell/PowerShell#2830)

https://gist.github.com/JustinGrote/52cf75ea75f2888dbaf4b0c86519d0a6

Features

  1. Breaks on any terminating exception (doesn't catch non-terminating errors on purpose unless erroractionpreference='stop' is set)
  2. Re-Execution in order to capture the relevant exception (toggleable behavior)
  3. Cleans up after itself (usually)
  4. WinPS5 and PS6+ compatible
  5. Works pretty much anywhere Powershell does (tested: vscode, cloud shell, azure functions, pwsh, etc.)

Quick Start

iex (irm https://git.io/PSDebugOnException);Debug-OnException

Example launch.json entry

{
    "name": "PowerShell Trap Exceptions",
    "type": "PowerShell",
    "request": "launch",
    "script": "Debug-OnException",
    "args": [
        "${file}"
    ],
    "cwd": "${file}"
}

Demos

VSCode Unsaved File

DebugExceptionUnsavedFile

VSCode Run Selection (F8)

DebugExceptionF8

Foreach Loop

DebugExceptionForeach

Foreach (More Real-World Example)

DebugExceptionForEachRealWorld

Exception Inside Module

DebugOnModuleException

Azure Cloud Shell

DebugCloudShellException

Powershell 5 (Windows Terminal, Works in VSCode too)

PS5Debug

VSCode Extension Integration

Will need help here as I suck at typescript. The script itself or the breakpoints it generates need to be wired up to a custom breakpoint "Terminating Exceptions" per @sgtoj's example. @TylerLeonhardt @SeeminglyScience @rjmholt maybe?

@SydneyhSmith
Copy link
Collaborator

@JustinGrote thanks for all your work here, this looks great. The change here will need to happen in Editor Services, probably in this chunk of code , using this https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetExceptionBreakpoints ....it would be awesome if you opened up a PR in that repo and we can help you iron out the details...Thanks!

@bormm
Copy link

bormm commented Sep 11, 2019

The script @JustinGrote created did not helps if the code itself also throws exceptions that are catched by intention. The script terminates in that case, where it would continue without the script.
I had an other solution: I just added a

trap { 
# whatever
}

and placed a breakpoint in that trap. This worked perfectly for me.

@ghost ghost added the Needs: Maintainer Attention Maintainer attention needed! label Sep 11, 2019
@JustinGrote
Copy link
Collaborator

There is also a PR to PowerShell 7 to specifically add this functionality to set-breakpoint, so it will be easier to support going forward.

@rkeithhill
Copy link
Collaborator

Three years later ... But hey, I'll be happy to finally have this support in the PS engine. :-)

@SydneyhSmith SydneyhSmith removed the Needs: Maintainer Attention Maintainer attention needed! label Sep 12, 2019
@ghost ghost added the Needs: Maintainer Attention Maintainer attention needed! label Sep 13, 2019
@rkeithhill
Copy link
Collaborator

Even if exceptions are handled, it will still result in a break in the debugger the moment the exception is thrown.

Hopefully, that can be configured. Debuggers typically over the ability break on both unhandled (Uncaught exceptions in VSCode) exceptions and when first thrown (All exceptions in VSCode). Hopefully there is a way to enable the former.

@KirkMunro
Copy link
Collaborator

KirkMunro commented Sep 13, 2019

Not yet.

I break into the debugger the moment the exception is raised.

I'll have to think about how that could be done. The only thing that comes to mind at the moment is reverse traversal of the AST to check for traps or catch blocks, but figuring out what handles what via inspection could be pretty complicated.

Actually, how would that even be possible in an interpreted language? How could I reliably identify that a catch all does or does not handle an exception, for example, when variables used in that catch all could come from anywhere?

@SeeminglyScience
Copy link
Collaborator

@KirkMunro

I think a exception could be considered unhandled when it reaches this code block in LocalPipeline and maybe when IsNested is false? Not sure about the latter, maybe it's just enough to hit that block.

@KirkMunro
Copy link
Collaborator

@SeeminglyScience: Even if that would work, I'm also concerned about the user experience. Today ActionPreference.Break enters the debugger the moment the corresponding exception or stream message occurs. The debugger is on the very line where the exception came from, allowing users to inspect the environment at that point in time, whether the exception is handled or not.

If we defer entering the debugger for unhandled exceptions until such a time that we know the exception is considered unhandled, where are we in our call stack? We may have unwound quite a bit to get to that point. IMHO it is far better for this community to bring users to the origin of the exception, so that they can understand what is going on.

That said, I'm all for efficient debugging (it's something I continue to actively work on in many areas), so I'd like to offer both, but I would want to do so with the debugger still breaking at the point of exception. We could do more work in the script block compiler so that we know the explicit types of exceptions that can be caught when an exception is first raised, to identify whether or not that exception is going to be handled, but then I'm not sure how to reliably identify if a catch all will handle an exception or not.

@SeeminglyScience
Copy link
Collaborator

If we defer entering the debugger for unhandled exceptions until such a time that we know the exception is considered unhandled, where are we in our call stack? We may have unwound quite a bit to get to that point.

Maybe, but the snippet I linked is at the end of a very long chain of try/catch's. With some experimentation you can probably find one where the stack is mostly intact and you can still determine if it's unhandled.

@SydneyhSmith SydneyhSmith removed the Needs: Maintainer Attention Maintainer attention needed! label Sep 20, 2019
@JustinGrote
Copy link
Collaborator

JustinGrote commented Mar 6, 2020

Now that Powershell 7 is out, perhaps it's as "easy" as just wiring up the "Uncaught Exceptions" button to set ErrorActionPreference = 'break' for now?
image

My current "workaround" is:
Start Interactive Session
$ErroractionPreference = 'Break'

Stops on exception as expected.

@SeeminglyScience @KirkMunro @rkeithhill

I'll be able to take a stab at a PR in a couple weeks, knowing basically no Typescript and just enough C# to read it and interpret it into powershell :).

@ghost ghost added the Needs: Maintainer Attention Maintainer attention needed! label Mar 6, 2020
@rjmholt
Copy link
Collaborator

rjmholt commented Mar 6, 2020

knowing basically no Typescript and just enough C# to read it and interpret it into powershell

Very happy to help with that. The hard won't be the language as much as navigating the code, but I think we have a fair idea of where things should go.

@rjmholt
Copy link
Collaborator

rjmholt commented Mar 6, 2020

The other hard part is managing the version-specificity. It's a pain writing code that works in 7 but fails gracefully and informatively in older PowerShells.

@SeeminglyScience
Copy link
Collaborator

@KirkMunro do you know if there's a way to only break on uncaught errors? Or otherwise determine that an error was uncaught? Also determine whether the error was terminating?

Right now it's all errors, even if caught. That's still awesome and way better than no error breakpoints period though.

@JustinGrote
Copy link
Collaborator

JustinGrote commented Mar 6, 2020

@SeeminglyScience You're right! For some reason I thought it was working on uncaught exceptions only.
image

Sounds like more of a upstream (Powershell) bug to me, since ErrorActionPreference = 'stop' doesn't stop on uncaught exceptions so the behavior is inconsistent.

@SeeminglyScience
Copy link
Collaborator

@JustinGrote Yeah for sure, there's still great value in enabling "All Exceptions".

@rjmholt
Copy link
Collaborator

rjmholt commented Mar 7, 2020

since ErrorActionPreference = 'stop' doesn't stop on uncaught exceptions so the behavior is inconsistent

Do you mean $ErrorActionPreference = 'Stop' doesn't stop on caught exceptions?

If so, what really happens is that it does "Stop" (by which I mean throw a terminating error), but then that error is caught.

To change the behaviour of $ErrorActionPreference = 'Debug' I feel that's more of a consistency break, since unlike other error actions it would need to search up the stack to determine if it's going to be caught or not. Honestly that's a pretty complex behaviour.

Anyway, didn't mean to sidetrack you.

@SeeminglyScience
Copy link
Collaborator

SeeminglyScience commented Mar 7, 2020

since unlike other error actions it would need to search up the stack to determine if it's going to be caught or not. Honestly that's a pretty complex behaviour.

My thought here is to add something to the compiler to emit something when processing a TryStatementAst that would allow tracking this easier. Still pretty complex but would be incredibly useful.

Edit: You were talking about Stop, not a hypothetical BreakOnUncaughtOrSomething. Oops

@SydneyhSmith SydneyhSmith removed the Needs: Maintainer Attention Maintainer attention needed! label Mar 12, 2020
@markm77
Copy link

markm77 commented Apr 29, 2020

[Aside]

Jason, @lzybkr, has experimented with a mixed managed code/script debugger and it would be ideal for this kind of thing. When the managed exception is thrown this debugger could show both managed and PowerShell script stacks.

I noticed this comment because I'm looking for a way to step through C# in my own PS module (dll) called by PowerShell script being debugged in vscode-powershell. At the moment I can't debug the PowerShell and C# together so @lzybkr if you ever chose to make available something that supports this I'd be grateful for one. 😊

@ghost ghost added the Needs: Maintainer Attention Maintainer attention needed! label Apr 29, 2020
@TylerLeonhardt
Copy link
Member

@markm77 you are able to have the PowerShell debugger and C# debugger running at the same time.... so feasibly you can launch debug a PowerShell script and attach the C# debugger... but it doesn't give you the ideal behavior of "stepping into C# from PowerShell".

@TylerLeonhardt TylerLeonhardt removed the Needs: Maintainer Attention Maintainer attention needed! label Apr 29, 2020
@lzybkr
Copy link
Member

lzybkr commented Apr 29, 2020

@markm77 - mixed debugging is a little painful but possible today if you attach a C# debugger, set breakpoints where you care about, then debug the PowerShell code with the PowerShell debugger. You don't get a nice shared stack but it's better than nothing.

My prototypes were based on the Desktop version of .Net, I have no idea if they are even possible now.

@ghost ghost added the Needs: Maintainer Attention Maintainer attention needed! label Apr 29, 2020
@markm77
Copy link

markm77 commented Apr 29, 2020

Thanks guys for interesting comments. To do mixed debugging do I have to re-attach the debugger each time the PowerShell script calls into a C# module cmdlet? Or can I attach the debugger just once following module import and it will re-attach as the DLL is called (much better)?

Also is there any recommended way for dropping the lock on the DLL to allow DLL re-build following any C# update? At the moment I run pwsh inside PowerShell before module import, then run exit to drop the lock on the DLL before C# re-build.

Cheers!

@lzybkr
Copy link
Member

lzybkr commented Apr 29, 2020

Attaching once is fine, but you must exit the process to rebuild - this is a limitation in .Net.

@markm77
Copy link

markm77 commented Apr 29, 2020

Thanks! I think I'm going to experiment with debugging an outer PS script that calls pwsh to do the import and run the inner PS script, hopefully the PowerShell debugger can hop to the inner process.... Otherwise I'll set up VS Code tasks. Have a great day!

@TylerLeonhardt TylerLeonhardt removed the Needs: Maintainer Attention Maintainer attention needed! label Apr 30, 2020
@SydneyhSmith SydneyhSmith removed this from the Future milestone Jan 26, 2021
@SetTrend
Copy link

SetTrend commented Mar 11, 2024

I'm not sure if I get the current situation right …

According to https://github.com/PowerShell/PowerShell/issues/2830, PowerShell seems to now support breaking into the debugger on uncaught exceptions. Yet, I can't find this working in the current version of VS Code PowerShell extension.

I have written a recursive function and I have no clue what's causing a "Collection was modified; enumeration operation may not execute." error without seeing the current stack trace and variable values.

@JustinGrote
Copy link
Collaborator

@SetTrend I don't know if we've wired up the button specifically but if you do $ErrorActionPreference = 'break' it should still break in debugging when an error occurs.

@SeeminglyScience has a breakpoint PR we are waiting that changes a lot of things that we are waiting on before we make any breakpoint feature changes.
#4065

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Debugging Issue-Enhancement A feature request (enhancement).
Projects
None yet
Development

No branches or pull requests