c++ - que - Tipo de puntero de solo escritura
const javascript (5)
Estoy escribiendo software para un sistema integrado.
Estamos usando punteros para acceder a los registros de un dispositivo FPGA.
Algunos de los registros son de solo lectura, mientras que otros son de solo escritura.
Los registros de solo escritura producirán valores indefinidos cuando se lean.
Quiero definir un tipo de puntero que permita al compilador detectar al leer valores de un registro de solo escritura (también conocido como desreferencia).
¿Se puede crear un puntero de solo escritura usando solo la sintaxis del lenguaje C?
(Estamos desarrollando el primer prototipo usando C, pero moviéndonos a C ++ en 2da generación).
¿Cómo se puede crear un puntero eficiente de solo escritura en C ++? (Recuerde, esto no rastrea elementos en la memoria dinámica, sino que accede a direcciones de hardware).
Este código se usa en un sistema integrado donde la seguridad y la calidad son las principales preocupaciones.
En C, puede usar punteros a tipos incompletos para evitar toda desreferenciación:
/* 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 */
Solo writeonly.c
, o en general cualquier código que tenga una struct writeonly
definición struct writeonly
, puede desreferenciar el puntero. Ese código, por supuesto, también puede leer accidentalmente el valor, pero al menos se evita que todos los demás códigos eliminen la referencia de los punteros, al tiempo que se pueden pasar esos punteros y almacenarlos en variables, matrices y estructuras.
writeonly.[ch]
podría proporcionar una función para escribir un valor.
He trabajado con un montón de hardware, y algunos de ellos tienen registros de "solo lectura" o "solo escritura" (o diferentes funciones dependiendo de si lees o escribes en el registro, lo que hace divertido cuando alguien decide hacerlo " reg | = 4; "en lugar de recordar el valor que debería tener, configure el bit 2 y escriba el nuevo valor, como debería. ¡Nada como intentar depurar hardware que tiene bits aleatorios que aparecen y desaparecen de los registros que no puede leer! Hasta ahora, no he visto ningún intento de bloquear realmente las lecturas de un registro de solo escritura, o escribe en registros de solo lectura.
Por cierto, dije que tener registros que son "solo escritura" es una muy mala idea, porque no se puede leer para verificar si el software ha configurado correctamente el registro, lo que hace que la depuración sea realmente difícil, y las personas que escriben drivers no me gusta depurar problemas difíciles que podrían hacerse realmente fáciles con dos líneas de código VHDL o Verilog.
Si tiene algún control sobre el diseño del registro, le sugiero que coloque los registros de "solo lectura" en una dirección alineada con 4KB, y los registros "solo en escritura" en otra dirección alineada con 4KB [más de 4 KB está bien]. Luego puede programar el controlador de memoria del hardware para evitar el acceso.
O bien, permita que el hardware produzca una interrupción si se leen los registros que no se deben leer, o si se escriben los registros que no deben escribirse. Supongo que el hardware produce interrupciones para otros fines.
Las otras sugerencias hechas usando varias soluciones de C ++ están bien, pero realmente no detienen a alguien que intenta usar los registros directamente, por lo que si realmente es una preocupación de seguridad (en lugar de "hagámoslo incómodo"), entonces debería tener hardware para proteger contra el mal uso del hardware.
No veo una forma elegante de hacerlo en C. Sin embargo, veo una manera de hacerlo:
#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
}
Esto tiene el beneficio de darle una comprobación de errores estáticos. Por supuesto, con este método, tendrá que salir y modificar todas las declaraciones de su puntero para usar macros, lo que probablemente no sea muy divertido.
Editar: como se describe en los comentarios.
#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
}
Probablemente escribiría una clase de contenedor pequeña para cada uno:
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; }
};
Al menos, suponiendo que tu sistema tenga un compilador razonable, espero que ambos se optimicen para que el código generado no se distinga de un puntero sin formato. Uso:
read_only<unsigned char> x(0x1234);
write_only<unsigned char> y(0x1235);
y = x + 1; // No problem
x = y; // won''t compile
Usaría una combinación de estructuras para rappresentar el registro y un par de funciones para manejarlas.
En un fpga_register.h
tendrías algo así como
#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);
con READ y WRITE en xor para expresar permisos.
Que en el fpga_register.c
definirías una nueva estructura
typedef struct register_t2 {
char permissions;
void * address;
} FPGARegisterReal;
para que devuelva un puntero a él en lugar de un puntero a FPGARegister
en fpga_init
.
Luego, en fpga_read
y fpga_write
, verifica los permisos y
- si la operación está permitida, devuelve el
FPGARegister
del argumento a unFPGARegisterReal
, ejecuta la acción deseada (establece o lee el valor) y devuelve un código de éxito - si la operación no está permitida, simplemente devuelve un código de error
De esta forma, nadie que incluya el archivo de encabezado podrá acceder a la estructura FPGARegisterReal
y, por lo tanto, no tendrá acceso directo a la dirección de registro. Obviamente, uno podría hackearlo, pero estoy bastante seguro de que tales ataques intencionales no son tus preocupaciones reales.