2 Punkte von GN⁺ 2025-09-24 | 2 Kommentare | Auf WhatsApp teilen
  • Die Programmiersprache Go hat offiziell Valgrind-Unterstützung hinzugefügt
  • Dadurch werden die Möglichkeiten zur Erkennung von Speicherfehlern und zum Debugging erweitert
  • Entwickler können Speicherlecks und Zugriffsfehler nun leichter erkennen
  • Dank verbesserter Kompatibilität mit Valgrind lassen sich Portierung und Wartung effizienter durchführen
  • Die Bewertung der Stabilität von Go-Code auf verschiedenen Plattformen wird einfacher

Bedeutung der Einführung der Valgrind-Unterstützung in Go

  • In Go wurde Valgrind-Unterstützung ergänzt, sodass Entwickler das Tool zur Erkennung von Speicherfehlern nun offiziell nutzen können
  • Damit lassen sich in Go-Code Probleme wie Use-after-free, Speicherlecks und ungültige Speicherzugriffe erkennen
  • Valgrind wird breit zur Erkennung von Speicherproblemen in verschiedenen Programmiersprachen eingesetzt und ist für die Go-Community eine wichtige Änderung zur Stärkung von Zuverlässigkeit und Robustheit
  • Die neue Funktion erleichtert auf mehreren Plattformen verschiedene Aufgaben rund um Go-Programme, darunter Debugging, Qualitätsprüfung und Stabilitätsbewertung
  • Besonders relevant ist bei diesem Update, dass in die Go-Runtime Instrumentierungscode für Valgrind aufgenommen wurde

Was ist Valgrind?

  • Valgrind ist ein Open-Source-Entwicklungswerkzeug zur Prüfung auf Speicherfehler, Thread-Fehler, Speicherlecks und mehr
  • Es wird häufig in Systemprogrammiersprachen wie C und C++ eingesetzt und ermöglicht eine präzise Erkennung von Problemen bei der Speicherverwaltung

Zusammenfassung dieser Erweiterung

  • Die mit dieser Änderung eingeführte Code-Instrumentierung ermöglicht es Valgrind, Ereignisse im Zusammenhang mit dynamisch alloziertem Speicher in der Go-Runtime präzise nachzuverfolgen
  • Entwickler können Go-Programme mit Valgrind ausführen und so potenzielle Speicherprobleme oder fehlerhafte Pointer-Zugriffe effektiv diagnostizieren
  • Dadurch ergibt sich für Go-basierte Infrastrukturen oder Services der Vorteil, hochwertigen Code zu erhalten und Problemen vorzubeugen

Erwartete Auswirkungen der Änderung

  • In Go-Projekten dürfte der Prozess zur Erkennung von Speicherfehlern und zur Verbesserung der Codequalität deutlich präziser werden
  • Es wird erwartet, dass sich die Sicherung von Kompatibilität und Zuverlässigkeit von Go-Code, der auf verschiedenen Plattformen bereitgestellt wird, erleichtert

2 Kommentare

 
taptaps 2025-09-25

Wenn man Beiträge zur Programmiersprache Go sieht, scheinen in den Kommentaren immer unbedingt Sachen zu stehen wie
„Bei Rust ist das nicht so“ oder „Bei Rust braucht man das nicht“, haha

 
GN⁺ 2025-09-24
Hacker-News-Kommentare
  • Ich bin der Autor dieses CL. Mit dieser Verbesserung möchte ich versuchen, die Speicherinitialisierungsverfolgung auszunutzen, um zu prüfen, ob Krypto-Code innerhalb einer festen Zeit läuft, also für Tests auf Konstantzeit-Eigenschaften; das ist ähnlich zu dem, was agl vor etwa 15 Jahren vorgeschlagen und in BoringSSL gemacht hat, siehe Link. Diese Eigenschaft ist nämlich wirklich schwer zu testen. Ich erwarte außerdem noch weitere interessante Effekte, etwa dass wir bei der Aktivierung von Valgrind in Go untersuchen können, ob sich das Speichermanagement der Runtime sauber nachverfolgen lässt. Ich möchte aber betonen, dass diese Unterstützung noch experimentell ist. Ich kann nicht mit 100%iger Sicherheit sagen, dass alle Konfigurationen gut funktionieren, und es könnten mehr schwer verständliche Warnungen auftreten.
    • Ich frage mich, ob es Bereiche gibt, in denen die Community bei der Entwicklung helfen kann.
    • Ich halte das für ein wirklich großartiges Feature. Hoffentlich lassen sich damit auch andere Probleme in Go finden. Ich frage mich aber, ob man statt einer so komplexen Verfolgung nicht einfach verschiedene Eingabewerte an eine kryptografische Funktion geben, dann direkt die Laufzeit messen und bei identischen Zeiten prüfen könnte, ob Konstantzeit gewährleistet ist. Zum Beispiel könnte es selbst bei Garbage Collection oder OS-Rauschen genügen, viele Inputs zu testen und zu schauen, ob die Messwerte innerhalb einer Epsilon-Toleranz liegen. Außerdem haben manche CPUs Zähler für bedingte Sprünge, die der rr-Debugger nutzt. Man könnte damit die Anzahl der Branches vor und nach der Entschlüsselung vergleichen; das hängt mit agls Aussage zusammen, dass für Konstantzeit die Verzweigungen identisch sein müssen. Und man könnte nach den ersten zehn Entschlüsselungen die maximale Zeit nehmen, einen kleinen Puffer hinzufügen und dann bei jeder weiteren Entschlüsselung Time Padding einfügen, also etwa noop ausführen, um die Dauer künstlich anzugleichen. Man könnte auch ein assert auslösen, das das Programm abstürzen lässt, wenn die Obergrenze überschritten wird. Allerdings wird das schwierig, wenn das OS den Prozess deschedult und dadurch das Timing verfälscht.
  • Es gefällt mir sehr, dass nach dem Hinzufügen der Valgrind-Header in den Tree nicht der Weg gewählt wurde, per cgo verschiedene Makros für Valgrind-Client-Requests aufzurufen, sondern stattdessen mit einer einzigen Assemblerfunktion die nötigen Befehle direkt ausgegeben werden, um die Client-Requests auszulösen. So ein Ansatz ist genau das Richtige für eine Bootstrap-Toolchain: nur die minimalen Building Blocks schaffen und den Rest auf Sprachebene erledigen.
    • Falls ihr diesen Weg nicht gewählt und stattdessen den bisherigen Ansatz oder andere Alternativen vermieden hättet: Wie hätte man dann etwas mit fast gleicher Performance bauen können, das den Prozess ähnlich vereinfacht wie Go? Ich denke, solche Fragen werden uns auch künftig beschäftigen.
  • Es ist schön zu sehen, dass rsc immer noch aktiv beiträgt, besonders dass er sogar Kommentare in Commit-Messages schreibt. Je älter ich werde, desto wichtiger erscheinen mir Commit-Messages. Wenn man einfach nur "Valgrind hinzugefügt" hinterlässt, hilft das später bei der Archivierung kaum weiter.
    • rsc ist wirklich ein Entwickler auf Rockstar-Niveau. In letzter Zeit probiert er viele neue Dinge aus, etwa den Umgang mit Issues oder PRs per AI, und ich erwarte, dass dabei ziemlich viel herauskommt.
  • Dieses Feature ist nur dann wirksam, wenn die Tests auf alle Pakete angewendet werden. Sonst geht es in irrelevanten Warnungen unter. Genau deshalb ist es schwierig, Valgrind für Python-Code zu verwenden.
    • Wenn das stimmt, müsste das genauso auch für C und C++ gelten. Ich selbst habe Valgrind in einem hybriden Python- + Boost-C++-Programm verwendet, und nach etwa einer Stunde Arbeit an einer Suppressions-Datei ließ es sich problemlos nutzen.
    • Zum Ordnen und Zusammenfassen übermäßig vieler Informationen könnte ein lokales LLM nützlich sein. Deshalb ist die Rolle einer Toolchain mit eingebauten Batterien wie Valgrind sehr wichtig.
    • Ich frage mich, was in der Go-Umgebung genau mit „alle Pakete werden getestet“ gemeint ist.
  • Valgrind ist eine versteckte Superkraft. Bei fast aller Software, die ich schreibe, führe ich Testfälle mit make check aus und starte mit make check-valgrind dieselben Tests noch einmal unter Valgrind. Letzteres nutze ich nur auf Entwicklerrechnern. Auf diese Weise finde ich oft Speicherlecks oder subtile Bugs.
    • Teilweise stimme ich zu, aber sobald Multithreading ins Spiel kommt, was Go häufig nutzt, funktioniert die Abstraktionsschicht von Valgrind nicht mehr besonders gut. Das basiert auf meiner letzten tieferen Arbeit mit C++-Code. Da eine eigene Scheduler-Logik verwendet wird, zeigen sich reale Nebenläufigkeits- und Race-Condition-Probleme unter Valgrind nicht so gut. Und generell ist der Performance-Verlust ziemlich stark. Trotzdem hat es mir mehrfach sehr geholfen, und ich bin dankbar, dass es weiter existiert.
  • Diese Unterstützung ist wirklich cool. Dadurch dürften noch einige weitere Bugs sichtbar werden. Ich frage mich allerdings, warum Valgrind gewählt wurde. Ich denke, dass Clang AddressSanitizer (asan) und MemorySanitizer (msan) vielfältigere Fehler finden, etwa use-after-return, und zudem viel schneller sind.
    • Go verwendet kein clang/llvm, daher sind solche Tools nicht anwendbar.
    • Go hat bereits seit einigen Jahren eigene Unterstützung für msan/asan.
    • Valgrind ist viel schneller und kann auch an bereits laufende Programme angehängt werden.
    • Valgrind bietet außerdem verschiedene Funktionen wie Speicherverfolgung und Speicherprofiling und ist daher auch aus Sicht des Performance-Trackings hervorragend.
  • Sehr beeindruckend. Eines der größten Probleme in Go sind Profiling und häufige Speicherlecks beziehungsweise Speicherdruck. Ich kenne derzeit kein wirkliches alternatives Tool, um das zu lösen.
    • Darüber würde ich gern mehr hören. Welche konkreten Profiling-Probleme hast du? Falls das In-Use-Speicherprofil zum Verfolgen von Speicherlecks nicht ausreicht, würde mich interessieren, ob du gorefs genutzt hast und welche Art von Speicherdruck problematisch ist. Das könnte helfen. goref-Repo. Zur Einordnung: Ich arbeite bei Datadog an Continuous Profiling und trage regelmäßig zum Go-Runtime-Profiling bei.
    • Ich frage mich, wie in einer Sprache mit GC überhaupt „anhaltende Speicherlecks“ entstehen können.
    • Im Idealfall hätte man wie in anderen Sprachen explizit steuern können, was auf den Stack kommt, statt sich allein auf Escape Analysis zu verlassen. Im Moment muss man mit Optionen wie -gcflags -m=3 oder über Einstellungen im VSCode-Go-Plugin wie ui.codelenses und ui.diagnostic.annotations arbeiten, was unkomfortabel ist.
    • Was „anhaltende Speicherlecks bzw. Speicherdruck“ angeht: In Go sollte man keine Goroutine erzeugen, wenn man nicht sicher weiß, wie sie wieder sauber aufgeräumt wird.
    • pprof funktioniert eigentlich auch ziemlich gut. Ich frage mich, welche zusätzlichen Funktionen du dir wünschst.
  • Dieser Ansatz wirkt vernünftig. Es gibt zwar ein Risiko, falls sich der Client-Request-Mechanismus einmal ändert, aber die Header ändern sich fast nie, besonders nicht beim Hinzufügen neuer Plattformen. Und da Go nur amd64 und arm64 betrifft, ist das Risiko gering. Der eigentliche Vorteil dieser Verbesserung liegt weniger in der Verhinderung von Speicherlecks als in der präzisen Erkennung nicht initialisierten Speichers. Wenn bei wiederverwendetem Speicher kein „Poisoning“ erfolgt, also kein absichtliches Sperren für die Nutzung, wird die Analyse schwierig. Dieses Feature ist auch für die übrigen Tools außer cachegrind und callgrind ziemlich nützlich.
  • Für das Go-Ökosystem fühlt sich diese Verbesserung weniger wie ein Gewinn als eher wie ein kleiner Fehlschlag an. Ich mag Valgrind sehr und habe es in meiner Zeit als C-Entwickler oft verwendet. Aber dass Go Valgrind überhaupt braucht, lässt vermuten, dass Sprache oder Ökosystem irgendwo Defizite haben. Ich benutze Rust seit etwa sechs Jahren und hatte noch nie das Gefühl, Valgrind zu brauchen, abgesehen von einem einzigen Fall bei einem Teamkollegen. Mir ist klar, dass dieses Gefühl wahrscheinlich durch cgo entsteht, aber es wirkt trotzdem irgendwie wie ein Rückschritt.
    • Ich verstehe nicht, warum in den Top-Kommentaren zu Go immer Rust erwähnt wird, oft mit einem spöttischen Unterton. Das wirkt zunehmend defensiv und zugleich etwas überlegen.
    • Der Hauptzweck dieses Features ist weniger korrektes Speicher-Tracking als vielmehr das Testen von Konstantzeit-Code. Diese Erklärung dazu existiert noch.
    • Ich habe Valgrind auch in Rust ein wenig verwendet. Man braucht es nicht oft, aber einen Bedarf gibt es definitiv. Rust kann in sehr unterschiedlichen Umgebungen eingesetzt werden: von High-Level-funktionalem Code bis zu C-ähnlichem Low-Level-Code für Mikrocontroller. Manchmal kommt man ganz ohne unsafe aus, manchmal ist es unverzichtbar. Auch FFI mit C ist ein häufiger Fall, daher entsteht früher oder später Bedarf. Als ich früher ein Rust-Modul für nginx gebaut habe, noch bevor es offizielle oder inoffizielle Bindings gab, machte ich häufig Fehler und bekam Hilfe von Valgrind.
    • Wie oft man es braucht, hängt davon ab, wie viel unsafe-Code man schreibt und wie stark man unsafe-Crates oder C/C++-Bibliotheken einbindet. Auch in Java, .NET oder Node kann es wegen externer Abhängigkeiten Situationen geben, in denen es nötig wird.