258 lines
8.4 KiB
C
258 lines
8.4 KiB
C
/* ************************************************************************** */
|
||
/* */
|
||
/* ::: :::::::: */
|
||
/* utils.c :+: :+: :+: */
|
||
/* +:+ +:+ +:+ */
|
||
/* By: thrieg <thrieg@student.42mulhouse.fr> +#+ +:+ +#+ */
|
||
/* +#+#+#+#+#+ +#+ */
|
||
/* Created: 2025/12/11 04:31:15 by thrieg #+# #+# */
|
||
/* Updated: 2025/12/12 00:19:01 by thrieg ### ########.fr */
|
||
/* */
|
||
/* ************************************************************************** */
|
||
|
||
#include "../includes/ft_strace.h"
|
||
#include "../includes/syscalls_x86.h"
|
||
#include "../includes/syscalls_x64.h"
|
||
#include <elf.h> // for EI_NIDENT, EI_CLASS, ELFCLASS32, ELFCLASS64
|
||
#include <fcntl.h> // for open
|
||
#include <sys/uio.h>
|
||
#include <sys/user.h> // for user_regs_struct
|
||
|
||
//returns 64 for x86_64, or 32 for 32 bits, -1 for open/read error, -2 for unrecognised file type
|
||
ssize_t binary_type(char *path_to_binary)
|
||
{
|
||
int fd;
|
||
unsigned char ident[EI_NIDENT];
|
||
ssize_t ret;
|
||
|
||
fd = open(path_to_binary, O_RDONLY);
|
||
if (fd == -1)
|
||
return (-1);
|
||
ret = read(fd, ident, EI_NIDENT);
|
||
close(fd);
|
||
if (ret == -1)
|
||
return (-1);
|
||
if (ret != EI_NIDENT)
|
||
return (-2);
|
||
|
||
/* Check this is an ELF file (binary) */
|
||
if (ident[0] != 0x7f || ident[1] != 'E'
|
||
|| ident[2] != 'L' || ident[3] != 'F')
|
||
return (-2);
|
||
|
||
if (ident[EI_CLASS] == ELFCLASS64)
|
||
return (64);
|
||
if (ident[EI_CLASS] == ELFCLASS32)
|
||
return (32);
|
||
return (-2); //don't know wtf this file is at this point
|
||
}
|
||
|
||
static void read_regs(pid_t pid, struct user_regs_struct *regs)
|
||
{
|
||
struct iovec io;
|
||
io.iov_base = regs;
|
||
io.iov_len = sizeof(*regs);
|
||
|
||
if (ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, &io) == -1)
|
||
{
|
||
fprintf(stderr, "PTRACE_GETREGSET failed: %s\n", strerror(errno));
|
||
return;
|
||
}
|
||
}
|
||
|
||
static void fill_args(long long args[6], struct user_regs_struct *regs, size_t binary_type)
|
||
{
|
||
if (binary_type == 64)
|
||
{
|
||
args[0] = (long long)regs->rdi;
|
||
args[1] = (long long)regs->rsi;
|
||
args[2] = (long long)regs->rdx;
|
||
args[3] = (long long)regs->r10;
|
||
args[4] = (long long)regs->r8;
|
||
args[5] = (long long)regs->r9;
|
||
}
|
||
else if (binary_type == 32)
|
||
{
|
||
args[0] = (long long)(regs->rbx & 0xFFFFFFFF);
|
||
args[1] = (long long)(regs->rcx & 0xFFFFFFFF);
|
||
args[2] = (long long)(regs->rdx & 0xFFFFFFFF);
|
||
args[3] = (long long)(regs->rsi & 0xFFFFFFFF);
|
||
args[4] = (long long)(regs->rdi & 0xFFFFFFFF);
|
||
args[5] = (long long)(regs->rbp & 0xFFFFFFFF);
|
||
}
|
||
}
|
||
|
||
//also sets argc, returns NULL if syscall not recognised
|
||
static const char *get_syscall_name(size_t binary_type, struct user_regs_struct *regs, unsigned char *argc)
|
||
{
|
||
if (binary_type == 64 && regs->orig_rax < g_syscalls_64_len)
|
||
{
|
||
*argc = g_syscalls_64[regs->orig_rax].argc;
|
||
return (g_syscalls_64[regs->orig_rax].name);
|
||
}
|
||
else if (binary_type == 32 && (regs->orig_rax & 0xFFFFFFFF) < g_syscalls_32_len)
|
||
{
|
||
*argc = g_syscalls_32[regs->orig_rax & 0xFFFFFFFF].argc;
|
||
return (g_syscalls_32[regs->orig_rax & 0xFFFFFFFF].name);
|
||
}
|
||
*argc = 6;
|
||
return (NULL);
|
||
}
|
||
|
||
void read_regs_and_print_entry(pid_t pid, size_t binary_type)
|
||
{
|
||
struct user_regs_struct regs;
|
||
read_regs(pid, ®s);
|
||
long long args[6];
|
||
fill_args(args, ®s, binary_type);
|
||
unsigned char argc = 6;
|
||
const char *syscall_name = get_syscall_name(binary_type, ®s, &argc);
|
||
if (syscall_name)
|
||
{
|
||
char buffer[400]; //shouldn't be able to overflow because we just print the 64bits registers' values in hex without trying to interpret the string or anything, so max len = syscall_name + 20*argc (64 bit in hex is 16 chars)
|
||
ssize_t buffer_len = snprintf(buffer, sizeof(buffer), "%s(", syscall_name);
|
||
if (buffer_len < 0)
|
||
{
|
||
printf("error: unexpected syscall name");
|
||
return;
|
||
}
|
||
for (unsigned char i = 0; i < argc; i++)
|
||
{
|
||
ssize_t wrote;
|
||
if (i > 0)
|
||
{
|
||
wrote = snprintf(buffer + buffer_len, sizeof(buffer) - buffer_len,
|
||
", %lld", args[i]);
|
||
}
|
||
else
|
||
{
|
||
wrote = snprintf(buffer + buffer_len, sizeof(buffer) - buffer_len,
|
||
"%lld", args[i]);
|
||
}
|
||
if (wrote < 0)
|
||
{
|
||
//shouldn't happen
|
||
break;
|
||
}
|
||
buffer_len += wrote;
|
||
}
|
||
if ((size_t)buffer_len < sizeof(buffer) - 2)
|
||
{
|
||
buffer[buffer_len++] = ')';
|
||
buffer[buffer_len] = '\0';
|
||
}
|
||
else
|
||
{
|
||
buffer[sizeof(buffer) - 1] = '\0';
|
||
}
|
||
printf("%-100s", buffer);
|
||
}
|
||
else
|
||
{
|
||
char placeholder_syscall_name[50]; //max 20 chars in a 64 bit register rax at worst, + leeway
|
||
snprintf(placeholder_syscall_name, sizeof(placeholder_syscall_name), "unknown_syscall_id_%zu", binary_type == 32 ? (size_t)(regs.orig_rax & 0xFFFFFFFF) : (size_t)regs.orig_rax);
|
||
char buffer[400]; //shouldn't be able to overflow because we just print the 64bits registers' values in hex without trying to interpret the string or anything, so max len = syscall_name + 20*argc (64 bit in hex is 16 chars)
|
||
ssize_t buffer_len = snprintf(buffer, sizeof(buffer), "%s(", placeholder_syscall_name);
|
||
if (buffer_len < 0)
|
||
{
|
||
printf("error: unexpected syscall name");
|
||
return;
|
||
}
|
||
for (unsigned char i = 0; i < argc; i++)
|
||
{
|
||
ssize_t wrote;
|
||
if (i > 0)
|
||
{
|
||
wrote = snprintf(buffer + buffer_len, sizeof(buffer) - buffer_len,
|
||
", %lld", args[i]);
|
||
}
|
||
else
|
||
{
|
||
wrote = snprintf(buffer + buffer_len, sizeof(buffer) - buffer_len,
|
||
"%lld", args[i]);
|
||
}
|
||
if (wrote < 0)
|
||
{
|
||
//shouldn't happen
|
||
break;
|
||
}
|
||
buffer_len += wrote;
|
||
}
|
||
if ((size_t)buffer_len < sizeof(buffer) - 2)
|
||
{
|
||
buffer[buffer_len++] = ')';
|
||
buffer[buffer_len] = '\0';
|
||
}
|
||
else
|
||
{
|
||
buffer[sizeof(buffer) - 1] = '\0';
|
||
}
|
||
printf("%-100s", buffer);
|
||
}
|
||
}
|
||
|
||
void read_regs_and_print_exit(pid_t pid, size_t binary_type)
|
||
{
|
||
struct user_regs_struct regs;
|
||
long ret;
|
||
|
||
read_regs(pid, ®s);
|
||
if (binary_type == 64)
|
||
ret = (long)regs.rax;
|
||
else
|
||
ret = (long)regs.rax & 0xFFFFFFFF;
|
||
printf(" = %ld\n", ret);
|
||
fflush(stdout);
|
||
}
|
||
|
||
void print_signal(pid_t pid, int sig)
|
||
{
|
||
siginfo_t si;
|
||
int valid = 0;
|
||
|
||
// Try to get the siginfo from the kernel
|
||
if (ptrace(PTRACE_GETSIGINFO, pid, 0, &si) == -1)
|
||
{
|
||
// If it fails, we’ll just print the basic signal name
|
||
valid = 0;
|
||
}
|
||
else
|
||
{
|
||
valid = 1;
|
||
}
|
||
|
||
//Basic signal name
|
||
const char *name = strsignal(sig);
|
||
if (!name)
|
||
name = "UNKNOWN";
|
||
|
||
if (valid)
|
||
{
|
||
/* Print something like:
|
||
--- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=1234, si_uid=1000} ---
|
||
*/
|
||
printf("--- %s {", name);
|
||
printf("si_signo=%d, ", si.si_signo);
|
||
printf("si_code=%d", si.si_code);
|
||
|
||
/* Some signals have extra fields — decode some common ones */
|
||
if (si.si_code == SI_USER || si.si_code == SI_QUEUE)
|
||
{
|
||
/* Sender PID and UID */
|
||
printf(", si_pid=%d, si_uid=%d", si.si_pid, si.si_uid);
|
||
}
|
||
/* For faults like SIGSEGV, SIGBUS, SIGILL, print fault address */
|
||
if (sig == SIGSEGV || sig == SIGBUS || sig == SIGILL)
|
||
{
|
||
/* si_addr is a void* pointing to the faulting address */
|
||
printf(", si_addr=%p", si.si_addr);
|
||
}
|
||
printf("} ---\n");
|
||
}
|
||
else
|
||
{
|
||
// Fallback if GETSIGINFO failed: just print the signal
|
||
printf("--- %s ---\n", name);
|
||
}
|
||
fflush(stdout);
|
||
}
|