Wenn man sich die Geschichte der ARM-Generationen ansieht, die der Raspberry Pi verwendet hat, ist es erstaunlich, wie alt der Chip ist.
Der im Raspberry Pi B+ verwendete ARM1176-Kern stammte selbst 2014, als der B+ erschien, schon aus dem Jahr 2003 und war damit bereits 11 Jahre alt.
Daher ist es nicht ungewöhnlich, dass man beim Bauen auf anderen Plattformen wie einem neueren Raspberry Pi Architektur-Flags explizit angeben muss, um kompatiblen Code zu erzeugen.
Wenn aber selbst beim Bauen auf dem Raspberry Pi B+ die richtige Architektur nicht als Standard gesetzt wird, wirkt das eher wie ein Konfigurationsfehler in den Defaults der Distribution.
Soweit ich mich erinnere, verwendete der ursprüngliche Pi Chips, die von TV-Boxen übrig waren.
Solche Produkte packen aus Kostengründen fast nie mehr Rechenleistung hinein, als nötig ist.
Er war von Anfang an als Billigcomputer gedacht.
Tatsächlich wurde nicht auf dem B+ gebaut.
Im Artikel steht: „Ich habe das Binary von meinem Build-Host, einem viel schnelleren Pi 4B, genommen und versucht, es auf diesem alten Gerät auszuführen, woraufhin eine illegal instruction auftrat.“
Das ist ähnlich, als würde man eine mit aktuellem MSVC unter Windows 11 gebaute .EXE auf einem alten PC mit Windows XP ausführen wollen.
Vermutlich läuft auch die gesamte auf dem Pi 4B laufende Pi-Distribution nicht auf dem B+, und der Kernel könnte auf dieselbe Weise kompiliert worden sein.
Es scheint im Blogpost oder Artikel nicht klar behandelt zu werden, aber ist das nicht ein Bug?
Beim Suchen nach LLVM-Bugs habe ich einen Eintrag gefunden, der fast wie dasselbe Problem aussieht, aber der Bug ist von 2012 und geschlossen. Nach den letzten paar Kommentaren zu urteilen könnte es sein, dass er in Wirklichkeit nicht behoben wurde; ich habe aber nur grob drübergeschaut, also kann ich das auch missverstanden haben. https://github.com/llvm/llvm-project/issues/13989
Beim erneuten Lesen fiel mir auf, dass am Ende des Artikels steht, dass ein funktionierendes Programm herauskommt, wenn man das Ziel explizit übergibt. Dann wirkt es eher wie eine Art Konfigurations-Bug; unter Unix würde ich erwarten, dass das Standardziel der aktuelle Prozessor ist, sicher bin ich mir aber nicht.
Der verlinkte Bug scheint ein Problem gewesen zu sein, bei dem selbst bei korrekt gesetztem Ziel falscher Code erzeugt wurde; zum Glück scheint das jetzt nicht der Fall zu sein.
Genau. Der verlinkte Bug bestand darin, dass der Compiler armv7-Instruktionen ausgab, obwohl man ihm gesagt hatte, er solle armv6 targeten.
Rachels Problem wurde gelöst, indem man dem Compiler mitteilte, dass er armv6 targeten soll; daher scheint dieser Bug bereits behoben zu sein und mit diesem Problem nichts zu tun zu haben.
Natürlich ist es ein Bug, aber es wirkt so, als hätte die Autorin statt eines Reports lieber einen Blogpost mit leicht klickträchtigem Titel geschrieben und ihn mit „zu seltsam“ beendet.
Die Datenbank, an der ich arbeite, ClickHouse, unternimmt ziemlich viel, um die Kompatibilität mit sehr alter Hardware zu erhalten.
Das Standard-ARM-Binary erfordert Armv8.2 von 2016 und kann auf Modellen ab dem Raspberry Pi 2 verwendet werden. Das x86-Binary läuft auf Hardware etwa ab 2010, die SSE4.2 und pclmul*-Instruktionen für schnelle CRCs besitzt.
Wir bauen auch Binaries für Systeme mit nur Armv8.0 und SSE2, testen sie aber nicht per CI. Ein schnelles Installationsskript lädt das passende Binary für den Zielhost herunter und entpackt es.
Ich finde es generell schwierig, die Balance zwischen Abwärtskompatibilität und der Nutzung von CPU-Funktionen der neuesten AArch64-Generationen zu finden. https://en.wikipedia.org/wiki/AArch64
Es gab erstaunlich viele Organisationen mit knappem Budget, etwa Universitäten in Schwellenländern, oder Hobby-Nutzer, die sich kein Hardware-Upgrade leisten konnten.
Technisch war ziemlich lästig, dass die CPU-Flags in /proc/cpuinfo nicht immer den -march=-Flags entsprechen, die man dem Compiler übergibt. Zum Beispiel erscheinen sie unterschiedlich als „lrcpc“ und „rcpc“.
Damit das richtig funktioniert, muss man praktisch zwei Arten von Flag-Sets verwalten.
In solchen Fällen halte ich es für besser für alle, mehrere Builds anzubieten, damit Kunden den auswählen können, der ihrer Architektur am nächsten kommt.
Das Problem dürfte wahrscheinlich sein, dass sich im aktuellen clang-13-Paket von bookworm das konfigurierte Target geändert hat.
Konkret ist das Standard-Target unter bullseye und clang-11 armv6k-unknown-linux-gnueabihf, während es unter bookworm und clang-13 arm-unknown-linux-gnueabihf ist.
Oder es könnte sein, dass sich auf LLVM-Seite der Default für diese Build-Konfiguration geändert hat.
Ich würde den Change-Management-Prozess von Debian gern besser verstehen. Ich bin mir auch nicht sicher, ob Raspbian tatsächlich von Debian verwaltet wird.
Wenn man jedoch [1] und [2] vergleicht, gibt es in der rules-Datei einen sauberen Test, der „wenn DEB_HOST_ARCH armhf ist, setze LLVM_HOST_TRIPLE auf armv6k“ macht, was die Änderung der Build-Konfiguration zu bestätigen scheint.
[1] http://raspbian.raspberrypi.org/raspbian/pool/main/l/llvm-to...
[2] http://raspbian.raspberrypi.org/raspbian/pool/main/l/llvm-to...
Es sieht nicht nach einer absichtlichen Änderung aus
Wie in den umliegenden Kommentaren /etc/env.d/gcc erwähnt wurde, ist es gut möglich, dass es an der Art liegt, wie Informationen aus der Umgebung gelesen werden
Das Standard-Tripel dürfte etwas wie arm-unknown-linux sein, wenn clang keine genaueren Informationen findet oder übergeben bekommt; offenbar ist der Mechanismus kaputt, der ein spezifischeres Ziel mitteilt
Das könnte bedeuten, dass es keinen ARMv6-Buildbot gibt, oder dass es zwar einen Buildbot gibt, dort aber die implizite Konfiguration weiterhin korrekt funktioniert
LLVM ist ein wirklich guter Cross-Compiler. Man kann damit ohne größere Probleme von praktisch jedem Ziel für jedes Ziel bauen
Clang ist da weniger attraktiv. Wenn es mit Target-Unterstützung gebaut wurde und man ihm korrekt mitteilen kann, für welches Ziel es bauen soll, macht es das vermutlich richtig. Auch in diesem Artikel war die Vermutung falsch, aber nachdem mehr Informationen geliefert wurden, funktionierte es korrekt
Bei den Runtime-Bibliotheken ist die Lage noch schlechter. Selbst wenn man für ein Ziel wie armv4 baut, muss man die passende libc usw. finden, und man muss dem Compiler möglicherweise mitteilen, wo diese Bibliotheken und Header liegen; bei diesem Teil sind die Details immer noch nicht ganz klar
Die meisten Distributionen und Compiler haben die ARMv6-Unterstützung vor einigen Jahren faktisch aufgegeben
Beim Bauen von Binärdateien für ein altes Synology-NAS hatte ich ähnliche Probleme
Warum sollte Clang Informationen aus /etc/env.d/gcc lesen?
clang/clang++ lesen Target-Flags und Profile aus /etc/env.d/gcc; diese korrekt zu pflegen, ist Aufgabe des Betriebssystems
Auf diesem Betriebssystem scheint diese Verwaltung nicht richtig funktioniert zu haben
Mein Gentoo-ARM-SBC auf Basis der noch älteren armv4-Architektur läuft auch mit aktuellen gcc-/clang-Updates weiterhin problemlos grep CTARGET /etc/env.d/gcc -r /etc/env.d/gcc/armv4tl-softfloat-linux-gnueabi-11.3.0:CTARGET="armv4tl-softfloat-linux-gnueabi"
/etc/env.d ist ein Gentoo-spezifisches Verzeichnis, das die Standard-Umgebungsvariablen für Benutzersitzungen definiert
Clang hat keine Funktion, dieses Verzeichnis zu lesen, daher sollte man nicht annehmen, dass es auch in anderen Distributionen existiert
Es ist lediglich so, dass Gentoo's Compiler-Konfiguration die Umgebungsvariable CTARGET liest, um das Ziel auszuwählen, und Gentoo /etc/env.d verwendet, um diesen Wert zu setzen
Im Artikel steht nicht, ob es Debian oder Raspbian ist, und falls Debian, ob es der armel- oder der armhf-Port ist
Ohne diese Information ist es wenig sinnvoll, darüber zu diskutieren, für welchen Befehlssatz LLVM kompiliert. Das hängt nämlich von LLVMs nativer Target-Konfiguration ab
Zur Einordnung: Debians llvm-toolchaim-snapshot unterstützt weiterhin armel, das ARMv5T als Baseline nutzt. Allerdings gibt es derzeit einen separaten Bug in LLVMs OpenMP-Bibliothek, wodurch der Build nicht erfolgreich durchläuft
Merkwürdig ist, dass das Clang-Binary selbst für einen mit dem Pi B+ kompatiblen Befehlssatz kompiliert wurde, aber ausgerechnet keinen mit dem Pi B+ kompatiblen Befehlssatz als Ziel nimmt
Das ist wirklich seltsam. Es soll ja nicht als Cross-Compiler verwendet werden, daher sollten Host und Target theoretisch identisch sein
Vermutlich ist das Image Raspbian. Es scheint keinen Grund zu geben, etwas anderes anzunehmen
Die Ausgabe von dpkg-architecture und der Inhalt von /etc/os-release wären hilfreich
Ohne das ist es schwierig, einen nützlichen Kommentar abzugeben
Der Titel ist leider reißerisch
Das ist eine Änderung des Standard-Targets, und clang kann weiterhin Binärdateien für den Pi B+ bauen
Man muss die Architektur nur explizit angeben. Daher wäre es wohl besser, den Titel etwas so zu ändern, dass klarer wird, dass es um eine Änderung der Voreinstellung geht
Wenn man auf der Zielmaschine selbst baut und trotzdem keine Binärdatei für genau diese Zielmaschine erzeugen kann, wirkt das nicht allzu reißerisch
Interessant, weil man beim Debuggen, warum ein Programm auf ARM nicht läuft, offenbar ungefähr so vorgehen kann
Ich habe einen Unity-Linux-Build, der in einem Container nicht läuft; selbst wenn ich beim Start von Docker das amd64-Flag übergebe, versucht Unity mono, einen Systemaufruf zu machen, den es nicht verwenden kann
Ich habe einen Workaround gefunden und es noch nicht debuggt. Ich habe den Entwicklermodus aktiviert und die Build-Einstellungen geändert, damit mono nicht verwendet wird
Irgendwann sollte ich wieder darin graben, um mehr darüber zu lernen
1 Kommentare
Hacker-News-Kommentare
Wenn man sich die Geschichte der ARM-Generationen ansieht, die der Raspberry Pi verwendet hat, ist es erstaunlich, wie alt der Chip ist.
Der im Raspberry Pi B+ verwendete ARM1176-Kern stammte selbst 2014, als der B+ erschien, schon aus dem Jahr 2003 und war damit bereits 11 Jahre alt.
Daher ist es nicht ungewöhnlich, dass man beim Bauen auf anderen Plattformen wie einem neueren Raspberry Pi Architektur-Flags explizit angeben muss, um kompatiblen Code zu erzeugen.
Wenn aber selbst beim Bauen auf dem Raspberry Pi B+ die richtige Architektur nicht als Standard gesetzt wird, wirkt das eher wie ein Konfigurationsfehler in den Defaults der Distribution.
Solche Produkte packen aus Kostengründen fast nie mehr Rechenleistung hinein, als nötig ist.
Im Artikel steht: „Ich habe das Binary von meinem Build-Host, einem viel schnelleren Pi 4B, genommen und versucht, es auf diesem alten Gerät auszuführen, woraufhin eine illegal instruction auftrat.“
Das ist ähnlich, als würde man eine mit aktuellem MSVC unter Windows 11 gebaute .EXE auf einem alten PC mit Windows XP ausführen wollen.
Vermutlich läuft auch die gesamte auf dem Pi 4B laufende Pi-Distribution nicht auf dem B+, und der Kernel könnte auf dieselbe Weise kompiliert worden sein.
Es scheint im Blogpost oder Artikel nicht klar behandelt zu werden, aber ist das nicht ein Bug?
Beim Suchen nach LLVM-Bugs habe ich einen Eintrag gefunden, der fast wie dasselbe Problem aussieht, aber der Bug ist von 2012 und geschlossen. Nach den letzten paar Kommentaren zu urteilen könnte es sein, dass er in Wirklichkeit nicht behoben wurde; ich habe aber nur grob drübergeschaut, also kann ich das auch missverstanden haben.
https://github.com/llvm/llvm-project/issues/13989
Beim erneuten Lesen fiel mir auf, dass am Ende des Artikels steht, dass ein funktionierendes Programm herauskommt, wenn man das Ziel explizit übergibt. Dann wirkt es eher wie eine Art Konfigurations-Bug; unter Unix würde ich erwarten, dass das Standardziel der aktuelle Prozessor ist, sicher bin ich mir aber nicht.
Der verlinkte Bug scheint ein Problem gewesen zu sein, bei dem selbst bei korrekt gesetztem Ziel falscher Code erzeugt wurde; zum Glück scheint das jetzt nicht der Fall zu sein.
Rachels Problem wurde gelöst, indem man dem Compiler mitteilte, dass er armv6 targeten soll; daher scheint dieser Bug bereits behoben zu sein und mit diesem Problem nichts zu tun zu haben.
Die Datenbank, an der ich arbeite, ClickHouse, unternimmt ziemlich viel, um die Kompatibilität mit sehr alter Hardware zu erhalten.
Das Standard-ARM-Binary erfordert Armv8.2 von 2016 und kann auf Modellen ab dem Raspberry Pi 2 verwendet werden. Das x86-Binary läuft auf Hardware etwa ab 2010, die SSE4.2 und pclmul*-Instruktionen für schnelle CRCs besitzt.
Wir bauen auch Binaries für Systeme mit nur Armv8.0 und SSE2, testen sie aber nicht per CI. Ein schnelles Installationsskript lädt das passende Binary für den Zielhost herunter und entpackt es.
Ich finde es generell schwierig, die Balance zwischen Abwärtskompatibilität und der Nutzung von CPU-Funktionen der neuesten AArch64-Generationen zu finden.
https://en.wikipedia.org/wiki/AArch64
Es gab erstaunlich viele Organisationen mit knappem Budget, etwa Universitäten in Schwellenländern, oder Hobby-Nutzer, die sich kein Hardware-Upgrade leisten konnten.
Technisch war ziemlich lästig, dass die CPU-Flags in /proc/cpuinfo nicht immer den -march=-Flags entsprechen, die man dem Compiler übergibt. Zum Beispiel erscheinen sie unterschiedlich als „lrcpc“ und „rcpc“.
Damit das richtig funktioniert, muss man praktisch zwei Arten von Flag-Sets verwalten.
Das Problem dürfte wahrscheinlich sein, dass sich im aktuellen clang-13-Paket von bookworm das konfigurierte Target geändert hat.
Konkret ist das Standard-Target unter bullseye und clang-11 armv6k-unknown-linux-gnueabihf, während es unter bookworm und clang-13 arm-unknown-linux-gnueabihf ist.
Oder es könnte sein, dass sich auf LLVM-Seite der Default für diese Build-Konfiguration geändert hat.
Wenn man jedoch [1] und [2] vergleicht, gibt es in der rules-Datei einen sauberen Test, der „wenn DEB_HOST_ARCH armhf ist, setze LLVM_HOST_TRIPLE auf armv6k“ macht, was die Änderung der Build-Konfiguration zu bestätigen scheint.
[1] http://raspbian.raspberrypi.org/raspbian/pool/main/l/llvm-to...
[2] http://raspbian.raspberrypi.org/raspbian/pool/main/l/llvm-to...
Es sieht nicht nach einer absichtlichen Änderung aus
Wie in den umliegenden Kommentaren
/etc/env.d/gccerwähnt wurde, ist es gut möglich, dass es an der Art liegt, wie Informationen aus der Umgebung gelesen werdenDas Standard-Tripel dürfte etwas wie
arm-unknown-linuxsein, wenn clang keine genaueren Informationen findet oder übergeben bekommt; offenbar ist der Mechanismus kaputt, der ein spezifischeres Ziel mitteiltDas könnte bedeuten, dass es keinen ARMv6-Buildbot gibt, oder dass es zwar einen Buildbot gibt, dort aber die implizite Konfiguration weiterhin korrekt funktioniert
LLVM ist ein wirklich guter Cross-Compiler. Man kann damit ohne größere Probleme von praktisch jedem Ziel für jedes Ziel bauen
Clang ist da weniger attraktiv. Wenn es mit Target-Unterstützung gebaut wurde und man ihm korrekt mitteilen kann, für welches Ziel es bauen soll, macht es das vermutlich richtig. Auch in diesem Artikel war die Vermutung falsch, aber nachdem mehr Informationen geliefert wurden, funktionierte es korrekt
Bei den Runtime-Bibliotheken ist die Lage noch schlechter. Selbst wenn man für ein Ziel wie armv4 baut, muss man die passende libc usw. finden, und man muss dem Compiler möglicherweise mitteilen, wo diese Bibliotheken und Header liegen; bei diesem Teil sind die Details immer noch nicht ganz klar
Beim Bauen von Binärdateien für ein altes Synology-NAS hatte ich ähnliche Probleme
/etc/env.d/gcclesen?clang/clang++lesen Target-Flags und Profile aus/etc/env.d/gcc; diese korrekt zu pflegen, ist Aufgabe des BetriebssystemsAuf diesem Betriebssystem scheint diese Verwaltung nicht richtig funktioniert zu haben
Mein Gentoo-ARM-SBC auf Basis der noch älteren armv4-Architektur läuft auch mit aktuellen gcc-/clang-Updates weiterhin problemlos
grep CTARGET /etc/env.d/gcc -r/etc/env.d/gcc/armv4tl-softfloat-linux-gnueabi-11.3.0:CTARGET="armv4tl-softfloat-linux-gnueabi"/etc/env.dist ein Gentoo-spezifisches Verzeichnis, das die Standard-Umgebungsvariablen für Benutzersitzungen definiertClang hat keine Funktion, dieses Verzeichnis zu lesen, daher sollte man nicht annehmen, dass es auch in anderen Distributionen existiert
Es ist lediglich so, dass Gentoo's Compiler-Konfiguration die Umgebungsvariable
CTARGETliest, um das Ziel auszuwählen, und Gentoo/etc/env.dverwendet, um diesen Wert zu setzenIm Artikel steht nicht, ob es Debian oder Raspbian ist, und falls Debian, ob es der armel- oder der armhf-Port ist
Ohne diese Information ist es wenig sinnvoll, darüber zu diskutieren, für welchen Befehlssatz LLVM kompiliert. Das hängt nämlich von LLVMs nativer Target-Konfiguration ab
Zur Einordnung: Debians
llvm-toolchaim-snapshotunterstützt weiterhin armel, das ARMv5T als Baseline nutzt. Allerdings gibt es derzeit einen separaten Bug in LLVMs OpenMP-Bibliothek, wodurch der Build nicht erfolgreich durchläuftDas ist wirklich seltsam. Es soll ja nicht als Cross-Compiler verwendet werden, daher sollten Host und Target theoretisch identisch sein
Vermutlich ist das Image Raspbian. Es scheint keinen Grund zu geben, etwas anderes anzunehmen
Die Ausgabe von
dpkg-architectureund der Inhalt von/etc/os-releasewären hilfreichOhne das ist es schwierig, einen nützlichen Kommentar abzugeben
Der Titel ist leider reißerisch
Das ist eine Änderung des Standard-Targets, und clang kann weiterhin Binärdateien für den Pi B+ bauen
Man muss die Architektur nur explizit angeben. Daher wäre es wohl besser, den Titel etwas so zu ändern, dass klarer wird, dass es um eine Änderung der Voreinstellung geht
Interessant, weil man beim Debuggen, warum ein Programm auf ARM nicht läuft, offenbar ungefähr so vorgehen kann
Ich habe einen Unity-Linux-Build, der in einem Container nicht läuft; selbst wenn ich beim Start von Docker das amd64-Flag übergebe, versucht Unity mono, einen Systemaufruf zu machen, den es nicht verwenden kann
Ich habe einen Workaround gefunden und es noch nicht debuggt. Ich habe den Entwicklermodus aktiviert und die Build-Einstellungen geändert, damit mono nicht verwendet wird
Irgendwann sollte ich wieder darin graben, um mehr darüber zu lernen