feat: Add command line arguments to process tooltips, exclude kthreads on Linux

Fixes #2558
This commit is contained in:
WerWolv 2025-12-11 17:09:26 +01:00
parent f234103320
commit 1db79f6117
2 changed files with 116 additions and 6 deletions

View File

@ -86,6 +86,7 @@ namespace hex::plugin::builtin {
struct Process {
u32 id;
std::string name;
std::string commandLine;
ImGuiExt::Texture icon;
};

View File

@ -5,6 +5,7 @@
#if defined(OS_WINDOWS)
#include <windows.h>
#include <winternl.h>
#include <psapi.h>
#include <shellapi.h>
#elif defined(OS_MACOS)
@ -30,9 +31,77 @@
#include <wolv/io/fs.hpp>
#include <wolv/io/file.hpp>
#include <wolv/utils/guards.hpp>
#include <wolv/literals.hpp>
namespace hex::plugin::builtin {
using namespace wolv::literals;
#if defined(OS_WINDOWS)
using NtQueryInformationProcessFunc = NTSTATUS (NTAPI*)(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength
);
std::string getProcessCommandLine(HANDLE processHandle) {
// Get NtQueryInformationProcess function
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
if (!ntdll) return "";
auto funcNtQueryInformationProcess = (NtQueryInformationProcessFunc)
(void*)GetProcAddress(ntdll, "NtQueryInformationProcess");
if (!funcNtQueryInformationProcess) return "";
// Query for PROCESS_BASIC_INFORMATION to get PEB address
PROCESS_BASIC_INFORMATION pbi = {};
ULONG len = 0;
NTSTATUS status = funcNtQueryInformationProcess(
processHandle,
ProcessBasicInformation,
&pbi,
sizeof(pbi),
&len
);
if (status != 0 || !pbi.PebBaseAddress) return "";
// Read PEB to get ProcessParameters address
PEB peb = {};
SIZE_T bytesRead = 0;
if (!ReadProcessMemory(processHandle, pbi.PebBaseAddress, &peb, sizeof(peb), &bytesRead))
return "";
// Read RTL_USER_PROCESS_PARAMETERS
RTL_USER_PROCESS_PARAMETERS params = {};
if (!ReadProcessMemory(processHandle, peb.ProcessParameters, &params, sizeof(params), &bytesRead))
return "";
// Read the command line string
std::vector<wchar_t> cmdLine(params.CommandLine.Length / sizeof(wchar_t) + 1, 0);
if (!ReadProcessMemory(processHandle, params.CommandLine.Buffer, cmdLine.data(),
params.CommandLine.Length, &bytesRead))
return "";
// Convert wide string to narrow string
int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, cmdLine.data(), -1, nullptr, 0, nullptr, nullptr);
if (sizeNeeded <= 0) return "";
std::string result(sizeNeeded, 0);
WideCharToMultiByte(CP_UTF8, 0, cmdLine.data(), -1, &result[0], sizeNeeded, nullptr, nullptr);
// Remove trailing null terminator
if (!result.empty() && result.back() == '\0')
result.pop_back();
return result;
}
#endif
bool ProcessMemoryProvider::open() {
if (m_selectedProcess == nullptr)
return false;
@ -202,7 +271,7 @@ namespace hex::plugin::builtin {
}
}
m_processes.push_back({ u32(processId), processName, std::move(texture) });
m_processes.push_back({ u32(processId), processName, getProcessCommandLine(processHandle), std::move(texture) });
}
#elif defined(OS_MACOS)
std::array<pid_t, 2048> pids;
@ -213,7 +282,7 @@ namespace hex::plugin::builtin {
const auto result = proc_pidinfo(pids[i], PROC_PIDTBSDINFO, 0,
&proc, PROC_PIDTBSDINFO_SIZE);
if (result == PROC_PIDTBSDINFO_SIZE) {
m_processes.emplace_back(pids[i], proc.pbi_name, ImGuiExt::Texture());
m_processes.emplace_back(pids[i], proc.pbi_name, proc->pbi_comm, ImGuiExt::Texture());
}
}
#elif defined(OS_LINUX)
@ -228,13 +297,40 @@ namespace hex::plugin::builtin {
continue; // not a PID
}
wolv::io::File file(path /"cmdline", wolv::io::File::Mode::Read);
// Parse status file
wolv::io::File file(path / "status", wolv::io::File::Mode::Read);
std::map<std::string, std::string> statusInfo;
if (file.isValid()) {
const auto statusContent = file.readString(1_MiB);
for (const auto &line : wolv::util::splitString(statusContent, "\n")) {
const auto delimiterPos = line.find(':');
if (delimiterPos != std::string::npos) {
const auto key = line.substr(0, delimiterPos);
const auto value = line.substr(delimiterPos + 1);
statusInfo[key] = wolv::util::trim(value);
}
}
}
// Skip kernel threads
if (statusInfo.contains("Kthread") && statusInfo["Kthread"] == "1")
continue;
// Parse process name
std::string processName;
if (statusInfo.contains("Name")) {
processName = statusInfo["Name"];
}
wolv::io::File cmdlineFile(path / "cmdline", wolv::io::File::Mode::Read);
if (!file.isValid())
continue;
std::string processName = file.readString(0xF'FFFF);
auto commandLine = cmdlineFile.readString(1_MiB);
if (processName.empty())
processName = commandLine;
m_processes.emplace_back(processId, processName, ImGuiExt::Texture());
m_processes.emplace_back(processId, processName, commandLine, ImGuiExt::Texture());
}
#endif
}
@ -265,7 +361,11 @@ namespace hex::plugin::builtin {
ImGui::TableNextColumn();
auto height = ImGui::GetTextLineHeight();
if (process->icon.isValid()) {
ImGui::Image(process->icon, { height, height });
} else {
ImGui::Dummy({ height, height });
}
ImGui::TableNextColumn();
ImGuiExt::TextFormatted("{}", process->id);
@ -274,6 +374,15 @@ namespace hex::plugin::builtin {
if (ImGui::Selectable(process->name.c_str(), m_selectedProcess != nullptr && process->id == m_selectedProcess->id, ImGuiSelectableFlags_SpanAllColumns))
m_selectedProcess = process;
if (ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayNormal)) {
if (ImGui::BeginTooltip()) {
ImGui::PushTextWrapPos(200_scaled);
ImGui::TextWrapped("%s", process->commandLine.c_str());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
}
ImGui::PopID();
}