首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C/C++中内存IO的硬件仿真

C/C++中内存IO的硬件仿真
EN

Stack Overflow用户
提问于 2015-08-28 07:57:55
回答 2查看 1.1K关注 0票数 2

好吧,关于什么和为什么的背景?

我想在桌面linux上编译和运行微控制器固件(裸金属,没有操作系统)。我不想编写字节码解释器或二进制翻译程序;我想编译原始源代码。将FW作为标准GUI应用程序运行具有许多优点,如快速开发迭代、高级调试、自动化测试、压力测试等。我以前用AVR微控制器做过一些项目,通常采取以下步骤:

  • 提供桌面上不存在的与HW相关的标头(主要是MMIO注册定义->全局变量)
  • 实现外围仿真代码(lcd,eeprom)
  • 做一些GUI来反映原始设备的用户界面(lcd,按钮)
  • 把所有东西粘合在一起

前三个步骤很简单(AVR的代码不多),最后一个步骤很棘手。FW中的一些构造在桌面版本中以无限循环的形式结束(例如。等待外围寄存器更改的繁忙循环,或中断处理程序对内存的更改),其他的都没有op (在实际系统上写入MMIO触发某些东西),并且将FW的主循环与GUI库的主循环融合也需要一些创造性。如果FW是很好的分层,那么低级别的代码可以用胶水函数代替,而不需要过多的黑客攻击。

虽然总体行为受到这些更改的影响,但我发现最终结果在许多情况下非常有用。不幸的是,这种方法是侵入性的(FW修改),而胶逻辑高度依赖于FW的体系结构(每次都需要重新设计)。

越来越接近这个问题了.

从C/C++的角度来看,FW与运行在适当操作系统上的代码之间最重要的区别是MMIO。MMIO访问具有副作用,对于读写有不同的副作用。在桌面应用程序中,这个概念是不存在的(除非您从用户空间中插入HW )。如果在读取或写入内存位置时可以定义钩子,那么就可以进行适当的外围仿真,并且FW基本上可以完整地编译。当然,这不能在C++中完成,本机语言的全部目的就是反对这一点。但是,内存调试器在工具的帮助下使用相同的概念(跟踪内存访问运行时)。

我有一些关于实现的想法,所以我的问题是,你认为它们有多可行,或者有没有其他方法来达到相同的结果?

  1. 根本没有乐器。如果访问了一个内存位置,并且调试器使用它来实现监视点(内存访问中断),x86可以发出信号。作为概念的证明,我创建了这个测试程序: #包括易失性int UDR;void (){printf(“UDR=1\n”);} volatile (){printf(“UDR=1\n”);} int main() { UDR=1;printf("%i\n",UDR);返回0}; UDR是我想要跟踪的MMIO寄存器,如果我使用以下脚本运行GDB下的编译程序: 监视UDR命令调用watch () cont end rwatch UDR命令调用read() cont end 结果正是我想要的: UDR写UDR读1 问题是,我不知道这是否可以扩展。据我所知,监视点是有限的HW资源,但无法在x86上找到限制。我可能需要不到100英镑。GDB还支持软件监视点,但只支持编写,因此它并不能真正用于此目的。代码只能在GDB会话下运行的另一个缺点。
  2. 运行时工具。如果我是正确的,Val差伦/libvex会这样做:读取已编译的二进制文件,并在内存访问位置(以及其他许多地方)插入插装代码。我可以用上面的GDB脚本来编写配置了地址和回调的新的Val砂矿工具,并执行应用程序的Val差事会话。你觉得这可行吗?我发现了一些关于新工具创建的文档,但这似乎并不容易。
  3. 编译时间仪表。内存和地址消毒液的clang和gcc都是这样工作的。这是一个由2部分组成的游戏,编译器会发出仪器化代码,并将一个清除器库(实现实际的检查)链接到应用程序。我的想法是用执行上述回调的自己的实现替换清除器库,而不执行任何编译器修改(这可能超出了我的能力)。不幸的是,我没有找到多少关于仪器化代码和消毒程序库是如何交互的文档,我只找到了描述检查算法的文章。

所以这就是我的问题,任何关于任何话题的评论都是非常感谢的。:)

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-08-28 08:49:20

我没有时间回答你问题中的所有问题,但这可能太长了,不能作评论.

因此,关于调试器中的“监视点”,它们使用调试寄存器,虽然您可以自己编写代码来使用这些寄存器(有API函数可以这样做--您需要处于内核模式才能写入这些寄存器),当您声明自己时,就会耗尽寄存器。这个数字也远低于你的100。在x86处理器中,有4个调试位置寄存器,用于读取和/或写入一个1-8字节宽的位置。因此,如果IO空间总共少于32字节(每个IO空间分布在不超过4个块,不超过8个字节),它就能工作。

选项2有一个问题,您需要确保IO寄存器使用的区域不会用于应用程序中的其他内容。如果所有IO寄存器都在前64 in中,这可能是“容易的”。否则,您必须尝试找出它是MMIO访问还是常规访问。除了写你自己版本的“瓦兰”之外,你也不能马上完成.即使你一开始就雇了一个写了英勇的人.

在匹配地址方面,备选方案3与备选案文2有同样的问题。我的感觉是,这对你没有多大帮助,你最好以另一种方式来对待它。

我在各种芯片模拟器中看到的方法是将对实际硬件的访问修改为函数调用。您可以通过类似于C++描述的方法在MSalters中这样做。

或者修改您的代码,这样您就可以:

代码语言:javascript
复制
MMIO_WRITE(UDR, 1);

然后让MMIO_WRITE转换为:

代码语言:javascript
复制
 #if REAL_HW
 MMIO_WRITE(x, y)   x = y
 #else
 MMIO_WRITE(x, y)  do_mmio_write(x, y)
 #endif

在其中,do_mmio_write能够以某种方式理解地址和他们所做的事情。

这就是我在工作中使用的GPU模型,我们即将制造的最新和最伟大的GPU,也是我之前为之工作的公司所使用的模型,就是这样一个模型。

是的,你必须重写你的一些代码--理想情况下,你的代码是这样写的,这样你就有了特定的小部分代码,可以接触到实际的硬件--如果你想从一种类型的微控制器转移到另一种类型的话,这当然是一个很好的实践,因为在这种情况下,你也需要做更多的重写。

正如马丁·詹姆斯( Martin James )所指出的,任何这样的模拟都存在这样的问题:如果你的实际模拟器不太好,你就会遇到“兼容性问题”--尤其是硬件和软件的竞争条件,在这种情况下,你的软件与模拟的硬件模型完全同步,但真正的硬件会异步地对软件做一些事情,所以你的两个寄存器的读取值现在将与软件模型不同,因为软件模型没有考虑到实际硬件中的某些任意变化--而现在,你有了其中一个错误,这种错误只发生了一次。而且只有在“不能调试”的硬件变体上,才不会在软件模型中。

票数 1
EN

Stack Overflow用户

发布于 2015-08-30 16:05:55

看看MSalters的评论和Mats的回答,我显然把这个话题复杂化了。由于我可以访问源代码,所以有一些语言级别的特性可以比使用工具更容易地挂钩MMIO操作。我使用一个极简系列回显示例评估了所建议的版本:

代码语言:javascript
复制
#include <avr/io.h>

void mainloop(volatile uint8_t* reg) {
    while(1) {
        loop_until_bit_is_set(UCSRA, RXC);
        uint8_t tmp = *reg;
        *reg = tmp+1;
        loop_until_bit_is_set(UCSRA,  TXC);
    }
}

int main(void) {
    UCSRB = _BV(RXEN) | _BV(TXEN);  // enable UART rx/tx
    UBRRL = 12;                     // 12: 38400 @8Mhz 0.2% error

    mainloop(&UDR);
}

它在串口上接收一个字节,并将该字节以一个字节递增。它具有常见的MMIO寄存器用例,包括将寄存器指针传递给函数。

C路

在这种情况下,所有的MMIO访问都是用宏包装的,对于生产代码来说,这些宏最终是非op的,但是在模拟中调用钩子函数。寄存器是mmio8_t,它是一种非整数类型,因此忘记放置宏将导致编译时错误。

代码语言:javascript
复制
#if 0   // this is the Production mode
#include <avr/io.h>

//MMIO macros are no op in production
#define MMIO_READ(mmio_reg) mmio_reg
#define MMIO_WRITE(mmio_reg, data) mmio_reg=data

typedef volatile uint8_t mmio8_t;
#endif

#if 1   // this is the Emulation mode
#include <stdio.h>
#include <stdint.h>
// register bit definitions for UCSRA and UCSRB skipped to shorten code sample

struct st_mmio8 {
    const char * name;
    // uint8_t value;
    // emulation hooks for the register
};
typedef const struct st_mmio8 mmio8_t;

mmio8_t UCSRA = { "UCSRA" };    //these are THE mmio registers
mmio8_t UCSRB = { "UCSRB" };
mmio8_t UBRRL = { "UBRRL" };
mmio8_t UDR = { "UDR" };

// some bit magic taken from <avr/io.h>
#define _BV(bit) (1 << (bit))
#define bit_is_set(sfr, bit) (MMIO_READ(sfr) & _BV(bit))
#define loop_until_bit_is_set(sfr, bit) do { } while (bit_is_clear(sfr, bit))

uint8_t MMIO_READ(mmio8_t addr) {
    printf("MMIO_READ id: %s\n", addr.name);
    return _BV(RXC) | _BV(TXC);
}

void MMIO_WRITE(mmio8_t addr, uint8_t val) {
    printf("MMIO_WRITE id: %s\n", addr.name);
}
#endif

void mainloop(mmio8_t* reg) {
    while(1)     {
        loop_until_bit_is_set(UCSRA, RXC);
        uint8_t tmp = MMIO_READ(*reg);
        MMIO_WRITE(*reg, tmp+1);
        loop_until_bit_is_set(UCSRA,  TXC);
    }
}

int main(void) {
    MMIO_WRITE(UCSRB, _BV(RXEN) | _BV(TXEN));  // enable UART rx/tx
    MMIO_WRITE(UBRRL, 12);                     // 12: 38400 @8Mhz 0.2% error

    mainloop(&UDR);
}

MMIO访问是正确的,但是代码的可读性降低了,特别是如果有人习惯了原来的样式。

C++样式

此选项依赖于C++运算符重载。使用类型转换赋值操作符定义mmio_t类以进行钩子写入,而要挂钩的强制转换操作符读取:

代码语言:javascript
复制
#if 0   // this is the Production mode
#include <avr/io.h>
using mmio8_t = volatile uint8_t;
#endif

#if 0   // this is the Emulation mode
#include <stdint.h>
#include <iostream>
#include <string>
// register bit definitions for UCSRA and UCSRB skipped to shorten code sample

// some bit magic taken from <avr/io.h>
#define _BV(bit) (1 << (bit))
#define bit_is_set(sfr, bit) (sfr & _BV(bit))
#define loop_until_bit_is_set(sfr, bit) do { } while (bit_is_clear(sfr, bit))

template<typename T>
class mmio_t {
    public:
    mmio_t(const std::string& regname) : regname(regname) {}

    //this is a non-chainable assignment
    void operator=(T data) {
        std::cout << "mmio_write " << regname << std::endl;
    }

    operator T() {
        std::cout << "mmio_read " << regname << std::endl;
        return _BV(TXC) | _BV(RXC);
    }
    private:
    std::string regname;
    //T value;
    //std::function hooks for emulation code
};
using mmio8_t = mmio_t<uint8_t>;

mmio8_t UCSRA("UCSRA");
mmio8_t UCSRB("UCSRB");
mmio8_t UBRRL("UBRRL");
mmio8_t UDR("UDR");

#endif

void mainloop(mmio8_t* reg) {
    while(1) {
        loop_until_bit_is_set(UCSRA, RXC);
        uint8_t tmp = *reg;
        *reg = tmp+2;
        loop_until_bit_is_set(UCSRA, TXC);
    }
}

int main(void) {
    UCSRB = _BV(RXEN) | _BV(TXEN);  // enable UART rx/tx
    UBRRL = 12;                     // 12: 38400 @8Mhz 0.2% error

    mainloop(&UDR);
}

除了引入mmio8_t类型之外,代码与原始代码相同,正确捕获的操作也是相同的。

虽然这些示例不完整,或者不可能100%正确,但它们显示了每个版本的基本特征。谢谢你所有的建议和想法!

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32266103

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档