有没有办法告诉调用c#函数的方法的参数?

Ars*_*ray 6 .net c# reflection stack-trace

我正在为我的c#应用程序开发一个免提日志机制.

这是我希望它看起来像:

函数a(arg1, arg2, arg 3.....)调用函数b(arg4,arg5,arg6....),它反过来调用log()哪些能够检测堆栈跟踪(这可以通过Environment.StackTrace),以及调用stacktrace中每个函数(例如ab)的值.

我希望它在调试和发布模式下工作(或者至少在调试模式下).

这可以在.net中做吗?

Jon*_*nna 10

绝对不可能:

b被调用的时候,一个人使用的堆栈中的空间arg1(IL堆栈,所以它可能从未被放入堆栈,但是已经在调用中注册)并不能保证仍被使用arg1.

通过扩展,如果arg1是引用类型,则如果在调用之后未使用它,则不保证它所引用的对象不被垃圾回收b.

编辑:

更详细一点,因为你的评论暗示你不是在讨论这个并且仍然认为它应该是可能的.

抖动使用的调用约定未在任何相关标准的规范中指定,这使得实现者可以自由地进行改进.它们确实在32位和64位版本以及不同版本之间有所不同.

但是,来自MS人员的文章表明,所使用的约定类似于__fastcall约定.在你的调用中a,arg1将被放入ECX寄存器*,并arg2进入EDX寄存器(我通过假设32位x86简化,其中amd64甚至更多参数被注册)运行代码的核心.arg3将被推入堆栈并确实存在于内存中.

需要注意的是,在这一点上,没有存储在哪个位置arg1arg2存在,他们只是在一个CPU寄存器.

在执行方法本身的过程中,根据需要使用寄存器和存储器.而b被调用.

现在,如果a需要,arg1或者arg2它必须在它调用之前推动它b.但如果没有,那么它就不会 - 甚至可能会重新订购以减少这种需求.相反,这些寄存器可能已经被用于其他东西 - 抖动并不是愚蠢的,所以如果它需要一个寄存器或堆栈上的一个插槽,并且有一个未使用该方法的其余部分,它就会发生重用那个空间.(就此而言,在上面的层次上,C#编译器将重用IL生成的虚拟堆栈中的槽使用).

因此,当b被调用时,arg4将其放入寄存器ECX,arg5进入EDX并arg6推入堆栈.在这一点上,arg1arg2 不存在,你不能再找出他们比你可以读一本书,它已被回收,变成卫生纸了.

(有趣的是,一个方法在同一个位置调用另一个具有相同参数的方法是很常见的,在这种情况下,ECX和EDX可以保持不变).

然后,b返回,将其返回值放入EAX寄存器,或EDX:EAX对或内存中,EAX指向它,具体取决于大小,a在将其返回到该寄存器之前做更多工作,依此类推.

现在,假设没有做任何优化.实际上,它可能b根本没有被调用,而是它的代码被内联.在这种情况下,寄存器中或堆栈中的值 - 以及后一种情况下它们在堆栈中的位置 - 是否与b签名无关,以及与相关值在何处相关a的所有内容执行,这将是在另一种情况下"呼"地不同的b,甚至在其他情况下"呼"地ba,因为整个呼叫a包括其呼叫b可能已经在一种情况下内联,在没有内联另一个,并在另一个内联不同.例如,如果arg4直接来自另一个调用返回的值,那么此时它可能在EAX寄存器中,而arg5在ECX中则与它相同,arg1并且arg6位于堆栈空间中间的某个位置用于a.

另一种可能性是调用b是一个被消除的尾调用:因为调用b将立即返回其返回值a(或其他一些可能性),而不是推送到堆栈,使用的值by a被替换为原位,并且返回地址已更改,以便返回从b跳转回调用的方法a,跳过一些工作(并减少内存使用,以至于某些功能样式方法会溢出堆栈而不是工作确实工作得很好.在这种情况下,在调用期间b,参数a可能完全消失,即使是那些已经在堆栈中的参数.

最后的案例是否应该被视为优化,这是值得商榷的.有些语言在很大程度上依赖于它的完成,因为它们具有良好的性能,如果它们甚至可以工作而不会产生可怕的性能(而不是溢出堆栈).

可以有各种其他优化方式.这里应该有其他的优化的各种方式-如果.NET团队或Mono团队做一些让我的代码更快或使用较少的内存,但否则相同的行为,而不必我的东西,我一个也不会抱怨!

而且假设首先编写C#的人从未改变过参数的值,这当然不是真的.考虑以下代码:

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

即使C#编译器和抖动是以如此浪费的方式设计的,你可以保证参数不会以上述方式改变,你怎么能知道count调用中的内容是factory什么?即使在第一次调用时它也是不同的,并且它不像以上那样是奇怪的代码.

所以,总结一下:

  1. 抖动:通常会注册参数.你可以期望x86在寄存器和amd64中放入2个指针,引用或整数参数,将4个指针,引用或整数参数和4个浮点参数放入寄存器.他们没有地方可以阅读.
  2. 抖动:堆栈上的参数经常被覆盖.
  3. 抖动:可能根本没有真正的呼叫,所以没有地方可以查找参数,因为它们可能在任何地方.
  4. 抖动:"呼叫"可能重新使用与最后一帧相同的帧.
  5. 编译器:IL可以为本地人重复使用插槽.
  6. 人:程序员可能会更改参数值.

从这一切来看,究竟怎么可能知道是什么arg1

现在,添加垃圾收集的存在.想象一下,如果我们能够神奇地知道究竟arg1是什么,尽管如此.如果它是对堆上对象的引用,它可能仍然对我们没有好处,因为如果以上所有意味着堆栈上没有更多的引用活动 - 并且应该很清楚,这肯定会发生 - 并且GC启动,然后可以收集对象.因此,我们可以神奇地掌握的是对不再存在的东西的引用 - 实际上很可能是堆中的某个区域现在被用于其他东西,爆炸使整个框架的整个类型安全!

与获得IL的反射相比,它没有任何可比性,因为:

  1. IL是静态的,而不仅仅是给定时间点的状态.同样地,我们可以更容易地从图书馆获得我们最喜欢的书籍的副本,而不是在我们第一次阅读它们时能够得到我们的反应.
  2. 无论如何,IL并未反映内联等的影响.如果一次调用在每次实际使用时被内联,然后我们使用反射来获得MethodBody该方法,那么它通常内联的事实是无关紧要的.

关于性能分析,AOP和拦截的其他答案中的建议尽可能接近.

*实际上,它this是实例成员的真正第一个参数.让我们假装一切都是静态的,所以我们不必一直指出这一点.