auto git bisect на примере ядра Linux

Данная заметка имеет собой цель продемонстрировать автоматический git bisect на примере ядра Linux. С последующим поиском официальной версии начиная с которой всё поломалось и последней хорошей версии.

Описание инструментария

Инструментарий1 представляет собой простой проект для сборки ядра и модулей с минимальным конфигом достаточным для запуска в qemu, минимального busybox, конфигурационных файлов и небольшого количества скриптов.

  • gcc 😉
  • binutils
  • make
  • qemu
  • expect (только если вы собрались запускать bisect из примера)

Ядро вместе с образом initramsfs запускается с помощью qemu:

$ qemu-system-x86_64 -cpu host \
-kernel build-linux/arch/x86/boot/bzImage \
-initrd initramfs.cpio.xz \
-nographic -append "nokaslr console=ttyS0 root=/dev/ram" \
-enable-kvm -serial mon:stdio

Пароля нет и нас сразу выбрасывает в консоль.

С помощью этого проекта может быть проведен простой bisect 2.

Внимание! Если вы планируете повторить манипуляции представленные ниже, или будете использовать как основу для своего проекта, имейте ввиду:
1. ядро сильно зависит от версии gcc и binutils, некоторые ядра
могут собраться только с определенной версией gcc, могут собраться с
небольшими правками или подавлением ошибок или не собраться вообще
2. с новой версией binutils (начиная с версии 2.31) могут быть проблемы с
загрузкой модулей на версиях ядра до v4.16-rc3

Версии gcc 7.3.0 и binutils 2.30 позволили мне без проблем собрать и
запустить версии ядра с v4.14 по v5.3-rc2.

Постановка задачи

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

В какой-то момент путь в debugfs поменял своё значение с изначально реализованного в версии v4.14:

# ls /sys/kernel/debug/
gpio-mockup-event
# ls /sys/kernel/debug/gpio-mockup-event
gpio-mockup-A

На (замечено на версии v5.3-rc2):

# ls /sys/kernel/debug/
gpio-mockup
# ls /sys/kernel/debug/gpio-mockup
gpiochip1

Что сломало мне тесты для моей программы, и задача состоит в том что бы найти когда, где и кто, в том числе определить <<хорошую>> и <<плохую>> официальные версии ядра.

Решение с помощью git bisect run

В случае повторения данного опыта не забудьте выполнить:

$ git submodule update --init

bisect.sh запускаем через git bisect run, сам же скрипт очень прост и состоит из трёх действий:

  1. очистить всё
  2. собрать всё
  3. запустить tests/bisect.expect (скрипт для expect)

Тест запускает qemu, ждёт промта, загружает модуль gpio-mockup и проверяет наличие директорий в /sys/debug/kernel.
Запускаем процесс (он такой же как и для ручного bisect за исключением последнего шага) :

$ git -C linux bisect start
$ git -C linux bisect good v4.14        # указываем любой хороший коммит
$ git -C linux bisect bad v5.3-rc2      # указываем любой плохой коммит
Bisecting: 73727 revisions left to test after this (roughly 16 steps)
[798bba01b44b0ddf8cd6e542635b37cc9a9b739c]
RDMA/core: Fail early if unsupported QP is provided

Запускаем git bisect run:

$ time git -C linux bisect run ../bisect.sh # запускаем наш простой скрипт

Ждем… Ждем… Ждем… В общем даже не смотря на попытку экономии времени сборки у меня одна сборка занимает:

$ time ../bisec.sh
real    2m1.695s
user    11m7.409s
sys     2m0.751s

По предварительной оценке git bisect должен справиться за 16 шагов.

И вот наконец-то результат:

d51ee07a8de7d6d3f7738a5e74861133fd2d46a0 is the first bad commit
commit d51ee07a8de7d6d3f7738a5e74861133fd2d46a0
Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
Date:   Thu Jan 17 15:04:23 2019 +0100</bgolaszewski@baylibre.com>

gpio: mockup: don't create the debugfs link named after the label

User-space tests no longer use it and we're breaking the interface
anyway.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com></bgolaszewski@baylibre.com>

:040000 040000 c1a1873f4cfcecace123b72fb036c3861151c9b9 61917a273f4f1f078639463a29acb8a103d50b41 M      drivers
bisect run success

real    42m6.873s
user    192m39.291s
sys     33m55.932s

Чтож результат дал нам номер коммита в котором всё пропало:
d51ee07a8de7d6d3f7738a5e74861133fd2d46a0.

Теперь можно посмотреть на список всех проделанных git bisect шагов:

$ git bisect log
git bisect start
# good: [bebc6082da0a9f5d47a1ea2edc099bf671058bd4]
Linux 4.14
git bisect good bebc6082da0a9f5d47a1ea2edc099bf671058bd4
# bad: [609488bc979f99f805f34e9a32c1e3b71179d10b]
Linux 5.3-rc2
git bisect bad 609488bc979f99f805f34e9a32c1e3b71179d10b</spoiler>

# good: [798bba01b44b0ddf8cd6e542635b37cc9a9b739c]
RDMA/core: Fail early if unsupported QP is provided
git bisect good 798bba01b44b0ddf8cd6e542635b37cc9a9b739c

# good: [e266ca36da7de45b64b05698e98e04b578a88888]
Merge tag 'staging-5.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
git bisect good e266ca36da7de45b64b05698e98e04b578a88888

# bad: [318222a35bfb0ae9b5ff3e359a583463e6cfcd94]
Merge branch 'akpm' (patches from Andrew)
git bisect bad 318222a35bfb0ae9b5ff3e359a583463e6cfcd94

# bad: [962d5ecca101e65175a8cdb1b91da8e1b8434d96]
Merge tag 'regmap-v5.2' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap
git bisect bad 962d5ecca101e65175a8cdb1b91da8e1b8434d96

# bad: [f47d633134f7033e3d0c667419d9f8afd69e308d]
Merge tag 'tag-chrome-platform-for-v5.1' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux
git bisect bad f47d633134f7033e3d0c667419d9f8afd69e308d

# good: [6c3f98faddc7f07981c5365ba2f45905ad75fcaa]
Merge branch 'i2c/for-5.1' of git://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux
git bisect good 6c3f98faddc7f07981c5365ba2f45905ad75fcaa

# bad: [2901752c14b8e1b7dd898d2e5245c93e531aa624]
Merge tag 'pci-v5.1-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci
git bisect bad 2901752c14b8e1b7dd898d2e5245c93e531aa624

# bad: [1a29e857507046e413ca7a4a7c9cd32fed9ea255]
Merge tag 'docs-5.1' of git://git.lwn.net/linux
git bisect bad 1a29e857507046e413ca7a4a7c9cd32fed9ea255

# bad: [3601fe43e8164f67a8de3de8e988bfcb3a94af46]
Merge tag 'gpio-v5.1-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio
git bisect bad 3601fe43e8164f67a8de3de8e988bfcb3a94af46

# good: [cf2e8c544cd3b33e9e403b7b72404c221bf888d1]
Merge tag 'mfd-next-5.1' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd
git bisect good cf2e8c544cd3b33e9e403b7b72404c221bf888d1

# good: [8fab3d713ca36bf4ad4dadec0bf38f5e70b8999d]
Merge tag 'gpio-v5.1-updates-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux into devel
git bisect good 8fab3d713ca36bf4ad4dadec0bf38f5e70b8999d

# bad: [9aac1e336c3ab3824f646224f4b2309b63c51668]
Documentation: gpio: legacy: Don't use POLLERR for poll(2)
git bisect bad 9aac1e336c3ab3824f646224f4b2309b63c51668

# good: [0248baca03b8f188eccbb991bda2caec4c330975]
Merge tag 'intel-gpio-v5.1-1' of git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel into devel
git bisect good 0248baca03b8f188eccbb991bda2caec4c330975

# bad: [e09313ce7ea1706d1642c7d5af103915e69fc6d0]
gpio: mockup: change the signature of unlocked get/set helpers
git bisect bad e09313ce7ea1706d1642c7d5af103915e69fc6d0

# good: [cbf1e092f2d86e6d7cdb7f9ff8a333f52c826232]
gpio: mockup: implement get_multiple()
git bisect good cbf1e092f2d86e6d7cdb7f9ff8a333f52c826232

# bad: [83336668b94eb44ecd78a0b7840e43f0859e05cb]
gpio: mockup: change the type of 'offset' to unsigned int
git bisect bad 83336668b94eb44ecd78a0b7840e43f0859e05cb

# bad: [d51ee07a8de7d6d3f7738a5e74861133fd2d46a0]
gpio: mockup: don't create the debugfs link named after the label
git bisect bad d51ee07a8de7d6d3f7738a5e74861133fd2d46a0
# first bad commit: [d51ee07a8de7d6d3f7738a5e74861133fd2d46a0] gpio: mockup: don't create the debugfs link named after the label

На самом деле это провал первой проверки (да — в тесте их две):

send "ls /sys/kernel/debug/\r"
expect {
"gpio-mockup-event" {}
timeout  { puts "gpio-mockup-event not found"; exit 1 }
}

Другая проверка ломается коммитом 2a9e27408e12de455b9fcf66b5d0166f2129579e (конечно было бы их разделить но ленив, поэтому я просто посмотрел коммиты <<рядом>>):

send "ls /sys/kernel/debug/gpio-mockup-event/\r"

expect {
"gpio-mockup-A" { puts "gpio-mockup-A found" }
timeout  { puts "gpio-mockup-A not found"; exit 1 }
}

Что ж давайте выясним когда коммит d51ee07a8de7 попал в основную ветку к Линусу и с какой официальной версии ядра он присутствует3.

Посмотрим все коммиты до d51ee07a8de7 фильтруя только merge коммиты4 (merge commits) и имеющие прямой путь5 (ancestry chain) :

$ git log --pretty=oneline d51ee07a8de7d6d3f7738a5e74861133fd2d46a0..master --ancestry-path --merges

Это даёт нам все merge коммиты между d51ee07a8de7 и master. Давайте глянем в конец списка (показаны только три последнии записи) :

3601fe43e8164f67a8de3de8e988bfcb3a94af46 Merge tag 'gpio-v5.1-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio
3dda927fdbaac926c50b550ccb51ed18c184468b Merge branch 'ib-qcom-ssbi' into devel
2f7db3c70fdfb22480a1b0aa734664fc256532f2 Merge tag 'gpio-v5.1-updates-for-linus-part-2' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux into devel

Как мы видим последний коммит это поглощение ветки из git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux в git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio devel, а первый в списке это уже поглощение ветки товарища Linus Walleij (<<начальника>> подсистемы GPIO) товарищем Linus Torvalds в ветку master.

Есть очень хороший скрипт6, который сразу приведёт к результату без ручной работы:

# это сразу даст нам нам коммит 3601fe43e816 и ничего более
$ git-find-merge d51ee07a8de7d6d3f7738a5e74861133fd2d46a0 master

Найдем первую версию после коммита 3601fe43e816:

$ git name-rev --name-only 3601fe43e816
tags/v5.1-rc1~102

Число 102 здесь это расстояние от 3601fe43e816 до v5.1-rc1, давайте проверим его используя опцию first-parent7:

$ git -P log --pretty --oneline --first-parent \
--graph 3601fe43e816..v5.1-rc1 | wc -l
102

Похоже, что все в порядке. Я могу заявить, что первая официальная версия ядра в которой всё <<сломалось>> v5.1-rc1, а в версии v5.0 всё было в порядке:

$ git describe 3601fe43e816
v5.0-8748-g3601fe43e816

Заключение

Ни для кого не секрет, что делать подобные вещи автоматически гораздо веселее. Такое количество шагов вручную cделать очень тяжело, учитывая долгое время сборки, можно элементарно забыть чем занимался.

Собственно при сформулированных требованиях можно выполнять любые манипуляции, сделать специальную программу и добавлять в initramfs, или проверять что-то своё и иметь под рукой bisect.

Тесты могут быть в любом удобном виде пока они выполняют требования git bisect run. Более того c помощью прикладных программ (например того же самого expect) можно огранизовать прошивку платы с нужной вам архитектурой и выполнять проверки непосредственно на ней.

Оригинал использованной в заголовке картинки : https://xkcd.com/1597/.