2012-08-24 7 views
6

저는 C# 응용 프로그램을위한 손쉬운 로그 메커니즘을 연구하고 있습니다. 스택 트레이스 (이 Environment.StackTrace를 통해 수행 할 수 있습니다)와 값을 감지 할 수보다 다시 log()를 호출 a(arg1, arg2, arg 3.....) 통화 b(arg4,arg5,arg6....)를 작동C#에서 함수를 호출하는 메서드의 인수를 알 수있는 방법이 있습니까?

기능 : 여기

내가 그것을 모양을하고 싶은거야 stacktrace에서 각 함수 (예 : ab)가 호출됩니다.

디버그 및 릴리스 모드 (또는 적어도 디버그 모드에서 작동)를 원합니다.

.net에서 가능합니까?

+0

흥미로운 질문 ... C#에서는 가능하지 않지만 IL 또는 반사를 사용했을 가능성이 있습니까? –

+2

내 생각에 그건 불가능합니다. 인터뷰 질문에 대한 좋은 평판을 얻지 못할 것입니다. (그것이 가능할 것이라고 상상해보십시오 - 어떻게 작동 할 것이라고 생각하십니까?) 가장 일반적인 "이상한 질문"보다 나은 방법입니다. C# 및 CLR 등에 대한 전반적인 지식을 밝힐 수있는 좋은 출발점입니다. –

+0

이론적으로는 가능해야합니다. 서수 stacktrace는 인수 유형뿐만 아니라 호출되는 함수에 대한 정보를 반환하며, 함수를 호출하는 데 사용 된 모든 변수는 어딘가에 스택에 저장해야합니다 (로컬 변수 일 수도 있음). –

답변

10

라도 유용 할 수 없습니다 :

시간 b으로

이라고에서 사용하는 스택의 공간 A의 arg1합니다 (IL 스택이, 그래서 아마도 그것은 심지어 스택에 넣어되지 않았다, 그러나에 enregistered했다 전화)는 arg1에 의해 계속 사용되는 것이 보장되지 않습니다.

arg1이 참조 유형 인 경우 참조 된 객체는 b을 호출 한 후 사용되지 않으면 가비지 수집되지 않았 음을 보장하지 않습니다.

편집 :

좀 더 상세하게, 당신의 의견을 알 수 있기 때문에 당신이 grokking하고 여전히 가능해야한다 생각하고 있습니다.

지터가 사용하는 호출 규칙은 구현 자에게 자유롭게 개선 할 수있는 관련 표준에 대한 사양에 지정되어 있지 않습니다. 그들은 실제로 32 비트와 64 비트 버전과 다른 버전에서 다릅니다.

그러나 MS 사람들의 기사에서는 사용 된 규칙이 __fastcall 규칙과 유사하다고 제안합니다. a을 호출 할 때 arg1을 ECX 레지스터에 넣고 EDX 레지스터에 arg2을 넣습니다 (32 비트 x86을 가정하여 단순화하고 amd64를 더 많은 인수가 등록됨) 코드가 실행되는 코어 . arg3은 스택에 푸시되고 실제로 메모리에 존재합니다.

이 시점에서 arg1arg2이있는 메모리 위치는 없으며 CPU 레지스터에만있는 점에 유의하십시오.

메서드 자체를 실행하는 과정에서 필요에 따라 레지스터와 메모리가 사용됩니다. 그리고 b이 호출됩니다.

aarg1 또는 arg2이 필요하면 b을 호출해야합니다. 하지만 그렇게하지 않으면 그럴 필요가 없습니다. 그리고 이러한 요구를 줄이기 위해 물건을 다시 주문할 수도 있습니다. 반대로이 레지스터는 이미이 시점에서 이미 다른 용도로 사용되었을 수도 있습니다. 지터가 바보가 아니므로 스택에 레지스터 나 슬롯이 필요하고 나머지 메서드에는 사용되지 않는 레지스터가 있으면 그 공간을 재사용 할 수 있습니다. (위의 레벨에서 C# 컴파일러는 IL에서 사용 된 가상 스택의 슬롯을 다시 사용합니다.)

b이 호출되면 arg4은 레지스터 ECX에, arg5은 EDX로, arg6은 스택에 푸시됩니다. 이 시점에서 arg1arg2은 존재하지 않으므로 휴지통으로 바꿔 화장지로 바꾼 다음 책을 읽을 수있는 것보다 더 이상 내용을 찾을 수 없습니다.

(흥미로운 점은 같은 위치에서 같은 인수를 사용하여 다른 메서드를 호출하는 방법이 매우 일반적이라는 점입니다.이 경우 ECX와 EDX는 그냥 그대로 둘 수 있습니다).

b 그런 다음 반환 값을 EAX 레지스터 또는 EDX : EAX 쌍 또는 EAX가 가리키는 메모리에 넣고 반환합니다. 크기에 따라 a은 반환 값을 레지스터에 넣기 전에 더 많은 작업을 수행합니다. 곧.

이제는 최적화가 수행되지 않았다고 가정합니다. 사실 b이 전혀 호출되지 않았을 수도 있지만 코드가 인라 인 된 것일 수 있습니다. 이 경우 레지스터 또는 스택에있는 값과 스택에있는 값이 더 이상 b의 서명과 관련이 없으며 관련 값이 a 인 곳과 관련이 없습니다. 'b에 대한 호출을 포함하여 다른 호출이 b 인 경우 또는 b에서 a의 전체 호출이 인라인되었을 수 있기 때문에 b에서 다른 "호출"의 경우에도 ba에서 달라질 수 있습니다. 다른 경우에 인라인되지 않으며, 또 다른 경우에 인라인됩니다. 예를 들어, arg4이 다른 호출에 의해 반환 된 값에서 바로 나온 경우이 시점에서 EAX 레지스터에있을 수 있습니다. 은 arg1과 동일하므로 ECX에 있었고 arg6은 중간에 중간에있었습니다. 스택 공간은 a에 의해 사용됩니다.

또 다른 가능성은 b에 대한 호출이 제거 된 꼬리 호출했다입니다 : b에 대한 호출은, 그것의 반환에 즉시 a가 너무 반환 값 (또는 다른 가능성을)해야 할 것보다는에 밀어 되었기 때문에 스택에서 a에 의해 사용 된 값이 바뀌고 반송 주소가 변경되어 b의 결과가 a이라는 메서드로 돌아가서 일부 작업을 건너 뛰고 (일부는 메모리 사용을 줄임) 기능적 스타일의 접근 방식이 대신 스택을 오버플로하여 실제로 잘 작동 함). 이 경우 b을 호출하는 동안 a에 대한 매개 변수는 스택에 있었던 경우조차 완전히 사라 졌을 가능성이 높습니다.

마지막 사례가 최적화로 간주되어야할지 여부는 매우 논쟁의 여지가 있습니다. 일부 언어는 성능에 크게 의존하고 좋은 성능을 발휘하며 스택없이 넘치는 대신 끔찍한 성능을 발휘합니다.

다른 모든 최적화가 가능합니다. 이어야합니다. .NET 팀 또는 Mono 팀이 내 코드를 더 빠르게 만들거나 메모리를 덜 사용하지만 달리 행동하지 않으면 무언가를 수행하지 않고도 한 가지를 수행하지 않아도됩니다. 불평!

그리고 처음에는 C#을 작성한 사람이 매개 변수의 값을 변경하지 않았다고 가정합니다. 이는 분명 사실이 아닙니다.

는 C# 컴파일러 및 지터는 위에 설명 된 방식으로 변경되지 않은 매개 변수를 보장 할 수있는 그런 낭비 방식으로 설계되었다하더라도
IEnumerable<T> RepeatedlyInvoke(Func<T> factory, int count) 
{ 
    if(count < 0) 
    throw new ArgumentOutOfRangeException(); 
    while(count-- != 0) 
    yield return factory(); 
} 

, 어떻게 당신이 count이 이미 한 것을 알 수 :이 코드를 고려 factory의 호출에서 왔습니까? 첫 번째 호출에서조차도 다르다. 이상한 코드는 위와 같지 않다.

은 그래서, 요약 :

  1. 지터 : 매개 변수는 종종 enregistered된다. x86에 2 개의 포인터, 참조 또는 정수 매개 변수를 레지스터에 넣고 amd64를 사용하여 4 개의 포인터, 참조 또는 정수 매개 변수 및 4 개의 부동 소수점 매개 변수를 레지스터에 넣을 수 있습니다. 그들에게는 읽을 곳이 없습니다.
  2. 지터 : 스택의 매개 변수는 종종 덮어 씁니다.
  3. 지터 : 실제 호출이 전혀 없을 수도 있으므로 매개 변수를 찾을 수있는 곳이 없습니다.
  4. 지터 : "호출"은 마지막 프레임과 동일한 프레임을 다시 사용할 수 있습니다.
  5. 컴파일러 : IL은 지역 주민을 위해 슬롯을 다시 사용할 수 있습니다.
  6. 사람 : 프로그래머는 매개 변수 값을 변경할 수 있습니다.

그 모두에서 어떻게 arg1이 무엇인지 알 수 있습니까?

이제 가비지 수집을 추가하십시오. 우리가 마침내 무엇이든간에 arg1이 어쨌든 무엇인지 알 수 있다고 상상해보십시오. 힙에있는 객체에 대한 참조 인 경우 위의 모든 내용이 스택에서 활성 상태 인 참조가 더 이상 없다는 것을 의미하기 때문에 여전히 좋지 않을 수 있습니다. 그리고 이것이 확실히 발생한다는 것이 분명해야합니다. GC가 시작되면 개체가 수집 될 수 있습니다. 그래서 우리가 마술처럼 구할 수있는 것은 더 이상 존재하지 않는 것에 대한 참조입니다. 실제로 힙의 영역에 현재 다른 용도로 사용되고있을 가능성이 있습니다. 전체 프레임 워크의 전체 유형 안전성이 강타됩니다!

  1. 일리노이 오히려 특정 시점에서 단지 상태보다는 정적이다 때문에

    은 상기 IL을 얻는 반사 비교 조금도에 아니다. 마찬가지로, 우리가 처음 읽을 때 우리의 반응을 되돌릴 수있는 것보다 훨씬 쉽게 도서관에서 우리가 좋아하는 책의 복사본을 얻을 수 있습니다.

  2. IL은 인라이닝 등의 영향을 반영하지 않습니다. 호출이 실제로 사용될 때마다 인라인 된 다음 리플렉션을 사용하여 해당 메서드의 MethodBody을 얻었습니다. 일반적으로 인라인 된 것은 무의미한 사실입니다.

프로필 링, AOP 및 차단에 대한 다른 답변은 얻을 수있는만큼 가깝습니다.

* 실제로는 this이 인스턴스 구성원의 실제 매개 변수입니다. 모든 것을 고정적이라고 생각하게하여 이것을 지적 할 필요가 없습니다.

+0

대단한 답변에 감사드립니다. – tito11

3

.net에서는 불가능합니다. 런타임시 JITter는 메소드 매개 변수를 저장하거나 스택의 초기 (전달 된) 값을 재 작성하기 위해 스택 대신 CPU 레지스터를 사용하기로 결정할 수 있습니다. 따라서 소스 코드의 어느 지점에서나 매개 변수를 기록 할 수 있도록하는 것은 매우 비용이 많이 드는 .net 일 것입니다.

내가 아는 한 일반적으로 할 수있는 유일한 방법은 .NET CLR 프로파일 링 API를 사용하는 것입니다.

가상 함수/속성 (인터페이스 메서드/속성 포함) 호출을 가로 채기 만하면 모든 가로 채기 프레임 워크 (Unity 또는 Castle)를 사용할 수 있습니다 (예 : Typemock 프레임 워크는 이러한 작업을 수행 할 수 있으며 CLR 프로파일 링 API를 사용합니다. 예를 들어).

MSDN Magazine

MSDN Blogs

이 C#에서 수 없습니다

Brian Long's blog

1

, 당신은 AOP 방식을 사용하고 메소드 인수를 수행해야합니다

는 .NET 프로파일 링 API에 대한 몇 가지 정보가있다 각 메소드가 호출 될 때 로깅. 이렇게하면 로깅 코드를 중앙 집중화하고 재사용 할 수 있으며, 그러면 인수 로깅을 요구하는 메소드를 표시하기 만하면됩니다.

PostSharp과 같은 AOP 프레임 워크를 사용하면 쉽게 달성 할 수 있다고 생각합니다.

1

아마도 유형 조롱이나 ICorDebug 매직 없이는 일어나지 않을 것입니다. 심지어 StackFrame 클래스는 매개 변수가 아닌 소스에 대한 정보를 얻을 수있는 멤버 만 나열합니다.

그러나 현재 사용중인 기능은 메서드 로깅을 사용하여 IntelliTrace로 존재합니다. 검토를 위해 필요한 것을 필터링 할 수 있습니다.

관련 문제