54 votes

Type de pointeur en écriture seule

J'écris un logiciel pour un système embarqué.

Nous utilisons des pointeurs pour accéder aux registres d'un dispositif FPGA.
Certains registres sont en lecture seule, tandis que d'autres sont en écriture seule.

Les registres en écriture seule produiront des valeurs non définies lorsqu'ils seront lus.

Je veux définir un type de pointeur qui permettra au compilateur de détecter la lecture de valeurs à partir d'un registre en écriture seule (c'est-à-dire le déréférencement).

Peut-on créer un pointeur en écriture seule en utilisant uniquement la syntaxe du langage C ?
(Nous développons le premier prototype en C, mais nous passons au C++ pour la deuxième génération).

Comment peut-on créer un pointeur en écriture seule efficace en C++ ? (Rappelez-vous, il ne s'agit pas de suivre des éléments dans la mémoire dynamique, mais d'accéder à des adresses matérielles).

Ce code est utilisé sur un système embarqué où la sécurité et la qualité sont des préoccupations majeures.

0 votes

Vous avez aussi des registres de lecture-écriture, non ?

9 votes

Je doute sérieusement que vous puissiez le faire en C. En C++, par contre, vous devriez pouvoir le faire relativement facilement.

1 votes

@dasblinkenlight En C et en C++, la seule solution est la discipline. Écriture *p = ... o ... = *p est toujours plus court et plus tentant que d'écrire et d'utiliser des macros, fonctions ou classes spéciales (éventuellement des classes de modèles).

60voto

Jerry Coffin Points 237758

J'écrirais probablement une petite classe d'enveloppe pour chacune :

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

Au moins en supposant que votre système dispose d'un compilateur raisonnable, je m'attendrais à ce que les deux soient optimisés de façon à ce que le code généré soit indiscernable de l'utilisation d'un pointeur brut. Utilisation :

read_only<unsigned char> x(0x1234);
write_only<unsigned char> y(0x1235);

y = x + 1;         // No problem

x = y;             // won't compile

0 votes

De beaux modèles ! Comment les combinez-vous pour les registres de lecture-écriture ?

0 votes

Pourquoi avez-vous choisi d'utiliser int et non volatile T* ? Si vous voulez vraiment un nombre entier, il y a intptr_t . Aussi, operator T() pourrait être const volatile .

3 votes

@JonPurdy : J'ai évité T volatile* car cela signifierait que l'utilisateur aurait un pointeur en lecture/écriture sur le registre - exactement ce que nous voulions éviter. Les compilateurs embarqués sont souvent quelque peu ... limités, donc s'attendre à ce qu'ils incluent intptr_t (qui vient d'être ajouté dans C++11) est une demande importante. Le cas échéant, j'en ferais un paramètre de modèle. Je suis d'accord sur const volatile -- juste édité ; merci.

8voto

Giacomo Tesio Points 4407

J'utiliserais une combinaison de structs pour représenter le registre et une paire de fonctions pour les gérer.

Dans un fpga_register.h vous auriez quelque chose comme

#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);

avec READ et WRITE en xor pour exprimer les permissions.

Que dans le fpga_register.c vous devez définir une nouvelle structure

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

de manière à renvoyer un pointeur sur celui-ci au lieu d'un pointeur sur FPGARegister en fpga_init .

Ensuite, le fpga_read y fpga_write vous vérifiez les permissions et

  • si l'opération est autorisée, renvoyer la FPGARegister de l'argument à un FPGARegisterReal exécuter l'action souhaitée (définir ou lire la valeur) et renvoyer un code de réussite.
  • si l'opération n'est pas autorisée, renvoyer simplement un code d'erreur

De cette façon, personne, y compris le fichier d'en-tête, ne pourra accéder à l'adresse de l'utilisateur. FPGARegisterReal et n'aura donc pas d'accès direct à l'adresse du registre. Évidemment, on pourrait le pirater, mais je suis sûr que de tels piratages intentionnels ne sont pas votre préoccupation réelle.

8voto

Mats Petersson Points 70074

J'ai travaillé avec beaucoup de matériel, et certains d'entre eux ont des registres "lecture seule" ou "écriture seule" (ou des fonctions différentes selon que vous lisez ou écrivez dans le registre, ce qui est amusant lorsque quelqu'un décide de faire "reg |= 4 ;" au lieu de se souvenir de la valeur qu'il devrait avoir, de mettre le bit 2 et d'écrire la nouvelle valeur, comme vous le devriez. Rien de tel que d'essayer de déboguer un matériel dont les bits apparaissent et disparaissent de façon aléatoire dans des registres que vous ne pouvez pas lire !) Jusqu'à présent, je n'ai pas vu de tentative de blocage des lectures d'un registre en écriture seule, ou des écritures dans des registres en lecture seule.

Au fait, ai-je dit que le fait d'avoir des registres en "écriture seule" est une VRAIE mauvaise idée, parce qu'il n'est pas possible de les relire pour vérifier si le logiciel les a correctement définis, ce qui rend le débogage vraiment difficile - et les personnes qui écrivent les pilotes n'aiment pas déboguer des problèmes difficiles qui pourraient être rendus très simples par deux lignes de code VHDL ou Verilog.

Si vous avez un certain contrôle sur la disposition des registres, je vous suggère de placer les registres "en lecture seule" à une adresse alignée sur 4KB, et les registres "en écriture seule" à une autre adresse alignée sur 4KB [plus de 4KB suffisent]. Vous pouvez ensuite programmer le contrôleur de mémoire du matériel pour empêcher l'accès.

Ou bien, laissez le matériel produire une interruption si des registres qui ne sont pas censés être lus sont lus, ou si des registres qui ne sont pas censés être écrits sont écrits. Je présume que le matériel produit des interruptions à d'autres fins ?

Les autres suggestions faites à l'aide de diverses solutions C++ sont bonnes, mais elles n'empêchent pas vraiment quelqu'un qui a l'intention d'utiliser les registres directement, donc si c'est vraiment une question de sécurité (plutôt que "rendons les choses plus difficiles"), alors vous devriez avoir du matériel pour vous protéger contre l'utilisation abusive du matériel.

1 votes

C'est un bon point lorsqu'il s'applique, mais la relecture de valeurs n'a pas toujours un sens conceptuel, comme dans le cas d'un registre qui s'ajoute à un fifo chaque fois que vous y écrivez.

0 votes

@Owen : Il est toujours utile de pouvoir relire "quelle est la dernière chose que j'ai écrite à ce registre". Mais oui, je suis d'accord, il y a des registres où cela n'a pas beaucoup de sens.

0 votes

Avoir des registres en écriture seule est une idée parfaitement acceptable si les écritures de registre déclenchent actions . Bien qu'il puisse être utile d'avoir des registres en lecture seule qui signalent ensuite l'état de ces actions, et que de tels registres puissent même partager une adresse avec les registres en écriture seule, l'adresse n'aurait pas vraiment un registre en lecture-écriture, mais plutôt un registre en lecture seule et un registre en écriture seule séparé.

7voto

Kaz Points 18072

En C, vous pouvez utiliser des pointeurs vers des types incomplets pour empêcher tout déréférencement :


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

Seulement writeonly.c ou, de manière générale, tout code qui a une définition struct writeonly peut déréférencer le pointeur. Ce code, bien sûr, peut aussi lire accidentellement la valeur, mais au moins tout autre code est empêché de déréférencer les pointeurs, tout en étant capable de faire circuler ces pointeurs et de les stocker dans des variables, des tableaux et des structures.

writeonly.[ch] pourrait fournir une fonction permettant d'écrire une valeur.

6voto

Martin Svanberg Points 134

Je ne vois pas de moyen élégant de le faire en C. Je vois cependant un moyen de le faire :

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

Cela présente l'avantage de vous offrir un contrôle statique des erreurs. Bien sûr, en utilisant cette méthode, vous devrez modifier toutes vos déclarations de pointeurs pour utiliser des macros, ce qui n'est probablement pas très amusant.

Editar: Comme décrit dans les commentaires.

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

0 votes

Oups, c'est correct. J'ai vu qu'il mentionnait la détection du déréférencement et je me suis un peu emballé.

0 votes

Cependant, le même principe pourrait être utilisé pour définir des macros pour lire et définir la valeur derrière le pointeur.

0 votes

@MartinSvanberg J'aimerais néanmoins le comprendre un peu plus... pouvez-vous m'expliquer comment il était censé fonctionner ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X