Samsung / netcoredbg

NetCoreDbg is a managed code debugger with GDB/MI, VSCode DAP and CLI interfaces for CoreCLR.
MIT License
836 stars 103 forks source link

Better evaluate expression ideas #132

Open noam-sol opened 1 year ago

noam-sol commented 1 year ago

Hey, I've noticed that the current implementation for evaluate expression is limited, and have some ideas about how to improve it.

  1. using the official Roslyn - although it compiles the expression
  2. using DynamicExpresso https://github.com/dynamicexpresso/DynamicExpresso - I think it's similar in its idea to the current evaluator implementation, but could possibilty support more expressions?

Maybe use Roslyn as a fallback if the current evaluation logic fails? If you guys can share some background on whether you've considered any of these it would be really helpful. I can help intergrating one of those aforementioned. Thanks!

viewizard commented 1 year ago

Debugger process can't "transfer" any objects/types and/or its metadata and method's code from debuggee process into debugger process. You can't just copy some raw memory from one managed process (debuggee) into another (debugger managed part). This could be done only for types that managed debugger part know (could create object with same metadata and methods code), usually this mean that only simple predefined types like int, float,... could be transfered as value only and created as managed object inside debugger managed part with same value.

This is why CLR Debug API provide ICorDebugEval and ICorDebugEval2 (https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugeval-interface), so, debugger could execute some code directly in debuggee process.

Also, initially netcoredbg used Roslin for eval (that was simple eval implementation for predefined types only), we faced, that generated by Roslin code could provoke memory leak in debugger process, since not all CLR versions could properly "unload" this code... (see: https://stackoverflow.com/questions/59597361/how-to-avoid-memory-leak-using-roslyn-csharpcompilation).

gbalykov commented 1 year ago

@noam-sol Thank you for interest to the project. Do you have some specific eval command or scenario in mind where you see limitations of netcoredbg eval?

noam-sol commented 1 year ago

@viewizard thanks for the quick reploy!

(...) You can't just copy some raw memory from one managed process (debuggee) into another (debugger managed part).

I was actually considering running Roslyn on the debuggee process (and not debugger managed-part), probably by loading some assembly into it.

(...), since not all CLR versions could properly "unload" this code... (see: https://stackoverflow.com/questions/59597361/how-to-avoid-memory-leak-using-roslyn-csharpcompilation).

Thanks for sharing this article, I wasn't aware of these issues. Does that mean that if I load my own assembly to the debuggee I can't unload it for old CLRs? (or is it specific for the way Roslyn uses assemblies?)

I'm asking this because maybe if Roslyn can't be used bc. of the memory leaks, maybe DynamicExpresso - https://github.com/dynamicexpresso/DynamicExpresso?

noam-sol commented 1 year ago

@noam-sol Thank you for interest to the project. Do you have some specific eval command or scenario in mind where you see limitations of netcoredbg eval?

Hi, I was trying to call a function with a lambda expression param.

@gbalykov

viewizard commented 1 year ago

I was actually considering running Roslyn on the debuggee process (and not debugger managed-part), probably by loading some assembly into it.

Previously (before current implementation start) we spend some time for "inject-related" eval (since this is really looks like easy way do all around with less cost). Unfortunately we are faced, that we can't guarantee initial debuggee process behaviour in this way. For example, by injection debuggee process load some data and code that don't belong to debuggee process but will be executed as part of debuggee process, that change internal runtime states, could start more managed threads, GC start working in another way... this usually don't affect for "hello world" size project but could be pain during large project debugging.

I was trying to call a function with a lambda expression param.

Looks like even MS debugger don't support this - https://learn.microsoft.com/en-us/visualstudio/debugger/expressions-in-the-debugger?view=vs-2022#c---unsupported-expressions - Creation of new anonymous methods is not supported..

noam-sol commented 1 year ago

@viewizard Thanks for giving some background on the evaluator, highly appreciated 🙏

"inject-related" eval (since this is really looks like easy way do all around with less cost).

I was indeed thinking the same way, but I see now why you guys didn't go this way.

Looks like even MS debugger don't support this

Huh, I didn't know that, but I find it quite useful. I'd still like to add some support for this. Can I open a PR if I manage to extend the existing evaluator? Do you have some clue on what would it take? I've started looking into the AST parsing and Command implemenation but not sure how to approach this. Thanks!

viewizard commented 1 year ago

Can I open a PR if I manage to extend the existing evaluator?

Sure, you can.

Do you have some clue on what would it take?

Frankly speaking, I don't have any ideas how we could create this. Usually, in case of object you could just call ICorDebugEval2::NewParameterizedObject (https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugeval2-newparameterizedobject-method) or ICorDebugEval2::NewParameterizedObjectNoConstructor (https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugeval2-newparameterizedobjectnoconstructor-method), but in case of anonymous method you should somehow generate code instead and provide as argument object, that is like "pointer" to generated method's code you have generated and stored in some memory. This is really looks like code injection.

qgindi commented 11 months ago

Do you have some specific eval command or scenario in mind where you see limitations of netcoredbg eval?

Please implement implicit or explicit cast or as. Now, when an expression calls a method with a parameter, the argument type must be exactly the same as the parameter type. For example cannot assign a string to object, or List<int> to IEnumerable<int>.

Scenario: use -var-create as a workaround for #85 . For it I use a generic class defined in a library that is always loaded in the debuggee process. The class C<T> has a method with a parameter of type T. The -var-create expression calls the method and passes the collection variable to it. The method gets collection elements and returns (as a string) to the IDE to display. It works with List, Dictionary etc. But does not work with ienumerable types returned by Linq methods, because these types are non-public. It would be possible only if netcoredbg could cast these types to IEnumerable or object.