2012-08-24 20 views
6

Tôi đang làm việc trên cơ chế đăng nhập vô hiệu cho ứng dụng C# của mình.Có cách nào để nói cho các đối số của phương thức gọi một hàm trong C#?

Dưới đây là những gì tôi muốn nó trông giống như:

chức năng a(arg1, arg2, arg 3.....) cuộc gọi chức năng b(arg4,arg5,arg6....), nó sẽ gọi log() đó là hơn khả năng phát hiện các stacktrace (điều này có thể được thực hiện thông qua Environment.StackTrace) và các giá trị với mà mỗi chức năng (ví dụ: ab) trong stacktrace được gọi.

Tôi muốn nó hoạt động ở chế độ gỡ lỗi và phát hành (hoặc ít nhất, ở chế độ gỡ lỗi).

Điều này có thể thực hiện trong .net?

+0

Câu hỏi thú vị ... không thể có trong C#, nhưng có thể trong IL hoặc sử dụng sự phản chiếu? –

+2

Đặt cược của tôi là điều đó là không thể. Nó sẽ làm cho một câu hỏi phỏng vấn độc đáo tốt, mặc dù (tưởng tượng rằng nó là có thể - làm thế nào để bạn nghĩ rằng nó sẽ làm việc?), Cách tốt hơn so với hầu hết các "câu hỏi kỳ quặc" điển hình; một điểm khởi đầu tốt để tiết lộ kiến ​​thức tổng thể của C# và CLR, vv –

+0

Về mặt lý thuyết, NÊN có thể. Stacktrace bình thường trả về thông tin về các hàm đang được gọi, cũng như các kiểu đối số của chúng và tất cả các biến được sử dụng để gọi hàm sẽ được lưu trữ trong ngăn xếp ở đâu đó (mặc dù có thể là biến cục bộ). –

Trả lời

10

provably không thể:

Vào thời điểm b được gọi, không gian trong ngăn xếp được sử dụng bởi một là arg1 (stack IL, vì vậy có thể nó không bao giờ thậm chí đặt trong một ngăn xếp, nhưng đã bị enregistered trên cuộc gọi) không được đảm bảo vẫn được sử dụng bởi arg1.

Nếu gia hạn, nếu arg1 là loại tham chiếu, đối tượng được đề cập không được đảm bảo là không thu gom rác, nếu không được sử dụng sau khi gọi tới b.

Sửa:

Một chút chi tiết hơn, kể từ khi bình luận của bạn cho thấy bạn không grokking này và vẫn nghĩ rằng nó nên có thể.

Quy ước gọi được sử dụng bởi jitter không được chỉ định trong thông số kỹ thuật cho bất kỳ tiêu chuẩn liên quan nào, cho phép người thực hiện tự do cải tiến. Chúng thực sự khác nhau giữa các phiên bản 32 bit và 64 bit và các phiên bản khác nhau.

Tuy nhiên, các bài viết từ MS cho rằng quy ước được sử dụng tương tự như quy ước __fastcall. Trong cuộc gọi của bạn đến a, arg1 sẽ được đưa vào thanh ghi ECX * và arg2 vào thanh ghi EDX (tôi đơn giản hóa bằng cách giả sử 32 bit x86, với amd64 nhiều đối số hơn được đăng ký) của lõi mã đang chạy . arg3 sẽ được đẩy vào ngăn xếp và thực sự sẽ tồn tại trong bộ nhớ. Lưu ý rằng tại thời điểm này, không có vị trí bộ nhớ trong đó arg1arg2 tồn tại, chúng chỉ nằm trong thanh ghi CPU.

Trong quá trình thực thi chính phương thức, thanh ghi và bộ nhớ được sử dụng khi cần thiết. Và b được gọi.

Bây giờ, nếu a cần arg1 hoặc arg2, nó sẽ phải đẩy trước khi gọi b. Nhưng nếu không, thì nó sẽ không - và mọi thứ thậm chí có thể được sắp xếp lại để giảm nhu cầu này. Ngược lại, những thanh ghi đó có thể đã được sử dụng cho cái gì đó đã có trước thời điểm này - jitter không ngu ngốc, vì vậy nếu nó cần một thanh ghi hoặc một khe trên ngăn xếp và có một cái không được sử dụng cho phần còn lại của phương thức, nó sẽ để tái sử dụng không gian đó. (Đối với vấn đề đó, ở cấp độ trên, trình biên dịch C# sẽ sử dụng lại các khe trong ngăn xếp ảo mà IL đã sử dụng).

Vì vậy, khi b được gọi, arg4 được đặt trong ECX đăng ký, arg5 vào EDX và arg6 được đẩy lên ngăn xếp. Tại thời điểm này, arg1arg2 không tồn tại và bạn không thể tìm ra chúng là gì hơn là bạn có thể đọc một cuốn sách sau khi nó đã được tái chế và biến thành giấy vệ sinh.

(Lưu ý thú vị là nó rất phổ biến cho một phương thức gọi một phương thức khác với cùng một đối số ở cùng một vị trí, trong trường hợp này ECX và EDX có thể chỉ còn lại một mình).

Sau đó, b trả về, đặt giá trị trả về trong sổ đăng ký EAX, hoặc cặp EDX: EAX hoặc bộ nhớ có EAX trỏ đến nó, tùy thuộc vào kích thước, a thực hiện thêm một số thao tác trước khi trả lại trong thanh ghi đó và Sớm.

Bây giờ, điều này giả định không có bất kỳ tối ưu hóa nào được thực hiện. Có thể thực tế, b hoàn toàn không được gọi, mà đúng hơn là mã của nó được gạch chân. Trong trường hợp này cho dù giá trị trong sổ đăng ký hoặc trên ngăn xếp - và trong trường hợp thứ hai, nơi chúng ở trên ngăn xếp, không còn liên quan gì đến chữ ký của b và mọi thứ cần làm với giá trị có liên quan trong thời gian a thực hiện và sẽ khác trong trường hợp có một "cuộc gọi" khác tới b hoặc thậm chí trong trường hợp có một "cuộc gọi" khác tới số b từ a, vì toàn bộ cuộc gọi của a bao gồm cả cuộc gọi đến b có thể đã được gạch chân trong một trường hợp, không được gạch chân trong một trường hợp khác và được gạch chân khác nhau. Ví dụ: arg4 xuất phát trực tiếp từ một giá trị được trả về bởi một cuộc gọi khác, có thể là trong sổ đăng ký EAX tại thời điểm này, trong khi arg5 ở ECX vì nó giống như arg1arg6 ở một nơi nào đó ở giữa ngăn xếp không gian được sử dụng bởi a. Một khả năng khác là cuộc gọi đến b là một cuộc gọi đuôi đã bị loại bỏ: Bởi vì cuộc gọi đến b sẽ có giá trị trả lại ngay lập tức được trả về a (hoặc một số khả năng khác), sau đó thay vì đẩy ngăn xếp, các giá trị được sử dụng bởi a được thay thế tại chỗ và địa chỉ trả lại thay đổi để trả về từ b nhảy trở lại phương thức được gọi là a, bỏ qua một số công việc (và giảm mức sử dụng bộ nhớ ở mức độ phương pháp tiếp cận phong cách chức năng mà sẽ tràn ngăn xếp thay vì làm việc và thực sự làm việc tốt). Trong trường hợp này, trong khi gọi tới b, các tham số đến a có thể hoàn toàn biến mất, ngay cả những thông số đã có trong ngăn xếp.

Rất đáng tranh cãi cho dù trường hợp cuối cùng này thậm chí có được coi là tối ưu hóa hay không; một số ngôn ngữ phụ thuộc nhiều vào nó được thực hiện như với nó họ cung cấp cho hiệu suất tốt và không có họ cho hiệu suất khủng khiếp nếu họ thậm chí làm việc ở tất cả (thay vì tràn ngăn xếp).

Có thể có tất cả các cách tối ưu hóa khác. Có nên là tất cả các cách tối ưu hóa khác - nếu nhóm .NET hoặc nhóm Mono làm điều gì đó làm cho mã của tôi nhanh hơn hoặc sử dụng ít bộ nhớ hơn, nhưng nếu không có thứ gì đó, tôi sẽ không phàn nàn!

Và đó giả định rằng người viết C# ở nơi đầu tiên không bao giờ thay đổi giá trị của một tham số, mà chắc chắn sẽ không đúng.Hãy xem xét mã này:

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

Thậm chí nếu trình biên dịch C# và jitter đã được thiết kế theo một cách lãng phí như vậy mà bạn có thể đảm bảo các thông số không được thay đổi trong cách mô tả ở trên, làm thế nào bạn có thể biết những gì count đã đã từ trong lời gọi factory? Ngay cả trong lần gọi đầu tiên, nó cũng khác, và nó không giống như ở trên là mã lạ.

Vì vậy, trong bản tóm tắt:

  1. Jitter: Các thông số thường enregistered. Bạn có thể mong đợi x86 đặt 2 tham số con trỏ, tham chiếu hoặc số nguyên trong thanh ghi và amd64 để đặt 4 tham số con trỏ, tham chiếu hoặc số nguyên và 4 tham số dấu chấm động vào thanh ghi. Họ không có vị trí để đọc chúng.
  2. Jitter: Thông số trên ngăn xếp thường được ghi đè.
  3. Jitter: Có thể không có một cuộc gọi thực sự nào cả, vì vậy không có nơi nào để tìm kiếm các thông số vì chúng có thể ở bất cứ đâu.
  4. Jitter: "cuộc gọi" có thể được tái sử dụng cùng một khung như hình cuối cùng.
  5. Trình biên dịch: IL có thể sử dụng lại các khe cho người dân địa phương.
  6. Con người: Người lập trình có thể thay đổi giá trị tham số.

Từ tất cả những điều đó, làm thế nào để có thể biết được arg1 là gì?

Bây giờ, hãy thêm vào sự tồn tại của bộ sưu tập rác. Hãy tưởng tượng nếu chúng ta có thể kỳ diệu biết những gì arg1 là anyway, mặc dù tất cả điều này. Nếu nó là một tham chiếu đến một đối tượng trên heap, nó vẫn có thể làm chúng ta không tốt, bởi vì nếu tất cả những điều trên có nghĩa là không có thêm tài liệu tham khảo nào đang hoạt động trên stack - và rõ ràng là điều này chắc chắn không xảy ra - và GC đá vào, sau đó đối tượng có thể đã được thu thập. Vì vậy, tất cả những gì chúng ta kỳ diệu có thể giữ là một tham chiếu đến cái gì đó không còn tồn tại - thực sự có thể là một khu vực trong heap hiện đang được sử dụng cho cái gì khác, bang đi toàn bộ loại an toàn của toàn bộ khuôn khổ!

Nó không phải trong các bit nhỏ nhất tương đương với phản ánh việc thu thập các IL, bởi vì:

  1. Các IL là tĩnh, chứ không phải chỉ là một nhà nước tại một thời điểm cho trước. Tương tự như vậy, chúng tôi có thể nhận được một bản sao các cuốn sách yêu thích của chúng tôi từ một thư viện dễ dàng hơn nhiều so với chúng tôi có thể lấy lại phản ứng của chúng tôi trong lần đầu tiên chúng tôi đọc chúng.
  2. IL không phản ánh tác động của nội tuyến, v.v. Nếu một cuộc gọi được inlined mỗi khi nó thực sự được sử dụng, và sau đó chúng tôi sử dụng sự phản chiếu để có được một phương pháp đó, một thực tế là nó thường được gạch chân là không liên quan.

Các đề xuất trong các câu trả lời khác về lược tả, AOP và chặn cũng gần như bạn sắp nhận được.

* Thực ra, this là thông số đầu tiên thực sự cho các thành viên thể hiện. Cho phép giả vờ tất cả mọi thứ là tĩnh vì vậy chúng tôi không phải tiếp tục chỉ ra điều này.

+0

cảm ơn vì một câu trả lời tuyệt vời – tito11

3

Không thể thực hiện được trong .net. Tại thời gian chạy JITter có thể quyết định sử dụng thanh ghi CPU thay vì ngăn xếp để lưu trữ các tham số phương pháp hoặc thậm chí viết lại các giá trị ban đầu (được truyền) trong ngăn xếp. Vì vậy, nó sẽ có hiệu suất rất tốn kém để .net cho phép để đăng nhập các thông số tại bất kỳ điểm nào trong mã nguồn.

Theo như tôi biết, cách duy nhất bạn có thể làm điều đó nói chung là sử dụng API hồ sơ CLR .net. (Ví dụ, khung công tác Typemock có thể thực hiện những điều như vậy và sử dụng API lược tả CLR)

Nếu bạn chỉ cần chặn các chức năng/thuộc tính ảo (bao gồm các phương thức/thuộc tính giao diện), bạn có thể sử dụng bất kỳ khung chặn nào (Unity hoặc Castle) ví dụ).

Có một số thông tin về .net profiling API:

MSDN Magazine

MSDN Blogs

Brian Long's blog

1

này là không thể trong C#, bạn nên sử dụng một cách tiếp cận AOP và thực hiện lập luận phương pháp ghi nhật ký khi mỗi phương thức được gọi. Bằng cách này bạn có thể tập trung mã đăng nhập của bạn, làm cho nó có thể tái sử dụng và sau đó bạn sẽ chỉ cần đánh dấu các phương thức nào yêu cầu ghi lại đối số.

Tôi tin rằng điều này có thể dễ dàng đạt được bằng cách sử dụng khung AOP như PostSharp.

1

Có thể sẽ không xảy ra nếu không có chế độ nhập kiểu hoặc một số ma thuật ICorDebug. Ngay cả lớp StackFrame chỉ liệt kê các thành viên cho phép bạn nhận thông tin về nguồn chứ không phải thông số.

Tuy nhiên, chức năng mà bạn đang tồn tại là IntelliTrace với ghi nhật ký phương thức. Bạn có thể lọc những gì bạn cần để xem xét.

Các vấn đề liên quan