Hallo,
ich portiere gerade meinen Code für den ENC28J60 von 8-bit
(AtMegea1284p) auf 32-bit (AtSamD20E17).
Ich habe inzwischen alles zum Laufen gebracht, stehe jetzt aber vor
einem mir unerklärlichen Phänomen.
In meinem Code für den NTP-Request gibt es folgende C-Structure:
1
// the ntp time message
2
struct ntp_msg {
3
uint8_t flags;
4
uint8_t stratum;
5
uint8_t poll;
6
int8_t precision;
7
8
uint16_t root_delay[2];
9
uint16_t root_dispersion[2];
10
uint32_t reference_identifier;
11
12
// timestamps:
13
uint32_t reference_timestamp[2];
14
uint32_t originate_timestamp[2];
15
uint32_t receive_timestamp[2];
16
uint32_t transmit_timestamp[2];
17
};
Das hat alles auf dem AtMega wunderbar funktioniert. Sobald er aber mit
dem ARM auf eine beliebige Variable mit uint32_t zugreift bleibt er
hängen. So sieht es anhand von UART-Ausgaben zumindest aus.
Gibt es hier irgendetwas, was ich als ARM-Neuling noch nicht weiß? Eine
32-bit Variable sollte doch nicht zu einem solchen Fehler führen oder?
Kann mir irgendjemand hier weiterhelfen?
Wenn da noch ein Pointer Cast dabei ist: HardFault wegen nicht aligned
Zugriff. Cortex M0 kann auf Words (32 Bit) nur aligned (letzte beiden
Bits gleich Null) zugreifen.
Aha. Das klingt ja tatsächlich nach einer Arm (ist ein Cortex-m0+)
Besonderheit.
Ich greife z.B über ntp_msg->reference_identifier zu.
Hier wird ja der Pointer auf die struct dereferenziert. Wenn ich dich
richtig verstanden habe führt das bei 32bit Membern zu einem Fehler?
Wie sollte ich es dann machen??
Jim M. schrieb:> HardFault wegen nicht aligned Zugriff
Die struct ist doch aligned also durch 4 teilbar. Selbst eine
"gefährliche" Aktion wie (uint32_t*)& sollte funktionieren.
Im Zweifel kann man ja eine LED anschalten um zu sehen ob wirklich
HardFault kommt:
void HardFault_Handler(void)
{
Panic_LED();
while (1);
}
Aber wieder ein Grund mehr statt M0 besser M3 zu nehmen, ist
schliesslich nicht teurer.
Lothar schrieb:> Die struct ist doch aligned also durch 4 teilbar. Selbst eine> "gefährliche" Aktion wie (uint32_t*)& sollte funktionieren.
Nicht wenn es sich um den Inhalt eines TCP oder UDP Paketes in einem
Ethernet Frame handelt. Manche sorgen deshalb dafür, dass der Ethernet
Frame gezielt um 2 Bytes misaligned ist.
Torsten C. schrieb:> Allein schon wegen der Endianess sind an solchen Stellen im C-Code> Byte-Arrays angebracht.
Eher nicht. Typisch ist vielmehr die Verwendung der üblichen
Socket-Funktionen ntohl/htonl etc. Und natürlich passendes Alignment der
struct im Frame.
Timo .. schrieb:> Aha. Das klingt ja tatsächlich nach einer Arm (ist ein Cortex-m0+)> Besonderheit.>> Ich greife z.B über ntp_msg->reference_identifier zu.>> Hier wird ja der Pointer auf die struct dereferenziert. Wenn ich dich> richtig verstanden habe führt das bei 32bit Membern zu einem Fehler?
Nicht grundsätzlich! Wenn wir von Variablen mit dieser Struktur reden,
die im C-Code definiert wurden, gibt es kein Problem, denn der Compiler
wird sie auch wieder 32-Bit-aligned anordnen.
Aber die andere, und hier naheliegende, Möglichkeit ist das Du so eine
Struktur vom Netzwerk empfängst und "irgendwo" in einem Buffer, - im
allgemeinsten Fall ein char array -, speicherst. Wenn keine besonderen
Maßnahmen vorgesehen sind, - und dafür spricht, dass der Code von einem
AVR portiert wird, der das Problem ja nicht hat -, dann kann es sein,
dass die Bytefolge der Struktur im Buffer nicht an einer 32-Bit-aligned
Adresse beginnt. Ein cast dieser Adresse und der Versuch dann auf die
Strukturmitglieder zuzugreifen, führt dann zu einem unaligned access.
An sich aber solltest Du versuchen den Fehler zumindest grob
einzukreisen.
Strukturen allein sind nie Fehlerursache und wir können da unmöglich
irgendwas zu sagen. Das unaligned ist ja auch nur eine Vermutung.
Ok Danke schonmal für die interessanten Antworten. Wenn ich von der
Arbeit zu Hause bin werde ich als erstes mal den HardFault_Handler
implementieren um zu sehen dass er hier wirklich abschmiert.
Melde mich dann nochmal...
A. K. schrieb:> Torsten C. schrieb:>> Allein schon wegen der Endianess sind an solchen Stellen im C-Code>> Byte-Arrays angebracht.>> Eher nicht. Typisch ist vielmehr die Verwendung der üblichen> Socket-Funktionen ntohl/htonl etc. Und natürlich passendes Alignment der> struct im Frame.
Byte-Arrays sind schon die richtige Lösung.
Der Code stammt vermutlich aus avr-uip. Der wichtige fehlende Teil ist
vermutlich:
Die struct wird zwar mit hton-ähnlichen Funktionen gefüllt, aber dann
1:1 rausgehauen :-( Letzteres ist ein Kardinalfehler. Schlimmer ist
eigentlich nur noch das Lesen von Datenpaketen direkt in eine struct.
Warum das direkte Schreiben ein Kardinalfehler ist zeigt das Beispiel
schön. Es funktioniert nicht überall, man liefert sich dem Compiler aus
und darf hoffen, dass er es hinter den Kulissen zufällig richtig macht.
Wenn man es unbedingt machen möchte sollte man unbedingt zusehen dass
für die struct Padding ausgeschaltet ist.
Jay schrieb:> Die struct wird zwar mit hton-ähnlichen Funktionen gefüllt, aber dann> 1:1 rausgehauen :-( Letzteres ist ein Kardinalfehler. Schlimmer ist> eigentlich nur noch das Lesen von Datenpaketen direkt in eine struct.
Das sehe ich anders. Wenn man jeweils 2 structs hat, eines mit
__atribute__((packed)) fürs Senden und Empfangen, und eines für die
Interne verarbeitung, macht dass die Sache massiv einfacher zu lesen.
Daniel A. schrieb:> Das sehe ich anders. Wenn man jeweils 2 structs hat, eines mit> __atribute__((packed)) fürs Senden und Empfangen, und eines für die> Interne verarbeitung, macht dass die Sache massiv einfacher zu lesen.
Bis du dann merkst, dass der gerade verwendete Compiler nichts von dem
Attribut weiß, oder wie im Fall den wir hier diskutieren, es vergessen
wurde.
Beim Empfangen gibt es noch den Extra-Spaß, dass das direkte Lesen in
eine Datenstruktur ein beliebter Ansatz für Hacker ist. Denn dabei wird
gerne viel vergessen und geschlampt. Das "massiv einfacher" führt dazu
dass viele Programmierer glauben nichts weiter als casten zu müssen.
Hingegen den Empfangspuffer als Byte-Array zu betrachten zwingt dazu
jeden einzelnen Wert "in die Hand" zu nehmen. Da ist es viel schwerer
notwendige Tests zu übersehen.
@Jay (Gast)
Casts sind keine notwendig, wenn man es richtig macht. Hier mal ein
Beispiel, wie würdest du das sicherer ohne structs machen? Die
Arraygrenzen muss man ja trozdem Prüfen:
Jay schrieb:> Es funktioniert nicht überall, man liefert sich dem Compiler aus und> darf hoffen, dass er es hinter den Kulissen zufällig richtig macht
1. Da wirkt kein Zufall mit, das ist alles aufs penibelste und haarklein
definiert in der jeweiligen ABI.
2. Wenn er zusieht dass alle struct-member ihrer jeweiligen Größe
entsprechend aligned sind und zusieht dass das für das ganze struct
ebenfalls gilt (also unbedachtes Casten von Pointern mit kleineren
Alignment auf solche mit größerem Alignment möglichst vermeiden) dann
gibts weder Padding noch Hardfaults noch sonstige Überraschungen.
3. Packed ist Pfusch und sollte um jeden Preis gemieden werden,
stattdessen Empfehlungen aus 2 anwenden.
Jay schrieb:>> Typisch ist vielmehr die Verwendung der üblichen>> Socket-Funktionen ntohl/htonl etc. Und natürlich passendes Alignment der>> struct im Frame.>> Byte-Arrays sind schon die richtige Lösung.
Kann man natürlich so machen, es geht beides. Ich habe freilich schon
genug existierenden Socket Code aus Unix&Co gesehen. Die erwähnten
Funktionen für die Byteorder gibt es darin nicht zufällig. Strukturen
für Networking sind üblicherweise so definiert, dass dies geht, ausser
eben Ethernet selbst.
Ist auch eine recht effiziente Methode, wenngleich das hier keine Rolle
spielen dürfte. ARM/PPC/MIPS Prozessoren mit Schwerpunkt im Networking
sind deshalb gerne big endian, um diese Byteorder-Funktionen zu
Leermakros zu machen.
Hallo,
also zu Jays 1. Beitrag. Ja den Code habe ich aus avr-uip übernommen.
Ich hatte aber nicht damit gerechnet dass sich hier ein solcher Fehler
beim Portieren einstellt :).
Ich werde es jetzt wohl so machen dass ich direkt aus dem Buffer
uip_appdata (hier uint8_t[300]) die Werte rauslese. Das ist zwar sehr
viel mühsamer, aber die struct zu packen scheint ja auch nicht als
astrein durchzugehen.
So wird man den Fehler wohl am sichersten umgehen können.
Danke nochmal für die Anmerkungen, man lernt nicht aus :) ...
Eine Frage hätte ich übrigens noch. Reicht es wenn ich alle structs mit
32 bit Membern umstelle oder muss ich auch alle structs mit 16 bit
Membern umstellen?
Wenn ich mich recht erinnere wird dieses casten auf struct an so einigen
Stellen verwendet im originalen uip-code.
Timo .. schrieb:> Eine Frage hätte ich übrigens noch. Reicht es wenn ich alle structs mit> 32 bit Membern umstelle oder muss ich auch alle structs mit 16 bit> Membern umstellen?
Wenn schon denn schon. Wenn du passendes Alignment der Daten nicht
sicherstellen kannst, dann müssen alle Daten größer als ein Byte
umgestellt werden. Andernfalls hängt dein Code dann doch wieder von
Annahmen über das Alignment ab und du könntest ebenso den erwähnten
Offset von 2 Bytes verwenden.
Timo .. schrieb:> Eine Frage hätte ich übrigens noch. Reicht es wenn ich alle structs mit> 32 bit Membern umstelle oder muss ich auch alle structs mit 16 bit> Membern umstellen?
Jedes Member soll an einem vielfachen seiner eigenen Größe zu liegen
kommen. Also zum Beispiel:
1
// ich habe eine Leerzeile eingefügt alle 8 Bytes zur Übersicht:
2
3
int8_t
4
int8_t
5
int16_t
6
int32_t
7
8
int32_t
9
int16_t
10
int16_t
11
12
int64_t
aber zum Beispiel NICHT
1
int8_t// ok
2
int8_t// ok
3
int32_t// falsch, offset 2 ist keine 4-er Zahl, Compiler würde zwei Bytes Padding davor einfügen
4
int16_t// wieder ok, aber das Kind ist weiter oben schon in den Brunnen gefallen
5
int64_t// wieder falsch wegen obigem Padding nun an Offset 10, ist keine 8-er-Zahl, nochmal 6 Bytes Padding :-(
und da hat imo der gcc (4.9.2) schlicht Mist gebaut, da das absolut
valides C ist.
-Wall und -Wextra schalten übrigens nicht die entsprechende Warnung
-Wcast-align an...
https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html