只写指针类型

Write-Only pointer type

本文关键字:类型 指针      更新时间:2023-10-16

我正在为嵌入式系统编写软件。

我们正在使用指针访问FPGA设备的寄存器。
一些寄存器是只读的,而另一些是只写的。

只读寄存器在读取时会产生未定义的值。

我想定义一个指针类型,它将允许编译器检测何时从只写寄存器读取值(也称为解引用)。

可以只使用C语言语法创建一个写指针吗?
(我们正在使用C开发第一个原型,但在第二代中将转向c++)

如何在c++中创建一个高效的只写指针?(请记住,这不是跟踪动态内存中的项,而是访问硬件地址。)

此代码用于安全性和质量最受关注的嵌入式系统。

我可能会为每个类写一个很小的包装器类:

template <class T>
class read_only {
    T volatile *addr;
public:
    read_only(int address) : addr((T *)address) {}
    operator T() volatile const { return *addr; }
};
template <class T>
class write_only { 
    T volatile *addr;
public:
    write_only(int address) : addr ((T *)address) {}
    // chaining not allowed since it's write only.
    void operator=(T const &t) volatile { *addr = t; } 
};

至少假设您的系统有一个合理的编译器,我希望这两个都经过优化,以便生成的代码与使用原始指针无法区分。用法:

read_only<unsigned char> x(0x1234);
write_only<unsigned char> y(0x1235);
y = x + 1;         // No problem
x = y;             // won't compile

我用过很多硬件,其中一些有"只读"或"只写"寄存器(或不同的函数取决于你是否读或写寄存器,当有人决定做"reg |= 4;"而不是记住它应该有的值时,设置位2并写入新值,就像你应该的那样。没有什么比调试硬件有随机位出现和消失从寄存器你不能读取!,)到目前为止,我还没有看到任何实际阻止从只读寄存器读取或向只读寄存器写入的尝试。

顺便说一下,我说过有"只写"的寄存器是一个非常坏的主意,因为你不能回读检查软件是否正确设置了寄存器,这使得调试非常困难-编写驱动程序的人不喜欢调试困难的问题,这些问题可以通过两行VHDL或Verilog代码变得非常容易。

如果你对寄存器布局有一些控制,我建议你把"只读"寄存器放在一个4KB对齐的地址,而"只写"寄存器放在另一个4KB对齐的地址[超过4KB是好的]。然后可以对硬件的内存控制器进行编程,以防止访问。

或者,如果不应该被读的寄存器正在被读,或者不应该被写的寄存器正在被写,让硬件产生一个中断。我想硬件是否会产生用于其他目的的中断?

使用各种c++解决方案提出的其他建议是好的,但它并不能真正阻止那些打算直接使用寄存器的人,所以如果它真的是一个安全问题(而不是"让它变得尴尬"),那么你应该有硬件来防止误用硬件。

我将使用结构体组合来表示寄存器,并使用一对函数来处理它们。

fpga_register.h中,你会有像

这样的东西
#define FPGA_READ = 1; 
#define FPGA_WRITE = 2;
typedef struct register_t {
    char permissions;
} FPGARegister;
FPGARegister* fpga_init(void* address, char permissions);
int fpga_write(FPGARegister* register, void* value);
int fpga_read(FPGARegister* register, void* value);

用READ和WRITE表示权限。

然后在fpga_register.c中定义一个新的结构体

typedef struct register_t2 {
    char permissions;
    void * address;
} FPGARegisterReal;

使您返回指向它的指针,而不是指向fpga_init上的FPGARegister的指针。

然后,在fpga_readfpga_write上检查权限和
  • 如果操作被允许,将FPGARegister从参数转换回FPGARegisterReal,执行所需的操作(设置或读取值)并返回成功代码
  • 如果不允许操作,则返回错误代码

这样,包括头文件在内的任何人都不能访问FPGARegisterReal结构体,因此它不能直接访问寄存器地址。显然,有人可以破解它,但我很确定这种故意的破解并不是您真正关心的。

在C语言中,可以使用指向不完全类型的指针来防止所有解引用:


/* writeonly.h */
typedef struct writeonly *wo_ptr_t;

/* writeonly.c */
#include "writeonly.h"
struct writeonly {
  int value 
};
/*...*/
   FOO_REGISTER->value = 42;

/* someother.c */
#include "writeonly.h"
/*...*/
   int x = FOO_REGISTER->value; /* error: deref'ing pointer to incomplete type */

只有writeonly.c,或者通常任何具有struct writeonly定义的代码,可以解引用指针。当然,这些代码也可能意外地读取该值,但至少所有其他代码都被阻止一起解引用指针,同时能够传递这些指针并将它们存储在变量、数组和结构体中。

writeonly.[ch]可以提供一个写值的函数。

我看不出在c语言中有什么优雅的做法,但是我看到了一个做法:

#define DEREF_PTR(type, ptr) type ptr; 
typedef char ptr ## _DEREF_PTR;
#define NO_DEREF_PTR(type, ptr) type ptr; 
#define DEREFERENCE(ptr) 
*ptr; 
{ptr ## _DEREF_PTR 
attempt_to_dereference_pointer_ ## ptr;}
int main(int argc, char *argv[]) {
    DEREF_PTR(int*, x)
    NO_DEREF_PTR(int*, y);
    DEREFERENCE(x);
    DEREFERENCE(y); // will throw an error
}

这样做的好处是可以进行静态错误检查。当然,使用这种方法,您必须修改所有指针声明以使用宏,这可能不是很有趣。

编辑:

如评论所述。

#define READABLE_PTR(type, ptr) type ptr; 
typedef char ptr ## _READABLE_PTR;
#define NON_READABLE_PTR(type, ptr) type ptr; 
#define GET(ptr) 
*ptr; 
{ptr ## _READABLE_PTR 
attempt_to_dereference_non_readable_pointer_ ## ptr;}
#define SET(ptr, value) 
*ptr = value;

int main(int argc, char *argv[]) {
    READABLE_PTR(int*, x)
    NON_READABLE_PTR(int*, y);
    SET(x, 1);
    SET(y, 1);
    int foo = GET(x);
    int bar = GET(y); // error
}

Dan Saks有一个Youtube演示,我找不到他在哪里介绍嵌入式设备的只写寄存器。

幸运的是,他写了一篇文章,更容易搜索,在这里:https://www.embedded.com/how-to-enforce-write-only-access/

这是本文中的代码,更新为c++ 11

class write_only_T{
public:
    write_only_T(){}
    write_only_T(T const& v) : m(v){}
    write_only_T(T&& v) : m(std::move(v)){}
    write_only_T& operator=(T const& v){
        m = v;
        return *this;
    }
    write_only_T& operator=(T&& v){
        m = std::move(v);
        return *this;
    }
    write_only_T(write_only_T const&) = delete;
    write_only_T(write_only_T&&) = delete;
    write_only_T& operator=(write_only_T const&) = delete;
    write_only_T& operator=(write_only_T&&) = delete;
private:
    T m;
};

我认为如果你使用这个,你不需要一个特殊的指针类型,因为只写是值的一个属性,但我可以想象一个合成指针类型跳过值类型。您可能需要引入只写引用类型等等。