C++에서 예외가 발생한 후 소스를 찾으시나요?
-
09-06-2019 - |
문제
MS VC++에서 답변을 찾고 있습니다.
불행하게도 C++ 예외가 매우 광범위하게 사용되는 대규모 C++ 응용 프로그램을 디버깅할 때.때로는 실제로 원하는 것보다 조금 늦게 예외를 포착할 때도 있습니다.
의사 코드의 예:
FunctionB()
{
...
throw e;
...
}
FunctionA()
{
...
FunctionB()
...
}
try
{
Function A()
}
catch(e)
{
(<--- breakpoint)
...
}
디버깅할 때 중단점을 사용하여 예외를 포착할 수 있습니다.하지만 예외가 발생한 경우 다시 추적할 수 없습니다. FunctionA()
또는 FunctionB()
, 또는 다른 기능.(광범위한 예외 사용과 위 예제의 거대한 버전을 가정).
내 문제에 대한 한 가지 해결책은 호출 스택을 결정하고 저장하는 것입니다. 예외 생성자에서 (즉.잡히기 전에).그러나 이렇게 하려면 이 기본 예외 클래스에서 모든 예외를 파생시켜야 합니다.또한 많은 코드가 필요하고 프로그램 속도가 느려질 수도 있습니다.
작업을 덜 필요로 하는 더 쉬운 방법이 있습니까?대규모 코드 베이스를 변경하지 않고도 가능합니까?
다른 언어에 이 문제에 대한 더 나은 해결책이 있습니까?
해결책
예외가 어디서 발생했는지에만 관심이 있다면 다음과 같은 간단한 매크로를 작성할 수 있습니다.
#define throwException(message) \
{ \
std::ostringstream oss; \
oss << __FILE __ << " " << __LINE__ << " " \
<< __FUNC__ << " " << message; \
throw std::exception(oss.str().c_str()); \
}
파일 이름, 줄 번호 및 함수 이름을 예외 텍스트에 추가합니다(컴파일러가 해당 매크로를 제공하는 경우).
그런 다음 다음을 사용하여 예외를 발생시킵니다.
throwException("An unknown enum value has been passed!");
다른 팁
코드에서 중단점을 지적했습니다.디버거에 있으므로 예외 클래스의 생성자에 중단점을 설정하거나 발생한 모든 예외를 중단하도록 Visual Studio 디버거를 설정할 수 있습니다(디버그->예외 C++ 예외를 클릭하고 발생 및 포착되지 않은 옵션 선택).
많은 어려운 디버깅 질문을 다루는 John Robbins가 쓴 훌륭한 책이 있습니다.책 이름은 Microsoft .NET 및 Microsoft Windows용 애플리케이션 디버깅.제목에도 불구하고 이 책에는 기본 C++ 응용 프로그램 디버깅에 대한 많은 정보가 포함되어 있습니다.
이 책에는 발생한 예외에 대한 호출 스택을 가져오는 방법에 대한 긴 섹션이 있습니다.내 기억이 정확하다면 그의 조언 중 일부는 다음과 같습니다. 구조적 예외 처리 (SEH) C++ 예외 대신(또는 추가로).나는 정말로 그 책을 충분히 추천할 수 없다.
예외 개체 생성자에 중단점을 넣습니다.예외가 발생하기 전에 중단점을 얻게 됩니다.
예외의 원인을 찾을 수 있는 방법이 없습니다. ~ 후에 던질 때 해당 정보를 포함하지 않으면 잡힌다.예외를 포착할 때쯤에는 스택이 이미 해제되어 스택의 이전 상태를 재구성할 방법이 없습니다.
생성자에 스택 추적을 포함하라는 제안이 최선의 방법입니다.예, 구축하는 동안 시간이 소요되지만 이것이 문제가 될 만큼 자주 예외를 발생시켜서는 안 됩니다.모든 예외를 새로운 기반에서 상속받는 것도 필요한 것보다 많을 수 있습니다.관련 예외를 상속하고(고마워요, 다중 상속) 이를 별도로 포착할 수 있습니다.
당신은 사용할 수 있습니다 스택트레이스64 추적을 작성하는 함수입니다(다른 방법도 있다고 생각합니다).확인해 보세요 이 기사 예를 들어 코드.
GCC 라이브러리를 사용하여 C++에서 이를 수행하는 방법은 다음과 같습니다.
#include <execinfo.h> // Backtrace
#include <cxxabi.h> // Demangling
vector<Str> backtrace(size_t numskip) {
vector<Str> result;
std::vector<void*> bt(100);
bt.resize(backtrace(&(*bt.begin()), bt.size()));
char **btsyms = backtrace_symbols(&(*bt.begin()), bt.size());
if (btsyms) {
for (size_t i = numskip; i < bt.size(); i++) {
Aiss in(btsyms[i]);
int idx = 0; Astr nt, addr, mangled;
in >> idx >> nt >> addr >> mangled;
if (mangled == "start") break;
int status = 0;
char *demangled = abi::__cxa_demangle(mangled.c_str(), 0, 0, &status);
Str frame = (status==0) ? Str(demangled, demangled+strlen(demangled)) :
Str(mangled.begin(), mangled.end());
result.push_back(frame);
free(demangled);
}
free(btsyms);
}
return result;
}
예외 생성자는 간단히 이 함수를 호출하고 스택 추적을 저장할 수 있습니다.매개변수가 필요합니다. numskip
왜냐하면 나는 스택 추적에서 예외 생성자를 잘라내는 것을 좋아하기 때문입니다.
이를 수행하는 표준 방법은 없습니다.
또한 호출 스택은 일반적으로 예외가 발생하는 시점에 기록되어야 합니다. 던져진;일단 그랬어 잡았다 스택이 펼쳐졌기 때문에 던진 시점에 무슨 일이 일어났는지 더 이상 알 수 없습니다.
Win32/Win64의 VC++에서는 ~할 것 같다 컴파일러 내장 _ReturnAddress()의 값을 기록하고 예외 클래스 생성자가 __declspec(noinline)인지 확인하여 충분히 사용 가능한 결과를 얻습니다.디버그 기호 라이브러리와 함께 SymGetLineFromAddr64를 사용하여 반환 주소에 해당하는 함수 이름(및 .pdb에 포함되어 있는 경우 줄 번호)을 얻을 수 있다고 생각합니다.
네이티브 코드에서는 다음을 설치하여 콜스택을 탐색해 볼 수 있습니다. 벡터 예외 처리기.VC++는 SEH 예외 위에 C++ 예외를 구현하며 벡터 예외 처리기는 프레임 기반 처리기보다 먼저 제공됩니다.그러나 벡터 예외 처리로 인해 발생하는 문제는 진단하기 어려울 수 있으므로 주의해야 합니다.
또한 Mike Stall은 몇 가지 경고를 했습니다. 관리 코드가 있는 앱에서 사용하는 방법에 대해 알아보세요.마지막으로 읽어보세요 Matt Pietrek의 기사 이 작업을 시도하기 전에 SEH 및 벡터 예외 처리를 이해했는지 확인하세요.(중요한 문제를 추적하는 데 도움이 되는 추가한 코드에 대한 중요한 문제를 추적하는 것만큼 기분 나쁜 일은 없습니다.)
MSDev에서는 예외가 발생했을 때 중단점을 설정할 수 있다고 생각합니다.
또는 예외 객체의 생성자에 중단점을 두십시오.
IDE에서 디버깅하는 경우 디버그->예외로 이동하여 C++ 예외에 대해 Thrown을 클릭합니다.
다른 언어?음, Java에서는 e.printStackTrace()를 호출합니다.그보다 훨씬 간단하지는 않습니다.
누군가 관심이 있을 경우를 대비하여 동료가 이메일을 통해 나에게 이 질문에 답했습니다.
Artem은 다음과 같이 썼습니다.
모든 전역 변수 등을 포함한 전체 프로그램 상태를 볼 수 있는 더 나은 크래시 덤프를 수행할 수 있는 MiniDumpWriteDump()에 대한 플래그가 있습니다.호출 스택의 경우 최적화로 인해 더 좋아질 수 있을지 의문입니다...(어쩌면 일부) 최적화를 끄지 않는 한.
그리고 인라인 기능을 비활성화하고 전체 프로그램을 최적화하는 것도 꽤 도움이 될 것 같아요.
실제로 덤프 유형은 다양합니다. 충분히 작은 유형을 선택할 수도 있지만 여전히 더 많은 정보가 있습니다.http://msdn.microsoft.com/en-us/library/ms680519(VS.85).aspx
이러한 유형은 호출 스택에 도움이 되지 않으며 볼 수 있는 변수의 양에만 영향을 미칩니다.
이러한 덤프 유형 중 일부는 우리가 사용하는 dbghelp.dll 버전 5.1에서 지원되지 않는 것으로 나타났습니다.최신 6.9 버전으로 업데이트할 수 있습니다. 방금 MS 디버깅 도구에 대한 EULA를 확인했습니다. 최신 dbghelp.dll은 여전히 재배포해도 괜찮습니다.
나는 내 자신의 예외를 사용합니다.매우 간단하게 처리할 수 있습니다. 텍스트도 포함되어 있습니다.나는 다음 형식을 사용합니다.
throw Exception( "comms::serial::serial( )", "Something failed!" );
또한 두 번째 예외 형식이 있습니다.
throw Exception( "comms::serial::serial( )", ::GetLastError( ) );
그런 다음 FormatMessage를 사용하여 DWORD 값에서 실제 메시지로 변환됩니다.어디서/어떤 형식을 사용하면 무슨 일이 일어났는지, 어떤 기능에서 발생했는지 확인할 수 있습니다.