Menuekopf

Montag, 9. August 2021

Selbstreparierende Betriebssysteme (die IgnoreFreeLibrary-Trap, oder wenn Computer die Kontrolle übernehmen :-( )

Aus gegebenem Anlass will ich heute aus dem - manchmal nicht ganz so gewöhnlichen - Alltag eines Softwareentwicklers berichten. Leser, die es lieber kurz mögen, können sich im zweiten Teil sofort auf die harten Fakten stürzen (einfach eine Textsuche nach IgnoreFreeLibrary anwerfen oder ans Ende dieses Beitrags scrollen).


Prolog:

Dass es mit dem Programmieren alleine nicht getan ist, setze ich als bekannt voraus. Jedes Computerprogramm weist den ein oder anderen Bug auf. Neben dem eigentlichen Entwickeln (Programmieren) fällt also irgendwann auch zwangsläufig Supportarbeit an. Selbst dann, wenn eigenständige Supportabteilungen in größeren SW-Formen zwischengeschaltet sein sollten.

  • Manche dieser Bugs machen richtige Probleme und man geht ihnen daher auf den Grund und findet - hoffentlich - irgendwann auch die Ursache und kann den Bug dann fixen (sprich eliminieren). 
  • Andere Bugs schlummern mitunter aber nur so vor sich hin und begnügen sich alleine mit ihrem Dasein. Das sind wohl die netten/braven Bugs, die sich möglicherweise denken, was soll ich die User und die Entwickler ärgern und damit meine Existenz aufs Spiel setzen -> bringt nur Ärger, also besser die Füße stillhalten, dann falle ich niemanden auf und habe trotzdem (k)einen Spaß :)


Hinweis in eigener Sache: Zum Zwecke der grafischen Illustration habe ich diesen Bug auf mein privates TrainingLab Software Projekt heruntergebrochen. Der hier beschriebene Fall trat bei einer  anderen Anwendung auf, auf die ich hier aber aus diversen Gründen nicht verweisen kann. Durch manuelles Anlegen eines bestimmten Registry-Eintrags kann man das hier vorgestellte Phänomen quasi mit jeder Windows Software, die externe DLLs nutzt, reproduzieren.


Als Softwareentwickler wird man sich eher selten mit der letzteren Variante aktiv auseinandersetzen. Manchmal fallen diese speziellen stillen Bugs - wenn überhaupt - nur bei einem Review auf, oder aber auch rein zufällig. Mitunter erst dann, wenn ein Kollege den Code reviewt (Vier-Augen-Prinzip) oder am betreffenden Code Änderungen vorgenommen werden und man dann auch einmal die ein oder andere ältere Funktion erweitert oder verschönert.

Wie auch immer diese beiden Arten gelagert sind, das sind m.E. die beiden Normfälle, die man bezüglich Programmierfehler irgendwann dann doch einmal auf dem Monitor haben wird. Der ersten Variante stellt man aktiv nach, die Zweite läuft einem meistens beiläufig über den Weg.


Keine Normalität ohne Ausnahme

Ebenso wie das Leben immer mal wieder die ein oder andere Überraschung für uns bereithält, kommt es hin und wieder auch in der Softwareentwicklung vor, dass es knallt, ohne dass ein (echter) Bug vorliegen muss. 
Ja richtig, das kommt schon mal vor, ausnahmsweise alles richtig gemacht im Leben (oder beim Programmieren) und trotzdem heißt es dann Gehe zurück auf  Los. Also das Wochenende schön brav mit der Familie verbracht und im noch schöneren Taunus Wandern gewesen - braver geht es ja eigentlich kaum noch! - und trotzdem kommt dann der Mann mit dem Hammer und macht Rabatz :-(

Genau das soll hier das Thema sein!

Das Ausgangsszenario

Ein User meldet ein Problem und die obligatorische 'Kann gar nicht sein'-Antwort (= die erste Lektion im Entwickler-Latein), erweist sich als nicht so richtig zielführend. Also ist Debuggen angesagt, man muss und will der Sache (irgendwann) auf den Grund gehen. 

Eines unserer Programme verhaspelt sich neuerdings, nach einer längeren Arbeitssitzung wirft die Anwendung einen Out Of Memory Fehler aus (und das auf einem Windows 10 System mit 32 GB Arbeitsspeicher!)



Um jetzt noch nicht zu sehr ins Detail zu gehen, will ich an dieser Stelle abkürzen: ein erstes Debuggen war ergebnislos, alles fehlerfrei, die 'Kann gar nicht sein'-Antwort hat sich wieder einmal als völlig korrekt erwiesen! :)

Leider hilft das dem User nicht weiter. Das Programm funktioniert nicht mehr in der angedachten Form, eine Deinstallation inkl. Neuinstallation - quasi neben dem neu Booten des Computers die Geheimwaffe schlechthin, die vieles wieder glattzubügeln vermag -, fruchtet nicht :-(

Wer ist hier der Chef

Wenn wir Softwareentwickler eines verdammen, dann ist es dieser Vereinfachungswahn, der irgendwann in unserer Branche zu einer Art Selbstläufer mutiert ist: alles muss einfacher werden, der User soll am besten gar nichts mehr eingeben müssen, die Software soll alles alleine handeln. Mit anderen Worten, der User wird sozusagen entmündigt, die Software wird so smart (schlau), dass sie für den User entscheidet, was er machen soll und was nicht.

Nichts gegen Vereinfachung! Natürlich ist es kein Fehler, Dinge einfacher zu machen und auch bei einer Software sollte man die einfache Nutzbarkeit/Bedienung immer als Zielsetzung haben, aber hey, der User muss noch mit der Software interagieren dürfen! Gewisse Vorgaben sollte die Software zumindest mit dem User abstimmen, wenn die Software nur noch als pseudointeraktive Demo ablaufen soll, dann wäre zumindest die Frage angebracht, ob der User das überhaupt will.

Noch mehr gilt das hier Gesagte für Betriebssysteme (auch BS bzw. OS genannt), also quasi der Schnittstelle zwischen User und den Anwendungen. 
Dass es hierbei eine Art Eigenleben gibt und das BS/OS mitunter selbst Entscheidungen treffen muss, ist klar, aber es sollte Grenzen geben. Und leider verschieben sich diese Grenzen in meinen Augen immer mehr, gleich ob man jetzt unter Linux, Mac OS, Windows oder was auch immer unterwegs ist. Und auch Android/iOS machen hier leider keine Ausnahme, da ist es eigentlich sogar noch schlimmer. 

Ich rede jetzt übrigens  nicht von irgendwelchen versteckten Telemetriedaten, die zum Teil - ohne unser Wissen - gesammelt werden, sondern von der reinen Funktionalität. 

Und hier ist MS mit einer - sicherlich gut gemeinten - (Selbst)Reparaturfunktion meiner Meinung nach deutlich über Ziel geschossen :-(. Ich nenne das die IgnoreFreeLibrary Trap (Falle). Trap umschreibt das sehr gut, da es sich hierbei um eine künstlich herbeigeführte Falle handelt. 

Tiefseetauchen ist angesagt

Zurück zum Problemfall. Nichts mehr geht beim User, oder sagen wir besser, die Software hakt an einer sehr zentralen Stelle und durstet mit einem Mal nach immer mehr Arbeitsspeicher.

Also tut Ursachenforschung (doch) not. Es sei nochmal angemerkt, dass eine längere Debug-Sitzung nötig war, um der Sache letztlich beizukommen. Auch Madame Google musste dabei aktiv mitwirken und um ehrlich zu sein, ohne Madames Hilfe würde ich womöglich immer noch im Dunkeln umherstochern.

Ich hatte vieles auf dem Monitor: 

  • eine amoklaufende Antiviren Software (da habe ich in meinen Berufsjahren auch schon viel Seltsames erleben dürfen) 
  • ein missratenes Update der betreffenden Anwendung -> doch einen daraus resultierenden Bug in der Anwendung konnte ich immer mehr ausschließen, vor allem nachdem wir zwecks Test auf ältere Versionen, die über die Jahre stabil funktionierten, downgegraded hatten (natürlich auch beim User) und das Problem bestehen blieb
  • etwaige Festplatten oder RAM-Probleme konnte ich hingegen schnell ausschließen, dafür war das Problem einfach zu gut und permanent reproduzierbar
  • und auch einen fiesen (Computer)Virus kann man in der heutigen Zeit natürlich leider nicht ausschließen
  • Irgendwann landet man dann mitunter bei den OS-Updates als mögliche Ursache, da diese in der heutigen Zeit aufgrund der Komplexität immer anfälliger werden. Zudem lieferte Google in der Tat einige Treffer, die einige aktuelle spezifische OS Updates erwähnten, die mit der Symptomatik - zumindest in der Theorie - in Verbindung hätten stehen können. 

Leider* konnte diese Ursache (OS-Update) dann doch ausgeschlossen werden, auch wenn die Spur erst mal sehr, sehr heiß erschien, da die betreffende Anwendung auf allen Testrechnern, auf denen die letzten OS-Updates noch nicht aufgespielt waren, problemlos lief. Je mehr man die Sache eingrenzt, desto näher meint man also dem Ziel näherzukommen :). Aber es fanden sich dann doch einige Testrechner, auf denen - trotz aller installierten OS-Updates - keine Probleme auftraten.

* Leider, weil man sich natürlich immer glücklich schätzt, wenn man andere für irgendwelche Miseren verantwortlich machen kann -> OS-Updates kommen da immer gut. Und natürlich habe ich auch schon etliche Male auf Microsoft geschimpft, war dann aber doch immer froh, nicht bei Apple gestrandet zu sein. Im Gegenzug musste der Herr Gates auf meinen PCs aber sehr lange Zeit die Fenster putzen, was alles in allem ein fairer Deal war und weswegen ich keinen Grund sah und sehe, Microsoft den Rücken zu kehren :-) 


Manchmal muss das Glück nachhelfen

Es nützte alles nichts, um dem Problem beizukommen, musste ich einige spezielle Testversionen erstellen, bei denen wirklich sehr viele Debugmeldungen implementiert wurden, in der Hoffnung, zumindest den Kernbereich des Problems so grob abstecken zu können. 

Bewährte Debughilfen


  • Hier ist dann immer etwas Fingerspitzengefühl nötig, die OutputDebugString Funktion ist zumindest auf Windows Systemen eine gute und nützliche Debughilfe, allerdings nicht unbedingt das Mittel der Wahl, wenn man auf den PC des betroffenen Anwenders nicht direkt zugreifen kann.
  • Hinweismeldungen, die beim Aufruf bestimmter Funktionen aufpoppen, können dem Anwender vor Ort zwar helfen, den Ablauf und das Auftreten des Fehlers visuell zu erfassen, können aber wiederum das interne Timing aus dem Tritt bringen, was manchmal dazu führen kann, dass bestimmte Bugs gar nicht aufschlagen, weil z.B. ein Thread Luft zum atmen hat, etc. Threads zu Debuggen ist dann sowieso noch mal eine Sache für sich :-(
  • Und spezielle Logdateien sind auch nicht immer hilfreich, gerade dann nicht, wenn das Auftreten des Bugs vor allem visuell dingfest gemacht werden muss, da er kaum Auswirkungen auf die Programmlogik hat, sondern in dem hier beschriebenen Fall lediglich Einfluss auf die grafische Ausgabe hatte und den Arbeitsspeicher förmlich auffraß. Auf Datenbank-Interaktionen und normale Programmfunktionen hatte dieser Bug nämlich keine Auswirkungen.

Mutter Zufall wollte es dann so, dass ich mit einem Mal den Bug auf einem Entwicklungsrechner reproduzieren konnte. Sozusagen aus heiterem Himmel, trat der Bug vor meinen eigenen Augen in Erscheinung, nachdem die ganze Zeit sämtliche Funktionen/Methoden 100% korrekt abgearbeitet wurden. 

Was ist da los? -> die Kartenansicht wird nicht mehr richtig aktualisiert! Offenbar werden die einzelnen Tracks nicht mehr entladen, was dann auch den wachsenden Speicherverbrauch erklären könnte.

Zwar hatte ich schon eine heiße Spur und konnte den Bug irgendwann auf das Zusammenspiel mit einer speziellen DLL (Dynamic Link Library) runterbrechen, aber das ergab alles noch keinen richtigen Sinn, zumal diese DLL auf allen Rechnern stabil funktioniert - von den nun zwei betroffenen PCs abgesehen.

Also noch einmal einen Scan der Registry getätigt, ob die betreffende Exe vielleicht in irgendeiner ominösen (Ausschluss)Liste gelandet sein könnte und voilà, da ist auf einmal ein Eintrag vorhanden, den ich so überhaupt nicht zuordnen kann und der einen Tag zuvor, als die Anwendung noch problemlos auf meinem Entwicklungsrechner lief, definitiv noch nicht vorhanden war. Und IgnoreFreeLibrary klingt dann doch etwas geheimnisvoll, etwa doch ein fieser Virus, der dann aber wiederum so blöde wäre, die relativ gut einsehbare Registry zu nutzen?

Die IgnoreFreeLibrary Trap

[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers]
"c:\\Program Files (x86)\\IrgendeinHersteller\\IrgendeineAnwendung\\IrgendeineExe.exe"="$ IgnoreFreeLibrary<IrgendeineDLL.dll>"

Wenn man nach IgnoreFreeLibrary googelt, dann bekommt man zwar einige Treffer aufgelistet, aber so richtig schlau wird man aus diesen Treffern noch nicht. Vor allem erfährt man nicht, was diesen Eintrag bewirkt haben könnte bzw. wie er genau zustande gekommen ist. Denn die von diesem Eintrag betroffenen Anwendungen haben ihn nicht getätigt, soviel steht fest!

Ich kann nur sagen, was dieser Eintrag konkret bewirkt: das ist eine Art Filter, der verhindert, dass eine DLL, die mit einer Anwendung verknüpft ist, von dieser Anwendung entladen (gefreed) werden kann. 

DLL Handling


  • Manche DLLs beherbergen Funktionen, die nur selten genutzt werden, daher macht es Sinn, die DLLs innerhalb einer Programmsitzung wieder zu entladen, wenn die in der DLL implementierten Funktionen, abgearbeitet wurden. Diese DLLs werden sozusagen dynamisch eingebunden.
  • Andere DLLs implementieren Funktionen, die regelmäßig aufgerufen werden. Dann macht es Sinn, die DLL direkt beim Programmstart zu laden und erst beim Schließen der Anwendung zu entladen. Das ist häufig bei OS-eigenen DLLs der Fall, deren zur Verfügung gestellte Funktionen von der betreffenden Anwendung quasi Nonstop genutzt werden. Man spricht hierbei von einem statischen Einbinden.

Wie auch immer, im ersten Fall wird man sich als Entwickler etwas dabei gedacht haben, die DLL dynamisch zu nutzen und diese zeitnahe zu entladen - z.B. um einen größeren Speicherblock wieder freizugeben, weil dieser nur temporär benötigt wird - und steht dann mitunter sehr dumm da, wenn diese gewünschte Logik nicht mehr funktioniert, weil sich das Betriebssystem querstellt und es der Anwendung verbietet, die DLL zu freen. Da beim Laden und Entladen einer DLL häufig bestimmte Initialisierungen und Deinitialisierungen erfolgen, kann diese Freigabeblockade die komplette Programmlogik aus dem Tritt bringen.

Soweit ich da nun durchgestiegen bin, kann Windows diesen Eintrag in Eigenregie anlegen, wenn es das Gefühl hat, dass eine Anwendung Probleme beim Verwenden einer bestimmten DLL hat. 
Wahrscheinlich wertet der dahinterstehende Überwachungsprozess Absturzberichte aus und falls ein Programm an einer bestimmten Stelle mehrmals abstürzen sollte, wird es irgendwann als problematisch erachtet und die Dinge nehmen ihren Lauf.

Ich kann jetzt nicht sagen, was beim betreffenden Anwender dazu geführt hat, dass dieser Eintrag angelegt wurde. Auf meinem Entwicklungsrechner (Windows 10) wurde dieser Eintrag wohl durch spezielle Debug-Versionen getriggert, da ich irgendwann beim DLL Handling bewusst Änderungen vorgenommen hatte, die den ein oder anderen Absturz oder Fehlverhalten im Zusammenspiel mit einer DLL provozieren sollten. 

In diesem speziellen Fall kann man natürlich trefflich drüber streiten, ob das OS dann selbst Maßnahmen ergreifen kann/darf, wenn eine Anwendung - wie hier bewusst provoziert - immer wieder zum Absturz neigt. 

Nachtrag 12.08.2021: Im Netz habe ich jetzt Informationen gefunden, die besagen, dass in diesem Fall der Kompatibilitätsassistent eine Hinweismeldung ausgeben würde. Ich selbst kann mich an solch eine Meldung nicht erinnern, will aber auch nicht ausschließen, dass mir dieser Hinweis beim Debuggen entgangen ist (also jene Meldung von mir unbemerkt mit einem Mausklick quittiert wurde).

Der betreffende User hat mir aber glaubhaft versichert, dass das Problem auf seinem PC (auch ein Windows 10 System) ohne vorangegangene Abstürze auftrat und er sich auch nicht an eine sprechende Hinweismeldung erinnern konnte, was mich nun etwas nachdenklich macht. Ich will die Existenz dieser Meldung aber nicht absprechen.

Natürlich können sich solche DLL-Abstürze auch versteckt abspielen, sodass die User das gar nicht mitbekommen, aber etwas ominös ist das dann doch.

Google-Suchergebnisse bzgl. der IgnoreFreeLibrary Trap

Fazit

Ich will das nicht werten, denn irgendwas wird sich Microsoft dabei sicherlich gedacht haben. Und ich behaupte, bei denen sitzen keine Dummköpfe in den Entwicklungsabteilungen! Unschön finde ich als Betroffener allerdings, dass mir dieses Phänomen relativ intransparent erscheint. Weder wird der Anwender auf das Anlegen dieses Registry-Eintrags bewusst hingewiesen - siehe aber meinen obigen Nachtrag vom 12.08.2021 -, noch ist dieses Thema ausführlich dokumentiert. Vielleicht bin ich aber auch nur zu dumm, die Google Suche richtig zu nutzen :-)

Wie auch immer, dieses Phänomen tritt wohl eher selten auf, wenn es aufschlägt, kann es aber ein sehr harten Brocken sein. Die Google Suche liefert dann doch einige Treffer und den Artikeln/Beiträgen kann ich entnehmen, dass nicht nur mir dieses Phänomen einige weitere graue Haare beschert haben dürfte. Das werden immer mehr, wenngleich die meisten neu hinzugekommenen grauen Haare auf die Android Deprecated Trap gehen dürften -> das ist eine andere dieser Fallen, die uns immer wieder einmal beim Programmieren den Wind aus den Segeln nehmen (aber das ist eine andere Geschichte). Ich sollte vielleicht mal wieder eine Runde Pitfall spielen, um besser mit solchen Traps klarzukommen :-)

Schlussfolgerung (quasi das finale Fazit :) )

Wenn man mit seiner Windows-Software auf unerwartete Probleme stößt, dann ist ein genauerer Blick auf den betreffenden Registry-Schlüssel ([HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers]) womöglich nicht falsch. Das kostet kaum Zeit und wenn man dort keinen entsprechenden Eintrag findet, dann kann man eine sehr fiese Ursache schon einmal ausschließen.


Vielleicht ist das hier Beschriebene für alte Programmierhasen ein alter Hut. Ich verdiene mit der Software-Entwicklung aber auch schon einige Jährchen meine Brötchen und dieses Phänomen war für mich komplett neu und die Ursachenforschung hat mich sehr viel Zeit gekostet.

Wenn's nach mir geht, dann können unsere Alltagscomputer in Sachen Smartheit langsam gerne mal wieder etwas auf die Bremse treten -> aber das liegt natürlich auch an uns Anwendern und Entwicklern, ob wir das weiterhin mit uns machen lassen oder nicht.

Danke für Lesen...

Und wie immer noch etwas gute Musik zum wieder Runterkommen :):

2 Kommentare:

Benjamin hat gesagt…

Danke für den ausführlichen Bericht. Bin heute auf exakt das gleiche Problem gestoßen. Habe den halben Code meiner DLL auseinandergenommen und bin in der Versionshistorie zurückgegangen, um den Punkt zu finden, an dem das Entladen der DLL noch funktioniert hat. Nur um am Ende herauszufinden, dass es weder am eigenen Code noch am Code der Host-Anwendung liegt - ein Umbenennen der Host-Executable war die Lösung. regedit angeworfen, nach dem Pfad der Executable gesucht und auf genau den "IgnoreFreeLibrary"-Eintrag gestoßen, den du hier erwähnst. Wirklich fies ;)

WRPSoft hat gesagt…

Hi Benjamin,

Danke für Deinen Kommentar. Mich hat das Eingrenzen des 'Bugs' damals auch sehr viel Zeit gekostet, was dann auch die Motivation war, das alles in einem Blogbeitrag festzuhalten (und sei es nur als interne Gedächtnisstütze für mich selbst).
Von Antiviren-Programmen war ich schon einiges gewohnt (z.B. Zugriff auf eigene Ini-Dateien 'gesperrt' :-)), aber dass das OS selbst eingreift, das war mir dann doch erst mal schwer zu vermitteln.

Irgendwann hat man zwar so eine erste grobe Ahnung, wo man suchen muss, aber klassische Bugs lassen sich normalerweise einfacher dingfest machen.

Ich habe übrigens bis heute NICHT herausgefunden, was genau dieses Verhalten triggert. Auch kann ich nicht mit Sicherheit sagen, ob Windows dann doch einen Assistenten oder kleines Diaologfenster einblendet und den User auf dieses (Sperr)Verhalten hinweist oder nicht. MS hält sich da m.E. ziemlich bedeckt, wirklich schwierig, konkrete Informationen zu diesem Thema einzuholen.

PS: Ich schaue jetzt immer mal in diesen Registryzweig, wenn ich auf einem (fremden) PC Zugriff auf die Registry habe und der User das erlaubt. Manchmal richtig spannend, was man dort findet :)

LG und einen guten Start in die Woche
Ralph