Is there any way to tell the arguments of the method calling the function in C #?

I am working on a frontal log mechanism for my C # application.

Here is what I would like to look like:

The function a(arg1, arg2, arg 3.....) calls the function b(arg4,arg5,arg6....) , which in turn calls log() , which can detect the stack (this can be done via Environment.StackTrace ) and the values ​​with which each function (e.g. a and b ) in stacktrace.

I want it to work in debug and release mode (or at least in debug mode).

Can this be done in .net?

+6
source share
4 answers

Permissible Impossible:

By the time b called, the space on the stack used by arg1 (the IL stack, so it may not even have been pushed onto the stack, but was registered on the call), arg1 is still not in use.

In addition, if arg1 is a reference type, the object it refers to is not guaranteed to be garbage collected if it is not used after b called.

Edit

A little more detailed, as your comment suggests that you are not trying to do this and still think it is possible.

Challenging conventions used by jitter are not specified in the specifications for any of the relevant standards, giving developers the freedom to make improvements. They really differ between 32-bit and 64-bit versions and different versions.

However, articles from MS suggest that the convention used is similar to the __ fastcall convention . When a is called, arg1 will be placed in the ECX * register and arg2 in the EDX register (I simplify by assuming 32-bit x86, even more arguments are registered with amd64) The core of the code works. arg3 will be arg3 stack and will really be in memory.

Please note that at this moment there is no memory space in which arg1 and arg2 exist, they are only in the CPU register.

In the process of executing the method itself, registers and memory are used as needed. And b is called.

Now, if a needs arg1 or arg2 , he will have to press this before he calls b . But if this is not so, then this will not happen - and things can even be reordered to reduce this need. Conversely, these registers may have already been used for something else already at this point - jitter is not stupid, so if he needs a register or slot on the stack, and there one of them is not used for the rest of the method, he will be for reuse of this space. (In this regard, at a level higher than this, the C # compiler will reuse slots in the virtual stack that IL uses).

So, when b is called, arg4 is arg4 to the ECX register, arg5 to EDX and arg6 pushed onto the stack. At the moment, arg1 and arg2 do not exist, and you can no longer find out what they are than to read a book after it has been recycled and turned into toilet paper.

(It is interesting to note that very often for a method you can call another with the same arguments in the same position, in which case ECX and EDX can simply be left alone).

Then b returned, placing its return value in the EAX or EDX: EAX register or in memory with EAX pointing to it, depending on the size, a does some more work before placing it in this register, etc.

Now this suggests that no optimizations have been made. It is possible that in fact b was not called at all, but rather that its code was embedded. In this case, whether the values ​​that are in the register or on the stack, and in the latter case, when they were on the stack, no longer have anything to do with the signature b and all that is related to where the corresponding values ​​are at the time of a , and it would be different in the case of another “call” to b or even in the case of another “call” to b from a , since the whole call of a , including its call b could be embedded in one case, and not embedded in another, but in another in another. If, for example, arg4 comes directly from the value returned by another call, it could be in the EAX register at that point, whereas arg5 was in ECX, since it was the same as arg1 and arg6 was halfway in the middle of space the stack used a .

Another possibility is that the call to b was an aborted tail-call: since the call to b had to return the return value also with a (or some other features), instead of pushing the stack, the values ​​used by a are replaced in place and the return address is changed so that the return of the b returns to the method that is called a , passing some of the work (and reduce memory usage to the extent that some of the approaches to functional styles that crowd the stack instead work and really work well). In this case, during a call to b parameters a probably completely disappeared, even those that were on the stack.

It is very debatable whether this latter case should even be considered optimization in general; some languages ​​depend heavily on how this is done, how with it, they give good performance and do not give terrible performance, even if they work at all (instead of overflowing the stack).

There may be all sorts of other optimizations. There should be any other optimization - if the .NET team or the Mono team do something that makes my code faster or uses less memory, but otherwise it behaves the same, without my something, I will not complain for anyone!

And if we assume that the person writing C #, in the first place, never changed the value of the parameter, which, of course, will not be true. Consider this code:

 IEnumerable<T> RepeatedlyInvoke(Func<T> factory, int count) { if(count < 0) throw new ArgumentOutOfRangeException(); while(count-- != 0) yield return factory(); } 

Even if the C # compiler and jitter were designed in such a wasteful way that you could guarantee that the parameters were not changed in the ways described above, how could you know that count already due to a factory call? Even on the first call, it is different, and it does not look like the strange code above.

So in short:

  • Jitter: parameters are often recorded. You can expect x86 to put 2 pointer, reference, or integer parameters in the registers and amd64 to put 4 pointer, reference, or integer parameters and 4 floating point parameters in the registers. They have no place to read.
  • Jitter: Options on the stack are often overwritten.
  • Jitter: there can’t be a real call at all, so there is no place to search for parameters, since they can be anywhere.
  • Jitter: A “challenge” can be reused with the same frame as the last one.
  • Compiler: IL can reuse slots for local users.
  • Person: a programmer can change parameter values.

From all this, how can I find out what arg1 ?

Now add garbage collection to existence. Imagine if we could find out that arg1 was anyway, despite all this. If it were a reference to an object on the heap, it still would not bring us any benefit, because if all of the above meant that there were no more active links on the stack, and it should be clear that this is definitely happening - and the GC will hit, then the object could be assembled. Thus, all we can magically get is a reference to something that no longer exists - perhaps, even in the heap area, which is now used for something else, to crack all the security of the whole structure!

This is not in the smallest bit, comparable to the reflection receiving IL, because:

  • IL is static, not just a state at a given time. Similarly, we can get a copy of our favorite books from the library much easier than we can get our reaction back the first time we read them.
  • IL in any case does not reflect the influence of the insert, etc. If a call was inserted every time it was actually used, and then we used reflection to get the MethodBody this method, the fact that its usually built-in doesn't matter.

Suggestions in other profiling, AOP, and intercept answers are as close as you are about to receive.

* In fact, this is the true first parameter for instance members. Let's pretend that everything is static, so we don’t need to point this out.

+10
source

This is not possible in .net. At run time, JITTER may decide to use the CPU registers instead of the stack to store the method parameters or even overwrite the initial (passed) values ​​on the stack. Thus, it would be very expensive to cost .net so that you can register parameters anywhere in the source code.

As far as I know, the only way to do this in general is to use the .net CLR profiling API. (For example, the Typemock framework is capable of doing such things and uses the CLR profile APIs)

If you only need to intercept calls to virtual functions / properties (including interface methods / properties), you can use any interception infrastructure (for example, Unity or Castle).

There is some information about the .net profiling API:

MSDN Magazine

MSDN Blogs

Brian Long Blog

+3
source

This is not possible in C #, you must use the AOP approach and log the method arguments when calling each method. This way you can centralize your logging code, make it reusable, and then you just need to specify which methods require logging of the arguments.

I believe this can be easily achieved using an AOP infrastructure like PostSharp .

+1
source

Perhaps this will not happen without the mockery type or some ICorDebug magic. Even the StackFrame class contains only lists that allow you to get information about the source, not about the parameters.

The functionality you use, however, exists as an IntelliTrace with method logging. You can filter what you need for review.

+1
source

Source: https://habr.com/ru/post/923713/


All Articles