┌──┌─┐┌──
──┘├─┘──┘ 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