Debugging-Story

Heute habe ich es endlich geschafft, den „Fehler“ in einem Firefox-Plugin (man könnte auch sagen „plag ihn“) zu finden. Es geht um das evince-browser-plugin welches für einen Firefox 10 auf einer embedded-Linux Plattform basierend auf ARM zu übersetzen war. Übersetzt hat es auch ganz gut, aber laufen wollte es nicht. Das öffnen von PDFs im Firefox endete in schneeweißen Browserfenstern und auf einem xterm von dem der Firefox augerufen wurde las man: toolkit is not Gtk2

Das war die Ausgangssituation und natürlich war ich mir sicher, dass der Browser mit Gtk2 gebaut war. Also hieß es nun rausfinden, was da schiefgeht.

Dieser Fehlertext war sehr schnell in der Initialisierungsfunktion des Plugins entdeckt. In der Datei evbp.c steht folgendes:

Dieser Fehlertext war schnell um ein %d %d erweitert um die Werte für ret und toolkit auszugeben.  Ein Testlauf zeigte dann auch, dass nach dem API-Aufruf über den Funktionsvektor in ret der Wert 2 stand. Was will mir diese 2 sagen? Ein Blick nach npapi.h vom Firefox-Browser bringt Klarheit:

Der Wert 2 ist also gleichzusetzen mit NPERR_INVALID_INSTANCE_ERROR.

Jetzt habe ich mich mal auf die Suche nach der Funktion in den Firefox-Sourcen gemacht und wurde in der Datei PluginModuleChild.cpp im Verzeichnis dom/plugins/ipc fündig. Da sieht man dann die Funktion die bestimmte Parameter des Browsers abfragen kann. Und das sieht so aus.

Und da war dann für mich erstmal der Moment erreicht an dem ich an meinen Programmierkünste zu zweifeln anfing. Die obige Funktion ist ja durchaus überschaubar und bei der switch-Anweisung ist der Zweig für den Wert NPNVToolkit so überschaubar, dass man ungläubig den Kopf schüttelt, denn der Wert 2 gehört nicht zu den Werten die dieser Programmzweig zurückliefern kann.

Also habe ich mal den gdbserver auf meinem ARM-Board angeworfen und versucht, das remote zu debuggen. Leider ist das Debuggen eines Firefox eine deutlich größere Herausforderung wie das Debuggen von „Hello World“ wie man es in den Tutorials findet. Eine Schwierigkeit ist, dass der Browser sich selbst forked wenn ein Plugin gestartet wird und einen neuen Job „plugin-container“ erzeugt. D.h. man braucht dann wieder irgendwie eine weitere Debugger-Instanz die den Kind-Prozess traced und Dinge wie follow-fork-mode funktionieren beim Remote-Debugging auch nicht. Auch catchpoints laufen nicht im remote-debugging Modus.

Es ist mir dann schon gelungen, das Plugin in dieser Routine anzuhalten, nur wenn man zulange wartet, dann schlägt wohl irgendein Watchdog im Parent zu und das Browserfenster schaltet auf schwarz und ab hier hat man einen Zombie-Prozess der plötzlich nicht mehr zu debuggen ist.

Das was ich über Tracing auf Source-Level-Ebene rausfand, war dass der switch wohl noch angesteuert wird und in der Variablen die da geprüft wird der Wert 13 drin stand.

Gucken wir in npapi.h wie dieser NPNVToolkit definiert ist:

Also 13 ge-oder-t mit einer NP_ABI_MASK die wohl 0 ist, denn der Debugger zeigte ja die 13 an. Aber warum gibt das blöde Teil dann einen falschen Fehlerwert zurück. Um hier weiter zu kommen habe ich die Routine dann mal mit dem gdb disassembliert. Das sieht dann so aus, wenn man den Schalter „/m“ mit angibt, der dann auch Source-Zeilen einstreut.

Bei dieser Darstellung muss man aufpassen, dass man nicht reinfällt. Die vermeintliche Abfolge der Assembleranweisungen ist nämlich nicht so, wie sie oben zu sehen ist, sondern hier zeigt der Disassembler erst mal eine Zeile aus dem Source an und dann, was er daraus gemacht hat. Die beiden Assemblerzeilen nach der Sourcezeile 1110 sind also eigentlich adressmäßig im Block darüber stehen. Wenn man den tatsächlichen Maschinencode sehen will muss man mit dem Schalter „/r“ disassemblieren, damit gibt es dann „raw“-Format.

Aber auch so bemerkte ich eine sehr interessante Zeile ab Adresse 0x40dbaa8c, hier wird nämlich auf den Wert 268435469 geprüft der in hexadezimaler Darstellung 0x1000000d entspricht. Das „d“ am Ende steht für die 13 (dezimal) die mir der Debugger anzeigt. Geht vielleicht der Firefox davon aus, dass oben erwähnte NP_ABI_MASK gar nicht Null ist..?

Also nochmal ein Blick in npapi.h. Da findet man dann folgenden Code-Block:

Und hier sieht man, dass der Wert dieser Maske wohl von diversen Umgebungsvariablen abhängt. Also habe ich als nächstes mal die Compile-Logs geprüft die mir OpenEmbedded dank auskommentiertem „inherit rm_work“  aufgehoben hat. Und siehe da, im Firefox sehe ich bei den Compilerflags „-DXP_UNIX=1“. Bei den Compile-Logs des Plugins ist jedoch kein solches Flag drin.

Das erklärt nun auch das absonderliche Verhalten. Klar, beide Teile der Plugin-API nutzten die Datei npapi.h, nur ist für den Browser der Wert für NPNVToolkit eben 268435469 und für das Plugin 13. Klar, dass die beiden sich dann nicht verstehen und die API-Funktion den Fehlerausgang mit dem NPERR_INVALID_INSTANCE_ERROR nimmt.

Also mal kurz das Bitbake-Rezept für das Plugin um dieses Compilerflag erweitert und schon funktioniert alles wie es soll.

Im Nachhinein betrachtet war der ganze Schlamassel mehreren Ursachen geschuldet:

  • Die Fehlermeldung des Plugins könnte in die Kategorie „irreführende Fehlermeldungen“ einsortiert werden.
  • Beim Rezept-Schreiben habe ich zwar das Include-Verzeichnis in den CFLAGS angegeben, nicht aber den Parameter von dem ich erst durch das Debuggen erfuhr.
  • In einer idealen Welt hätte Firefox bei der Installation eine PackageConfig-Datei (Endung „.pc“) angelegt in der die richtigen Compilerflags stehen. Hat er aber nicht.
  • In einer idealen Welt hätte die configure-Routine diese Compiler-Flags ausgelesen und verwendet ohne dass ich sie explizit angeben muss. Tut sie aber nicht, was aber für mich nachvollziehbar ist nachdem ich mich ein wenig mit den Autotools auseinandergesetzt habe. Da verstehe ich schon, dass man solche Checks nicht so einfach implementiert.

Fazit: Problem erkannt, Problem gelöst. Und jede Menge gelernt über Dinge wie Autotools, gdb, remote-debugging, ARM-Assemblercode und wie man Fehler in fremden Programmen aufspüren kann. War anstrengend, aber eben auch eine schöne geistige Herausforderung. Das nächste mal aber bitte ein Debug-Kandidat auf dem Niveau von „Hello World“… 🙂

[ratings]