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

в 23:55, , рубрики: C, Git, open source, reactos, self-hosted, Блог компании Фонд ReactOS, Программирование, разработка под windows, реверс-инжиниринг, самокомпиляция, Самосборка, системное программирование, системы контроля версий

Всем доброго времени суток! 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? :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).

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

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

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

git.exe clone -v "https://github.com/minoca/os.git" "C:Documents and SettingsAdministratorBureauminocaos"

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)
{
    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;
}

После выяснения этих деталей я отписался на 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!

Имя переменной как бы намекает, что значение было получено из 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)

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

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

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

image

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

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

    printf("git_setup_gettext(); 1n");
    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(); 2n");
    sanitize_stdfds();

    printf("git_setup_gettext(); 2n");
    git_setup_gettext();

    printf("before argv[0] = %sn", argv[0]);
    argv[0] = git_extract_argv0_path(argv[0]);
    printf("after argv[0] = %sn", argv[0]);

    restore_sigpipe_to_default();
    printf("restore_sigpipe_to_default(); donen");

    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 ! 
(часть вырезана, совпадает с ранее приведённой)

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

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

И вот тут 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

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

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.

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

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

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

Оставалось лишь выяснить, какая функция в msvcrt предоставляет полный путь к исполняемому файлу запущенного приложения. Немного погуглив, я предположил, что дело в функции _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

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

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!!!

Послесловие

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

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

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

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

Ссылки

Автор: Stas'M

Источник

Поделиться

* - обязательные к заполнению поля