/* * Cppcheck - A tool for static C/C++ code analysis * Copyright (C) 2007-2023 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "cppcheckexecutorseh.h" #ifdef USE_WINDOWS_SEH #include "cppcheckexecutor.h" #include "utils.h" #include #include #include namespace { const ULONG maxnamelength = 512; struct IMAGEHLP_SYMBOL64_EXT : public IMAGEHLP_SYMBOL64 { TCHAR nameExt[maxnamelength]; // actually no need to worry about character encoding here }; typedef BOOL (WINAPI *fpStackWalk64)(DWORD, HANDLE, HANDLE, LPSTACKFRAME64, PVOID, PREAD_PROCESS_MEMORY_ROUTINE64, PFUNCTION_TABLE_ACCESS_ROUTINE64, PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64); fpStackWalk64 pStackWalk64; typedef DWORD64 (WINAPI *fpSymGetModuleBase64)(HANDLE, DWORD64); fpSymGetModuleBase64 pSymGetModuleBase64; typedef BOOL (WINAPI *fpSymGetSymFromAddr64)(HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64); fpSymGetSymFromAddr64 pSymGetSymFromAddr64; typedef BOOL (WINAPI *fpSymGetLineFromAddr64)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64); fpSymGetLineFromAddr64 pSymGetLineFromAddr64; typedef DWORD (WINAPI *fpUnDecorateSymbolName)(const TCHAR*, PTSTR, DWORD, DWORD); fpUnDecorateSymbolName pUnDecorateSymbolName; typedef PVOID (WINAPI *fpSymFunctionTableAccess64)(HANDLE, DWORD64); fpSymFunctionTableAccess64 pSymFunctionTableAccess64; typedef BOOL (WINAPI *fpSymInitialize)(HANDLE, PCSTR, BOOL); fpSymInitialize pSymInitialize; HMODULE hLibDbgHelp; // avoid explicit dependency on Dbghelp.dll bool loadDbgHelp() { hLibDbgHelp = ::LoadLibraryW(L"Dbghelp.dll"); if (!hLibDbgHelp) return false; pStackWalk64 = (fpStackWalk64) ::GetProcAddress(hLibDbgHelp, "StackWalk64"); pSymGetModuleBase64 = (fpSymGetModuleBase64) ::GetProcAddress(hLibDbgHelp, "SymGetModuleBase64"); pSymGetSymFromAddr64 = (fpSymGetSymFromAddr64) ::GetProcAddress(hLibDbgHelp, "SymGetSymFromAddr64"); pSymGetLineFromAddr64 = (fpSymGetLineFromAddr64)::GetProcAddress(hLibDbgHelp, "SymGetLineFromAddr64"); pSymFunctionTableAccess64 = (fpSymFunctionTableAccess64)::GetProcAddress(hLibDbgHelp, "SymFunctionTableAccess64"); pSymInitialize = (fpSymInitialize) ::GetProcAddress(hLibDbgHelp, "SymInitialize"); pUnDecorateSymbolName = (fpUnDecorateSymbolName)::GetProcAddress(hLibDbgHelp, "UnDecorateSymbolName"); return true; } void printCallstack(FILE* outputFile, PEXCEPTION_POINTERS ex) { if (!loadDbgHelp()) return; const HANDLE hProcess = GetCurrentProcess(); const HANDLE hThread = GetCurrentThread(); pSymInitialize( hProcess, nullptr, TRUE ); CONTEXT context = *(ex->ContextRecord); STACKFRAME64 stack= {0}; #ifdef _M_IX86 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; #else stack.AddrPC.Offset = context.Rip; stack.AddrPC.Mode = AddrModeFlat; stack.AddrStack.Offset = context.Rsp; stack.AddrStack.Mode = AddrModeFlat; stack.AddrFrame.Offset = context.Rsp; stack.AddrFrame.Mode = AddrModeFlat; #endif IMAGEHLP_SYMBOL64_EXT symbol; symbol.SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); symbol.MaxNameLength = maxnamelength; DWORD64 displacement = 0; int beyond_main=-1; // emergency exit, see below for (ULONG frame = 0; ; frame++) { BOOL result = pStackWalk64 ( #ifdef _M_IX86 IMAGE_FILE_MACHINE_I386, #else IMAGE_FILE_MACHINE_AMD64, #endif hProcess, hThread, &stack, &context, nullptr, pSymFunctionTableAccess64, pSymGetModuleBase64, nullptr ); if (!result) // official end... break; pSymGetSymFromAddr64(hProcess, (ULONG64)stack.AddrPC.Offset, &displacement, &symbol); TCHAR undname[maxnamelength]= {0}; pUnDecorateSymbolName((const TCHAR*)symbol.Name, (PTSTR)undname, (DWORD)getArrayLength(undname), UNDNAME_COMPLETE); if (beyond_main>=0) ++beyond_main; if (_tcscmp(undname, _T("main"))==0) beyond_main=0; fprintf(outputFile, "%lu. 0x%08I64X in ", frame, (ULONG64)stack.AddrPC.Offset); fputs((const char *)undname, outputFile); fputc('\n', outputFile); if (0==stack.AddrReturn.Offset || beyond_main>2) // StackWalk64() sometimes doesn't reach any end... break; } FreeLibrary(hLibDbgHelp); hLibDbgHelp=nullptr; } void writeMemoryErrorDetails(FILE* outputFile, PEXCEPTION_POINTERS ex, const char* description) { fputs(description, outputFile); fprintf(outputFile, " (instruction: 0x%p) ", ex->ExceptionRecord->ExceptionAddress); // Using %p for ULONG_PTR later on, so it must have size identical to size of pointer // This is not the universally portable solution but good enough for Win32/64 C_ASSERT(sizeof(void*) == sizeof(ex->ExceptionRecord->ExceptionInformation[1])); switch (ex->ExceptionRecord->ExceptionInformation[0]) { case 0: fprintf(outputFile, "reading from 0x%p", reinterpret_cast(ex->ExceptionRecord->ExceptionInformation[1])); break; case 1: fprintf(outputFile, "writing to 0x%p", reinterpret_cast(ex->ExceptionRecord->ExceptionInformation[1])); break; case 8: fprintf(outputFile, "data execution prevention at 0x%p", reinterpret_cast(ex->ExceptionRecord->ExceptionInformation[1])); break; default: break; } } /* * Any evaluation of the exception needs to be done here! */ int filterException(FILE *outputFile, int code, PEXCEPTION_POINTERS ex) { fputs("Internal error: ", outputFile); switch (ex->ExceptionRecord->ExceptionCode) { case EXCEPTION_ACCESS_VIOLATION: writeMemoryErrorDetails(outputFile, ex, "Access violation"); break; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: fputs("Out of array bounds", outputFile); break; case EXCEPTION_BREAKPOINT: fputs("Breakpoint", outputFile); break; case EXCEPTION_DATATYPE_MISALIGNMENT: fputs("Misaligned data", outputFile); break; case EXCEPTION_FLT_DENORMAL_OPERAND: fputs("Denormalized floating-point value", outputFile); break; case EXCEPTION_FLT_DIVIDE_BY_ZERO: fputs("Floating-point divide-by-zero", outputFile); break; case EXCEPTION_FLT_INEXACT_RESULT: fputs("Inexact floating-point value", outputFile); break; case EXCEPTION_FLT_INVALID_OPERATION: fputs("Invalid floating-point operation", outputFile); break; case EXCEPTION_FLT_OVERFLOW: fputs("Floating-point overflow", outputFile); break; case EXCEPTION_FLT_STACK_CHECK: fputs("Floating-point stack overflow", outputFile); break; case EXCEPTION_FLT_UNDERFLOW: fputs("Floating-point underflow", outputFile); break; case EXCEPTION_GUARD_PAGE: fputs("Page-guard access", outputFile); break; case EXCEPTION_ILLEGAL_INSTRUCTION: fputs("Illegal instruction", outputFile); break; case EXCEPTION_IN_PAGE_ERROR: writeMemoryErrorDetails(outputFile, ex, "Invalid page access"); break; case EXCEPTION_INT_DIVIDE_BY_ZERO: fputs("Integer divide-by-zero", outputFile); break; case EXCEPTION_INT_OVERFLOW: fputs("Integer overflow", outputFile); break; case EXCEPTION_INVALID_DISPOSITION: fputs("Invalid exception dispatcher", outputFile); break; case EXCEPTION_INVALID_HANDLE: fputs("Invalid handle", outputFile); break; case EXCEPTION_NONCONTINUABLE_EXCEPTION: fputs("Non-continuable exception", outputFile); break; case EXCEPTION_PRIV_INSTRUCTION: fputs("Invalid instruction", outputFile); break; case EXCEPTION_SINGLE_STEP: fputs("Single instruction step", outputFile); break; case EXCEPTION_STACK_OVERFLOW: fputs("Stack overflow", outputFile); break; default: fprintf(outputFile, "Unknown exception (%d)\n", code); break; } fputc('\n', outputFile); printCallstack(outputFile, ex); fflush(outputFile); return EXCEPTION_EXECUTE_HANDLER; } } /** * Signal/SEH handling * Has to be clean for using with SEH on windows, i.e. no construction of C++ object instances is allowed! * TODO Check for multi-threading issues! * */ int check_wrapper_seh(CppCheckExecutor& executor, int (CppCheckExecutor::*f)(CppCheck&) const, CppCheck& cppcheck) { FILE *outputFile = CppCheckExecutor::getExceptionOutput(); __try { return (&executor->*f)(cppcheck); } __except (filterException(outputFile, GetExceptionCode(), GetExceptionInformation())) { fputs("Please report this to the cppcheck developers!\n", outputFile); return -1; } } #endif