所以我的问题听起来像这样。
我有一些平台相关的代码(嵌入式系统),可以写入在特定地址进行硬编码的MMIO位置。
我使用标准可执行文件(主要用于测试)和模拟(因为在实际的硬件平台中查找基本错误所需的时间更长)中的一些管理代码来编译此代码。
为减轻硬编码的指针,我只是将它们重新定义为内存池中的一些变量。而且效果很好。
问题在于某些MMIO位置(例如w1c)上存在特定的硬件行为,这使得“正确”测试变得几乎不可能。
这些是我想到的解决方案:
1-以某种方式重新定义对这些寄存器的访问,并尝试插入一些立即函数来模拟动态行为。确实不可用,因为有多种方法可以写入MMIO位置(指针和填充物)。
2-以某种方式保留地址的硬编码,并通过段错误捕获非法访问,找到触发的位置,准确提取访问的位置,进行处理并返回。我真的不确定这将如何工作(即使可能)。
3-使用某种仿真。这肯定会起作用,但是将使在标准计算机上快速本机运行的全部目的无效。
4-虚拟化?可能需要花费很多时间来实施。不确定如何获得收益。
有人能在不深入的情况下实现这一目标吗?也许有一种方法可以某种方式操纵编译器来定义一个内存区域,每次访问都会为该内存区域生成一个回调。并不是x86 / gcc方面的专家。
编辑:似乎不太可能以独立于平台的方式执行此操作,并且由于它仅是Windows,因此我将使用可用的API(似乎按预期工作)。在这里找到这个问题:
Is set single step trap available on win 7?
我将整个“模拟”寄存器文件放在多个页面中,加以保护,并触发一个回调,从该回调中我将提取所有必要的信息,执行我的工作,然后继续执行。
谢谢大家的回复。
答案 0 :(得分:3)
我认为#2是最好的方法。我通常使用方法4,但是我使用它来测试内核中运行的代码,因此我需要在内核下一层来捕获和模拟访问。由于您已经将代码放入用户模式应用程序中,因此#2应该更简单。
此问题的答案可能有助于实施#2。 How to write a signal handler to catch 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;
}
用于读取寄存器的代码以汇编方式编写,以确保目标寄存器和指令的长度均已知。
答案 1 :(得分:2)
这是Windows版本prl's answer的样子:
#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;
}
答案 2 :(得分:0)
因此,解决方案(代码段)如下:
首先,我有一个变量:
__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;
将发出READ。
通过这种方式,我可以以不需要修改原始源代码的方式来操纵数据流。 注意:这旨在用作测试框架的一部分,并且任何罚金都可以接受。
例如,可以完成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发出一个,并跟踪“虚拟寄存器文件”,因此会受到更大的损失。这样,根据注册的监视器,我只能保护特定的内存区域或不保护任何区域。