Подтвердить что ты не робот

Тип указателя только для записи

Я пишу программное обеспечение для встроенной системы.

Мы используем указатели для доступа к регистрам устройства FPGA.
Некоторые регистры доступны только для чтения, а другие - только для записи.

Регистры только для записи будут выдавать значения undefined при чтении.

Я хочу определить тип указателя, который позволит компилятору обнаруживать при чтении значений из регистров только для записи (разыменования a.k.a.).

Можно ли создать указатель только для записи, используя только синтаксис языка C?
(Мы разрабатываем первый прототип с использованием C, но переходим на С++ на 2-м поколении.)

Как создать эффективный указатель только для записи в С++? (Помните, что это не отслеживание элементов в динамической памяти, а доступ к аппаратным адресам.)

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

4b9b3361

Ответ 1

Я бы, наверное, написал для каждого крошечный класс-оболочку:

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 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

Ответ 2

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

В 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 в xor для выражения разрешений.

Чем в fpga_register.c вы должны определить новую структуру

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

чтобы вы вернули указатель на него вместо указателя на FPGARegister на fpga_init.

Затем на fpga_read и fpga_write вы проверяете разрешения и

  • если разрешена операция, отбросить FPGARegister из аргумента в FPGARegisterReal, выполнить требуемое действие (установить или прочитать значение) и вернуть код успеха
  • Если операция не разрешена, просто верните код ошибки

Таким образом, никто, включая заголовочный файл, не сможет получить доступ к структуре FPGARegisterReal и, следовательно, не будет иметь прямого доступа к адресу регистра. Очевидно, можно было взломать его, но я совершенно уверен, что такие намеренные взломы не являются вашими реальными проблемами.

Ответ 3

Я работал с большим количеством аппаратного обеспечения, и некоторые из них имеют регистры "только для чтения" или "только для записи" (или разные функции в зависимости от того, читаете или записываете в регистр, что делает забаву, когда кто-то решает сделать "reg | = 4;" вместо того, чтобы помнить значение, которое оно должно иметь, установить бит 2 и написать новое значение, как и вы. Ничто не похоже на попытку отладки аппаратного обеспечения, которое имеет случайные биты, появляющиеся и исчезающие из регистров, вы не можете read!;) Я до сих пор не видел попыток фактического блокирования чтения из регистра с записью или записи в регистры только для чтения.

Кстати, я сказал, что наличие регистров, которые являются "только для записи", является ДЕЙСТВИТЕЛЬНО плохой идеей, потому что вы не можете прочитать, чтобы проверить, правильно ли установлено программное обеспечение, что делает отладку очень трудной - и люди, пишущие драйверы, не любят отлаживать сложные проблемы, которые могут быть сделаны очень легко с помощью двух строк кода VHDL или Verilog.

Если у вас есть некоторый контроль над макетом регистра, я бы посоветовал вам вводить регистры "только для чтения" с адресом с выравниванием по 4 КБ, а регистры "writeonly" регистрируются в другом адресе с адресом 4 КБ [более 4 КБ в порядке]. Затем вы можете запрограммировать контроллер памяти на оборудовании, чтобы предотвратить доступ.

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

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

Ответ 4

В 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] может предоставить функцию для записи значения.

Ответ 5

Я не вижу элегантного способа сделать это на 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
}

Это дает вам возможность проводить статическую проверку ошибок. Конечно, используя этот метод, вам придется выйти и изменить все ваши объявления указателей на использование макросов, что, вероятно, не очень весело.

Edit: Как описано в комментариях.

#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
}