1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "Win32Exception.h"
10 
11 #include "Util.h"
12 #include "WIN32Util.h"
13 #include "utils/StringUtils.h"
14 #include "utils/URIUtils.h"
15 
16 #include "platform/win32/CharsetConverter.h"
17 
18 #include <VersionHelpers.h>
19 #include <dbghelp.h>
20 
21 typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType,
22                                         const PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
23                                         const PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
24                                         const PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
25 
26 // StackWalk64()
27 typedef BOOL (__stdcall *tSW)(
28   DWORD MachineType,
29   HANDLE hProcess,
30   HANDLE hThread,
31   LPSTACKFRAME64 StackFrame,
32   PVOID ContextRecord,
33   PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
34   PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
35   PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
36   PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress );
37 
38 // SymInitialize()
39 typedef BOOL (__stdcall *tSI)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess );
40 // SymCleanup()
41 typedef BOOL (__stdcall *tSC)( IN HANDLE hProcess );
42 // SymGetSymFromAddr64()
43 typedef BOOL (__stdcall *tSGSFA)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD64 pdwDisplacement, OUT PIMAGEHLP_SYMBOL64 Symbol );
44 // UnDecorateSymbolName()
45 typedef DWORD (__stdcall WINAPI *tUDSN)( PCSTR DecoratedName, PSTR UnDecoratedName, DWORD UndecoratedLength, DWORD Flags );
46 // SymGetLineFromAddr64()
47 typedef BOOL (__stdcall *tSGLFA)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line );
48 // SymGetModuleBase64()
49 typedef DWORD64 (__stdcall *tSGMB)( IN HANDLE hProcess, IN DWORD64 dwAddr );
50 // SymFunctionTableAccess64()
51 typedef PVOID (__stdcall *tSFTA)( HANDLE hProcess, DWORD64 AddrBase );
52 // SymGetOptions()
53 typedef DWORD (__stdcall *tSGO)( VOID );
54 // SymSetOptions()
55 typedef DWORD (__stdcall *tSSO)( IN DWORD SymOptions );
56 
57 // GetCurrentPackageFullName
58 typedef LONG (__stdcall *GCPFN)(UINT32*, PWSTR);
59 
60 std::string win32_exception::mVersion;
61 
write_minidump(EXCEPTION_POINTERS * pEp)62 bool win32_exception::write_minidump(EXCEPTION_POINTERS* pEp)
63 {
64   // Create the dump file where the xbmc.exe resides
65   bool returncode = false;
66   std::string dumpFileName;
67   std::wstring dumpFileNameW;
68   KODI::TIME::SystemTime stLocalTime;
69   KODI::TIME::GetLocalTime(&stLocalTime);
70 
71   dumpFileName = StringUtils::Format(
72       "kodi_crashlog-%s-%04d%02d%02d-%02d%02d%02d.dmp", mVersion.c_str(), stLocalTime.year,
73       stLocalTime.month, stLocalTime.day, stLocalTime.hour, stLocalTime.minute, stLocalTime.second);
74 
75   dumpFileName = CWIN32Util::SmbToUnc(URIUtils::AddFileToFolder(CWIN32Util::GetProfilePath(), CUtil::MakeLegalFileName(dumpFileName)));
76 
77   dumpFileNameW = KODI::PLATFORM::WINDOWS::ToW(dumpFileName);
78   HANDLE hDumpFile = CreateFileW(dumpFileNameW.c_str(), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
79 
80   HMODULE hDbgHelpDll = nullptr;
81 
82   if (hDumpFile == INVALID_HANDLE_VALUE)
83   {
84     goto cleanup;
85   }
86 
87   // Load the DBGHELP DLL
88   hDbgHelpDll = ::LoadLibrary(L"DBGHELP.DLL");
89   if (!hDbgHelpDll)
90   {
91     goto cleanup;
92   }
93 
94   MINIDUMPWRITEDUMP pDump = (MINIDUMPWRITEDUMP)::GetProcAddress(hDbgHelpDll, "MiniDumpWriteDump");
95   if (!pDump)
96   {
97     goto cleanup;
98   }
99 
100   // Initialize minidump structure
101   MINIDUMP_EXCEPTION_INFORMATION mdei;
102   mdei.ThreadId = GetCurrentThreadId();
103   mdei.ExceptionPointers = pEp;
104   mdei.ClientPointers = FALSE;
105 
106   // Call the minidump api with normal dumping
107   // We can get more detail information by using other minidump types but the dump file will be
108   // extremely large.
109   BOOL bMiniDumpSuccessful = pDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &mdei, 0, NULL);
110   if( !bMiniDumpSuccessful )
111   {
112     goto cleanup;
113   }
114 
115   returncode = true;
116 
117 cleanup:
118 
119   if (hDumpFile != INVALID_HANDLE_VALUE)
120     CloseHandle(hDumpFile);
121 
122   if (hDbgHelpDll)
123     FreeLibrary(hDbgHelpDll);
124 
125   return returncode;
126 }
127 
128 /* \brief Writes a simple stack trace to the XBMC user directory.
129    It needs a valid .pdb file to show the method, filename and
130    line number where the exception took place.
131 */
write_stacktrace(EXCEPTION_POINTERS * pEp)132 bool win32_exception::write_stacktrace(EXCEPTION_POINTERS* pEp)
133 {
134   #define STACKWALK_MAX_NAMELEN 1024
135 
136   std::string dumpFileName, strOutput;
137   std::wstring dumpFileNameW;
138   CHAR cTemp[STACKWALK_MAX_NAMELEN];
139   DWORD dwBytes;
140   KODI::TIME::SystemTime stLocalTime;
141   KODI::TIME::GetLocalTime(&stLocalTime);
142   bool returncode = false;
143   STACKFRAME64 frame = { 0 };
144   HANDLE hCurProc = GetCurrentProcess();
145   IMAGEHLP_SYMBOL64* pSym = NULL;
146   HANDLE hDumpFile = INVALID_HANDLE_VALUE;
147   tSC pSC = NULL;
148 
149   HMODULE hDbgHelpDll = ::LoadLibrary(L"DBGHELP.DLL");
150   if (!hDbgHelpDll)
151   {
152     goto cleanup;
153   }
154 
155   tSI pSI       = (tSI) GetProcAddress(hDbgHelpDll, "SymInitialize" );
156   tSGO pSGO     = (tSGO) GetProcAddress(hDbgHelpDll, "SymGetOptions" );
157   tSSO pSSO     = (tSSO) GetProcAddress(hDbgHelpDll, "SymSetOptions" );
158   pSC           = (tSC) GetProcAddress(hDbgHelpDll, "SymCleanup" );
159   tSW pSW       = (tSW) GetProcAddress(hDbgHelpDll, "StackWalk64" );
160   tSGSFA pSGSFA = (tSGSFA) GetProcAddress(hDbgHelpDll, "SymGetSymFromAddr64" );
161   tUDSN pUDSN   = (tUDSN) GetProcAddress(hDbgHelpDll, "UnDecorateSymbolName" );
162   tSGLFA pSGLFA = (tSGLFA) GetProcAddress(hDbgHelpDll, "SymGetLineFromAddr64" );
163   tSFTA pSFTA   = (tSFTA) GetProcAddress(hDbgHelpDll, "SymFunctionTableAccess64" );
164   tSGMB pSGMB   = (tSGMB) GetProcAddress(hDbgHelpDll, "SymGetModuleBase64" );
165 
166   if(pSI == NULL || pSGO == NULL || pSSO == NULL || pSC == NULL || pSW == NULL || pSGSFA == NULL || pUDSN == NULL || pSGLFA == NULL ||
167      pSFTA == NULL || pSGMB == NULL)
168     goto cleanup;
169 
170   dumpFileName = StringUtils::Format(
171       "kodi_stacktrace-%s-%04d%02d%02d-%02d%02d%02d.txt", mVersion.c_str(), stLocalTime.year,
172       stLocalTime.month, stLocalTime.day, stLocalTime.hour, stLocalTime.minute, stLocalTime.second);
173 
174   dumpFileName = CWIN32Util::SmbToUnc(URIUtils::AddFileToFolder(CWIN32Util::GetProfilePath(), CUtil::MakeLegalFileName(dumpFileName)));
175 
176   dumpFileNameW = KODI::PLATFORM::WINDOWS::ToW(dumpFileName);
177   hDumpFile = CreateFileW(dumpFileNameW.c_str(), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
178 
179   if (hDumpFile == INVALID_HANDLE_VALUE)
180   {
181     goto cleanup;
182   }
183 
184   frame.AddrPC.Mode = AddrModeFlat; // Address mode for this pointer: flat 32 bit addressing
185   frame.AddrStack.Mode = AddrModeFlat; // Address mode for this pointer: flat 32 bit addressing
186   frame.AddrFrame.Mode = AddrModeFlat; // Address mode for this pointer: flat 32 bit addressing
187 
188 #if defined(_X86_)
189   frame.AddrPC.Offset = pEp->ContextRecord->Eip; // Current location in program
190   frame.AddrStack.Offset = pEp->ContextRecord->Esp; // Stack pointers current value
191   frame.AddrFrame.Offset = pEp->ContextRecord->Ebp; // Value of register used to access local function variables.
192 #else
193   frame.AddrPC.Offset = pEp->ContextRecord->Rip; // Current location in program
194   frame.AddrStack.Offset = pEp->ContextRecord->Rsp; // Stack pointers current value
195   frame.AddrFrame.Offset = pEp->ContextRecord->Rbp; // Value of register used to access local function variables.
196 #endif
197 
198   if(pSI(hCurProc, NULL, TRUE) == FALSE)
199     goto cleanup;
200 
201   DWORD symOptions = pSGO();
202   symOptions |= SYMOPT_LOAD_LINES;
203   symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;
204   symOptions &= ~SYMOPT_UNDNAME;
205   symOptions &= ~SYMOPT_DEFERRED_LOADS;
206   symOptions = pSSO(symOptions);
207 
208   pSym = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN);
209   if (!pSym)
210     goto cleanup;
211   memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN);
212   pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
213   pSym->MaxNameLength = STACKWALK_MAX_NAMELEN;
214 
215   IMAGEHLP_LINE64 Line;
216   memset(&Line, 0, sizeof(Line));
217   Line.SizeOfStruct = sizeof(Line);
218 
219   IMAGEHLP_MODULE64 Module;
220   memset(&Module, 0, sizeof(Module));
221   Module.SizeOfStruct = sizeof(Module);
222   int seq=0;
223 
224   strOutput = StringUtils::Format("Thread %d (process %d)\r\n", GetCurrentThreadId(), GetCurrentProcessId());
225   WriteFile(hDumpFile, strOutput.c_str(), strOutput.size(), &dwBytes, NULL);
226 
227   while(pSW(IMAGE_FILE_MACHINE_I386, hCurProc, GetCurrentThread(), &frame, pEp->ContextRecord, NULL, pSFTA, pSGMB, NULL))
228   {
229     if(frame.AddrPC.Offset != 0)
230     {
231       DWORD64 symoffset=0;
232       DWORD   lineoffset=0;
233       strOutput = StringUtils::Format("#%2d", seq++);
234 
235       if(pSGSFA(hCurProc, frame.AddrPC.Offset, &symoffset, pSym))
236       {
237         if(pUDSN(pSym->Name, cTemp, STACKWALK_MAX_NAMELEN, UNDNAME_COMPLETE)>0)
238           strOutput.append(StringUtils::Format(" %s", cTemp));
239       }
240       if(pSGLFA(hCurProc, frame.AddrPC.Offset, &lineoffset, &Line))
241         strOutput.append(StringUtils::Format(" at %s:%d", Line.FileName, Line.LineNumber));
242 
243       strOutput.append("\r\n");
244       WriteFile(hDumpFile, strOutput.c_str(), strOutput.size(), &dwBytes, NULL);
245     }
246   }
247   returncode = true;
248 
249 cleanup:
250   if (pSym)
251     free( pSym );
252 
253   if (hDumpFile != INVALID_HANDLE_VALUE)
254     CloseHandle(hDumpFile);
255 
256   if(pSC)
257     pSC(hCurProc);
258 
259   if (hDbgHelpDll)
260     FreeLibrary(hDbgHelpDll);
261 
262   return returncode;
263 }
264 
ShouldHook()265 bool win32_exception::ShouldHook()
266 {
267   if (!IsWindows8OrGreater())
268     return true;
269 
270   bool result = true;
271 
272   auto module = ::LoadLibrary(L"kernel32.dll");
273   if (module)
274   {
275     auto func = reinterpret_cast<GCPFN>(::GetProcAddress(module, "GetCurrentPackageFullName"));
276     if (func)
277     {
278       UINT32 length = 0;
279       auto r = func(&length, nullptr);
280       result = r == APPMODEL_ERROR_NO_PACKAGE;
281     }
282 
283     ::FreeLibrary(module);
284   }
285 
286   return result;
287 }
288