원래 주소: http://blogs.msdn.com/tess/archive/2006/10/10/ASP.NET-Crash-_2D00_-Crazy-looping-in-a-SiteMap.aspx
게시됨: 2006년 10월 10일 화요일 오후 4:10
작성자: Tess
어느 날, 저는 제 블로그에 대해 다음과 같은 질문을 간략하게 설명하는 이메일을 받았습니다.
사이트맵을 빨리 만들고 싶어서 BuildSiteMap() 메서드를 재정의하고 그 안에 가짜 사이트맵 노드를 추가하는 루프를 작성했습니다.
공개 재정의 SiteMapNode BuildSiteMap(){
for (int i = 0; i < 5; i++)
myRoot.ChildNodes.Add(new SiteMapNode(this, i.ToString(), i.ToString(), i.ToString()));
myRoot를 반환합니다.
}
프로그램을 실행하면 스택 오버플로가 발생하고 서버가 충돌합니다. 디버거를 사용하여 단계별로 살펴보니 정말 이상한 점을 발견했습니다.
1) int i = 0
2) 나는 < 5
3) 마이루트...
4) 정수 i = 0
5) 나는 < 5
등.
i의 값은 루프가 올바른 것처럼 보이는 SiteMapNode(속성 액세스, 메서드 호출)를 호출하지 않는 한 결코 증가하지 않는 것 같습니다.
이 루프를 불확정하게 만드는 이유는 무엇입니까? 언뜻 보면 컴파일러나 CLR 버그일 수 있습니다.
(이 문제가 발생했을 때 저는 ASP.NET 2.0의 사이트 탐색에 대해 전혀 몰랐지만 다음 기사를 발견했습니다... http://weblogs.asp. net/scottgu/archive/2005/11/20/431019.aspx 및 http://aspnet.4guysfromrolla.com/articles/111605-1.aspx , 설명이 정말 좋습니다.)
초기 생각
이 문제에서 가장 중요한 점은 항상 다시 시작된다는 점입니다. 즉, 그 자리에서 디버깅이 가능하다는 것입니다. 하지만 아직 그렇게 멀리 가지 말고, 되돌아보고 지금 우리가 가진 것이 무엇인지 살펴봅시다...
1. 스택 오버플로
2. 계속해서 시작되는 사이클
이전 블로그 포스팅에서 스택 오버플로에 대해 다루었는데, 다시 한번 말씀드리지만... 스택 오버플로는 함수 포인터, 변수 포인터, 매개변수 등이 너무 많이 할당되어 스택에 할당된 메모리 양이 부족할 때 발생합니다. 충분한. 스택 오버플로의 가장 일반적인 원인은 재귀가 종료되지 않는 것입니다. 즉, 함수 A는 함수 B를 호출하고, 함수 B는 함수 A를 호출합니다.
콜스택은 이렇게 생겼습니다....
...
함수B()
함수A()
함수B()
함수A()
좋아요, 다 괜찮습니다. 하지만 그것은 단지 스택 오버플로를 설명하는 것뿐입니다. 그렇다면 이 미친 순환에는 무슨 일이 벌어지고 있는 걸까요?
좋아요...그런 함수가 있다고 상상해보세요(-->에 중단점이 있습니다)
무효 MyRecursiveFunction(){
for(int i=0; i<5; i++){
--> MyRecursiveFunction();
}
}
중단점에서 처음 멈출 때 i 값은 0이어야 하며 콜스택은 다음과 같습니다.
내재귀함수()
...
이제 MyRecrusive 함수를 호출합니다. 함수 자체가 호출될 때마다 i=0이 다시 나타납니다(실제로 동일한 루프에 있지는 않지만). MyRecrusive 함수를 여러 번 호출하고 이를 실제 실행되는 코드로 바꾸면 다음과 유사한 코드가 실행됩니다.
for(int i=0; i<5; i++){
for(int i2=0; i2<5; i2++){
for(int i3=0; i3<5; i3++){
for(int i4=0; i4<5; i4++){
for(int i5=0; i5<5; i5++){
for(int i6=0; i6<5; i6++){
for(int i7=0; i7<5; i7++){
...
}
}
}
}
}
}
}
...Visual Studio에서 보면 동일한 루프가 항상 실행되고 변수 i의 값이 변경되지 않는 것처럼 보입니다. 지금은 실제로 스택 호출을 보기 전까지는 이에 대해 깊이 이해하지 못할 것입니다.
콜스택을 살펴보면 이제 콜스택은 다음과 같습니다...
MyRecursiveFunction()
내재귀함수()
내재귀함수()
내재귀함수()
내재귀함수()
내재귀함수()
내재귀함수()
...
초기 생각의 결론은 의심할 여지 없이 재귀를 살펴보고 싶다는 것입니다...하지만 어디에서?
myRoot.ChildNodes.Add(new SiteMapNode(this, i.ToString(), i.ToString(), i.ToString()));
예제의 코드는
그다지 복잡해 보이지 않습니다.
여기서 가장 의심스러운 점은 다음과 같습니다
.new SiteMapNode() 및 myRoot.ChildNodes.Add()를 사용하여 리플렉터를 사용하면 더 이상 신비롭지 않습니다.
디버깅 문제
마지막으로:) 더 적은 단어, 더 많은 windbg 액션...
다시 렌더링하기 쉽기 때문에 Windbg(파일/프로세스에 첨부)를 w3wp.exe에 연결하고 g를 눌러 시작하면 컴퓨터에서 다시 렌더링하겠습니다. 그런 다음 문제가 다시 나타나고 프로그램이 중단되어 스택 오버플로(우리가 이미 알고 있음)라고 알려줍니다.
(7e4.ddc): 스택 오버플로 - 코드 c00000fd(첫 번째 기회)
첫 번째 예외는 예외 처리 전에 보고됩니다.
이 예외는 예상되고 처리될 수 있습니다.
eax=0fa4235c ebx=02beca74 ecx=02beca74 edx=02becb54 esi=02becb54 edi=02beca74
eip=686b5cb4 esp=02163000 ebp=02163004 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210246
System_Web_ni+0xf5cb4:686b5cb4 56 push esi
스택을 확인하고 !clrstack 명령을 사용하여 스택이 어떻게 중단되었는지 살펴보겠습니다. 하지만 우리는 볼 수만 있습니다....
0:016> !clrstack
OS 스레드 ID: 0xddc (16)
ESP EIP 02163000 686b5cb4 System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode)
... 이것은 실제로 우리에게 별로 도움이 되지 않습니다. 때때로 스택 오버플로가 발생할 때 !clrstack 명령을 사용하면 이러한 문제가 발생합니다. 따라서 원시 스택을 보려면 !dumpstack 명령도 사용해야 합니다.
0:016> !쓰레기통
OS 스레드 ID: 0xddc (16)
현재 프레임: (MethodDesc 0x68b03720 +0x4 System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))
ChildEBP RetAddr 발신자, 수신자
02163004 686b1fc4(MethodDesc 0x68aeff30 +0x18 System.Web.SiteMapNode.get_ChildNodes())
0216300c 0f765641 (MethodDesc 0xfa42328 +0x59 ViewSiteMapProvider.BuildSiteMap())
0216303c 686b5cdf(MethodDesc 0x68b03720 +0x2f System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))
02163074 686b1fc4(MethodDesc 0x68aeff30 +0x18 System.Web.SiteMapNode.get_ChildNodes())
0216307c 0f765641(MethodDesc 0xfa42328 +0x59 ViewSiteMapProvider.BuildSiteMap())
021630ac 686b5cdf(MethodDesc 0x68b03720 +0x2f System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))
021630e4 686b1fc4(MethodDesc 0x68aeff30 +0x18 System.Web.SiteMapNode.get_ChildNodes())
021630ec 0f765641 (MethodDesc 0xfa42328 +0x59 ViewSiteMapProvider.BuildSiteMap())
0216311c 686b5cdf(MethodDesc 0x68b03720 +0x2f System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))
02163154 686b1fc4(MethodDesc 0x68aeff30 +0x18 System.Web.SiteMapNode.get_ChildNodes())
0216315c 0f765641 (MethodDesc 0xfa42328 +0x59 ViewSiteMapProvider.BuildSiteMap())
...
좋습니다. ChildNodes 속성에 문제가 있는 것 같습니다. 이 속성을 사용하면 GetChildNodes 함수가 호출되어 BuildSiteMap 함수가 다시 호출되고 다시 ChildNodes 속성이 호출되는 식으로 스택 오버플로가 발생합니다.
결론적으로
BuildSitemap에 대한 문서에서 다음 단락을 찾을 수 있습니다.
BuildSiteMap 메서드는 FindSiteMapNode, GetChildNodes 및 GetParentNode 메서드의 기본 구현에 의해 호출됩니다. 파생 클래스에서 BuildSiteMap 메서드를 재정의하는 경우 사이트 맵 데이터를 한 번만 로드하고 후속 호출에서 반환하는지 확인하세요.
재귀 및 스택 오버플로를 방지하려면 이 메서드를 호출하지 않는 것이 가장 좋습니다. BuildSiteMap 예제에서처럼 AddNode 메서드를 사용하여 하위 노드를 추가할 수 있습니다.
이 내용은 사이트 맵 제공자 문서에 보관되어 있으며 읽어 볼 가치가 있습니다.
BuildSiteMap은 일반적으로 다른 사이트 맵에서 제공하는 메서드나 속성을 호출해서는 안 됩니다. 왜냐하면 많은 메서드와 속성이 기본적으로 BuildSiteMap 호출을 구현하기 때문입니다. 예를 들어 BuildSiteMap의 RootNode는 재귀를 발생시켜 스택 오버플로로 인해 종료됩니다.
http://www.cnblogs.com/Ring1981/archive/2006/10/19/443280.html