Guten Tag euch! 
Mein Name ist Stanislav und ich schreibe gerne Code. Dies ist mein erster englischer Artikel über Habr, den ich aus mehreren Gründen verfasst habe:
Dieser Artikel ist eine englische Version meines allerersten Artikels über Russisch.
Lassen Sie mich die Hauptfiguren in dieser Geschichte vorstellen, die den Fehler behoben haben, der Git daran hinderte, in ReactOS zu laufen - den französischen Entwickler Hermès Bélusca-Maïto (oder nur Hermes mit hbelusca
Spitznamen hbelusca
) und natürlich mich (mit x86corez
Spitznamen x86corez
).
Die Geschichte beginnt mit den folgenden Nachrichten aus dem IRC-Kanal von ReactOS Development:
Jun 03 18:52:56 <hbelusca> Anybody want to work on some small problem? If so, can someone figure out why this problem https://jira.reactos.org/browse/CORE-12931 happens on ReactOS? :D Jun 03 18:53:13 <hbelusca> That would help having a good ROS self-hosting system with git support. Jun 03 18:53:34 <hbelusca> (the git assertion part only).
Nachbesprechung
Da die ReactOS-Zielplattform Windows Server 2003 ist, wurde Git Version 2.10.0 für die Untersuchung ausgewählt - es ist die letzte, die Windows XP und 2003 unterstützt.
Die Tests wurden in der ReactOS-Eingabeaufforderung durchgeführt. Die externen Symptome des Problems waren eher zweideutig. Wenn Sie beispielsweise git ohne zusätzliche Parameter ausführen, wird die Hilfemeldung problemlos in der Konsole angezeigt. Aber sobald Sie git clone
oder auch nur git --version
ausprobieren, bleibt die Konsole in den meisten Fällen einfach leer. Gelegentlich wurde eine fehlerhafte Nachricht mit der Behauptung angezeigt:
git.exe clone -v "https://github.com/minoca/os.git" "C:\Documents and Settings\Administrator\Bureau\minocaos" A ssertionfailed ! P rogram : C : \ P rogram F iles \ G it \ mingw 3 2 \ bin \ git . exe F ile : exec _ cmd . c , L ine 2 3 E xpression : argv 0 _ path This application has requested the Runtime to terminate in an unusual way. Please contact the application's support team for more information.
Glücklicherweise ist git ein Open-Source-Projekt, und es hat uns bei unserer Untersuchung sehr geholfen. Es war nicht allzu schwierig, den tatsächlichen Codeblock zu finden, in dem eine nicht behandelte Ausnahme aufgetreten ist: https://github.com/git-for-windows/git/blob/4cde6287b84b8f4c5ccb4062617851a2f3d7fc78/exec_cmd.c#L23
char *system_path(const char *path) { /* snip */ #ifdef RUNTIME_PREFIX assert(argv0_path); // it asserts here assert(is_absolute_path(argv0_path)); /* snip */ #endif strbuf_addf(&d, "%s/%s", prefix, path); return strbuf_detach(&d, NULL); }
argv0_path
Variablenwert argv0_path
wird durch diesen Code zugewiesen:
const char *git_extract_argv0_path(const char *argv0) { const char *slash; if (!argv0 || !*argv0) return NULL; slash = find_last_dir_sep(argv0); if (slash) { argv0_path = xstrndup(argv0, slash - argv0); return slash + 1; } return argv0; }
Nachdem ich alle diese Informationen erhalten habe, habe ich einige Nachrichten an den IRC-Kanal gesendet:
Jun 03 19:04:36 <x86corez> hbelusca: https://github.com/git-for-windows/git/blob/4cde6287b84b8f4c5ccb4062617851a2f3d7fc78/exec_cmd.c#L23 Jun 03 19:04:41 <x86corez> assertion is here Jun 03 19:04:57 <hbelusca> yes I know, I've seen the code yesterday. The question is why it's FALSE on ROS but TRUE on Windows. Jun 03 19:06:02 <x86corez> argv0_path = xstrndup(argv0, slash - argv0); Jun 03 19:06:22 <x86corez> xstrndup returns NULL %-) Jun 03 19:06:44 <hbelusca> ok, so what's the values of argv0 and slash on windows vs. on ROS? :P Jun 03 19:08:48 <x86corez> good question!
Der Variablenname legt nahe, dass der tatsächliche Wert von argv[0]
- normalerweise enthält er den ausführbaren Namen im Nullindex. Aber später war nicht alles so offensichtlich ...
Jun 03 20:15:21 <x86corez> hbelusca: surprise... git uses its own xstrndup implementation Jun 03 20:15:35 <x86corez> so I can't simply hook it xD Jun 03 20:15:56 <hbelusca> well, with such a name "xstrndup" it's not surprising it's its own implementation Jun 03 20:16:04 <x86corez> probably I would need an user-mode debugger... like OllyDbg Jun 03 20:16:09 <hbelusca> that's everything but standardized function. Jun 03 20:16:24 <hbelusca> x86corez: ollydbg should work on ROS. Jun 03 20:16:30 <mjansen> what are you breaking today? Jun 03 20:16:44 <x86corez> mjansen: https://jira.reactos.org/browse/CORE-12931 Jun 03 20:16:51 <hbelusca> (of course if you also are able to compile that git with symbols and all the stuff, it would be very nice)
Aktion
Danach habe ich beschlossen, git direkt aus der Quelle zu kompilieren, damit ich interessierende Variablen "on the fly" direkt auf die Konsole drucken kann. Ich habe dieses Schritt-für-Schritt-Tutorial befolgt und zum Kompilieren einer kompatiblen Git-Version diesen Zweig ausgewählt: https://github.com/git-for-windows/git/tree/v2.10.0-rc2
Das Einrichten der mingw32-Toolchain verlief reibungslos und ich habe gerade mit dem Erstellen begonnen. Es gibt jedoch fast immer undokumentierte Fallstricke in solchen Schritt-für-Schritt-Tutorials, und ich bin sofort auf eines davon gestoßen:

Durch Versuch und Irrtum sowie durch Verwendung von Hinweisen aus der Halle (IRC-Kanal) wurden alle Fehler bei der Kompilierung behoben. Wenn jemand meinen Schritten folgen möchte, ist hier der Unterschied, der den Compiler glücklich macht: https://pastebin.com/ZiA9MaKt
Um zu vermeiden, dass während der Initialisierung mehrere Funktionen aufgerufen werden, und um den Fehler leicht zu reproduzieren, habe ich beschlossen, mehrere Debug-Ausdrucke direkt am Anfang der main()
-Funktion zu platzieren, die sich im Fall von git in common-main.c
:
int main(int argc, const char **argv) { /* * Always open file descriptors 0/1/2 to avoid clobbering files * in die(). It also avoids messing up when the pipes are dup'ed * onto stdin/stdout/stderr in the child processes we spawn. */ //DebugBreak(); printf("sanitize_stdfds(); 1\n"); sanitize_stdfds(); printf("git_setup_gettext(); 1\n"); git_setup_gettext(); /* * Always open file descriptors 0/1/2 to avoid clobbering files * in die(). It also avoids messing up when the pipes are dup'ed * onto stdin/stdout/stderr in the child processes we spawn. */ printf("sanitize_stdfds(); 2\n"); sanitize_stdfds(); printf("git_setup_gettext(); 2\n"); git_setup_gettext(); printf("before argv[0] = %s\n", argv[0]); argv[0] = git_extract_argv0_path(argv[0]); printf("after argv[0] = %s\n", argv[0]); restore_sigpipe_to_default(); printf("restore_sigpipe_to_default(); done\n"); return cmd_main(argc, argv); }
Ich habe die folgende Ausgabe:
C:\>git --version sanitize_stdfds(); 1 git_setup_gettext(); 1 sanitize_stdfds(); 2 git_setup_gettext(); 2 before argv[0] = git after argv[0] = git restore_sigpipe_to_default(); done A ssertionfailed ! (cutted a part of error message which is the same as the one above)
Man kann davon ausgehen, dass hier alles in Ordnung ist, der Wert von argv[0]
ist korrekt. Ich habe die Idee, git im Debugger auszuführen, zum Beispiel OllyDbg, aber etwas ist schief gelaufen ...
Jun 04 01:54:46 <sanchaez> now please try gdb/ollydbg in ROS Jun 04 01:58:11 <sanchaez> you have gdb in RosBE Jun 04 01:58:20 <sanchaez> just in case :p Jun 04 01:59:45 <x86corez> ollydbg says "nope" with MEMORY_MANAGEMENT bsod Jun 04 02:00:07 <x86corez> !bc 0x0000001A Jun 04 02:00:08 <hTechBot> KeBugCheck( MEMORY_MANAGEMENT ); Jun 04 02:00:13 <hbelusca> :/ Jun 04 02:00:49 <sanchaez> welp Jun 04 02:00:56 <sanchaez> you only have one option now :D
Und genau hier schlug Sanchaez eine hervorragende Idee vor, die viele Dinge beleuchtet!

Es wurde keine Bestätigung mehr vorgenommen, und git hat seine Version erfolgreich gedruckt.
Jun 04 02:23:40 <x86corez> it prints! Jun 04 02:23:44 <x86corez> but only in gdb Jun 04 02:23:53 <hbelusca> oh Jun 04 02:24:00 <hbelusca> C:\git/git.exe Jun 04 02:24:13 <hbelusca> I wonder whether it's the same in windows, or not.
Der Fall wurde aus dem Totpunkt verschoben, und ich habe verschiedene Methoden ausprobiert, um git in der Eingabeaufforderung auszuführen, und den richtigen Weg gefunden!

Das Problem war eindeutig, dass Git den vollständigen Pfad in der Befehlszeile erwartete. Also habe ich die Debug-Ausgabe unter Windows verglichen. Die Ergebnisse haben mich etwas überrascht.

Aus irgendeinem Grund enthielt der Wert argv[0]
den vollständigen Pfad zur Binärdatei git.exe.
Jun 05 23:01:44 <hbelusca> x86corez: can you try to run git also by not using cmd.exe? Jun 05 23:02:05 <hbelusca> (to exclude the possibility it's cmd that doesn't call Createprocess with a complete path) Jun 05 23:02:09 <hbelusca> while I think it should... Jun 05 23:02:30 <x86corez> not using cmd... moment Jun 05 23:02:55 <hbelusca> x86corez: alternatively, on windows, try starting git using our own cmd.exe :)
Hermes schlug vor zu prüfen, ob ReactOS cmd.exe hier eine schuldige Komponente ist ...

Dieser Screenshot bestätigte jedoch, dass das eigentliche Problem woanders liegt.
Jun 05 23:04:38 <x86corez> ROS cmd is not guilty Jun 05 23:07:57 <hbelusca> If there was a possibility to consult the received path, before looking at the contents of argvs... ? Jun 05 23:08:30 <x86corez> dump contents of actual command line? Jun 05 23:08:39 <hbelusca> yeah Jun 05 23:09:39 <hbelusca> The thing you retrieve using GetCommandLineW Jun 05 23:10:03 <hbelusca> (which is, after simplifications, basically : NtCurrentPeb()->ProcessParameters->CommandLine ) Jun 05 23:10:59 <hbelusca> Also I was thinking it could be a side-effect of having (or not having) git path into the env-vars.... Jun 05 23:12:17 <x86corez> hbelusca, command line is "git --version" Jun 05 23:12:34 <hbelusca> Always? Jun 05 23:12:39 <x86corez> Yes, even on Windows Jun 05 23:15:13 <hbelusca> ok but then it would be nice if these different results are at least the same on Windows and on ROS, so that we can 100% exclude problems outside of msvcrt.
Die letzte Option war das Testen von ReactOS msvcrt.dll unter Windows. Ich habe versucht, die Datei in demselben Verzeichnis abzulegen, in dem sich git.exe befindet, aber es hat nicht geholfen. Mark schlug vor, eine lokale Datei hinzuzufügen:
Jun 05 22:59:01 <mjansen> x86corez: add .local file next to msvcrt.dll ;) Jun 05 22:59:47 <mjansen> exename.exe.local Jun 05 23:00:17 <x86corez> just an empty file? Jun 05 23:00:21 <mjansen> yea Jun 05 23:00:49 <hbelusca> mjansen: do we support these .local files? Jun 05 23:00:52 <mjansen> we dont Jun 05 23:00:54 <mjansen> windows does Jun 05 23:15:48 <x86corez> moment... I'll try with .local Jun 05 23:18:43 <x86corez> mjansen: I've created git.exe.local but it still doesn't load msvcrt.dll in this directory
Aber aus irgendeinem Grund funktionierte diese Methode auch nicht. Vielleicht die Tatsache, dass ich alle Experimente mit der Server Edition von Windows (2008 R2) durchgeführt habe.
Die letzte Idee wurde von Hermes vorgeschlagen:
Jun 05 23:19:28 <hbelusca> last solution: patch "msvcrt" name within git and perhaps other mingwe dlls ^^ Jun 05 23:20:12 <x86corez> good idea about patching!
Also habe ich alle Vorkommen von msvcrt
in git.exe durch msvcrd
Verwendung von WinHex ersetzt und ReactOS msvcrt.dll entsprechend umbenannt, und hier sind wir:

Jun 05 23:23:29 <x86corez> Yes! guilty is msvcrt :) Jun 05 23:25:37 <hbelusca> ah, so as soon as git uses our msvcrt we get the problem on windows. Jun 05 23:25:38 <x86corez> hbelusca, mjansen, https://image.prntscr.com/image/FoOWnrQ4SOGMD-66DLW16Q.png Jun 05 23:25:58 <hbelusca> aha and it asserts <3 Jun 05 23:26:03 <hbelusca> (it shows the assertion now)
Fehler wurde Jetzt treffen wir die gleiche Behauptung, aber in Windows! Und das bedeutet, dass die Ursache unserer Probleme in einer der ReactOS msvcrt-Funktionen liegt.
Beachten Sie auch, dass die Bestätigungsmeldung in Windows korrekt angezeigt wird.
Jun 05 23:26:13 <x86corez> but it prints text and correctly. Jun 05 23:26:20 <hbelusca> oh Jun 05 23:26:33 <x86corez> and on ROS it doesn't print in most cases xD Jun 05 23:26:38 <hbelusca> so also it excludes another hypothesis, namely that it could have been a bug in our msvcrt/crt Jun 05 23:26:56 <hbelusca> So possibly a strange bug in our console
Um das eigentliche Problem zu lösen, mussten wir die API-Funktion in msvcrt finden, die den vollständigen Pfad der aktuellen Anwendung angibt. Ich habe ein bisschen gegoogelt und angenommen, dass das Problem bei der Funktion _pgmptr
.
Jun 06 00:07:43 <x86corez> https://msdn.microsoft.com/en-us/library/tza1y5f7.aspx Jun 06 00:07:57 <x86corez> When a program is run from the command interpreter (Cmd.exe), _pgmptr is automatically initialized to the full path of the executable file. Jun 06 00:08:01 <x86corez> this ^^) Jun 06 00:08:50 <hbelusca> That's what GetModuleFileName does. Jun 06 00:09:04 <x86corez> yeah Jun 06 00:10:30 <hbelusca> Of course in ROS msvcrt we don't do this, but instead we initialize pgmptr to what argv[0] could be. Jun 06 00:11:08 <hbelusca> That's one thing. Jun 06 00:11:34 <hbelusca> The other thing is that nowhere it appears (in MS CRT from VS, or in wine) that argv is initialized using pgmptr. Jun 06 00:13:33 <x86corez> hbelusca, I've checked argv[0] in some ROS command line tools, running them in Windows Jun 06 00:13:56 <x86corez> they all interpret argv[0] as command line, not full path Jun 06 00:14:04 <x86corez> so... I think it's git specific behaviour Jun 06 00:14:16 <x86corez> or specific mingw compiler settings Jun 06 00:28:12 <hbelusca> x86corez: I'm making a patch for our msvcrt, would be nice if you could test it :) Jun 06 00:28:21 <x86corez> I'll test it
Hermes hat einen Link zum Patch gesendet, ich habe ihn manuell angewendet und das System neu aufgebaut, und nach diesen Schritten ist das ursprüngliche Problem auf magische Weise verschwunden!

Jun 06 00:34:26 <x86corez> hbelusca, IT WORKS! Jun 06 00:35:10 <hbelusca> LOL Jun 06 00:35:18 <hbelusca> So it seems that something uses pgmptr to rebuild an argv. Jun 06 00:35:52 <x86corez> I've even able to clone :) Jun 06 00:36:19 <hbelusca> \o/ Jun 06 00:36:21 <gigaherz> 2.10.0-rc2? not the release? Jun 06 00:36:24 <hbelusca> ok I'm gonna commit that stuff. Jun 06 00:36:43 <hbelusca> x86corez: gonna have ROS self-hosting <33 Jun 06 00:36:48 <x86corez> yeah! Jun 06 00:37:01 <x86corez> gigaherz: I've built that from sources Jun 06 00:37:37 <gigaherz> oh, for testing this bug? o_O Jun 06 00:37:50 <sanchaez> yes, you missed the fun :p Jun 06 00:39:46 <x86corez> git 2.10.0-windows.1 (release) works too! Jun 06 00:39:54 <encoded> commit!!!
Nachwort
Ein weiterer Fehler, der indirekt verhindert, dass ReactOS sich selbst erstellt, wurde dank gemeinsamer Anstrengungen behoben. Der lustige Zufall ist die Tatsache, dass nicht lange bevor ein weiterer Fehler in derselben dynamischen msvcrt-Bibliothek (nämlich in der qsort
Funktion) behoben wurde, die es nicht ermöglichte, die USB-Treiber in ReactOS zu kompilieren.
Ich bin an der Entwicklung vieler Projekte beteiligt, die in verschiedenen Programmiersprachen geschrieben sind, sowohl Closed als auch Open Source. Ich trage seit 2014 zum ReactOS-Projekt bei, habe aber erst 2017 begonnen, aktiv zu helfen und tatsächlich Code zu schreiben. Es ist besonders interessant, in diesem Bereich zu arbeiten, da es sich um ein gesamtes Betriebssystem handelt! Sie spüren eine enorme Größenordnung des Ergebnisses, in das die Anstrengungen investiert wurden, sowie ein angenehmes Gefühl, dass es einen Fehler weniger gibt! :) :)
Jemand mag sich fragen, warum ich zum Beispiel zu ReactOS und nicht zu Linux beitrage. Historisch gesehen schreibe ich in den meisten Fällen Programme für Windows und meine Lieblingsprogrammiersprache ist Delphi. Vielleicht ist die Architektur von Windows NT zusammen mit der Win32-API deshalb für mich sehr interessant, und das ReactOS-Projekt der kostenlosen Windows-Alternative lässt den alten Traum wahr werden - es ermöglicht Ihnen herauszufinden, wie alles in der Praxis unter der Haube funktioniert.
Ich hoffe, Ihnen hat mein erster englischer Artikel hier gefallen. Ich freue mich auf Ihre Kommentare!
Links