Merge pull request #695 from HyperDbg/linux

Linux
This commit is contained in:
Sina Karvandi 2026-10-07 15:11:04 +02:00 • committed by GitHub
commit f165ff26bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 716 additions and 126 deletions

View file

@ -119,6 +119,8 @@ typedef void * HMODULE;
// Win32 wait/event constants (used by the cross-platform sync wrappers)
# define INFINITE 0xFFFFFFFF
# define WAIT_OBJECT_0 0x00000000
# define WAIT_TIMEOUT 0x00000102
# define WAIT_FAILED 0xFFFFFFFF
// Win32 invalid handle sentinel (returned by the cross-platform file/serial wrappers)
# define INVALID_HANDLE_VALUE ((HANDLE)(SIZE_T)-1)

View file

@ -15,6 +15,7 @@
#if defined(__linux__)
# include "../header/PlatformMem.h"
# include <linux/io.h> // ioremap / iounmap / virt_to_phys / phys_to_virt
#endif // defined(__linux__)
#if defined(_WIN32) || defined(_WIN64)
@ -269,10 +270,10 @@ PlatformMemFreePool(PVOID BufferAddress)
//
// -------------------------------------------------------------------------
// Cross-platform wrappers for the memory-manager / pool APIs the shared sources
// use. Windows forwards to the WDK; the Linux arm is a placeholder stub for now.
// use. Windows forwards to the WDK; the Linux arm forwards to the equivalent
// kernel API where one exists, and is still a placeholder stub where it does not
// (see the remaining TODO(Linux) markers below).
// The #ifdef lives INSIDE each wrapper so the call sites stay OS-agnostic.
// TODO(Linux): replace each Linux arm with its real equivalent (ioremap,
// virt_to_phys, phys_to_virt, ...) as the callers are brought up.
// -------------------------------------------------------------------------
//
@ -282,7 +283,24 @@ PlatformMemMapIoSpace(PHYSICAL_ADDRESS PhysicalAddress, SIZE_T NumberOfBytes, ME
#if defined(_WIN32) || defined(_WIN64)
return MmMapIoSpace(PhysicalAddress, NumberOfBytes, CacheType);
#elif defined(__linux__)
return NULL; // TODO(Linux): ioremap()
//
// MmMapIoSpace takes the cacheability as an argument, so pick the ioremap
// flavour that matches rather than defaulting everything to uncached —
// plain ioremap() is UC- on x86, which would silently be the wrong
// attribute for an MmCached or MmWriteCombined request.
//
switch (CacheType)
{
case MmCached:
return (PVOID)ioremap_cache((phys_addr_t)PhysicalAddress.QuadPart, NumberOfBytes);
case MmWriteCombined:
return (PVOID)ioremap_wc((phys_addr_t)PhysicalAddress.QuadPart, NumberOfBytes);
case MmNonCached:
default:
return (PVOID)ioremap((phys_addr_t)PhysicalAddress.QuadPart, NumberOfBytes);
}
#endif
}
@ -302,7 +320,13 @@ PlatformMemUnmapIoSpace(PVOID BaseAddress, SIZE_T NumberOfBytes)
#if defined(_WIN32) || defined(_WIN64)
MmUnmapIoSpace(BaseAddress, NumberOfBytes);
#elif defined(__linux__)
// no-op // TODO(Linux): iounmap()
//
// iounmap() recovers the length from the vm_area it created, so the
// NumberOfBytes the WDK needs is unused here.
//
UNREFERENCED_PARAMETER(NumberOfBytes);
iounmap((void __iomem *)BaseAddress);
#endif
}
@ -312,9 +336,16 @@ PlatformMemGetPhysicalAddress(PVOID BaseAddress)
#if defined(_WIN32) || defined(_WIN64)
return MmGetPhysicalAddress(BaseAddress);
#elif defined(__linux__)
//
// virt_to_phys() is valid for direct-map (kmalloc/kzalloc) addresses, which
// is what every Platform* allocator in this file hands out. It is NOT valid
// for vmalloc or ioremap addresses — MmGetPhysicalAddress resolves those on
// Windows, so a caller that starts passing one will need vmalloc_to_pfn()
// here instead.
//
PHYSICAL_ADDRESS Pa;
Pa.QuadPart = 0;
return Pa; // TODO(Linux): virt_to_phys()
Pa.QuadPart = (LONGLONG)virt_to_phys(BaseAddress);
return Pa;
#endif
}
@ -324,7 +355,7 @@ PlatformMemGetVirtualForPhysical(PHYSICAL_ADDRESS PhysicalAddress)
#if defined(_WIN32) || defined(_WIN64)
return MmGetVirtualForPhysical(PhysicalAddress);
#elif defined(__linux__)
return NULL; // TODO(Linux): phys_to_virt()
return phys_to_virt((phys_addr_t)PhysicalAddress.QuadPart);
#endif
}
@ -364,7 +395,11 @@ PlatformMemFreeContiguousMemory(PVOID BaseAddress)
#if defined(_WIN32) || defined(_WIN64)
MmFreeContiguousMemory(BaseAddress);
#elif defined(__linux__)
// no-op // TODO(Linux): pair with the contiguous allocator used by callers
//
// Pairs with PlatformMemAllocateContiguousZeroedMemory above, which backs
// the contiguous allocation with kzalloc().
//
kfree(BaseAddress);
#endif
}
@ -387,7 +422,11 @@ PlatformMemFreePoolUntagged(PVOID P)
#if defined(_WIN32) || defined(_WIN64)
ExFreePool(P);
#elif defined(__linux__)
// no-op // TODO(Linux): kfree(), paired with the matching allocation stub
//
// Pairs with the kmalloc/kzalloc-backed PlatformMemAllocate* wrappers above
// (kfree tolerates NULL, matching the callers' expectations).
//
kfree(P);
#endif
}

View file

@ -13,6 +13,7 @@
#if defined(__linux__)
# include "../header/PlatformProcess.h"
# include <linux/sched/task.h> // init_task
//
// Backing token for the NT global PsProcessType declared in PlatformProcess.h —
@ -182,8 +183,11 @@ PlatformProcessGetCurrentThreadTeb(VOID)
//
/**
* @brief The System process' EPROCESS. Windows: PsInitialSystemProcess. Linux:
* NULL stub. TODO(Linux): a PEPROCESS shim over &init_task.
* @brief The System process' EPROCESS. Windows: PsInitialSystemProcess.
* Linux: init_task, the swapper/0 task every other task descends from,
* which is the closest counterpart to the NT System process. PEPROCESS
* is opaque and backed by task_struct on Linux (see WdkTypes.h), so the
* address is handed straight back.
*/
PEPROCESS
PlatformProcessGetInitialSystemProcess(VOID)
@ -191,7 +195,7 @@ PlatformProcessGetInitialSystemProcess(VOID)
#if defined(_WIN32) || defined(_WIN64)
return PsInitialSystemProcess;
#elif defined(__linux__)
return NULL; // TODO(Linux)
return (PEPROCESS)&init_task;
#endif
}

View file

@ -127,11 +127,10 @@ PlatformTimeConvertToTimeFields(PLARGE_INTEGER Time, PTIME_FIELDS TimeFields)
/**
* @brief Read the performance counter (and, optionally, its frequency).
* @details Windows: KeQueryPerformanceCounter(). Linux arm is a stub that
* reports 0 for both counter and frequency.
*
* TODO(Linux): back with ktime_get_ns()/tsc so busy-wait loops using this
* actually elapse.
* @details Windows: KeQueryPerformanceCounter(). Linux: ktime_get_ns(), whose
* tick is one nanosecond — so the reported frequency is fixed at
* NSEC_PER_SEC. Both platforms only promise a monotonic counter plus
* the frequency needed to convert it, which is all the callers use.
*/
LARGE_INTEGER
PlatformTimeQueryPerformanceCounter(PLARGE_INTEGER PerformanceFrequency)
@ -143,12 +142,12 @@ PlatformTimeQueryPerformanceCounter(PLARGE_INTEGER PerformanceFrequency)
#elif defined(__linux__)
LARGE_INTEGER Counter;
Counter.QuadPart = 0;
Counter.QuadPart = (LONGLONG)ktime_get_ns();
if (PerformanceFrequency != NULL)
PerformanceFrequency->QuadPart = 0;
PerformanceFrequency->QuadPart = NSEC_PER_SEC;
return Counter; // TODO(Linux)
return Counter;
#else

View file

@ -22,7 +22,128 @@
# include <strings.h>
# include <signal.h>
# include <dlfcn.h>
# include <time.h> // clock_gettime / CLOCK_MONOTONIC (PlatformQueryPerformanceCounter)
# include <sys/mman.h> // munmap (PlatformUnmapFile)
# include <time.h> // clock_gettime / CLOCK_MONOTONIC (PlatformQueryPerformanceCounter)
# include <pthread.h> // events and threads
#endif // defined(__linux__)
#if defined(__linux__)
//
// Events and threads are heap objects behind the HANDLE. Other HANDLEs on Linux
// are file descriptors, FILE pointers, etc., so every object created here is
// kept in a registry; PlatformCloseHandle and the event routines only touch a
// HANDLE after finding it there
//
typedef enum _PLATFORM_HANDLE_TYPE
{
PLATFORM_HANDLE_EVENT,
PLATFORM_HANDLE_THREAD,
} PLATFORM_HANDLE_TYPE;
typedef struct _PLATFORM_HANDLE_OBJECT
{
struct _PLATFORM_HANDLE_OBJECT * Next;
PLATFORM_HANDLE_TYPE Type;
//
// PLATFORM_HANDLE_EVENT
//
pthread_mutex_t Lock;
pthread_cond_t Cond;
BOOLEAN ManualReset;
BOOLEAN Signaled;
//
// PLATFORM_HANDLE_THREAD
//
pthread_t Thread;
} PLATFORM_HANDLE_OBJECT, *PPLATFORM_HANDLE_OBJECT;
static pthread_mutex_t g_PlatformHandleRegistryLock = PTHREAD_MUTEX_INITIALIZER;
static PPLATFORM_HANDLE_OBJECT g_PlatformHandleRegistry = NULL;
/**
* @brief Add an object to the handle registry
*/
static VOID
PlatformHandleRegister(PPLATFORM_HANDLE_OBJECT Object)
{
pthread_mutex_lock(&g_PlatformHandleRegistryLock);
Object->Next = g_PlatformHandleRegistry;
g_PlatformHandleRegistry = Object;
pthread_mutex_unlock(&g_PlatformHandleRegistryLock);
}
/**
* @brief Find an object in the handle registry
*
* @param Handle
* @param Remove TRUE to also unlink it from the registry
*
* @return PPLATFORM_HANDLE_OBJECT the object, or NULL if Handle isn't one
*/
static PPLATFORM_HANDLE_OBJECT
PlatformHandleLookup(HANDLE Handle, BOOLEAN Remove)
{
PPLATFORM_HANDLE_OBJECT * Link;
PPLATFORM_HANDLE_OBJECT Found = NULL;
pthread_mutex_lock(&g_PlatformHandleRegistryLock);
for (Link = &g_PlatformHandleRegistry; *Link != NULL; Link = &(*Link)->Next)
{
if ((HANDLE)*Link == Handle)
{
Found = *Link;
if (Remove)
*Link = Found->Next;
break;
}
}
pthread_mutex_unlock(&g_PlatformHandleRegistryLock);
return Found;
}
/**
* @brief Find an event in the handle registry
*/
static PPLATFORM_HANDLE_OBJECT
PlatformEventLookup(HANDLE Handle)
{
PPLATFORM_HANDLE_OBJECT Object = PlatformHandleLookup(Handle, FALSE);
return (Object != NULL && Object->Type == PLATFORM_HANDLE_EVENT) ? Object : NULL;
}
//
// Start parameters of a thread, freed by the thread itself
//
typedef struct _PLATFORM_THREAD_START
{
PLATFORM_THREAD_ROUTINE Routine;
PVOID Param;
} PLATFORM_THREAD_START, *PPLATFORM_THREAD_START;
/**
* @brief pthread entry point that calls the Win32-style thread routine
*/
static void *
PlatformThreadTrampoline(void * Arg)
{
PLATFORM_THREAD_START Start = *(PPLATFORM_THREAD_START)Arg;
free(Arg);
Start.Routine(Start.Param);
return NULL;
}
#endif // defined(__linux__)
/**
@ -536,14 +657,29 @@ PlatformCreateEvent(BOOLEAN ManualReset, BOOLEAN InitialState)
#if defined(_WIN32)
return CreateEvent(NULL, ManualReset, InitialState, NULL);
#elif defined(__linux__)
PPLATFORM_HANDLE_OBJECT Event;
pthread_condattr_t CondAttr;
Event = (PPLATFORM_HANDLE_OBJECT)calloc(1, sizeof(PLATFORM_HANDLE_OBJECT));
if (Event == NULL)
return NULL;
Event->Type = PLATFORM_HANDLE_EVENT;
Event->ManualReset = ManualReset;
Event->Signaled = InitialState;
//
// TODO: back this with a pthread mutex+cond (or eventfd) when the Linux
// kernel-debugger transport is implemented. For now return a dummy
// non-NULL handle so existing NULL-checks treat creation as success.
// Timed waits use the monotonic clock so wall-clock changes don't affect them
//
(void)ManualReset;
(void)InitialState;
return (HANDLE)(uintptr_t)1;
pthread_condattr_init(&CondAttr);
pthread_condattr_setclock(&CondAttr, CLOCK_MONOTONIC);
pthread_cond_init(&Event->Cond, &CondAttr);
pthread_condattr_destroy(&CondAttr);
pthread_mutex_init(&Event->Lock, NULL);
PlatformHandleRegister(Event);
return (HANDLE)Event;
#else
# error "Unsupported platform"
#endif
@ -558,7 +694,24 @@ PlatformSetEvent(HANDLE EventHandle)
#if defined(_WIN32)
return (BOOLEAN)SetEvent(EventHandle);
#elif defined(__linux__)
(void)EventHandle; // TODO: signal the underlying cond/eventfd
PPLATFORM_HANDLE_OBJECT Event = PlatformEventLookup(EventHandle);
if (Event == NULL)
return FALSE;
pthread_mutex_lock(&Event->Lock);
Event->Signaled = TRUE;
//
// A manual-reset event releases every waiter, an auto-reset event only one
//
if (Event->ManualReset)
pthread_cond_broadcast(&Event->Cond);
else
pthread_cond_signal(&Event->Cond);
pthread_mutex_unlock(&Event->Lock);
return TRUE;
#else
# error "Unsupported platform"
@ -574,7 +727,15 @@ PlatformResetEvent(HANDLE EventHandle)
#if defined(_WIN32)
return (BOOLEAN)ResetEvent(EventHandle);
#elif defined(__linux__)
(void)EventHandle; // TODO: clear the underlying cond/eventfd
PPLATFORM_HANDLE_OBJECT Event = PlatformEventLookup(EventHandle);
if (Event == NULL)
return FALSE;
pthread_mutex_lock(&Event->Lock);
Event->Signaled = FALSE;
pthread_mutex_unlock(&Event->Lock);
return TRUE;
#else
# error "Unsupported platform"
@ -584,7 +745,9 @@ PlatformResetEvent(HANDLE EventHandle)
/**
* @brief Platform independent wrapper for WaitForSingleObject
*
* @return 0 (WAIT_OBJECT_0) on success
* @details On Linux only events can be waited on
*
* @return WAIT_OBJECT_0 when signaled, WAIT_TIMEOUT, or WAIT_FAILED
*/
DWORD
PlatformWaitForSingleObject(HANDLE Handle, DWORD TimeoutMilliseconds)
@ -592,13 +755,51 @@ PlatformWaitForSingleObject(HANDLE Handle, DWORD TimeoutMilliseconds)
#if defined(_WIN32)
return WaitForSingleObject(Handle, TimeoutMilliseconds);
#elif defined(__linux__)
PPLATFORM_HANDLE_OBJECT Event = PlatformEventLookup(Handle);
struct timespec Deadline;
int Ret = 0;
if (Event == NULL)
return WAIT_FAILED;
if (TimeoutMilliseconds != INFINITE)
{
clock_gettime(CLOCK_MONOTONIC, &Deadline);
Deadline.tv_sec += TimeoutMilliseconds / 1000;
Deadline.tv_nsec += (long)(TimeoutMilliseconds % 1000) * 1000000L;
if (Deadline.tv_nsec >= 1000000000L)
{
Deadline.tv_sec += 1;
Deadline.tv_nsec -= 1000000000L;
}
}
pthread_mutex_lock(&Event->Lock);
while (!Event->Signaled && Ret != ETIMEDOUT)
{
if (TimeoutMilliseconds == INFINITE)
pthread_cond_wait(&Event->Cond, &Event->Lock);
else
Ret = pthread_cond_timedwait(&Event->Cond, &Event->Lock, &Deadline);
}
if (!Event->Signaled)
{
pthread_mutex_unlock(&Event->Lock);
return WAIT_TIMEOUT;
}
//
// TODO: wait on the underlying cond/eventfd. For now return immediately as
// success — no real transport exists yet to wait on.
// An auto-reset event is consumed by the waiter it releases
//
(void)Handle;
(void)TimeoutMilliseconds;
return 0;
if (!Event->ManualReset)
Event->Signaled = FALSE;
pthread_mutex_unlock(&Event->Lock);
return WAIT_OBJECT_0;
#else
# error "Unsupported platform"
#endif
@ -613,7 +814,30 @@ PlatformCloseHandle(HANDLE Handle)
#if defined(_WIN32)
return (BOOLEAN)CloseHandle(Handle);
#elif defined(__linux__)
(void)Handle; // TODO: free the underlying cond/eventfd or close the fd
PPLATFORM_HANDLE_OBJECT Object = PlatformHandleLookup(Handle, TRUE);
//
// Not an event or a thread, nothing is released for other handle kinds yet
//
if (Object == NULL)
return TRUE;
if (Object->Type == PLATFORM_HANDLE_EVENT)
{
pthread_cond_destroy(&Object->Cond);
pthread_mutex_destroy(&Object->Lock);
}
else
{
//
// Like CloseHandle on a Win32 thread, the thread keeps running, it
// just releases its resources itself once it exits
//
pthread_detach(Object->Thread);
}
free(Object);
return TRUE;
#else
# error "Unsupported platform"
@ -633,14 +857,33 @@ PlatformCreateThread(PLATFORM_THREAD_ROUTINE Routine, PVOID Param)
#if defined(_WIN32)
return CreateThread(NULL, 0, Routine, Param, 0, NULL);
#elif defined(__linux__)
//
// TODO: back this with pthread_create when the Linux kernel-debugger
// transport is implemented. Returning NULL leaves the listening
// thread unstarted, which is all the callers check for.
//
(void)Routine;
(void)Param;
return NULL;
PPLATFORM_HANDLE_OBJECT Object;
PPLATFORM_THREAD_START Start;
Object = (PPLATFORM_HANDLE_OBJECT)calloc(1, sizeof(PLATFORM_HANDLE_OBJECT));
Start = (PPLATFORM_THREAD_START)malloc(sizeof(PLATFORM_THREAD_START));
if (Object == NULL || Start == NULL)
{
free(Object);
free(Start);
return NULL;
}
Object->Type = PLATFORM_HANDLE_THREAD;
Start->Routine = Routine;
Start->Param = Param;
if (pthread_create(&Object->Thread, NULL, PlatformThreadTrampoline, Start) != 0)
{
free(Object);
free(Start);
return NULL;
}
PlatformHandleRegister(Object);
return (HANDLE)Object;
#else
# error "Unsupported platform"
#endif
@ -665,9 +908,9 @@ PlatformCreateThread(PLATFORM_THREAD_ROUTINE Routine, PVOID Param)
* (teardown before thread-kill, and SD_SEND -> SHUT_RDWR to wake a
* blocked reader), which the port's no-logic-changes rule defers.
*
* Returning TRUE is consistent with PlatformCreateThread, which
* returns NULL on Linux — the thread is never started there, so
* there is nothing to terminate yet.
* Until then this is a no-op that returns TRUE: the thread keeps
* running and leaves its loop on the next receive error, and
* PlatformCloseHandle detaches it so it is cleaned up on exit.
*
* @param Thread handle to the thread to terminate
* @param ExitCode exit code for the terminated thread
@ -944,19 +1187,19 @@ PlatformReadFileAtOffset(HANDLE FileHandle, UINT64 Offset, VOID * Buffer, DWORD
return (BOOLEAN)ReadFile(FileHandle, Buffer, NumberOfBytes, BytesRead, NULL);
#elif defined(__linux__)
//
// TODO (linux): pread((int)(intptr_t)FileHandle, Buffer, NumberOfBytes, Offset)
// once PlatformMapFileReadOnly wraps a real fd. Unreached today
// because the map returns NULL on Linux, so callers bail first.
// pread() is the positioned read: it takes the offset directly instead of
// needing the SetFilePointerEx seek the Windows arm does first. Unreached
// today because PlatformMapFileReadOnly still returns NULL on Linux (it is
// blocked on the wide-char path), so callers bail before getting here.
//
(void)FileHandle;
(void)Offset;
(void)Buffer;
(void)NumberOfBytes;
ssize_t ReadResult = pread((int)(intptr_t)FileHandle, Buffer, NumberOfBytes, (off_t)Offset);
if (BytesRead != NULL)
{
*BytesRead = 0;
*BytesRead = (ReadResult < 0) ? 0 : (DWORD)ReadResult;
}
return FALSE;
return (BOOLEAN)(ReadResult >= 0);
#else
# error "Unsupported platform"
#endif
@ -984,13 +1227,18 @@ PlatformUnmapFile(VOID * BaseAddress, SIZE_T FileSize, HANDLE FileHandle)
}
#elif defined(__linux__)
//
// TODO (linux): munmap(BaseAddress, FileSize) and close the fd behind
// FileHandle once PlatformMapFileReadOnly is implemented.
// No-op for now since the map always returns NULL.
// munmap() needs the length, which is why the size is passed back in here;
// the fd handed back by PlatformMapFileReadOnly is closed alongside it.
// Unreached today since the map still returns NULL on Linux.
//
(void)BaseAddress;
(void)FileSize;
(void)FileHandle;
if (BaseAddress != NULL)
{
munmap(BaseAddress, FileSize);
}
if (FileHandle != INVALID_HANDLE_VALUE)
{
close((int)(intptr_t)FileHandle);
}
#else
# error "Unsupported platform"
#endif

View file

@ -5,8 +5,8 @@
* @details See platform-serial.h. The Windows branch wraps the Win32 serial primitives
* (CreateFile / Comm* / overlapped ReadFile/WriteFile) and owns the per-direction
* OVERLAPPED state internally so the protocol layer never sees it. The Linux
* branch is currently stubbed (returns FALSE/NULL) and is the home where the
* termios-based implementation over /dev/tty* will live.
* branch uses blocking file descriptors: termios for tty devices, and a
* UNIX domain socket for VMware's serial-port socket on a Linux host.
*
* @version 0.20
* @date 2026-06-08
@ -28,57 +28,247 @@
#elif defined(__linux__)
//
// TODO: implement the serial transport on Linux using termios over /dev/tty*:
// - PlatformSerialOpen -> open(PortName, O_RDWR | O_NOCTTY)
// - PlatformSerialConfigure -> tcgetattr/cfsetspeed/tcsetattr (raw, 8-N-1)
// - PlatformSerialReadByte -> read() (with VTIME/VMIN or poll() for the timeout role)
// - PlatformSerialWrite -> write()
// - PlatformSerialClose -> close()
// Named-pipe transport would map onto a UNIX domain socket / FIFO.
//
// Until then these return failure so the kernel-debugger connection simply
// reports "not supported on Linux yet" rather than crashing.
//
# include <errno.h>
# include <fcntl.h>
# include <stdint.h>
# include <string.h>
# include <termios.h>
# include <unistd.h>
# include <sys/socket.h>
# include <sys/stat.h>
# include <sys/un.h>
//
// The transport handle wraps a file descriptor. fd 0 is a valid descriptor but
// a NULL HANDLE means failure, so the handle stores fd + 1
//
# define SERIAL_FD_TO_HANDLE(Fd) ((HANDLE)(intptr_t)((Fd) + 1))
# define SERIAL_HANDLE_TO_FD(H) ((int)(intptr_t)(H) - 1)
/**
* @brief Open the serial transport
* @details PortName is either a tty device (/dev/ttyS*, /dev/ttyUSB*, a pty)
* or a UNIX domain socket, which is how VMware exposes a VM's serial
* port ("named pipe") on a Linux host
*
* @param PortName path of the device or socket
* @param Role debugger or debuggee side (both use blocking I/O on Linux)
*
* @return HANDLE transport handle, or NULL on failure (errno is set)
*/
HANDLE
PlatformSerialOpen(const char * PortName, PLATFORM_SERIAL_IO_ROLE Role)
{
(void)PortName;
struct stat St;
int Fd;
(void)Role;
return NULL;
if (stat(PortName, &St) != 0)
return NULL;
if (S_ISSOCK(St.st_mode))
{
struct sockaddr_un Addr = {0};
if (strlen(PortName) >= sizeof(Addr.sun_path))
{
errno = ENAMETOOLONG;
return NULL;
}
Fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (Fd < 0)
return NULL;
Addr.sun_family = AF_UNIX;
strcpy(Addr.sun_path, PortName);
if (connect(Fd, (struct sockaddr *)&Addr, sizeof(Addr)) != 0)
{
int SavedErrno = errno;
close(Fd);
errno = SavedErrno;
return NULL;
}
}
else
{
Fd = open(PortName, O_RDWR | O_NOCTTY | O_CLOEXEC);
if (Fd < 0)
return NULL;
}
return SERIAL_FD_TO_HANDLE(Fd);
}
/**
* @brief Convert a baud rate to its termios speed constant
*
* @param BaudRate
*
* @return speed_t the speed, or 0 if termios has no constant for it
*/
static speed_t
PlatformSerialBaudToSpeed(DWORD BaudRate)
{
switch (BaudRate)
{
case 110:
return B110;
case 300:
return B300;
case 600:
return B600;
case 1200:
return B1200;
case 2400:
return B2400;
case 4800:
return B4800;
case 9600:
return B9600;
case 19200:
return B19200;
case 38400:
return B38400;
case 57600:
return B57600;
case 115200:
return B115200;
default:
return 0;
}
}
/**
* @brief Configure baud rate and raw 8-N-1 framing
* @details A socket has no line settings, so non-tty handles are accepted as-is
*
* @param Handle
* @param BaudRate
*
* @return BOOLEAN
*/
BOOLEAN
PlatformSerialConfigure(HANDLE Handle, DWORD BaudRate)
{
(void)Handle;
(void)BaudRate;
return FALSE;
int Fd = SERIAL_HANDLE_TO_FD(Handle);
struct termios Tio;
speed_t Speed;
if (!isatty(Fd))
return TRUE;
Speed = PlatformSerialBaudToSpeed(BaudRate);
if (Speed == 0)
{
errno = EINVAL;
return FALSE;
}
if (tcgetattr(Fd, &Tio) != 0)
return FALSE;
cfmakeraw(&Tio); // 8 data bits, no parity, no echo, no line processing
Tio.c_cflag &= ~(CSTOPB | CRTSCTS);
Tio.c_cflag |= CLOCAL | CREAD;
Tio.c_cc[VMIN] = 1;
Tio.c_cc[VTIME] = 0;
if (cfsetispeed(&Tio, Speed) != 0 || cfsetospeed(&Tio, Speed) != 0)
return FALSE;
if (tcsetattr(Fd, TCSANOW, &Tio) != 0)
return FALSE;
tcflush(Fd, TCIOFLUSH);
return TRUE;
}
/**
* @brief Read a single byte (blocking)
*
* @param Handle
* @param OutByte
* @param BytesRead
* @param Role
*
* @return BOOLEAN TRUE if a byte was read
*/
BOOLEAN
PlatformSerialReadByte(HANDLE Handle,
CHAR * OutByte,
DWORD * BytesRead,
PLATFORM_SERIAL_IO_ROLE Role)
{
(void)Handle;
(void)OutByte;
ssize_t Ret;
(void)Role;
do
{
Ret = read(SERIAL_HANDLE_TO_FD(Handle), OutByte, 1);
} while (Ret < 0 && errno == EINTR);
if (BytesRead)
*BytesRead = 0;
return FALSE;
*BytesRead = Ret > 0 ? (DWORD)Ret : 0;
return Ret == 1;
}
/**
* @brief Write a buffer (blocking, until everything is sent)
*
* @param Handle
* @param Buffer
* @param Length
* @param Synchronous unused, all writes are blocking on Linux
*
* @return BOOLEAN
*/
BOOLEAN
PlatformSerialWrite(HANDLE Handle, const void * Buffer, UINT32 Length, BOOLEAN Synchronous)
{
(void)Handle;
(void)Buffer;
(void)Length;
const char * Ptr = (const char *)Buffer;
ssize_t Ret;
(void)Synchronous;
return FALSE;
while (Length > 0)
{
Ret = write(SERIAL_HANDLE_TO_FD(Handle), Ptr, Length);
if (Ret < 0)
{
if (errno == EINTR)
continue;
return FALSE;
}
Ptr += Ret;
Length -= (UINT32)Ret;
}
return TRUE;
}
/**
* @brief Close the transport handle
*
* @param Handle
*
* @return BOOLEAN
*/
BOOLEAN
PlatformSerialClose(HANDLE Handle)
{
if (Handle == NULL)
return FALSE;
return close(SERIAL_HANDLE_TO_FD(Handle)) == 0;
}
#else

View file

@ -191,13 +191,23 @@ set(SourceFiles
include_directories(
"../dependencies/phnt"
"../dependencies"
"../dependencies/zydis/dependencies/zycore/include"
"../include"
"../dependencies/zydis/include"
"."
"../script-eval"
"../dependencies/keystone/include"
)
#
# On Linux libhyperdbg links the system Zydis/Zycore (see the top-level
# CMakeLists.txt), so it must also use the system headers; the bundled headers
# are a different Zydis version and fail the ZYDIS_VERSION check at runtime
#
if(NOT UNIX)
include_directories(
"../dependencies/zydis/dependencies/zycore/include"
"../dependencies/zydis/include"
)
endif()
set_source_files_properties(
"../include/platform/user/code/platform-intrinsics.c"
"../include/platform/user/code/platform-lib-calls.c"

View file

@ -78,6 +78,17 @@ CommandDebugCheckComPort(const CHAR * ComPort, UINT32 * Port)
*Port = COM4_PORT;
return TRUE;
}
#if defined(__linux__)
else if (ComPort[0] == '/')
{
//
// A device or socket path (e.g., /dev/ttyS0, or the VMware serial
// port socket), only usable by the debugger, it has no I/O port
//
*Port = 0;
return TRUE;
}
#endif // defined(__linux__)
return FALSE;
}
@ -186,10 +197,10 @@ HyperDbgDebugCurrentDeviceUsingComPort(const CHAR * PortName, DWORD Baudrate)
//
// check if com port address is valid or not
//
if (!CommandDebugCheckComPort(PortName, &Port))
if (!CommandDebugCheckComPort(PortName, &Port) || Port == 0)
{
//
// com port is invalid
// com port is invalid (a device path has no I/O port for the debuggee)
//
return FALSE;
}
@ -412,6 +423,18 @@ CommandDebug(vector<CommandToken> CommandTokens, string Command)
return;
}
#if defined(__linux__)
//
// A device or socket path has no I/O port, so it can't be used by the debuggee
//
if (IsSerial && IsPrepare && Port == 0)
{
ShowMessages("err, a device path can only be used with 'remote', use COM1-COM4 with 'prepare'\n\n");
CommandDebugHelp();
return;
}
#endif // defined(__linux__)
//
// Perform connecting to the remote machine or prepare to send
//

View file

@ -2432,14 +2432,34 @@ KdSendResponseOfThePingPacket()
//
// ShowMessages("the ping request is received\n");
CHAR * Signature = (CHAR *)BuildSignature;
UINT32 SignatureLength = sizeof(BuildSignature);
#if defined(__linux__)
//
// TODO(Linux): DEV ONLY, remove before merging. Lets the signature sent to a
// separately built (Windows) debuggee be overridden, e.g.,
// HYPERDBG_BUILD_SIGNATURE=0.2.0-20261004.0959
//
CHAR * OverrideSignature = getenv("HYPERDBG_BUILD_SIGNATURE");
if (OverrideSignature != NULL && OverrideSignature[0] != '\0')
{
ShowMessages("sending overridden build signature '%s' (dev only)\n", OverrideSignature);
Signature = OverrideSignature;
SignatureLength = (UINT32)strlen(OverrideSignature) + 1;
}
#endif // defined(__linux__)
//
// Send the handshake packet to debuggee
//
if (!KdCommandPacketAndBufferToDebuggee(
DEBUGGER_REMOTE_PACKET_TYPE_DEBUGGER_TO_DEBUGGEE_EXECUTE_ON_USER_MODE,
DEBUGGER_REMOTE_PACKET_REQUESTED_ACTION_ON_USER_MODE_DEBUGGER_VERSION,
(CHAR *)BuildSignature,
sizeof(BuildSignature)))
Signature,
SignatureLength))
{
ShowMessages("err, unable to send response to the ping packet\n");
return FALSE;
@ -2524,10 +2544,8 @@ KdPrepareSerialConnectionToRemoteSystem(HANDLE SerialHandle,
}
#else
//
// TODO(Linux): waiting for the first byte on the serial port is
// Win32-only here (SetCommMask/WaitCommEvent); the Linux home for it is
// platform-serial.c. Unreachable for now, as the Linux serial path is
// refused in KdPrepareAndConnectDebugPort.
// Linux: no need to wait for the first byte explicitly, the serial
// handle is blocking so the reads that follow wait for it
//
#endif // _WIN32
}
@ -2770,6 +2788,23 @@ StartAgain:
return Result;
}
/**
* @brief Close the serial (or named pipe) handle of the kernel debugger
*
* @param Handle
*
* @return BOOLEAN
*/
static BOOLEAN
KdCloseSerialHandle(HANDLE Handle)
{
#ifdef _WIN32
return PlatformCloseHandle(Handle);
#else
return PlatformSerialClose(Handle);
#endif // _WIN32
}
/**
* @brief Prepare and initialize COM port
*
@ -2940,13 +2975,24 @@ KdPrepareAndConnectDebugPort(const CHAR * PortName,
*/
#else
//
// TODO(Linux): opening and configuring the serial port is Win32-only
// here (CreateFile/PurgeComm/GetCommState/SetCommState). The Linux
// home for this is platform-serial.c, whose serial routines are still
// stubs. Until they are implemented, refuse the serial path.
// It's a serial, PortName is a tty device or a UNIX socket (e.g., the
// VMware serial port socket)
//
ShowMessages("err, serial connection is not supported on Linux yet\n");
return FALSE;
Comm = PlatformSerialOpen(PortName,
IsPreparing ? PLATFORM_SERIAL_IO_DEBUGGEE : PLATFORM_SERIAL_IO_DEBUGGER);
if (Comm == NULL)
{
ShowMessages("err, unable to open '%s' (%s)\n", PortName, strerror(errno));
return FALSE;
}
if (!PlatformSerialConfigure(Comm, Baudrate))
{
ShowMessages("err, unable to configure '%s' (%s)\n", PortName, strerror(errno));
PlatformSerialClose(Comm);
return FALSE;
}
#endif // _WIN32
}
else
@ -2987,7 +3033,7 @@ KdPrepareAndConnectDebugPort(const CHAR * PortName,
//
if (!KdCheckIfDebuggerIsListening(Comm))
{
PlatformCloseHandle(Comm);
KdCloseSerialHandle(Comm);
g_SerialRemoteComPortHandle = NULL;
g_IsDebuggeeInHandshakingPhase = FALSE;
@ -3012,7 +3058,7 @@ KdPrepareAndConnectDebugPort(const CHAR * PortName,
//
if (HyperDbgInstallKdDriver() == 1 || HyperDbgLoadVmmModule() == 1)
{
PlatformCloseHandle(Comm);
KdCloseSerialHandle(Comm);
g_SerialRemoteComPortHandle = NULL;
g_IsConnectedToHyperDbgLocally = FALSE;
@ -3026,7 +3072,7 @@ KdPrepareAndConnectDebugPort(const CHAR * PortName,
//
if (!g_DeviceHandle)
{
PlatformCloseHandle(Comm);
KdCloseSerialHandle(Comm);
g_SerialRemoteComPortHandle = NULL;
g_IsConnectedToHyperDbgLocally = FALSE;
@ -3041,7 +3087,7 @@ KdPrepareAndConnectDebugPort(const CHAR * PortName,
if (DebuggeeRequest == NULL)
{
PlatformCloseHandle(Comm);
KdCloseSerialHandle(Comm);
g_SerialRemoteComPortHandle = NULL;
g_IsConnectedToHyperDbgLocally = FALSE;
@ -3091,7 +3137,7 @@ KdPrepareAndConnectDebugPort(const CHAR * PortName,
if (!StatusIoctl)
{
PlatformCloseHandle(Comm);
KdCloseSerialHandle(Comm);
g_SerialRemoteComPortHandle = NULL;
g_IsConnectedToHyperDbgLocally = FALSE;
@ -3124,7 +3170,7 @@ KdPrepareAndConnectDebugPort(const CHAR * PortName,
}
else
{
PlatformCloseHandle(Comm);
KdCloseSerialHandle(Comm);
g_SerialRemoteComPortHandle = NULL;
g_IsConnectedToHyperDbgLocally = FALSE;
@ -3899,7 +3945,7 @@ KdUninitializeConnection()
//
if (g_SerialRemoteComPortHandle != NULL)
{
PlatformCloseHandle(g_SerialRemoteComPortHandle);
KdCloseSerialHandle(g_SerialRemoteComPortHandle);
g_SerialRemoteComPortHandle = NULL;
}

View file

@ -1554,10 +1554,8 @@ StartAgain:
}
#else
//
// TODO(Linux): waiting for the first byte on the serial port is
// Win32-only here (SetCommMask/WaitCommEvent); the Linux home for it is
// platform-serial.c. Unreachable for now, as the Linux serial path is
// refused in KdPrepareAndConnectDebugPort.
// Linux: no need to wait for the first byte explicitly, the serial
// handle is blocking so the read below waits for it
//
#endif // _WIN32

View file

@ -41,24 +41,55 @@ SymbolLoadOrDownloadSymbols(BOOLEAN IsDownload, BOOLEAN SilentLoad)
}
/**
* @brief Attempt to resolve a name or expression to an address.
* @brief check and convert string to a 64 bit unsigned integer and also
* check for symbol object names and evaluate expressions
* @details Same as the Windows implementation in symbol.cpp. Symbol names
* aren't resolved on Linux yet (the name lookup always reports not
* found), but numbers and expressions (e.g., @rax, @rsp+10) are
*
* On Linux, full symbol resolution (ELF/DWARF) is not yet implemented.
* This stub handles the common case of a plain hex/decimal literal so that
* numeric addresses still work everywhere in the debugger.
* @param TextToConvert the target string
* @param Result result will be save to the pointer
*
* @return BOOLEAN shows whether the conversion was successful or not
*/
BOOLEAN
SymbolConvertNameOrExprToAddress(const string & TextToConvert, PUINT64 Result)
{
try
BOOLEAN IsFound = FALSE;
BOOLEAN HasError = FALSE;
UINT64 Address = 0;
if (ConvertStringToUInt64(TextToConvert, &Address))
{
*Result = std::stoull(TextToConvert, nullptr, 0);
return TRUE;
//
// It's a hex number
//
IsFound = TRUE;
}
catch (...)
else
{
return FALSE;
//
// Check for symbol object names
//
Address = ScriptEngineConvertNameToAddressWrapper(TextToConvert.c_str(), &IsFound);
if (!IsFound)
{
//
// As the last resort, test whether it's an expression; in the
// Debugger Mode it's sent to the debuggee to be evaluated
//
Address = ScriptEngineEvalSingleExpression(TextToConvert, &HasError);
IsFound = !HasError;
}
}
if (IsFound)
{
*Result = Address;
}
return IsFound;
}
BOOLEAN