Trap all accesses to an address range (Linux)

2020-05-24 04:03发布

问题:

Background

I'm writing a framework to enable co-simulation of RTL running in a simulator and un-modified host software. The host software is written to control actual hardware and typically works in one of two ways:

  1. Read/Write calls through a driver
  2. Memory mapped access using mmap

The former case is pretty straightforward - write a library that implements the same read / write calls as the driver and link against that when running a simulation. This all works wonderfully and I can run un-modified production software as stimulus for my RTL simulations.

The second case is turning out to be far more difficult than the first...

Trapping mmap

Initially I thought I could use LD_PRELOAD to intercept the mmap call. In my implementation of mmap I'd allocate some page-aligned memory and then mprotect it and set a signal handler to trap SIGSEGV.

There are numerous problems with this approach:

Read vs Write

I can determine the address of the access from siginfo_t->si_addr but not whether the access was read or write.

Catching repeat accesses

In the signal handler I need to un-protect the memory region, otherwise the I'll get repeat SIGSEGVs as soon as my handler exits and the host code can never continue. However if I unprotect the region then my signal handler won't trap subsequent accesses.

Signal handler nastiness

To block in a signal handler while the simulator drives the RTL and returns a result violates all sorts of programming rules - particularly given the simulator could trigger all sorts of other events and execute arbitrary code before returning a result from this access.

Other approaches

I was wondering if it's possible to create a file-like object that behaves like a disk rather than using mprotect on a buffer. I haven't found any information suggesting this is feasible.

Questions

Is it possible to trap all accesses to an mmap region and how?

  • Accesses need to block for an indeterminate period of time (while simulator runs)
  • Read accesses need to retrieve a new value placed by my trap

Assuming LD_PRELOAD and mprotect is the best route:

  • Can I determine whether the access was a read or a write call?
  • How do I trap subsequent accesses since I have to un-mprotect the region?

Related Questions

How to write a signal handler to catch SIGSEGV?

Possible to trap write to address (x86 - linux)

回答1:

On X86 you can set Trap flag for the caller's context to get SIGTRAP after one instruction (this flag is typically used for single-stepping). That is, when SIGSEGV is encountered, you set TF in the caller's EFLAGS (see ucontext.h), enable reading with mprotect and return. If SIGSEGV is repeated instantly with the same IP, you enable writing (and optionally disable reading, if you want to distinguish read-modify-write from write-only access). If you get SIGSEGV from the same IP for read-only and write-only protection, enable read-write.

Whenever you get SIGTRAP, you can analyze what value was written (if it was a write access), and you can also re-protect the page to trap future accesses.

Correction: if both reads and writes can have side-effects, try write-only protection first, then apply reading side-effects and try read-only protection, then enable writing and handle side-effects of writing in the final SIGTRAP handler.

UPDATE: I was deadly wrong on recommending hypothetical write-only protection which turns out not to exist on most architectures. Fortunately there's a more straightforward way to know whether the operation that failed tries to read memory, at least on x86:

Page fault exception pushes an error code to the stack, which is available in Linux SIGSEGV handler as the err member of sigcontext structure. Bit 1 of the error code is 1 for write faults and 0 otherwise. For read-modify-write operation, it will be 0 initially (here you can emulate reading, knowing exactly that it's going to happen).