/** * \file cf_debug.c * \author myusgun * * \brief 디버그 유틸 구현 */ #include "cf_debug.h" #include "cf_local.h" #include "cf_error.h" #include "cf_thread.h" #include #include #include #include #if defined(_WIN32) || defined(_WIN64) # include #else # include #endif #define IS_READABLE_CHAR(__c) (' ' <= __c && __c <= '~') #define ASSERT_CTX(__ctx) \ if (__ctx == NULL) \ return CF_ERROR_DEBUG_INVALID_CTX /** 디버그 컨텍스트 (Opaque) */ typedef void * CF_Debug_Ctx; /** 콜스택 인터페이스 */ typedef struct cf_debug_callStack { char file[NAME_LENGTH + 1]; /* *< 파일 이름 */ char function[NAME_LENGTH + 1]; /* *< 함수 이름 */ int line; /* *< 라인 넘버 */ } CF_Debug_CallStack; /** 콜스택 컨텍스트 */ typedef struct __cf_debug_callstack__ { char file[NAME_LENGTH + 1]; char func[NAME_LENGTH + 1]; int line; int frameIndex; struct __cf_debug_callstack__ * caller; } CF_DEBUG_CALLSTACK; /** 디버그 컨텍스트 (CF_Debug_Ctx의 구현) */ typedef struct __cf_debug_ctx__ { int fd; CF_Mutex_Ctx mutex; CF_DEBUG_CALLSTACK callstack; } CF_DEBUG_CTX; CF_Debug_Ctx gDebugSingleCtx = NULL; static int CF_Debug_Local_Print (FILE * fp, const char * file, const char * func, const int line, const char * fmt, va_list valist) { fprintf (fp, "[DEBUG][%s:%d][%s] ", file, line, func); vfprintf (fp, fmt, valist); return CF_OK; } static int CF_Debug_Local_PrintBin (FILE * fp, const char * file, const char * func, const int line, const unsigned char * bin, const size_t len) { size_t i, j; for (i = 0 ; i < len ; i += 16) { fprintf (fp, "[DEBUG][%s:%d][%s] ", file, line, func); fprintf (fp, "%06x : ", (unsigned int)i); for (j = 0 ; j < 16 ; j++) { if (i+j < len) fprintf (fp, "%02x ", bin[i+j]); else fprintf (fp, " "); } fprintf (fp, " "); for (j = 0 ; j < 16 ; j++) { if (i+j < len) fprintf (fp, "%c", IS_READABLE_CHAR (bin[i+j]) ? bin[i+j] : '.'); } fprintf (fp, "\n"); } return CF_OK; } /** * 디버그 메시지를 지정된 파일 포인터로 출력 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param fp 파일 포인터. 표준출력(stdout) 및 표준오류(stderr) 사용 가능 * \param file 파일 경로 * \param func 함수 이름 * \param line 라인 넘버 * \param fmt 포맷 스트링 * \param ... 가변 인자 * * \see CF_DEBUG_PRINT */ int CF_Debug_Print (FILE * fp, const char * file, const char * func, const int line, const char * fmt, ...) { va_list valist; va_start (valist, fmt); CF_Debug_Local_Print (fp, file, func, line, fmt, valist); va_end (valist); return CF_OK; } /** * 바이너리 데이터를 디버그 메시지와 함께 지정된 파일 포인터로 출력 * * \return CF_OK 반환 * * \param fp 파일 포인터. 표준출력(stdout) 및 표준오류(stderr) 사용 가능 * \param file 파일 경로 * \param func 함수 이름 * \param line 라인 넘버 * \param bin 라인 넘버 * \param len 바이너리 길이 * \param fmt 포맷 스트링 * \param ... 가변 인자 * * \see CF_DEBUG_PRINT_BIN */ int CF_Debug_PrintBin (FILE * fp, const char * file, const char * func, const int line, const unsigned char * bin, const size_t len, const char * fmt, ...) { va_list valist; va_start (valist, fmt); CF_Debug_Local_Print (fp, file, func, line, fmt, valist); va_end (valist); CF_Debug_Local_PrintBin (fp, file, func, line, bin, len); return CF_OK; } /** * 컨텍스트에 콜스택 푸시 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param ctx 디버그 컨텍스트 * \param file 파일 경로 * \param func 함수 이름 * \param line 라인 넘버 */ static int CF_Debug_CallStackPush (CF_Debug_Ctx ctx, const char * file, const char * func, const int line) { CF_DEBUG_CTX * context = (CF_DEBUG_CTX *) ctx; CF_DEBUG_CALLSTACK * push = NULL; ASSERT_CTX (ctx); push = (CF_DEBUG_CALLSTACK *) calloc (sizeof (CF_DEBUG_CALLSTACK), 1); if (push == NULL) return CF_ERROR_DEBUG_PUSH_CALLSTACK; /* push to callstack */ snprintf (push->file, NAME_LENGTH, "%s", file); snprintf (push->func, NAME_LENGTH, "%s", func); push->line = line; push->caller = context->callstack.caller; push->frameIndex = (push->caller) ? push->caller->frameIndex + 1 : 0; context->callstack.caller = push; return CF_OK; } /** * 컨텍스트에서 콜스택에서 TOP을 제거하지 않고 가져옴 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param ctx 디버그 컨텍스트 * \param callstack 콜스택 정보를 가져올 콜스택 데이터 구조체 포인터 * * \see CF_Debug_CallStack */ static int CF_Debug_CallStackPeek (CF_Debug_Ctx ctx, CF_Debug_CallStack * callstack) { CF_DEBUG_CTX * context = (CF_DEBUG_CTX *) ctx; CF_DEBUG_CALLSTACK * pop = NULL; pop = context->callstack.caller; if (pop == NULL) return CF_ERROR_DEBUG_PEEK_CALLSTACK; if (callstack != NULL) { snprintf (callstack->file , NAME_LENGTH, "%s", pop->file); snprintf (callstack->function, NAME_LENGTH, "%s", pop->func); callstack->line = pop->line; } return CF_OK; } /** * 컨텍스트에서 콜스택 팝 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param ctx 디버그 컨텍스트 * \param callstack 콜스택 정보를 가져올 콜스택 데이터 구조체 포인터 * * \see CF_Debug_CallStack */ static int CF_Debug_CallStackPop (CF_Debug_Ctx ctx, CF_Debug_CallStack * callstack) { CF_DEBUG_CTX * context = (CF_DEBUG_CTX *) ctx; CF_DEBUG_CALLSTACK * pop = NULL; ASSERT_CTX (ctx); pop = context->callstack.caller; if (pop == NULL) return CF_ERROR_DEBUG_POP_CALLSTACK; if (CF_Debug_CallStackPeek (ctx, callstack) < 0) return CF_ERROR_DEBUG_PEEK_CALLSTACK; context->callstack.caller = pop->caller; free (pop); return CF_OK; } /** * 디버그 컨텍스트를 해제 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param ctx 디버그 컨텍스트 * * \see CF_DEBUG_DESTROY_CTX */ static int CF_Debug_DestroyCtx (CF_Debug_Ctx ctx) { CF_DEBUG_CTX * context = (CF_DEBUG_CTX *) ctx; ASSERT_CTX (ctx); while (context->callstack.caller) CF_Debug_CallStackPop (ctx, NULL); if (context->mutex) CF_Mutex_DestoryCtx (&context->mutex); free (context); return CF_OK; } /** * 디버그 컨텍스트를 생성 * * \return 성공 시, CF_Debug_Ctx 형태의 컨텍스트; 실패 시, NULL * \see CF_DEBUG_CREATE_CTX */ static int CF_Debug_CreateCtx (CF_Debug_Ctx * ctx) { int result = 0; CF_DEBUG_CTX * context = NULL; TRY { context = (CF_DEBUG_CTX *) calloc (sizeof (CF_DEBUG_CTX), 1); if (context == NULL) { result = CF_ERROR_DEBUG_CREATE_CTX; TRY_BREAK; } result = CF_Mutex_CreateCtx (&context->mutex); if (result < 0) { TRY_BREAK; } *ctx = (CF_Debug_Ctx) context; } CATCH_IF (result < 0) { CF_Debug_DestroyCtx (context); } return result; } /** * 콜스택 매니저 초기화 (글로벌 컨텍스트) * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \see CF_Debug_Finalize */ int CF_Debug_Initialize (void) { int result = 0; if (gDebugSingleCtx == NULL) result = CF_Debug_CreateCtx (&gDebugSingleCtx); return result; } /** * 콜스택 매니저 해제 (글로벌 컨텍스트) * * \return 성공 시, CF_OK; 실패 시, 오류 코드 */ int CF_Debug_Finalize (void) { CF_Debug_Ctx ctx = gDebugSingleCtx; gDebugSingleCtx = NULL; return CF_Debug_DestroyCtx (ctx); } /** * 함수 진입을 명시 (글로벌 컨텍스트) * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param file 파일 경로 * \param func 함수 이름 * \param line 라인 넘버 * * \see CF_Debug_LeaveFunction */ int CF_Debug_EnterFunction (const char * file, const char * func, const int line) { CF_DEBUG_CTX * ctx = (CF_DEBUG_CTX *)gDebugSingleCtx; ASSERT_CTX (ctx); CF_Mutex_Lock (&ctx->mutex); CF_Debug_CallStackPush (gDebugSingleCtx, file, func, line); CF_Mutex_Unlock (&ctx->mutex); return CF_OK; } /** * 함수 종료를 명시 (글로벌 컨텍스트) * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \see CF_Debug_EnterFunction */ int CF_Debug_LeaveFunction (void) { CF_DEBUG_CTX * ctx = (CF_DEBUG_CTX *)gDebugSingleCtx; ASSERT_CTX (ctx); CF_Mutex_Lock (&ctx->mutex); CF_Debug_CallStackPop (gDebugSingleCtx, NULL); CF_Mutex_Unlock (&ctx->mutex); return CF_OK; } /** * 현재 콜스택을 출력 (글로벌 컨텍스트) * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param fp 출력 할 파일 포인터 */ int CF_Debug_PrintCallStack (FILE * fp) { int iter = 0; CF_DEBUG_CTX * ctx = gDebugSingleCtx; CF_DEBUG_CALLSTACK * callstack = NULL; ASSERT_CTX (ctx); for ( callstack = ctx->callstack.caller ; callstack ; callstack = callstack->caller) { fprintf (fp, "#%-4d %s <%s:%d>\n", iter++, callstack->func, callstack->file, callstack->line); } return CF_OK; }