Schreibgeschützter Zeigertyp

Lesezeit: 11 Minuten

Ich schreibe Software für ein eingebettetes System.

Wir verwenden Zeiger, um auf Register eines FPGA-Geräts zuzugreifen.
Einige der Register sind schreibgeschützt, während andere nur schreibbar sind.

Die Nur-Schreib-Register erzeugen beim Lesen undefinierte Werte.

Ich möchte einen Zeigertyp definieren, der es dem Compiler ermöglicht, beim Lesen von Werten aus einem Nur-Schreiben-Register (auch bekannt als Dereferenzierung) zu erkennen.

Kann ein Nur-Schreiben-Zeiger nur mit C-Syntax erstellt werden?
(Wir entwickeln den ersten Prototypen mit C, wechseln aber in der 2. Generation zu C++.)

Wie kann in C++ ein effizienter Nur-Schreib-Zeiger erstellt werden? (Denken Sie daran, dass dies keine Elemente im dynamischen Speicher verfolgt, sondern auf Hardwareadressen zugreift.)

Dieser Code wird auf einem eingebetteten System verwendet, bei dem Sicherheit und Qualität höchste Priorität haben.

  • Ich bezweifle ernsthaft, dass Sie es in C tun können. In C++ hingegen sollten Sie es relativ einfach tun können.

    – Sergej Kalinitschenko

    17. April 2013 um 0:27 Uhr

  • @Alexey Nicht so. Schauen Sie sich Jerrys Antwort an.

    – Konrad Rudolf

    17. April 2013 um 0:41 Uhr

  • @dasblinkenlight Nun, in C könntest du sie hinter einem verstecken undurchsichtiger Zeiger mit wohldefinierten Lese-/Schreibfunktionen.

    – Konrad Rudolf

    17. April 2013 um 0:42 Uhr

  • @KonradRudolph Wie gesagt, das musst du schreiben und dich daran halten. Das ist Disziplin.

    – Alexey Frunze

    17. April 2013 um 0:44 Uhr

  • @Alexey Ich verstehe deinen Standpunkt nicht. Es ist genauso viel Disziplin wie der Konsum int Anstatt von std::string: keine, wirklich. Sobald Sie Ihre Variablen mit dem entsprechenden Typ deklariert haben, stellt der Compiler sicher, dass Sie sie nicht falsch verwenden.

    – Konrad Rudolf

    17. April 2013 um 1:17 Uhr

Benutzeravatar von Jerry Coffin
Jerry Sarg

Ich würde wahrscheinlich für jeden eine kleine Wrapper-Klasse schreiben:

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

Zumindest vorausgesetzt, Ihr System verfügt über einen vernünftigen Compiler, würde ich erwarten, dass beide optimiert werden, sodass der generierte Code nicht von der Verwendung eines Rohzeigers zu unterscheiden ist. Verwendungszweck:

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

y = x + 1;         // No problem

x = y;             // won't compile

  • Schöne Vorlagen! Wie kombinieren Sie sie für Read-Write-Register?

    – Giacomo Tesio

    17. April 2013 um 6:38 Uhr

  • Warum haben Sie sich für die Verwendung entschieden? int und nicht volatile T*? Wenn Sie tatsächlich eine ganze Zahl wollen, gibt es sie intptr_t. Ebenfalls, operator T() könnte sein const volatile.

    – Jon Purdy

    17. April 2013 um 7:47 Uhr


  • @ JonPurdy: Ich habe es vermieden T volatile* weil das bedeuten würde, dass der Benutzer einen Lese-/Schreibzeiger auf das Register hätte – genau das, was wir vermeiden wollten. Eingebettete Compiler sind oft etwas … eingeschränkt, also erwarten Sie, dass sie enthalten sind intptr_t (gerade in C++11 hinzugefügt) verlangt viel. Wenn überhaupt, würde ich es zu einem Vorlagenparameter machen. Ich stimme ungefähr zu const volatile — gerade bearbeitet; Danke.

    – Jerry Sarg

    17. April 2013 um 13:30 Uhr


  • +1. Da Nur-Schreiben schwer zu debuggen ist, bietet Ihnen das Isolieren der Schreibvorgänge in einer Funktion einen guten Ort, um Protokollierung hinzuzufügen. Ich bin immer noch besorgt über die unbekümmerte Verwendung von int für eine Adresse. Das letzte eingebettete System, an dem ich gearbeitet habe, hatte 16-Bit-Ints, aber 32-Bit-Adressen. Ich würde eine Typdefinition für eine Adresse erstellen und diese verwenden, sodass es einfach ist, den Code für andere Systeme wiederzuverwenden.

    – Adrian McCarthy

    17. April 2013 um 16:01 Uhr

  • @AdrianMcCarthy: Ein Typedef könnte auch funktionieren, aber wie ich oben sagte, würde ich wahrscheinlich nur einen Template-Parameter verwenden, wenn ich es von int ändern würde. Zum Testen können Sie auch eine Klasse erstellen, die den zuletzt geschriebenen Wert speichert (und den Abruf ermöglicht), sodass der Rest des Codes ihn wie einen Lese-/Schreibspeicher behandeln kann.

    – Jerry Sarg

    17. April 2013 um 16:43 Uhr


Ich habe mit viel Hardware gearbeitet, und einige davon haben “Nur-Lesen”- oder “Nur-Schreiben”-Register (oder unterschiedliche Funktionen, je nachdem, ob Sie das Register lesen oder in das Register schreiben, was Spaß macht, wenn sich jemand dafür entscheidet ” reg |= 4;” Anstatt sich den Wert zu merken, den es haben sollte, setzen Sie Bit 2 und schreiben Sie den neuen Wert, wie Sie es sollten. Nichts geht über den Versuch, Hardware zu debuggen, bei der zufällige Bits in Registern erscheinen und verschwinden, die Sie nicht lesen können! ; ) Ich habe bisher keine Versuche gesehen, Lesevorgänge aus einem Nur-Schreib-Register oder Schreibvorgänge in Nur-Lese-Register tatsächlich zu blockieren.

Übrigens, habe ich gesagt, dass es eine WIRKLICH schlechte Idee ist, Register zu haben, die “nur schreiben” sind, weil Sie nicht zurücklesen können, um zu überprüfen, ob die Software das Register richtig eingestellt hat, was das Debuggen wirklich schwierig macht – und Leute, die Treiber schreiben mögen es nicht, schwierige Probleme zu debuggen, die durch zwei Zeilen VHDL- oder Verilog-Code wirklich einfach gemacht werden könnten.

Wenn Sie eine gewisse Kontrolle über das Registerlayout haben, würde ich vorschlagen, dass Sie “schreibgeschützte” Register an einer 4-KB-ausgerichteten Adresse und “schreibgeschützte” Register an einer anderen 4-KB-ausgerichteten Adresse platzieren [more than 4KB is fine]. Dann können Sie den Speichercontroller der Hardware programmieren, um den Zugriff zu verhindern.

Oder lassen Sie die Hardware einen Interrupt erzeugen, wenn Register gelesen werden, die nicht gelesen werden sollen, oder Register geschrieben werden, die nicht geschrieben werden sollen. Ich nehme an, die Hardware erzeugt Interrupts für andere Zwecke?

Die anderen Vorschläge, die mit verschiedenen C++-Lösungen gemacht wurden, sind in Ordnung, aber es hält jemanden nicht wirklich davon ab, die Register direkt zu verwenden, also wenn es wirklich ein Sicherheitsproblem ist (und nicht “lass es uns umständlich machen”), dann solltest du es haben Hardware zum Schutz vor Missbrauch der Hardware.

  • Dies ist ein guter Punkt, an dem es zutrifft, aber das Zurücklesen von Werten ist konzeptionell nicht immer sinnvoll, wie z. B. ein Register, das jedes Mal, wenn Sie darauf schreiben, an ein Fifo angehängt wird.

    – Owen

    23. April 2013 um 7:47 Uhr

  • @Owen: Es ist immer noch nützlich, zurücklesen zu können, “was ich zuletzt in dieses Register geschrieben habe”. Aber ja, ich stimme zu, es gibt einige Register, wo das nicht viel Sinn macht.

    – Mats Petersson

    23. April 2013 um 8:21 Uhr

  • Schreibgeschützte Register zu haben, ist eine vollkommen gute Idee, wenn Registerschreibvorgänge ausgelöst werden Aktionen. Obwohl es hilfreich sein kann, schreibgeschützte Register zu haben, die dann den Status dieser Aktionen melden, und solche Register möglicherweise sogar eine Adresse mit den schreibgeschützten Registern teilen, hätte die Adresse nicht wirklich ein Lese-Schreib-Register, sondern eher ein Nur-Lese-Register und ein separates Nur-Schreiben-Register.

    – Superkatze

    22. September 2016 um 0:18 Uhr

Benutzeravatar von Giacomo Tesio
Giacomo Tesio

Ich würde eine Kombination von Strukturen verwenden, um das Register darzustellen, und ein Paar von Funktionen, um sie zu handhaben.

In einem fpga_register.h du hättest sowas wie

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

mit READ und WRITE in xor, um Berechtigungen auszudrücken.

Als im fpga_register.c Sie würden eine neue Struktur definieren

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

sodass Sie einen Zeiger darauf statt eines Zeigers auf zurückgeben FPGARegister an fpga_init.

Dann weiter fpga_read und fpga_write Sie überprüfen die Berechtigungen und

  • Wenn die Operation erlaubt ist, werfen Sie die zurück FPGARegister vom Argument zu a FPGARegisterRealführen Sie die gewünschte Aktion aus (setzen oder lesen Sie den Wert) und geben Sie einen Erfolgscode zurück
  • Wenn die Operation nicht zulässig ist, geben Sie einfach einen Fehlercode zurück

Auf diese Weise kann niemand einschließlich der Header-Datei auf die zugreifen FPGARegisterReal Struktur und hat somit keinen direkten Zugriff auf die Registeradresse. Offensichtlich könnte man es hacken, aber ich bin mir ziemlich sicher, dass solche absichtlichen Hacks nicht Ihr eigentliches Anliegen sind.

In C können Sie Zeiger auf unvollständige Typen verwenden, um jegliche Dereferenzierung zu verhindern:


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

Nur writeonly.coder allgemein jeder Code, der eine Definition hat struct writeonly, kann den Zeiger dereferenzieren. Dieser Code kann natürlich versehentlich auch den Wert lesen, aber zumindest wird der gesamte andere Code daran gehindert, die Zeiger insgesamt zu dereferenzieren, während er in der Lage ist, diese Zeiger herumzureichen und sie in Variablen, Arrays und Strukturen zu speichern.

writeonly.[ch] könnte eine Funktion zum Schreiben eines Werts bereitstellen.

Benutzeravatar von Martin Svanberg
Martin Schwanberg

Ich sehe keinen eleganten Weg, dies in C zu tun. Ich sehe jedoch einen Weg, es zu tun:

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

Dies hat den Vorteil, dass Sie eine statische Fehlerprüfung erhalten. Wenn Sie diese Methode verwenden, müssen Sie natürlich alle Ihre Zeigerdeklarationen ändern, um Makros zu verwenden, was wahrscheinlich nicht viel Spaß macht.

Bearbeiten:
Wie in den Kommentaren beschrieben.

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

  • Hoppla, das ist richtig. Ich sah seine Erwähnung der Erkennung von Dereferenzierung und kam mir irgendwie zuvor.

    – Martin Swanberg

    17. April 2013 um 1:24 Uhr

  • Das gleiche Prinzip könnte jedoch verwendet werden, um Makros zum Lesen und Setzen des Werts hinter dem Zeiger zu definieren.

    – Martin Swanberg

    17. April 2013 um 1:26 Uhr

  • @MartinSvanberg Trotzdem würde ich es gerne ein bisschen besser verstehen … können Sie erklären, wie es funktionieren sollte?

    – Giacomo Tesio

    17. April 2013 um 1:26 Uhr

  • Wenn Sie einen Zeiger haben x definiert als ein Zeiger, der dereferenziert werden kann (DEREF_PTR(int*, x)) definiert der Präprozessor auch einen Typ namens hinter den Kulissen x_DEREF_PTR. Bei einem Anruf zu DEREFERENCE gemacht wird, instanziiert es eine Variable dieses Typs in einem separaten Gültigkeitsbereich. Für mit definierte Zeiger NO_DEREF_PTRexistiert dieser Typ nicht, und daher wird ein Fehler ausgegeben.

    – Martin Swanberg

    17. April 2013 um 1:29 Uhr

  • @MartinSvanberg erlaubt in der aktualisierten Version keine Nur-Lese-Zeiger, oder?

    – Giacomo Tesio

    17. April 2013 um 6:32 Uhr

Dan Saks hat eine Youtube-Präsentation, die ich nicht finden konnte, wo er Nur-Schreib-Register für eingebettete Geräte vorstellt.

Glücklicherweise hat er hier einen Artikel geschrieben, der einfacher zu durchsuchen war: https://www.embedded.com/how-to-enforce-write-only-access/

Dies ist der Code aus dem Artikel, aktualisiert für 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;
};

Ich glaube nicht, dass Sie einen speziellen Zeigertyp benötigen, wenn Sie dies verwenden, da Nur-Schreiben eine Eigenschaft des Werts ist, aber ich kann mir einen synthetischen Zeigertyp vorstellen, der den Werttyp überspringt. Wahrscheinlich müssen Sie einen Nur-Schreiben-Referenztyp einführen und so weiter.

  • Hoppla, das ist richtig. Ich sah seine Erwähnung der Erkennung von Dereferenzierung und kam mir irgendwie zuvor.

    – Martin Swanberg

    17. April 2013 um 1:24 Uhr

  • Das gleiche Prinzip könnte jedoch verwendet werden, um Makros zum Lesen und Setzen des Werts hinter dem Zeiger zu definieren.

    – Martin Swanberg

    17. April 2013 um 1:26 Uhr

  • @MartinSvanberg Trotzdem würde ich es gerne ein bisschen besser verstehen … können Sie erklären, wie es funktionieren sollte?

    – Giacomo Tesio

    17. April 2013 um 1:26 Uhr

  • Wenn Sie einen Zeiger haben x definiert als ein Zeiger, der dereferenziert werden kann (DEREF_PTR(int*, x)) definiert der Präprozessor auch einen Typ namens hinter den Kulissen x_DEREF_PTR. Bei einem Anruf zu DEREFERENCE gemacht wird, instanziiert es eine Variable dieses Typs in einem separaten Gültigkeitsbereich. Für mit definierte Zeiger NO_DEREF_PTRexistiert dieser Typ nicht, und daher wird ein Fehler ausgegeben.

    – Martin Swanberg

    17. April 2013 um 1:29 Uhr

  • @MartinSvanberg erlaubt in der aktualisierten Version keine Nur-Lese-Zeiger, oder?

    – Giacomo Tesio

    17. April 2013 um 6:32 Uhr

1410810cookie-checkSchreibgeschützter Zeigertyp

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy