Trap memory accesses inside a standard executable built with MinGW

余生长醉 提交于 2019-12-11 07:55:01

问题


So my problem sounds like this.

I have some platform dependent code (embedded system) which writes to some MMIO locations that are hardcoded at specific addresses.

I compile this code with some management code inside a standard executable (mainly for testing) but also for simulation (because it takes longer to find basic bugs inside the actual HW platform).

To alleviate the hardcoded pointers, i just redefine them to some variables inside the memory pool. And this works really well.

The problem is that there is specific hardware behavior on some of the MMIO locations (w1c for example) which makes "correct" testing hard to impossible.

These are the solutions i thought of:

1 - Somehow redefine the accesses to those registers and try to insert some immediate function to simulate the dynamic behavior. This is not really usable since there are various ways to write to the MMIO locations (pointers and stuff).

2 - Somehow leave the addresses hardcoded and trap the illegal access through a seg fault, find the location that triggered, extract exactly where the access was made, handle and return. I am not really sure how this would work (and even if it's possible).

3 - Use some sort of emulation. This will surely work, but it will void the whole purpose of running fast and native on a standard computer.

4 - Virtualization ?? Probably will take a lot of time to implement. Not really sure if the gain is justifiable.

Does anyone have any idea if this can be accomplished without going too deep? Maybe is there a way to manipulate the compiler in some way to define a memory area for which every access will generate a callback. Not really an expert in x86/gcc stuff.

Edit: It seems that it's not really possible to do this in a platform independent way, and since it will be only windows, i will use the available API (which seems to work as expected). Found this Q here:

Is set single step trap available on win 7?

I will put the whole "simulated" register file inside a number of pages, guard them, and trigger a callback from which i will extract all the necessary info, do my stuff then continue execution.

Thanks all for responding.


回答1:


I think #2 is the best approach. I routinely use approach #4, but I use it to test code that is running in the kernel, so I need a layer below the kernel to trap and emulate the accesses. Since you have already put your code into a user-mode application, #2 should be simpler.

The answers to this question may provide help in implementing #2. How to write a signal handler to catch SIGSEGV?

What you really want to do, though, is to emulate the memory access and then have the segv handler return to the instruction after the access. This sample code works on Linux. I'm not sure if the behavior it is taking advantage of is undefined, though.

#include <stdint.h>
#include <stdio.h>
#include <signal.h>

#define REG_ADDR ((volatile uint32_t *)0x12340000f000ULL)

static uint32_t read_reg(volatile uint32_t *reg_addr)
{
    uint32_t r;
    asm("mov (%1), %0" : "=a"(r) : "r"(reg_addr));
    return r;
}

static void segv_handler(int, siginfo_t *, void *);

int main()
{
    struct sigaction action = { 0, };
    action.sa_sigaction = segv_handler;
    action.sa_flags = SA_SIGINFO;
    sigaction(SIGSEGV, &action, NULL);

    // force sigsegv
    uint32_t a = read_reg(REG_ADDR);

    printf("after segv, a = %d\n", a);

    return 0;
}


static void segv_handler(int, siginfo_t *info, void *ucontext_arg)
{
    ucontext_t *ucontext = static_cast<ucontext_t *>(ucontext_arg);
    ucontext->uc_mcontext.gregs[REG_RAX] = 1234;
    ucontext->uc_mcontext.gregs[REG_RIP] += 2;
}

The code to read the register is written in assembly to ensure that both the destination register and the length of the instruction are known.




回答2:


This is how the Windows version of prl's answer could look like:

#include <stdint.h>
#include <stdio.h>
#include <windows.h>

#define REG_ADDR ((volatile uint32_t *)0x12340000f000ULL)

static uint32_t read_reg(volatile uint32_t *reg_addr)
{
  uint32_t r;
  asm("mov (%1), %0" : "=a"(r) : "r"(reg_addr));
  return r;
}

static LONG WINAPI segv_handler(EXCEPTION_POINTERS *);

int main()
{
  SetUnhandledExceptionFilter(segv_handler);

  // force sigsegv
  uint32_t a = read_reg(REG_ADDR);

  printf("after segv, a = %d\n", a);

  return 0;
}


static LONG WINAPI segv_handler(EXCEPTION_POINTERS *ep)
{
  // only handle read access violation of REG_ADDR
  if (ep->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION ||
      ep->ExceptionRecord->ExceptionInformation[0] != 0 ||
      ep->ExceptionRecord->ExceptionInformation[1] != (ULONG_PTR)REG_ADDR)
    return EXCEPTION_CONTINUE_SEARCH;

  ep->ContextRecord->Rax = 1234;
  ep->ContextRecord->Rip += 2;
  return EXCEPTION_CONTINUE_EXECUTION;
}



回答3:


So, the solution (code snippet) is as follows:

First of all, i have a variable:

__attribute__ ((aligned (4096))) int g_test;

Second, inside my main function, i do the following:

AddVectoredExceptionHandler(1, VectoredHandler);
DWORD old; 
VirtualProtect(&g_test, 4096, PAGE_READWRITE | PAGE_GUARD, &old);

The handler looks like this:

LONG WINAPI VectoredHandler(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
    static DWORD last_addr;

    if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) {
        last_addr = ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
        ExceptionInfo->ContextRecord->EFlags |= 0x100; /* Single step to trigger the next one */
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) {
        DWORD old;
        VirtualProtect((PVOID)(last_addr & ~PAGE_MASK), 4096, PAGE_READWRITE | PAGE_GUARD, &old);
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

This is only a basic skeleton for the functionality. Basically I guard the page on which the variable resides, i have some linked lists in which i hold pointers to the function and values for the address in question. I check that the fault generating address is inside my list then i trigger the callback.

On first guard hit, the page protection will be disabled by the system, but i can call my PRE_WRITE callback where i can save the variable state. Because a single step is issued through the EFlags, it will be followed immediately by a single step exception (which means that the variable was written), and i can trigger a WRITE callback. All the data required for the operation is contained inside the ExceptionInformation array.

When someone tries to write to that variable:

*(int *)&g_test = 1;

A PRE_WRITE followed by a WRITE will be triggered,

When i do:

int x = *(int *)&g_test;

A READ will be issued.

In this way i can manipulate the data flow in a way that does not require modifications of the original source code. Note: This is intended to be used as part of a test framework and any penalty hit is deemed acceptable.

For example, W1C (Write 1 to clear) operation can be accomplished:

void MYREG_hook(reg_cbk_t type)
{
    /** We need to save the pre-write state
      * This is safe since we are assured to be called with
      * both PRE_WRITE and WRITE in the correct order 
      */
    static int pre;

    switch (type) {
        case REG_READ: /* Called pre-read */
            break;

        case REG_PRE_WRITE: /* Called pre-write */
            pre = g_test;
            break;

        case REG_WRITE: /* Called after write */
            g_test = pre & ~g_test; /* W1C */
            break;

        default:
            break;    
    }
}

This was possible also with seg-faults on illegal addresses, but i had to issue one for each R/W, and keep track of a "virtual register file" so a bigger penalty hit. In this way i can only guard specific areas of memory or none, depending on the registered monitors.



来源:https://stackoverflow.com/questions/54890486/trap-memory-accesses-inside-a-standard-executable-built-with-mingw

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!