How do I trick linux into thinking a memory read/write was successful? I am writing a C++ library such that all reads/writes are redirected and handled transparently to the end user. Anytime a variable is written or read from, the library will need to catch that request and shoot it off to a hardware simulation which will handle the data from there.
Note that my library is platform dependent on:
Linux ubuntu 3.16.0-39-generic #53~14.04.1-Ubuntu SMP x86_64 GNU/Linux
gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
Current Approach: catch SIGSEGV and increment REG_RIP
My current approach involves getting a memory region using mmap()
and shutting off access using mprotect()
. I have a SIGSEGV handler to get the info containing the memory address, export the read/write elsewhere, then increment context REG_RIP.
void handle_sigsegv(int code, siginfo_t *info, void *ctx)
{
void *addr = info->si_addr;
ucontext_t *u = (ucontext_t *)ctx;
int err = u->uc_mcontext.gregs[REG_ERR];
bool is_write = (err & 0x2);
// send data read/write to simulation...
// then continue execution of program by incrementing RIP
u->uc_mcontext.gregs[REG_RIP] += 6;
}
This works for very simple cases, such as:
int *num_ptr = (int *)nullptr;
*num_ptr = 10; // write segfault
But for anything even slightly more complex, I receive a SIGABRT:
30729 Illegal instruction (core dumped) ./$target
Using mprotect() within SIGSEGV handler
If I were to not increment REG_RIP, handle_sigsegv()
will be called over and over again by the kernel until the memory region becomes available for reading or writing. I could run mprotect()
for that specific address, but that has multiple caveats:
- Subsequent memory access will not trigger a SIGSEGV due to the memory region now having PROT_WRITE ability. I have tried to create a thread that continuously marks the region as PROT_NONE, but that does not elude the next point:
mprotect()
will, at the end of the day, perform the read or write into memory, invalidating the use case of my library.
Writing a device driver
I have also attempted to write a device module such that the library can call mmap()
on the char device, where the driver will handle the reads and writes from there. This makes sense in theory, but I have not been able to (or do not have the knowledge to) catch every load/store the processor issues to the device. I have attempted overwrite the mapped vm_operations_struct
and/or the inode's address_space_operations
struct, but that will only call reads/writes when a page is faulted or a page is flushed into backing store.
Perhaps I could use mmap()
and mprotect()
, like explained above, on the device that writes data nowhere (similar to /dev/null
), then have a process that recognizes the reads/writes and routes the data from there (?).
Utilize syscall()
and provide a restorer assembly function
The following was pulled from the segvcatch
project1 that converts segfaults into exceptions.
#define RESTORE(name, syscall) RESTORE2(name, syscall)
#define RESTORE2(name, syscall)\
asm(\
".text\n"\
".byte 0\n"\
".align 16\n"\
"__" #name ":\n"\
" movq $" #syscall ", %rax\n"\
" syscall\n"\
);
RESTORE(restore_rt, __NR_rt_sigreturn)
void restore_rt(void) asm("__restore_rt") __attribute__
((visibility("hidden")));
extern "C" {
struct kernel_sigaction {
void (*k_sa_sigaction)(int, siginfo_t *, void *);
unsigned long k_sa_flags;
void (*k_sa_restorer)(void);
sigset_t k_sa_mask;
};
}
// then within main ...
struct kernel_sigaction act;
act.k_sa_sigaction = handle_sigegv;
sigemptyset(&act.k_sa_mask);
act.k_sa_flags = SA_SIGINFO|0x4000000;
act.k_sa_restorer = restore_rt;
syscall(SYS_rt_sigaction, SIGSEGV, &act, NULL, _NSIG / 8);
But this ends up functioning no different than a regular sigaction()
configuration. If I do not set the restorer function the signal handler is not called more than once, even when the memory region is still not available. Perhaps there is some other trickery I could do with the kernel signal here.
Again, the entire objective of the library is to transparently handle reads and writes to memory. Perhaps there is a much better way of doing things, maybe with ptrace()
or even updating the kernel code that generates the segfault signal, but the important part is that the end-user's code does not require changes. I have seen examples using setjmp()
and longjmp()
to continue after a segfault, but that would require adding those calls to every memory access. The same goes for converting a segfault to a try/catch.
1 segvcatch project