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

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

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

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

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

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

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

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

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

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

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

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

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

Установлена ​​ли одноступенчатая ловушка на win 7?

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

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

Ответы [ 3 ]

0 голосов
/ 27 февраля 2019

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

#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;
}
0 голосов
/ 28 февраля 2019

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

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

__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 и отслеживать «виртуальный регистровый файл», чтобы получить больший штрафной удар.Таким образом, я могу охранять только определенные области памяти или нет, в зависимости от зарегистрированных мониторов.

0 голосов
/ 27 февраля 2019

Я думаю, что # 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;
}

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

...