Доступ к памяти ловушек внутри стандартного исполняемого файла, созданного с помощью MinGW

Так что моя проблема звучит так.

У меня есть некоторый зависимый от платформы код (встроенная система), который записывает в некоторые местоположения MMIO, которые жестко закодированы по определенным адресам.

Я скомпилировал этот код с некоторым кодом управления внутри стандартного исполняемого файла (в основном для тестирования), но также и для моделирования (поскольку нахождение основных ошибок на реальной платформе HW занимает больше времени).

Чтобы облегчить жестко закодированные указатели, я просто переопределяю их для некоторых переменных в пуле памяти. И это работает очень хорошо.

Проблема заключается в том, что в некоторых местах MMIO (например, w1c) существует определенное аппаратное поведение, что делает "правильное" тестирование трудным или невозможным.

Вот решения, о которых я подумал:

1 - Каким-то образом переопределить доступ к этим регистрам и попытаться вставить некоторую непосредственную функцию для имитации динамического поведения. Это не очень удобно, поскольку существуют различные способы записи в местоположения MMIO (указатели и прочее).

2 - Каким-то образом оставить адреса жестко закодированными и перехватить нелегальный доступ через ошибку сегмента, найти местоположение, которое сработало, извлечь точно, где был сделан доступ, обработать и вернуть. Я не совсем уверен, как это будет работать (и даже если это возможно).

3 - Используйте какую-то эмуляцию. Это, безусловно, сработает, но лишит цели быстрой и стандартной работы на стандартном компьютере.

4 - Виртуализация Вероятно, потребуется много времени для реализации. Не совсем уверен, оправдан ли выигрыш.

Кто-нибудь имеет какие-либо идеи, если это можно сделать, не вдаваясь слишком глубоко? Может быть, есть способ манипулировать компилятором каким-либо образом, чтобы определить область памяти, для которой каждый доступ будет генерировать обратный вызов. Не совсем эксперт по x86/gcc.

Изменить: Кажется, что на самом деле это не возможно сделать независимым от платформы способом, и так как это будут только окна, я буду использовать доступный API (который, кажется, работает как ожидалось). Нашел этот Q здесь:

Доступна ли одношаговая ловушка на win 7?

Я помещу весь "смоделированный" регистровый файл на несколько страниц, буду их защищать и вызову обратный вызов, из которого я извлеку всю необходимую информацию, сделаю свои вещи и продолжу выполнение.

Спасибо всем за отклик.

3 ответа

Решение

Итак, решение (фрагмент кода) выглядит следующим образом:

Прежде всего, у меня есть переменная:

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

Во-вторых, внутри моей основной функции я делаю следующее:

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

Обработчик выглядит так:

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;
}

Это только базовый каркас для функциональности. В основном я защищаю страницу, на которой находится переменная, у меня есть несколько связанных списков, в которых я держу указатели на функцию и значения для рассматриваемого адреса. Я проверяю, что адрес, генерирующий ошибку, находится внутри моего списка, затем я запускаю обратный вызов.

При первом попадании в систему защита страницы будет отключена системой, но я могу вызвать мой обратный вызов PRE_WRITE, где я могу сохранить состояние переменной. Поскольку через EFlags выдается один шаг, за ним сразу же следует исключение из одного шага (что означает, что переменная была записана), и я могу вызвать обратный вызов WRITE. Все данные, необходимые для операции, содержатся в массиве ExceptionInformation.

Когда кто-то пытается записать в эту переменную:

*(int *)&g_test = 1;

Будет активирован PRE_WRITE, за которым следует WRITE,

Когда я делаю:

int x = *(int *)&g_test;

ЧИТАТЬ будет выпущен.

Таким образом, я могу манипулировать потоком данных таким образом, чтобы не требовалось модифицировать исходный исходный код. Примечание: это предназначено для использования в качестве части тестовой структуры, и любой штрафной удар считается приемлемым.

Например, операция W1C (запись 1 для очистки) может быть выполнена:

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;    
    }
}

Это было возможно и с ошибками сегмента на недопустимых адресах, но мне приходилось выдавать по одному для каждого R/W и отслеживать "виртуальный регистровый файл", чтобы получить больший штрафной удар. Таким образом, я могу охранять только определенные области памяти или нет, в зависимости от зарегистрированных мониторов.

Я думаю, что №2 - лучший подход. Я обычно использую подход № 4, но использую его для тестирования кода, работающего в ядре, поэтому мне нужен слой ниже ядра, чтобы перехватывать и эмулировать доступы. Поскольку вы уже поместили свой код в приложение пользовательского режима, #2 должен быть проще.

Ответы на этот вопрос могут помочь в реализации #2. Как написать обработчик сигнала для отлова SIGSEGV?

Однако вы действительно хотите эмулировать доступ к памяти, а затем обработчик segv возвращается к инструкции после доступа. Этот пример кода работает в Linux. Однако я не уверен, что поведение, которым он пользуется, не определено.

#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;
}

Код для чтения регистра написан на ассемблере, чтобы гарантировать, что регистр назначения и длина инструкции известны.

Вот как может выглядеть версия prl для Windows:

#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;
}
Другие вопросы по тегам