Buen dia para ti! 
Mi nombre es Stanislav y me gusta escribir código. Este es mi primer artículo en inglés sobre Habr que hice debido a varias razones:
Este artículo es una versión en inglés de mi primer artículo sobre ruso.
Permítanme presentarles las principales figuras de esta historia que realmente solucionaron el error que impedía que Git se ejecute en ReactOS: el desarrollador francés Hermès Bélusca-Maïto (o simplemente Hermes con el apodo hbelusca
) y, por supuesto, yo (con el apodo x86corez
).
La historia comienza con los siguientes mensajes del canal ReactOS Development IRC:
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).
Debriefing
Dado que la plataforma objetivo de ReactOS es Windows Server 2003, se eligió la versión 2.10.0 de Git para la investigación: es la última que admite Windows XP y 2003.
Las pruebas se realizaron en el símbolo del sistema ReactOS, los síntomas externos del problema fueron bastante ambiguos. Por ejemplo, cuando ejecuta git sin parámetros adicionales, muestra el mensaje de ayuda en la consola sin ningún problema. Pero una vez que prueba git clone
o incluso solo git --version
en la mayoría de los casos, la consola permanece vacía. Ocasionalmente mostraba un mensaje roto con una afirmación:
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.
Afortunadamente, git es un proyecto de código abierto, y ayudó mucho en nuestra investigación. No fue demasiado difícil localizar el bloque de código real donde ocurrió una excepción no controlada: 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); }
Y el valor de la variable argv0_path
es asignado por este código:
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; }
Una vez que obtuve toda esta información, envié algunos mensajes al canal IRC:
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!
El nombre de la variable sugiere que el valor real se deriva de argv[0]
, generalmente contiene el nombre ejecutable en índice cero. Pero posteriormente no todo fue tan obvio ...
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)
Acción
Después de eso, decidí compilar git directamente desde la fuente, así podré imprimir variables de interés "sobre la marcha" directamente en la consola. He seguido este tutorial paso a paso y para compilar la versión compatible de git he seleccionado esta rama: https://github.com/git-for-windows/git/tree/v2.10.0-rc2
La configuración de la cadena de herramientas mingw32 se realizó sin problemas y recién comencé a construir. Sin embargo, casi siempre hay dificultades indocumentadas en tales tutoriales paso a paso, e inmediatamente me encontré con uno de ellos:

A través de prueba y error, además de utilizar sugerencias de la sala (canal IRC), se corrigieron todos los errores de tiempo de compilación. Si alguien quiere seguir mis pasos, aquí está la diferencia para hacer feliz al compilador: https://pastebin.com/ZiA9MaKt
Para evitar llamar a varias funciones durante la inicialización y reproducir el error fácilmente, decidí colocar varias impresiones de depuración justo al comienzo de la función main()
, que se encuentra en common-main.c
en el caso de git:
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); }
Tengo el siguiente resultado:
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)
Uno puede asumir que todo está bien aquí, el valor argv[0]
es correcto. Tengo una idea para ejecutar git dentro del depurador, OllyDbg por ejemplo, pero algo salió mal ...
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
¡Y justo aquí Sanchaez sugirió una excelente idea que arroja luz sobre muchas cosas!

La afirmación ya no ocurrió, y git imprimió con éxito su versión.
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.
El caso se movió desde el punto muerto, e intenté diferentes formas de ejecutar git en el símbolo del sistema, ¡y encontré el camino correcto!

El problema era claramente que git esperaba la ruta completa en la línea de comando. Así que comparé su salida de depuración en Windows. Los resultados me sorprendieron un poco.

Por alguna razón, el valor argv[0]
contenía la ruta completa al binario 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 sugirió verificar si ReactOS cmd.exe es un componente culpable aquí ...

Pero esta captura de pantalla confirmó que el problema real está en otro lugar.
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.
La última opción era probar ReactOS msvcrt.dll en Windows. Traté de colocar el archivo en el mismo directorio donde se encuentra git.exe, pero no ayudó. Mark sugirió agregar un archivo .local:
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
Pero por alguna razón este método tampoco funcionó. Quizás el hecho de que hice todos los experimentos en la edición del servidor de Windows (2008 R2).
La última idea fue sugerida por Hermes:
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!
Así que reemplacé todas las apariciones de msvcrt
en git.exe con msvcrd
usando WinHex, y renombré ReactOS msvcrt.dll en consecuencia, y aquí estamos:

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)
error se convirtió ¡Ahora llegamos a la misma afirmación, pero en Windows! Y eso significa que la fuente de nuestros problemas está en una de las funciones de ReactOS msvcrt.
También vale la pena señalar que el mensaje de aserción se muestra correctamente en Windows.
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
Entonces, para resolver el problema real, tuvimos que encontrar la función API en msvcrt que proporciona la ruta completa de la aplicación actual. Busqué en Google un poco y supuse que el problema es con la función _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 envió un enlace al parche, lo apliqué manualmente y reconstruí el sistema, y después de estos movimientos, ¡el problema original desapareció mágicamente!

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!!!
Epílogo
Dicho esto, otro error que evita indirectamente que ReactOS se construya se ha solucionado gracias a los esfuerzos colectivos. La coincidencia divertida es el hecho de que no mucho antes de que se solucionara otro error en la misma biblioteca dinámica msvcrt (es decir, en la función qsort
) que no permitía compilar los controladores USB en ReactOS.
Participo en el desarrollo de muchos proyectos escritos en diferentes lenguajes de programación, tanto de código cerrado como de código abierto. Estoy contribuyendo al proyecto ReactOS desde 2014, pero comencé a ayudar activamente y realmente escribo código solo en 2017. ¡Es especialmente interesante trabajar en esta área porque es un sistema operativo completo! ¡Usted siente una gran escala del resultado en el que se invirtieron los esfuerzos, así como una agradable sensación de que hay un error menos! :)
Alguien puede preguntarse por qué estoy contribuyendo a ReactOS y no a Linux, por ejemplo. Históricamente, en la mayoría de los casos, escribo programas para Windows y mi lenguaje de programación favorito es Delphi. Quizás es por eso que la arquitectura de Windows NT junto con la API Win32 es muy interesante para mí, y el proyecto ReactOS de la alternativa gratuita de Windows hace realidad el viejo sueño: le permite descubrir cómo funciona todo en la práctica.
Espero que hayas disfrutado mi primer artículo en inglés aquí. Espero sus comentarios!
Enlaces