5 Punkte von GN⁺ 2025-01-24 | 4 Kommentare | Auf WhatsApp teilen
  • Während EdgeDB Netzwerk-I/O von Python nach Rust verlagerte, wirkte ein neuer auf reqwest basierender HTTP-Fetch-Test nur auf ARM64-CI so, als würde er hängen, tatsächlich stürzte er aber ab
  • Die Analyse des Core Dumps zeigte, dass die Problemstelle nicht der neue HTTP-Code war, sondern innerhalb von getenv() in libc; beim Durchlaufen des Arrays von Umgebungsvariablen scheiterte ein Lesezugriff auf den ungültigen Pointer 0x220
  • Ein anderer Thread setzte im openssl-probe-Pfad SSL_CERT_FILE und SSL_CERT_DIR; dabei allokierte setenv() environ neu, während gleichzeitig ein Python-Fehlerpfad getenv() aufrief, was zu einer Race Condition führte
  • Im Rust-Code gab es kein explizites unsafe, aber während std::env::set_var() die globale Umgebung veränderte, synchronisierte Rusts interner Lock nicht mit anderen Runtimes oder direkten libc-Aufrufen
  • Die Lösung war, unter Linux vom rust-native-tls-/openssl-Backend von reqwest auf rustls umzusteigen; auch Rust Edition 2024 und glibc entwickeln sich in eine Richtung, die diese Problemklasse reduziert

Crash, der nur auf ARM64-CI sichtbar wurde

  • EdgeDB entwickelte eine neue HTTP-Fetch-Funktion, während ein großer Teil des Netzwerk-I/O-Codes von Python nach Rust portiert wurde
  • Als HTTP-Client-Bibliothek wurde reqwest verwendet; lokal und auf x86_64-CI-Runnern liefen die Tests durch, auf ARM64-CI-Runnern schlugen sie jedoch sporadisch fehl
  • Zunächst sah es so aus, als würde der Test-Runner unendlich hängen; in den CI-Logs blieb ein Test ohne Fehlermeldung stundenlang als laufend stehen und lief schließlich in ein Timeout
  • Die erste Hypothese war ein Unterschied im Speichermodell zwischen Intel und ARM64
    • Intel hat ein vergleichsweise strenges Speichermodell mit einer globalen Ordnung, auf die sich alle Prozessoren bei Speicherschreibvorgängen einigen
    • ARM hat ein schwächer geordnetes Speichermodell, bei dem Schreibvorgänge in verschiedenen Threads in unterschiedlicher Reihenfolge sichtbar werden können

Verfolgung des Core Dumps in der Docker-CI-Umgebung

  • Die nächtlichen CI-Maschinen liefen auf Amazon AWS, sodass man sich als echter root-Benutzer außerhalb des Containers anmelden und dmesg sowie Systemlogs einsehen konnte
  • Innerhalb und außerhalb des Containers wurde nach der PID gesucht, die zu hängen schien, doch der betreffende Prozess existierte nicht; damit war klar, dass es sich nicht um einen Deadlock, sondern um einen Crash handelte
  • Da Docker-Container einen Prozess-Namespace verwenden, wurde der Core Dump an den Docker-Host weitergereicht; in journalctl fand sich ein Core Dump des python3-Prozesses
  • Als der Core Dump zunächst mit gdb geöffnet wurde, war der Backtrace nutzlos, weil die .so-Dateien aus dem Container fehlten
  • Nach dem Kopieren von /lib, /usr usw. aus dem Container und dem Setzen von solib-absolute-prefix in gdb ließ sich bestätigen, dass der Crash in getenv() aus libc.so.6 auftrat

Der kaputte Pointer einer Umgebungsvariablen, den getenv() las

  • Der vollständige Backtrace verlief über getenv()__dcigettext()strerror_r()strerror()PyErr_SetFromErrnoWithFilenameObjects()
  • Nicht der neue HTTP-Code stürzte direkt ab; vielmehr rief Python beim Erzeugen einer errno-basierten Exception über einen gettext-bezogenen Pfad getenv() auf
  • Die Implementierung von getenv() in GLIBC 2.17 durchläuft POSIX-environ als char **-Liste und prüft Pointer auf Umgebungsvariablen-Strings bis zum abschließenden NULL-Pointer
  • Aus Disassembly und Registerzustand ergab sich, dass getenv() beim Lesen eines Bytes von Adresse x19 = 0x220 abstürzte
    • 0x220 war offensichtlich keine gültige Speicheradresse
    • Bei der Prüfung von environ selbst wirkte die aktuelle Liste der Umgebungsvariablen konsistent

Ursache: Race Condition zwischen setenv() und getenv()

  • setenv() ist keine Funktion, die sich in Multithread-Umgebungen sicher aufrufen lässt; Probleme mit seltsamen Crashes in libc-getenv() wurden bereits mehrfach wiederentdeckt
  • Der Abgleich von Disassembly und C-Code zeigte, dass x20 dem Pointer ep entsprach, der durch das environ-Array lief
  • Zum Zeitpunkt des Crashs war x20 0x248b5000, während environ aktuell auf 0x28655750 zeigte, also rund 60 MB weiter hinten lag
  • Beim Vergleich des Speichers rund um das alte Umgebungsarray mit dem aktuellen environ trat der letzte Unterschied bei den Einträgen SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt und SSL_CERT_DIR=/etc/ssl/certs auf
  • Ein anderer Thread hatte während eines setenv()-Aufrufs environ verschoben, und getenv() las weiter aus dem alten Umgebungsarray — ein Use-after-free

Verbindung zu openssl-probe und TLS-Backend

  • In einem älteren Issue zu rust-native-tls fand sich der Hinweis, dass openssl-probe die Umgebungsvariablen SSL_CERT_FILE und SSL_CERT_DIR setzt, um Systemzertifikate zu finden
  • Unter Linux wird dieser Pfad verwendet, wenn das openssl-Backend von rust-native-tls genutzt wird
  • Der problematische openssl-probe-Code setzte diese beiden Umgebungsvariablen ohne explizites unsafe via env::set_var()
    • SSL_CERT_FILE
    • SSL_CERT_DIR
  • Durch diese Kombination interagierte Rust-Code ohne unsafe ungünstig mit der libc-Nutzung innerhalb desselben Prozesses und verursachte den Crash

Warum der Fehler auf ARM64 Linux reproduzierbar war

  • Dieser Crash tritt nur auf, wenn setenv() das Umgebungsarray per realloc verschiebt und gleichzeitig ein anderer Thread getenv() aufruft
  • Für die Reproduktion mussten mehrere Bedingungen zugleich erfüllt sein
    • Die Anzahl der Umgebungsvariablen musste so liegen, dass realloc ausgelöst wurde
    • Ein nicht zusammenhängender I/O-Fehler musste von asyncio abgefangen werden
    • Der Python-Fehlerpfad musste genau in diesem Moment getenv() aufrufen, um die Umgebungsvariable LANGUAGE zu lesen
  • Der fehlerhafte Wert 0x220 lag gemessen in 64-Bit-Wörtern nahe an der früheren Größe der Umgebung
    • 0x220 / 8 = 68
    • Offenbar wurde dieser Wert an die Stelle des abschließenden NULL im alten Umgebungsblock geschrieben; vermutlich kennzeichnete er die Größe eines freien Blocks im System-malloc
  • Weil so viele Voraussetzungen zusammenkommen mussten, war es fast schon Glück, dass sich der Fehler auf einer einzelnen Plattform recht gut reproduzieren ließ

Hinweise aus dem ARM64-Disassembly

  • Im Disassembly von getenv() war zunächst verwirrend, dass nicht klar sichtbar war, an welcher Stelle sich x20 änderte
  • Der Schlüssel war der Pre-Index-Adressierungsmodus von AArch64
  • ldr x19, [x20, #8]! arbeitet wie folgt
    • x19 = *(x20 + 8)
    • x20 = x20 + 8
  • Wegen dieses Adressierungsmodus lief x20 durch das Array der Pointer auf Umgebungsvariablen, auch wenn es nicht explizit auf der linken Seite geschrieben wurde

Angewandter Fix und Veränderungen in verwandten Projekten

  • Letztlich fiel die Entscheidung, unter Linux vom rust-native-tls-/openssl-Backend von reqwest auf rustls zu migrieren
  • Ursprünglich war das native-TLS-Backend gewählt worden, um während der Portierung des Python-Codes nach Rust nicht zwei TLS-Engines gleichzeitig mitzuliefern
  • Nach diesem Vorfall wurde entschieden, dass zwei TLS-Engines kurzfristig akzeptabel sind
  • Als Alternative wäre es möglich gewesen, den ersten Aufruf von try_init_ssl_cert_env_vars() auszuführen, während Pythons GIL gehalten wird
    • Rust besitzt einen internen Lock, der Race Conditions beim Lesen und Schreiben der Umgebung zwischen Rust-Code verhindert
    • Er verhindert jedoch keine direkten libc-Aufrufe aus Code anderer Sprachen
    • Wenn die GIL gehalten wird, lässt sich zumindest die Race Condition mit Python-Threads vermeiden
  • Das Rust-Projekt kennt dieses Problem bereits und plant, die Environment-Setter-Funktionen in Edition 2024 als unsafe zu markieren
  • Auch das glibc-Projekt hat kürzlich Änderungen aufgenommen, die realloc vermeiden und stattdessen alte Umgebungsarrays leaken, um die Thread-Sicherheit von getenv() zu erhöhen

4 Kommentare

 
carnoxen 2025-01-24

setenv ist nicht threadsicher, und C möchte das nicht ändern

Die Funktion setenv macht schon wieder Ärger.

 
y15un 2025-01-24

Ich würde den Titel als „Die Thread-Unsicherheit der C-Stdlib kann nicht einmal das ach so sichere Rust retten“ schreiben. :)

 
halfenif 2025-01-24

Ich habe es definitiv verstanden.

 
GN⁺ 2025-01-24
Meinungen auf Hacker News
  • Der wichtigste Punkt hier ist, dass die Funktionen zum Setzen von Umgebungsvariablen in der nächsten Rust-Edition unsafe werden
    Mit etwas Glück wirkt sich das bis auf die Crates aus, die solche Crashes verursachen; inzwischen wurde upstream ein Issue eröffnet: https://github.com/alexcrichton/openssl-probe/issues/30

    • Das Grundproblem wird damit aber nicht wirklich behoben: getenv und setenv bzw. unsetenv können nicht sicher aus unterschiedlichen Threads aufgerufen werden
      Die einzige verlässliche Lösung scheint zu sein, diese Funktionen so zu ändern, dass sie zwingend einen Mutex nehmen
    • Die Einschätzung des Library-Autors, dass „die beste Lösung für dieses Problem heute ist, eine Library wie rustls zu verwenden und diese Crate nicht mehr zu nutzen“, wirkt vernünftig und ist erfreulich
      Leider sieht das Ökosystem anders aus: https://github.com/seanmonstar/reqwest/blob/master/Cargo.tom...
    • Menschen sind darauf trainiert, Blöcke und Präfixe wie ____UNSAFE_payattention__nevermindthatthisappears50timesinthisfile___ zu ignorieren
      In Web-Frameworks ist es ähnlich: Vue hat die Direktive v-html, React hat dangerouslySetInnerHTML; in dieser Hinsicht ist Vue meiner Ansicht nach eindeutig besser
  • set_var und remove_var aus der Rust-Standardbibliothek werden in der nächsten Edition, der Edition 2024, korrekt einen unsafe {}-Block verlangen
    Die Dokumentation erwähnt die Sicherheitsprobleme inzwischen ebenfalls, aber diese Funktionen überhaupt als safe anzubieten war ein Fehler – ein Fehler, den auch höherstufige Sprachen gemacht haben
    https://doc.rust-lang.org/stable/std/env/fn.set_var.html
    In glibc gibt es einen Patch, der getenv in weiteren Fällen sicher macht, in denen die Umgebung geändert wird; da C aber weiterhin direkten Zugriff auf environ erlaubt, kann es bei Änderungen nie vollständig sicher werden: https://github.com/bminor/glibc/commit/7a61e7f557a97ab597d6f...

    • Es ist überraschend, dass glibc nun alte Versionen beibehält und eine exponentielle Größenanpassungsstrategie übernimmt
      Pro aktiver Umgebungsvariable entsteht amortisiert ein Speicherleck konstanter Größe, aber bei der Variablen selbst gibt es ohnehin schon ein solches Leck, das sogar von der Länge abhängt und auch nicht mehr verwendete Werte umfasst
      Es wird sicher pathologische Nutzungsmuster geben, bei denen der Speicher deshalb auch in API-konformen Programmen unbegrenzt wächst
      Um Programme zu reparieren, die durch Nutzung der API aus mehreren Threads gegen die Regeln verstoßen, wird in Programme, die sich an die API halten, offenbar der Bug unbegrenzten Speicherwachstums eingeführt. Das ist interessant, aber unangenehm – eher Pragmatismus als Dogma
    • Wenn die Implementierung der Standardbibliothek die Synchronisierung übernehmen kann, stellt sich die Frage, warum überhaupt unsafe verlangt werden muss
  • Selbst wenn Maintainer der C-Standardbibliothek dagegen sind, setenv multithread-sicher zu machen, sollte zumindest eine neue thread-sichere API definiert werden – ob innerhalb von POSIX oder indem man erst einen De-facto-Standard schafft, den POSIX später übernimmt
    Mit der Zeit, die dafür aufgewendet wird zu erklären, warum man nichts tun kann, hätte man dieses Problem beheben können; der alte setenv hätte ersetzt und in vielen Softwareprojekten als veraltet markiert und entfernt werden können
    Angesichts der Tatsache, dass glibc Änderungen vornimmt, die dieses Problem faktisch beseitigen sollen, wirkt auch die Aussage des Musl-Maintainers wenig überzeugend, dass es sich in Musl nicht beheben lasse

    • Das größte Problem ist nicht, dass es keine thread-sichere API gibt, sondern dass extern char **environ; existiert
      Solange environ öffentlich zugänglich ist, gibt es nicht einmal die Garantie, dass setenv und getenv überhaupt verwendet werden. Denn beide sind nicht verpflichtend
      Wenn man environ entfernen könnte, wäre es ziemlich einfach, setenv und getenv thread-sicher zu machen. Wenn man es nicht entfernen kann, ist es unmöglich; dennoch kann man argumentieren, dass thread-sichere setenv und getenv eine Verbesserung wären, auch wenn es keine vollständige Lösung ist
    • Auch alle exec()-Funktionen, die keine Umgebung als Argument entgegennehmen oder PATH durchsuchen, um eine ausführbare Datei zu finden, werden wohl eine Sperre brauchen
    • Es überzeugt nicht, zu behaupten, man wisse es besser als die Experten, die zu dem Schluss gekommen sind, dass es sich nicht abwärtskompatibel beheben lässt
  • Unter Linux auf Bugs rund um Umgebungsvariablen zu stoßen, wirkt fast wie ein Initiationsritus; auf anderen Unix-Systemen scheint das merkwürdigerweise weniger problematisch zu sein.
    Linus und der Kernel beheben POSIX-Bugs pragmatisch so, dass sie in der Praxis nicht auslösen; bei glibc ist es etwas amüsant, wie weit man selbst Jahrzehnte, nachdem Leute versucht haben, das Problem wenigstens etwas abzumildern, noch hinterherhinkt.
    Klar gibt es allerlei Ärgernisse wie TZ, aber wenn man getenv_r() bereitgestellt, es mit setenv() synchronisiert und bei der Verwendung von getenv() schon beim Kompilieren und Linken gewarnt hätte, wäre ein großer Teil der Probleme verschwunden.
    Man hätte sogar noch weiter gehen und die Umgebungs-Pointer per Copy-on-Write (COW) schreibgeschützt halten können.
    Stattdessen wurde das Problem auf einzelne Anwendungen abgewälzt, was ein großer Fehler ist, weil Anwendungsentwickler kaum wissen können, was ihre Abhängigkeiten tun. Genau in so einer Lage war ich vor langer Zeit auch, und damals sagte der Closed-Source-Bibliotheksanbieter, ich solle doch aufhören, diesen Spielzeug-Unix-Klon Linux zu verwenden.

    • Ich weiß nicht, wie man zu der Einschätzung kommt, dass es „unter Linux Bugs rund um Umgebungsvariablen gibt und auf anderen Unix-Systemen weniger Probleme“.
      Das Problem ist nicht die Implementierung, sondern die API selbst. setenv(), unsetenv(), putenv() und besonders environ sind in Multithread-Programmen grundsätzlich unsicher.
      Auch getenv_r() kann einen nicht vollständig retten. Denn während ein Thread den alten Wert einer Umgebungsvariable in den bereitgestellten Puffer kopiert, kann ein anderer Thread setenv() aufrufen.
      Den Fall, dass man mit getenv() einen Wert erhält und ein anderer Thread danach setenv() aufruft und diesen Speicher ungültig macht, behebt getenv_r() zwar, aber es gibt keine Möglichkeit, andere API-brechende Aufrufe zu verhindern.
      libc kann manche Probleme abmildern, indem sie innerhalb von getenv()/setenv()/putenv()/unsetenv() einen Mutex hält, aber weiterhin kann libc nicht garantieren, dass ein von getenv() zurückgegebener Wert lange genug gültig bleibt, damit der aufrufende Code ihn verwenden kann.
      Es gibt auch keine gute Möglichkeit, direkten Zugriff auf environ sicher zu machen. Man könnte environ zwar thread-lokal machen, aber dann könnten die Umgebungsansichten der einzelnen Threads dauerhaft auseinanderlaufen, und die Ergebnisse eines getenv_r()-Aufrufs könnten von einer direkten Prüfung von environ abweichen.
      Abwärtskompatibilität hier zu wahren, ist wirklich schwierig, und schon das Hinzufügen eines Mutex zum Schutz der Funktionen könnte die Bedeutung bestehender Programme verändern und sie dadurch kaputtmachen.
  • Früher gab es schon einmal einen Artikel darüber, dass setenv schrecklich ist: https://www.evanjones.ca/setenv-is-not-thread-safe.html
    Es gab auch eine Diskussion dazu, und schon der erste Kommentar sagte, dass es in Rust Probleme verursacht: https://news.ycombinator.com/item?id=38342642

    • Das ist bereits bekannt.
      Die meisten der übrigen Probleme hier scheinen eher die Entwicklungsumgebung zu betreffen. Es wurde per Docker auf einer Remote-Maschine in einem Amazon-Rechenzentrum getestet, und diese Maschine konnte Prozessabstürze nicht melden.
      Außerdem gab es im Container nicht genug Debug-Symbolinformationen, um einen Backtrace zu erhalten. Hätte man beim ersten Fehlschlag einen sauberen Backtrace bekommen, wäre es sofort klar gewesen.
      Fraglich ist ohnehin, warum überhaupt setenv verwendet wird.
  • Dabei musste ich an die 12-factor app-Bewegung denken, an die einige frühere Kollegen sehr fest geglaubt haben. Einer dieser „Factors“ besagte, dass Anwendungskonfiguration über Umgebungsvariablen erfolgen sollte.
    Ich fand das immer etwas unsinnig, weil diese Art der Konfiguration im Grunde Werte vom Typ String in einem flachen Namensraum wie in einen Korb wirft.
    Die Risiken von getenv()/setenv()/environ sehe ich ebenfalls als starkes Argument dafür, Umgebungsvariablen nicht für Konfiguration zu verwenden.
    Natürlich gibt es nicht immer eine hervorragende und gut unterstützte Alternative. Ich bevorzuge Konfigurationsdateien, und man kann auch Template-Konfigurationen verwenden, in die nur noch die Werte für Entwicklung, Staging und Produktion eingetragen werden. Meist nutze ich YAML, trotz seiner Nachteile und Fallstricke; auch wenn es bessere Formate für Konfigurationsdateien geben mag, halte ich YAML für deutlich besser als Umgebungsvariablen.

    • Es gibt viel starke Abneigung gegenüber Windows und Microsoft, aber im Lauf der Zeit gibt es durchaus Fälle, in denen sich ihr API-Design rechtfertigt.
      Unter NT können Umgebungsvariablen typisiert und templatisiert werden, und es gibt mit der Registry auch eine Konfigurationsdatenbank mit Namensräumen. Umständlich und seltsam ist sie allerdings schon.
      Außerdem bietet MSVC für fast alle Standardbibliotheksfunktionen Thread-sichere Varianten an.
      Ich höre oft, wie neue C/C++-Entwickler die mangelnde POSIX-Kompatibilität von MSVC beklagen, aber sie scheinen nicht wirklich darüber nachzudenken, was das tatsächlich bedeutet. Es wirkt eher so, als wollten sie einfach Kreuzkompatibilität mit C-Programmen, die in den 1990ern geschrieben wurden.
    • Bei Umgebungsvariablen habe ich ähnliche Bedenken. Mir gefällt nicht, dass man sie von überall lesen kann.
      Das behindert die Fähigkeit, das Verhalten allein aus der Signatur einer Funktion abzuleiten, und macht viele Funktionen unrein, die rein hätten sein können.
      Wenn es ein Sprachfeature gäbe, mit dem eine App markieren kann, dass Umgebungsvariablen in keinem Prozess beliebig nutzbar sind und nur einmal, nicht pro Variable, sondern einmal gesammelt gelesen werden dürfen, würde ich es überall einsetzen.
    • getenv() selbst ist völlig in Ordnung; das Problem ist setenv().
      Theoretisch wird die Umgebung gesetzt, bevor diese mysteriöse App startet, sodass man es gar nicht brauchen sollte.
      Aber ein flacher Namensraum, String-Werte und ein frei zugänglicher globaler Bereich, den alle teilen, ohne zu wissen, welche Libraries und Module noch dazukommen, sind auch ohne die Sicherheitsprobleme von setenv() keine gute Idee.
    • Zur „12-factor app“ sollte ein Anhang gehören, dass der Prozess die Umgebung während seiner Lebensdauer als schreibgeschützt behandeln muss.
      Die meisten Probleme, über die hier gesprochen wird, scheinen daher zu kommen, dass Leute die Umgebung als Key-Value-Store für veränderlichen globalen Zustand missbrauchen wollen. Ich weiß nicht, warum man das überhaupt tun sollte.
      Die JVM behandelt die Umgebung praktisch als unveränderlich, und es ist gut möglich, dass Scala- und Java-Firmen wie SoundCloud die 12-factor-app-Bewegung beeinflusst haben. Ich habe nie erlebt, dass sich die Umgebung geändert oder Threading-Probleme verursacht hätte.
      Selbst wenn sich die Umgebung ändert, bleibt die beim Start der JVM erstellte unveränderliche Kopie gleich, und Code, der über die normalen Java-APIs mit der Umgebung interagiert, sieht diese Änderung nicht.
      Das Problem bei Konfigurationsdateien ist, dass das Parsen pro Prozess erfolgt. Deshalb ist Linux/Unix so ein Durcheinander. Jedes Tool hat andere Konventions- und Mechanismen für Konfiguration, und es gibt keinen Standard.
      Im Docker-Ökosystem läuft es darauf hinaus, dass man im Container zwar tun kann, was man will, die Schnittstelle nach außen aber entweder darin besteht, ein Volume zu mounten und die komplexe Konfigurationsweise jeder einzelnen App zu befolgen, oder einfach Umgebungsvariablen zu verwenden.
      Die meiste moderne Software, die heute mit Docker läuft, ist so Docker-freundlich, dass ihr Verhalten vollständig über die Umgebung gesteuert werden kann, und in vielen Fällen reicht das aus.
      Mit Docker Compose oder Kubernetes hat man dann eine Liste von Umgebungsvariablen, die definiert, wie der Prozess gestartet wird, in einer YAML-Datei; damit bekommt man bis zu einem gewissen Grad die gewünschte Struktur. Ich mag YAML nicht, aber es funktioniert gut genug, und auch wenn Syntaxprobleme einem den Tag ruinieren können, sind die Alternativen ebenfalls nicht frei von Problemen.
    • Das ist eigentlich ein separates Problem. Wenn man Umgebungsvariablen als Konfiguration einliest und danach nicht mehr anfasst, ist das vollkommen sicher.
      Ich verwende ebenfalls den 12-factor-app-Stil, aber sobald die Variablen in der App angekommen sind, validiere und speichere ich die Daten. Danach gibt es keinerlei Problem.
  • Ein großartiger Artikel, der einem schwer erkennbaren Bug nachgeht.
    Es war alles dabei: sporadische Bugs, Architekturspezifika, in Abhängigkeiten versteckt, Rust, Python GIL und sogar gettext.
    Solche detaillierten Fehleranalyse-Berichte kommen dem eigenen Erleben am nächsten. In Situationen, in denen eine Abhängigkeit das intern nutzt und man gar nicht wissen konnte, ist es schwer, einfach zu sagen: „Dann benutzt halt X nicht.“

  • Es heißt einerseits: „Die nächtliche CI-Maschine läuft auf Amazon AWS und hat den Vorteil, dass man einen echten root-Benutzer statt eines Containers verwenden kann“, und zugleich: „Außerhalb des Containers liegen keine benötigten Dateien, und der Container ist sehr minimal, sodass sich gdb nicht leicht installieren lässt.“
    Haben die Leute inzwischen die Fähigkeit verloren, lokal zu bauen und zu debuggen, ohne Cloud und Container?

    • Ja. Es ist schockierend, wie sehr Cloud-SaaS das Verständnis der Leute verzerrt hat.
      Für die allerkleinsten Dinge braucht man inzwischen allerlei Cloud-Komplexität und Deployment-Schichten. Damit wurde die PC-Revolution zu 100 % zurückgedreht, zurück in die klobige und teure Ära des Mainframe-Computing.
      Der Grund ist, dass in der Cloud Geld steckt, und dass die Cloud DRM ist. Wenn man Software dorthin bringt, kann man Abogebühren verlangen, sie lassen sich nicht umgehen, und man kann perfekte Abhängigkeit für immer aufrechterhalten. Oft können Nutzer nicht einmal ihre eigenen Daten herausbekommen.
      Praktisch ist es auch für Echtzeitanalysen zur Produktoptimierung.
      Computing-Architektur ist dem Geschäftsmodell nachgelagert. Mainframes starben ursprünglich, weil es kein Internet gab und PCs günstiger waren, aber auch, weil Anbieter viel Macht zur Bindung der Kunden verloren.
      Jetzt gibt es einen Weg, dieses deutlich profitablere Modell wiederzubeleben. Die lästige Freiheit der Nutzer ist verschwunden, und ehrlich gesagt: Wenn Nutzer solche Freiheit bekommen, zahlen sie oft nicht, wodurch es schwer wird, ein Geschäft mit hochwertiger Software aufzubauen.
    • Das ist ein Problem wie zufällige Speicherbeschädigung, die nur auf ARM auftritt.
      Lokal konnten sie den Crash sehr wahrscheinlich nicht reproduzieren. Die Entwicklermaschinen waren vermutlich überwiegend x86, und dort trat der Crash wohl nicht auf.
      Das Crash-Handling hätte besser sein müssen, aber sie scheinen sich dessen bewusst zu sein, und es ist nicht der Kern dessen, was hier behandelt wurde.
    • Natürlich haben sie diese Fähigkeit nicht verloren, aber auf unseren Maschinen ist der Crash nicht aufgetreten.
    • Wie soll man lokal debuggen, wenn man wahrscheinlich kein Gerät hat, das die betroffene Architektur ausführt? Es ist ohnehin viel schneller, in der tatsächlichen Umgebung zu debuggen, in der der Fehler auftritt.
  • Veränderlicher globaler Zustand ist böse. Freunde lassen Freunde keinen veränderlichen globalen Zustand verwenden.
    Ich hasse Umgebungsvariablen. Das ist der „Linux-Weg“, aber ich meide ihn wie die Pest. Kann ich nur dringend empfehlen.
    libc ist furchtbar, und die Welt sollte endlich darüber hinwegkommen.

    • Umgebungsvariablen sind in Ordnung, wenn man sie innerhalb eines Prozesses als schreibgeschützt behandelt.
    • Wenn „veränderlicher globaler Zustand böse“ ist, müsste man auch CPU und RAM wegwerfen.
    • Ich frage mich, was als Alternative vorgeschlagen wird.
      Das Problem ist weder Linux noch veränderlicher globaler Zustand oder Ressourcen, noch libc.
      Das Problem ist, dass man am Arbeitsplatz nicht die Zeit bekommt, Dinge richtig zu machen. Wenn man etwa ein Problem mit GDB finden will, bevor es knallt, muss der Chef einem die Zeit geben, den Code und alles, was dieser Code berührt, beharrlich zu debuggen und zurückzuverfolgen.
      In halbgaren Code fließt zu viel Geld. Traurig, aber wahr.
    • libc hat die Welt ins Informationszeitalter katapultiert.
    • Ich frage mich, was die bevorzugte Alternative wäre.
  • Wegen zu vieler solcher Probleme habe ich schließlich getenv / setenv / putenv per LD_PRELOAD gepatcht.

    • Also mit einer fixen Implementierung, die die Umgebung leakt, wie sie gerade in glibc gelandet ist?