문제

나는 작업 중입니다 멀티스레드 힙을 손상시키는 C++ 애플리케이션입니다.이러한 손상을 찾는 일반적인 도구는 적용할 수 없는 것 같습니다.소스 코드의 이전 빌드(18개월)는 최신 릴리스와 동일한 동작을 나타내므로 이는 오랫동안 존재해 왔지만 눈에 띄지 않았습니다.단점은 소스 델타를 사용하여 버그가 발생한 시기를 식별할 수 없다는 것입니다. 많이 저장소의 코드 변경 사항.

충돌 동작에 대한 프롬프트는 이 시스템에서 처리량(내부 표현에 포함된 데이터의 소켓 전송)을 생성하는 것입니다.주기적으로 앱에서 예외를 발생시키는 테스트 데이터 세트가 있습니다(다양한 장소, 다양한 원인 - 힙 할당 실패 포함).힙 손상).

이 동작은 CPU 성능이나 메모리 대역폭과 관련된 것으로 보입니다.기계에 더 많은 것이 있을수록 충돌이 더 쉬워집니다.하이퍼스레딩 코어 또는 듀얼 코어 코어를 비활성화하면 손상 비율이 줄어들지만 제거되지는 않습니다.이는 타이밍 관련 문제를 암시합니다.

이제 문제는 다음과 같습니다.
경량 디버그 환경에서 실행되는 경우(예: Visual Studio 98 / AKA MSVC6) 힙 손상은 쉽게 재현할 수 있습니다. 무언가 끔찍하게 실패하고 예외가 발생하기까지 10~15분이 지나야 합니다. alloc; 정교한 디버그 환경(Rational Purify, VS2008/MSVC9 또는 Microsoft Application Verifier) ​​시스템이 메모리 속도 제한을 받고 충돌이 발생하지 않습니다(메모리 제한:CPU가 그 이상으로 올라가지 않습니다 50%, 디스크 표시등이 켜져 있지 않습니다. 프로그램이 최대한 빨리 진행되고 있으며, 상자를 소모하고 있습니다. 1.3G 2G RAM).그래서, 문제를 재현할 수 있는지(원인은 식별할 수 없음) 또는 재현할 수 없는 문제나 원인을 식별할 수 있는지 선택할 수 있습니다.

다음 위치에 대한 현재 최선의 추측은 다음과 같습니다.

  1. 미친듯이 지저분한 상자를 구입하세요(현재 개발 상자를 교체하려면:2GB RAM E6550 Core2 Duo);이를 통해 강력한 디버그 환경에서 실행할 때 잘못된 동작을 유발하는 충돌을 재현할 수 있습니다.또는
  2. 재작성 연산자 new 그리고 delete 사용 VirtualAlloc 그리고 VirtualProtect 작업이 완료되자마자 메모리를 읽기 전용으로 표시합니다.아래에서 실행 MSVC6 그리고 OS가 해제된 메모리에 글을 쓰고 있는 악당을 잡아내도록 하세요.예, 이것은 절망의 표시입니다.도대체 누가 다시 쓴 거야? new 그리고 delete?!이것이 Purify et al.처럼 느리게 만들 것인지 궁금합니다.

그리고 아니요:Purify 계측 기능이 내장되어 배송되는 것은 옵션이 아닙니다.

방금 동료가 지나가다가 "스택 오버플로?"라고 물었습니다.지금 스택 오버플로가 발생하고 있나요?!?"

이제 질문은 다음과 같습니다. 힙 손상자를 어떻게 찾나요?


업데이트:균형을 맞추다 new[] 그리고 delete[] 문제 해결을 위해 많은 노력을 기울인 것 같습니다.15분이 아닌 이제 앱이 충돌하기까지 약 2시간이 소요됩니다.아직 거기에 없습니다.추가 제안이 있으십니까?힙 손상이 지속됩니다.

업데이트:Visual Studio 2008의 릴리스 빌드가 훨씬 더 좋아 보입니다.현재 의혹은 STL 함께 제공되는 구현 VS98.


  1. 문제를 재현합니다. Dr Watson 추가 분석에 도움이 될 수 있는 덤프가 생성됩니다.

그 점을 기록해 두겠지만, 왓슨 박사는 그 사실이 있은 후에야 발이 걸려 넘어질 것이지, 힙이 짓밟힐 때가 아닐 까봐 걱정됩니다.

또 다른 시도는 다음과 같습니다. WinDebug 매우 강력한 디버깅 도구로서 동시에 가볍습니다.

지금 당장 그 일이 진행되고 있습니다.뭔가 잘못될 때까지 별로 도움이 되지 않습니다.나는 현장에서 파손된 사람을 잡고 싶습니다.

아마도 이러한 도구를 사용하면 적어도 문제를 특정 구성 요소로 좁힐 수 있을 것입니다.

별로 희망은 없지만 절박한 시기에는...

그리고 프로젝트의 모든 구성 요소에 올바른 런타임 라이브러리 설정(C/C++ tab, VS 6.0 프로젝트 설정의 코드 생성 카테고리)?

아니요, 아닙니다. 내일 몇 시간 동안 작업 공간(58개 프로젝트 포함)을 살펴보고 적절한 플래그로 모두 컴파일 및 연결되어 있는지 확인하겠습니다.


업데이트:30초가 걸렸습니다.모든 프로젝트를 선택하세요. Settings 대화 상자에서 올바른 설정이 없는 프로젝트(모두 올바른 설정이 있음)를 찾을 때까지 선택을 취소합니다.

도움이 되었습니까?

해결책

나의 첫 번째 선택은 다음과 같은 전용 힙 도구입니다. 페이지힙.exe.

새로 작성하고 삭제하는 것이 유용할 수 있지만 하위 수준 코드에서 커밋된 할당을 포착할 수는 없습니다.이것이 당신이 원하는 것이라면 우회하는 것이 좋습니다 low-level alloc APIMicrosoft Detours를 사용하고 있습니다.

또한 다음과 같은 온전성 검사도 수행합니다.런타임 라이브러리가 일치하는지 확인하세요(릴리스와 릴리스 비교).디버그, 멀티스레드 vs.단일 스레드, dll 대 dllstatic lib) 잘못된 삭제 항목을 찾고(예: delete []를 사용해야 하는 위치 삭제) 할당 항목이 혼합되거나 일치하지 않는지 확인하세요.

또한 선택적으로 스레드를 끄고 문제가 언제 해결되는지 확인하십시오.

첫 번째 예외 발생 시 호출 스택 등은 어떤 모습입니까?

다른 팁

내 업무에도 동일한 문제가 있습니다(우리는 또한 VC6 때때로).그리고 이에 대한 쉬운 해결책은 없습니다.몇 가지 힌트만 있습니다.

  • 프로덕션 시스템에서 자동 크래시 덤프를 사용해 보십시오(참조: 프로세스 덤퍼).내 경험에 따르면 Dr.왓슨은 완벽하지 않다 덤핑용.
  • 모두 제거 잡다(...) 귀하의 코드에서.그들은 종종 심각한 메모리 예외를 숨깁니다.
  • 확인하다 고급 Windows 디버깅 - 당신과 같은 문제에 대한 훌륭한 팁이 많이 있습니다.나는 온 마음을 다해 이것을 추천합니다.
  • 당신이 사용하는 경우 STL 노력하다 STLPort 그리고 빌드를 확인했습니다.잘못된 반복자는 지옥입니다.

행운을 빌어요.귀하와 같은 문제를 해결하는 데 몇 달이 걸립니다.이에 대비하세요...

다음을 사용하여 원본 애플리케이션을 실행합니다. ADplus -crash -pn appnename.exe메모리 문제가 발생하면 멋진 큰 덤프가 표시됩니다.

덤프를 분석하여 어떤 메모리 위치가 손상되었는지 파악할 수 있습니다.운이 좋다면 덮어쓰기 메모리는 그것이 어디서 왔는지 알아낼 수 있는 고유한 문자열입니다.운이 좋지 않으면 파헤쳐 봐야 할 것입니다. win32 힙을 만들고 원래 메모리 특성이 무엇인지 파악합니다.(heap -x가 도움이 될 수 있음)

무엇이 문제인지 파악한 후에는 특별한 힙 설정을 사용하여 Appverifier 사용량을 좁힐 수 있습니다.즉.무엇을 지정할 수 있습니다 DLL 모니터링하거나 모니터링할 할당 크기를 결정합니다.

이를 통해 범인을 잡을 수 있을 만큼 모니터링 속도가 빨라지기를 바랍니다.

내 경험상 전체 힙 검증 모드는 필요하지 않았지만 크래시 덤프를 분석하고 소스를 탐색하는 데 많은 시간을 보냈습니다.

추신:당신이 사용할 수있는 디버그 진단 덤프를 분석합니다.그것은 다음을 지적할 수 있다. DLL 손상된 힙을 소유하고 기타 유용한 세부 정보를 제공합니다.

우리는 우리 자신의 malloc과 free 함수를 작성함으로써 꽤 행운을 누렸습니다.프로덕션에서는 표준 malloc 및 free를 호출하지만 디버그에서는 원하는 모든 작업을 수행할 수 있습니다.또한 이러한 함수를 사용하기 위해 new 및 delete 연산자를 재정의하는 것 외에는 아무 작업도 수행하지 않는 간단한 기본 클래스가 있으며, 그러면 작성하는 모든 클래스가 해당 클래스에서 상속될 수 있습니다.엄청난 양의 코드가 있는 경우 malloc에 ​​대한 호출을 대체하고 새로운 malloc을 해제하고(realloc을 잊지 마세요!) 해제하는 것이 큰 작업일 수 있지만 장기적으로는 매우 유용합니다.

스티브 맥과이어의 책에서 견고한 코드 작성 (강력히 권장됨) 다음과 같이 이러한 루틴에서 수행할 수 있는 디버그 작업의 예가 있습니다.

  • 누출을 찾기 위해 할당을 추적합니다.
  • 필요한 것보다 더 많은 메모리를 할당하고 메모리의 시작과 끝 부분에 마커를 배치합니다. 자유 루틴 중에 이러한 마커가 여전히 남아 있는지 확인할 수 있습니다.
  • 할당(초기화되지 않은 메모리 사용량 찾기) 및 해제(해제된 메모리 사용량 찾기) 마커를 사용하여 메모리를 memset합니다.

또 다른 좋은 아이디어는 절대 같은 것을 사용 strcpy, strcat, 또는 sprintf -- 항상 사용 strncpy, strncat, 그리고 snprintf.우리는 버퍼의 끝 부분을 쓰지 않도록 하기 위해 이것에 대한 우리 자신의 버전도 작성했으며, 이것 역시 많은 문제를 발견했습니다.

런타임 분석과 정적 분석을 모두 사용하여 이 문제를 해결해야 합니다.

정적 분석의 경우 PREfast(cl.exe /analyze).불일치를 감지합니다. delete 그리고 delete[], 버퍼 오버런 및 기타 여러 문제가 있습니다.하지만 특히 프로젝트에 여전히 L4 고정되지 않았습니다.

PREfast는 Visual Studio Team System에서 사용할 수 있으며, 보기에, Windows SDK의 일부로.

메모리 손상의 명백한 무작위성은 스레드 동기화 문제와 매우 흡사합니다. 버그는 시스템 속도에 따라 재현됩니다.객체(메모리 덩어리)가 스레드 간에 공유되고 동기화(중요 섹션, 뮤텍스, 세마포어, 기타) 기본 요소가 클래스별(객체별, 클래스별) 기반이 아닌 경우 다음과 같은 상황이 발생할 수 있습니다. 여기서 클래스(메모리 덩어리)는 사용 중에 삭제/해제되거나 삭제/해제된 후에 사용됩니다.

이에 대한 테스트로 각 클래스와 메서드에 동기화 기본 요소를 추가할 수 있습니다.이렇게 하면 많은 개체가 서로를 기다려야 하기 때문에 코드 속도가 느려지지만 이로 인해 힙 손상이 제거되면 힙 손상 문제는 코드 최적화 문제가 됩니다.

메모리가 부족한 상황인가요?그렇다면 새로운 것이 돌아올 수도 있습니다. NULL std::bad_alloc을 던지는 대신.이전 VC++ 컴파일러는 이것을 제대로 구현하지 않았습니다.에 관한 기사가 있습니다. 레거시 메모리 할당 실패 충돌 STL 다음으로 구축된 앱 VC6.

이전 빌드를 시도했지만 저장소 기록에서 계속 더 뒤로 돌아가서 버그가 발생한 정확한 시기를 확인할 수 없는 이유가 있습니까?

그렇지 않은 경우에는 문제를 추적하는 데 도움이 되는 일종의 간단한 로깅을 추가하는 것이 좋습니다. 하지만 구체적으로 기록하려는 항목이 무엇인지는 알 수 없습니다.

Google 및 발생한 예외 문서를 통해 이 문제를 일으킬 수 있는 것이 정확히 무엇인지 알아낼 수 있다면 코드에서 찾아야 할 사항에 대한 추가 통찰력을 얻을 수 있습니다.

내 첫 번째 조치는 다음과 같습니다.

  1. "릴리스" 버전에서 바이너리를 빌드하되 디버그 정보 파일을 생성합니다(이 가능성은 프로젝트 설정에서 찾을 수 있습니다).
  2. 문제를 재현하려는 시스템에서 Dr Watson을 기본 디버거(DrWtsn32 -I)로 사용하십시오.
  3. 문제를 재현해 보세요.Watson 박사는 추가 분석에 도움이 될 수 있는 덤프를 생성할 것입니다.

또 다른 시도는 WinDebug를 매우 강력하면서도 가벼운 디버깅 도구로 사용하는 것입니다.

아마도 이러한 도구를 사용하면 적어도 문제를 특정 구성 요소로 좁힐 수 있을 것입니다.

그리고 프로젝트의 모든 구성 요소에 올바른 런타임 라이브러리 설정(C/C++ 탭, VS 6.0 프로젝트 설정의 코드 생성 범주)이 있는지 확인하십시오.

따라서 귀하가 갖고 있는 제한된 정보에서 이는 하나 이상의 항목이 조합될 수 있습니다.

  • 잘못된 힙 사용량(예: 이중 해제, 해제 후 읽기, 해제 후 쓰기, allocs로 HEAP_NO_SERIALIZE 플래그 설정 및 동일한 힙의 여러 스레드에서 해제)
  • 메모리 부족
  • 잘못된 코드(예: 버퍼 오버플로, 버퍼 언더플로 등)
  • "타이밍" 문제

처음 두 개는 있지만 마지막은 아닌 경우 지금쯤 pageheap.exe를 사용하여 발견했을 것입니다.

이는 코드가 공유 메모리에 액세스하는 방식 때문일 가능성이 높습니다.불행하게도, 그것을 추적하는 것은 다소 고통스러울 것입니다.공유 메모리에 대한 비동기식 액세스는 종종 이상한 "타이밍" 문제로 나타납니다.공유 메모리에 대한 액세스를 플래그와 동기화하기 위해 획득/해제 의미 체계를 사용하지 않거나 잠금을 적절하게 사용하지 않는 등의 것입니다.

최소한 앞서 제안한 것처럼 어떻게든 할당을 추적할 수 있으면 도움이 될 것입니다.그러면 적어도 힙 손상이 발생할 때까지 실제로 발생한 일을 확인하고 이를 통해 진단을 시도할 수 있습니다.

또한 할당을 여러 힙으로 쉽게 리디렉션할 수 있는 경우 이를 통해 문제가 해결되는지 또는 더 재현 가능한 버그 동작이 발생하는지 확인할 수 있습니다.

VS2008로 테스트할 때 메모리 절약을 예로 설정하고 HeapVerifier를 실행했습니까?그러면 힙 할당자의 성능 영향이 줄어들 수 있습니다.(또한 디버그->응용 프로그램 검증 프로그램으로 시작을 실행해야 하지만 이미 알고 있을 수도 있습니다.)

Windbg 및 !heap 명령의 다양한 사용으로 디버깅을 시도할 수도 있습니다.

MSN

새로 작성/삭제를 다시 작성하기로 선택한 경우 이 작업을 수행했으며 다음 위치에 간단한 소스 코드가 있습니다.

http://gandolf.homelinux.org/~smhanov/blog/?id=10

이는 메모리 누수를 포착하고 메모리 블록 전후에 보호 데이터를 삽입하여 힙 손상을 포착합니다.모든 CPP 파일 상단에 #include "debug.h"를 넣고 DEBUG 및 DEBUG_MEM을 정의하면 통합할 수 있습니다.

Graeme의 맞춤형 malloc/free 제안은 좋은 생각입니다.활용 방법을 제공하기 위해 부패에 대한 몇 가지 패턴을 특성화할 수 있는지 확인하십시오.

예를 들어, 항상 동일한 크기(예: 64바이트)의 블록에 있는 경우 malloc/free 쌍을 변경하여 항상 자체 페이지에 64바이트 청크를 할당합니다.64바이트 청크를 해제하면 해당 페이지에 메모리 보호 비트를 설정하여 읽기 및 쓰기를 방지합니다(VirtualQuery 사용).그러면 이 메모리에 액세스하려고 시도하는 사람은 누구나 힙을 손상시키는 대신 예외를 생성하게 됩니다.

이는 처리되지 않은 64바이트 청크의 수가 중간 정도이거나 상자에서 태울 메모리가 많다고 가정합니다!

비슷한 문제를 해결해야 할 시간이 거의 없었습니다.문제가 여전히 존재하는 경우 다음을 수행하는 것이 좋습니다.new/delete 및 malloc/calloc/realloc/free에 대한 모든 호출을 모니터링합니다.모든 호출을 등록하는 함수를 내보내는 단일 DLL을 만듭니다.이 함수는 코드 소스, 할당된 영역에 대한 포인터 및 이 정보를 테이블에 저장하는 호출 유형을 식별하기 위한 매개변수를 수신합니다.모든 할당/해제 쌍이 제거됩니다.마지막에 또는 필요한 후에 왼쪽 데이터에 대한 보고서를 생성하기 위해 다른 함수를 호출합니다.이를 통해 잘못된 호출(신규/무료 또는 malloc/삭제) 또는 누락을 식별할 수 있습니다.코드에 버퍼를 덮어쓴 경우 저장된 정보가 잘못될 수 있지만 각 테스트에서는 식별된 오류에 대한 솔루션을 감지/발견/포함할 수 있습니다.오류를 식별하는 데 도움이 되도록 여러 번 실행합니다.행운을 빌어요.

이것이 경쟁 조건이라고 생각하십니까?여러 스레드가 하나의 힙을 공유하고 있습니까?HeapCreate를 사용하여 각 스레드에 개인 힙을 제공하면 HEAP_NO_SERIALIZE를 사용하여 빠르게 실행할 수 있습니다.그렇지 않고 다중 스레드 버전의 시스템 라이브러리를 사용하는 경우 힙은 스레드로부터 안전해야 합니다.

몇 가지 제안.W4에서 수많은 경고를 언급하셨습니다. 경고 수준 4에서 깔끔하게 컴파일되도록 코드를 수정하는 데 시간을 할애하는 것이 좋습니다. 이렇게 하면 찾기 어려운 미묘한 버그를 방지하는 데 큰 도움이 됩니다.

두 번째 - /analyze 스위치의 경우 실제로 많은 경고가 생성됩니다.내 프로젝트에서 이 스위치를 사용하기 위해 내가 한 일은 #pragma warning을 사용하여 /analyze에서 생성된 모든 추가 경고를 끄는 새 헤더 파일을 만드는 것이었습니다.그런 다음 파일 아래쪽에서 관심 있는 경고만 켭니다.그런 다음 /FI 컴파일러 스위치를 사용하여 이 헤더 파일이 모든 컴파일 단위에 먼저 포함되도록 합니다.이를 통해 출력을 제어하는 ​​동안 /analyze 스위치를 사용할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top