четверг, 31 января 2008 г.

Баг процессора?

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

000 sreg2 111, при sreg2 равном 01, что означает cs, даст код 0x0f, с которого начинаются все двухбайтовые команды.

Хотя с другой стороны эта же команда может быть закодирована через 0x0f 0x89, и скорее всего любой вменяемый ассемблер должен так и поступить. Проверять лень.

Я конечно вполне понимаю всю практическую бесполезность данной команды, но не смотря на это в мануале она описана. Ее теоретически можно даже использовать например при переходе в защищенный режим делать не

    jmp KERNEL_CODE_SELECTOR:label
label:

а
    push KERNEL_CODE_SELECTOR
    pop cs

При условии, что eip не изменяется.

PS: В принципе возникноверие этой нестыковки понятно. На 286 было мало команд и мало регистров, двух бит хватало на сегментный регистр, но в 386 появились дополнительные сегментные регистры fs и gs, кроме того 256 кодов стало не хватать, а других свободных кодов не было, и видимо было принято решение использовать для расширенных инструкций код 0x0f, посколько никто никогда не использует короткую форму pop cs. Да и длинная по большому счету не нужна никому.

PPS: B IA32 Software Developer Manual кстати, в описании команды pop, написано
The POP instruction cannot pop a value into the CS register. To load the CS register from the stack, use the RET instruction.
Но ведь закодировать то можно. Прям загадка какая-то. :)

понедельник, 28 января 2008 г.

Разворот стека методом прямой трассировки кода.

Во, как загнул... и это не шутка, метод уже реально работает.

RISC - это наверное рай для программистов на ассемблере. И для программистов компиляторов заодно. Это же счастье, когда любая инструкция занимает ровно 32 бита, и общее количество инструкций порядка 3 дестков...

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

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

Для разворота стека методом прямой трассировки кода - необходимо для начала определить какую либо точку кода. Чтобы проделать это - мы берем первое слово из стека, причем, это именно голова стека, то есть то место откуда стек начался. И проверяем, не может ли это значение быть адресом возврата? Для этого необходимо убедиться, что перед ним стоит инструкция вызова.

Можно сказать что это режим разворота без точки выполнения. Мы еще будем к нему возвращаться в случае потери адреса.

    инструкция call (в любой форме)
value <-- адрес возврата


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

В случае обнаружения адреса инструкций - включаем режим трассировки. Трассировка осуществляется до тех пор, пока указатель трассировки не совпадет с адресом, на котором прервалось выполнение приложения. Если встречается какая-либо инструкция, явно показывающая, что код не соответствует стеку, то данный вариант разворота отменяется, трассировка пошла в неправильную ветвь.

Кого-то, может быть, пугает слово трассировка, но не стоит пугаться. Нам вовсе не обязательно эмулировать все инструкции. Мы анализируем следующие группы инструкций:
Инструкции, модифицирующие содержимое регистра esp, двигают указатель стека; Инструкции безусловных переходов передвигают указатель трассировки, за исключением случая, когда переход осуществляется на саму инструкцию; Инструкции условных переходов проверяются по обоим ветвям. Что касается различных форм инструкции call - то адрес в стеке обязательно должен соответствовать адресу следующей инструкции, иначе этот call протрассировывается мимо. По разумным соображениям я не включаю в список команд вызова различные прерывания. Да и не все call'ы одинаково полезны, как я уже писал раньше. Я могу точно определять явно заданные адреса, но косвенные формы лишь показывают что вызов есть, но куда было передано управление - остается неизвестным, происходит потеря адреса.

Анализируется еще одна ситуация, когда содержимое стека соответствует срабатыванию исключения (адрес возврата, селектор сегмента), но в этом случае происходит потеря адреса, ибо доподлинно не известно, какое исключение сработало.

Остальные инструкции достаточно просто пропустить, но тут то нас поджидает один неприятный момент, на IA32 инструкции имеют очень переменную длину, и определение длины текущей инструкции - задача весьма нетривиальная. Мне удалось решить ее с помощью двух таблиц, по 256 байт каждая, и небольшой функции. Но таблицы я еще не заполнил до конца.

В этом методе осталось еще не реализованной возможность определения количества аргументов, но теоретически это возможно.

Пока что, без переходов через шлюзы - результат вполне обнадеживающий:

CallStack: Stopped in StubKernelUsePage+122 (0x00102a00)
CallStack: Called by StubPageInitMode+31 (0x00100df9)
CallStack: Called by StubEntry+587 (0x00101428)
CallStack: Called by unknown+0 (0x001000d4)

Последняя точка осталась неизвестной, потому, что для определения символа необходимо отмотать по коду несколько команд назад StubEntryLo=0x001000cc, но это мелочи по большому счету.

пятница, 25 января 2008 г.

.bss в файле???

Всем известно, что .bss это неинициализированная секция и не должна занимать места в файле, но собирая на досуге свое ядро наткнулся на проблему. .bss почему-то находится в файле и занимает там много места, хотя не должна.

Ага, понятно, все происходит из за стека, который я хочу включить в .bss, но именую словом .stack.

Не знаю, почему в binutils или в gcc или где там, уж не знаю, придают такое значение именам, но
3 .stack 00001000 00000000 00000000 00000298 2**0
CONTENTS, READONLY

В то время как переименованная секция
3 .bss.stack 00001000 00000000 00000000 00000298 2**0
ALLOC


Теперь стало гораздо лучше.

PS: У меня почему-то комплекс, боязнь толстых программ. А еще у меня есть старое ядро, которое полностью занимало 17KiB, оно у меня как некий образец объема. Новое конечно будет больше, но пока стрипнутое занимает 15KiB.

В любом случае, комплекс - не комплекс, но 5 килобайт нулей - это слишком расточительно.

понедельник, 21 января 2008 г.

Что-то великоватый Image...

Собираю новое ядро (2.6.23) для своего LPC2468, и что-то странное происходит с Image...

В то время, как vmlinux вполне корректен на первый взгляд...
$ file vmlinux
vmlinux: ELF 32-bit LSB executable, ARM, version 1, statically linked, not stripped
$ ls -la vmlinux
-rwxr-xr-x 1 dron dron 1919833 Янв 21 10:08 vmlinux


Этого совершенно нельзя сказать про полученный, стандартными средствами, надо сказать, Image.
$ ls -la arch/arm/boot/Image
-rw-r--r-- 1 dron dron 2685758032 Янв 21 10:08 arch/arm/boot/Image
$ file arch/arm/boot/Image
arch/arm/boot/Image: X11 SNF font data, LSB first


Надо сказать что в старом ядре linux.bin собирался кастомной рулей:
linux.bin: vmlinux FORCE
    @$(OBJCOPY) $(OBJCOPYFLAGS) vmlinux $@
    @echo ' Kernel: $@ is ready'


Но лично я не вижу никаких принципиальных отличий от стандартной рули для Image:
$(obj)/Image: vmlinux FORCE
    $(call if_changed,objcopy)
    @echo ' Kernel: $@ is ready'


Которые собственно разворачиваются в
arm-linux-objcopy -O binary -R .note -R .comment -S vmlinux linux.bin
arm-linux-objcopy -O binary -R .note -R .comment -S vmlinux arch/arm/boot/Image

соответственно. Пока даже мыслей нету - почему возникает такая ерунда.

Буду думать.

PS: Ну все ясно. Как говорится - нечего на objcopy пинять, коли vmlinux кривой. в него затесалась секция .note.gnu.build-id, замапленная на 0, в то время как основной код замаплен на 0xa0008000. вот и получается бинарь в 3 гига. Прибил секцию - пришло счастье...

$ ~/arm-linux/bin/arm-linux-objdump -h vmlinux
Sections:
Idx Name Size VMA LMA File off Algn
0 .note.gnu.build-id 00000024 00000000 00000000 00008000 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA, LINK_ONCE_DISCARD
1 .text.head 00000184 a0008000 a0008000 00010000 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE

четверг, 17 января 2008 г.

Массивные изменения с контролем версий.

Обычно изменения исходников достаточно немногочисленны и разобраться с ними вручную не составляет труда. Но иногда возникают сложности...

Я сейчас строю небольшой дистрибутивчик, встраиваемое решение. Состоит он из ядра linux, busybox и одной программочки из util-linux. Минимум. В оригинале система была построена на базе uClinux, но сделать с ним что-то было весьма не просто, старый, ничего не собирается. Поэтому я его распотрошил, выдернул ядро 2.6.11.8 со всякими патчами как было, взял свежий busybox, свежий util-linux, написал Makefile который все быстро билдит почти с учетом всех зависимостей.

Busybox конечно глючная штука. никак с ним не поборюсь, но основные утилиты работают. Самое страшное для меня - это ядро. Надо отметить, что погружал я его в систему контроля версий почастям... сперва ядро 2.6.11.8, потом hsc0 патч, потом погрузил имеющуюся версию и теперь могу отделить патч от разработчика платы в чистом виде. Кроме того на него уже наложен мой патч на шифрование, но там все просто.

От проекта требуется в первую очередь работать с USB, причем не только как хост, но и как девайс. Вот с этим какраз возникают проблемы, которые заключаются в том, что не все флешки одинаково качественны. И не всякое чтение дойдет до середины флешки.

Это была присказка.. или как говорят - преамбула... теперь наступила амбула.

Поскольку в текущем состоянии все работает, имеет смысл перейти на всежее ядро, предполагаю, что в последних ядрах USB стек сильно отличается от ядра 2.6.11.8, которое выпущено в 2005 году. Если мне не изменяет память USB 2.0 тогда только зародилось.

Делаем бранч, но чтобы не раздувать БД не просто меняем одно ядро на другое, а аккуратно патчим с текущего до последнего. При этом необходимые патчи на архитектуру и на шифрование конечно исчезают, но я их потом аккууратненько наложу. С архитектурой всеравно без напильника не обойтись.

Вот после этого и наступила засада... svn status показывает список изменений, необъятного размера, где часть файлов исчезла, часть файлов появилась, и еще много просто модифицировалось. Надо думать как аккуратно зафиксировать все это. Может быть, пришедшее ко мне в голову решение весьма тривиально, но тем не менее оно достойно того, чтобы взять его на вооружение.

$ svn status | grep \! | sed -e 's/\!//g' | xargs svn rm
$ svn status | grep ? | sed -e 's/\?//g' | xargs svn add
$ svn commit

Первая команда удаляет все исчезнувшие файлы, вторая добавляет все новые. И фиксируем.
Такой вот узелок на память.