Subversion Repositories Kolibri OS

Rev

Rev 1065 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. ; Copyright (c) 2008-2009, diamond
  2. ; All rights reserved.
  3. ;
  4. ; Redistribution and use in source and binary forms, with or without
  5. ; modification, are permitted provided that the following conditions are met:
  6. ;       * Redistributions of source code must retain the above copyright
  7. ;       notice, this list of conditions and the following disclaimer.
  8. ;       * Redistributions in binary form must reproduce the above copyright
  9. ;       notice, this list of conditions and the following disclaimer in the
  10. ;       documentation and/or other materials provided with the distribution.
  11. ;       * Neither the name of the <organization> nor the
  12. ;       names of its contributors may be used to endorse or promote products
  13. ;       derived from this software without specific prior written permission.
  14. ;
  15. ; THIS SOFTWARE IS PROVIDED BY Alexey Teplov aka <Lrz> ''AS IS'' AND ANY
  16. ; EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  17. ; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. ; DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
  19. ; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  20. ; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  21. ; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  22. ; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  23. ; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  24. ; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. ;*****************************************************************************
  26.  
  27.                                                 Нет повести печальнее на свете,
  28.                                                 Чем повесть о заклинившем Reset'е...
  29.  
  30. Загрузчик для FAT- и NTFS-томов для случаев, когда основной бутсектор загружает
  31. Windows, для носителей с размером сектора 512 байт.
  32.  
  33. =====================================================================
  34.  
  35. Требования для работы:
  36. 1) Все используемые файлы должны быть читабельны.
  37. 2) Минимальный процессор - 80386.
  38. 3) В системе должно быть как минимум 592K свободной базовой памяти.
  39. 4) Пути к используемым файлам не должны содержать символических ссылок NTFS
  40.         (жёсткие ссылки допускаются).
  41. 5) Используемые файлы не должны быть сжатыми или разреженными файлами
  42.         (актуально для NTFS, для FAT выполнено автоматически).
  43.  
  44. =====================================================================
  45.  
  46. Документация в тему (ссылки проверялись на валидность 08.08.2008):
  47.         официальная спецификация FAT: http://www.microsoft.com/whdc/system/platform/firmware/fatgen.mspx
  48.                 в формате PDF: http://staff.washington.edu/dittrich/misc/fatgen103.pdf
  49.                 русский перевод: http://wasm.ru/docs/11/fatgen103-rus.zip
  50.         спецификация NTFS: file://C:/windows/system32/drivers/ntfs.sys
  51.                 и file://C:/ntldr либо file://C:/bootmgr
  52.         неофициальное описание NTFS: http://sourceforge.net/project/showfiles.php?group_id=13956&package_id=16543
  53.         официальная спецификация расширения EDD BIOS 3.0: http://www.phoenix.com/NR/rdonlyres/19FEBD17-DB40-413C-A0B1-1F3F560E222F/0/specsedd30.pdf
  54.                 то же, версия 1.1: http://www.phoenix.com/NR/rdonlyres/9BEDED98-6B3F-4DAC-BBB7-FA89FA5C30F0/0/specsedd11.pdf
  55.         описание функций BIOS: Interrupt List by Ralf Brown: http://www.cs.cmu.edu/~ralf/files.html
  56.         официальная спецификация Boot BIOS: http://www.phoenix.com/NR/rdonlyres/56E38DE2-3E6F-4743-835F-B4A53726ABED/0/specsbbs101.pdf
  57.         официальное описание bcdedit для Vista: http://www.microsoft.com/whdc/system/platform/firmware/bcdedit_reff.mspx
  58.         официальное описание работы с базой данных загрузчика Vista: http://www.microsoft.com/whdc/system/platform/firmware/bcd.mspx
  59.         формат таблицы разделов жёсткого диска: http://www.microsoft.com/technet/prodtechnol/windows2000serv/reskit/prork/prcb_dis_qxql.mspx
  60.  
  61. =====================================================================
  62.  
  63. Схема используемой памяти:
  64.         600-2000        код загрузчика (и данные)
  65.         2000-3000       стек
  66.         3000-3200       сектор MBR
  67.         3200-3400       бутсектор логического диска
  68.         3400-3C00       информация о кэше для таблиц FAT16/FAT32:
  69.                         для FAT16 - массив на 0x100 байт, каждый байт равен
  70.                         0 или 1 в зависимости от того, загружен ли
  71.                         соответствующий  сектор таблицы FAT16;
  72.                         для FAT32 - 100h входов по 8 байт: 4 байта
  73.                         (две ссылки - вперёд и назад) для организации L2-списка
  74.                         всех прочитанных секторов в порядке возрастания
  75.                         последнего времени использования + 4 байта для номера
  76.                         сектора; при переполнении кэша выкидывается элемент из
  77.                         головы списка, то есть тот, к которому дольше всех
  78.                         не было обращений
  79.         3400-3440       информация о кэше для файловых записей NTFS в
  80.                         таком же формате, как и кэш для FAT32, но на 8 входов
  81.         3480-34C0       заголовки для кэшей записей индекса NTFS
  82.         3500-3D00       информация о кэшах записей индекса NTFS: с каждой
  83.                         файловой записью связан свой кэш для
  84.                         соответствующего индекса
  85.         4000-8000       место для информации об атрибутах для NTFS
  86.         60000-80000     таблица FAT12 / место под таблицу FAT16 /
  87.                                 кэш для таблицы FAT32 / кэш для структур NTFS
  88.         80000-90000     текущий рассматриваемый кластер
  89.         90000-92000     FAT: кэш для корневой папки
  90.         92000-...       FAT: кэш для некорневых папок (каждой папке отводится
  91.                         2000h байт = 100h входов, одновременно в кэше
  92.                         может находиться не более 7 папок;
  93.                         точный размер определяется размером доступной
  94.                         физической памяти - как правило, непосредственно
  95.                         перед A0000 размещается EBDA, Extended BIOS Data Area)
  96.  
  97. =====================================================================
  98.  
  99. Основной процесс загрузки.
  100. 0a. Загрузка из-под DOS и Win9x: установка kordldr.win осуществляется
  101.         размещением команды install=c:\kordldr.win в первой строке config.sys;
  102.         при этом основной загрузчик системы загружает kordldr.win как обычный
  103.         com-файл, в какой-то сегмент по смещению 100h и передаёт управление
  104.         в начало кода (xxxx:0100).
  105. 0б. Загрузка из-под WinNT/2000/XP: установка kordldr.win осуществляется
  106.         добавлением строки наподобие c:\kordldr.win="KordOS" в секцию
  107.         [operating systems] файла boot.ini; если загружаемый файл имеет размер
  108.         не менее 8 Кб (0x2000 байт) и по смещению 3 содержит сигнатуру 'NTFS'
  109.         (в случае kordldr.win так и есть), то основной загрузчик каждой из
  110.         этих систем загружает kordldr.win по адресу 0D00:0000 и передаёт
  111.         управление на адрес 0D00:0256.
  112. 0в. Загрузка из-под Vista: установка kordldr.win осуществляется манипуляциями
  113.         с базой данных основного загрузчика через bcdedit и подробно описана в
  114.         инструкции к kordldr.win; основной загрузчик загружает целиком
  115.         kordldr.win по адресу 0000:7C00 и передаёт управление в начало кода.
  116. 1. При загрузке из-под DOS/9x основной загрузчик не ожидает, что загруженная
  117.         им программа окажется в свою очередь загрузчиком, и в этом случае
  118.         kordldr.win оказывается в условиях, когда основной загрузчик уже
  119.         установил какое-то окружение, в частности, перехватил некоторые
  120.         прерывания. Поэтому перед остальными действиями загрузчик должен
  121.         восстановить систему в начальное состояние. (При загрузке под
  122.         NT-линейкой такой проблемы не возникает, поскольку там основной
  123.         загрузчик ничего в системе не трогает.) Поэтому перед собственно
  124.         инициализацией KordOS при работе из-под DOS/9x производятся
  125.         дополнительные действия. Первым делом kordldr проверяет, какой из
  126.         случаев 0а и 0в имеет место (случай 0б отличается тем, что передаёт
  127.         управление не на начало кода): определяет значение ip (команда call
  128.         помещает в стек адрес следующей после call инструкции, команда pop si
  129.         выталкивает его в регистр si), и если оно равно 100h, то kordldr
  130.         загружен как com-файл из-под DOS/9x. Тогда он спрашивает подтверждения
  131.         у пользователя (поскольку в этой схеме kordldr загружается всегда,
  132.         он должен оставить возможность продолжить загрузку DOS/9x). Если
  133.         пользователь хочет продолжить обычную загрузку, kordldr завершается.
  134.         Иначе используется тот факт, что при выдаче прерывания перезагрузки
  135.         int 19h система предварительно снимает все свои перехваты BIOSовских
  136.         прерываний, а потом в свою очередь выдаёт int 19h уже BIOSу. Так что
  137.         kordldr устанавливает свой обработчик трассировочного прерывания,
  138.         устанавливает флаг трассировки и передаёт управление DOSовскому
  139.         обработчику. Обработчик трассировочного прерывания ничего не делает
  140.         до тех пор, пока следующей инструкцией не оказывается int 19h, а
  141.         в этот момент отбирает управление и продолжает загрузку KordOS.
  142.         При этом BIOSовские обработчики восстановлены за исключением,
  143.         быть может, прерывания таймера int 8, которое, возможно, восстановлено
  144.         до команды jmp far на оригинальный обработчик. В последнем случае его
  145.         нужно восстановить явно.
  146. 2. Загрузчик перемещает свой код на адрес 0000:0600.
  147. 3. (метка real_entry) Загрузчик устанавливает сегментные регистры ds = es = 0,
  148.         настраивает стек ss:sp = 0000:3000 и устанавливает bp так, чтобы
  149.         все данные можно было адресовать через [bp+N] с однобайтовым N
  150.         (в дальнейшем они так и будут адресоваться для освобождения ds и
  151.         экономии на размере кода). Разрешает прерывания на случай, если
  152.         они были запрещены. Выдаёт сообщение о начале загрузки, начинающееся
  153.         с весёлой рожицы (символ с ASCII-кодом 2).
  154. 4. Определяет характеристики жёсткого диска, указанного в качестве
  155.         загрузочного: проверяет поддержку LBA (функция 41h прерывания 13h),
  156.         если LBA не поддерживается, то определяет геометрию - число дорожек
  157.         и число секторов на дорожке (функция 8 прерывания 13h), эти параметры
  158.         нужны функции чтения с диска.
  159. 5. (метка new_partition_ex) Устраивает цикл по разделам жёсткого диска.
  160.         Цель цикла - для каждого логического диска попытаться загрузиться с
  161.         него (действия по загрузке с конкретного логического диска начинаются
  162.         с метки not_extended), при ошибке загрузки управление передаётся
  163.         назад этому циклу (метка next_partition), и поиск подходящего раздела
  164.         продолжается. На выходе заполняется одна переменная partition_start,
  165.         имеющая смысл начала текущего рассматриваемого логического диска,
  166.         но по ходу дела из-за приколов таблиц разделов используются ещё четыре
  167.         переменных. cur_partition_ofs - фактически счётчик цикла, формально
  168.         указатель на текущий вход в текущей загрузочной записи. Сама
  169.         загрузочная запись считывается в память начиная с адреса 3000h.
  170.         Три оставшихся нужны для правильной работы с расширенными разделами.
  171.         В каждой загрузочной записи помещается не более 4 записей о разделах.
  172.         Поэтому главной загрузочной записи, размещающейся в первом физическом
  173.         секторе диска, может не хватить, и обычно создаётся так называемый
  174.         расширенный раздел с расширенными загрузочными записями, формат
  175.         которых почти идентичен главной. Расширенный раздел может быть только
  176.         один, но в нём может быть много логических дисков и расширенных
  177.         загрузочных записей. Расширенные загрузочные записи организованы
  178.         в односвязный список, в каждой такой записи первый вход указывает
  179.         на соответствующий логический диск, а второй - на следующую расширенную
  180.         загрузочную запись.
  181.         При этом в главной загрузочной записи все адреса разделов являются
  182.         абсолютными номерами секторов. В расширенных же записях адреса разделов
  183.         относительны, причём с разными базами: адрес логического диска
  184.         указывается относительно расширенной записи, а адрес следующей
  185.         расширенной записи указывается относительно начала расширенного
  186.         раздела. Такой разнобой выглядит несколько странно, но имеет место
  187.         быть. Три оставшихся переменных содержат: extended_part_start -
  188.         начало расширенного раздела; extended_parent - текущая рассматриваемая
  189.         расширенная загрузочная запись; extended_part_cur - следующая
  190.         загрузочная запись для рассмотрения.
  191.         Цикл выглядит так: просматриваются все разделы, указанные в текущей
  192.         (главной или расширенной) загрузочной записи; для нормальных разделов
  193.         (они же логические диски) происходит переход на not_extended, где
  194.         устанавливается partition_start и начинается собственно загрузка
  195.         (последующие шаги); при встрече с разделом, тип которого указывает
  196.         на расширенность (5 или 0xF), код запоминает начало этого раздела
  197.         (в главной загрузочной записи такой тип означает расширенный раздел,
  198.         в расширенной - только указатель на следующую расширенную запись,
  199.         в обоих случаях он может встретиться только один раз в данной записи);
  200.         когда код доходит до конца списка, все нормальные разделы, описываемые
  201.         в этой записи, уже просмотрены, так что код с чистой совестью переходит
  202.         к следующей расширенной записи. Если он её не встретил, значит, уже
  203.         все логические разделы были подвергнуты попыткам загрузиться, и все
  204.         безрезультатно, так что выводится ругательство и работа останавливается
  205.         (jmp $).
  206.         Может возникнуть вопрос, зачем нужна такая сложная схема и почему
  207.         нельзя узнать нужный логический диск заранее или хотя бы ограничиться
  208.         первым попавшимся логическим диском, не крутя цикл. Так вот, вариант
  209.         с предварительным определением нужного раздела в данном случае не
  210.         используется, поскольку повлёк бы за собой нетривиальные лишние
  211.         действия по установке (в текущем виде установку можно провести вручную,
  212.         и она сводится к указанию системному загрузчику на существование
  213.         kordldr); кстати, в альтернативной версии загрузки после
  214.         Windows-загрузчика, когда установка осуществляется не вручную, а
  215.         специальной программой под Windows, используется модифицированная
  216.         версия, в которой как раз начальный физический сектор нужного раздела
  217.         прописывается установщиком. Сам kordldr не может установить, с какого
  218.         раздела его загрузил Windows-загрузчик (и вообще под NT/2000/XP обязан
  219.         быть файлом на диске C:\). Вариант с первым попавшимся логическим
  220.         диском был реализован в первой версии загрузчика, но по ходу дела
  221.         обнаружилось, что таки нужно крутить цикл: во-вторых, может быть
  222.         приятным, что сама система может стоять вовсе не на системном C:\, а и
  223.         на других дисках; во-первых, диск C: может и не быть первым логическим
  224.         разделом - Vista любит создавать скрытый первичный раздел перед
  225.         системным, и тогда диск C: становится вторым логическим.
  226. 6. Извещает пользователя о том, что происходит попытка загрузки с очередного
  227.         логического диска.
  228. 7. Читает первый сектор логического диска и определяет файловую систему.
  229.         И в FAT, и в NTFS поле со смещением +11 содержит число байт в секторе
  230.         и должно совпадать с характеристикой физического носителя, то есть
  231.         200h байт. И в FAT, и в NTFS поле со смещением +13 содержит число
  232.         секторов в кластере и должно быть степенью двойки.
  233.         Критерий NTFS: поле со смещением +3 содержит строку NTFS и поле со
  234.         смещением +16 нулевое (в FAT оно содержит число таблиц FAT и обязано
  235.         быть ненулевым).
  236.         Критерий FAT: загрузчик вычисляет число кластеров, определяет
  237.         предположительный тип (FAT12/FAT16/FAT32) и проверяет байт по смещению
  238.         +38 для FAT12/16, +66 для FAT32 (он должен быть равен 0x29).
  239.         После определения типа файловой системы извещает пользователя об
  240.         определённом типе. Если файловая система не распознана, выдаёт
  241.         соответствующее сообщение и переходит к следующему логическому диску.
  242. 8a. Для FAT12-томов: засовывает в стек идентификатор файловой системы -
  243.         константу '12'; устанавливает указатель на функцию получения следующего
  244.         в цепочке FAT кластера на FAT12-обработчик; считывает в память всю
  245.         таблицу FAT12 (она не превосходит 0x1800 байт = 6 Кб), при ошибке
  246.         чтения пытается использовать другие копии FAT.
  247. 8б. Для FAT16-томов: засовывает в стек идентификатор файловой системы -
  248.         константу '16'; устанавливает указатель на функцию получения следующего
  249.         в цепочке FAT кластера на FAT16-обработчик; инициализирует информацию
  250.         о кэше секторов FAT (массив байт с возможными значениями 0 и 1,
  251.         означающими, был ли уже загружен соответствующий сектор - всего в
  252.         таблице FAT16 не более 0x100 секторов) - ни один сектор ещё не
  253.         загружен, все байты нулевые.
  254. 8в. Для FAT32-томов: засовывает в стек идентификатор файловой системы -
  255.         константу '32'; устанавливает указатель на функцию получения следующего
  256.         в цепочке FAT кластера на FAT16-обработчик; инициализирует информацию
  257.         о кэше секторов FAT (формат информации описан выше, в распределении
  258.         используемой загрузчиком памяти) - ни один сектор ещё не загружен.
  259. 8г. Общее для FAT-томов: определяет значения служебных переменных
  260.         root_start (первый сектор корневого каталога в FAT12/16, игнорируется
  261.         при обработке FAT32-томов), data_start (начало данных с поправкой,
  262.         вводимой для того, чтобы кластер N начинался с сектора
  263.         N*sectors_per_cluster+data_start), root_clus (первый кластер корневого
  264.         каталога в FAT32, 0 в FAT12/16); устанавливает указатель на функцию
  265.         загрузки файла на FAT-обработчик.
  266. 8д. Для NTFS-томов: засовывает в стек идентификатор файловой системы -
  267.         константу 'nt'; определяет значение служебной переменной frs_size
  268.         (размер в байтах файловой записи, File Record Segment), для полной
  269.         корректности проверяет, что это значение (равное 0x400 байт на всех
  270.         реальных NTFS-томах - единственный способ изменить его заключается
  271.         в пересоздании всех системных структур вручную) не превосходит 0x1000
  272.         и кратно размеру сектора 0x200 байт; инициализирует кэш файловых
  273.         записей - ничего ещё не загружено; считывает первый кластер $MFT
  274.         и загружает информацию о расположении на диске всей таблицы $MFT
  275.         (атрибут 0x80, $Data); устанавливает указатель на функцию загрузки
  276.         файла на NTFS-обработчик.
  277. 9. (метка load_secondary) Вызывает функцию загрузки файла для файла вторичного
  278.         загрузчика. При обнаружении ошибки переходит на обработчик ошибок с
  279.         соответствующим сообщением.
  280. 10. Устанавливает регистры для вторичного загрузчика: al='h' (жёсткий диск),
  281.         ah=номер диска (для готового бинарника - 0 (BIOS-идентификатор 80h),
  282.         может быть изменён путём модификации константы в исходнике или
  283.         специальным установщиком), bx=идентификатор файловой системы (берётся
  284.         из стека, куда ранее был засунут на шаге 8), ds:si=указатель на
  285.         callback-функцию.
  286. 11. Передаёт управление вторичному загрузчику дальним переходом на 1000:0000.
  287.  
  288. Функция обратного вызова для вторичного загрузчика:
  289.         предоставляет возможность чтения файла.
  290. Вход и выход описаны в спецификации на загрузчик.
  291. Чтение файла:
  292. 1. Сохраняет стек вызывающего кода и устанавливает свой стек:
  293.         ss:sp = 0:3000, bp=dat: пара ss:bp при работе с остальным
  294.         кодом должна указывать на 0:dat.
  295. 2. Разбирает переданные параметры и вызывает процедуру загрузки файла.
  296. 3. Восстанавливает стек вызывающего кода и возвращает управление.
  297.  
  298. Вспомогательные процедуры.
  299. Процедура чтения секторов (read):
  300. на входе должно быть установлено:
  301.         ss:bp = 0:dat
  302.         es:bx = указатель на начало буфера, куда будут прочитаны данные
  303.         eax = стартовый сектор (относительно начала логического диска)
  304.         cx = число секторов (должно быть больше нуля)
  305. на выходе: es:bx указывает на конец буфера, в который были прочитаны данные,
  306.         флаг CF установлен, если возникла ошибка чтения
  307. 1. Переводит стартовый сектор (отсчитываемый от начала тома) в сектор на
  308.         устройстве, прибавляя номер первого сектора логического диска,
  309.         найденный при переборе дисков.
  310. 2. В цикле (шаги 3-6) читает секторы, следит за тем, чтобы на каждой итерации
  311.         CHS-версия: все читаемые секторы были на одной дорожке.
  312.         LBA-версия: число читаемых секторов не превосходило 7Fh (требование
  313.         спецификации EDD BIOS).
  314. CHS-версия:
  315. 3. Переводит абсолютный номер сектора в CHS-систему: сектор рассчитывается как
  316.         единица плюс остаток от деления абсолютного номера на число секторов
  317.         на дорожке; дорожка рассчитывается как остаток от деления частного,
  318.         полученного на предыдущем шаге, на число дорожек, а цилиндр - как
  319.         частное от этого же деления. Если число секторов для чтения больше,
  320.         чем число секторов до конца дорожки, уменьшает число секторов для
  321.         чтения.
  322. 4. Формирует данные для вызова int 13h (ah=2 - чтение, al=число секторов,
  323.         dh=головка, (младшие 6 бит cl)=сектор,
  324.         (старшие 2 бита cl и весь ch)=дорожка, dl=диск, es:bx->буфер).
  325. 5. Вызывает BIOS. Если BIOS рапортует об ошибке, выполняет сброс диска
  326.         и повторяет попытку чтения, всего делается не более трёх попыток
  327.         (несколько попыток нужно в случае дискеты для гарантии того, что
  328.         мотор раскрутился). Если все три раза происходит ошибка чтения,
  329.         переходит на код обработки ошибок с сообщением "Read error".
  330. 6. В соответствии с числом прочитанных на текущей итерации секторов
  331.         корректирует текущий сектор, число оставшихся секторов и указатель на
  332.         буфер (в паре es:bx корректируется es). Если всё прочитано, заканчивает
  333.         работу, иначе возвращается на шаг 3.
  334. LBA-версия:
  335. 3. Если число секторов для чтения больше 7Fh, уменьшает его (для текущей
  336.         итерации) до 7Fh.
  337. 4. Формирует в стеке пакет для int 13h (кладёт все нужные данные командами
  338.         push, причём в обратном порядке: стек - структура LIFO, и данные в
  339.         стеке хранятся в обратном порядке по отношению к тому, как их туда
  340.         клали).
  341. 5. Вызывает BIOS. Если BIOS рапортует об ошибке, переходит на код обработки
  342.         ошибок с сообщением "Read error". Очищает стек от пакета,
  343.         сформированного на предыдущем шаге.
  344. 6. В соответствии с числом прочитанных на текущей итерации секторов
  345.         корректирует текущий сектор, число оставшихся секторов и указатель на
  346.         буфер (в паре es:bx корректируется es). Если всё прочитано, заканчивает
  347.         работу, иначе возвращается на шаг 3.
  348.  
  349. Процедура обработки ошибок (find_error_si и find_error_sp):
  350. на входе: указатель на сообщение об ошибке в si либо на верхушке стека
  351. 0. Если вызывается find_error_si, она помещает переданный указатель в стек.
  352. 1. Если ошибка произошла в процессе работы callback-функции, то
  353.         (метка error_in_callback) обработчик просто возвращает управление
  354.         вызвавшему коду, рапортуя о ненайденном файле.
  355. 2. Если же ошибка произошла до передачи управления вторичному загрузчику,
  356.         обработчик выводит сообщение типа "Error: <текущий объект>: <ошибка>"
  357.         и (восстановив стек) переходит к следующему логическому диску.
  358.  
  359. Процедура чтения файла/атрибута по известному размещению на диске
  360.         (read_file_chunk):
  361. на входе должно быть установлено:
  362.         ds:si = указатель на информацию о размещении
  363.         es:bx = указатель на начало буфера, куда будут прочитаны данные
  364.         ecx = лимит числа секторов для чтения, старшее слово должно быть 0
  365. на выходе: es:bx указывает на конец буфера, в который были прочитаны данные,
  366.         флаг CF установлен, если возникла ошибка чтения
  367. 1. Определяет, является ли атрибут резидентным (возможно только в NTFS
  368.         и означает, что данные файла/атрибута уже были целиком прочитаны при
  369.         обработке информации о файле) или нерезидентным (означает, что данные
  370.         хранятся где-то на диске, и имеется информация о том, где именно).
  371. 2. Для резидентных атрибутов (метка read_file_chunk.resident) просто копирует
  372.         данные по месту назначения (с учётом указанного лимита).
  373. 3. Для нерезидентных атрибутов информация состоит из пар <размер очередного
  374.         фрагмента файла в кластерах, стартовый кластер фрагмента>; процедура
  375.         читает фрагменты, пока файл не закончится или пока не будет достигнут
  376.         указанный лимит.
  377.  
  378. Процедура просмотра кэша (cache_lookup):
  379. на входе должно быть установлено:
  380.         eax = искомое значение
  381.         ss:si = указатель на структуру-заголовок кэша
  382. на выходе: ss:di = указатель на вход в кэше; флаг CF установлен, если значение
  383.         было только что добавлено, и сброшен, если оно уже было в кэше.
  384. 1. Просматривает кэш в поисках указанного значения. Если значение найдено
  385.         (при этом флаг CF оказывается сброшенным), переходит к шагу 4.
  386. 2. Если кэш уже заполнен, удаляет из кэша самый старый вход (он находится в
  387.         голове двусвязного списка), иначе добавляет к кэшу ещё один вход.
  388. 3. Устанавливает в полученном входе указанное значение. Устанавливает флаг
  389.         CF, последующие шаги не меняют состояния флагов. Переходит к шагу 5.
  390. 4. Удаляет вход из списка.
  391. 5. Добавляет сектор в конец списка (самый новый вход).
  392.