-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Function Pointer Returned is NOT the Actual Method Pointer Invoked by Runtime #111224
Comments
Tagging subscribers to this area: @dotnet/area-system-reflection |
There is not a concept of the method pointer. A managed method can have several different function pointers for tiered JIT and R2R etc. Tier 0 version of application code is probably using the R2R version. |
See also the comments here: runtime/src/coreclr/vm/method.cpp Lines 2080 to 2108 in 11f3549
For shared generics things are more complicated. The function pointer needs to have instantiation information "with it", however in JITed code, the instantiation information is provide at call site, and accepted as an external parameter from the shared generic function. |
@huoyaoyuan
I was under the impression that PrepareMethod causes the JIT to provide the jitted address in the MethodDescriptor, thereby obviating the need to invoke the pre-jit stub, or is that a concept that applies only to non-generic methods. I know that behind the scenes, there's a generic method dictionary, where one entry exists for reference types as __Canon and separate ones for value types, so maybe the stub is for checking that dictionary to resolve the correct pointer. But I don't believe that exists outside of the C++ implementation of the runtime. |
The stub will just set the instantiation and jump to shared generic code. There won't be much overhead when you are invoking indirectly. It's conceptually equivalent to declaring a helper
The invokable function pointer you get is the stub. You won't be able to get the real function pointer of shared generic code, because it's not invokable with standard calling convention.
By definition, it's specific to each instantiation.
It's now outdated with TieredJIT. A method can have multiple versions of code rather than the single pre-jit stub. Only the fully-optimized tier will be treated as final.
Explaining for the function pointer stuff: what you get as function pointer matches the signature of current instantiation, for example BTW, the graph you are referring to are also quite outdated. The layout of method table today can be found at runtime/src/coreclr/vm/methodtable.h Lines 3866 to 3939 in 058fe35
|
Thank you so much for all of this! This has been very helpful. I really appreciate it, and I hope you have a great day. |
For anyone reading this, I was able to get the address of the stub!!! |
This is likely very unreliable. They are allocated in different paces by code/stub allocator. You shouldn't make any assumption about relative addresses between code fragments. Also for the naming: the function pointer is the stub. You are probably finding the shared generic body. A potential working way is to read the code pointed to by the function pointer, and extract the jump target address from the instructions. Again this is implementation detail and can change at any time. |
Of course it is unreliable. It's undocumented and buried in assembly code. But, I need it for writing unit tests to allow method redirection for static, generic methods. Microsoft has not maintained MS fakes and it doesn't support the latest .NET. There are many things that are unmockable, so I need another way to test them. And when it changes, then I just update my code. Problem solved.
I have read many things on this topic, including the book of the runtime. They are often referred to as pre-code stubs or jump stubs, so that is why I use that term. Looking back on this thread, I think it was quite clear that I was referring to this generic precode stub when I talked about how the address was not the same as the address in the disassembled JIT.
Of course I tried that before posting this. I went to the address and disassembled the code. It did not jump to the same address as what I got when I took the function pointer address, added 0x4000 to it, took the memory address from that location, then read the memory address at that location. This is shared generic method that is resolved initially at runtime, so they were not at all the same. After the generic method is compiled, then they are the same. You mentioned PrepareMethod can't be used because of tiered JIT. Well I can easily get around that by disabling it in an environmental variable. However when I overwrite the function preamble with a JMP stub, the performance monitors that the JIT injects to circle back later and recompile it are never executed, therefore preventing tiered JIT overwrite |
If these methods are in your code, or your own code calls them, you can add an abstraction to support mocking. If some third-party code calls another third-party static method, mocking it is very likely going to be disproportionately hard to implement, compared to the returned value. What do these methods that you want to mock do? |
Hi, Theodore. I have had this discussion many times about how to get around things that can't be mocked. Yes there are ways to overcome them, such as wrapping them. But that doesn't help with code coverage. The bottom line is that:
Therefore, I am left with no other choice but to hack the runtime to get around this, by injecting a long jump stub into the preamble of the method. I shouldn't have to go to those lengths, but it was it is. Generics are more difficult because the JIT puts in a precode stub before the geneic method is compiled to native. And I had no way to know where it is by using reflection. The GetFunctionPointer() returns a different address than the stub I am referring to. And the PrepareMethod() on RuntimeHelpers does not get rid of it like it does for all other methods. But now I know where it lives, and I am good-to-go. I was trying to help others who are facing the same issue, and I know there are others because I have found many redirector authors, but none of them could get past this issue with generic methods. So anyone who is trying to do the same thing and stumbled upon this, now you know that you get the function pointer, add 0x4000 to it, and that gives you a pointer to pointer where you can inject your stub. |
Description
I have found that this is happening only when I try to get the method pointer for generic static methods in BCL. The same does not occur if I create my own static class, with a static generic method. Through disassembly, I've found that the address given to the
CALL
opcode is not the same as the address of the function pointer returned. It doesn't matter if the generic argument is reference type or value type.Reproduction Steps
Example:
System.Tuple.Create<T>(T item)
Expected behavior
The addresses should be the same, just as it is if I had created the same class in my local project.
Actual behavior
Addresses are different.
Regression?
No response
Known Workarounds
No response
Configuration
.NET 8
Windows 11
x64
Other information
No response
The text was updated successfully, but these errors were encountered: