┌──┌─┐┌──
──┘├─┘──┘ Presents
┐  ┌┐┐┌─┤ VMag, Issue 2, 1 June 1998
└─┘┘ ┘└─┘ ──────────────────────────

                           Memory Control Blocks.


           При  запуске  любой  программы создается как минимум два
       новых  блока  памяти  -  для  собственно  программы и для ее
       окружения.  Сегментный адрес блока окружения можно узнать по
       смещению  2Ch  от  начала PSP. DOS предоставляет три функции
       для работы с блоками памяти:
         ah=48h   Создать,
         ah=49h   Уничтожить, и
         ah=4Ah   Изменить размер блока.
           и  функцию ah=58h для работы со стратегией распределения
       памяти. Подробности - у Ральфа Брауна.
           При  каждом  выделении  блока DOS создает так называемый
       блок  управления  памятью  (Memory  Control  Block  -  MCB).
       Считается,  что  работа  с   MCB   является   исключительной
       прерогативой   DOS  ;)   и   поэтому   недокументирована   в
       официальных  источниках.  MCB  располагается непосредственно
       перед  блоком,  к  которому  относится.  Любое  некорректное
       нарушение  структуры  MCB  с   околоединичной   вероятностью
       вызовет  дружелюбный  текст  "Memory allocation error",  что
       всегда немного фатально.
                               Формат MCB:
           0    1    2   3   4   5   6   7   8   9   A...E   F
         ┌───┬────┬────┬───┬───┬───┬───┬───┬───┬───┬─ ∙∙∙ ─┬───┐
         │Тип│Владелец │Размер │ Reserved  │Имя программы      │
         └───┴────┴────┴───┴───┴───┴───┴───┴───┴───┴─ ∙∙∙ ─┴───┘
              Тип блока может быть
           'M' : Промежуточный блок
           'Z' : Последний блок
           'D' : Блок драйвера
           'F' : В  этом  блоке  хранится  информация  об открытых
                 файлах
           'X' : - // -, но открытых с помощью FCB
           'B' : Здесь находятся DOS'овые буферы
           'L' : DOS хранит в этом блоке информацию о каждом диске
           'S' : Что-то, связанное со стеками.

           Кстати,  в литературе почему-то ;) описаны лишь типы 'M'
       и  'Z' - инициалы "духовного отца" MicroSoft Mark Zbikovski,
       остальное  -  лишь  ни к чему не обязывающие домыслы автора,
       основанные на самоличных наблюдениях.
              Поле  'Владелец' - это сегментный адрес блока памяти,
       которому  "принадлежит"   данный   блок.   Может   принимать
       фиксированные значения
           0   : блок свободен
           8   : DOS-блок
             или сегментный адрес любого другого блока, в том числе
       данного  блока  (владеет  собой сам). Если блок не в силах с
       собой  совладать  ;),  то есть в поле 'Владелец' имеет чужой
       адрес,  то  поле  'Имя  программы'  DOS  игнорируется  - там
       находится всякий мусор.
              Размер  блока  дается  в  параграфах,  если  блок  не
       последний,  то  следующий  MCB  можно  найти  по сегментному
       адресу   old_MCB+Размер+1,  для  последнего  блока  значение
       old_MCB+Размер+1 теоретически должно равняться A000.
              Три  зарезервированных  байта  пока что (точнее, пока
       им  не придумали никакого применения) можно использовать как
       угодно.
              В поле 'Имя  программы' расположено  имя файла без
       расширения и возведенное в верхние регистр.
           Обращение  к  блокам  памяти  идет по цепочке MCB. Адрес
       первого  MCB  хранится во внутренней переменной DOS, и может
       быть получен с помощью недокументированной функции
         ah=52h   Получить  адрес  "списка  списков",  возвращает в
       es:bx  List of lists, и слово по смещению es:[bx-2] является
       сегментным адресом первого MCB.
           Это была теория... Что касается практического применения
       вот этого всего бреда, то
        1. Можно менять тип MCB, в частности 'M'-блок - на 'Z', и в
       этом  случае  от  DOS  будут  скрыты  все  последующие блоки
       (именно  поэтому  выше  можно  встретить фразу "теоретически
       должно равняться A000").
        2. Изменение  размера  последнего  ('Z')  блока  приведет к
       уменьшению реального объема свободной памяти для DOS, причем
       в верхних адресах останется участок "ничей" памяти, опять же
       невидимой для DOS.
Пример: установка скрытого обработчика int 1Ch
----------------
.model tiny
.code
.startup
                mov     ax,ds
                dec     ax
                mov     es,ax                   ;MCB
                xor     di,di
                mov     byte ptr es:[di],'Z'    ;маркировать как последний,
                                                ; если это вдруг не так
                dec     word ptr es:[di+3]      ;уменьшить размер на
                                                ; 1 параграф
                mov     ax,cs
                add     ax,word ptr es:[di+3]
                mov     ds,ax                   ;ds:0 - начало "ничей" памяти
                mov     byte ptr ds:[di],0CFh   ;iret
                xor     dx,dx
                mov     ax,251Ch
                int     21h
                int     20h
end
----------------
        3. Маскировка  под  DOS-блок - запись  в поле 'Владелец' 8.
        4. Любопытная  методика  определения своей (или чужой, если
       известны  данные)   резидентности   в   памяти   с   помощью
       сканирования  MCB-цепочки.  Этот  способ особенно актуален в
       резидентных  кусках  кода без PSP, когда необходимо бороться
       за каждый байт  резидентной  порции. Подобный способ убирает
       несколько  байт,  нужных  для   отработки   механизма   типа
       'запрос/ответ'  или  даже  целый обработчик прерывания, если
       таковое    используется    исключительно   для   определения
       резидентности  (например,  int  2Fh).  В  реализации  он  не
       отличается  от  метода  поиска  по  сигнатурам, просто более
       красив и удобен.
Пример:
----------------
.model tiny
.code
.startup
                mov     ah,52h
                int     21h
                mov     ax,es:[bx-2]
Label_000:
                mov     es,ax
                mov     di,8
                mov     cx,di
                mov     si,offset ID_Text
                repe    cmpsb                   ;мой блок?
                jne     Not_Mine
                cmp     word ptr es:[1],0       ;а может он свободный...
                jne     Already_Installed       ;а-а-а, ну тогда конечно
Not_Mine:
                add     ax,es:[3]
                inc     ax
                cmp     byte ptr es:[0],'Z'
                jne     Label_000
                cmp     ax,0A000h               ;есть ничья память?
                je      Really_Last_Block
                mov     ah,9
                mov     dx,offset Text_3
                int     21h
Really_Last_Block:
        push    ds ds
        pop     es
                mov     ax,ds
                dec     ax
                mov     ds,ax
                mov     bx,word ptr ds:[3]
                dec     bx                      ;уменьшить выделенную
                mov     ah,4Ah                  ;память на 1 параграф
                int     21h
        pop     ds
                mov     ah,48h                  ;и получить этот параграф
                mov     bx,1                    ;или другой свободный
                int     21h
                mov     es,ax
;Этот прием используется, чтобы не допускать "дырок" в памяти. В случае, если
;есть N свободных параграфов памяти или они остались от освобожденного
;окружения, лезем туда, иначе - в верхние адреса. Если этого не сделать, то
;после завершения программы память может выглядеть как:
; XXXX: NC/VC/DN/...
; YYYY: free (осталась от программы)
; ZZZZ: выпрошенные N параграфов
; WWWW: free
; Hеэстетично ;)
                cld
                mov     si,offset _16_bytes     ;это мог бы быть код
                xor     di,di
                mov     cx,16
                rep     movsb
        push    es es
        pop     ax ds
                dec     ax
                mov     es,ax
                mov     word ptr es:[1],ds
        push    cs
        pop     ds
                mov     si,offset ID_Text
                mov     di,8
                mov     cx,di                   ;заполнение program name
                rep     movsb                   ;чем-нибудь своим
                mov     ah,9
                mov     dx,offset Text_1
                int     21h
                int     20h                     ;именно так
Already_Installed:
                mov     ah,9
                mov     dx,offset Text_2
                int     21h
                mov     word ptr es:[di-15],0   ;высвобождение блока
                int     20h
_16_bytes       db 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
Text_1          db 'Now installing...',13,10,'$'
Text_2          db 'It was already installed. Clearing memory...',13,10,'$'
Text_3          db 'There are some hidden blocks in memory.',13,10,'$'
ID_Text         db 'BeHeMoT '
end
----------------

         Другие возможности применения MCB оставляю вашей фантазии.

                                                              15.07.97
                                         BeHeMoT, 2:450/63.215@FidoNet