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

Better evaluate expression ideas #132

Open
noam-sol opened this issue Jul 10, 2023 · 8 comments
Open

Better evaluate expression ideas #132

noam-sol opened this issue Jul 10, 2023 · 8 comments

Comments

@noam-sol
Copy link
Contributor

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
Copy link
Member

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
Copy link
Member

@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
Copy link
Contributor Author

noam-sol commented Jul 11, 2023

@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
Copy link
Contributor Author

noam-sol commented Jul 11, 2023

@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
Copy link
Member

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
Copy link
Contributor Author

noam-sol commented Jul 11, 2023

@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
Copy link
Member

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
Copy link

qgindi commented Dec 23, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants