Как стать автором
Обновить
96.73
Фонд ReactOS
Операционная система

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

Время на прочтение 10 мин
Количество просмотров 12K

Всем доброго времени суток! 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 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)
{
    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/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(); 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 ! 
(часть вырезана, совпадает с ранее приведённой)

Казалось бы, всё нормально, 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 воплощает в реальность давнюю мечту — на практике позволяет узнать, как всё это устроено изнутри.


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


Ссылки


Теги:
Хабы:
+64
Комментарии 40
Комментарии Комментарии 40

Публикации

Информация

Сайт
reactos.org
Дата регистрации
Численность
51–100 человек
Местоположение
Россия

Истории