2012-08-24 48 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與方法記錄一起存在。您可以過濾需要審覈的內容。

相關問題