How do I fool Linux into thinking that memory read / write was successful? I am writing a C ++ library so that all reads / writes are redirected and processed transparently to the end user. At any time, when a variable is written or read, the library will need to catch this request and drop it to the hardware simulation, which will process the data from there.
Please note that my library is platform dependent:
Linux ubuntu 3.16.0-39-generi # 53 ~ 14.04.1-Ubuntu SMP x86_64 GNU / Linux
gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
Current approach: catch SIGSEGV and increase REG_RIP
My current approach involves getting a memory area using mmap() and turning off access using mprotect() . I have a SIGSEGV handler to get information containing a memory address, export read / write to another location, and then increase the REG_RIP context.
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);
This works for very simple cases, such as:
int *num_ptr = (int *)nullptr; *num_ptr = 10;
But for something even a little more complicated, I get SIGABRT:
30729 Invalid instruction (flushing kernel) ./$ target
Using mprotect () in a SIGSEGV Handler
If I do not need to increase REG_RIP, handle_sigsegv() will be called again and again by the kernel until the memory area becomes readable or writable. I could run mprotect() for this particular address, but this has a few caveats:
- Subsequent access to the memory will not call SIGSEGV due to the fact that the memory area now has the ability PROT_WRITE. I tried to create a stream that continuously marks the area as PROT_NONE, but this does not elude the following point:
mprotect() will read or write to memory at the end of the day, which will invalidate the use of my library.
Writing a device driver
I also tried to write a device module so that the library could call mmap() on the char device, where the driver handles reading and writing from there. This makes sense in theory, but I could not (or did not have the knowledge) catch every load / store processor problems on the device. I tried to overwrite the displayed vm_operations_struct and / or inode address_space_operations structure, but this will only read / write when the page crashes or the page is flushed to the backup storage.
Perhaps I could use mmap() and mprotect() as described above on a device that doesn't write data anywhere (similar to /dev/null ), then has a process that recognizes read / write and routes the data from there (?) .
Use syscall() and provide the restore function of the restore
The following is deduced from the segvcatch 1 project: converts segfaults to 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; }; }
But this ends up being the same as the normal sigaction() configuration. If I do not set the restore function, the signal handler is not called more than once, even if the memory area is still inaccessible. Perhaps there is another trick that I could do with the kernel signal here.
Again, the whole purpose of the library is to transparently handle reads and writes to memory. There may be a much better way to do something, perhaps with ptrace() or even update the kernel code that generates the segfault signal, but the important part is that the end-user code does not need to be changed. I have seen examples using setjmp() and longjmp() to continue after segfault, but for this you will need to add these calls to each memory access. The same goes for converting segfault to try / catch.
1 segvcatch project