Exception&log
-
Upload
nam-hyeonuk -
Category
Software
-
view
104 -
download
1
Transcript of Exception&log
Exception & Log
NHN NEXT남현욱
01
Exception
01 Exception
•StructuredExceptionHandling(SEH)프로그램을 아무리 잘 만들어도 예외 상황(Exception)은 피할 수 없다. 예외는 프로그램 외부
의 불가항력적인 상황때문에 발생하는 경우가 많기 때문이다(존재해야 할 파일이 존재하지 않
거나, 메모리 할당을 받아야하는데 메모리가 부족하거나, 기타 등등). 이런 예외 상황을 처리하
기 위해 윈도우즈에서는 SEH를 이용한다. SEH를 이용하면 실행 코드와 예외처리 코드를 분리
시켜 코드의 가독성, 유지 보수성을 높여준다는 장점을 갖고 있다. 구조나 사용방법은 C++ try
catch 구문과 거의 똑같다.
__try : 실행 코드 블럭
__except ( exception filter ) : 예외 상황 발생시 예외에 따라 실행.
01 Exception
•ExceptionFilterSEH에서 __except는 인자로 exception filter를 받는다. 이 필터는 예외를 누가 처리할지, 예
외를 처리한 다음에 어떻게 할 것인지 등등을 지정하는 역할을 담당한다. exception filter에는
아래 세 가지 종류가 있다. 아래 세 가지 값은 excpt.h 파일에 정의되어 있음.
•EXCEPTION_EXECUTE_HANDLER
•EXCEPTION_CONTINUE_EXECUTION
•EXCEPTION_CONTINUE_SEARCH
01 Exception
•ExceptionFilterEXCEPTION_EXECUTE_HANDLER이 필터가 적용될 경우, __try 블록에서 예외가 발생한 시점에 그 이후 __try 코드 블록의 실행을
모두 생략하고 global unwind를 수행한 후 __except블록의 코드 및 그 이후 코드들을 수행하
게 된다.int main() { int a = 0, b = 0; __try { b = 5 / a; //divide by 0 printf("ignore."); } __except(EXCEPTION_EXECUTE_HANDLER) { printf("execption."); } printf("after exception.");
}
//실행시 execption. after exception. 출력
01 Exception
•ExceptionFilterEXCEPTION_CONTINUE_EXECUTION이 건 위 필터와 정 반대로 동작한다. 이 필터가 지정되어 있을 경우 __try 블록에서 예외가 발생
했던 코드로 다시 돌아가 해당 코드를 수행한다. 즉 이 필터가 지정되어 있을 경우 반복 시행에서
뭔가 예외 상황을 해결할 수 있는 처리를 해주어야한다는 것이다. 그렇지 않을 경우 다른 문제가
발생하거나 원인도 모른 채 프로그램이 죽거나 할 수 있다.
int main(){ int a = 0, b = 0; //divide by 0 __try{ b = 5/a; } __except(ExceptHandle(&a)) {} }
int ExceptHandle(int* divider){ scanf("%d",÷r); return divider ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_EXECUTION; }실행시 0으로 나눈 것에서 에러 발생 -> 0 아닌 수 나올 때까지 반복 실행 -> 0 아닌 수 나오면 __except 안의 코드 실행 후 다음 코드로 진행.
01 Exception
•ExceptionFilterEXCEPTION_CONTINUE_SEARCH이 필터가 지정되어 있을 경우 현재 지정된 __except에서 예외 처리를 수행하지 않고 콜스택을
거꾸로 되찾아가면서 다른 __except 블록에 처리를 넘긴다(거기서도 EXCEPTION_CONTIN-
UE_SEARCH라면 다시 그것보다 더 먼저 호출한 함수쪽으로 쭉 진행). 그러다가 아무런 핸들러
도 찾지 못한다면 종료된다.
void foo(){ int a = 0, b = 0; __try { b = 5 / a; // divide by 0 } __except (EXCEPTION_CONTINUE_SEARCH) { printf("ignore."); }}
int main(){ __try { foo(); } __except (EXCEPTION_EXECUTE_HANDLER) { printf("exception."); }}
실행시 exception. 출력됨.
01 Exception
•UnhandledException앞선 필터들을 통해 예외가 발생했을 때 SEH를 통해 어떤식으로 예외를 처리할 수 있는지
살펴보았다. 그런데 이런 예외 중에서 처리되지 않은 예외가 발생했을 때 특정 핸들러를
호출하고 싶을 수 있다. 예를 들어 아까전에 EXCEPTION_CONTINUE_SEARCH 필터를
설정했을 경우 끝까지 핸들러를 찾지 못하면 그냥 프로그램이 죽어버리는데, 이런 상황에
Unhandled Exception filter를 설정해주면 해당 함수가 호출된다. 처리되지 않은 예외가
발생했을 때 프로그램의 dump를 뜬 다음 프로그램을 종료시키거나 하는 처리를 할 수가 있는
것이다. 이 설정은 windows의 SetUnhandledExceptionFilter 함수를 통해 할 수 있다.
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter( _In_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);
LPTOP_LVEL_EXCEPTION_FILTER는 LPEXCEPTION_POINTERS를 인자로 받고 LONG을
리턴하는 WINAPI 호출 규약의 함수 포인터이다.
( LONG WINAPI filter(LPEXCEPTION_POINTERS * info); )
01 Exception
•Dump프로그램이 죽을 때 덤프 정보를 남기려면 어떻게 해야 할까?덤프 파일을 만들어주는 함수를
만든 다음 이 함수를 SetUnhandledExceptionFilter 함수를 이용해 등록하면 처리되지 않은
예외가 발생했을때 이 함수가 호출되어 자동으로 덤프 파일을 만들어줄 것이다. 덤프를 만들기
위해 MiniDumpWriteDump 함수를 쓸 수 있다.
BOOL WINAPI MiniDumpWriteDump( _In_ HANDLE hProcess, // 덤프 뜰 프로세스 핸들 _In_ DWORD ProcessId, // 덤프 뜰 프로세스 아이디 _In_ HANDLE hFile, // 덤프 기록할 파일 핸들 _In_ MINIDUMP_TYPE DumpType, // 기록할 덤프의 타입(후술) //덤프 뜰 상황이 발생하게 만든 예외 정보(NULL이면 기록되지 않음) _In_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, //덤프 파일에 기록할 사용자 정의 정보(NULL이면 기록되지 않음) _In_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, //확장된 미니 덤프 정보를 받을 콜백 루틴(NULL이면 아무 콜백도 호출 안 됨) _In_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
01 Exception
•Dump사용가능한 MiniDump의 타입에는 굉장히 많은 종류가 있다. 전체 목록 및 각각의
기능이 궁금하다면 MSDN의 MINIDUMP_TYPE 문서를 보면 된다. 대표적인 2가지는
MiniDumpNormal과 MiniDumpWithFullMemory이다. MiniDumpNormal은 로컬
레지스터 값을 포함해서 덤프를 뜨고, MiniDumpWithFullMemory는 전체 메모리 값을
포함하여 덤프를 뜬다.
사용예제
MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; exceptionInfo.ThreadId = GetCurrentThreadId(); exceptionInfo.ExceptionPointers = e; exceptionInfo.ClientPointers = FALSE; HANDLE hProcess = GetCurrentProcess(); DWORD dwProcessID = GetCurrentProcessId(); DWORD dwThreadID = GetCurrentThreadId();
MiniDumpWriteDump(hProcess, dwProcessId, hFile, MiniDumpWithFullMemory, &exceptionInfo, NULL, NULL);
01 Exception
•StackWalk64스레드의 스택 정보를 기록하고 싶다면 StackWalk64 함수를 쓰면 된다. 인자가 굉장히 많고
복잡하다.BOOL WINAPI StackWalk64( _In_ DWORD MachineType, _In_ HANDLE hProcess, _In_ HANDLE hThread, _Inout_ LPSTACKFRAME64 StackFrame, _Inout_ PVOID ContextRecord, _In_opt_ PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, _In_opt_ PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, _In_opt_ PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, _In_opt_ PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress);
01 Exception
•StackWalk64
MachineType생성할 스택 추적 정보의 컴퓨터 아키텍쳐 종류이다. 아래 3가지 값중 하나를 가진다.
•IMAGE_FILE_MACHINE_I386 Intel x86 아키텍쳐
•IMAGE_FILE_MACHINE_IA64 Intel Itanium 아키텍쳐
•IMAGE_FILE_MACHINE_AMD64 x64(AMD64 or EM64T) 아키텍쳐
hProcess,hThread스택을 추적할 프로세스 및 스레드 핸들.
StackFrameSTACKFRAME64 구조체에 대한 포인터. StackWalk64 함수 호출이 성공적으로 이루어지면
이 포인터에 다음 스택 프레임에 대한 정보가 들어간다.
01 Exception
•StackWalk64
ContextRecordCONTEXT 구조체에 대한 포인터. MachineType 인자가 IMAGE_FILE_MACHINE_I386
이 아닐 때만 필요하다(해당 MachineType이 아니라도 사용가능한 context record
값을 넘기는게 권장된다고 함). 이 인자로 넘어간 ContextRecord 값은 수정될 수 있다.
RtlCaptureContext 함수로 얻어올 수 있다고 한다.
ReadMemoryRoutine메모리를 읽어오는 데 필요한 함수를 지정한다. 아무것도 지정하지 않을 경우 기본값
(ReadProcessMemoryPros64)로 지정됨.
FunctionTableAccessRoutine(optional)프로세스의 run time function table에 대한 접근을 제공하는 함수를 지정.
SymFunctionTableAccess64 라는 함수를 보통 쓴다고 한다
01 Exception
•StackWalk64
GetModuleBaseRoutine(optional)주어진 가상함수에 대한 module base를 제공하는 함수. 보통 SymGetModuleBase64
함수를 쓴다고 함.
TranslateAddress(optional)16비트 주소에 대한 변환을 제공하는 함수. 필요없으면 NULL을 넘긴다.
01 Exception
•StackWalk64사용 예제
StackWalk64를 쓰기 위한 기본 변수들
BOOL res;HANDLE hProcess;HANDLE hThread;CONTEXT context = {0, };STACKFRAME64 stack = {0, };ULONG frame;const int MaxNameLen = 255;SYMBOL_INFO* symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO) + MaxNameLen);DWORD64 displacement64 = 0;DWORD displacement = 0;IMAGEHLP_LINE64 imageHelpLine;DWORD dwSymOptions = SymGetOptions();
01 Exception
•StackWalk64사용 예제
Symbol Option 초기화
dwSymOptions |= SYMOPT_LOAD_LINES;dwSymOptions |= SYMOPT_UNDNAME;dwSymOptions |= SYMOPT_EXACT_SYMBOLS;SymSetOptions(dwSymOptions);
memset(symbol, 0, sizeof(SYMBOL_INFO) + MaxNameLen);symbol->SizeOfStruct = sizeof(SYMBOL_INFO);symbol->MaxNameLen = MaxNameLen;
memset(&imageHelpLine, 0, sizeof(imageHelpLine));imageHelpLine.SizeOfStruct = sizeof(imageHelpLine);
SymInitialize(GetCurrentProcess(), ".", 1);
01 Exception
•StackWalk64사용 예제
stack 구조체 초기화 및 context 정보 가져오기
RtlCaptureContext(&context);memset(&stack, 0, sizeof(STACKFRAME64));
hProcess = GetCurrentProcess();hThread = GetCurrentThread();stack.AddrPC.Offset = context.Eip;stack.AddrPC.Mode = AddrModeFlat;stack.AddrStack.Offset = context.Esp;stack.AddrStack.Mode = AddrModeFlat;stack.AddrFrame.Offset = context.Ebp;stack.AddrFrame.Mode = AddrModeFlat;stack.AddrBStore.Mode = AddrModeFlat;stack.AddrReturn.Mode = AddrModeFlat;
01 Exception
•StackWalk64사용 예제
스택 정보 가져오기
for (frame = 0;; frame++){ res = StackWalk64 ( IMAGE_FILE_MACHINE_I386, hProcess, hThread, &stack, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL );
01 Exception
•StackWalk64사용 예제
심볼 정보 가져오기 및 출력
SymFromAddr(hProcess, stack.AddrPC.Offset, &displacement64, symbol); SymGetLineFromAddr64(hProcess, stack.AddrPC.Offset, &displacement, &imageHelpLine); printf ( "Frame %lu:\nSymbol name: %s\nFile Name : %s\n" "Line Number : %d\nPC address: 0x%08LX\n" "Frame address: 0x%08LX\n", frame, symbol->Name, imageHelpLine.FileName, imageHelpLine.LineNumber, (ULONG64)stack.AddrPC.Offset, (ULONG64)stack.AddrStack.Offset, (ULONG64)stack.AddrFrame.Offset );
01 Exception
•StackWalk64사용 예제
마무리
if (!res) { break; } }
SymCleanup(hProcess); free(symbol);}
02
Log
02 Log
•C++LogLibraryC++에는 이미 잘 만들어진 로그 라이브러리들이 많다. 그 중 몇 가지만 살펴보자.
glog구글에서 만든 C++ 로그 라이브러리. 어플리케이션 수준의 로깅을 구현. 디버그 모드에서만 로
그를 출력하는 기능, 특정 상태에서만 로그를 기록하는 기능 등등을 지원한다.
사용법관련링크 : http://jjalidev.blogspot.kr/2013/10/glog.html
log4Cxxlog4j를 본따 C++ 버젼으로 만든 라이브러리. 빠르고 확장성이 좋다고 한다. 평가가 좋은 편이
지만 2008년 이후로 업데이트가 없다.
사용법관련링크 : http://mindgear.tistory.com/184
02 Log
•Boost::logC++ 라이브러리중 가장 유명하고 많이 쓰이는 라이브러리인 Boost 라이브러리의 log 기능을
한 번 써보자. Boost 설치는 링크 참조. 글에 적힌 걸 그대로 따라하기만 하면 생각보다 어렵지
않게 설치할 수 있다. Simplicity, Extensibility, Performance를 가진 라이브러리를 목표로
만들었다고 함. 메뉴얼링크
02 Log
•Boost::log간략한 구조 설명( 앞 슬라이드의 그림 참조)
LoggingSourceslogger는 실제 기록될 메시지를 형식화(format)하기 위한 스트림을 제공해주는 오브젝트다.
라이브러리에서 다양한 logger를 지원하며 직접 logger를 만들어 쓰거나 확장해 쓸 수 있다.
Attributesandattributevalueslog source를 초기화하기 위해서는 log record에 관련된 모든 정보를 logging core에 넘겨주
어야한다. 이 데이터들은 attribute들의 집합으로 구성되어 있으며 각각의 attribute들은 기본
적으로 하나의 함수이다. attribute set에는 global / thread-specific, source-specific의
세 가지 종류가 있다.
Loggingcoreandfilteringattribute value 집합이 모두 구성되면, logging core는 해당 log record가 기록되어야 하는
log인지 판단하고 필요없으면 버린다(filtering).
Sinksandformatting필터링을 통과하면 해당 record를 적절히 형식화해서 해당 record가 기록되어야 할 sink로 보
낸다. front-end / back-end로 구성.
02 Log
•Boost::logTriviallogging제일 간단한 로깅. 그냥 해당 메시지 콘솔 창에 출력하는 기능을 한다(std::cout).
#include <boost/log/trivial.hpp>
int main(){ BOOST_LOG_TRIVIAL(trace) << "A trace severity message"; BOOST_LOG_TRIVIAL(debug) << "A debug severity message"; BOOST_LOG_TRIVIAL(info) << "An informational severity message"; BOOST_LOG_TRIVIAL(warning) << "A warning severity message"; BOOST_LOG_TRIVIAL(error) << "An error severity message"; BOOST_LOG_TRIVIAL(fatal) << "A fatal severity message";
return 0;}
02 Log
•Boost::logSetupSinklog가 출력될 sink를 직접 설정해서 쓰기.
namespace logging = boost::log;namespace src = boost::log::sources;namespace sinks = boost::log::sinks;namespace keywords = boost::log::keywords;
int main(){ using namespace logging::trivial;
logging::add_file_log ( keywords::file_name = "sample_%N.log", keywords::rotation_size = 10 * 1024 * 1024, keywords::format = "[%TimeStamp%] : %Message%" );
logging::core::get()->set_filter ( severity >= info ); logging::add_common_attributes();
src::severity_logger<severity_level> lg;
BOOST_LOG_SEV(lg, trace) << "A trace severity message"; BOOST_LOG_SEV(lg, debug) << "A debug severity message"; BOOST_LOG_SEV(lg, info) << "An informational severity message"; BOOST_LOG_SEV(lg, warning) << "A warning severity message"; BOOST_LOG_SEV(lg, error) << "An error severity message"; BOOST_LOG_SEV(lg, fatal) << "A fatal severity message";
return 0;}
사용법이 상당히 방대해서 예제는 이정도로.. 메뉴얼 문서에 튜토리얼이 굉장히 잘 정리되어있으니 그걸 참고하자!
02 Log
•멀티스레드에서의로그싱글 스레드에서는 아무 문제가 없지만, 역시나 멀티 스레드가 끼면 문제가 복잡해진다. 여러 스
레드에서 동시에 이런 저런 내용들을 로깅하려고 할 때 순서가 꼬일 수 있기 때문이다. boost 같
은 경우에는 sink에서 frond-end와 back-end를 나눈 뒤 front-end에서 스레드간 동기화 등
의 작업을 수행하게 만들고 back-end에서 실제로 로그를 기록하는 작업을 수행하는 식으로 이
런 순서 문제를 해결했다(실제 내부 구조가 어떤 식으로 동작하는지는 잘 모르겠다). 개인적으로
생각하기에는 아래 2가지 방법 정도가 있을 것 같다.
queue이용하기thread-safe한 queue를 만들어서 거기 로그할 내용을 집어넣은 뒤 꺼내서 실제로 기록하는
것이다.
GCE같은구조이용하기코딩 과제에 있는 GCE 클래스처럼 만들면 lock 안 걸고 순서 보장하는 log 기록이 가능할 것 같
다.(GCE도 그런 역할을 하니까) log를 기록하는 애가 아무도 없으면 직접 기록하고, 누군가 log
를 기록 중이라면 걔한테 자기가 해야할 log까지 떠맡기고 그냥 자기 할 일 하는 방식.