Главная » Хабрахабр » Заводим Git for Windows под ReactOS

Заводим Git for Windows под ReactOS

image Всем доброго времени суток!

Это моя первая статья на Хабре, на написание которой меня сподвигло несколько факторов: Меня зовут Станислав, и я люблю писать код.

  • Недостаток статей технического плана в хабе ReactOS
  • Недавнее возвращение Geektimes на Хабр
  • Возможность собрать ReactOS в ReactOS
  • Довольно интересный случай исправления проблемы в ReactOS, в котором я принимал непосредственное участие

Позвольте представить вам виновников данного торжества (исправленного бага, который мешал запуску Git в ReactOS) — хорватский разработчик Hermès Bélusca-Maïto (далее просто Гермес, с ником hbelusca), и собственно я (с ником x86corez).

История начинается со следующих сообщений из IRC-канала разработчиков ReactOS:

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? 😀
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).

Разбор полётов

10. Поскольку на данный момент ReactOS нацелен на совместимость с Windows Server 2003, в качестве подопытного кролика был выбран Git версии 2. 0 — последней, в которой заявлена поддержка Windows XP и 2003.

Например, при запуске git без указания дополнительных аргументов, он без каких-либо проблем выводил справочную информацию в консоль. Тестирование осуществлялось в командной строке ReactOS, внешние симптомы проблемы были довольно неоднозначными. Но стоило попробовать git clone или хотя бы git --version, в большинстве случаев консоль совсем ничего не выводила, либо изредка показывала битое сообщение с assertion:

git.exe clone -v "https://github.com/minoca/os.git" "C:\Documents and Settings\Administrator\Bureau\minocaos" A s s e r t i o n f a i l e d ! P r o g r a m : C : \ P r o g r a m F i l e s \ G i t \ m i n g w 3 2 \ b i n \ g i t . e x e F i l e : e x e c _ c m d . c , L i n e 2 3 E x p r e s s i o n : a r g v 0 _ p a t h This application has requested the Runtime to terminate in an unusual way.
Please contact the application's support team for more information.

Исследованию проблемы очень поспособствовало, что клиент git — с открытым исходным кодом, и найти строку, на которой возникало исключение, не составило большого труда: https://github.com/git-for-windows/git/blob/4cde6287b84b8f4c5ccb4062617851a2f3d7fc78/exec_cmd.c#L23

char *system_path(const char *path)
{ /* вырезано */ #ifdef RUNTIME_PREFIX assert(argv0_path); // проблема возникает здесь assert(is_absolute_path(argv0_path)); /* вырезано */
#endif strbuf_addf(&d, "%s/%s", prefix, path); return strbuf_detach(&d, NULL);
}

А значение переменной argv0_path задавалось данным участком кода:

const char *git_extract_argv0_path(const char *argv0)
return argv0;
}

После выяснения этих деталей я отписался на 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? 😛
Jun 03 19:08:48 <x86corez> good question!

Но в дальнейшем всё оказалось не столь очевидно... Имя переменной как бы намекает, что значение было получено из argv[0] — обычно в нулевом индексе этого массива содержится строка с именем команды, которой была вызвана текущая программа.

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)

Переход к активным действиям

Мной был выбран данный пошаговый мануал, и для сборки совместимой версии я выбрал эту ветку: https://github.com/git-for-windows/git/tree/shears/v2. После этого я принял решение собрать git из исходников, чтобы было проще "на лету" выводить значения интересующих переменных прямо в консоль. 0-rc2 10.

Но в подобных пошаговых мануалах зачастую есть недокументированные подводные камни, и на один из них я сразу же наткнулся: Настройка тулчейна mingw32 прошла без проблем, и я запустил сборку.

image

Если кто-то захочет повторить мой путь, делюсь готовым диффом для успешной сборки: https://pastebin.com/ZiA9MaKt Путём проб и ошибок, а также пользуясь подсказками из зала (IRC-канала) все проблемы компиляции удалось решить.

Дабы исключить влияние множества функций при инициализации, я решил вывести несколько отладочных сообщений прямо в начале функции main(), которая в случае git расположена в файле 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);
}

Вывод получился следующий:

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 s s e r t i o n f a i l e d ! (часть вырезана, совпадает с ранее приведённой)

Пришла идея погонять git внутри отладчика, например в OllyDbg, но что-то пошло не так... Казалось бы, всё нормально, argv[0] таким и должен быть.

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 😀

И вот тут sanchaez подсказал отличную идею, которая пролила свет на многое!

image

Исключения больше не возникало, и git успешно печатал свой номер версии.

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.

Дело сдвинулось с мёртвой точки, и я решил попробовать по-разному запускать git в командной строке, и не прогадал!

image

Тогда я решил проверить, какие данные будут выведены в Windows. Проблема явно была в том, что git ожидал полный путь в командной строке. Результаты меня немного удивили.

image

В переменной argv[0] почему-то был полный путь к приложению.

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 🙂

Гермес предложил проверить, может быть проблема где-то в командной строке ReactOS...

image

Вариант с оболочкой cmd отпадает. Но это оказалось не так.

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.

Я попробовал положить файл в той же директории, где лежал git.exe, но это не помогло. Теперь оставалось лишь протестировать библиотеку msvcrt.dll из ReactOS под Windows. Марк посоветовал способ с файлом .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

Возможно сказался тот факт, что все эксперименты я проводил на серверной версии Windows Server 2008 R2. Но и этот вариант почему-то тоже не сработал.

Последнюю идею предложил Гермес:

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!

При помощи WinHex я заменил все вхождения подстроки msvcrt в файле git.exe на msvcrd, и переименовал msvcrt.dll от ReactOS соответствующим образом, и вот что получилось:

image

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)

А это означает, что источник проблемы в реализации одной из функций msvcrt из ReactOS. Теперь уже в Windows мы получили то же самое сообщение об ошибке!

Можно также отметить, что под 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

Немного погуглив, я предположил, что дело в функции _pgmptr. Оставалось лишь выяснить, какая функция в msvcrt предоставляет полный путь к исполняемому файлу запущенного приложения.

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

Гермес прислал ссылку на патч, я вручную применил его и пересобрал систему, после чего всё магическим образом заработало как надо!

image

Jun 06 00:34:26 <x86corez> hbelusca, IT WORKS!
Jun 06 00:35:10 <hbelusca> L O L
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!!!

Послесловие

Забавным совпадением является тот факт, что незадолго до этого был исправлен другой баг в том же msvcrt (а именно в функции qsort), который не позволял собрать драйверы USB в ReactOS. Таким образом, ещё один баг, косвенно препятствующий само-сборке ReactOS внутри ReactOS, был исправлен благодаря коллективным усилиям.

С проектом ReactOS я сотрудничаю с 2014 года, но активно помогать и писать код начал только в 2017. Я участвую в разработке многих проектов, написанных на разных языках программирования, как закрытых, так и с открытым исходным кодом. Чувствуется огромный масштаб результата, в который были вложены усилия, а также приятное ощущение, что одним багом стало меньше! Работать в этой области особенно интересно, потому что это целая операционная система! 🙂

Так исторически сложилось, что в большинстве случаев я пишу программы для Windows, а мой любимый язык программирования — Delphi. Кто-то наверняка задастся вопросом, почему я помогаю именно ReactOS, а не Linux например. Возможно именно поэтому архитектура Windows NT вместе с Win32 API мне очень интересна, а проект свободной операционной системы ReactOS воплощает в реальность давнюю мечту — на практике позволяет узнать, как всё это устроено изнутри.

С нетерпением жду ваших комментариев! Надеюсь, вам было интересно прочитать мою первую статью здесь.

Ссылки


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Создание пакетов для Kubernetes с Helm: структура чарта и шаблонизация

Теперь подойдём к практике с другой стороны — с точки зрения создателя чартов (т.е. Про Helm и работу с ним «в общем» мы рассказали в прошлой статье. И хотя эта статья пришла из мира эксплуатации, она получилась больше похожей на ...

[Из песочницы] Экономим на RAID-контроллере, или как накормить Варю иопсами

В наш век облачных сервисов, AWS Lambda и прочих шаред хостингов абсолютно неосязаемых вычислительных ресурсов иногда хочется немножко своего. Кроме желания, иногда бывают и потребности вдумчиво покрутить тот или иной программный продукт с минимальными затратами на платформу. Найти какие-то излишки ...