PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : SIGBUS bei SPARC64



DukePyrolator
24-12-2007, 10:35
Hallo,

ich habe hier bei einem open-source-projekt (www.denorastats.org) einen Fehler, der aber nur auf SPARC64 Systemen auftritt.



Program received signal SIGBUS, Bus error.
[Switching to Thread 16384 (LWP 27806)]
decode_ip (buf=0xb4650 "") at base64.c:531
531 ia = *(struct in_addr *) targ;


zu SIGBUS siehe auch:
http://en.wikipedia.org/wiki/SIGBUS
http://en.wikipedia.org/wiki/Bus_error


Die Funktion ist folgende:



int decode_ip(char *buf)
{
int len = strlen(buf);
char targ[25];
struct in_addr ia;

b64_decode(buf, targ, 25);
ia = *(struct in_addr *) targ; <-- hier tritt der Fehler auf


if (len == 24) { /* IPv6 (noch nicht eingebaut)*/
return 0;
} else if (len == 8) /* IPv4 */
return ia.s_addr;
else /* Error?? */
return 0;
}




das struct in_addr enthält nur s_addr als unsigned int. (das hat auf allen Systemen die gleiche Länge)

sizeof(ia) = 4
sizeof(ia.s_addr) = 4
sizeof(targ) = 25

nur die ersten 4 Bytes von targ enthalten Daten, das fünfte Byte ist NULL und die restlichen Bytes enthalten irgendwelchen Datenmüll.

Bei allen Systemen (inkl. Windows) funktioniert das einwandfrei, nur eben auf SPARC64 Systemen nicht.

Ich bekomme in den nächsten Tagen Zugriff auf einen Rechner mit SPARC64 Architektur wo ich das selber testen kann. Ich vermute mal, ich muss einfach nur targ[25] auf targ[4] ändern damit es funktioniert. (oder auch nicht).
(targ würde ich trotzdem gerne auf 25 lassen, weil ich später evtl. auch noch mit IPv6-Adressen arbeiten will.)

Was genau passiert eigentlich bei "ia = *(struct in_addr *) targ;"? Wird da der Pointer nur auf die ersten Bytes von targ gesetzt oder werden die ersten 4 Bytes von targ (null terminated string) im Speicher auf die Adresse von ia kopiert? (jaja, dämliche Formulierung, ich hoffe Ihr wisst trotzdem was ich damit meine)

Mich interessiert jetzt natürlich erstmal, wieso ausgerechnet dieses Problem nur auf SPARC64 auftritt. Was läuft denn da anders als bei "normalen" Systemen? (gibts da irgendwo ne Dokumentation im Netz?)

Gibts evtl. noch eine elegantere Lösung für dieses Problem?


Grüße
Pyro

locus vivendi
24-12-2007, 12:09
Ich kenne mich mit Sparc64 sehr schlecht aus, aber der Grund könnte möglicherweise sein das die Speicherausrichtung des Puffers "targ" nicht kompatibel ist. Du kannst ja als kleinen Test mal diesen Puffer ("char targ[25]") durch
"char* targ = malloc(sizeof(struct in_addr))" austauschen. Die Größenangabe 25 beim Aufruf von b64_decode musst du dann evtl. noch anpassen. Ich wäre übrigens an einer Rückmeldung interessiert, ob das etwas zu bringen scheint oder nicht.

DukePyrolator
25-12-2007, 08:22
Momentan hab ich grosse Probleme den Fehler zu reproduzieren:

Auf dem ersten Rechner, wo ich es versucht habe, lief Debian auf einer Ultrasparc 1. Dort konnte ich problemlos compilen, aber dann haben die mysql-bibliotheken den Dienst verweigert. (auch mit SIGBUS). (selbst der lokale mysql-client hat nicht funktioniert)

Auf dem zweiten "Versuchsrechner" lief Solaris ... nach gut einer Stunde tüfteln lief endlich ./configure ohne Fehlermeldungen durch. Dann hab ich make gestartet, mir hunderte von Fehlermeldungen und Warnungen angeschaut und dann aufgegeben....

Jetzt werd ich erstmal Kontakt zu dem Russen aufnehmen der den Fehler festgestellt hat und fragen ob ich seine Box benutzen darf ... ;)

Ich meld mich dann wieder.

jan61
31-12-2007, 12:36
Moin,

SPARC erwartet im Gegensatz zu Intel eine korrekte Ausrichtung der Speicheradressen (der gleiche Fehler dürfte auch bei 32-Bit-SPARC auftreten). Du kannst also auf SPARC-Prozessoren nicht einen char-Pointer so einfach zu einem Pointer auf eine "struct in_addr" machen, genau das versucht die fehlerhafte Zeile. Versuche mal, den Inhalt per memcpy zu transportieren (ungetestet):
memset(&ia, 0, sizeof(struct in_addr));
memcpy(&ia, targ, sizeof(struct in_addr));Jan

jan61
31-12-2007, 13:17
Ich kenne mich mit Sparc64 sehr schlecht aus, aber der Grund könnte möglicherweise sein das die Speicherausrichtung des Puffers "targ" nicht kompatibel ist. Du kannst ja als kleinen Test mal diesen Puffer ("char targ[25]") durch
"char* targ = malloc(sizeof(struct in_addr))" austauschen. Die Größenangabe 25 beim Aufruf von b64_decode musst du dann evtl. noch anpassen. Ich wäre übrigens an einer Rückmeldung interessiert, ob das etwas zu bringen scheint oder nicht.

Speicheraurichtung ist schon das richtige Stichwort, aber Deine Idee dürfte da nichts ändern. Du benutzt ja nach wie vor einen char-Pointer für targ, Ziel ist aber ein Pointer auf eine in_addr-Struktur. Das wird SPARC nicht gut finden ;-)

Jan

locus vivendi
31-12-2007, 14:14
Zitat von locus vivendi Beitrag anzeigen
Ich kenne mich mit Sparc64 sehr schlecht aus, aber der Grund könnte möglicherweise sein das die Speicherausrichtung des Puffers "targ" nicht kompatibel ist. Du kannst ja als kleinen Test mal diesen Puffer ("char targ[25]") durch
"char* targ = malloc(sizeof(struct in_addr))" austauschen. Die Größenangabe 25 beim Aufruf von b64_decode musst du dann evtl. noch anpassen. Ich wäre übrigens an einer Rückmeldung interessiert, ob das etwas zu bringen scheint oder nicht.Speicheraurichtung ist schon das richtige Stichwort, aber Deine Idee dürfte da nichts ändern. Du benutzt ja nach wie vor einen char-Pointer für targ, Ziel ist aber ein Pointer auf eine in_addr-Struktur. Das wird SPARC nicht gut finden ;-)

Ich habe dabei die Vorschrift aus dem C-Standard im Kopf, die besagt, dass ein Pointer der von malloc zurück geliefert wird, geignet ausgerichtet ist, um darüber auf jedes Objekt welches nicht größer als die Speicheranforderung ist zugreifen zu können.
Aus 7.20.3 des C-Standards:
"The order and contiguity of storage allocated by successive calls to the calloc, malloc,
and realloc functions is unspecified. The pointer returned if the allocation succeeds is
suitably aligned so that it may be assigned to a pointer to any type of object and then
used to access such an object or an array of such objects in the space allocated (until
the space is explicitly deallocated)."

(Genaugenommen habe ich die Bedingung "nicht größer als die Speicheranforderung" also hinzugedichtet, diese muss man als im Standard wohl implizit betrachten)

locus vivendi
31-12-2007, 14:23
P.S.: Ich weiß nicht ob C oder C++ gemeint. Oben habe ich mich auf C bezogen. Die C++ Regeln sind kompatibel damit.

anda_skoa
31-12-2007, 15:47
Bei so etwas casten ist immer problematisch.

Meistens ist es klüger, die Bytes des Zieltyps explizit zu schrieben, d.h. aus dem char Array byteweise rausnehmen, in eine Variable des Zieltyps zuweisen und dann mit Bitoperationen das Zielbyte setzen.

Ciao,
_

jan61
01-01-2008, 14:29
Ich habe dabei die Vorschrift aus dem C-Standard im Kopf, die besagt, dass ein Pointer der von malloc zurück geliefert wird, geignet ausgerichtet ist, um darüber auf jedes Objekt welches nicht größer als die Speicheranforderung ist zugreifen zu können....

Ja, mit der Größe des zugewiesenen Speichers hast Du schon recht, das Problem kommt aber nicht daher, sondern von der Ausrichtung der Speicheradresse (wäre die Größe des Speichers das Problem, wäre ein SEGFAULT gekommen, kein Bus Error). Ein char-Pointer kann byteweise ausgerichtet sein, ein Pointer auf einen int muss auf 4 (oder bei 64-Bit-Werten 8) Byte zeigen. Sparc-Prozessoren hängen die Adressen genauso ausgerichtet auf (eine 4-Byte-Integer-Adresse beginnt immer an einem Vielfachen von 4), damit gibt es Probleme, wenn man einen char-Pointer (der an einer beliebigen Adresse erscheinen kann) zuweist. Intel macht das AFAIK so, dass prinzipiell alle Adressen an WORD- oder DWORD-Grenzen ausgerichtet sind (mit dem daraus resultierenden Verschnitt).

Jan

EDIT: Wir hatten in der Firma exakt dieses Problem vor ein paar Wochen beim Portieren einer Win*-Software nach Solaris. Und noch was: Wenigstens arbeiten SPARC und Intel mit der gleichen Byte-Order, wenn da noch eine Konvertierung LSB <-> MSB dazukommt, dann klappt selbst das byte-weise Kopieren nicht mehr. Deshalb gibt es dafür einen eigenen Satz an Konverterfunktionen (hton[ls], ntoh[ls]).

locus vivendi
02-01-2008, 19:55
Ja, mit der Größe des zugewiesenen Speichers hast Du schon recht, das Problem kommt aber nicht daher, sondern von der Ausrichtung der Speicheradresse (wäre die Größe des Speichers das Problem, wäre ein SEGFAULT gekommen, kein Bus Error).
Aber das ist ja gerade der Inhalt des Zitates aus dem C-Standard, dass Zeiger die von malloc kommen geeignet ausgerichtet sind. Das muss auch so sein, weil malloc ja nicht weiß welches Objekt (bzw. welcher Typ von Objekt) in dem zu allozierenden Speicher gespeichert werden soll. Daher muss malloc einen Zeiger liefern, der so ausgerichtet ist, dass jeder mögliche Typ hinein passt.

jan61
03-01-2008, 19:46
Aber das ist ja gerade der Inhalt des Zitates aus dem C-Standard, dass Zeiger die von malloc kommen geeignet ausgerichtet sind. Das muss auch so sein, weil malloc ja nicht weiß welches Objekt (bzw. welcher Typ von Objekt) in dem zu allozierenden Speicher gespeichert werden soll. Daher muss malloc einen Zeiger liefern, der so ausgerichtet ist, dass jeder mögliche Typ hinein passt.

Jetzt bin ich dahinter gekommen, was Du meinst ;-) - der malloc soll es richten, dass typunabhängig eine gültige Adresse rauskommt. Ok, hört sich erstmal logisch an. Ich habe mir mal ein kleines C-Progrämmchen gebastelt, um das zu testen:

jan@jack:~/tmp> cat t_m.c
#include <stdio.h>
#include <malloc.h>

int main(int argc, char **argv) {
struct s_t {
short p2;
long p3;
char p1[243];
} t, *pt;
char *str;
char s[249];

printf("%u\n", s);
str = malloc(sizeof(struct s_t));
printf("%u\n", str);
pt = malloc(sizeof(struct s_t));
printf("%u\n", pt);
t = *(struct s_t *)pt;
t = *(struct s_t *)str;
t = *(struct s_t *)s;
return(0);
}
Läuft auf meiner Linux-Kiste sauber durch:
jan@jack:~/tmp> ./t_m
3221213648
134520840
134521096
Morgen werde ich das mal auf unserer Solaris-Möhre ausprobieren, ich melde mich dann mit dem Ergebnis - bin gespannt, ob sich Solaris an den C-Standard hält. Spätestens bei der letzten Zuweisung an t sollte eigentlich nach meinen Erfahrungen Schluss mit lustig sein, das wäre der Fall aus dem Originalposting.

Jan

jan61
07-01-2008, 19:23
Moin,

so, bin erst heute zum Testen gekommen. Das Problem ist, dass wir nur 32-Bit-Systeme haben und sowohl auf Solaris8 als auch Solaris10 keiner der Fehler auftrat (gcc 2.95 bzw. gcc 3.3). Wenn man sich die Adressen angeschaut hat, waren alle ordentlich an 4-Byte-Grenzen ausgerichtet.

Ich habe mir das Problem, das wir in der Firma hatten, nochmal näher angeschaut: So 100% identisch war das nicht, da wurde versucht, mit einem Offset von 5 zu einer per "char x[nnn]" definierten Adresse an einen uint32 zuzuweisen, was bei Intel geklappt hat, in Solaris8 nicht. Hier war also definitiv die Adresse krumm.

Fazit: Auch wenn ich das Problem nicht nachstellen konnte, sollten die 2 skizzierten Lösungsmöglichkeiten funktionieren. Entweder den Speicher per malloc() anfordern oder byteweise (z. B. mit memcpy() oder per Byte-Zuweisung und shift) den Inhalt ins Ziel kopieren.

JAn

locus vivendi
08-01-2008, 16:30
Moin,

so, bin erst heute zum Testen gekommen. Das Problem ist, dass wir nur 32-Bit-Systeme haben und sowohl auf Solaris8 als auch Solaris10 keiner der Fehler auftrat (gcc 2.95 bzw. gcc 3.3). Wenn man sich die Adressen angeschaut hat, waren alle ordentlich an 4-Byte-Grenzen ausgerichtet.
Danke das du dir die Mühe gemacht hast das zu überprüfen. Ich habe leider nur Zurgriff auf x86 bzw. x86-64 Systeme und kann deshalb in der Praxis kaum testen ob meine Annahmen bezüglich Portierbarkeit zutreffen.


Fazit: Auch wenn ich das Problem nicht nachstellen konnte, sollten die 2 skizzierten Lösungsmöglichkeiten funktionieren. Entweder den Speicher per malloc() anfordern oder byteweise (z. B. mit memcpy() oder per Byte-Zuweisung und shift) den Inhalt ins Ziel kopieren.
Ich frage mich ein bisschen, warum im Code den der Original-Poster gezeigt hat nicht gleich eine in_addr Variable angelegt wird und dann ggf. beim Aufruf von b64_decode dessen Address gecastet auf char* übergeben wird. Das wäre eigentlich von vornherein die einfachste Methode meiner Meinung nach. Vielleicht gibt es ja noch irgendeine Bedingung von der ich nichts weiß.

jan61
10-01-2008, 21:05
...Ich frage mich ein bisschen, warum im Code den der Original-Poster gezeigt hat nicht gleich eine in_addr Variable angelegt wird und dann ggf. beim Aufruf von b64_decode dessen Address gecastet auf char* übergeben wird. Das wäre eigentlich von vornherein die einfachste Methode meiner Meinung nach. Vielleicht gibt es ja noch irgendeine Bedingung von der ich nichts weiß.

Da gibts verschiedene Möglichkeiten: Von Bequemlichkeit (Programmierer sind ja von Haus aus faul - eigene Erfahrung ;-) über Unkenntnis, Performance (lies einen char*-Stream, und fülle damit möglichst schnell eine in_addr-Struktur - was Schnelleres als einfach eine Zuweisung wie "ab Adresse 0815 steht alles, was Du brauchst" ist nicht so einfach), Zeitdruck bis hin zu "andere OS als XYZ??? Gibts doch gar nicht" usw. ist alles drin.

Jan

DukePyrolator
14-10-2008, 13:30
So, nach langer langer Zeit mal wieder ne Rückmeldung von mir:

Das Problem wurde behoben. Es lag am GCC.
Mit der aktuellen Version läuft alles fehlerfrei.