rx: add filesystem utility, cross platform runDebugger implementation

This commit is contained in:
DH 2025-12-25 22:06:07 +03:00
parent 05759b33b2
commit c5be529f96
4 changed files with 288 additions and 29 deletions

View file

@ -6,6 +6,7 @@ add_library(${PROJECT_NAME} OBJECT
src/debug.cpp
src/die.cpp
src/FileLock.cpp
src/filesystem.cpp
src/hexdump.cpp
src/Mappable.cpp
src/mem.cpp

View file

@ -0,0 +1,12 @@
#pragma once
#include <string>
namespace rx {
// Get the full path to the current executable
std::string getExecutablePath();
// Find an executable by name in system PATH
// Returns the full path if found, empty string otherwise
std::string findExecutable(const std::string &name);
} // namespace rx

View file

@ -1,8 +1,8 @@
#include "debug.hpp"
#include "Process.hpp"
#include "filesystem.hpp"
#include "print.hpp"
#include <fstream>
#include <list>
#include <thread>
#include <vector>
@ -15,7 +15,6 @@
#else
#ifdef __linux__
#include <linux/limits.h>
#include <sys/ptrace.h>
#endif
#include <unistd.h>
@ -86,37 +85,36 @@ void rx::waitForDebugger() {
}
void rx::runDebugger() {
#ifdef __linux__
int pid = ::getpid();
char path[PATH_MAX];
::readlink("/proc/self/exe", path, sizeof(path));
if (fork()) {
std::this_thread::sleep_for(std::chrono::seconds(1));
waitForDebugger();
auto pid = getCurrentProcessId();
auto path = rx::getExecutablePath();
auto pidString = std::to_string(pid);
// Find gdb in PATH
auto gdbPath = rx::findExecutable("gdb");
if (gdbPath.empty()) {
return;
}
auto pidString = std::to_string(pid);
const char *gdbPath = "/usr/bin/gdb";
std::vector<std::string> args = {
path,
pidString,
"-iex",
"set pagination off",
"-ex",
"handle SIGSYS nostop noprint",
"-ex",
"handle SIGUSR1 nostop noprint"
// TODO: collect elfs
// "-ex", "add-symbol-file <path to elf> 0x400000"
};
std::list<std::string> storage;
std::vector<const char *> argv;
argv.push_back(gdbPath);
argv.push_back(path);
argv.push_back(pidString.c_str());
argv.push_back("-iex");
argv.push_back("set pagination off");
argv.push_back("-ex");
argv.push_back("handle SIGSYS nostop noprint");
argv.push_back("-ex");
argv.push_back("handle SIGUSR1 nostop noprint");
// TODO: collect elfs
// argv.push_back("-ex");
// argv.push_back("add-symbol-file <path to elf> 0x400000");
argv.push_back(nullptr);
execv(gdbPath, (char **)argv.data());
#endif
ProcessId debuggerPid = rx::spawn(gdbPath, args);
if (debuggerPid != static_cast<ProcessId>(-1)) {
std::this_thread::sleep_for(std::chrono::seconds(1));
waitForDebugger();
}
}
void rx::breakpoint() {

248
rx/src/filesystem.cpp Normal file
View file

@ -0,0 +1,248 @@
#include <rx/filesystem.hpp>
#include <rx/types.hpp>
#include <cstdlib>
#include <cstring>
#include <vector>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <sys/stat.h>
#endif
#include <climits>
#if defined(__APPLE__)
#include <mach-o/dyld.h>
#elif defined(__linux__)
#include <unistd.h>
#elif defined(__FreeBSD__) || defined(__DragonFly__)
#include <sys/sysctl.h>
#include <sys/types.h>
#elif defined(__NetBSD__) | defined(__OpenBSD__)
#include <sys/sysctl.h>
#elif defined(__sun)
#include <cstdlib>
#endif
std::string rx::getExecutablePath() {
#ifdef _WIN32
wchar_t path[MAX_PATH];
DWORD len = ::GetModuleFileNameW(nullptr, path, MAX_PATH);
if (len == 0 || len == MAX_PATH) {
return {};
}
// Convert wide string to narrow string
int size =
::WideCharToMultiByte(CP_UTF8, 0, path, -1, nullptr, 0, nullptr, nullptr);
if (size <= 0) {
return {};
}
std::string result(size - 1, '\0');
::WideCharToMultiByte(CP_UTF8, 0, path, -1, result.data(), size, nullptr,
nullptr);
return result;
#elif defined(__APPLE__)
char path[PATH_MAX];
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
return path;
}
// Buffer too small, allocate larger
std::string result(size, '\0');
if (_NSGetExecutablePath(result.data(), &size) == 0) {
result.resize(size - 1); // Remove null terminator
return result;
}
return {};
#elif defined(__linux__) || defined(__ANDROID__)
char path[PATH_MAX];
ssize_t len = ::readlink("/proc/self/exe", path, sizeof(path) - 1);
if (len == -1) {
return {};
}
path[len] = '\0';
return path;
#elif defined(__FreeBSD__) || defined(__DragonFly__)
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
char path[PATH_MAX];
size_t len = sizeof(path);
if (sysctl(mib, 4, path, &len, nullptr, 0) == 0) {
return path;
}
return {};
#elif defined(__NetBSD__)
int mib[4] = {CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME};
char path[PATH_MAX];
size_t len = sizeof(path);
if (sysctl(mib, 4, path, &len, nullptr, 0) == 0) {
return path;
}
return {};
#elif defined(__OpenBSD__)
// OpenBSD doesn't provide a direct way to get executable path
// Return empty string as fallback
return {};
#elif defined(__sun)
// Solaris
const char *path = getexecname();
if (path) {
if (path[0] == '/') {
return std::string(path);
}
// Relative path, need to prepend current directory
char cwd[PATH_MAX];
if (getcwd(cwd, sizeof(cwd))) {
return std::string(cwd) + "/" + path;
}
}
return {};
#else
#error "rx::filesystem::getExecutablePath not implemented for this platform"
#endif
}
std::string rx::findExecutable(const std::string &name) {
if (name.empty()) {
return {};
}
#ifdef _WIN32
// On Windows, search PATH with automatic .exe extension handling
std::vector<std::string> extensions;
// Check if name already has an extension
bool hasExtension = name.find('.') != std::string::npos;
if (hasExtension) {
extensions.push_back("");
} else {
// Try common executable extensions
const char *pathExt = std::getenv("PATHEXT");
if (pathExt) {
std::string pathExtStr = pathExt;
size_t start = 0;
while (start < pathExtStr.size()) {
size_t end = pathExtStr.find(';', start);
if (end == std::string::npos) {
end = pathExtStr.size();
}
std::string ext = pathExtStr.substr(start, end - start);
extensions.push_back(ext);
start = end + 1;
}
} else {
extensions = {".exe", ".bat", ".cmd", ".com"};
}
}
// Check if it's an absolute or relative path
if (name.find('\\') != std::string::npos ||
name.find('/') != std::string::npos ||
(name.size() >= 2 && name[1] == ':')) {
// It's a path, check if file exists
for (const auto &ext : extensions) {
std::string fullPath = name + ext;
DWORD attrs = ::GetFileAttributesA(fullPath.c_str());
if (attrs != INVALID_FILE_ATTRIBUTES &&
!(attrs & FILE_ATTRIBUTE_DIRECTORY)) {
return fullPath;
}
}
return {};
}
// Search in PATH
const char *pathEnv = std::getenv("PATH");
if (!pathEnv) {
return {};
}
std::string_view pathStr = pathEnv;
size_t start = 0;
while (start < pathStr.size()) {
size_t end = pathStr.find(';', start);
if (end == std::string::npos) {
end = pathStr.size();
}
auto dir = std::string(pathStr.substr(start, end - start));
if (!dir.empty()) {
// Add separator if needed
if (dir.back() != '\\' && dir.back() != '/') {
dir += '\\';
}
for (const auto &ext : extensions) {
std::string fullPath = dir + name + ext;
DWORD attrs = ::GetFileAttributesA(fullPath.c_str());
if (attrs != INVALID_FILE_ATTRIBUTES &&
!(attrs & FILE_ATTRIBUTE_DIRECTORY)) {
return fullPath;
}
}
}
start = end + 1;
}
return {};
#else
// Unix-like systems
// Check if it's already a path (contains /)
if (name.find('/') != std::string::npos) {
struct stat st;
if (stat(name.c_str(), &st) == 0 && S_ISREG(st.st_mode) &&
(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
return name;
}
return {};
}
// Search in PATH
const char *pathEnv = std::getenv("PATH");
if (!pathEnv) {
return {};
}
std::string pathStr = pathEnv;
size_t start = 0;
while (start < pathStr.size()) {
size_t end = pathStr.find(':', start);
if (end == std::string::npos) {
end = pathStr.size();
}
std::string dir = pathStr.substr(start, end - start);
if (!dir.empty()) {
// Add separator if needed
if (dir.back() != '/') {
dir += '/';
}
std::string fullPath = dir + name;
struct stat st;
if (stat(fullPath.c_str(), &st) == 0 && S_ISREG(st.st_mode) &&
(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
return fullPath;
}
}
start = end + 1;
}
return {};
#endif
}