好吧,关于什么和为什么的背景?
我想在桌面linux上编译和运行微控制器固件(裸金属,没有操作系统)。我不想编写字节码解释器或二进制翻译程序;我想编译原始源代码。将FW作为标准GUI应用程序运行具有许多优点,如快速开发迭代、高级调试、自动化测试、压力测试等。我以前用AVR微控制器做过一些项目,通常采取以下步骤:
前三个步骤很简单(AVR的代码不多),最后一个步骤很棘手。FW中的一些构造在桌面版本中以无限循环的形式结束(例如。等待外围寄存器更改的繁忙循环,或中断处理程序对内存的更改),其他的都没有op (在实际系统上写入MMIO触发某些东西),并且将FW的主循环与GUI库的主循环融合也需要一些创造性。如果FW是很好的分层,那么低级别的代码可以用胶水函数代替,而不需要过多的黑客攻击。
虽然总体行为受到这些更改的影响,但我发现最终结果在许多情况下非常有用。不幸的是,这种方法是侵入性的(FW修改),而胶逻辑高度依赖于FW的体系结构(每次都需要重新设计)。
越来越接近这个问题了.
从C/C++的角度来看,FW与运行在适当操作系统上的代码之间最重要的区别是MMIO。MMIO访问具有副作用,对于读写有不同的副作用。在桌面应用程序中,这个概念是不存在的(除非您从用户空间中插入HW )。如果在读取或写入内存位置时可以定义钩子,那么就可以进行适当的外围仿真,并且FW基本上可以完整地编译。当然,这不能在C++中完成,本机语言的全部目的就是反对这一点。但是,内存调试器在工具的帮助下使用相同的概念(跟踪内存访问运行时)。
我有一些关于实现的想法,所以我的问题是,你认为它们有多可行,或者有没有其他方法来达到相同的结果?
所以这就是我的问题,任何关于任何话题的评论都是非常感谢的。:)
发布于 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中这样做。
或者修改您的代码,这样您就可以:
MMIO_WRITE(UDR, 1);然后让MMIO_WRITE转换为:
#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 )所指出的,任何这样的模拟都存在这样的问题:如果你的实际模拟器不太好,你就会遇到“兼容性问题”--尤其是硬件和软件的竞争条件,在这种情况下,你的软件与模拟的硬件模型完全同步,但真正的硬件会异步地对软件做一些事情,所以你的两个寄存器的读取值现在将与软件模型不同,因为软件模型没有考虑到实际硬件中的某些任意变化--而现在,你有了其中一个错误,这种错误只发生了一次。而且只有在“不能调试”的硬件变体上,才不会在软件模型中。
发布于 2015-08-30 16:05:55
看看MSalters的评论和Mats的回答,我显然把这个话题复杂化了。由于我可以访问源代码,所以有一些语言级别的特性可以比使用工具更容易地挂钩MMIO操作。我使用一个极简系列回显示例评估了所建议的版本:
#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,它是一种非整数类型,因此忘记放置宏将导致编译时错误。
#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类以进行钩子写入,而要挂钩的强制转换操作符读取:
#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%正确,但它们显示了每个版本的基本特征。谢谢你所有的建议和想法!
https://stackoverflow.com/questions/32266103
复制相似问题