2013-04-17 17 views
52

Sto scrivendo software per un sistema incorporato.Tipo di puntatore di sola scrittura

Utilizziamo i puntatori per accedere ai registri di un dispositivo FPGA.
Alcuni registri sono di sola lettura, mentre altri sono di sola scrittura.

I registri di sola scrittura generano valori non definiti durante la lettura.

Voglio definire un tipo di puntatore che consenta al compilatore di rilevare durante la lettura di valori da registri di sola scrittura (a.k.a. dereferencing).

È possibile creare un puntatore di sola scrittura utilizzando solo la sintassi del linguaggio C?
(Stiamo sviluppando il primo prototipo utilizzando C, ma passando a C++ su 2a generazione.)

Come può essere creato un puntatore di sola scrittura efficiente in C++? (Ricordare, non si tratta di elementi di tracciamento nella memoria dinamica, ma di accedere agli indirizzi hardware.)

Questo codice viene utilizzato su un sistema embedded in cui la sicurezza e la qualità sono i problemi più importanti.

+0

Hai anche registri di lettura-scrittura, giusto? –

+8

Dubito seriamente che lo si possa fare in C. In C++, d'altra parte, si dovrebbe essere in grado di farlo in modo relativamente semplice. – dasblinkenlight

+1

@dasblinkenlight In C e in C++ l'unica soluzione è la disciplina. Scrivere '* p = ...' o '... = * p' è sempre più breve e più allettante della scrittura e utilizza alcune macro, funzioni o classi speciali (possibilmente classi di modelli). –

risposta

61

probabilmente sarei scrivere una minuscola classe wrapper per ogni:

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

Almeno supponendo che il sistema è dotato di un compilatore ragionevole, mi aspetto sia di questi per essere ottimizzato in modo che il codice generato è indistinguibile da usando un puntatore crudo. Utilizzo:

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

y = x + 1;   // No problem 

x = y;    // won't compile 
+0

Modelli carini! Come li combinate per i registri di lettura-scrittura? –

+0

Perché hai scelto di usare 'int' e non' volatile T * '? Se in realtà vuoi un intero, c'è 'intptr_t'. Inoltre, l'operatore T() potrebbe essere 'const volatile'. –

+3

@JonPurdy: ho evitato 'T volatile *' perché ciò significherebbe che l'utente ha un puntatore di lettura/scrittura sul registro, esattamente ciò che volevamo evitare. I compilatori incorporati sono spesso un po '... limitati, quindi aspettarsi che includano 'intptr_t' (appena aggiunto in C++ 11) sta chiedendo molto. Se mai, lo farei un parametro template. Sono d'accordo su 'const volatile' - appena modificato; grazie. –

8

Vorrei utilizzare una combinazione di strutture per rappresentare il registro e una coppia di funzioni per gestirli.

In un fpga_register.h si dovrebbe avere qualcosa di simile

#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 leggere e scrivere in XOR per esprimere i permessi.

Rispetto al fpga_register.c definirebbe una nuova struct

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

modo che si restituisce un puntatore ad esso, invece di un puntatore a FPGARegister su fpga_init.

Poi, il fpga_read e fpga_write di controllare i permessi e

  • se è consentita l'operetion, gettato indietro il FPGARegister dalla argomento ad una FPGARegisterReal, eseguire l'azione desiderata (impostare o leggere il valore) e restituire un codice di successo
  • se l'operazione non è consentita, solo restituire un codice di errore

in questo modo, nessuno tra cui il file di intestazione sarà in grado di accedere al 012 Struttura, e quindi non avrà accesso diretto all'indirizzo del registro. Ovviamente, si potrebbe hackerarlo, ma sono abbastanza sicuro che tali hack intenzionali non sono le tue reali preoccupazioni.

8

Ho lavorato con un sacco di hardware, e alcuni dei quali hanno registri "sola lettura" o "sola scrittura" (o funzioni diverse a seconda se si legge o si scrive sul registro, il che rende divertenti quando qualcuno decide di fare "reg | = 4;" invece di ricordare il valore che dovrebbe avere, il bit 2 impostare e scrivere il nuovo valore, come si dovrebbe Niente come cercare di debug hardware che ha bit casuali appaiono e scompaiono dai registri si puo'. t leggere;!) non ho visto finora tutti i tentativi di bloccare effettivamente legge da un sola scrittura registro, o scrive in sola lettura registri.

A proposito, ho detto che avere registri che sono "solo scrittura" è davvero una pessima idea, perché non puoi leggere di nuovo per controllare se il software ha impostato correttamente il registro, il che rende davvero difficile il debug - e le persone che scrivono i driver non amano il debug di problemi complessi che potrebbero essere resi davvero facili da due linee di codice VHDL o Verilog.

Se si ha il controllo sul layout del registro, suggerirei di mettere registri "readonly" a un indirizzo allineato a 4KB e registri "writeonly" in un altro indirizzo allineato a 4KB [più di 4KB va bene]. Quindi è possibile programmare il controller della memoria dell'hardware per impedire l'accesso.

Oppure, lasciare che l'hardware produrre un interrupt se i registri che non dovrebbero essere letti vengono letti, o registri che non dovrebbero essere scritti sono scritti. Presumo che l'hardware produca interruzioni per altri scopi?

Gli altri suggerimenti creati utilizzando varie soluzioni C++ vanno bene, ma in realtà non fermano qualcuno che è intenzionato a utilizzare i registri direttamente, quindi se è davvero un problema di sicurezza (piuttosto che "rendiamolo imbarazzante"), allora dovresti avere l'hardware per proteggere dall'uso improprio dell'hardware.

+1

Questo è un buon punto in cui si applica, ma la lettura dei valori non ha sempre un senso concettuale, come un registro che si aggiunge a un fifo ogni volta che si scrive su di esso. – Owen

+0

@Owen: È ancora utile poter leggere "qual è stata l'ultima cosa che ho scritto su questo registro". Ma sì, sono d'accordo, ci sono alcuni registri in cui ciò non ha molto senso. –

+0

Avere registri di sola scrittura è un'idea perfetta se il registro scrive trigger * azioni *. Mentre può essere utile avere registri di sola lettura che riportano lo stato di quelle azioni, e tali registri potrebbero anche condividere un indirizzo con i registri di sola scrittura, l'indirizzo non avrebbe davvero un registro di lettura-scrittura, ma piuttosto un registro di sola lettura e un registro di sola scrittura separato. – supercat

6

non vedo modo elegante di farlo in C. Io però vedo un modo di farlo:

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

Questo ha il vantaggio di dare il controllo degli errori statici. Naturalmente, con questo metodo, dovrete andare fuori e modificare tutte le tue dichiarazioni di puntatore di utilizzare le macro, che probabilmente non è un sacco di divertimento.

Modifica: Come descritto nei commenti.

#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

Questo non fa distinzione tra letture e scritture. –

+0

Whoops, è corretto. Ho visto la sua menzione di scoprire il dereferenziamento e una specie di anticipo su me stesso. –

+0

Tuttavia, lo stesso principio potrebbe essere utilizzato per definire macro per la lettura e l'impostazione del valore dietro il puntatore. –

7

In C, è possibile utilizzare i puntatori ai tipi incompleti per evitare che tutti dereferenziazione:


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

.210

Solo writeonly.c, o, in generale, qualsiasi codice che ha una definizione struct writeonly, può dereference il puntatore. Questo codice, naturalmente, può anche accidentalmente leggere il valore, ma almeno a tutti gli altri codici viene impedito di dereferenziare i puntatori tutti insieme, pur essendo in grado di passare quei puntatori e memorizzarli in variabili, array e strutture.

writeonly.[ch] potrebbe fornire una funzione per la scrittura di un valore.

Problemi correlati