From a06bdb456f89b7ae33c4f755cfea19623ee530e8 Mon Sep 17 00:00:00 2001
From: CHEN FENG <chen3feng@gmail.com>
Date: Tue, 15 Aug 2023 19:07:44 +0800
Subject: [PATCH 1/3] Fix build error on VS 2022

---
 sleepy.vcxproj                                   |  4 ++++
 src/copyfiles.bat                                |  2 +-
 src/crashback/badapp/badapp.vcxproj              |  4 ++++
 src/crashback/client/crashback.vcxproj           |  4 ++++
 src/crashback/crashreport/crashreport.vcxproj    |  4 ++++
 src/profiler/processinfo.cpp                     |  2 +-
 src/profiler/profiler.cpp                        |  6 +++---
 src/utils/except.h                               | 15 +++++++++++----
 thirdparty/ms/dbghelp.h                          |  4 ++--
 thirdparty/wine                                  |  2 +-
 thirdparty/wxWidgetsSetup/wxWidgetsSetup.vcxproj |  3 +++
 11 files changed, 38 insertions(+), 12 deletions(-)

diff --git a/sleepy.vcxproj b/sleepy.vcxproj
index 86466d5d..813582ab 100644
--- a/sleepy.vcxproj
+++ b/sleepy.vcxproj
@@ -28,18 +28,22 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
diff --git a/src/copyfiles.bat b/src/copyfiles.bat
index 9c46ef42..e7f10207 100644
--- a/src/copyfiles.bat
+++ b/src/copyfiles.bat
@@ -18,7 +18,7 @@ if errorlevel 1 ((@echo Failed to copy dbghelp_*) & (exit /b 1))
 
 if not exist "%DBGHELPERS%\dbghelpw.dll" copy /y "thirdparty\wine\dlls\dbghelp\vs\bin\%PLATFORM%\%CONFIGURATION%\dbghelpw.dll" "%DEST%"
 if errorlevel 1 ((@echo Failed to copy dbghelpw.dll) & (exit /b 1))
-if %PLATFORM%==x64 if not exist "%DBGHELPERS%\dbghelpw_wow64.dll" copy /y "thirdparty\wine\dlls\dbghelp\vs\bin\%PLATFORM%\%CONFIGURATION% - Wow64\dbghelpw.dll" "%DEST%\dbghelpw_wow64.dll"
+if %PLATFORM%==x64 if not exist "%DBGHELPERS%\dbghelpw_wow64.dll" copy /y "thirdparty\wine\dlls\dbghelp\vs\bin\%PLATFORM%\%CONFIGURATION%\dbghelpw.dll" "%DEST%\dbghelpw_wow64.dll"
 if errorlevel 1 ((@echo Failed to copy dbghelpw_wow64.dll) & (exit /b 1))
 
 if %PLATFORM%==Win32 set PLATFORM_BITS=32
diff --git a/src/crashback/badapp/badapp.vcxproj b/src/crashback/badapp/badapp.vcxproj
index af8035a2..f9c732d4 100644
--- a/src/crashback/badapp/badapp.vcxproj
+++ b/src/crashback/badapp/badapp.vcxproj
@@ -28,19 +28,23 @@
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
diff --git a/src/crashback/client/crashback.vcxproj b/src/crashback/client/crashback.vcxproj
index 3f85de15..cf8e85f0 100644
--- a/src/crashback/client/crashback.vcxproj
+++ b/src/crashback/client/crashback.vcxproj
@@ -29,19 +29,23 @@
     <ConfigurationType>StaticLibrary</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <ConfigurationType>StaticLibrary</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
     <ConfigurationType>StaticLibrary</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
     <WholeProgramOptimization>false</WholeProgramOptimization>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>StaticLibrary</ConfigurationType>
     <CharacterSet>Unicode</CharacterSet>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
diff --git a/src/crashback/crashreport/crashreport.vcxproj b/src/crashback/crashreport/crashreport.vcxproj
index 0eb72443..20d696a5 100644
--- a/src/crashback/crashreport/crashreport.vcxproj
+++ b/src/crashback/crashreport/crashreport.vcxproj
@@ -28,19 +28,23 @@
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>NotSet</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>NotSet</CharacterSet>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>NotSet</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>NotSet</CharacterSet>
+    <PlatformToolset>v143</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
diff --git a/src/profiler/processinfo.cpp b/src/profiler/processinfo.cpp
index 21df8abc..7864d8ba 100644
--- a/src/profiler/processinfo.cpp
+++ b/src/profiler/processinfo.cpp
@@ -133,4 +133,4 @@ ProcessInfo ProcessInfo::FindProcessById(DWORD process_id)
 			return process;
 	}
 	throw SleepyException("Could not found process with specified id: " + std::to_string((unsigned long long) process_id));
-}
\ No newline at end of file
+}
diff --git a/src/profiler/profiler.cpp b/src/profiler/profiler.cpp
index 13fe2883..e07d9f4e 100644
--- a/src/profiler/profiler.cpp
+++ b/src/profiler/profiler.cpp
@@ -112,7 +112,7 @@ void applyHacks(HANDLE process_handle, CONTEXT32 &context)
 
 	// First, skip over any stub functions (a useless push/mov/pop header, followed by a jump).
 	// Move instead to the jump target.
-	if (ReadProcessMemory(process_handle, (LPCVOID)context.Eip, tmp, 16, &numRead) && numRead >= 16)
+	if (ReadProcessMemory(process_handle, (LPCVOID)(DWORD64)context.Eip, tmp, 16, &numRead) && numRead >= 16)
 	{
 		int n = 0;
 
@@ -136,13 +136,13 @@ void applyHacks(HANDLE process_handle, CONTEXT32 &context)
 	}
 
 	// Skip over any jmp [__imp__blah] thunks.
-	if (ReadProcessMemory(process_handle, (LPCVOID)context.Eip, tmp, 16, &numRead) && numRead >= 16)
+	if (ReadProcessMemory(process_handle, (LPCVOID)(DWORD64)context.Eip, tmp, 16, &numRead) && numRead >= 16)
 	{
 		// Look for "jmp [foo]", and move the IP forward to '[foo]'.
 		if (tmp[0] == 0xff && tmp[1] == 0x25)
 		{
 			DWORD ptr = (tmp[5] << 24) | (tmp[4] << 16) | (tmp[3] << 8) | (tmp[2] << 0);
-			if (ReadProcessMemory(process_handle, (LPCVOID)ptr, tmp, 4, &numRead) && numRead >= 4)
+			if (ReadProcessMemory(process_handle, (LPCVOID)(DWORD64)ptr, tmp, 4, &numRead) && numRead >= 4)
 			{
 				context.Eip = (tmp[3] << 24) | (tmp[2] << 16) | (tmp[1] << 8) | (tmp[0] << 0);
 			}
diff --git a/src/utils/except.h b/src/utils/except.h
index da5376ce..735aeb2d 100644
--- a/src/utils/except.h
+++ b/src/utils/except.h
@@ -23,6 +23,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 #pragma once
 
+#include <codecvt>
+#include <locale>
 #include <stdexcept>
 #include <string>
 
@@ -30,15 +32,20 @@ class SleepyException : public std::runtime_error
 {
 	std::wstring _what;
 
-	std::string helper(const wchar_t *what)
+	std::string helper(const std::wstring& what)
 	{
 		// Need a temporary to build std::string.
 		// Can't use _what in initialization-list because
 		// it will always be initialized after the superclass.
 		// Can't use _what here because it is still not initialized
 		// and contains junk.
-		std::wstring ws(what);
-		return std::string(ws.begin(), ws.end());
+
+		// setup converter
+		using convert_type = std::codecvt_utf8<wchar_t>;
+		std::wstring_convert<convert_type, wchar_t> converter;
+
+		// use converter (.to_bytes: wstr->str, .from_bytes: str->wstr)
+		return converter.to_bytes(what);;
 	}
 
 public:
@@ -46,7 +53,7 @@ class SleepyException : public std::runtime_error
 		: std::runtime_error(what), _what(std::wstring(what.begin(), what.end())) {}
 
 	SleepyException(const std::wstring &what)
-		: std::runtime_error(std::string(what.begin(), what.end())), _what(what) {}
+		: std::runtime_error(helper(what)), _what(what) {}
 
 	SleepyException(const wchar_t *what)
 		: std::runtime_error(helper(what)), _what(what) {}
diff --git a/thirdparty/ms/dbghelp.h b/thirdparty/ms/dbghelp.h
index f6f67f0c..afe918dc 100644
--- a/thirdparty/ms/dbghelp.h
+++ b/thirdparty/ms/dbghelp.h
@@ -1540,7 +1540,7 @@ SymGetHomeDirectoryW(
     __in size_t size
     );
 
-typedef enum {
+enum {
     hdBase = 0, // root directory for dbghelp
     hdSym,      // where symbols are stored
     hdSrc,      // where source is stored
@@ -3056,7 +3056,7 @@ SymSrvStoreFileW(
 
 // used by SymGetSymbolFile's "Type" parameter
 
-typedef enum {
+enum {
     sfImage = 0,
     sfDbg,
     sfPdb,
diff --git a/thirdparty/wine b/thirdparty/wine
index 12126ff2..d47751be 160000
--- a/thirdparty/wine
+++ b/thirdparty/wine
@@ -1 +1 @@
-Subproject commit 12126ff2bc695847c67bf36f133472f806501365
+Subproject commit d47751be64569a3d577f772ae7b8f5ebcec4aeab
diff --git a/thirdparty/wxWidgetsSetup/wxWidgetsSetup.vcxproj b/thirdparty/wxWidgetsSetup/wxWidgetsSetup.vcxproj
index 5cd535c7..d6ebe35e 100644
--- a/thirdparty/wxWidgetsSetup/wxWidgetsSetup.vcxproj
+++ b/thirdparty/wxWidgetsSetup/wxWidgetsSetup.vcxproj
@@ -11,6 +11,9 @@
     <RootNamespace>wxWidgetsSetup</RootNamespace>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <PlatformToolset>v143</PlatformToolset>
+  </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ItemGroup>
     <CustomBuild Include="setup.h">

From 17ef6c748db2db07ab1db689f1b294c012a4a5b4 Mon Sep 17 00:00:00 2001
From: CHEN FENG <chen3feng@gmail.com>
Date: Tue, 15 Aug 2023 19:16:20 +0800
Subject: [PATCH 2/3] Add 'Command Line' column in process selection ListView

---
 src/profiler/processinfo.cpp      | 42 +++++++++++++++++++++++++++++++
 src/profiler/processinfo.h        |  2 ++
 src/wxProfilerGUI/processlist.cpp | 26 +++++++++++++++++++
 src/wxProfilerGUI/processlist.h   |  2 ++
 4 files changed, 72 insertions(+)

diff --git a/src/profiler/processinfo.cpp b/src/profiler/processinfo.cpp
index 7864d8ba..fbce55bf 100644
--- a/src/profiler/processinfo.cpp
+++ b/src/profiler/processinfo.cpp
@@ -27,8 +27,49 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include "../utils/osutils.h"
 #include "../utils/except.h"
 #include <windows.h>
+#include <winternl.h>
 #include <tlhelp32.h>
 
+#ifndef STATUS_SUCCESS
+#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+#endif
+
+static std::wstring GetProcessCommandLine(HANDLE processHandle)
+{
+	std::wstring commandLine;
+	do {
+		// NtQueryInformationProcess is an internal function, So, we need to get the address of the function from ntdll
+		auto pNtQueryInformationProcess = reinterpret_cast<decltype(&::NtQueryInformationProcess)>(
+			GetProcAddress(GetModuleHandle(L"ntdll"), "NtQueryInformationProcess"));
+		if (!pNtQueryInformationProcess) break;
+
+		PROCESS_BASIC_INFORMATION pbi;
+		if (pNtQueryInformationProcess(processHandle, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr) != STATUS_SUCCESS)
+		{
+			break;
+		}
+
+		// Reading the PEB structure of the process
+		PEB peb;
+		if (!ReadProcessMemory(processHandle, pbi.PebBaseAddress, &peb, sizeof(peb), nullptr))
+		{
+			break;
+		}
+
+		// Reading the process parameters structure
+		RTL_USER_PROCESS_PARAMETERS upp;
+		if (!ReadProcessMemory(processHandle, peb.ProcessParameters, &upp, sizeof(upp), nullptr))
+		{
+			break;
+		}
+
+		commandLine.resize(upp.CommandLine.Length);
+		ReadProcessMemory(processHandle, upp.CommandLine.Buffer, &commandLine[0], upp.CommandLine.Length, nullptr);
+	} while (0);
+
+	return commandLine;
+}
+
 ProcessInfo::ProcessInfo(DWORD id_, const std::wstring& name_, HANDLE process_handle_)
 :	id(id_),
 	name(name_),
@@ -40,6 +81,7 @@ ProcessInfo::ProcessInfo(DWORD id_, const std::wstring& name_, HANDLE process_ha
 #ifdef _WIN64
 	is64Bits = Is64BitProcess(process_handle);
 #endif
+	commandLine = GetProcessCommandLine(process_handle);
 }
 
 ProcessInfo::~ProcessInfo()
diff --git a/src/profiler/processinfo.h b/src/profiler/processinfo.h
index 398f7624..45f1bc66 100644
--- a/src/profiler/processinfo.h
+++ b/src/profiler/processinfo.h
@@ -58,6 +58,7 @@ class ProcessInfo
 #ifdef _WIN64
 	bool getIs64Bits() const { return is64Bits; }
 #endif
+	const std::wstring& getCommandLine() const { return commandLine; }
   FILETIME prevKernelTime, prevUserTime;
   int cpuUsage;
   __int64 totalCpuTimeMs;
@@ -69,6 +70,7 @@ class ProcessInfo
 #ifdef _WIN64
 	bool is64Bits;
 #endif
+	std::wstring commandLine;
 };
 
 
diff --git a/src/wxProfilerGUI/processlist.cpp b/src/wxProfilerGUI/processlist.cpp
index 95d68c4c..94d20172 100644
--- a/src/wxProfilerGUI/processlist.cpp
+++ b/src/wxProfilerGUI/processlist.cpp
@@ -63,6 +63,8 @@ ProcessList::ProcessList(wxWindow *parent, const wxPoint& pos,
 	InsertColumn(COL_TOTALCPU, itemCol);
 	itemCol.m_text = _T("PID");
 	InsertColumn(COL_PID, itemCol);
+	itemCol.m_text = _T("Command Line");
+	InsertColumn(COL_COMMANDLINE, itemCol);
 
 	SetColumnWidth(COL_NAME, FromDIP(270));
 #ifdef _WIN64
@@ -71,6 +73,7 @@ ProcessList::ProcessList(wxWindow *parent, const wxPoint& pos,
 	SetColumnWidth(COL_CPUUSAGE, FromDIP(50));
 	SetColumnWidth(COL_TOTALCPU, FromDIP(70));
 	SetColumnWidth(COL_PID, FromDIP(50));
+	SetColumnWidth(COL_COMMANDLINE, FromDIP(500));
 
 	sort_column = COL_CPUUSAGE;
 	sort_dir = SORT_DOWN;
@@ -232,6 +235,18 @@ struct TypeDescPred { bool operator () (const ProcessInfo &a, const ProcessInfo
 } };
 #endif
 
+struct CommandLineAscPred {
+	bool operator () (const ProcessInfo& a, const ProcessInfo& b) {
+		return wcsicmp(a.getCommandLine().c_str(), b.getCommandLine().c_str()) < 0;
+	}
+};
+
+struct CommandLineDescPred {
+	bool operator () (const ProcessInfo& a, const ProcessInfo& b) {
+		return wcsicmp(a.getCommandLine().c_str(), b.getCommandLine().c_str()) > 0;
+	}
+};
+
 void ProcessList::sortByName()
 {
 	if (sort_dir == SORT_UP)
@@ -273,6 +288,15 @@ void ProcessList::sortByType()
 		std::stable_sort(processes.begin(), processes.end(), TypeDescPred());
 }
 #endif
+
+void ProcessList::sortByCommandLine()
+{
+	if (sort_dir == SORT_UP)
+		std::stable_sort(processes.begin(), processes.end(), CommandLineAscPred());
+	else
+		std::stable_sort(processes.begin(), processes.end(), CommandLineDescPred());
+}
+
 void ProcessList::OnSort(wxListEvent& event)
 {
 	SetSortImage(sort_column, SORT_NONE);
@@ -301,6 +325,7 @@ void ProcessList::updateSorting()
 #ifdef _WIN64
 		case COL_TYPE: sortByType(); break;
 #endif
+		case COL_COMMANDLINE: sortByCommandLine(); break;
 	}
 	fillList();
 }
@@ -334,6 +359,7 @@ void ProcessList::fillList()
 			SetItem(i,COL_TYPE,"32-bit");
 		}
 #endif
+		this->SetItem(i, COL_COMMANDLINE, processes[i].getCommandLine());
 	}
 	Thaw();
 }
diff --git a/src/wxProfilerGUI/processlist.h b/src/wxProfilerGUI/processlist.h
index 7a7a3647..901f8bc2 100644
--- a/src/wxProfilerGUI/processlist.h
+++ b/src/wxProfilerGUI/processlist.h
@@ -67,6 +67,7 @@ class ProcessList : public wxSortedListCtrl
 #ifdef _WIN64
 	void sortByType();
 #endif
+	void sortByCommandLine();
 	void reloadSymbols(bool download);
 
 	const ProcessInfo* getSelectedProcess();
@@ -82,6 +83,7 @@ class ProcessList : public wxSortedListCtrl
 		COL_CPUUSAGE,
 		COL_TOTALCPU,
 		COL_PID,
+		COL_COMMANDLINE,
 		NUM_COLUMNS
 	};
 

From f5caa5039b80da831ff2b251c9b358416788a0b7 Mon Sep 17 00:00:00 2001
From: CHEN FENG <chen3feng@gmail.com>
Date: Mon, 16 Oct 2023 09:26:13 +0800
Subject: [PATCH 3/3] Show window title in the process selection list

---
 src/profiler/processinfo.cpp      | 35 +++++++++++++++++++++++++++++++
 src/profiler/processinfo.h        |  2 ++
 src/profiler/symbolinfo.cpp       |  2 +-
 src/wxProfilerGUI/processlist.cpp | 25 ++++++++++++++++++++++
 src/wxProfilerGUI/processlist.h   |  2 ++
 5 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/src/profiler/processinfo.cpp b/src/profiler/processinfo.cpp
index fbce55bf..cff61448 100644
--- a/src/profiler/processinfo.cpp
+++ b/src/profiler/processinfo.cpp
@@ -70,6 +70,40 @@ static std::wstring GetProcessCommandLine(HANDLE processHandle)
 	return commandLine;
 }
 
+static bool IsMainWindow(HWND hWnd)
+{
+	return GetWindow(hWnd, GW_OWNER) == nullptr && IsWindowVisible(hWnd);
+}
+
+struct EnumWindowInfo
+{
+	DWORD pid;
+	HWND hWndFound;
+};
+
+static BOOL CALLBACK EnumWindowsCallback(HWND hWnd, LPARAM lParam)
+{
+	EnumWindowInfo* info = reinterpret_cast<EnumWindowInfo*>(lParam);
+	DWORD pid = 0;
+	GetWindowThreadProcessId(hWnd, &pid);
+
+	if (pid != info->pid) return TRUE;
+	if (!IsMainWindow(hWnd)) return TRUE;
+	info->hWndFound = hWnd;
+	return FALSE;
+}
+
+std::wstring GetProcessMainWindowTitle(DWORD pid)
+{
+	EnumWindowInfo info = {pid, nullptr};
+	EnumWindows(EnumWindowsCallback, reinterpret_cast<LPARAM>(&info));
+	if (info.hWndFound == nullptr) return {};
+	std::wstring title(256, '\0');
+	int len = GetWindowTextW(info.hWndFound, &title[0], static_cast<int>(title.size()));
+	title.resize(len);
+	return title;
+}
+
 ProcessInfo::ProcessInfo(DWORD id_, const std::wstring& name_, HANDLE process_handle_)
 :	id(id_),
 	name(name_),
@@ -81,6 +115,7 @@ ProcessInfo::ProcessInfo(DWORD id_, const std::wstring& name_, HANDLE process_ha
 #ifdef _WIN64
 	is64Bits = Is64BitProcess(process_handle);
 #endif
+	title = GetProcessMainWindowTitle(id);
 	commandLine = GetProcessCommandLine(process_handle);
 }
 
diff --git a/src/profiler/processinfo.h b/src/profiler/processinfo.h
index 45f1bc66..30c87cc6 100644
--- a/src/profiler/processinfo.h
+++ b/src/profiler/processinfo.h
@@ -58,6 +58,7 @@ class ProcessInfo
 #ifdef _WIN64
 	bool getIs64Bits() const { return is64Bits; }
 #endif
+	const std::wstring& getTitle() const { return title; }
 	const std::wstring& getCommandLine() const { return commandLine; }
   FILETIME prevKernelTime, prevUserTime;
   int cpuUsage;
@@ -70,6 +71,7 @@ class ProcessInfo
 #ifdef _WIN64
 	bool is64Bits;
 #endif
+	std::wstring title;
 	std::wstring commandLine;
 };
 
diff --git a/src/profiler/symbolinfo.cpp b/src/profiler/symbolinfo.cpp
index ff34effb..8c4dae29 100644
--- a/src/profiler/symbolinfo.cpp
+++ b/src/profiler/symbolinfo.cpp
@@ -157,7 +157,7 @@ void SymbolInfo::loadSymbolsUsing(DbgHelp* dbgHelp, const std::wstring& sympath)
 		{
 			// This is a secondary dbgHelp, so just complement debug
 			// information for modules that have none.
-			
+
 			for (size_t m=0;m<modules.size();m++)
 			{
 				Module &mod = modules[m];
diff --git a/src/wxProfilerGUI/processlist.cpp b/src/wxProfilerGUI/processlist.cpp
index 94d20172..0dd35e52 100644
--- a/src/wxProfilerGUI/processlist.cpp
+++ b/src/wxProfilerGUI/processlist.cpp
@@ -63,6 +63,8 @@ ProcessList::ProcessList(wxWindow *parent, const wxPoint& pos,
 	InsertColumn(COL_TOTALCPU, itemCol);
 	itemCol.m_text = _T("PID");
 	InsertColumn(COL_PID, itemCol);
+	itemCol.m_text = _T("Title");
+	InsertColumn(COL_TITLE, itemCol);
 	itemCol.m_text = _T("Command Line");
 	InsertColumn(COL_COMMANDLINE, itemCol);
 
@@ -73,6 +75,7 @@ ProcessList::ProcessList(wxWindow *parent, const wxPoint& pos,
 	SetColumnWidth(COL_CPUUSAGE, FromDIP(50));
 	SetColumnWidth(COL_TOTALCPU, FromDIP(70));
 	SetColumnWidth(COL_PID, FromDIP(50));
+	SetColumnWidth(COL_TITLE, FromDIP(200));
 	SetColumnWidth(COL_COMMANDLINE, FromDIP(500));
 
 	sort_column = COL_CPUUSAGE;
@@ -235,6 +238,18 @@ struct TypeDescPred { bool operator () (const ProcessInfo &a, const ProcessInfo
 } };
 #endif
 
+struct TitleAscPred {
+	bool operator () (const ProcessInfo& a, const ProcessInfo& b) {
+		return wcsicmp(a.getTitle().c_str(), b.getTitle().c_str()) < 0;
+	}
+};
+
+struct TitleDescPred {
+	bool operator () (const ProcessInfo& a, const ProcessInfo& b) {
+		return wcsicmp(a.getTitle().c_str(), b.getTitle().c_str()) > 0;
+	}
+};
+
 struct CommandLineAscPred {
 	bool operator () (const ProcessInfo& a, const ProcessInfo& b) {
 		return wcsicmp(a.getCommandLine().c_str(), b.getCommandLine().c_str()) < 0;
@@ -289,6 +304,14 @@ void ProcessList::sortByType()
 }
 #endif
 
+void ProcessList::sortByTitle()
+{
+	if (sort_dir == SORT_UP)
+		std::stable_sort(processes.begin(), processes.end(), TitleAscPred());
+	else
+		std::stable_sort(processes.begin(), processes.end(), TitleDescPred());
+}
+
 void ProcessList::sortByCommandLine()
 {
 	if (sort_dir == SORT_UP)
@@ -325,6 +348,7 @@ void ProcessList::updateSorting()
 #ifdef _WIN64
 		case COL_TYPE: sortByType(); break;
 #endif
+		case COL_TITLE: sortByTitle(); break;
 		case COL_COMMANDLINE: sortByCommandLine(); break;
 	}
 	fillList();
@@ -359,6 +383,7 @@ void ProcessList::fillList()
 			SetItem(i,COL_TYPE,"32-bit");
 		}
 #endif
+		this->SetItem(i, COL_TITLE, processes[i].getTitle());
 		this->SetItem(i, COL_COMMANDLINE, processes[i].getCommandLine());
 	}
 	Thaw();
diff --git a/src/wxProfilerGUI/processlist.h b/src/wxProfilerGUI/processlist.h
index 901f8bc2..a450040b 100644
--- a/src/wxProfilerGUI/processlist.h
+++ b/src/wxProfilerGUI/processlist.h
@@ -67,6 +67,7 @@ class ProcessList : public wxSortedListCtrl
 #ifdef _WIN64
 	void sortByType();
 #endif
+	void sortByTitle();
 	void sortByCommandLine();
 	void reloadSymbols(bool download);
 
@@ -83,6 +84,7 @@ class ProcessList : public wxSortedListCtrl
 		COL_CPUUSAGE,
 		COL_TOTALCPU,
 		COL_PID,
+		COL_TITLE,
 		COL_COMMANDLINE,
 		NUM_COLUMNS
 	};