2012-08-24 46 views
6

我正在为我的c#应用程序的解除日志机制工作。有没有什么办法可以告诉在c#中调用函数的方法的参数?

这里是想什么我它看起来像:

功能a(arg1, arg2, arg 3.....)调用函数b(arg4,arg5,arg6....),进而调用log()比能够检测堆栈跟踪(这可以通过Environment.StackTrace完成),并与价值观调用堆栈跟踪中的每个函数(例如,ab)。

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

这是可以做.net?

+0

有趣的问题...不可能在C#中,但也许在IL或使用反射? –

+2

我敢打赌,这是不可能的。尽管这可能会产生一个非常规的非常规面试问题(想象一下这可能 - 你认为它会起作用吗?),比大多数典型的“古怪问题”要好得多;揭示C#和CLR的整体知识的一个很好的起点。 –

+0

从理论上讲,这应该是可能的。序数堆栈跟踪返回正在调用的函数的信息,以及它们的参数类型,用于调用函数的所有变量都应该存储在堆栈的某处(尽管可能是局部变量)。 –

回答

10

可证明不可能的:

的时候b被调用时,所使用的堆栈空间的arg1(的IL栈,所以可能有人甚至从来没有把在堆栈中,但已经enregistered上呼叫)不保证仍然使用arg1

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

编辑

一些详细信息,因为您的意见建议你不所著的Grokking这一点,仍然认为这是可能的。

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

但是,来自MS人员的文章建议使用的约定类似于__fastcall约定。在您拨打a时,arg1将被放入ECX寄存器*和arg2,放入运行代码的核心的EDX寄存器(我简化为假设32位x86,其中amd64甚至更多参数已注册) 。 arg3将被推入堆栈并确实存在于内存中。

请注意,此时,没有内存位置,其中arg1arg2存在,它们只在CPU寄存器中。

在执行方法本身的过程中,必要时使用寄存器和存储器。并调用b

现在,如果a将需要arg1arg2它将不得不在推出之前它呼吁b。但是,如果没有,那么它不会 - 甚至可能会重新排序以减少这种需求。相反,这些寄存器可能已经用于其他方面 - 抖动不是愚蠢的,所以如果它需要一个寄存器或栈上的一个槽,并且有一个未被用于剩余的方法,它就会重用这个空间。 (就此而言,在此之上的级别上,C#编译器将重新使用IL生成的虚拟堆栈中的插槽)。

因此,当调用b时,arg4被放入寄存器ECX中,arg5放入EDX中并且arg6被推入栈中。在这一点上,arg1arg2不存在,你不能再发现他们是什么,而不是你可以阅读一本书后,它已被回收利用,变成卫生纸。

(有趣的注意的是,它是很常见的一种方法与在相同位置相同的参数,在这种情况下,ECX和EDX可以只单独留在家中调用另一个)。

然后,b返回,将其返回值放入EAX寄存器或EDX:EAX对中或带有EAX的内存中,指向它的大小取决于大小,a在将其返回寄存器之前做了一些更多工作,等等。现在

,这是假设有没有做过任何的优化。实际上,有可能根本没有调用b,而是将其代码内联。在这种情况下,无论在寄存器中还是在堆栈上 - 以及在后一种情况下它们在堆栈中的哪个值,都不再与b的签名有关,并且与a期间的相关值的相关处并且在另一个“呼叫”到b的情况下,或者甚至在从a的另一个“呼叫”到b的情况下,这将是不同的,因为包括呼叫b的呼叫在内的整个呼叫a可能已经内联在另一种情况下,不在另一种情况下插入,而在另一种情况下插入不同。举例来说,如果,arg4从另一个调用返回的值开门见山,也可能是在这一点上,EAX寄存器,而arg5在ECX,因为它是一样arg1arg6在的中间的某个中途堆栈空间正在使用a

另一种可能性是调用b是被淘汰尾部调用:因为调用b将不得不立即a回到过它的返回值(或一些其他的可能性),然后,而不是推到,正在使用的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(); 
} 

即使C#编译器和抖动都在,你可以保证参数在上述的方式没有改变这种浪费的方式设计,你怎么会知道什么count就已经来自factory的调用?即使在第一次调用时,它也是不同的,并且不是上面的代码是奇怪的。

因此,简言之:

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

从这一切,如何才有可能知道什么是arg1是什么?

现在,添加垃圾收集的存在。想象一下,如果我们能够神奇地知道什么arg1是无论如何,尽管所有这一切。如果它是对堆中某个对象的引用,那么它可能对我们没有好处,因为如果上述所有内容都意味着堆栈中没有更多的引用被激活 - 并且应该清楚的是,这确实发生了 - 并且GC启动,那么该对象可能已被收集。所以我们可以魔法般地获得的东西是对已经不存在的东西的引用 - 事实上很可能是堆中的某个区域现在被用于其他东西,而是让整个框架的整个类型安全!

这不是在可比反射获得IL一点半点,因为:

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

有关性能分析,AOP和拦截的其他答案中的建议与您将要得到的一样接近。

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

+0

感谢您的好评 – tito11

3

在.net中是不可能的。在运行时,JITter可能决定使用CPU寄存器而不是堆栈来存储方法参数,甚至可以重写堆栈中的初始(传递)值。因此.net允许在源代码中的任何一点记录参数对于性能来说是非常高性价比的。

据我所知,唯一的办法就是使用.net CLR分析API。 (例如Typemock框架能够做这样的事情,它使用CLR剖析API)

如果您只需要拦截虚函数/属性(包括接口方法/属性)调用,则可以使用任何拦截框架(Unity或Castle例如)。

大约有.NET分析API的一些信息:

MSDN Magazine

MSDN Blogs

Brian Long's blog

1

这不是在C#中有可能,你应该使用AOP方法和执行方法的参数记录何时调用每个方法。这样你可以集中你的日志代码,使它可以重用,然后你只需要标记哪些方法需要参数日志记录。

我相信这可以很容易地实现使用AOP框架,如PostSharp

1

可能不会发生没有类型嘲讽或ICorDebug的魔法。 即使StackFrame类也只列出允许您获取有关源的信息的成员,而不是参数。

然而,您所使用的功能作为IntelliTrace与方法记录一起存在。您可以过滤需要审核的内容。

相关问题