Pull to refresh

Comments 46

А как вообще тестируются коммиты в ядро линукса?
Тестируется на собираемость и на запускаемость в той же кему. А то что много различных устройств нельзя так протестировать, и что без тестов и всесторонней проверки кода ядра, это опускается.
Отличный вопрос! На самом деле помимо разрозненных девелоперских тестов и тестов различных команд / подсистем (например, mmtests) существует проект 0-day kernel test. Подробнее можно почитать здесь: lwn.net/Articles/514278.
Вообще хорошим подходом, было бы вычленять системы из реального кода и прогонять их на реакцию. Но в виду того что опять же не всё поддаётся отладке (отладчики на тот же интел стоят баснословных денег и редки в природе), и не везде можно собрать статистику появляются различные мигрирующие и случайные баги.
Кстати, Intel продвигает Intel® Processor Trace, которая при помощи Trace Hub позволит обойтись без этих баснословно стоящих устройств, если я правильно понимаю.
Ну да, т.к. помимо отладчика (который я видел только на картинках), нужна ещё и соответствующая мат-плата. Одна такая, правда под амд мне всё же попадалась, но скорее всего из-за задержки в релизе (выпустили дебаг версию, т.к. некогда было править рабочую версию).
Обычным людям вполне хватает qemu.
Обычные люди не пишут настолько низкоуровневый код, что вызовы в нём ещё и надо согласовывать между собой.
Ну и да, qemu ну вот нисколько не предназначен для отладки ядра, равно как и другая виртуальная машина с «быстрой» симуляцией. Тут нужен полноценный эмулятор процессора и его окружения.
qemu ну вот нисколько не предназначен для отладки ядра

Возможно вам тогда интересно будет узнать, что в течение трёх последних лет я разрабатываю и отлаживаю linux для xtensa преимущественно с помощью qemu. И могу сказать, что для подобных чисто софтверных багов он подходит отлично. А кроме того, «быстрая» симуляция не значит «плохая».
«быстрая» обозначает то что не все инструкции будут исполнятся также как и на реальном железе, с тем же отношением. Также большинство моделей процессоров и то что есть в qemu не сходятся по разным показателям. Никто не говорит что нельзя по qemu разрабатывать, но при этом не стоит забывать что это симулятор и то насколько он соответствует реальной модели не знает никто.
Поэтому без реального железа всё равно нельзя говорить что код исправен — типичный пример atmel: чередуя релизы разработчики ядра по очереди ломают одно из устройств, а точнее его драйвер.
Ну вообще-то «быстрая» в контексте qemu обозначает, что используется бинарная трансляция совместно с разнообразными трюками для поддержки MMU, что быстрее наивной интерпретации.
На качество эмуляции эта быстрота не влияет, насколько точно поддерживать архитектуру решает разработчик.

насколько он соответствует реальной модели не знает никто

Знает как минимум человек, знакомый с железом и заглянувший в реализацию qemu. А ещё есть тесты.
Не забываем что там не только MMU но и другие привилегированные инструкции, а также немного изменённое адресное пространство, которое работает не так, как реальный режим процессора.
немного изменённое адресное пространство, которое работает не так, как реальный режим процессора

Расскажите поподробнее об изменённом адресном пространстве? И о том, что может помешать разработчику учесть эти изменения?
То что это не соответствует тому что он ожидает, и это мешает ему увидеть ошибки подобные описанной в статье.
Какая-то недосказанность в этой истории: вроде уже С-код исполняется, функции вызываются. Что особенного в функции sprintf?
Исследований по поиску причин тоже не видно.
Особенного ничего нету, просто для части функций нету проекции virt-to-real. Поэтому адреса получившиеся после линковки оказались недействительными, в этом и вся ошибка.
для части функций нету проекции virt-to-real

Да ладно.
Функции из arch/x86/boot можно звать, потому что они слинкованы по адресам реального режима, а остальные — нет, так?
Для экскурсии можете посмотреть System.map, в частности тому же spirntf отводится верхняя граница адресов в памяти (виртуальный адрес).
System.map описывает vmlinux, там вообще ни одного адреса ниже 3G нет.
Тем не менее, код выполняется в реальном режиме и startup_32 вызывает load_ucode_bsp в реальном режиме.
c1ae13da T load_ucode_bsp

и это работает, как я понимаю, только потому что код position-independent.
Какая то часть да, в том числе и инициализация, загрузчик и распаковщик. Однако остальная часть кода имеет фиксированные адреса и точки входа. И после загрузки для них с помощью MMU меняется виртуальный адрес (для тех устройств где есть MMU).
no111u3 «загрузчик» и «распаковщик» уже закончились к началу startup_32, MMU ещё не инициализирован на момент вызова load_ucode_bsp.
У меня был простой вопрос: «что особенного в sprintf», если вам не терпится ответить — ответьте пожалуйста на него.
/*
* Calculate the delta between where we were compiled to run
* at and where we were actually loaded at.
То что он вычислил куда прыгнуть, а вот несчастный sprintf он таким образом не может использовать, т.к. он был встроен при компиляции и вызов для него рассчитывался исходя из виртуальной адресации. Как говорится всё бы было хорошо, но адрес вызова sprintf который был подставлен виртуальный, и чтобы его преобразовать в реальный нужно знать об этом.
Да ни при чём тут адрес вызова sprintf. Вызывается она нормально, это легко проверить.
Вызовется то да, да вот работать не будет нормально (т.к. внутри него адреса то другие).
Сдаётся мне, ничего особенного в нём нет, и следующая замена скорее всего починила бы это место:
        sprintf(name, __pa_nodebug("intel-ucode/%02x-%02x-%02x"), family, model, stepping);

Но в целом, конечно, поддерживать этот код — это реальная жуть.
Ну лучше пока не сделали, да и не будут.
Вы невнимательно прочитали мой пост. параметры не имеют значения, я даже проверил ваше предположение — не работает. Проблема в самом символе sprintf.
Я тоже проверил своё предложение. Действительно не работает.
Однако я точно попадаю в sprintf и дальше в vsprintf (по реальным адресам, разумеется). Дальше пока не смотрел.
Ну и если вызывать strcpy то она вполне работает с такой заменой.
Будет интересно увидеть ваш анализ.
Проблема из-за того, что компилятор реализовал switch в vsnprintf через таблицу переходов. Я вижу два вот таких стрёмных места:
c12b905b:       ff 24 85 54 c6 7f c1    jmp    *-0x3e8039ac(,%eax,4)
                        c12b905e: R_386_32      .rodata

Разумеется в .rodata абсолютные виртуальные адреса.
Может это место скомпилировать с -fPIC?
Процитирую Borislav'а: …even if we build the string properly, we choke later in get_builtin_firmware().
А так похоже, что это хорошее объяснение поведения sprintf().
Это понятно, что весь этот код работает на честном слове, и даже если пофиксить здесь то повалится там.
Я реально удивлён что этот код никак не отделён от остального и не собирается как-то особенно. А например, начнёт завтра gcc все свитчи делать таблицами переходов…
Так никто и не рассчитывал что подобный код будет исполнятся из разных адресных пространств. Разбить на независимые модули — да, и ещё раз да. Но опять же необходимо проработать архитектуру, чтобы это работало.
Ну нифига себе! Это прямо жабство какое-то.
А как ещё надо свичи делать? Серией ifов?
Немного подробностей:
вставляем sprintf в удобное место, я сделал так:
 void __init load_ucode_bsp(void)
 {       
         int vendor, family;
+        char str[100];
+        
+        sprintf(str, __pa_nodebug(":%d"), 1234);
         if (check_loader_disabled_bsp())
                 return;

конфигурируем ядро, включаем CONFIG_DEBUG_INFO и собираем ядро. Загружаем его в qemu:
$ qemu-system-i386 -kernel arch/x86/boot/bzImage -s -S

Запускаем gdb, загружаем символы, соединяемся с qemu:
$ gdb
(gdb) target remote :1235
Remote debugging using :1235
0x0000fff0 in ?? ()
(gdb) add-symbol-file vmlinux 0x1000000
add symbol table from file "vmlinux" at
        .text_addr = 0x1000000
(y or n) y
Reading symbols from /home/jcmvbkbc/ws/tensilica/linux/z/vmlinux...done.
(gdb) b sprintf
Breakpoint 1 at 0x12b9150: file /home/jcmvbkbc/ws/tensilica/linux/linux-xtensa/lib/vsprintf.c, line 2120.
(gdb) c
Continuing.

Breakpoint 1, sprintf (buf=<error reading variable: can't compute CFA for this frame>, fmt=<error reading variable: can't compute CFA for this frame>) at /home/jcmvbkbc/ws/tensilica/linux/linux-xtensa/lib/vsprintf.c:2120
....
1885                    switch (spec.type) {
1: x/10i $pc
=> 0x12b8e88 <vsnprintf+232>:   jmp    *-0x3e8039cc(,%eax,4)
(gdb) si
0x00000000 in ?? ()
1: x/10i $pc
=> 0x0: push   %ebx

В сессии gdb работает source-level отладка.
target remote :1235 читать как target remote :1234.
qemu запущенный с -s ожидает gdb на порте 1234, чтобы перевесить gdbserver на другой порт вместо -s можно указать -gdb tcp::port
Может это место скомпилировать с -fPIC?

Попробовал, не компилируется. Похоже из-за inline assembly или явного использования регистров.
Однако нашёл опцию -fno-jump-tables отключающую именно это поведение.
Множество разработчиков тестирует свой код не на реальных машинах, а в виртуальных, с помощью того же QEMU. Так вот там всё прекрасно работает.

Двусмысленно. sprintf вызванный из реального режима там точно так же падает. Т.е. «не работает». Т.е. qemu достаточно точно эмулирует для воспроизведения и отладки этого бага (до свитча реализованного таблицей я дошёл как раз в qemu). Т.е. «работает».
А как запускали QEMU? Я ему указал на ядро, initrd и командную строку. Запускается без проблем.
Ответил выше. Конечно дерево tip я не собирал, просто вставил sprintf в загрузчик микрокода и походил по нему.
Стиль гиперссылок выдаёт разработчика ядра, привыкшего к простому тексту.
#define GETASCII(a) a>9? a+'a': a+'\0'

char name[]=«intel-ucode/00-00-00»;
char b;

b=family>>8;
name[12]=GETASCII(b);
b=family & 0x0F;
name[13]=GETASCII(b);

b=model>>8;
name[15]=GETASCII(b);
b=model & 0x0F;
name[16]=GETASCII(b);

name[19]=GETASCII(stepping);
Причём функция преобразования доступна, вот я рассказывал здесь: habrahabr.ru/post/252453 (см. главу Бонусы). И предложенное мной исправление в этом же состояло. :-)
Sign up to leave a comment.

Articles