Форум программистов, компьютерный форум, киберфорум
Наши страницы
Assembler, MASM, TASM
Войти
Регистрация
Восстановить пароль
 
 
Рейтинг 4.72/335: Рейтинг темы: голосов - 335, средняя оценка - 4.72
Charles Kludge
Клюг
7641 / 3156 / 382
Регистрация: 03.05.2011
Сообщений: 8,382
20.12.2013, 22:35 #41
JWasm(краткий экскурс)
JWasm (автор - Andreas 'Japheth' Grech)- это форк ассемблера WASM, допиленый до совместимости с МАСМом, и не только:
- поддержка выходных форматов Intel OMF, MS Coff (32/64-bit), Elf (32/64-bit),
двоичный, Windows PE (32/64-bit) и DOS MZ.
- существуют версии JWasm для DOS, Windows и Linux.
Последняя версия 2.12 вышла вчера, 19.12.2013.
Главное и очень радующее отличие от МАСМа - это многопроходность, т. е. отпадает необходимость ставить секцию .data и определения 'EQU' и '=' в начале проги, перед секцией .code .
Ключики и опции командной строки в основном совпадают с МАСМом, но есть отличия:
JWasm [опции] asm-файл [опции] [asm-файл] ... [@env_var]
-<0|1|..|10>[p] тип процессора: 0=8086 (default), 1=80186, 2=80286, 3=80386,
4=80486, 5=Pentium, 6=PPro, 7=P2, 8=P3, 9=P4, 10=x86-64;
<p> разрешить привелегированные инструкции
-AT не поддерживается, но .com-файл получить можно, об этом ниже.
-Bl не поддерживается
-e<number> макс. кол-во ошибок (умолчание=50)
-eq не выводить сообщения об ошибках
-F не поддерживается
-Fe не поддерживается
-Fd[=<fname>] записать файл импорта
-Fi<fname> принудительно включтить <fname> в трансляцию
-Fm не поддерживается
-Fw<fname> задать имя файла ошибок
-FPi87 команды 80x87 (умолчание)
-fpc запретить команды FPU
-fp<n> тип FPU, <n>: 0=8087 (умолчание), 2=80287, 3=80387
-FR не поддерживается
-Fr не поддерживается
-H не поддерживается
-link не поддерживается
-m<t|s|c|m|l|h|f> модель памяти: (Tiny, Small, Compact, Medium, Large, Huge, Flat)
-nc=<name> имя класса для сегменнта кода
-safeseh Assert all exception handlers are declared
-Sc не поддерживается
-Sg включить генерируемый код в листинг
-Sp,Ss,Sl,St не поддерживается
-zcm добавлять префикс '_' к именам Си (умолчание)
-zcw НЕ добавлять префикс '_' к именам Си
-zf<0|1> задать тип FASTCALL: 0=стиль MS VC (умолчание), 1=регистровое соглашение OW
-Zg генерить код как Masm
-Zi[0|1|2|3] добавить отладочную инфу (OMF & COFF) для: 0=глобальных имен, 1= +локальных, 2= +типов (умолчание), 3= +констант
-zlc не добавлять OMF-записи о данных в коде
-zld не добавлять OMF-записи об оптимизации вызовов far call
-zl<f|p|s> запретить записи в таблице символов COFF: f=o точке входа .file, p=o статических процедурах static procs, s=o доп. точках входа для секций
-Zne запретить расширения синтаксиса, НЕ поддерживаемые Masm'ом
-zt<0|1|2> тип декорации(включения парамеров вызова в имя) для STDCALL: 0=выкл, 1=без суффикса '@size' для ф-ций, 2=полная декорация (умолчание)
-Zv8 включить видимость PROC для Masm v8+
-zze выключить декорацию имён для экспортируемых символов
-zzs сохранить декорированное имя стартового адреса (только COFF)
@env_var имя файла или переменной окружения с опциями

выходные форматы:
-bin бинарный файл
-coff объектный файл формата COFF
-elf объектный файл формата 32-bit ELF
-elf64 объектный файл формата 64-bit ELF
-mz бинарный файл формата DOS MZ
-omf объектный файл формата OMF (умолчание)
-pe бинарный файл формата PE, 32/64-bit
-win64 объектный файл формата 64-bit COFF
Описание остальных опций можно найти в локализованом мане по МАСМу.

Имеем файлик hello.asm
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ifdef DOSCOM
.code
    org 100h
start:
else
.stack
.code
start:  push    cs
    pop ds
endif
    mov dx, offset hello
    mov ah,9
    int 21h
    mov ax, 4c00h
    int 21h
 
hello   db  'Bye-bye, ugly MASM!$'
    end start
из которого можем получить hello.com командой jwasm -mt -bin -Fo=hello.com hello.asm -DDOSCOM
или hello.exe командой jwasm -ms -mz -Fo=hello.exe hello.asm
Страничка проекта: http://www.japheth.de/JWasm.html
5
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
20.12.2013, 22:35
Ответы с готовыми решениями:

Глоссарий для раздела Assembler, MASM, TASM
Thread, AFP, Charles Kludge, 6a6kin, Убежденный, Ethereal, выкладывайте свои...

Организация тем в разделе Assembler, MASM, TASM
Всем привет! Друзья, возник вопрос: как лучше организовать темы в разделе...

Полезные макросы для MASM и TASM
Не претендую на создание чего-то нового и гениального, но макросы довольно...

MASM, TASM, FASM: что выбрать для программирования в ядре
Какой асемлер выбрать для проганья в едре? вынь

Видеоуроки по Ассемблеру MASM/TASM (для DOS) на русском языке
Всем доброго времени суток. Вобщем, ищу видеоуроки на русском языке по...

62
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
30.12.2013, 10:46  [ТС] #42
Reverse Engeneering
Представим что программа написана на языке С, в которой функция xchg меняет местами две целочисленные переменные a и b
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
void xchg (int *a, int *b);
int main() 
{
   int a=2, b=3;
   xchg(&a,&b);
   printf("a= %d b=%d\n",a,b);
   return 0; 
}
void xchg(int *a,int *b)
{
   int temp;
   temp = *a;
   *a = *b;
   *b = temp;
}
Как положено в С, функция xchg получает два указателя на int.
А теперь поставим перед собой задачу научиться сочетать функции написанные на С, и функции, написанные на ассемблере. Проще всего это сделать, подсмотрев, как компилятор транслирует функцию на язык ассемблера.
В компиляторе Borland C++ выдачей листинга на ассемблере управляет ключ -S. Чтобы получить этот листинг, сохраним функцию в отдельном файле xchg.c:
файл xchg.c
C
1
2
3
4
5
6
7
void xchg(int *a, int *b)
{
   int temp;
   temp = *a;
   *a = *b;
   *b = temp;    
}
и запустим из командной строки компилятор
Код
bcc32 -c -S xchg.c
ключ -с в командной строке означает, что на выходе создается только объектный файл xchg.obj, компоновщик не запускается, а ключ -S создаст нам ассемблерный листинг функции.
После запуска компилятора, в папке где хранится исходный текст функции xchg.c появятся обектный файл xchg.obj и ассемблерный листинг xchg.asm. При открытии видно множество непонятных директив, меток, начинающихся знаком вопроса и комментариев. Текст создан компилятором и поэтому трудночитаем, но в нем можно выделить строки непосредственно относящиеся к нашей функции
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
_xchg proc near
?livel@0:
;
; void xchg(int *a, int *b) {
;
          push ebp
          mov ebp,esp
          push ebx
          mov edx,dword ptr [ebp+12]
          mov eax,dword ptr [ebp+8]
;
; int tmp;
; tmp = *a;
;
?livel@16: ; EAX = a, EDX = b
@1:
          mov ecx,dword ptr [eax]
;
;*a = *b;
;
?livel@32: ; EAX = a, EDX = b, ECX = temp
          mov ebx,dword ptr [edx]
          mov dword ptr [eax],ebx
;
;*b = tmp;
;
?livel@48: ; EDX = b, ECX = tmp
          mov dword ptr [edx],ecx
;
; }
;
?livel@64: ;
@2:
          pop ebx
          pop ebp
          ret
_xchg endp
В этом отрывке сочетаются команды ассемблера и комментарии, в которых показаны соответствующие инструкции языка С. Функция начинается прологом
Assembler
1
2
push ebp
mov ebp,esp
после него отсчет параметров переданных в стек идет относительно ebp. Параметры расположены в [ebp+8] и [ebp+12] и передаются в регистры edx, eax
Assembler
1
2
mov edx,dword ptr [ebp+12]
mov eax,dword ptr [ebp+8]
из комментария понятно, что в регистр еах передан параметр а, в регистр edx передан параметр b. Из следующего комментария понятно, что под временную переменную использован регистр есх.
Assembler
1
2
3
4
mov ecx,dword ptr [eax];tmp = *a
mov ebx,dword ptr [edx]; *a = *b
mov dword ptr [eax],ebx;
mov dword ptr [edx],ecx;*b = tmp;
функция завершается выталкиванием из стека регистров ebx, ebp и возвратом из процедуры ret. Регистр ebx выталкивается потому что в начале процедуры он был сохранен в стеке. Содержимое ecx компилятор не сохраняет. Зная "законы компиляции", легко "распотрошить" созданную компилятором функцию и на ее основе создать свою.
4
Charles Kludge
Клюг
7641 / 3156 / 382
Регистрация: 03.05.2011
Сообщений: 8,382
05.01.2014, 13:03 #43
Reverse Engeneering в умелых руках и с хорошим инструментарием вообще способен творить чудеса. То, что на асме выглядело так:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
RegMewMDIStatClass:
    add esp, 0FFFFFFD8h
    mov dword ptr [esp], 83h    ; 'a'
    mov dword ptr [esp+4], offset MDIStatwndproc
    xor eax, eax
    mov [esp+8], eax
    mov dword ptr [esp+0Ch], 4
    mov eax, ds:MewHINST
    mov [esp+10h], eax
    xor eax, eax
    mov [esp+14h], eax
    push    7F00h
    push    0
    call    LoadCursorA
    mov [esp+18h], eax
    xor eax, eax
    mov [esp+1Ch], eax
    xor eax, eax
    mov [esp+20h], eax
    mov eax, offset aMewmdistat; "MewMDIStat"
    mov [esp+24h], eax
    push    esp
    call    RegisterClassA
    add esp, 28h
    retn

Сначала быстро и непринужденно превращается в более читабельное:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
; int __pascal RegMewMDIStatClass()
RegMewMDIStatClass  proc near
 
    wc  = WNDCLASS ptr -28h
 
    add esp, -28h
    mov [esp+28h+wc.style], CS_VREDRAW or CS_HREDRAW or CS_PARENTDC
    mov [esp+28h+wc.lpfnWndProc], offset MDIStatwndproc
    xor eax, eax
    mov [esp+28h+wc.cbClsExtra], eax
    mov [esp+28h+wc.cbWndExtra], 4
    mov eax, ds:MewHINST
    mov [esp+28h+wc.hInstance], eax
    xor eax, eax
    mov [esp+28h+wc.hIcon], eax
    push    IDC_ARROW       ; lpCursorName
    push    0           ; hInstance
    call    LoadCursorA
    mov [esp+28h+wc.hCursor], eax
    xor eax, eax
    mov [esp+28h+wc.hbrBackground], eax
    xor eax, eax
    mov [esp+28h+wc.lpszMenuName], eax
    mov eax, offset aMewmdistat ; "MewMDIStat"
    mov [esp+28h+wc.lpszClassName], eax
    push    esp         ; lpWndClass
    call    RegisterClassA
    add esp, 28h
    retn
    RegMewMDIStatClass  endp
 
; ------------
    align   4
    aMewmdistat db 'MewMDIStat',0; DATA XREF: RegMewMDIStatClass+4B**
    align   10h

а затем уже просто в конфетку:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//  Detected compiler: Delphi
 
#include <windows.h>
#include <defs.h>
 
int __pascal RegMewMDIStatClass()
{
  int result; // eax@1
  WNDCLASS wc; // [sp+0h] [bp-28h]@1
 
  wc.style = CS_PARENTDC|CS_HREDRAW|CS_VREDRAW;
  wc.lpfnWndProc = (WNDPROC)MDIStatwndproc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 4;
  wc.hInstance = MewHINST;
  wc.hIcon = 0;
  wc.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW);
  wc.hbrBackground = 0;
  wc.lpszMenuName = 0;
  wc.lpszClassName = "MewMDIStat";
  LOWORD(result) = RegisterClassA(&wc);
  return result;
};
И это при том, что изначально код был писан на Дельфи.
4
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
16.01.2014, 07:01  [ТС] #44
Статья представляет из себя сокращение 10 главы книги Магда Ю.С. "Ассемблер для процессоров Intel Pentium". — СПб.: Питер, 2006. — 410 с.: ил.
Интерфейс с языками высокого уровня
В процессе разработки программ на языках высокого уровня одной из важнейших проблем, с которой сталкивается разработчик, является производительность приложения. Эффективным средством ее повышения является применение языка ассемблера для разработки критических участков программного кода и оформление их в виде подпрограмм. Необходимость разработки отдельной подпрограммы на ассемблере возникает, когда требуется:
  • реализовать какой-то специальный алгоритм, который требует нетривиальной обработки данных и который трудно создать средствами языка высокого уровня;
  • обеспечить высокое быстродействие какого-либо алгоритма обработки данных или фрагмента программы.
Общие принципы построения интерфейсов
Принципы создания интерфейсов ассемблерных подпрограмм с ЯВУ будут рассматриваться применительно к Microsoft Visual C++ и Borland Delphi. В этих пакетах программ в качестве базовых языков программирования используются языки C++ и Pascal.
Для компиляции ассемблерных подпрограмм и процедур можно задействовать макроассемблер MASM версии 6.14 или 7.10. При разработке и анализе программного кода будут рассматриваться только 32-разрядные подпрограммы на ассемблере, разработанные с помощью модели памяти flat.
В примерах будут использованы отдельно скомпилированные модули на ассемблере (OBJ-файлы), которые компонуются с программами на C++ .NET и Delphi 2005.
версия
компилятора
командная строкаприложение
ml /с /Fo имя_файла.obj имя_файла.asmVisual C++ .NET
6.14ml /с /Fo имя_файла.obj имя_файла.asmDelphi
7.10ml /с /omf /Fo имя_файла.obj имя_файла.asmDelphi
Различия в параметрах связаны с тем, что Visual C++ .NET работает с объектными файлами в формате COFF (Common Object File Format), a Delphi использует файлы в стандарте OMF (Object Module Format). ml.exe версии 6.14 по умолчанию создаст объектный файл в формате OMF, в то время как ml.exe версии 7.10 — в формате COFF.
Если во время сборки приложения на Delphi появятся сообщения наподобие следующих, то это свидетельствует о некорректном формате объектного файла:
[Error] Project1.dpr(15): Е2045 Bad object file format: \...\sub1.obj'
[Error] Project1.dpr(11): E2065 Unsatisfied forward or external declaration: ‘sub1’

Второе сообщение является следствием обнаруженной компилятором некорректности.
Чтобы избежать подобных ошибок во время сборки программ в Delphi, следует указывать параметр /omf (версия 7.10 MASM) при компиляции ассемблерной процедуры. Во время компиляции ассемблерных модулей можно использовать упрощенный вариант командной строки, например:
  • версия 6.14.хххх:
    ml /с имя_файла.asm
  • версия 7.10.xxxx:
    ml /с /omf имя_файла.asm
В этом случае имя объектного файла будет совпадать с именем файла исходного текста. В процессе сборки проекта в Visual C++ .NET вы можете получить предупреждение компоновщика:
Warning: converting object format from OMF to COFF
Это предупреждение свидетельствует о том, что OMF-файл будет преобразован в формат COFF, и принципиально оно ничего не меняет, поскольку компилятор Visual C++ .NET преобразует OMF-файл в формат COFF в любом случае.
Перед сборкой приложения в Visual C++ .NET необходимо добавить в проект объектный файл с вызываемой процедурой. Лучше всего поместить объектный файл с процедурой в рабочий каталог проекта.
При сборке проекта в Delphi следует указать местоположение объектного файла при помощи директивы
{$L путь_к_объектному_файлу}
Перед разработкой ассемблерных процедур для использования в программах на C++ и Pascal остановимся подробно на требованиях, которые должен соблюдать разработчик при создании интерфейса:
  • имена идентификаторов (переменных и процедур), включенных в объектные файлы, должны соответствовать правилам построения имен данным компилятором ЯВУ;
  • модель памяти, используемая ассемблерной подпрограммой, должна быть flat;
  • способ передачи параметров процедуре должен быть указан в вызывающей программе;
  • если вызывающая программа ожидает результат выполнения процедуры, то его тип должен быть указан в программе и реально соответствовать тому, который возвращает ассемблерная процедура.
Для языка Pascal все строчные буквы в именах внешних идентификаторов преобразуются в прописные. Компилятор C++ не изменяет регистр букв, поэтому имена идентификаторов чувствительны к регистру. Компилятор C++ помещает в имя процедуры префикс и суффикс в виде определенной последовательности символов.
Способ передачи параметров ассемблерной процедуре должен учитывать следующее:
  • параметры в вызываемую подпрограмму передаются либо по значению, либо по ссылке. При передаче по значению передается 32-разрядное значение операнда, а при передаче по ссылке — его адрес (32-разрядный);
  • параметры в процедуру могут передаваться через стек, регистры или общую область памяти.
Рассмотрим только наиболее распространенные способы передачи параметров — через стек и регистры.
Способы передачи параметров
ДирективаПередача параметровОчистка стекаИспользование регистров
register (fastcall)Слева направоПроцедура
eax, edx, ecx (Delphi)
ecx, edx (Visual C++ .NET)
pascalСлева направоПроцедураНет
cdeclСправа налевоВызывающая программаНет
stdcallСправа налевоПроцедураНет
safecallСправа налевоПроцедураНет

Директивы, указанные в первой колонке слева, называют соглашениями о вызовах (calling conventions), соглашениями об именовании или соглашениями о передаче параметров. Все эти определения являются синонимами и определяют способы взаимодействия программных модулей (в общем случае, разработанных с помощью разных языков программирования). Любая из этих директив определяет:
  • способ передачи параметров в процедуры;
  • способ синхронизации области стека при его использовании процедурой;
  • способ формирования имен и данных для взаимодействия нескольких программ и/или процедур.
Остальные колонки подробно описывают каждый тип соглашения о вызовах.
Вторая колонка слева показывает порядок размещения параметров в стеке или в регистрах (при передаче параметров через регистры). Например, пусть мнемоника вызываемой процедуры записывается так:
myproc (param1, param2, paramЗ)
Тогда при способе передачи параметров stdcall первым помещается в стек параметр paramЗ, вторым — параметр param2 и третьим — параметр param1. Если, например, используется способ передачи параметров register, то параметр param1 помещается в регистр eax, параметр param2 — в регистр edx и, наконец, параметр paramЗ — в ecx.
Параметры в стеке размещаются, начиная с адреса esp+4, поскольку указатель стека esp содержит эффективный адрес (ЕА) команды следующей после вызова call myproc. Это действительно для любых способов передачи параметров, использующих стек (pascal, cdecl, safecall), параметры будут находиться в стеке, начиная с адреса esp+4.
Область стека
http://www.cyberforum.ru/cgi-bin/latex.cgi?\leftarrowМладшие адресаСтаршие адреса
 esp esp + 4 esp+ 8 esp+ 12
программный
"мусор"
адрес возврата
из процедуры
param1param2рaramЗ
Вызов процедуры myproc в соответствии с соглашением stdcall
Перед возвращением в основную программу необходимо восстанавливать (очищать стек). Речь идет о том, что после завершения процедуры указатель стека смещается на 4 в сторону увеличения адресов и будет указывать на ставшие ненужными параметры. Например, после завершения процедуры myproc регистр esp будет указывать на параметр param1.
Область стека
http://www.cyberforum.ru/cgi-bin/latex.cgi?\leftarrowМладшие адресаСтаршие адреса
  esp esp + 4 esp + 8
??param1param2paramЗ
Содержимое стека после завершения процедуры myproc
Если каждый раз после вызова процедур оставлять стек в таком состоянии, то область памяти стека быстро переполнится, что вызовет ошибку и останов программы. По этой причине указатель стека нужно очищать. Это можно сделать, сместив указатель стека вверх (в сторону увеличения адресов) на величину, определяемую количеством занимаемых ненужными параметрами байтов.
В нашем примере нужно сместить указатель стека на 12 вверх (3 параметра х 4 байта). Такую операцию может выполнить как вызывающая программа, так и сама процедура. Способ очистки стека определяется соглашением о передаче параметров. В случае соглашения stdcall стек очищает сама вызываемая процедура, для чего в исходный текст включается команда ret n, где n — число возвращаемых байтов памяти. Эта команда должна быть последней командой процедуры. Для процедуры myproc команда возврата должна выглядеть как ret 12, хотя вместо нее можно использовать комбинацию команд:
Assembler
1
2
add esp,12
ret
вышеуказанные команды не изменяют содержимое ячеек памяти, выделенных под область стека, поскольку это не имеет смысла (при следующих обращениях к стеку данные перезаписываются).
Выбор того или иного способа передачи параметров определяется практическими аспектами. Если в программе применяются функции Windows API, то стандартным соглашением вызова для них является stdcall. Директива cdecl является стандартной для компиляторов C++ и используется ими по умолчанию при вызове процедур из других модулей.
Наиболее быстрым способом передачи параметров является регистровый (register). В Visual C++ .NET этот способ называется — fastcall. Если количество передаваемых в процедуру параметров не превышает трех, то стек не используется, что и дает выигрыш в скорости.
Способ передачи параметров типа pascal используется редко и поддерживается компиляторами в целях обратной совместимости (backward compatibility).
Вызываемая процедура в большинстве случаев возвращает основной программе результат в регистре eax. Результатом может быть либо непосредственное значение, либо адрес. Во втором случае процедура возвращает ссылку.
Интерфейс ассемблерных процедур с Delphi
Для демонстрации интерфейса ассемблерных процедур с программами на Delphi (и Visual C++ .NET) будем использовать простейшие 32-разрядные консольные приложения, вызывающие процедуру и отображающие результат ее работы на экране.
В первом примере вызываемая процедура (example1) вычисляет разность двух целых чисел, передаваемых ей в качестве параметров, и возвращает результат в регистре eax в основную программу.
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.686
.model flat 
option casemap:none 
.data 
res DD 0 
.code
example1 proc 
    push ebp 
    mov ebp, esp
    finit
    fild dword ptr [ebp+8] 
    fisub dword ptr [ebp+12] 
    fistp dword ptr res 
    mov eax, res 
    pop ebp 
    ret 8 
example1 endp 
end
Здесь процедуре через стек передаются два параметра, представляющие собой целые числа. Параметры процедуры можно извлечь из стека с помощью регистра ebp.
Assembler
1
2
push ebp
mov ebp, esp
Параметры, передаваемые процедуре example1, к этому моменту будут расположены в стеке так, как показано ниже.
Область стека
http://www.cyberforum.ru/cgi-bin/latex.cgi?\leftarrowМладшие адреса Старшие адреса
espesp+ 4esp + 8esp + 12
EAebpПараметр 1Параметр 2
Передача параметров в процедуру example1

Еще один вопрос, который предстоит решить, — какой способ передачи параметров должна выбрать вызывающая программа на Delphi. В нашем примере выберем способ передачи параметров в соответствии с соглашением stdcall. В этом случае по адресу ЕВР+12 будет находиться правый параметр, а по адресу ЕВР+8 — левый. Далее при помощи следующих команд определяется разность двух целых чисел:
Assembler
1
2
fild dword ptr [ЕВР+8] 
fisub dword ptr [ebp+12]
Результат вычитания, находящийся в вершине стека сопроцессора st(0), помещается в переменную res командой
Assembler
1
fistp dword ptr res
содержимое res сохраняется в регистре eax, и после коррекции стека (команда pop ebp) происходит выход из процедуры. Поскольку принято соглашение stdcall, то вызываемая процедура сама должна восстановить указатель стека, командой ret 8.
В исходном тексте процедуры example1 нет упоминания о соглашении stdcall. Вызывающая программа не проверяет, какое соглашение использует процедура: параметры передаются в соответствии с директивами, указанными в основной программе. Точно так же вызывающая программа не проверяет, был ли очищен указатель стека после выхода из вызываемой процедуры. Разработчик должен сам заботиться о корректном написании программного кода процедуры.
Программный код вызывающей программы, написанной на Delphi.
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
program Project1;
{$APPTYPE CONSOLE}
 
uses
    SysUtils;
{$L f:\example1.obj}
 
function example1(i1:Integer;i2:Integer):Integer:stdcall:external;
var
al, a2: Integer; 
ires: Integer:
begin
    a1 := -1601;
    a2 := -8892;
    ires:= example1(a1, a2);
    WriteLn(IntToStr(ires)); 
end.
Следует указать компоновщику путь к объектному модулю, в котором находится процедура example1. Это выполняет директива
Delphi
1
{$L f:\example1.obj}
Имя процедуры и имя объектного файла выбраны совпадающими для удобства, в общем случае они могут различаться.
Вызываемую процедуру нужно объявить как внешнюю (external) и использующую соглашение о вызовах stdcall. Следует указать параметры и их тип, а также тип возвращаемого внешней процедурой значения. Эти действия выполняет директива
Delphi
1
function example1(i1:Integer;i2:Integer):Integer:stdcall:external;
Процедура возвращает результат (ключевое слово function), принимает два целочисленных параметра, i1 и i2, возвращаемое значение является целым числом.
В соответствии с этим объявлением процедуры в вызывающей программе, процедура example1 извлекает параметр i1 по адресу ЕВР+8, а параметр i2 — по адресу ЕВР+12 Результат выполнения процедуры example1 численно равен i1 - i2.
Остальная часть программы на Delphi обеспечивает инициализацию переменных и вызов процедуры example2:
Delphi
1
2
3
а1 := -1601;
а2 := -8892;
ires := example1(a1, а2);
После выполнения этих операторов целочисленная переменная ires будет содержать значение 7291, которое преобразуется к строковому типу функцией IntToStr(ires) и выводится на экран функцией WriteLn.
В этом примере при вызове процедуры example1 было использовано соглашение stdcall. Посмотрим, как изменятся вызывающая программа и процедура example1, если в качестве соглашения об именовании принять cdecl. Что касается процедуры example1, то здесь произойдет только одно изменение — команда возврата из процедуры ret будет использоваться без параметров; этот фрагмент кода выглядит так:
Assembler
1
2
3
4
5
6
7
8
.data
 res DD 0
.code
example1 proc
....
 ret
example1 endp 
end
В вызывающей программе на Delphi в строке объявления процедуры example1 следует заменить директиву stdcall на cdecl:
Delphi
1
function example1(i1:Integer:i2:Integer):Integer:cdecl :external;
Изменения в исходных текстах минимальны. Рассмотрим теперь самый быстрый способ передачи параметров, использующий соглашение register. В этом случае в вызывающей программе изменения также оказываются минимальными, и касаются они объявления процедуры example1:
Delphi
1
function examplеЗ(i1:Integer:i2:Integer):Integer:register:external;
Исходный текст процедуры example1 при использовании соглашения register намного упрощается.
Модифицированный вариант процедуры example1 для соглашения register
Assembler
1
2
3
4
5
6
7
8
9
.686
.model flat
 option casemap:none
 .code
example3 proc 
    sub eax,edx
    ret
example3 endp 
end
Поскольку параметры в процедуру при данном соглашении передаются через регистры, то для вычисления разности i1 — i2 нужно выполнить команду
Assembler
1
sub eax,edx
Cоглашение register предполагает размещение левого параметра (i1) в регистре eax, а правого (i2) — в регистре edx. Поэтому результат можно получить при помощи всего одной команды — sub. Поскольку результат вычитания остается в регистре eax, то на этом процедура examplel работу заканчивает. Как видно, для такого метода передачи параметров требуется меньшее число команд, поскольку не нужно обращаться к области стека и, кроме того, регистровые операции требуют меньше машинных циклов. По этой причине метод вызова процедур с использованием регистров является очень быстрым.
До сих пор мы рассматривали работу процедур, возвращающих в регистре eax определенное значение. Намного чаще в вызывающую программу возвращается не сама переменная, а ее адрес. В этом случае процедура возвращает ссылку. При помощи ссылки можно обращаться к строкам и массивам, что значительно расширяет область применения ассемблерных процедур.
В следующем примере процедура (example2) вычисляет разность двух чисел с плавающей точкой, являющихся ее параметрами, и возвращает адрес результата в вызывающую программу. При вызове процедуры используется соглашение stdcall.
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.686
.model flat
option casemap:none
.data
res DD 0
.code
examp1e2 proc 
    push ebp 
    mov ebp, esp 
    finit
    fid dword ptr [ebp+8] 
    fsub dword ptr [ebp+12] 
    fstp dword ptr res 
    lea eax,res 
    pop ebp 
    ret 8
example2 endp 
end
Во многом исходный текст процедуры example2 напоминает программный код процедуры example1. Но, во-первых, здесь используются команды FPU для операций над числами с плавающей точкой, во-вторых, вместо значения в регистре eax возвращается адрес переменной res, содержащей разность вещественных чисел (команда lea eax,res).
Вызывающая программа для процедуры
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
program Project1:
{$APPTYPE CONSOLE}
uses SysUti1s:
{$L f:\example2.obj}
function example2(x1:Single;x2:Single):PSingle:stdcall:external; 
var
bl, b2: Single; 
fres: PSingle;
begin 
    b1:= -23.78;
    b2:= -45.09;
    fres:= example2(b1, b2);
    WriteLn(FloatToStr(fres^));
end
компоновщику Delphi нужно указать где находится объектный файл с процедурой examplе2, что выполняется директивой
Delphi
1
{$L f:\example2.obj}
Далее объявляется процедура examplе2 с соответствующими атрибутами:
Delphi
1
function example2(x1:Single;x2:Single):PSingle:stdcal1:external;
Параметрами этой функции выступают числа с плавающей точкой в коротком (Single) формате x1 и х2. Процедура возвращает указатель (PSingle) на результат вычитания x1 из х2.
Обратите внимание на два оператора:
Delphi
1
2
fres:= examplе2(b1, b2);
WriteLn(FloatToStr(fres^));
Первый оператор запоминает адрес результата в переменной-указателе fres, а второй выполняет следующие действия:
  1. Разыменовывает указатель fres (оператор ^ извлекает значение по указанному адресу).
  2. Преобразует полученное число в строку символов функцией FloatToStr.
  3. Выводит результат на экран функцией WriteLn.
Интерфейс ассемблерных процедур с Visual C++ .NET
Используем тексты процедур example1 и example2. В первом примере при помощи процедуры example1 вычислим разность двух целых чисел и отобразим результат операции на экране. Предположим, что процедура использует соглашение stdcall. В этом случае исходный текст процедуры example1 останется практически без изменений, но потребуется внести коррективы в имя процедуры: добавить в начале имени символ подчеркивания, а в конце имени — суффикс @n, где n — число байтов, необходимое для передачи параметров. Подобная форма именования соответствует требованиям компилятора C++ для соглашения stdcall.
Модифицированная версия процедуры example1 для работы с C++ .NET (соглашение stdcall)
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.686
.model flat
option casemap:none
.data
res DD 0
.code
_examplel@8 proc
    push ebp
    mov ebp, esp
    finit
    fild dword ptr [ebp+8]
    fisub dword ptr [ebp+12]
    fistp dword ptr res
    mov eax, res
    pop ebp
    ret 8 
_examplel@8 endp 
end
Обратите внимание на способ формирования имени ассемблерной процедуры (_example1@8) — он соответствует требованиям соглашения stdcall.
Вызывающая программа на Visual C++ .NET
C++
1
2
3
4
5
6
7
8
9
#include <stdio.h>
extern "С" int __stdcall example1(int i1, int i2);
int main(void)
{
    int i1 = 455; 
    int i2 = -743;
    printf("EXAMPLE1: %d\n", example1(i1, i2)); 
    return 0;
}
В этой программе процедура example1 объявлена следующим образом:
C++
1
extern "С" int __stdcall example1(int i1, int i2);
Ключевое слово extern указывает на то, что процедура example1 является внешней, директива __stdcall устанавливает соглашение об именовании и, кроме
этого, требует формирования имени вызываемой процедуры специальным образом (как было показано ранее).
Для компилятора Visual C++ .NET соглашением об именовании по умолчанию является cdecl, поэтому указывать его необязательно. Любое другое соглашение (stdcall, fastcall) необходимо указывать явным образом при объявлении внешней процедуры. Двойное подчеркивание при задании соглашения об именовании обязательно.
Оператор "С" предотвращает декорирование имени процедуры. Этот параметр имеет смысл только для компилятора C++, помните, что в объявлении процедуры присутствие оператора "С" необходимо.
Модифицируем предыдущий пример так, чтобы вызываемая процедура использовала соглашение об именовании cdecl. Для этого в исходном тексте процедуры example1 изменим имя процедуры, чтобы оно удовлетворяло соглашению cdecl — в начале имени должен присутствовать символ подчеркивания, в то время как оставшаяся часть остается неизменной. Кроме того, в исходном тексте процедуры команду ret нужно указать без параметров.
Модифицированная версия процедуры example1, использующая соглашение cdecl
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.686
.model flat 
option casemap:none 
.data 
res DD 0 
.code
_example1 proc 
    push ebp 
    mov ebp, esp
    finit
    fild dword ptr [ebp+8]
    fisub dword ptr [ebp+12]
    fistp dword ptr res 
    mov eax,res
    pop ebp
    ret
_example1 endp
 end
Программный код вызывающей программы на Visual C++ .NET
C++
1
2
3
4
5
6
7
8
9
#include <stdio.h>
extern "С" int example1 (int i1, int i2);
int main(void)
{
    int i1 = 45; 
    int i2 = -73;
    printf("EXAMPLE1: %d\n", example1(i1, i2)); 
    return 0;
}
Обратите внимание па объявление внешней процедуры example1. Здесь отсутствует явное указание соглашения о передаче параметров, поскольку по умолчанию используется соглашение cdecl.
Проведем анализ самого быстрого способа передачи параметров — fastcall. В соответствии с таблицей первые два параметра в вызываемую процедуру передаются слева направо с помощью регистров ecx и edx, а остальные — справа налево через стек. Кроме того, имя вызываемой процедуры при таком соглашении должно формироваться следующим образом: в начале имени процедуры ставится знак амперсанда (@), а в конце — сочетание символов имеющее тот же смысл, что и для соглашения stdcall.
Модифицированная версия процедуры example1, использующая соглашение fastcall
Assembler
1
2
3
4
5
6
7
8
9
10
.686
.model flat 
option casemap:none 
.code
@examp1el@8 proc 
    mov eax,ecx 
    sub eax,edx
    ret
@examplel@8 endp
 end
Вызывающая программа на Visual C++ .NET
C++
1
2
3
4
5
6
7
8
9
#include <stdio.h>
extern "С" int __fastcall example1(int i1, int i2);
int main(void)
{
    int i1 = 145; 
    int i2 = -203;
    printf("EXAMPLEl: %d\n", example1(i1, i2));
    return 0:
}
До сих пор все рассматриваемые версии процедуры example1 возвращали в вызывающую программу непосредственное значение. Сейчас мы проанализируем пример процедуры, возвращающей не значение переменной, а указатель на значение (адрес). Далее приводится исходный текст процедуры examplе2, вычисляющей разность двух чисел с плавающей точкой, которые являются входными параметрами для этой процедуры. Процедура возвращает в регистре eax адрес результата. Будем полагать, что для процедуры examplе2 принято соглашение об именовании stdcall.
Вычисление разности двух чисел с плавающей точкой
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.686
.model flat 
option casemap:none 
.data 
    res DD 0 
.code
_example2@8 proc 
    push ebp 
    mov ebp,esp 
    finit
    fid dword ptr [ebp+8] 
    fsub dword ptr [ebp+12] 
    fstp dword ptr res 
    lea eax,res 
    pop ebp 
    ret 8 
_example2@8 endp 
end
Программный код вызывающей программы на Visual C++ .NET
C++
1
2
3
4
5
6
7
8
9
#include <stdio.h>
extern "С" float* __stdcall example2(float f1, float f2);
int main(void)
{
    float f1 = 1.45; 
    float f2 = -2.03;
    printf("\nEXAMPLE2: %5.2f\n”. *example2(f1, f2)); 
    return 0;
}
Обратите внимание на то, как обрабатываются ссылки в Visual C++ .NET. Рассмотрим объявление процедуры examplе2:
C++
1
extern "С" float* __stdcall examplе2(float f1, float f2);
В этом объявлении указатель на число с плавающей точкой в коротком формате декларируется как float*. Для получения значения числа и вывода его на экран дисплея следует разыменовать указатель, для чего используется оператор разыменования *, который должен помещаться перед указателем. Следующий оператор C++ демонстрирует это:
C++
1
printf("\nEXAMPLE2: %5.2f\n". *example2(f1, f2));
5
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
18.01.2014, 03:54  [ТС] #45
Включение ассемблерных процедур в программы на Бейсике
Процедуры на языке ассемблера состоят из строк байтов машинного кода. При выполнении этой процедуры Бейсик передает управление из последовательности инструкций, составляющих программу на Бейсике, в то место, где хранятся инструкции, которые могут быть декодированы в последовательность инструкций языка ассемблера. При завершении ассемблерной процедуры управление возвращается в то место программы, откуда была вызвана процедура.
Ассемблерные процедуры, используемые в программах на Бейсике, могут быть представлены двумя вариантами. В обоих вариантах процедуры включены в программу, а не хранятся в виде отдельного файла. В первом варианте требуется, чтобы коды процедуры находились в отдельном месте в памяти, а для второго варианта, этого не требуется.
В первом варианте процедура помещается в операторы DATA и программа пересылается в неиспользуемую часть памяти, а затем вызывается оператором CALL. Чтобы код процедуры не накладывался на какие-либо данные и наоборот. Для этого процедура помещается в те адреса памяти, к которым Бейсик не может получить доступ. Поскольку интерпретатор Бейсика не имеет доступ за пределы 64K, то для системы с памятью 256K, нужно поместить процедуру в старших 64K адресах. Для систем с памятью 128K нужно вычислить сколько памяти требуется операционной системе, Бейсику и драйверам устройств. Допустимо, чтобы они занимали 25K плюс 64K, используемых Бейсиком. В системах с 64K при старте используется команда CLEAR, которая ограничивает объем памяти доступный для Бейсика. CLEAR,n ограничивает Бейсик n байтами. Затем процедура размещается в самых верхние адресах памяти.
Для указания начала области, куда будет помещена процедура, используется оператор DEF SEG, затем с помощью оператора READ считываются байты процедуры и помещаются в память до тех пор, пока вся процедура не будет помещена на место. Например:
PureBasic
1
2
3
4
5
6
7
8
9
10
100 DATA &Hxx, &Hxx, &Hxx, &Hxx, &Hxx  '10-байтная процедура
110 DATA &Hxx, &Hxx, &Hxx, &Hxx, &Hxx
 .
 .
300 '''помещаем процедуру в память
310 DEF SEG = &H3000   'указываем на область памяти
320 FOR N = 0 TO 9     'для каждого из 10 байтов
330 READ Q             'читаем байт данных
340 POKE N,Q           'помещаем его в память
350 NEXT
После загрузки процедура в память перед ее использованием, необходимо чтобы последний оператор DEF SEG указывал на начало процедуры. Затем целой переменной присваевается значение 0 и пишется оператор CALL с именем этой переменной. Если процедуре передаются параметры, то они должны быть указаны в скобках в конце оператора CALL. Например:
PureBasic
1
2
3
4
5
6
500 DEF SEG = &H3000   'указываем на начало процедуры
510 DOGS = 12          'у нее 3 параметра
520 CATS = 44          '
530 POSSUMS = 1        '
540 CASUALTIES = 0     'начинаем выполнение с 1-го байта
550 CALL CASUALTIES(DOGS,CATS,POSSUMS)  'выполняем процедуру
Второй вариант создания ассемблерных процедур более простой и экономичный, позволяет избежать проблем с распределением памяти. Создается процедура в виде строковой переменной внутри программы. Каждый байт кодируется с помощью CHR$. Затем используется функция VARPTR для определения положения этой строки в памяти. Смещение по которому находится эта переменная хранится в двух байтах, которые идут за тем, на который укажет VARPTR (в первом байте содержится длина строки). Затем этот адрес используется для вызова процедуры. Оператор DEF SEG используется для указания на сегмент данных Бейсика, чтобы полученное смещение указывало на адрес строки для оператора CALL. Например:
PureBasic
1
2
3
4
5
100 DEF SEG         'устанавливаем сегмент на данные Бейсика
110 X$ = "CHR$(B4)+..."  'код процедуры
120 Y = VARPTR(X$)       'получаем дескриптор строки
130 Z = PEEK(Y+1)+PEEK(Y+2)*256  'вычисляем ее адрес
140 CALL Z
Многие значения, выражаемые через CHR$() могут быть представлены и в виде символов ASCII. Вы можете писать ROUT = CHR$(12) + "AB" вместо ROUT = CHR$(12) + CHR$(65) + CHR$(66). Hа самом деле большинство символов ASCII могут вводиться путем нажатия клавиши Alt, наборе номера кода на дополнительной клавиатуре, а затем отпускания клавиши Alt. Однако коды от 0 до 31 не могут быть введены таким образом для наших целей.


Переделано из Джордейн Р. "Справочник программиста персональных компьютеров типа IBM PC, XT и AT" М:."Финансы и статистика" 1992
2
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
20.01.2014, 09:55  [ТС] #46
Всё о резидентах
П.Абель
РЕЗИДЕНТНЫЕ ПРОГРАММЫ


Существует ряд распространенных фирменных программ (Prokey, Superkey, Homebase, Sidekick и др.), специально разработанных как резидентные, которые находятся в памяти во время работы других программ. Эти программы можно активизировать нажатием определенных клавиш. Такие программы называются резидентными, и они загружаются в память сразу после загрузки DOS перед выполнением обычных программ.
Для того, чтобы оставить резидентную COM-программу в памяти, необходимо вместо команды ret или int 20h для выхода использовать команду int 27h или функцию DOS 31h. Для int 27h следует передать системе в регистре dx размер программы:
Assembler
1
2
mov dx,prog-size
int     27h
При выполнении программы инициализации DOS резервирует (выделяет) в старших доступных адресах блок памяти и загружает в него резидентную программу. Это наиболее простая часть создания резидентной программы.
Более сложная часть включает программирование механизма активизации резидентной программы, которая хотя и присоединена к DOS, но не является внутренней программой DOS, как DIR, COPY или CLS. Общим подходом является модификация таблицы векторов прерываний таким образом, чтобы резидентная программа, получала управление при нажатии определенных клавиш или их комбинаций, а все остальные передавала через себя. Резидентная программа обычно (но не обязательно) состоит из следующих частей:
  1. секции, переопределяющей адреса в таблице векторов прерываний;
  2. процедуры, выполняющейся только один раз при загрузке программы и предназначенной для:
    • замены адреса в таблице векторов прерываний на собственный адрес;
    • установки размера части программы, которая должна стать резидентной;
    • использования прерывания DOS для завершения программы и создания резидентной части по указанному размеру;
  3. процедуры, которая остается резидентной и активизируется, например, по вводу с клавиатуры или по сигналам таймера.
Процедура инициализации должна создать необходимые условия для того, чтобы обеспечить работу резидентной программы, а затем - высшая жертва! - позволить себе быть стертой в памяти. В результате память будет распределена следующим образом:
АдресаСодержимое
000000h-003FFhТаблица векторов прерываний
000400h-Область данных BIOS
Драйверы устройств DOS
...
Резидентная часть программы - остается в памяти
PSP
Инициализирующая часть программы - перекрывается следующей программой
Остальная часть доступной памяти
0A0000h-0AFFFFhВидеобуфер графика
0B0000h-0BFFFFhВидеобуфер текст
0С0000h-0C7FFFh 
0С8000h-ПЗУ дисков
0F0000h-0FFFFFhROM BIOS
100000h-10FFEFhОбласть HIMEM

Рассмотрим пример резидентной программы (1.ZIP - 1131 байт). Программа перехватывает обработчик прерываний клавиатуры
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
; masm dos com #
comment * Резидентная программа для очистки
экрана и установки цвета при нажатии Alt+Left Shift *
.286
.model tiny
.code
org     100h
begin:  jmp     initze   ;Выполняется только один раз
colors: pusha    ;Сохранить регистры        
        push ds
    push 40h
    pop ds
        cmp byte ptr ds:[17h],1010y;Alt+Left Shift нажаты?
        jne exit          ; нет - выйти
        mov ax,615h      ;Функция прокрутки
;AL=число строк прокрутки вверх AL=00 - очистить всё окно
;ВН=атрибут, используемый при выводе на экран пустых строк внизу окна
        mov bh,0C0h  ;Установить алый цвет
        xor cx,cx;СН,СL=строка, столбец верхнего левого угла окна
        mov dx,014Fh;DH,DL=строка, столбец нижнего правого угла окна
        int 10h
exit:   pop ds
        popa  ;Восстановить регистры
    db 0EAh  ;Обработать прерывание
kbsave  dd   ?;двойное слово для сохранения обработчика адреса int 9 BIOS 
initze: push 0;Подпрограмма инициализации       
        pop ds;Установить сегмент данных ds=0
        cli   ;Запретить прерывания
    mov si,9*4  ; адрес для int 9 в таблице векторов прерываний
    mov di,offset kbsave;для COM-программ es=cs
        movsw;ds:[9*4]->cs:kbsave;сохранить смещение старого обработчика
        movsw;ds:[9*4+2]->cs:kbsave+2;сохранить сегменный адрес старого обработчика
        mov word ptr ds:[si-4],offset colors;подменить смещение int 9 на смещение нового обработчика
        mov word ptr ds:[si-2],cs;подменить сегменный адрес int 9 на сегменный адрес нового обработчика
        sti    ;Разрешить прерывания
        mov dx,offset initze;Размер программы
        int 27h;Завершить и остаться резидентом
end     begin


Пример иллюстрирует резидентную программу, которая устанавливает цвет экрана при одновременном нажатии клавиш Alt и Left Shift. Основные моменты, представляющие интерес:
Флаговый байт клавиатуры (KBFLAG), который отражает состояние клавиатуры, находится по адресу 417h. Бит 3 в этом байте регистрирует нажатие клавиши Alt, а бит 1 - нажатие клавиши Left Shift.
Первая выполняемая команда jmp initze обходит резидентную часть и передает управление в процедуру инициализации (initze) в конце программы. Эта процедура устанавливает регистр DS равным сегментному адресу таблицы векторов прерываний, копирует адрес элемента таблицы для int 9 в переменую kbsave в резидентной части программы. Следующим шагом в таблице утанавливается адрес резидентного обработчика и содержимое регистра cs. Таким образом, вектор прерывания int 9 содержит два измененных слова: смещение и значение адреса из регистра cs, которые вместе определяют адрес процедуры colors в памяти, куда будут направляться теперь все символы, поступающие с клавиатуры. Затем процедура инициализации заносит в регистр dx размер процедуры COLORS (адрес INITZE на один байт больше, чем адрес конца процедуры COLORS) и прекращает работу, используя int 27h.
Процедура COLORS является резидентной, и она получает управление при нажатии любой клавиши на клавиатуре. Так как это происходит при работе других программ (например, DOS или текстового редактора), то процедура должна сохранить все регистры, которые она использует (а также несколько других на всякий случай). Затем происходит вызов по адресу KBSAVE, т.е. вызов подпрограммы обработки прерывания, после чего процедура проверяет флаг клавиатуры для определения нажатия клавиш Alt и Left Shift. Если эти клавиши были нажаты, то процедура устанавливает необходимые цвета. Завершающие команды включают восстановление всех запомненных вначале регистров (в обратной последовательности) и выход из обработки прерывания по команде IRET.
Поскольку приведенная программа носит иллюстративный характер, ее можно модифицировать или расширить для собственных целей. Некоторые фирменные программы, также изменяющие адрес в векторной таблице для прерывания 9, не разрешают конкурентное использование резидентных программ, аналогичных рассмотренной в данной главе.


Написание резидентных программ для MS DOS
В статье описаны основные шаги, используемые при написании резидентных программ на ассемблере для операционной системы MS DOS. В конце статьи подробно разобран пример резидентной программы.
Резидентная программа для MS DOS представляет собой фрагмент кода, постоянно находящийся в оперативной памяти компьютера и вызываемый при возникновении определённых условий. Далее будет показано как написать резидентную программу на ассемблере, постоянно находящуюся в памяти и вызываемую при возникновении в системе прерываний. Сначала рассмотрим определения и основные типы прерываний для процессоров x86.
Прерывание для процессоров x86 представляет собой некоторое событие в системе, нуждающееся в определённой обработке. При возникновении прерывания, за исключением одного случая, выполнение текущей программы прерывается и происходит обработка прерывания. После обработки прерывания продолжается выполнение прерванной программы.
Для процессоров x86 существуют следующие виды прерываний: аппаратные, программные и внутренние прерывания процессора. Аппаратные прерывания, в свою очередь, разделяются на маскируемые и немаскируемые. Маскируемые аппаратные прерывания при определённых условиях могут быть проигнорированны процессором, а немаскируемые прерывания обрабатываются всегда.
Аппаратное прерывание можно определить как запрос от некоторого периферийного устройства (клавиатура, последовательный порт, дисковод и т. д.) на обработку данных этого устройства, управление им или возникновение исключительной ситуации для этого устройства. При возникновении такого запроса выполнение текущей программы прерывается (если это прерывание не замаскировано) и вызывается процедура обработчика прерывания. Обработчик прерывания выполняет необходимые действия для получения данных от периферийного устройства или для управления им и возвращает управление в прерванную программу.
Программные прерывания представляют собой вызов каких-либо функций или сервисов операционной системы и прикладных программ с использованием команды INT XX, где XX - номер прерывания от 0 до 255. Внутренние прерывания процессора возникают при выполнении программой каких-либо операций, вызывающих фатальные ошибки (например, деление на 0, переполнение при делении, выход за границы сегмента и т. д.), а также при использовании режима отладки.
В любом случае, при возникновении прерывания какого-либо типа вызывается обработчик этого прерывания, который представляет собой специальным образом оформленную процедуру. Для аппаратных прерываний обработчик прерывания должен помимо работы с устройством, вызвавшим прерывание, выполнить некоторые операции по управлению аппаратурой механизма прерываний процессора x86.
Рассмотрим процесс написания процедуры обработчика прерывания на ассемблере, вызываемого при возникновении программного прерывания. Общая структура и синтаксис для обработчика программного прерывания:

Assembler
1
2
3
4
5
6
7
8
9
10
11
NAME proc
     ; 1. сохранение модифицируемых регистров
               . . .
     ; 2. инициализациясегментных регистров
               . . .
     ; 3. выполнение необходимых действий
               . . .
     ; 4. восстановление используемых регистров
               . . .
     IRET
NAME ENDP
Идентификатор NAME определяет имя процедуры обработчика, которое может быть любой последовательностью разрешённых в ассемблере символов, но не должно быть служебным или зарезервированным словом.
В секции 1 выполняется сохранение всех регистров, изменяемых в процедуре обработчика. Это необходимо для того, чтобы после возвращения управления в прерванную программу, она получила регистры в том же виде, какими они были до вызова программного прерывания. Если прерывание должно возвращать в вызвавшую его программу некоторые результаты в регистрах, то сохранять значение этих регистров не требуется.
В секции 2 выполняется инициализация сегментных регистров DS, ES или SS для обращения процедуры обработчика прерывания к своим внутренним данным, стеку или некоторому дополнительному сегменту. Значения инициализируемых регистров должны быть сохранены в секции 1.
В секции 3, собственно, выполняется основной код процедуры обработчика прерывания, выполняются необходимые действия и заносятся значения в регистры, если прерывание должно возвращать в вызвавшую его программу некоторые результаты в регистрах.
В секции 4 происходит восстановление значений для изменённых процедурой обработчика прерывания регистров, кроме тех регистров, в которых вызвавшей прерывание программе возвращаются результаты.
Команда IRET выполняет возврат из процедуры обработчика прерывания в вызвавшую его программу.
Рассмотрим подробнее какие действия выполняют команды INT и IRET.
Так как при выполнении команды INT XX должен быть вызван некоторый обработчик прерывания с номером XX необходимо по номеру узнать адрес обработчика в памяти (сегмент и смещение). Для этого служит специальная таблица векторов прерываний, располагающаяся по адресу 0000:0000 в оперативной памяти компьютера. Эта таблица содержит 256 четырёхбайтовых значений, определяющих адреса обработчиков прерываний в памяти. Первые 15 четырёхбайтовых значений в таблице зарезервированны для аппаратных прерываний (маскируемых и немаскируемых) и для внутренних прерываний процессора. Остальные значения в таблице определяют адреса обработчиков программных прерываний. Среди этих значений есть и такие, которые предназначены для пользовательских обработчиков программных прерываний. Первые два байта для каждой ячейки в таблице определяют смещение обработчика соответствующего программного прерывания. Следующие два байта определяют сегмент обработчика прерывания. При вызове команды INT XX выполняются следующие действия:
В стеке сохраняются в следующей последовательности: регистр флагов, сегментный регистр CS, регистр указателя команд IP. Сбрасываются флаги IF и TF в регистре флагов.
Вычисляется смещение относительно начала таблицы векторов прерываний: смещение=XX * 4, где XX - номер прерывания.
В сегментный регистр CS по вычисленному смещению из таблицы векторов прерываний заносится значение сегмента обработчика прерывания, а в регистр IP - смещение обработчика прерывания.
Происходит передача управления на обработчик программного прерывания. При этом все регистры кроме CS, IP и регистра флагов сохраняют своё значение таким, каким оно было до вызова команды INT XX.
Таким образом, при входе в обработчик программного прерывания, в стеке находятся значения регистров CS, IP и регистра флагов. Эти значения находились в данных регистрах до вызова команды INT XX. В вершине стека располагается значение регистра IP.
При вызове команды IRET выполняются следующие действия:
  1. Из стека восстанавливается значение регистра IP.
  2. Из стека восстанавливается значение регистра CS.
  3. Из стека восстанавливается значение регистра флагов.
Происходит передача управления в прерванную программу, на команду, находящуюся непосредственно за командой программного прерывания INT XX.
После выполнения команды IRET структура стека становится такой же, какой она была до вызова команды INT XX.
Таковы основные моменты, используемые при написании обработчиков программных прерываний. Рассмотрим теперь структуру и работу обработчиков аппаратных прерываний.
В отличие от обработчиков программных прерываний, обработчики аппаратных прерываний вызываются не командой INT, а самим процессором. Выше было сказано, что при написании обработчиков аппаратных прерываний они должны выполнять ещё и некоторые действия по управлению аппаратурой механизма прерываний процессора x86. В простейшем случае, структура такого обработчика выглядит следующим образом:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
NAME PROC
     ; 1. сохранение модифицируемых регистров
               . . .
     ; 2. инициализациясегментных регистров
               . . .
     ; 3. выполнение необходимых действий
               . . .
     ; 4. восстановление используемых регистров
               . . .
     MOV AL, 20h
     OUT 20h, AL
     IRET
NAME endp
Команда OUT 20h, AL выполняет действия по управлению аппаратурой механизма прерываний процессоров x86. Конкретно, она посылает сигнал EOI (End Of Interrupt - конец прерывания) в контроллер прерываний, сообщая ему таким образом, что обработка аппаратного прерывания завершена.
При возникновении аппаратного прерывания от некоторого периферийного устройства контроллер прерываний выполняет проверку, не замаскировано ли это прерывание. Если оно не замаскировано, то контроллер выполняет сравнение приоритетов этого прерывания с другим, если несколько прерываний поступили в контроллер одновременно. Если прерывание замаскировано или заблокировано, то оно игнорируется контроллером. После выбора прерывания с более высоким приоритетом (логика назначения приоритетов прерываниям может быть запрограммирована пользователем) контроллер посылает сигнал INTR (Interrupt Request - запрос прерывания) в процессор. Если в процессоре в регистре флагов сброшен флаг прерывания IF, то сигнал INTR игнорируется. Если флаг IF установлен, то процессор отвечает контроллеру сигналом INTA (Interrupt Acknoledge) на что контроллер, в свою очередь, посылает процессору номер вектора прерывания для выбранного прерывания и блокирует все прерывания этого и более низкого приоритета. Процессор по полученному номеру вектора прерывания отыскивает в таблице векторов прерываний адрес соответствующего обработчика аппаратного прерывания и вызывает его.
Команда OUT 20h, AL, вызываемая в конце обработчика аппаратного прерывания, разблокирует контроллер прерываний, разрешая ему работу с ранее заблокированными прерываниями.
Если требуется написать обработчик аппаратного прерывания, который должен только выполнять определённые действия при возникновении аппаратного прерывания (например, выдавать звуковой сигнал при нажатии на любую клавишу), всю работу по управлению соответствующей аппаратурой можно возложить на системный обработчик этого аппаратного прерывания. В таком случае, структура обработчика будет следующей:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SYS_HANDLER DD ?
               . . .
NAME PROC
     PUSHF
     CALL CS:SYS_HANDLER
 
     ; 1. сохранение модифицируемых регистров
               . . .
     ; 2. инициализациясегментных регистров
               . . .
     ; 3. выполнение необходимых действий
               . . .
     ; 4. восстановление используемых регистров
               . . .
     IRET
NAME endp
Команда CALL CS:OLD_HANDLER вызывает системный обработчик нужного аппаратного прерывания, который выполняет все необходимые действия по управлению аппаратурой и контроллером прерываний. OLD_HANDLER определяет ячейку памяти размером в двойное слово (4 байта) для хранения адреса системного обработчика прерывания. Команда PUSHF создаёт в стеке структуру для команды IRET, вызываемой в системном обработчике. Подобный подход можно использовать и для программных прерываний, когда помимо тех действий, которые выполняет системный обработчик программного прерывания (например, INT 10h - прерывание BIOS) нужно выполнить какие-либо дополнительные действия. Также можно определить структуру обработчика программного или аппаратного прерывания, когда системный обработчик вызывается в конце процедуры нашего обработчика:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
SYS_HANDLER DD ?
               . . .
NAME PROC
     ; 1. сохранение модифицируемых регистров
               . . .
     ; 2. инициализациясегментных регистров
               . . .
     ; 3. выполнение необходимых действий
               . . .
     ; 4. восстановление используемых регистров
               . . .
     JMP SYS_HANDLER
NAME endp
В этом случае команда JMP SYS_HANDLER выполняет дальний переход на системный обработчик прерывания, поэтому в конце нашего обработчика не нужно вызывать команду IRET - она будет вызвана в системном обработчике.
После того, как определено, каким образом оформить процедуру обработчика аппаратного или программного прерывания, рассмотрим действия, необходимые для того, чтобы эта процедура обработчика вызывалась при возникновении прерывания.
Как уже было сказано выше, в оперативной памяти компьютера по адресу 0000:0000 располагается таблица векторов прерываний, элементы которой определяют адреса обработчиков прерываний в памяти. Для обработчика программного или аппаратного прерывания без вызова системного обработчика нужно лишь записать в соответствующий элемент таблицы векторов прерываний значение сегмента и смещения этого обработчика. Рассмотрим необходимые операции для записи сегмента и смещения в таблицу для обработчика программного или аппаратного прерывания с номером N:
Assembler
1
2
3
4
5
6
7
8
9
MOV AX, 0000H                   ; запись в ES значения
MOV ES, AX                      ; сегмента 0000h
MOV DI, N                       ; запись в DI номера обработчика
MOV CL, 2                       ; умножение DI
SHL DI, CL                      ; на 4
MOV AX, OFFSET HANDLER          ; запись в AX смещения обработчика
STOSW                           ; сохранение смещения в таблице
MOV AX, SEGMENT HANDLER         ; запись в AX сегмента обработчика
STOSW                           ; сохранение сегмента в таблице
После выполнения этих действий и при выполнении команды INT N будет вызван обработчик, адрес которого был установлен в таблице векторов прерываний.
Рассмотрим теперь необходимые операции для установки сегмента и смещения в таблице для обработчика программного или аппаратного прерывания, в котором будет вызван системный обработчик этого прерывания. Для этого перед записью в таблицу новых значений сегмента и смещения нужно сначала сохранить значения сегмента и смещения системного обработчика:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SYS_HANDLER DD ?                        ; определение ячейки памяти для хранения
                                        ; адреса системного обработчика
     . . .
 
MOV AX, 0000H                           ; запись в ES значения
MOV ES, AX                              ; сегмента 0000h
MOV DI, N                               ; запись в DI номера обработчика
MOV CL, 2                               ; умножение DI
SHL DI, CL                              ; на 4
MOV WORD PTR SYS_HANDLER, ES:[DI]       ; сохранение смещения системного обработчика
MOV AX, OFFSET HANDLER                  ; запись в AX смещения нового обработчика
STOSW                                   ; сохранение смещения в таблице
MOV WORD PTR SYS_HANDLER+2, ES:[DI]     ; сохранение сегмента системного обработчика
MOV AX, SEGMENT HANDLER                 ; запись в AX сегмента нового обработчика
STOSW                                   ; сохранение сегмента в таблице
При установке значений сегмента и смещения обработчика аппаратного прерывания нужно до этого сбросить флаг IF (команда CLI), а после установки новых значений установить флаг IF (команда STI). Это необходимо для того, чтобы в процессе установки значений сегмента и смещения не возникло аппаратное прерывание.
Приведённые выше фрагменты кода можно упростить, используя функции прерывания DOS INT 21h. Функция DOS 35h позволяет получить адрес обработчика прерывания. При этом в регистр AH записывается номер функции (35h), в регистр AL записывается номер прерывания. После вызова прерывания INT 21h в регистре ES возвращается значение сегмента обработчика указанного прерывания. В регистре BX взвращается значение смещения обработчика указанного прерывания:
Assembler
1
2
3
4
5
6
7
8
9
SYS_HANDLER DD ?
 
     . . .
 
MOV AH, 35H
MOV AL, N
INT 21H
MOV WORD PTR SYS_HANDLER, BX
MOV WORD PTR SYS_HANDLER+2, ES
Функция DOS 25h позволяет установить адрес обработчика прерывания. В регистр AH записывается номер функции (25h), в регистр AL записывается номер прерывания. В регистр DS записывается значение сегмента обработчика прерывания, адрес которого нужно установить в таблице векторов прерываний. В регистр DX записывается значение смещения обработчика прерывания:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
MY_HANDLER PROC
 
     . . .
 
MY_HANDLER_ENDP
 
     . . .
 
MOV AH, 25H
MOV AL, N
MOV DS, SEGMENT MY_HANDLER
MOV DX, OFFSET MY_HANDLER
INT 21H
При написании обработчика аппаратного прерывания нужно учитывать то, что он должен быть завершен до возникновения очередного аппаратного прерывания для этого обработчика. Если это условие не выполнено, то, в лучшем случае, возникшее прерывание будет потеряно. В худшем случае невыполнение этого условия может повлечь за собой потерю данных или зависание компьютера. Например, при написании аппаратного обработчика прерываний от таймера, код обработчика должен выполнятся по времени менее 1/18 с., так как прерывания от таймера по умолчанию генерируются 18.2 раз в секунду. То же можно сказать и об обработчике аппаратных прерываний от клавиатуры - код обработчика должен выполняться не дольше задержки повторения символов.
Также при написании любого обработчика прерывания нужно инициализировать сегментный регистр DS, если обработчик обращается к каким-либо внутренним ячейкам памяти. Вместо этого можно использовать обращение к ячейкам памяти с использованием префикса замены сегмента (например CS:[BX]), но это увеличивает размер соответствующей команды. Использование префикса замены сегмента эффективно в том случае, когда количество обращений к внутренним ячейкам памяти обработчика невелико (2 - 3).
Рассмотрим теперь средства для завершения резидентной программы и сохранении части её кода в памяти для последующего использования при возникновении прерываний.
Прерывание DOS INT 27h. Прерывание предназначено для завершения программы и сохранения её резидентной в памяти. Для этого в регистр DX нужно занести количество байт для сохраняемой части программы плюс один байт. Начало сохраняемой части программы совпадает со смещением 0000h относительно кодового сегмента, поэтому для COM программ нужно учитывать размер префикса программного сегмента (PSP - Program Segment Prefix) - 256 байт.
Прерывание DOS INT 21h, функция 31h. Функция 31h прерывания DOS INT 21h выполняет те же действия, что и прерывание DOS INT 27h, но в регистр DX заносится размер сохраняемой части программы в параграфах (блоки памяти длиной 16 байт). Также имеется возможность определить код возврата при завершении резидентной программы (заносится в регистр AL).
Ниже приведёна общая структура резидентной программы:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
; данные для резидентной части программы
     ...          
HANDLER PROC
 
     ...          
HANDLER ENDP
; данные для нерезидентной части программы
 
     ...          
; основная часть программы (нерезидентная)
 
     ...          
; установка (и получение) адреса обработчика
; завершение программы и сохранение её резидентной в памяти
Следующий фрагмент кода даёт пример определения основной структуры резидентной программы на ассемблере (программа типа COM):
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
CODE SEGMENT                         ; определение кодового сегмента
ASSUME CS:CODE, DS:CODE              ; CS и DS указывают на сегмент кода
ORG 100H                             ; размер PSP для COM файла
BEGIN: JMP START                     ; переход на нерезидентную часть программы
 
SYS_HANDLER DD ?                     ; определение ячейки памяти для хранения
                                     ; адреса системного обработчика
A DW 0                               ; определение внутренних ячеек памяти для
B DB 1                               ; резидентной части программы 
 
     ...
 
KB_HANDLER PROC                      ; процедура обработчика прерываний
                                     ; от клавиатуры
PUSHF                                ; создание в стеке структуры для IRET
CALL CS:SYS_HANDLER                  ; вызов системного обработчика
 
     ...
 
IRET                                 ; возврат из обработчика
KB_HANDLER ENDP
KB_END:                              ; метка для определения размера резидентной
                                     ; части программы
C DB 2                               ; ячейки памяти для нерезидентной части
D DW 3                               ; программы
 
     ...
 
START:                               ; начало нерезидентной части программы
 
     ...
 
MOV AH, 35H                          ; получение адреса системного обработчика
MOV AL, 09H                          ; прерываний от клавиатуры
INT 21H
MOV WORD PTR SYS_HANDLER, BX
MOV WORD PTR SYS_HANDLER+2, ES
 
MOV AH, 25H                          ; установка адреса нового обработчика
MOB AL, 09H                          ; прерываний от клавиатуры
MOV DX, OFFSET KB_HANDLER
INT 21H
 
MOV DX, (KB_END + 10FH) / 16         ; вычисление размера резидентной части
INT 31H                              ; завершение резидентной программы с
                                     ; сохранением части её кода в памяти
CODE ENDS                            ; конец сегмента кода
END BEGIN                            ; конец программы
Рассмотрим пример резидентной программы "часы" на ассемблере (0.ZIP - 1216 байт). Программа перехватывает обработчик прерываний от таймера и с возникновением очередного прерывания выводит в левом верхнем углу экрана текущее время. Ниже представлен исходный текст программы с комментариями.
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
; masm dos com #
.286
.model tiny
.code    
org     100h
start:  jmp load
decode  proc
        db 0D4h,10h; распаковываем BCD-число
        add ax,'00'; превращаем неупакованное BCD-число в ASCII-символ
        mov word ptr es:[di],0F00h+":"; двоеточие - разделитель часов, минут, секунд
        mov es:[di+2],ah;выводим на экран первую цифру
        mov byte ptr es:[di+3],0Fh ;атрибут символа (ярко-белый на черном фоне)
        mov es:[di+4],al;выводим на экран вторую цифру
        mov byte ptr es:[di+5],0Fh ;атрибут символа (ярко-белый на черном фоне)
        add di,6
        ret           ; возврат из процедуры
decode  endp          ; конец процедуры 
clock   proc          ; процедура обработчика прерываний от таймера
        push es       ; сохранение модифицируемых регистров
    pusha
        push 0B800h
        pop es
        mov di,-2
    mov al,4
    out 70h,al    
    in al,71h     ; в AL - часы
        call decode
        mov al,2
    out 70h,al
    in al,71h     ; в AL - минуты
        call decode
    mov al,0
    out 70h,al
    in al,71h     ; в AL - секунды
        call decode
    popa
        pop es        ; восстановление модифицируемых регистров
        db 0EAh       ; начало кода команды JMP FAR
old_int_1Ch dd ?; вызов старого обработчика 1Ch прерываний и возврат из обработчика
clock   endp          ; конец процедуры обработчика
load:   mov ax,3      ; стираем с экрана и устанавливаем текстовый режим 80х25
    int 10h       ; 0B800h - начало сегмента видеобуфера
    mov ax,351Ch  ; получение адреса старого обработчика
        int 21h       ; прерываний от таймера
    mov word ptr old_int_1Ch,bx; сохранение смещения обработчика
        mov word ptr old_int_1Ch+2,es; сохранение сегмента обработчика
        mov ax,251Ch  ; установка адреса нашего обработчика
        mov dx,offset clock; указание смещения нашего обработчика
        int 21h
        mov ax,3100h  ; функция DOS завершения резидентной программы
        mov dx,(load - start + 10Fh)/16; определение размера резидентной части программы в параграфах
        int 21h
        end start                    ; конец программы
Рассмотрим работу программы.
Идентификатор old_int_1Ch определяет ячейку памяти размером 4 байта, которая хранит адрес старого обработчика прерываний от таймера. Эта ячейка будет нужна, когда будет вызываться старый обработчик прерываний от таймера.
Процедура decode преобразует двоично-десятичное число в регистре AL в два ASCII символа, соответствующих значению часов, минут или секунд (это зависит от конкретного значения, записанного в регистре AL). Процедура прибавляет к значению разряда числа (младший разряд находится в первых четырёх битах регистра AL, старший - в старших четырёх битах) ASCII код символа '0', тем самым формируя ASCII код для цифры младшего или старшего разряда. Далее этот ASCII код выводится на экран.
Процедура clock является обработчиком прерываний от таймера. Дело в том, что номер аппаратного обработчика прерываний от таймера - 8. Но в системном обработчике этого аппаратного прерывания есть вызов INT 1Ch. Прерывание 1Ch определяет пользовательский обработчик прерываний от таймера, который вызывается системным. Таким образом, ячейка old_int_1Ch хранит адрес старого пользовательского обработчика прерываний от таймера. В начале процедуры clock на экран выводится текущее время, а затем вызывается старый пользовательский обработчик прерываний от таймера. В процедуре clock сохраняются в стеке все модифицируемые регистры (в том числе и сегмента ES). Следующим шагом является получение значения текущего времени из СМOS. Значения часов, минут и секунд представлено в двоично-десятичном формате - младший разряд числа находится в младших четырёх битах регистра, а старший разряд числа - в старших четырёх битах. Трижды вызывается процедура decode для записи на экран соответственно часов, минут и секунд. Регистр ES настраивается на сегмент видеопамяти, а регистр DI настраивается на начало сегмента видеопамяти. Далее в цикле происходит вывод в видеопамять символов и атрибутов. После этого восстанавливаются значения всех модифицируемых процедурой регистров и происходит возврат из обработчика.
В основной (нерезидентной) части программы при помощи функции DOS 35h происходит получение адреса старого пользовательского обработчика прерываний от таймера (прерывание 1Сh). Значения сегмента и смещения старого обработчика записываются в ячейку old. Далее устанавливается адрес нашего обработчика прерываний от таймера. Для этого в регистр DX записывается смещение нашего обработчика (смещение процедуры clock) и вызывается функция DOS 25h (регистр DS уже содержит значение сегмента нашего обработчика). После этого вычисляется размер резидентной части программы в параграфах. Для этого сначала вычисляется размер резидентной части в байтах, не считая префикса программного сегмента. Затем к полученному значению прибавляется размер префикса программного сегмента - 256 и число 0Fh. Прибавление числа 0Fh необходимо, чтобы округление при делении на 16 осуществлялось в большую сторону. После вычисления размера резидентной части в параграфах происходит вызов функции DOS 31h, которая завершает программу и сохраняет часть её кода в памяти. Резидентная программа запущена.


Зубков
Резидентные программы
Программы, остающиеся в памяти, после того как управление возвращается в DOS, называются резидентными. Превратить программу в резидентную просто — достаточно вызвать специальную системную функцию DOS.
Функция DOS 31h: Оставить программу резидентной
Ввод: АН = 31h
 AL = код возврата
 DX = размер резидента в 16-байтных параграфах (больше 06h), считая от начала PSP

Кроме того, существует и иногда используется предыдущая версия этой функции — прерывание 27h:


INT 27h: Оставить программу резидентной

Ввод:АН = 27h
 DX = адрес последнего байта программы (считая от начала PSP) + 1

Эта функция не может оставлять резидентными программы размером больше 64 Кб, но многие программы, написанные на ассемблере, соответствуют этому условию. Так как резидентные программы уменьшают объем основной памяти, их всегда пишут на ассемблере и оптимизируют для достижения минимального размера.

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

В качестве первой резидентной программы рассмотрим именно пассивный резидент, который будет активироваться при попытке программ вызывать INT 21h и запрещать удаление файлов с указанного диска.

Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
; tsr.asm
; Пример пассивной резидентной программы.
; Запрещает удаление файлов на диске, указанном в командной строке, всем
; программам, использующим средства DOS
        .model     tiny
        .code
        org        2Ch
envseg             dw    ?          ; сегментный адрес копии окружения DOS
 
        org        80h
cmd_len            db    ?          ; длина командной строки
cmd_line           db    ?          ; начало командной строки
 
        org        100h             ; СОМ-программа
start:
old_int21h:
        jmp        short initialize ; эта команда занимает 2 байта, так что
        dw         0                ; вместе с этими двумя байтами получим
                                    ; old_int21h dd ?
int21h_handler     proc   far       ; обработчик прерывания 21h
        pushf                       ; сохранить флаги
        cmp        ah,41h           ; Если вызвали функцию 41h (удалить
        je         fn41h            ; файл)
        cmp        ax,7141h         ; или 7141h (удалить файл с длинным именем),
        je         fn41h            ; начать наш обработчик,
        jmp        short not_fn41h  ; иначе - передать управление
                                    ; предыдущему обработчику
fn41h:
        push       ax               ; сохранить модифицируемые
        push       bx               ; регистры
        mov        bx,dx
        cmp        byte ptr ds:[bx+1],':' ; если второй символ ASCIZ-строки,
                                          ; переданной INT 21h,
                                          ; двоеточие - первый символ
                                          ; должен быть именем диска,
        je         full_spec
        mov        ah,19h           ; иначе:
        int        21h              ; функция DOS 19h - определить текущий диск,
        add        al,'А'           ; преобразовать номер диска к заглавной букве,
        jmp        short compare    ; перейти к сравнению
full_spec:
        mov        al,byte ptr [bx] ; AL = имя диска из ASCIZ-строки
        and        al,11011111b     ; преобразовать к заглавной букве
compare:
        cmp        al,byte ptr cs:cmd_line[1]  ; если диски
        je         access_denied    ; совпадают - запретить доступ,
        pop        bx               ; иначе: восстановить
        pop        ax               ; регистры
not_fn41h:
        popf                        ; и флаги
        jmp        dword ptr cs:old_int21h ; и передать управление
                                    ; предыдущему обработчику INT 21h
access_denied:
        pop        bx               ; восстановить регистры
        pop        ax
        popf
        push       bp
        mov        bp,sp
        or         word ptr [bp+6],1 ; установить флаг переноса
                                     ; (бит 0) в регистре флагов,
                                     ; который поместила команда INT в стек
                                     ; перед адресом возврата
        pop         bp
        mov         ax,5              ; возвратить код ошибки "доступ запрещен"
        iret                          ; вернуться в программу
int21h_handler     endp
 
initialize         proc    near
        cmp        byte ptr cmd_len,3  ; проверить размер командной строки
        jne        not_install         ; (должно быть 3 - пробел, диск, двоеточие),
        cmp        byte ptr cmd_line[2],':'   ; проверить третий символ
        jne        not_install         ; командной строки (должно быть двоеточие),
        mov        al,byte ptr cmd_line[1]
        and        al,11011111b        ; преобразовать второй
                                       ; символ к заглавной букве,
        cmp        al,'А'              ; проверить, что это не
        jb         not_install         ; меньше "А" и не больше
        cmp        al,'Z'              ; "Z",
        ja         not_install         ; если хоть одно из этих условий
                                       ; не выполняется - выдать информацию
                                       ; о программе и выйти, иначе - начать
                                       ; процедуру инициализации
        mov        ax,3521h            ; АН = 35h, AL = номер прерывания
        int        21h                 ; получить адрес обработчика INT 21h
        mov        word ptr old_int21h,bx    ; и поместить его в old_int21h
        mov        word ptr old_int21h+2,es
 
        mov        ax,2521h            ; AH = 25h, AL = номер прерывания
        mov        dx,offset int21h_handler ; DS:DX - адрес нашего обработчика
        int        21h                  ; установить обработчик INT 21h
        mov        ah,49h               ; AH = 49h
        mov        es,word ptr envseg   ; ES = сегментный адрес блока с нашей
                                        ; копией окружения DOS
        int        21h                  ; освободить память из-под окружения
        mov        dx,offset initialize ; DX - адрес первого байта за концом
                                        ; резидентной части программы
        int        27h                  ; завершить выполнение, оставшись
                                        ; резидентом
 
not_install:
        mov        ah,9                 ; АН = 09h
        mov        dx,offset usage      ; DS:DX = адрес строки с информацией об
                                        ; использовании программы
        int        21h                  ; вывод строки на экран
        ret                             ; нормальное завершение программы
 
; текст, который выдает программа при запуске с неправильной командной строкой:
usage   db        "Использование: tsr.com D:",0Dh,0Ah
        db        "Запрещает удаление на диске D:",ODh,OAh
        db        "$"
initialize        endp
        end       start
Если запустить эту программу с командной строкой D:, никакой файл на диске D нельзя будет удалить командой Del, средствами оболочек типа Norton Commander и большинством программ для DOS. Действие этого запрета, однако, не будет распространяться на оболочку Far, которая использует системные функции Windows API, и на программы типа Disk Editor, обращающиеся с дисками при помощи функций BIOS (INT 13h). Несмотря на то что мы освободили память, занимаемую окружением DOS (а это могло быть лишних 512 или даже 1024 байта), наша программа все равно занимает в памяти 352 байта из-за того, что первые 256 байт отводятся для блока PSP. Существует возможность оставить программу резидентной без PSP — для этого инсталляционная часть программы должна скопировать резидентную часть с помощью, например, movs в начало PSP. Но при этом возникает сразу несколько проблем: во-первых, команда INT 27h, так же как и функция DOS 31h, использует данные из PSP для своей работы, во-вторых, код резидентной части должен быть написан для работы с нулевого смещения, а не со 100h, как обычно, и, в-третьих, некоторые программы, исследующие выделенные блоки памяти, определяют конец блока по адресу, находящемуся в PSP программы — владельца блока со смещением 2. С первой проблемой можно справиться вручную, создав отдельные блоки памяти для резидентной и инсталляционной частей программы, новый PSP для инсталляционной части и завершив программу обычной функцией 4Ch или INT 20h. Реальные программы, делающие это, существуют (например, программа поддержки нестандартных форматов дискет PU_1700), но мы не будем чрезмерно усложнять наш первый пример и скопируем резидентную часть не в позицию 0, а в позицию 80h, то есть, начиная с середины PSP, оставив в нем все значения, необходимые для нормальной работы функций DOS.

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

Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
; tsrpsp.asm
; Пример пассивной резидентной программы с переносом кода в PSP.
; Запрещает удаление файлов на диске, указанном в командной строке,
; всем программам, использующим средства DOS
 
        .model     tiny
        .code
        org        2Ch
envseg             dw    ?          ; сегментный адрес копии окружения DOS
 
        org        80h
cmd_len            db    ?          ; длина командной строки
cmd_line           db    ?          ; начало командной строки
 
        org        100h             ; СОМ-программа
start:
old_int21h:
        jmp        short initialize ; переход на инициализирующую часть
 
int21h_handler     proc    far      ; обработчик прерывания 21h
        pushf                       ; сохранить флаги
        cmp        ah,41h           ; Если вызвали функцию 41h
                                    ; (удалить файл)
        je         fn41h
        cmp        ax,7141h         ; или 7141h (удалить файл
                                    ; с длинным именем),
        je         fn41h            ; начать наш обработчик,
        jmp        short not_fn41h  ; иначе - передать
                                    ; управление предыдущему обработчику
fn41h:
        push       ax               ; сохранить модифицируемые
        push       bx               ; регистры
        mov        bx,dx            ; можно было бы использовать
                                    ; адресацию [edx+1], но в старшем
                                    ; слове EDX совсем не обязательно 0,
        cmp        byte ptr [bx+1],':'  ; если второй символ
                                        ; ASCIZ-строки, переданной INT 21h,
                                        ; двоеточие, первый символ должен
                                        ; быть именем диска,
        je         full_spec
        mov        ah,19h           ; иначе:
        int        21h              ; функция DOS 19h - определить
                                    ; текущий диск
        add        al,'А'           ; преобразовать номер диска
                                    ; к заглавной букве
        jmp        short compare    ; перейти к сравнению
full_spec:
        mov        al,byte ptr [bx] ; AL = имя диска из ASCIZ-строки
        and        al,11011111b     ; преобразовать к заглавной букве
compare:
        db         3Ch              ; начало кода команды CMP AL,число
drive_letter:      db    'Z'        ; сюда процедура инициализации
                                    ; впишет нужную букву
        pop        bx               ; эти регистры больше не
        pop        ax               ; понадобятся, если диски совпадают -
        je         access_denied    ; запретить доступ
not_fn41h:
        popf                        ; восстановить флаги и передать
                                    ; управление предыдущему
                                    ; обработчику INT 21h:
                   db    0EAh       ; начало кода команды
                                    ; JMP, FAR-число
old_int21h         dd    0          ; сюда процедура инициализации
                                    ; запишет адрес предыдущего
                                    ; обработчика INT 21h
access_denied:
        popf
        push       bp
        mov        bp,sp              ; чтобы адресоваться в стек
                                      ; в реальном режиме,
        or         word ptr [bp+6],1  ; установить флаг
                                      ; переноса (бит 0) в регистре
                                      ; флагов, который поместила команда
                                      ; INT в стек перед адресом возврата
        pop        bp
        mov        ax,5               ; возвратить код ошибки
                                      ; "доступ запрещен"
        iret                          ; вернуться в программу
int21h_handler     endp
 
tsr_length         equ   $-int21h_handler
 
initialize         proc  near
        cmp        byte ptr cmd_len,3 ; проверить размер
                                      ; командной строки
        jne        not_install        ; (должно быть 3 -
                                      ; пробел, диск, двоеточие)
        cmp        byte ptr cmd_line[2],':'  ; проверить
                                      ; третий символ командной
        jne        not_install        ; строки (должно быть двоеточие)
        mov        al,byte ptr cmd_line[1]
        and        al,11011111b       ; преобразовать второй
                                      ; символ к заглавной букве
        cmp        al,'A'             ; проверить, что это не меньше "А"
        jb         not_install        ; и не больше
        cmp        al,'Z'             ; "Z",
        ja         not_install        ; если хоть одно из
                                      ; этих условий
; не выполняется - выдать информацию о программе и выйти,
; иначе - начать процедуру инициализации
        mov        byte ptr drive_letter,al  ; вписать имя
                                             ; диска в код резидента
        push       es
        mov        ax,3521h           ; АН = 35h,
                                      ; AL = номер прерывания
        int        21h                ; получить адрес обработчика INT 21h
        mov        word ptr old_int21h,bx  ; и вписать его
                                           ; в код резидента
        mov        word ptr old_int21h+2,es
        pop        es
 
        cld                           ; перенос кода резидента,
        mov        si,offset int21h_handler  ; начиная
                                             ; с этого адреса,
        mov        di,80h                    ; в PSP:0080h
        rep        movsb
        mov        ax,2521h                  ; AH = 25h,
                                             ; AL = номер прерывания
        mov        dx,0080h           ; DS:DX - адрес нашего обработчика
        int        21h                ; установить обработчик INT 21h
        mov        ah,49h             ; AH = 49h
        mov        es,word ptr envseg ; ES = сегментный адрес блока
                                      ; с нашей копией окружения DOS
        int        21h                ; освободить память из-под
                                      ; окружения
        mov        dx,80h+tsr_length  ; DX - адрес первого
                                      ; байта за концом резидентной части
                                      ; программы
        int        27h                ; завершить выполнение,
                                      ; оставшись резидентом
not_install:
        mov        ah,9               ; АН = 09h
        mov        dx,offset usage    ; DS:DX = адрес строки
                                      ; с информацией об
                                      ; использовании программы
        int        21h                ; вывод строки на экран
        ret                           ; нормальное завершение
                                      ; программы
 
; текст, который выдает программа при запуске
; с неправильной командной строкой:
usage   db         "Usage: tsr.com D:",0Dh,0Ah
        db         "Denies delete on drive D:",0Dh,0Ah
        db         "$"
initialize         endp
        end        start
Теперь эта резидентная программа занимает в памяти только 208 байт.
Мультиплексорное прерывание
Если вы запустите предыдущий пример несколько раз, с разными или даже одинаковыми именами дисков в командной строке, объем свободной памяти DOS каждый раз будет уменьшаться на 208 байт, то есть каждый новый запуск устанавливает дополнительную копию резидента, даже если она идентична уже установленной. Разумеется, это неправильно — инсталяционная часть обязательно должна уметь определять, загружен ли уже резидент в памяти перед его установкой. В нашем случае это не приводит ни к каким последствиям, кроме незначительного уменьшения объема свободной памяти, но во многих чуть более сложных случаях могут возникать различные проблемы, например многократное срабатывание активного резидента по каждому аппаратному прерыванию, которое он перехватывает.

Для того чтобы идентифицировать себя в памяти, резидентные программы обычно или устанавливали обработчики для неиспользуемых прерываний, или вводили дополнительную функцию в используемое прерывание. Например: наш резидент мог бы проверять в обработчике INT 21h АН на равенство какому-нибудь числу, не соответствующему функции DOS, и возвращать в, например, AL код, означающий, что резидент присутствует. Очевидная проблема, связанная с таким подходом, — вероятность того, что кто-то другой выберет то же неиспользуемое прерывание или что будущая версия DOS станет использовать ту же функцию. Именно для решения этой проблемы, начиная с версии DOS 3.3, был предусмотрен специальный механизм, позволяющий разместить до 64 резидентных программ в памяти одновременно, — мультиплексорное прерывание.
INT 2Fh: Мультиплексорное прерывание
Ввод:
АН = идентификатор программы
00h – 7Fh зарезервировано для DOS/Windows
B8h – BFh зарезервировано для сетевых функций
C0h – FFh отводится для программ
 AL = код функции
00h — проверка наличия программы
остальные функции — свои для каждой программы
 ВХ, СХ, DX = 0 (так как некоторые программы выполняют те или иные действия в зависимости от значений этих регистров)
Вывод: Для подфункции AL = 00h, если установлен резидент с номером АН, он должен вернуть 0FFh в AL и какой-либо идентифицирующий код в других регистрах, например адрес строки с названием и номером версии.
Оказалось, что такого уровня спецификации совершенно недостаточно и резидентные программы по-прежнему работали по-разному, находя немало способов конфликтовать между собой. Поэтому появилась новая спецификация — AMIS (альтернативная спецификация мульти-плексорного прерывания). Все резидентные программы, следующие этой спецификации, должны поддерживать базовый набор функций AMIS, а их обработчики прерываний должны быть написаны в соответствии со стандартом IBM ISP, который делает возможным выгрузку резидентных программ из памяти в любом порядке.

Начало обработчика прерывания должно выглядеть следующим образом:
смещение
длина
в байтах
 
+00h:2 0EBh, 10h (команда jmp short на первый байт после этого блока)
+02h:4 адрес предыдущего обработчика: именно по адресу, хранящемуся здесь, обработчик должен выполнять call или jmp
+06h:2 424Вh — сигнатура ISP-блока
+08h:1 80h, если это первичный обработчик аппаратного прерывания (то есть он посылает контроллеру прерываний сигнал EOI). 00h, если это обработчик программного прерывания или дополнительный обработчик аппаратного
+09h:2 команда jmp short на начало подпрограммы аппаратного сброса — обычно состоит из одной команды IRET
+0Bh:7 зарезервировано
Все стандартное общение с резидентной программой по спецификации AMIS происходит через прерывание 2Dh. При установке инсталляционная часть резидентной программы должна проверить, не установлена ли ее копия, просканировав все идентификаторы от 00 до 0FFh, и, если нет, установить обработчик на первый свободный идентификатор.
INT 2Dh: Мультиплексорное прерывание AMIS
Ввод:АН = идентификатор программы
 AL = код функции
  • 00: проверка наличия
  • 01: получить адрес точки входа
  • 02: деинсталляция
  • 03: запрос на активизацию (для «всплывающих программ»)
  • 04: получить список перехваченных прерываний
  • 05: получить список перехваченных клавиш
  • 06: получить информацию о драйвере (для драйверов устройств)
  • 07 – 0Fh — зарезервировано для AMIS
  • 1Fh – 0FFh — свои для каждой программы
Вывод:

Рассмотрим функции, описанные в спецификации AMIS как обязательные.


INT 2Dh AL = 00h: Функция AMIS — проверка наличия резидентной программы

Ввод:АН = идентификатор программы
 AL = 00h
Вывод:AL = 00h, если идентификатор не занят
 AL = FFh, если идентификатор занят
 СН = старший номер версии программы
 CL = младший номер версии программы
 DX:DI = адрес AMIS-сигнатуры, по первым шестнадцати байтам которой и происходит идентификация
  • Первые 8 байт — имя производителя программы
  • следующие 8 байт — имя программы
  • затем или 0 или ASCIZ-строка с описанием программы, не больше 64 байт.
INT 2Dh AL = 03h: Функция AMIS — выгрузка резидентной программы из памяти

Ввод:АН = идентификатор программы
 AL = 02h
 DX:BX = адрес, на который нужно передать управление после выгрузки
Вывод:ВХ = сегментный адрес резидента
 AL = код функции
  • 01h — выгрузка не удалась
  • 02h — выгрузка сейчас невозможна, но произойдет чуть позже
  • 03h — резидент не умеет выгружаться сам, но его можно выгрузить, резидент все еще активен
  • 04h — резидент не умеет выгружаться сам, но его можно выгрузить, резидент больше неактивен
  • 05h — сейчас выгружаться небезопасно — повторить запрос позже
  • 06h — резидент был загружен из CONFIG.SYS и выгрузиться не может, резидент больше неактивен
  • 07h — это драйвер устройства, который не умеет выгружаться сам
INT 2Dh AL = 03h: Функция AMIS — запрос на активизацию

Ввод:АН = идентификатор программы
 AL = 03h
Вывод:AL = код функции
  • 00h — резидент — «невсплывающая» программа
  • 01h — сейчас «всплывать» нельзя — повторить запрос позже
  • 02h — сейчас «всплыть» не могу, но «всплыву» при первой возможности
  • 03h — уже «всплыл»
  • 04h — «всплыть» невозможно
 AL = 0FFh — программа «всплыла», отработала и завершилась
 ВХ — код завершения

INT 2Dh AL = 04h: Функция AMIS — получить список перехваченных прерываний
Ввод:АН = идентификатор программы
 AL = 04h
Вывод:AL = 04h
 DX:BX = адрес списка прерываний, состоящего из 3-байтных структур:
  • байт 1: номер прерывания (2Dh должен быть последним)
  • байты 2,3: смещение относительно сегмента, возвращенного в DX обработчика прерывания (по этому смещению должен находиться стандартный заголовок ISP)
INT 2Dh AL = 05h: Функция AMIS — получить список перехваченных клавиш
Ввод:АН = идентификатор программы
 AL = 05h
Вывод:AL = 0FFh — функция поддерживается
 DX:BX = адрес списка клавиш:
 +00h: 1 байт: тип проверки клавиши:
  • бит 0: проверка до обработчика INT 9
  • бит 1: проверка после обработчика INT 9
  • бит 2: проверка до обработчика INT 15h/AH = 4Fh
  • бит 3: проверка после обработчика INT 15h/AH = 4Fh
  • бит 4: проверка при вызове INT 16h/AH = 0, 1, 2
  • бит 5: проверка при вызове INT 16h/AH = 10h, 11h, 12h
  • бит 6: проверка при вызове INT 16h/AH = 20h, 21h, 22h
  • бит 7: 0
.+01h: 1 байт: количество перехваченных клавиш
.+02h: массив структур по 6 байт:
  • байт 1: скан-код клавиши
    • старший бит — отпускание клавиши
    • 00/80h — если срабатывание только по состоянию Shift-Ctrl-Alt и т.д.
  • байты 2, 3: необходимое состояние клавиатуры (формат тот же, что и в слове состояния клавиатуры, только бит 7 соответствует нажатию любой клавиши Shift)
  • байты 4, 5: запрещенное состояние клавиатуры (формат тот же)
  • байт 6: способ обработки клавиши
    • бит 0: клавиша перехватывается после обработчиков
    • бит 1: клавиша перехватывается до обработчиков
    • бит 2: другие обработчики не должны «проглатывать» клавишу
    • бит 3: клавиша не сработает, если, пока она была нажата, нажимали или отпускали другие клавиши
    • бит 4: клавиша преобразовывается в другую
    • бит 5: клавиша иногда «проглатывается», а иногда передается дальше
    • биты 6, 7: 0
Теперь можно написать резидентную программу, которая не загрузится дважды в память. В этой программе установим дополнительный обработчик на аппаратное прерывание от клавиатуры IRQ1 (INT 9), который будет отслеживать комбинацию клавиш Alt-А; после их нажатия программа перейдет в активное состояние, выведет на экран свое окно и среагирует уже на большее количество клавиш. Такие программы, активизирующиеся по нажатию какой-либо клавиши, часто называют «всплывающими» программами, но наша программа на самом деле будет только казаться «всплывающей». Настоящая «всплывающая» программа после активизации в обработчике INT 9h не возвращает управление, пока пользователь не закончит с ней работать. В нашем случае управление возвратится после каждого нажатия клавиши, хотя сами клавиши будут поглощаться программой, так что можно ей пользоваться одновременно с работающими программами, причем на скорости их работы активный ascii.com никак не скажется.

Так же как и с предыдущим примером, программы, не использующие средства DOS/BIOS для работы с клавиатурой, например файловый менеджер FAR, будут получать все нажатые клавиши параллельно с нашей программой, что приведет к нежелательным эффектам на экране. Кроме того, в этом упрощенном примере отсутствуют некоторые необходимые проверки (например, текущего видеорежима) и функции (например, выгрузка программы из памяти), но тем не менее это — реально используемая программа, с помощью которой легко посмотреть, какой символ соответствует какому ASCII-коду, и ввести любой символ, которого нет на клавиатуре, в частности псевдографику.

Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
; ascii.asm
; Резидентная программа для просмотра и ввода ASCII-символов
; HCI:
;      Alt-A - активация программы
;      Клавиши управления курсором - выбор символа
;      Enter - выход из программы с вводом символа
;      Esc - выход из программы без ввода символа
; API:
;      Программа занимает первую свободную функцию прерывания 2Dh
;      в соответствии со спецификацией AMIS 3.6
;      Поддерживаются функции AMIS 00h, 02h, 03h, 04h и 05h
;      Обработчики прерываний построены в соответствии с IBM ISP
 
; адрес верхнего левого угла окна (23-я позиция в третьей строке)
START_POSITION     equ    (80*2+23)*2
 
        .model     tiny
        .code
        .186                            ; для сдвигов и команд pusha/popa
        org        2Ch
envseg             dw    ?              ; сегментный адрес окружения DOS
 
        org        100h                 ; начало СОМ-программы
start:
        jmp        initialize           ; переход на инициализирующую часть
hw_reset9:
        retf                            ; ISP: минимальный hw_reset
 
; Обработчик прерывания 09h (IRQ1)
int09h_handler     proc    far
        jmp        short actual_int09h_handler ; ISP: пропустить блок
old_int09h         dd    ?                     ; ISP: старый обработчик
                   dw    424Bh                 ; ISP: сигнатура
                   db    00h                   ; ISP: вторичный обработчик
        jmp        short hw_reset9             ; ISP: ближний jmp на hw_reset
                   db    7 dup (0)             ; ISP: зарезервировано
actual_iht09h_handler:                         ; начало обработчика INT 09h
 
; Сначала вызовем предыдущий обработчик, чтобы дать BIOS возможность
; обработать прерывание и, если это было нажатие клавиши, поместить код
; в клавиатурный буфер, так как мы пока не умеем работать с портами клавиатуры
; и контроллера прерываний
        pushf
        call       dword ptr cs:old_int09h
 
; По этому адресу обработчик INT 2Dh запишет код команды IRET
; для дезактивации программы
disable_point      label  byte
        pusha                           ; это аппаратное прерывание - надо
        push       ds                   ; сохранить все регистры
        push       es
        cld                             ; флаг для команд строковой обработки
        push       0B800h
        pop        es                   ; ES = сегментный адрес видеопамяти
        push       0040h
        pop        ds                   ; DS = сегментный адрес области данных BIOS
        mov        di,word ptr ds:001Ah ; адрес головы буфера клавиатуры
        cmp        di,word ptr ds:001Ch ; если он равен адресу хвоста,
        je         exit_09h_handler     ; буфер пуст, и нам делать нечего
                                        ; (например, если прерывание пришло по
                                        ; отпусканию клавиши),
        mov        ax,word ptr [di]     ; иначе: считать символ из головы
                                        ; буфера
        cmp        byte ptr cs:we_are_active,0   ; если программа уже
        jne        already_active                ; активирована - перейти
                                                 ; к обработке стрелок и т.п.
        cmp        ah,1Eh               ; если прочитанная клавиша не А
        jne        exit_09h_handler     ; (скан-код 1Eh) - выйти,
        mov        al,byte ptr ds:0017h ; иначе: считать байт
                                        ; состояния клавиатуры,
        test       al,08h               ; если не нажата любая Alt,
        jz         exit_09h_handler     ; выйти,
        mov        word ptr ds:001Ch,di ; иначе: установить адреса
                                        ; головы и хвоста буфера одинаковыми,
                                        ; пометив его тем самым как пустой
        call       save_screen          ; сохранить область экрана, которую
                                        ; накроет всплывающее окно
        push       cs
        pop        ds                   ; DS = наш сегментный адрес
        call       display_all          ; вывести на экран окно программы
        mov        byte ptr we_are_active,1       ; установить флаг
        jmp        short exit_09h_handler         ; и выйти из обработчика
 
; Сюда передается управление, если программа уже активирована.
; При этом ES = B800h, DS = 0040h, DI = адрес головы буфера клавиатуры,
; АХ = символ из головы буфера
already_active:
        mov        word ptr ds:001Ch,di ; установить адреса
                                        ; головы и хвоста буфера одинаковыми,
                                        ; пометив его тем самым как пустой
        push       cs
        pop        ds                   ; DS = наш сегментный адрес
        mov        al,ah                ; команды cmp al,? короче команд cmp ah,?
        mov        bh,byte ptr current_char       ; номер выделенного в
                                                  ; данный момент ASCII-символа,
        cmp        al,48h               ; если нажата стрелка вверх (скан-код 48h),
        jne        not_up
        sub        bh,16                ; уменьшить номер символа на 16,
not_up:
        cmp        al,50h               ; если нажата стрелка вниз (скан-код 50h),
        jne        not_down
        add        bh,16                ; увеличить номер символа на 16,
not_down:
        cmp        al,4Bh               ; если нажата стрелка влево,
        jne        not_left
        dec        bh                   ; уменьшить номер символа на 1,
not_left:
        cmp        al,4Dh               ; если нажата стрелка вправо,
        jne        not_right
        inc        bh                   ; увеличить номер символа на 1,
not_right:
        cmp        al,1Ch               ; если нажата Enter (скан-код 1Ch),
        je         enter_pressed        ; перейти к его обработчику
        dec        al                   ; Если не нажата клавиша Esc (скан-код 1),
        jnz        exit_with_display    ; выйти из обработчика, оставив
                                        ; окно нашей программы на экране,
exit_after_enter:                       ; иначе:
        call       restore_screen       ; убрать наше окно с экрана,
        mov        byte ptr we_are_active,0       ; обнулить флаг активности,
        jmp        short exit_09h_handler         ; выйти из обработчика
 
exit_with_display:            ; выход с сохранением окна (после нажатия стрелок)
        mov        byte ptr current_char,bh       ; записать новое значение
                                                  ; текущего символа
        call       display_all          ; перерисовать окно
 
exit_09h_handler:                       ; выход из обработчика INT 09h
        pop        es
        pop        ds                   ; восстановить регистры
        рора
        iret                            ; и вернуться в прерванную программу
we_are_active      db    0              ; флаг активности: равен 1, если
                                        ; программа активна
current_char       db    37h            ; номер ASCII-символа, выделенного
                                        ; в данный момент
 
; сюда передается управление, если в активном состоянии была нажата Enter
enter_pressed:
        mov        ah,05h               ; Функция 05h
        mov        ch,0                 ; CH = 0
        mov        cl,byte ptr current_char  ; CL = ASCII-код
        int        16h                       ; поместить символ в буфер клавиатуры
        jmp        short exit_after_enter    ; выйти из обработчика, стерев окно
 
; процедура save_screen
; сохраняет в буфере screen_buffer содержимое области экрана, которую закроет
; наше окно
 
save_screen        proc    near
        mov        si,START_POSITION
        push       0B800h               ; DS:SI - начало этой области в видеопамяти
        pop        ds
        push       es
        push       cs
        pop        es
        mov        di,offset screen_buffer ; ES:DI - начало буфера в программе
        mov        dx,18                ; DX = счетчик строк
save_screen_loop:
        mov        cx,33                ; CX = счетчик символов в строке
        rep        movsw                ; скопировать строку с экрана в буфер
        add        si,(80-33)*2         ; увеличить SI до начала следующей строки
        dec        dx                   ; уменьшить счетчик строк,
        jnz        save_screen_loop     ; если он не ноль - продолжить цикл
        pop        es
        ret
save_screen        endp
 
; процедура restore_screen
; восстанавливает содержимое области экрана, которую закрывало наше
; всплывающее окно данными из буфера screen_buffer
 
restore_screen     proc    near
        mov        di,START_POSITION    ; ES:DI - начало области в видеопамяти
        mov        si,offset screen_buffer      ; DS:SI - начало буфера
        mov        dx,18                        ; счетчик строк
restore_screen_loop:
        mov        cx, 33               ; счетчик символов в строке
        rep        movsw                ; скопировать строку
        add        di,(80-33)*2         ; увеличить DI до начала следующей строки
        dec        dx                   ; уменьшить счетчик строк,
        jnz        restore_screen_loop  ; если он не ноль - продолжить
        ret
restore_screen     endp
 
; процедура display_all
; выводит на экран текущее состояние всплывающего окна нашей программы
display_all        proc    near
 
; шаг 1: вписать значение текущего выделенного байта в нижнюю строку окна
        mov        al,byte ptr current_char     ; AL = выбранный байт
        push       ax
        shr        al,4                         ; старшие четыре байта
        cmp        al,10                        ; три команды,
        sbb        al,69h                       ; преобразующие цифру в AL
        das                                     ; в ее ASCII-код (0 - 9, А - F)
        mov        byte ptr hex_byte1,al        ; записать символ на его
                                                ; место в нижней строке
        pop        ax
        and        al,0Fh                       ; младшие четыре бита
        cmp        al,10                        ; то же преобразование
        sbb        al,69h
        das
        mov        byte ptr hex_byte2,al        ; записать младшую цифру
 
; шаг 2: вывод на экран окна. Было бы проще хранить его как массив и выводить
; командой movsw, как и буфер в процедуре restore_screen, но такой массив займет
; еще 1190 байт в резидентной части. Код этой части процедуры display_all - всего
; 69 байт.
; шаг 2.1: вывод первой строки
        mov        ah,1Fh                       ; атрибут белый на синем
        mov        di,START_POSITION            ; ES:DI - адрес в видеопамяти
        mov        si,offset display_line1      ; DS:SI - адрес строки
        mov        cx,33                        ; счетчик символов в строке
display_loop1:
        mov        al,byte ptr [si]             ; прочитать символ в AL
        stosw                                   ; и вывести его с атрибутом из АН
        inc        si                           ; увеличить адрес символа в строке
        loop       display_loop1
 
; шаг 2.2: вывод собственно таблицы
        mov        dx,16                        ; счетчик строк
        mov        аl,-1                        ; выводимый символ
display_loop4:                                  ; цикл по строкам
        add        di,(80-33)*2                 ; увеличить DI до начала
        push       ax                           ; следующей строки
        mov        al,0B3h
        stosw                                   ; вывести первый символ (0B3h)
        pop        ax
        mov        cx,16                        ; счетчик символов в строке
display_loop3:                                  ; цикл по символам в строке
        inc        al                           ; следующий ASCII-символ
        stosw                                   ; вывести его на экран
        push       ax
        mov        al,20h                       ; вывести пробел
        stosw
        pop        ax
        loop       display_loop3                ; и так 16 раз
        push       ax
        sub        di,2                         ; вернуться назад на 1 символ
        mov        al,0B3h                      ; и вывести 0B3h на месте
        stosw                                   ; последнего пробела
        pop        ax
        dec        dx                           ; уменьшить счетчик строк
        jnz        display_loop4
 
; шаг 2.3: вывод последней строки
        add        di,(80-33)*2         ; увеличить DI до начала следующей строки
        mov        сх,33                ; счетчик символов в строке
        mov        si,offset display_line2 ; DS:SI - адрес строки
display_loop2:
        mov        al,byte ptr [si]     ; прочитать символ в AL
        stosw                           ; вывести его с атрибутом на экран
        inc        si                   ; увеличить адрес символа в строке
        loop       display_loop2
 
; шаг 3: подсветка (изменение атрибута) у текущего выделенного символа
        mov        al,byte ptr current_char     ; AL = текущий символ
        mov        ah,0
        mov        di,ax
        and        di,0Fh               ; DI = остаток от деления на 16
                                        ; (номер в строке)
        shl        di,2                 ; умножить его на 2, так как на экране
                                        ; используется слово на символ,
                                        ; и еще раз на 2, так как
                                        ; между символами - пробелы
        shr        ах,4                 ; АХ = частное от деления на 16
                                        ; (номер строки)
        imul       ax,ax,80*2           ; умножить его на длину строки на экране,
        add        di,ax                ; сложить их,
        add        di,START_POSITION+2+80*2+1   ; добавить адрес начала окна + 2,
                                        ; чтобы пропустить первый столбец, + 80*2,
                                        ; чтобы пропустить первую строку, + 1, 
                                        ; чтобы получить адрес атрибута,
                                        ; а не символа
        mov        al,071h              ; атрибут - синий на сером
        stosb                           ; вывод на экран
        ret
display_all        endp
 
int09h_handler     endp                 ; конец обработчика INT 09h
 
; буфер для хранения содержимого части экрана, которая накрывается нашим окном
screen_buffer      db    1190 dup(?)
 
; первая строка окна
display_line1      db    0DAh,11 dup (0C4h),'* ASCII *',11 dup (0C4h),0BFh
 
; последняя строка окна
display_line2      db    0C0h,11 dup (0C4h),'* Hex '
hex_byte1          db    ?              ; старшая цифра текущего байта
hex_byte2          db    ?              ; младшая цифра текущего байта
                   db    ' *',10 dup (0C4h),0D9h
 
hw_reset2D:        retf                 ; ISP: минимальный hw_reset
 
; обработчик прерывания INT 2Dh
; поддерживает функции AMIS 3.6 00h, 02h, 03h, 04h и 05h
int2Dh_handler     proc    far
        jmp        short actual_int2Dh_handler  ; ISP: пропустить блок
old_int2Dh         dd    ?                      ; ISP: старый обработчик
                   dw    424Bh                  ; ISP: сигнатура
                   db    00h                    ; ISP: программное прерывание
        jmp        short hw_reset2D             ; ISP: ближний jmp на hw_reset
                   db    7 dup (0)              ; ISP: зарезервировано
actual_int2Dh_handler:                  ; начало собственно обработчика INT 2Dh
                   db    80h,0FCh       ; начало команды CMP АН, число
mux_id             db                   ; идентификатор программы
        je         its_us               ; если вызывают с чужим АН - это не нас
        jmp        dword ptr cs:old_int2Dh
its_us:
        cmp        al,06                ; функции 06h и выше
        jae        int2D_no             ; не поддерживаются
        cbw                             ; AX = номер функции
        mov        di,ax                ; DI = номер функции
        shl        di,1                 ; умножить его на 2, так как jumptable -
                                        ; таблица слов
        jmp        word ptr cs:jumptable[di]    ; косвенный переход на обработчики
                                                ; функций
jumptable          dw    offset int2D_00,offset int2D_no
                   dw    offset int2D_02,offset int2D_03
                   dw    offset int2D_04,offset int2D_05
 
int2D_00:                               ; проверка наличия
        mov        al,0FFh              ; этот номер занят
        mov        сх,0100h             ; номер версии 1.0
        push       cs
        pop        dx                   ; DX:DI - адрес AMIS-сигнатуры
        mov        di,offset amis_sign
        iret
int2D_no:                               ; неподдерживаемая функция
        mov        al,00h               ; функция не поддерживается
        iret
int2D_02:                               ; выгрузка программы
        mov        byte ptr cs:disable_point,0CFh ; записать код команды IRET
                                        ; по адресу disable_point
                                        ; в обработчик INT 09h
        mov        al,04h               ; программа дезактивирована, но сама
                                        ; выгрузиться не может
        mov        bx,cs                ; BX - сегментный адрес программы
        iret
int2D_03:                      ; запрос на активизацию для "всплывающих" программ
        cmp        byte ptr we_are_active,0     ; если окно уже на экране,
        je         already_popup
        call       save_screen                  ; сохранить область экрана,
        push       cs
        pop        ds
        call       display_all                  ; вывести окно
        mov        byte ptr we_are_active,1     ; и поднять флаг
already_popup:
        mov        al,03h               ; код 03: программа активизирована
        iret
int2D_04:                               ; получить список перехваченных прерываний
        mov        dx,cs                ; список в DX:BX
        mov        bx,offset amis_hooklist
        iret
int2D_05:                               ; получить список "горячих" клавиш
        mov        al,0FFh              ; функция поддерживается
        mov        dx,cs                ; список в DX:BX
        mov        bx,offset amis_hotkeys
        iret
int2Dh_handler     endp
 
; AMIS: сигнатура для резидентных программ
amis_sign          db    "Cubbi..."     ; 8 байт - имя автора
                   db    "ASCII..."     ; 8 байт - имя программы
                   db    "ASCII display and input utility",0 ; ASCIZ-комментарий
                                        ; не более 64 байт
 
; AMIS: список перехваченных прерываний
amis_hooklist      db    09h
                   dw    offset int09h_handler
                   db    2Dh
                   dw    offset int2Dh_handler
 
; AMIS: список "горячих" клавиш
amis_hotkeys       db    01h            ; клавиши проверяются после стандартного
                                        ; обработчика INT 09h
                   db    1              ; число клавиш
                   db    1Eh            ; скан-код клавиши (А)
                   dw    08h            ; требуемые флаги (любая Alt)
                   dw    0              ; запрещенные флаги
                   db    1              ; клавиша глотается
 
; конец резидентной части
; начало процедуры инициализации
 
initialize         proc    near
        mov        ah,9
        mov        dx,offset usage      ; вывести информацию о программе
        int        21h
 
; проверить, не установлена ли уже наша программа
        mov        ah,-1                ; сканирование номеров от FFh до 00h
more_mux:
        mov        al,00h               ; Функция 00h - проверка наличия программы
        int        2Dh                  ; мультиплексорное прерывание AMIS,
        cmp        al,00h               ; если идентификатор свободен,
        jne        not_free
        mov        byte ptr mux_id,ah   ; записать его номер прямо в код
                                        ; обработчика int 2Dh,
        jmp        short next_mux
not_free:
        mov        es,dx                ; иначе - ES:DI = адрес их сигнатуры
        mov        si,offset amis_sign  ; DS:SI = адрес нашей сигнатуры
        mov        cx,16                ; сравнить первые 16 байт,
        repe       cmpsb
        jcxz       already_loaded       ; если они не совпадают,
next_mux:
        dec        ah                   ; перейти к следующему идентификатору,
        jnz        more_mux             ; пока это не 0
        ; (на самом деле в этом примере сканирование происходит от FFh до 01h,
        ; так как 0 мы используем в качестве признака отсутствия свободного
        ; номера в следующей строке)
free_mux_found:
        cmp        byte ptr mux_id,0      ; если мы ничего не записали,
        je         no_more_mux            ; идентификаторы кончились
        mov        ax,352Dh               ; АН = 35h, AL = номер прерывания
        int        21h                    ; получить адрес обработчика INT 2Dh
        mov        word ptr old_int2Dh,bx ;и поместить его в old_int2Dh
        mov        word ptr old_int2Dh+2,es
        mov        ax,3509h               ; AH = 35h, AL = номер прерывания
        int        21h                    ; получить адрес обработчика INT 09h
        mov        word ptr old_int09h,bx ; и поместить его в old_int09h
        mov        word ptr old_int09h+2,es
        mov        ax,252Dh               ; AH = 25h, AL = номер прерывания
        mov        dx,offset int2Dh_handler ; DS:DX - адрес нашего
        int        21h                      ; обработчика
        mov        ax,2509h                 ; AH = 25h, AL = номер прерывания
        mov        dx,offset int09h_handler ; DS:DX - адрес нашего
        int        21h                      ; обработчика
        mov        ah,49h                   ; AH = 49h
        mov        es,word ptr envseg       ; ES = сегментный адрес среды DOS
        int        21h                      ; освободить память
        mov        ah,9
        mov        dx,offset installed_msg  ; вывод строки об успешной
        int        21h                      ; инсталляции
        mov        dx,offset initialize     ; DX - адрес первого байта за
                                            ; концом резидентной части
        int        27h                      ; завершить выполнение, оставшись
                                            ; резидентом
; сюда передается управление, если наша программа обнаружена в памяти
already_loaded:
        mov        ah,9                     ; АН = 09h
        mov        dx,offset already_msg    ; вывести сообщение об ошибке
        int        21h
        ret                                 ; и завершиться нормально
 
; сюда передается управление, если все 255 функций мультиплексора заняты
; резидентными программами
no_more_mux:
        mov         ah,9
        mov         dx, offset no_more_mux_msg
        int         21h
        ret
 
; текст, который выдает программа при запуске:
usage              db    "ASCII display and input program"
                   db    " v1.0",0Dh,0Ah
                   db    "Alt-A   - активация",0Dh,0Ah
                   db    "Стрелки - выбор символа",0Dh,0Ah
                   db    "Enter   - ввод символа",0Dh,0Ah
                   db    "Escape  - выход",0Dh,0Ah
                   db    "$"
; текст, который выдает программа, если она уже загружена:
already_msg        db    "Ошибка: программа уже загружена",0Dh,0Ah,'$'
; текст, который выдает программа, если все функции мультиплексора заняты:
no_more_mux_msg    db    "Ошибка: Слишком много резидентных программ"
                   db    0Dh,0Ah,'$'
; текст,  который выдает программа при успешной установке:
installed_msg      db    "Программа загружена в память",0Dh,0Ah,'$'
 
initialize         endp
        end        start
Резидентная часть этой программы занимает в памяти целых 2064 байта (из которых на собственно коды команд приходится только 436). Это вполне терпимо, учитывая, что обычно программа типа ascii.com запускается перед простыми текстовыми редакторами для DOS (edit, multiedit, встроенные редакторы оболочек типа Norton Commander и т.д.), которые не требуют для своей работы полностью свободной памяти. В других случаях, как, например, при создании программы, копирующей изображение с экрана в файл, может оказаться, что на счету каждый байт; такие программы часто применяют для сохранения изображений из компьютерных игр, которые задействуют все ресурсы компьютера по максимуму. Здесь резидентным программам приходится размещать данные, а иногда и часть кода, в старших областях памяти, пользуясь спецификациями HMA, UMB, EMS или XMS. В следующей главе рассмотрен простой пример именно такой программы.

3
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
20.01.2014, 09:55  [ТС] #47
Выгрузка резидентной программы из памяти

Чтобы выгрузить резидентную программу из памяти, необходимо сделать три вещи: закрыть открытые программой файлы и устройства, восстановить все перехваченные векторы прерываний, и наконец, освободить всю занятую программой память. Трудность может вызвать второй шаг, так как после нашего резидента могли быть загружены другие программы, перехватившие те же прерывания. Если в такой ситуации восстановить вектор прерывания в значение, которое он имел до загрузки нашего резидента, программы, загруженные позже, не будут получать управление. Более того, они не будут получать управление только по тем прерываниям, которые у них совпали с прерываниями, перехваченными нашей программой, в то время как другие векторы прерываний будут все еще указывать на их обработчики, что почти наверняка приведет к ошибкам. Поэтому, если хоть один вектор прерывания не указывает на наш обработчик, выгружать резидентную программу нельзя. Это всегда было главным вопросом, и спецификации AMIS и IBM ISP (см. предыдущую главу) являются возможным решением этой проблемы. Если вектор прерывания не указывает на нас, имеет смысл проверить, не указывает ли он на ISP-блок (первые два байта должны быть EBh 10h, а байты 6 и 7 — «K» и «B»), и, если это так, взять в качестве вектора значение из этого блока и т.д. Кроме того, программы могут изменять порядок, в котором обработчики одного и того же прерывания вызывают друг друга.

Последний шаг в выгрузке программы — освобождение памяти — можно выполнить вручную, вызывая функцию DOS 49h на каждый блок памяти, который программа выделяла через функцию 48h, на блок с окружением DOS, если он не освобождался при загрузке, и наконец, на саму программу. Однако есть способ заставить DOS сделать все это (а также закрыть открытые файлы и вернуть код возврата) автоматически, вызвав функцию 4Ch, объявив резидент текущим процессом. Посмотрим, как это делается на примере резидентной программы, занимающей много места в памяти. Кроме того, этот пример реализует все приемы, использующиеся для вызова функций DOS из обработчиков аппаратных прерываний, о которых рассказано в главе 5.8.3.

Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
; scrgrb.asm
; Резидентная программа, сохраняющая изображение с экрана в файл.
; Поддерживается только видеорежим 13h (320x200x256) и только один файл.
 
; HCI:
; Нажатие Alt-G создает файл scrgrb.bmp в текущем каталоге с изображением,
; находившимся на экране в момент нажатия клавиши.
; Запуск с командной строкой /u выгружает программу из памяти
 
; API:
; Программа занимает первую свободную функцию прерывания 2Dh (кроме нуля)
; в соответствии со спецификацией AMIS 3.6
; Поддерживаемые подфункции AMIS: 00h, 02h, 03h, 04h, 05h
; Все обработчики прерываний построены в соответствии с IBM ISP
 
; Резидентная часть занимает в памяти 1056 байт, если присутствует EMS,
; и 66 160 байт, если EMS не обнаружен
 
        .model     tiny
        .code
        .186                            ; для сдвигов и команд pusha/popa
        org        2Ch
envseg             dw    ?              ; сегментный адрес окружения
 
        org        80h
cmd_len            db    ?              ; длина командной строки
cmd_line           db    ?              ; командная строка
 
        org        100h                 ; COM-программа
start:
        jmp        initialize           ; переход на инициализирующую часть
 
; Обработчик прерывания 09h (IRQ1)
 
int09h_handler     proc    far
        jmp        short actual_int09h_handler  ; пропустить ISP
old_int09h         dd    ?
                   dw    424Bh
                   db    00h
        jmp        short hw_reset
                   db    7 dup (0)
actual_int09h_handler:                  ; начало собственно обработчика INT 09h
        pushf
        call       dword ptr cs:old_int09h ; сначала вызвать старый
                                        ; обработчик, чтобы он завершил аппаратное
                                        ; прерывание и передал код в буфер
        pusha                           ; это аппаратное прерывание - надо
        push       ds                   ; сохранить все регистры
        push       es
        push       0040h
        pop        ds                   ; DS = сегментный адрес области данных BIOS
        mov        di,word ptr ds:001Ah ; адрес головы буфера
                                        ; клавиатуры,
        cmp        di,word ptr ds:001Ch ; если он равен адресу
                                        ; хвоста,
        je         exit_09h_handler     ; буфер пуст, и нам делать нечего,
 
        mov        ax,word ptr [di]     ; иначе: считать символ,
        cmp        ah,22h               ; если это не G (скан-код 22h),
        jne        exit_09h_handler     ; выйти
 
        mov        al,byte ptr ds:0017h ; байт состояния клавиатуры,
        test       al,08h               ; если Alt не нажата,
        jz         exit_09h_handler     ; выйти,
 
        mov        word ptr ds:001Ch,di ; иначе: установить адреса головы
                                        ; и хвоста буфера равными, то есть
                                        ; опустошить его
        call       do_grab              ; подготовить BMP-файл с изображением
        mov        byte ptr cs:io_needed, 1 ; установить флаг
                                            ; требующейся записи на диск
        cli
        call       safe_check           ; проверить, можно ли вызвать DOS,
        jc         exit_09h_handler
        sti
        call       do_io                ; если да - записать файл на диск
 
exit_09h_handler:
        pop        es
        pop        ds                   ; восстановить регистры
        рора
        iret                            ; и вернуться в прерванную программу
int09h_handler     endp
 
hw_reset:          retf
 
; Обработчик INT 08h (IRQ0)
 
int08h_handler     proc       far
        jmp        short actual_int08h_handler ; пропустить ISP
old_int08h         dd    ?
                   dw    424Bh
                   db    00h
        jmp        short hw_reset
                   db    7 dup (0)
actual_int08h_handler:                  ; собственно обработчик
        pushf
        call       dword ptr cs:old_int08h ; сначала вызвать стандартный
                                        ; обработчик, чтобы он завершил
                                        ; аппаратное прерывание (пока оно
                                        ; не завершено, запись на диске невозможна)
        pusha
        push       ds
        cli                        ; между любой проверкой глобальной переменной
                                   ; и принятием решения по ее значению -
                                   ; не повторно входимая область, прерывания
                                   ; должны быть запрещены
        cmp        byte ptr cs:io_needed,0 ; проверить,
        je         no_io_needed            ; нужно ли писать на диск
        call       safe_check              ; проверить,
        jc         no_io_needed            ; можно ли писать на диск
        sti                             ; разрешить прерывания на время записи
        call       do_io                ; запись на диск
no_io_needed:
        pop        ds
        рора
        iret
int08h_handler     endp
 
; Обработчик INT 13h
; поддерживает флаг занятости INT 13h, который тоже надо проверять перед
; записью на диск
 
int13h_handler     proc    far
        jmp        short actual_int13h_handler  ; пропустить ISP
old_int13h         dd    ?
                   dw    424Bh
                   db    00h
        jmp        short hw_reset
                   db    7 dup (0)
actual_int13h_handler:                  ; собственно обработчик
        pushf
        inc        byte ptr cs:bios_busy ; увеличить счетчик занятости INT 13h
        cli
        call       dword ptr cs:old_int13h
        pushf
        dec        byte ptr cs:bios_busy ; уменьшить счетчик
        popf
        ret        2                ; имитация команды IRET, не восстанавливающая
        ; флаги из стека, так как обработчик INT 13h возвращает некоторые
        ; результаты в регистре флагов, а не в его копии, хранящейся
        ; в стеке. Он тоже завершается командой ret 2
int13h_handler     endp
 
; Обработчик INT 28h
; вызывается DOS, когда она ожидает ввода с клавиатуры и функциями DOS можно
; пользоваться
 
int28h_handler     proc    far
        jmp        short actual_int28h_handler  ; пропустить ISP
old_int28h         dd    ?
                   dw    424Вh
                   db    00h
        jmp        short hw_reset
                   db    7 dup (0)
actual_int28h_handler:
        pushf
        push       di
        push       ds
        push       cs
        pop        ds
        cli
        cmp        byte ptr io_needed,0 ; проверить,
        je         no_io_needed2        ; нужно ли писать на диск
        lds        di,dword ptr in_dos_addr
        cmp        byte ptr [di+1],1    ; проверить,
        ja         no_io_needed2        ; можно ли писать на диск (флаг
                                        ; занятости DOS не должен быть больше 1)
        sti
        call       do_io                ; запись на диск
no_io_needed2:
        pop        ds
        pop        di
        popf
        jmp        dword ptr cs:old_int28h ; переход на старый
                                           ; обработчик INT 28h
int28h_handler     endp
 
; Процедура do_grab
; помещает в буфер палитру и содержимое видеопамяти, формируя BMP-файл.
; Считает, что текущий видеорежим - 13h
 
do_grab            proc    near
        push       cs
        pop        ds
 
        call       ems_init             ; отобразить наш буфер в окно EMS
 
        mov        dx,word ptr cs:buffer_seg
        mov        es,dx                ; поместить сегмент с буфером в ES и DS
        mov        ds,dx                ; для следующих шагов процедуры
        mov        ax,1017h             ; Функция 1017h - чтение палитры VGA
        mov        bx,0                 ; начиная с регистра палитры 0,
        mov        сх,256               ; все 256 регистров
        mov        dx,BMP_header_length ; начало палитры в BMP
        int        10h                  ; видеосервис BIOS
 
; перевести палитру из формата, в котором ее показывает функция 1017h
; (три байта на цвет, в каждом байте 6 значимых бит),
; в формат, используемый в BMP-файлах
; (4 байта на цвет, в каждом байте 8 значимых бит)
        std                             ; движение от конца к началу
        mov        si,BMP_header_length+256*3-1   ; SI- конец 3-байтной палитры
        mov        di,BMP_header_length+256*4-1   ; DI - конец 4-байтной палитры
        mov        сх,256                         ; СХ - число цветов
adj_pal:
        mov        al,0
        stosb                           ; записать четвертый байт (0)
        lodsb                           ; прочитать третий байт
        shl        al,2                 ; масштабировать до 8 бит
        push       ax
        lodsb                           ; прочитать второй байт
        shl        al,2                 ; масштабировать до 8 бит
        push       ax
        lodsb                           ; прочитать третий байт
        shl        al,2                 ; масштабировать до 8 бит
        stosb                           ; и записать эти три байта
        pop        ax                   ; в обратном порядке
        stosb
        pop        ax
        stosb
        loop       adj_pal
 
; Копирование видеопамяти в BMP.
; В формате BMP строки изображения записываются от последней к первой, так что
; первый байт соответствует нижнему левому пикселю
 
        cld                             ; движение от начала к концу (по строке)
        push       0A000h
        pop        ds
        mov        si,320*200           ; DS:SI - начало последней строки на экране
        mov        di,bfoffbits         ; ES:DI - начало данных в BMP
        mov        dx,200               ; счетчик строк
bmp_write_loop:
        mov        cx,320/2             ; счетчик символов в строке
        rep        movsw                ; копировать целыми словами, так быстрее
        sub        si,320*2             ; перевести SI на начало предыдущей строки
        dec        dx                   ; уменьшить счетчик строк,
        jnz        bmp_write_loop       ; если 0 - выйти из цикла
        call       ems_reset            ; восстановить состояние EMS
                                        ; до вызова do_grab
        ret
do_grab            endp
 
; Процедура do_io
; создает файл и записывает в него содержимое буфера
 
do_io              proc    near
        push       cs
        pop        ds
        mov        byte ptr io_needed,0 ; сбросить флаг требующейся
                                        ; записи на диск
        call       ems_init             ; отобразить в окно EMS наш буфер
        mov        ah,6Ch               ; Функция DOS 6Ch
        mov        bx,2                 ; доступ - на чтение/запись
        mov        cx,0                 ; атрибуты - обычный файл
        mov        dx,12h               ; заменять файл, если он существует,
                                        ; создавать, если нет
        mov        si,offset filespec   ; DS:SI - имя файла
        int        21h                  ; создать/открыть файл
        mov        bx,ax                ; идентификатор файла - в ВХ
 
        mov        ah,40h               ; Функция DOS 40h
        mov        cx,bfsize            ; размер BMP-файла
        mov        ds,word ptr buffer_seg
        mov        dx,0                 ; DS:DX - буфер для файла
        int        21h                  ; запись в файл или устройство
 
        mov        ah,68h               ; сбросить буфера на диск
        int        21h
 
        mov        ah,3Eh               ; закрыть файл
        int        21h
        call       ems_reset
        ret
do_io              endp
 
; Процедура ems_init,
; если буфер расположен в EMS, подготавливает его для чтения/записи
ems_init           proc    near
        cmp        dx,word ptr ems_handle   ; если не используется EMS
        cmp        dx,0                 ; (EMS-идентификаторы начинаются с 1),
        je         ems_init_exit        ; ничего не делать
 
        mov        ax,4700h             ; Функция EMS 47h
        int        67h                  ; сохранить EMS-контекст
 
        mov        ax,4100h             ; Функция EMS 41h
        int        67h                  ; определить адрес окна EMS
        mov        word ptr buffer_seg,bx   ; сохранить его
 
        mov        ax,4400h             ; Функция EMS 44h
        mov        bx,0                 ; начиная со страницы 0,
        int        67h                  ; отобразить страницы EMS в окно
        mov        ax,4401h
        inc        bx
        int        67h                  ; страница 1
        mov        ax,4402h
        inc        bx
        int        67h                  ; страница 2
        mov        ax,4403h
        inc        bx
        int        67h                  ; страница 3
ems_init_exit:
        ret
ems_init           endp
 
; Процедура ems_reset
; восстанавливает состояние EMS
 
ems_reset          proc    near
        mov        dx,word ptr cs:ems_handle
        cmp        dx,0
        je         ems_reset_exit
        mov        ax,4800h             ; Функция EMS 48h
        int        67h                  ; восстановить EMS-контекст
ems_reset_exit:
        ret
ems_reset          endp
 
; Процедура safe_check
; возвращает CF = 0, если в данный момент можно пользоваться функциями DOS,
; и CF = 1, если нельзя
 
safe_check         proc    near
        push       es
        push       cs
        pop        ds
 
        les        di,dword ptr in_dos_addr ; адрес флагов занятости DOS,
        cmp        word ptr es:[di],0   ; если один из них не 0,
        pop        es
        jne        safe_check_failed    ; пользоваться DOS нельзя,
 
        cmp        byte ptr bios_busy,0 ; если выполняется прерывание 13h,
        jne        safe_check_failed    ; тоже нельзя
 
        clc                             ; CF = 0
        ret
safe_check_failed:
        stc                             ; CF = 1
        ret
safe_check         endp
 
in_dos_addr        dd    ?              ; адрес флагов занятости DOS
io_needed          db    0              ; 1, если надо записать файл на диск
bios_busy          db    0              ; 1, если выполняется прерывание INT 13h
buffer_seg         dw    0              ; сегментный адрес буфера для файла
ems_handle         dw    0              ; идентификатор EMS
filespec           db    'scrgrb.bmp',0 ; имя файла
 
; Обработчик INT 2Dh
 
hw_reset2D:        retf
 
int2Dh_handler     proc    far
        jmp        short actual_int2Dh_handler  ; пропустить ISP
old_int2Dh         dd    ?
                   dw    424Bh
                   db    00h
        jmp        short hw_reset2D
                   db    7 dup (0)
actual_int2Dh_handler:                  ; собственно обработчик
                   db    80h,0FCh       ; начало команды CMP АН,число
mux_id             db    ?              ; идентификатор программы,
        je         its_us               ; если вызывают с чужим АН - это не нас
        jmp        dword ptr cs:old_int2Dh
its_us:
        cmp        al,06                ; функции AMIS 06h и выше
        jae        int2D_no             ; не поддерживаются
        cbw                             ; AX = номер функции
        mov        di,ax                ; DI = номер функции
        shl        di,1                 ; * 2, так как jumptable - таблица слов
        jmp        word ptr cs:jumptable[di]  ; переход на обработчик функции
jumptable          dw    offset int2D_00,offset int2D_no
                   dw    offset int2D_02,offset int2D_no
                   dw    offset int2D_04,offset int2D_05
 
int2D_00:                               ; проверка наличия
        mov        al,0FFh              ; этот номер занят
        mov        cx,0100h             ; номер версии программы 1.0
        push       cs
        pop        dx                   ; DX:DI - адрес AMIS-сигнатуры
        mov        di,offset amis_sign
        iret
int2D_no:                               ; неподдерживаемая функция
        mov        al,00h               ; функция не поддерживается
        iret
unload_failed:    ; сюда передается управление, если хоть один из векторов
                  ; прерываний был перехвачен кем-то после нас
        mov        al,01h               ; выгрузка программы не удалась
        iret
int2D_02:                               ; выгрузка программы из памяти
        cli                             ; критический участок
        push       0
        pop        ds      ; DS - сегментный адрес таблицы векторов прерываний
        mov        ax,cs                ; наш сегментный адрес
; проверить, все ли перехваченные прерывания по-прежнему указывают на нас,
; обычно достаточно проверить только сегментные адреса (DOS не загрузит другую
; программу с нашим сегментным адресом)
        cmp        ax,word ptr ds:[09h*4+2]
        jne        unload_failed
        cmp        ax,word ptr ds:[13h*4+2]
        jne        unload_failed
        cmp        ax,word ptr ds:[08h*4+2]
        jne        unload_failed
        cmp        ax,word ptr ds:[28h*4+2]
        jne        unload_failed
        cmp        ax,word ptr ds:[2Dh*4+2]
        jne        unload_failed
 
        push       bx                   ; адрес возврата - в стек
        push       dx
 
; восстановить старые обработчики прерываний
        mov        ax,2509h
        lds        dx,dword ptr cs:old_int09h
        int        21h
        mov        ax,2513h
        lds        dx,dword ptr cs:old_int13h
        int        21h
        mov        ax,2508h
        lds        dx,dword ptr cs:old_int08h
        int        21h
        mov        ax,2528h
        lds        dx,dword ptr cs:old_int28h
        int        21h
        mov        ax,252Dh
        lds        dx,dword ptr cs:old_int2Dh
        int        21h
        mov        dx,word ptr cs:ems_handle ; если используется EMS
        cmp        dx,0
        je         no_ems_to_unhook
        mov        ax,4500h             ; функция EMS 45h
        int        67h                  ; освободить выделенную память
        jmp        short ems_unhooked
no_ems_to_unhook:
ems_unhooked:
 
; собственно выгрузка резидента
        mov        ah,51h               ; Функция DOS 51h
        int        21h                  ; получить сегментный адрес PSP
                                        ; прерванного процесса (в данном случае
                                        ; PSP - копии нашей программы,
                                        ; запущенной с ключом /u)
        mov        word ptr cs:[16h],bx ; поместить его в поле
                                        ; "сегментный адрес предка" в нашем PSP
        pop        dx                   ; восстановить адрес возврата из стека
        pop        bx
        mov        word ptr cs:[0Ch],dx ; и поместить его в поле
        mov        word ptr cs:[0Ah],bx ; "адрес перехода при
                                        ; завершении программы" в нашем PSP
        pop        bx                   ; BX = наш сегментный адрес PSP
        mov        ah,50h               ; Функция DOS 50h
        int        21h                  ; установить текущий PSP
; теперь DOS считает наш резидент текущей программой, а scrgrb.com /u -
; вызвавшим его процессом, которому и передаст управление после вызова
; следующей функции
        mov        ax,4CFFh             ; Функция DOS 4Ch
        int        21h                  ; завершить программу
 
int2D_04:                               ; получить список перехваченных прерываний
        mov        dx,cs                ; список в DX:BX
        mov        bx,offset amis_hooklist
        iret
int2D_05:                               ; получить список "горячих" клавиш
        mov        al,0FFh              ; функция поддерживается
        mov        dx,cs                ; список в DX:BX
        mov        bx,offset amis_hotkeys
        iret
int2Dh_handler     endp
 
; AMIS: сигнатура для резидентной программы
amis_sign          db    "Cubbi..."     ; 8 байт
                   db    "ScrnGrab"     ; 8 байт
                   db    "Simple screen grabber using EMS",0
 
; AMIS: список перехваченных прерываний
amis_hooklist      db    09h
                   dw    offset int09h_handler
                   db    08h
                   dw    offset int08h_handler
                   db    28h
                   dw    offset int28h_handler
                   db    2Dh
                   dw    offset int2Dh_handler
; AMIS: список "горячих" клавиш
amis_hotkeys       db    1
                   db    1
                   db    22h            ; скан-код клавиши (G)
                   dw    08h            ; требуемые флаги клавиатуры
                   dw    0
                   db    1
 
; конец резидентной части
; начало процедуры инициализации
 
initialize         proc    near
        jmp        short initialize_entry_point
        ; пропустить различные варианты выхода без установки резидента,
        ; помещенные здесь потому, что на них передают управление
        ; команды условного перехода, имеющие короткий радиус действия
 
exit_with_message:
        mov        ah,9                 ; функция вывода строки на экран
        int        21h
        ret                             ; выход из программы
 
already_loaded:                         ; если программа уже загружена в память
        cmp        byte ptr unloading,1 ; если мы не были вызваны с /u
        je         do_unload
        mov        dx,offset already_msg
        jmp        short exit_with_message
 
no_more_mux:         ; если свободный идентификатор INT 2Dh не найден
        mov        dx,offset no_more_mux_msg
        jmp        short exit_with_message
 
cant_unload1:        ; если нельзя выгрузить программу
        mov        dx,offset cant_unload1_msg
        jmp        short exit_with_message
 
do_unload:           ; выгрузка резидента: при передаче управления сюда АН содержит
                     ; идентификатор программы - 1
        inc        ah
        mov        al,02h               ; AMIS-функция выгрузки резидента
        mov        dx,es                ; адрес возврата
        mov        bx,offset exit_point ; в DX:BX
        int        2Dh              ; вызов нашего резидента через мультиплексор
 
        push       cs               ; если управление пришло сюда -
                                    ; выгрузка не произошла
        pop        ds
        mov        dx,offset cant_unload2_msg
        jmp        short exit_with_message
 
exit_point:                         ; если управление пришло сюда -
        push       cs               ; выгрузка произошла
        pop        ds
        mov        dx,offset unloaded_msg
        push       0                    ; чтобы сработала команда RET для выхода
        jmp        short exit_with_message
 
initialize_entry_point:             ; сюда передается управление в самом начале
        cld
        cmp        byte ptr cmd_line[1],'/'
        jne        not_unload
        cmp        byte ptr cmd_line[2],'u' ; если нас вызвали с /u
        jne        not_unload
        mov        byte ptr unloading,1     ; выгрузить резидент
not_unload:
        mov        ah, 9
        mov        dx,offset usage      ; вывод строки с информацией о программе
        int        21h
        mov        ah,-1                ; сканирование от FFh до 01h
more_mux:
        mov        al,00h               ; функция AMIS 00h -
                                        ; проверка наличия резидента
        int        2Dh                  ; мультиплексорное прерывание
        cmp        al,00h               ; если идентификатор свободен,
        jne        not_free
        mov        byte ptr mux_id,ah   ; вписать его сразу в код обработчика,
        jmp        short next_mux
not_free:
        mov        es,dx                ; иначе - ES:DI = адрес AMIS-сигнатуры
                                        ; вызвавшей программы
        mov        si,offset amis_sign  ; DS:SI = адрес нашей сигнатуры
        mov        cx,16                ; сравнить первые 16 байт,
        repe       cmpsb
        jcxz       already_loaded       ; если они не совпадают,
next_mux:
        dec        ah                   ; перейти к следующему идентификатору,
        jnz        more_mux             ; если это 0
 
free_mux_found:
        cmp        byte ptr unloading, 1 ; и если нас вызвали для выгрузки,
        je         cant_unload1          ; а мы пришли сюда - программы нет в
                                         ; памяти,
        cmp        byte ptr mux_id,0     ; если при этом mux_id все еще 0,
        je         no_more_mux           ; идентификаторы кончились
 
; проверка наличия устройства ЕММХХХХ0
        mov        dx,offset ems_driver
        mov        ax,3D00h
        int        21h              ; открыть файл/устройство
        jc         no_emmx
        mov        bx,ax
        mov        ax,4400h
        int        21h              ; IOCTL: получить состояние файла/устройства
        jc         no_ems
        test       dx,80h           ; если старший бит DX = 0, ЕММХХХХ0 - файл
        jz         no_ems
; выделить память под буфер в EMS
        mov        ax,4100h             ; функция EMS 41h
        int        67h                  ; получить адрес окна EMS
        mov        bp,bx                ; сохранить его пока в ВР
        mov        ax,4300h             ; Функция EMS 43h
        mov        bx,4                 ; нам надо 4 * 16 Кб
        int        67h                  ; выделить EMS-память (идентификатор в DХ),
        cmp        ah,0                 ; если произошла ошибка (нехватка памяти?),
        jnz        ems_failed           ; не будем пользоваться EMS,
        mov        word ptr ems_handle,dx ; иначе: сохранить идентификатор
                                          ; для резидента
        mov        ax,4400h             ; Функция 44h - отобразить
        mov        bx,0                 ; EMS-страницы в окно
        int        67h                  ; страница 0
        mov        ax,4401h
        inc        bx
        int        67h                  ; страница 1
        mov        ax,4402h
        inc        bx
        int        67h                  ; страница 2
        mov        ax,4403h
        inc        bx
        int        67h                  ; страница 3
        mov        dx,offset ems_msg    ; вывести сообщение об установке в EMS
        jmp        short ems_used
ems_failed:
no_ems:                                 ; если EMS нет или он не работает,
        mov        ah,3Eh
        int        21h                  ; закрыть файл/устройство ЕММХХХХ0,
no_emmx:
; занять общую память
        mov        ah,9
        mov        dx,offset conv_msg   ; вывод сообщения об этом
        int        21h
        mov        sp,length_of_program+100h+200h   ; перенести стек
        mov        ah,4Ah               ; Функция DOS 4Ah
 
next_segment = length_of_program+100h+200h+0Fh
next_segment = next_segment/16          ; такая запись нужна только для
                                        ; WASM, остальным ассемблерам это
                                        ; можно было записать в одну строчку
        mov        bx,next_segment      ; уменьшить занятую память, оставив
                                        ; текущую длину нашей программы + 100h
                                        ; на PSP +200h на стек
        int        21h
 
        mov        ah,48h               ; Функция 48h - выделить память
bfsize_p = bfsize+0Fh
bfsize_p = bfsize_p/16
        mov        bx,bfsize_p          ; размер BMP-файла 320x200x256 в 16-байтных
        int        21h                  ; параграфах
 
ems_used:
        mov        word ptr buffer_seg,ax ; сохранить адрес буфера для резидента
 
; скопировать заголовок BMP-файла в начало буфера
        mov        cx,BMP_header_length
        mov        si,offset BMP_header
        mov        di,0
        mov        es,ax
        rep        movsb
 
; получить адреса флага занятости DOS и флага критической ошибки (считая, что
; версия DOS старше 3.0)
        mov        ah,34л               ; Функция 34h - получить флаг занятости
        int        21h
        dec        bx                   ; уменьшить адрес на 1, чтобы он указывал
                                        ; на флаг критической ошибки,
        mov        word ptr in_dos_addr,bx
        mov        word ptr in_dos_addr+2,es ; и сохранить его для резидента
 
; перехват прерываний
        mov        ax,352Dh                 ; АН = 35h, AL = номер прерывания
        int        21h                      ; получить адрес обработчика INT 2Dh
        mov        word ptr old_int2Dh,bx   ; и поместить его в old_int2Dh
        mov        word ptr old_int2Dh+2,es
        mov        ax,3528h                 ; AH = 35h, AL = номер прерывания
        int        21h                      ; получить адрес обработчика INT 28h
        mov        word ptr old_int28h,bx   ; и поместить его в old_int28h
        mov        word ptr old_int28h+2,es
        mov        ax,3508h                 ; AH = 35h, AL = номер прерывания
        int        21h                      ; получить адрес обработчика INT 08h
        mov        word ptr old_int08h,bx   ; и поместить его в old_int08h
        mov        word ptr old_int08h+2,es
        mov        ax,3513h                 ; AH = 35h, AL = номер прерывания
        int        21h                      ; получить адрес обработчика INT 13h
        mov        word ptr old_int13h,bx   ; и поместить его в old_int13h
        mov        word ptr old_int13h+2,es
        mov        ax,3509h                 ; AH = 35h, AL = номер прерывания
        int        21h                      ; получить адрес обработчика INT 09h
        mov        word ptr old_int09h,bx   ; и поместить его в old_int09h
        mov        word ptr old_int09h+2,es
        mov        ax,252Dh                 ; AH = 25h, AL = номер прерывания
        mov        dx,offset int2Dh_handler ; DS:DX - адрес обработчика
        int        21h                      ; установить новый обработчик INT 2Dh
        mov        ax,2528h                 ; AH = 25h, AL = номер прерывания
        mov        dx,offset int28h_handler ; DS:DX - адрес обработчика
        int        21h                      ; установить новый обработчик INT 28h
        mov        ax,2508h                 ; AH = 25h, AL = номер прерывания
        mov        dx,offset int08h_handler ; DS:DX - адрес обработчика
        int        21h                      ; установить новый обработчик INT 08h
        mov        ax,2513h                 ; AH = 25h, AL = номер прерывания
        mov        dx,offset int13h_handler ; DS:DX - адрес обработчика
        int        21h                      ; установить новый обработчик INT 13h
        mov        ax,2509h                 ; AH = 25h, AL = номер прерывания
        mov        dx,offset int09h_handler ; DS:DX - адрес обработчика
        int        21h                      ; установить новый обработчик INT 09h
 
; освободить память из-под окружения DOS
        mov        ah,49h               ; Функция DOS 49h
        mov        es,word ptr envseg   ; ES = сегментный адрес окружения DOS
        int        21h                  ; освободить память
 
; оставить программу резидентной
        mov        dx,offset initialize ; DX - адрес первого байта за концом
                                        ; резидентной части
        int        27h                  ; завершить выполнение, оставшись
                                        ; резидентом
initialize         endp
 
ems_driver         db    'EMMXXXX0',0   ; имя EMS-драйвера для проверки
 
; текст, который выдает программа при запуске:
usage              db    'Простая программа для копирования экрана только из'
                   db    ' видеорежима 13h',0Dh,0Ah
                   db    ' Alt-G - записать копию экрана в scrgrb.bmp'
                   db    0Dh,0Ah
                   db    ' scrgrb.com /u - выгрузиться из памяти',0Dh,0Ah
                   db    '$'
 
; тексты, которые выдает программа при успешном выполнении:
ems_msg            db    'Загружена в EMS',0Dh,0Ah,'$'
conv_msg           db    'He загружена в EMS',0Dh,0Ah,'$'
unloaded_msg       db    'Программа успешно выгружена из памяти',0Dh,0Ah,'$'
 
; тексты, которые выдает программа при ошибках:
already_msg        db    'Ошибка: Программа уже загружена',0Dh,0Ah,'$'
no_more_mux_msg    db    'Ошибка: Слишком много резидентных программ'
                   db    0Dh,0Ah,'$'
cant_unload1_msg   db    'Ошибка: Программа не обнаружена в памяти',0Dh,0Ah,'$'
cant_unload2_msg   db    'Ошибка: Другая программа перехватила прерывания'
                   db    0Dh,0Ah,'$'
unloading          db    0              ; 1, если нас запустили с ключом /u
 
; BMP-файл (для изображения 320x200x256)
BMP_header         label    byte
; файловый заголовок
BMP_file_header    db    "BM"           ; сигнатура
                   dd    bfsize         ; размер файла
                   dw    0,0            ; 0
                   dd    bfoffbits      ; адрес начала BMP_data
; информационный заголовок
BMP_info_header    dd    bi_size        ; размер BMP_info_header
                   dd    320            ; ширина
                   dd    200            ; высота
                   dw    1              ; число цветовых плоскостей
                   dw    8              ; число бит на пиксель
                   dd    0              ; метод сжатия данных
                   dd    320*200        ; размер данных
                   dd    0B13h          ; разрешение по X (пиксель на метр)
                   dd    0B13h          ; разрешение по Y (пиксель на метр)
                   dd    0              ; число используемых цветов (0 - все)
                   dd    0              ; число важных цветов (0 - все)
bi_size = $-BMP_info_header             ; размер BMP_info_header
BMP_header_length = $-BMP_header        ; размер обоих заголовков
bfoffbits = $-BMP_file_header+256*4     ; размер заголовков + размер палитры
bfsize = $-BMP_file_header+256*4+320*200 ; размер заголовков +
                                         ; размер палитры + размер данных
length_of_program = $-start
        end        start
В этом примере, достаточно сложном из-за необходимости избегать всех возможностей повторного вызова прерываний DOS и BIOS, добавилась еще одна мера предосторожности — сохранение состояния EMS-памяти перед работой с ней и восстановление в исходное состояние. Действительно, если наш резидент активируется в тот момент, когда какая-то программа работает с EMS, и не выполнит это требование, программа будет читать/писать уже не в свои EMS-страницы, а в наши. Аналогичные предосторожности следует предпринимать всякий раз, когда вызываются функции, затрагивающие какие-нибудь глобальные структуры данных. Например: функции поиска файлов используют буфер DTA, адрес которого надо сохранить (функция DOS 2Fh), затем создать собственный (функция DOS 1Ah) и в конце восстановить DTA прерванного процесса по сохраненному адресу (функция 1Ah). Таким образом надо сохранять/восстанавливать состояние адресной линии А20 (функции XMS 07h и 03h), если резидентная программа хранит часть своих данных или кода в области HMA, сохранять состояние драйвера мыши (INT 33h, функции 17h и 18h), сохранять информацию о последней ошибке DOS (функции DOS 59h и 5D0Ah), и так с каждым ресурсом, который затрагивает резидентная программа. Писать полноценные резидентные программы в DOS сложнее всего, но, если не выходить за рамки реального режима, это — самое эффективное средство управления системой и реализации всего, что только можно реализовать в DOS.


5.9.4. Полурезидентные программы

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

В качестве примера напишем простой загрузчик для игры «Tie Fighter», который устранит ввод пароля, требующийся при каждом запуске игры. Разумеется, это условный пример, так как игра никак не шифрует свои файлы, и тот же эффект можно было достигнуть, изменив всего два байта в файле front.ovl. Единственное преимущество нашего загрузчика будет состоять в том, что он оказывается годен для всех версий игры (от «X-Wing» до «Tie Fighter: Defender of the Empire»).

Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
; tieload.asm
; Пример полурезидентной программы - загрузчик, устраняющий проверку пароля
; для игр компании Lucasarts:
; "X-Wing", "X-Wing: Imperial Pursuit", "B-Wing",
; "Tie Fighter", "Tie Fighter: Defender of the Empire"
;
        .model     tiny
        .code
        .386                            ; для команды LSS
        org        100h                 ; СОМ-программа
start:
; освободить память после конца программы (+ стек)
        mov        sp,length_of_program ; перенести стек
        mov        ah,4Ah               ; функция DOS 4Ah
        mov        bx,par_length        ; размер в параграфах
        int        21h                  ; изменить размер выделенной памяти
 
; заполнить поля ЕРВ, содержащие сегментные адреса
        mov        ax,cs
        mov        word ptr EPB+4,ax
        mov        word ptr EPB+8,ax
        mov        word ptr EPB+0Ch,ax
 
; загрузить программу без выполнения
        mov        bx,offset EPB        ; ES:BX - EPB
        mov        dx, offset filename  ; DS:DX - имя файла (TIE.EXE)
        mov        ax,4B01h             ; функция DOS 4B01h
        int        21h                  ; загрузить без выполнения
        jnc        program_loaded       ; если TIE.EXE не найден,
        mov        byte ptr XWING,1     ; установить флаг для find_passwd
        mov        ax,4B01h
        mov        dx,offset filename2  ; и попробовать BWING.EXE,
        int        21h
        jnc        program_loaded       ; если он не найден,
        mov        ax,4B01h
        mov        dx,offset filename3  ; попробовать XWING.EXE,
        int        21h
        jc         error_exit           ; если и он не найден
                                        ; (или не загружается по
                                        ; какой-нибудь другой причине)
                                        ; выйти с сообщением об ошибке
program_loaded:
; Процедура проверки пароля не находится непосредственно в исполняемом файле
; tie.exe, bwing.exe или xwing.exe, а подгружается позже из оверлея front.ovl,
; bfront.ovl или fcontend.ovl соответственно. Найти команды, выполняющие чтение
; из этого оверлея, и установить на них наш обработчик find_passwd
        cld
        push       cs
        pop        ax
        add        ax,par_length
        mov        ds,ax
        xor        si,si            ; DS:SI - первый параграф после конца нашей
                                    ; программы (то есть начало области, в которую
                                    ; была загружена модифицируемая программа)
        mov        di,offset read_file_code ; ES:DI - код для сравнения
        mov        cx,rf_code_l             ; CX - его длина
        call       find_string              ; поиск кода,
        jc         error_exit2              ; если он не найден - выйти
                                            ; с сообщением об ошибке
; заменить 6 байт из найденного кода командами call find_passwd и nop
        mov        byte ptr [si],9Ah        ; CALL (дальний)
        mov        word ptr [si+1],offset find_passwd
        mov        word ptr [si+3],cs
        mov        byte ptr [si+5],90h      ; NOP
 
; запустить загруженную программу
; надо записать правильные начальные значения в регистры для ЕХЕ-программы
; и заполнить некоторые поля ее PSP
        mov        ah,51h               ; функция DOS 51h
        int        21h                  ; BX = PSP-сегмент загруженной программы
        mov        ds,bx                ; поместить его в DS
        mov        es,bx                ; и ES. Заполнить также поля PSP:
        mov        word ptr ds:[0Ah],offset exit_without_msg
        mov        word ptr ds:[0Ch],cs ; "адрес возврата"
        mov        word ptr ds:[16h],cs ; и "адрес PSP - предка"
        lss        sp,dword ptr cs:EPB_SSSP ; загрузить SS:SP
        jmp        dword ptr cs:EPB_CSIP    ; и передать управление на
                                            ; точку входа программы
 
XWING   db         0        ; 1/0: тип защиты X-wing/Tie-fighter
ЕРВ     dw         0        ; запускаемый файл получает среду DOS от tieload.com,
        dw         0080h,?  ; и командную строку,
        dw         005Ch,?  ; и первый FCB,
        dw         006Ch,?  ; и второй FCB
EPB_SSSP  dd       ?        ; начальный SS:SP - заполняется DOS
EPB_CSIP  dd       ?        ; начальный CS:IP - заполняется DOS
 
filename1          db    "tie.exe",0    ; сначала пробуем запустить этот файл,
filename2          db    "bwing.exe",0  ; потом этот,
filename3          db    "xwing.exe",0  ; а потом этот
 
; сообщения об ошибках
error_msg          db    "Ошибка: не найдены ни один из файлов TIE.EXE, "
                   db    "BWING.EXE, XWING. EXE",0Dh,0Ah,'$'
error_msg2         db    "Ошибка: участок кода не найден",0Dh,0Ah,'$'
 
; команды, выполняющие чтение оверлейного файла в tie.exe/bwing.exe/xwing.exe:
read_file_code:
                   db    33h,0D2h       ; xor dx,dx
                   db    0B4h,3Fh       ; mov ah,3Fh
                   db    0CDh,21h       ; int 21h
                   db    72h            ; jz (на разный адрес в xwing и tie)
rf_code_l = $-read_file_code
 
; Команды, вызывающие процедуру проверки пароля.
; Аналогичный набор команд встречается и в других местах, поэтому find_passwd
; будет выполнять дополнительные проверки
passwd_code:
                   db    89h,46h,0FCh   ; mov [bp-4],ax
                   db    89h,56h,OFEh   ; mov [bp-2],dx
                   db    52h            ; push dx
                   db    50h            ; push ax
                   db    9Ah            ; call far
passwd_l = $-passwd_code
 
error_exit:
        mov        dx,offset error_msg  ; вывод сообщения об ошибке 1
        jmp        short exit_with_msg
error_exit2:
        mov        dx,offset error_msg2 ; вывод сообщения об ошибке 2
exit_with_msg:
        mov        ah, 9                ; Функция DOS 09h
        int        21h                  ; вывести строку на экран
exit_without_msg:                  ; сюда также передается управление после
                                   ; завершения загруженной программы (этот адрес
                                   ; был вписан в поле PSP "адрес возврата")
        mov        ah,4Ch          ; Функция DOS 4Ch
        int        21h             ; конец программы
 
; эту процедуру вызывает программа tie.exe/bwing.exe/xwing.exe каждый раз, когда
; она выполняет чтение из оверлейного файла
find_passwd        proc    far
; выполнить три команды, которые мы заменили на call find_passwd
        xor        dx,dx
        mov        ah,3Fh          ; функция DOS 3Fh
        int        21h             ; чтение из файла или устройства
deactivation_point:                ; по этому адресу мы запишем код команды RETF,
                                   ; когда наша задача будет выполнена,
        pushf                      ; сохраним флаги
        push       ds              ; и регистры
        push       es
        pusha
        push       cs
        pop        es
        mov        si,dx           ; DS:DX - начало только что прочитанного участка
                                   ; оверлейного файла
        mov        di,offset passwd_code  ; ES:DI - код для сравнения
        dec        si                     ; очень скоро мы его увеличим обратно
search_for_pwd:          ; в этом цикле найденные вхождения эталонного кода
                         ; проверяются на точное соответствие коду проверки пароля
        inc        si    ; процедура find_string возвращает DS:SI указывающим на
                         ; начало найденного кода - чтобы искать дальше, надо
                         ; увеличить SI хотя бы на 1
        mov        cx,passwd_l          ; длина эталонного кода
        call       find_string          ; поиск его в памяти,
        jc         pwd_not_found        ; если он не найден - выйти
; find_string нашла очередное вхождение нашего эталонного кода вызова
; процедуры - проверим, точно ли это вызов процедуры проверки пароля
        cmp        byte ptr [si+10],00h ; этот байт должен быть 00
        jne        search_for_pwd
        cmp        byte ptr cs:XWING,1  ; в случае X-wing/B-wing
        jne        check_for_tie
        cmp        word ptr [si+53],0774h ; команда je должна быть здесь,
        jne        search_for_pwd
        jmp        short pwd_found
check_for_tie:                            ; а в случае Tie Fighter -
        cmp        word ptr [si+42],0774h ; здесь
        jne        search_for_pwd
pwd_found:     ; итак, вызов процедуры проверки пароля найден - отключить его
        mov        word ptr ds:[si+8],9090h    ; NOP NOP
        mov        word ptr ds:[si+10],9090h   ; NOP NOP
        mov        byte ptr ds:[si+12],90h     ; NOP
               ; и деактивировать нашу процедуру find_passwd
        mov        byte ptr cs:deactivation_point,0CBh  ; RETF
pwd_not_found:
        popa                            ; восстановить регистры
        pop        es
        pop        ds
        popf                            ; и флаги
        ret                             ; и вернуть управление в программу
find_passwd        endp
 
; процедура find_string
; выполняет поиск строки от заданного адреса до конца всей общей памяти
; ввод: ES:DI - адрес эталонной строки
;       СХ - ее длина
;       DS:SI - адрес, с которого начинать поиск
; вывод: CF = 1, если строка не найдена,
; иначе: CF = 0 и DS:SI - адрес, с которого начинается найденная строка
find_string        proc    near
        push       ax
        push       bx
        push       dx                   ; сохранить регистры
do_cmp:
        mov        dx,1000h             ; поиск блоками по 1000h (4096 байт)
cmp_loop:
        push       di
        push       si
        push       cx
        repe       cmpsb                ; сравнить DS:SI со строкой
        pop        cx
        pop        si
        pop        di
        je         found_code           ; если совпадение - выйти с CF = 0,
        inc        si                   ; иначе - увеличить DS:SI на 1,
        dec        dx                   ; уменьшить счетчик в DX
        jne        cmp_loop             ; и, если он не ноль, продолжить
; пройден очередной 4-килобайтный блок
        sub        si,1000h             ; уменьшить SI на 1000h
        mov        ax,ds
        inc        ah                   ; и увеличить DS на 1
        mov        ds,ax
        cmp        ax,9000h             ; если мы добрались до
        jb         do_cmp               ; сегментного адреса 9000h -
        pop        dx                   ; восстановить регистры
        pop        bx
        pop        ax
        stc                             ; установить CF = 1
        ret                             ; и выйти
; сюда передается управление, если строка найдена found_code:
        pop        dx                   ; восстановить регистры
        pop        bx
        pop        ax
        clc                             ; установить CF = 0
        ret                             ; и выйти
find_string        endp
 
end_of_program:
lengtn_of_program = $-start+100h+100h   ; длина программы в байтах
par_length = length_of_program + 0Fh
par_length = par_length/16              ; длина программы в параграфах
        end        start


5.9.5. Взаимодействие между процессами

Из того, что DOS является однозадачной операционной системой, вовсе не следует, что в ней не могут существовать одновременно несколько процессов. Это только означает, что сама система не будет предоставлять никаких специальных возможностей для их одновременного выполнения, кроме возможности оставлять программы резидентными в памяти. Так, чтобы организовать общую память для нескольких процессов, надо загрузить пассивную резидентную программу, которая будет поддерживать функции выделения блока памяти (возвращающая идентификатор), определения адреса блока (по его идентификатору) и освобождения блока — приблизительно так же, как работают драйверы EMS или XMS.

Чтобы реализовать многозадачность, придется запустить активную резидентную программу, которая перехватит прерывание IRQ0 и по каждому такту системного таймера будет по очереди отбирать управление от каждого из запущенных процессов и передавать следующему. Практически никто не реализует полноценную многозадачность в DOS, когда каждый процесс имеет собственную память и не может обращаться к памяти другого процесса, — для этого существует защищенный режим, но встречаются довольно простые реализации для облегченного варианта многозадачности — переключение нитей.

Нить — это процесс, который использует тот же код и те же данные, что и остальные такие же процессы в системе, но отличается от них содержимым стека и регистров. Тогда резидентная программа-диспетчер по каждому прерыванию таймера будет сохранять регистры прерванной нити в ее структуру, считывать регистры следующей нити в очереди и возвращать управление, а структуры и стеки всех нитей будут храниться в какой-нибудь специально выделенной общедоступной области памяти. Указанная программа также должна поддерживать несколько вызовов при помощи какого-нибудь программного прерывания — создание нити, удаление нити и, например, передача управления следующей нити, пока текущая нить находится в состоянии ожидания.

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

Попробуем сделать простой прототип такой многозадачности в DOS (всего с двумя нитями) и посмотрим, со сколькими проблемами придется столкнуться.

Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
; scrsvr.asm
; Пример простой задачи, реализующей нитевую многозадачность в DOS.
; Изображает на экране две змейки, двигающиеся случайным образом, каждой из
; которых управляет своя нить.
;
; Передача управления между нитями не работает в окне DOS (Windows 95)
 
        .model     tiny
        .code
        .386                            ; ГСЧ использует 32-битные регистры
        org        100h                 ; СОМ-программа
start:
        mov        ax,13h               ; видеорежим 13h
        int        10h                  ; 320x200x256
        call       init_threads         ; инициализировать наш диспетчер
; с этого места и до вызова shutdown_threads исполняются две нити с одним и тем
; же кодом и данными, но с разными регистрами и стеками
; (в реальной системе здесь был бы вызов fork или аналогичной функции)
 
        mov        bx,1               ; цвет (синий)
        push       bp
        mov        bp,sp              ; поместить все локальные переменные в стек,
                                      ; чтобы обеспечить повторную входимость
        push       1                  ; добавка к X на каждом шаге
x_inc              equ    word ptr [bp-2]
push    0                             ; добавка к Y на каждом шаге
y_inc              equ    word ptr [bp-4]
        push       128-4        ; относительный адрес головы буфера line_coords
coords_head        equ    word ptr [bp-6]
        push       0            ; относительный адрес хвоста буфера line_coords
coords_tail        equ    word ptr [bp-8]
        sub        sp,64*2      ; line_coords - кольцевой буфер координат точек
        mov        di,sp
        mov        cx,64
        mov        ax,10              ; заполнить его координатами (10, 10)
        push       ds
        pop        es
        rep        stosw
line_coords        equ    word ptr [bp-(64*2)-8]
 
        push       0A000h
        pop        es                   ; ES - адрес видеопамяти
 
main_loop:                              ; основной цикл
        call       display_line         ; изобразить текущее состояние змейки
 
; изменить направление движения случайным образом
        push       bx
        mov        ebx,50               ; вероятность смены направления 2/50
        call       z_random             ; получить случайное число от 0 до 49
        mov        ax,word ptr x_inc
        mov        bx,word ptr y_inc
        test       dx,dx                ; если это число - 0,
        jz         rot_right            ; повернем направо,
        dec        dx                   ; а если 1 -
        jnz        exit_rot             ; налево
 
; повороты
        neg        ax                   ; налево на 90 градусов
        xchg       ax,bx                ; dY = -dX, dX = dY
        jmp        short exit_rot
rot_right:
        neg        bx                   ; направо на 90 градусов
        xchg       ax,bx                ; dY = dX, dX = dY
exit_rot:
        mov        word ptr x_inc,ax    ; записать новые значения инкрементов
        mov        word ptr y_inc,bx
        pop        bx                   ; восстановить цвет в ВХ
 
; перемещение змейки на одну позицию вперед
        mov        di,word ptr coords_head       ; DI - адрес головы
        mov        cx,word ptr line_coords[di]   ; СХ-строка
        mov        dx,word ptr line_coords[di+2] ; DX-столбец
        add        cx,word ptr y_inc             ; добавить инкременты
        add        dx,word ptr x_inc
        add        di,4                          ; DI - следующая точка в буфере,
        and        di,127                        ; если DI > 128, DI = DI - 128
        mov        word ptr coords_head,di       ; теперь голова здесь
        mov        word ptr line_coords[di],cx   ; записать ее координаты
        mov        word ptr line_coords[di+2],dx
        mov        di,word ptr coords_tail       ; прочитать адрес хвоста
        add        di,4                          ; переместить его на одну
        and        di,127                        ; позицию вперед
        mov        word ptr coords_tail,di       ; и записать на место
 
; пауза,
; из-за особенностей нашего диспетчера (см. ниже) мы не можем пользоваться
; прерыванием BIOS для паузы, поэтому сделаем просто пустой цикл. Длину цикла
; придется изменить в зависимости от скорости процессора
        mov        cx,-1
        loop       $                    ; 65 535 команд loop
        mov        cx,-1
        loop       $
        mov        cx,-1
        loop       $
        mov        ah,1
        int        16h                  ; если не было нажато никакой клавиши,
        jz         main_loop            ; продолжить основной цикл,
        mov        ah,0                 ; иначе - прочитать клавишу
        int        16h
        leave                           ; освободить стек от локальных переменных
        call       shutdown_threads     ; выключить многозадачность
; с этого момента у нас снова только один процесс
        mov        ах,3                 ; видеорежим 3
        int        10h                  ; 80x24
        int        20h                  ; конец программы
 
; процедура вывода точки на экран в режиме 13h
; СХ = строка, DX = столбец, BL = цвет, ES = A000h
putpixel           proc    near
        push       di
        lea        ecx,[ecx*4+ecx]      ; CX = строка * 5
        shl        cx,6                 ; CX = строка * 5 * 64 = строка * 320
        add        dx,cx                ; DX = строка * 320 + столбец = адрес
        mov        di,dx
        mov        al,bl
        stosb                           ; записать байт в видеопамять
        pop        di
        ret
putpixel           endp
 
; процедура display_line
; выводит на экран нашу змейку по координатам из кольцевого буфера line_coords
display_line       proc    near
        mov        di,word ptr coords_tail ; начать вывод с хвоста,
continue_line_display:
        cmp        di,word ptr coords_head ; если DI равен адресу головы,
        je         line_displayed          ; вывод закончился,
        call       display_point           ; иначе - вывести точку на экран,
        add        di,4                    ; установить DI на следующую точку
        and        di,127
        jmp        short continue_line_display ; и так далее
line_displayed:
        call       display_point
        mov        di,word ptr coords_tail ; вывести точку в хвосте
        push       bx
        mov        bx,0                    ; нулевым цветом,
        call       display_point           ; то есть стереть
        pop        bx
        ret
display_line       endp
 
; процедура display_point
; выводит точку из буфера line_coords с индексом DI
display_point      proc  near
        mov        cx,word ptr line_coords[di]   ; строка
        mov        dx,word ptr line_coords[di+2] ; столбец
        call       putpixel                      ; вывод точки
        ret
display_point      endp
 
; процедура z_random
; стандартный конгруэнтный генератор случайных чисел (неоптимизированный)
; ввод: ЕВХ - максимальное число
; вывод: EDX - число от 0 до ЕВХ-1
z_random:
        push       ebx
        cmp        byte ptr zr_init_flag,0  ; если еще не вызывали,
        je         zr_init                  ; инициализироваться,
        mov        eax,zr_prev_rand         ; иначе - умножить предыдущее
zr_cont:
        mul        rnd_number               ; на множитель
        div        rnd_number2              ; и разделить на делитель,
        mov        zr_prev_rand,edx         ; остаток от деления - новое число
        pop        ebx
        mov        eax,edx
        xor        edx,edx
        div        ebx                      ; разделить его на максимальное
        ret                                 ; и вернуть остаток в EDX
zr_init:
        push       0040h                    ; инициализация генератора
        pop        fs                       ; 0040h:006Ch -
        mov        eax,fs:[006Ch]           ; счетчик прерываний таймера BIOS,
        mov        zr_prev_rand,eax         ; он и будет первым случайным числом
        mov        byte ptr zr_init_flag,1
        jmp        zr_cont
rnd_number         dd    16807              ; множитель
rnd_number2        dd    2147483647         ; делитель
zr_init_flag       db    0                  ; флаг инициализации генератора
zr_prev_rand       dd    0                  ; предыдущее случайное число
 
; здесь начинается код диспетчера, обеспечивающего многозадачность
 
; структура данных, в которой мы храним регистры для каждой нити
thread_struc struc
_ах     dw      ?
_bx     dw      ?
_cx     dw      ?
_dx     dw      ?
_si     dw      ?
_di     dw      ?
_bp     dw      ?
_sp     dw      ?
_ip     dw      ?
_flags  dw      ?
thread_struc ends
 
; процедура init_threads
; инициализирует обработчик прерывания 08h и заполняет структуры, описывающие
; обе нити
init_threads       proc    near
        pushf
        pusha
        push       es
        mov        ax,3508h             ; AH = 35h, AL = номер прерывания
        int        21h                  ; определить адрес обработчика,
        mov        word ptr old_int08h,bx ; сохранить его
        mov        word ptr old_int08h+2,es
        mov        ax,2508h             ; AH = 25h, AL = номер прерывания
        mov        dx,offset int08h_handler ; установить наш
        int        21h
        pop        es
        popa                  ; теперь регистры те же, что и при вызове процедуры
        popf
 
        mov        thread1._ax,ax       ; заполнить структуры
        mov        thread2._ax,ax       ; threadl и thread2,
        mov        thread1._bx,bx       ; в которых хранится содержимое
        mov        thread2._bx,bx       ; всех регистров (кроме сегментных -
        mov        thread1._cx,cx       ; они в этом примере не изменяются)
        mov        thread2._cx,cx
        mov        thread1._dx,dx
        mov        thread2._dx.dx
        mov        thread1._si,si
        mov        thread2._si,si
        mov        thread1._di,di
        mov        thread2._di,di
        mov        thread1._bp,bp
        mov        thread2._bp,bp
        mov        thread1._sp,offset thread1_stack+512
        mov        thread2._sp,offset thread2_stack+512
        pop        ax                   ; адрес возврата (теперь стек пуст)
        mov        thread1._ip,ax
        mov        thread2._ip,ax
        pushf
        pop        ax                   ; флаги
        mov        thread1._flags,ax
        mov        thread2._flags,ax
        mov        sp,thread1._sp       ; установить стек нити 1
        jmp        word ptr thread1._ip ; и передать ей управление
init_threads       endp
 
current_thread     db    1              ; номер текущей нити
 
; Обработчик прерывания INT08h (IRQ0) переключает нити
int08h_handler     proc    far
        pushf                           ; сначала вызвать старый обработчик
                   db    9Ah            ; код команды call far
old_int08h         dd    0              ; адрес старого обработчика
; Определить, произошло ли прерывание в момент исполнения нашей нити или
; какого-то обработчика другого прерывания. Это важно, так как мы не собираемся
; возвращать управление тому, кого прервал таймер, по крайней мере сейчас.
; Именно поэтому нельзя пользоваться прерываниями для задержек в наших нитях и
; поэтому программа не работает в окне DOS (Windows 95)
        mov        save_di,bp           ; сохранить ВР
        mov        bp,sp
        push       ax
        push       bx
        pushf
        mov        ax,word ptr [bp+2]   ; прочитать сегментную часть
        mov        bx,cs                ; обратного адреса,
        cmp        ax,bx                ; сравнить ее с CS,
        jne        called_far           ; если они не совпадают - выйти,
        popf
        pop        bx                   ; иначе - восстановить регистры
        pop        ax
        mov        bp,save_di
        mov        save_di,di           ; сохранить DI, SI
        mov        save_si,si
        pushf                           ; и флаги
; определить, с какой нити на какую надо передать управление,
        cmp        byte ptr current_thread,1  ; если с первой,
        je         thread1_to_thread2         ; перейти на thread1_to_thread2,
        mov        byte ptr current_thread,1  ; если с 2 на 1, записать
                                              ; в номер 1
        mov        si,offset thread1          ; и установить SI и DI
        mov        di,offset thread2          ; на соответствующие структуры,
        jmp        short order_selected
thread1_to_thread2:                           ; если с 1 на 2,
        mov        byte ptr current_thread,2  ; записать в номер нити 2
        mov        si,offset thread2          ; и установить SI и DI
        mov        di,offset thread1
order_selected:
; записать все текущие регистры в структуру по адресу [DI]
; и загрузить все регистры из структуры по адресу [SI]
; начать с SI и DI:
        mov        ax,[si]._si          ; для MASM все выражения [reg]._reg надо
        push       save_si              ; заменить на (thread_struc ptr [reg])._reg
        pop        [di]._si
        mov        save_si,ax
        mov        ax,[si]._di
        push       save_di
        pop        [di]._di
        mov        save_di,ax
; теперь все основные регистры
        mov        [di._ax],ax
        mov        ax,[si._ax]
        mov        [di._bx],bx
        mov        bx,[si._bx]
        mov        [di._cx],cx
        mov        cx,[si._cx]
        mov        [di._dx],dx
        mov        dx,[si._dx]
        mov        [di._bp],bp
        mov        bp,[si._bp]
; флаги
        pop        [di._flags]
        push       [si._flags]
        popf
; адрес возврата
        pop        [di._ip]             ; адрес возврата из стека
        add        sp,4                 ; CS и флаги из стека - теперь он пуст
; переключить стеки
        mov        [di._sp],sp
        mov        sp,[si._sp]
        push       [si._ip]             ; адрес возврата в стек (уже новый)
        mov        di,save_di           ; загрузить DI и SI
        mov        si,save_si
        retn                            ; и перейти по адресу в стеке
; управление переходит сюда, если прерывание произошло в чужом коде
called_far:
        popf                            ; восстановить регистры
        pop        bx
        pop        ax
        mov        bp,save_di
        iret                            ; и завершить обработчик
int08h_handler     endp
 
save_di            dw    ?              ; переменные для временного хранения
save_si            dw    ?              ; регистров
 
; процедура shutdown_threads
; выключает диспетчер
shutdown_threads   proc    near
        mov        ax,2508h             ; достаточно просто восстановить прерывание
        lds        dx,dword ptr old_int08h
        int        21h
        ret
shutdown_threads   endp
 
; структура, описывающая первую нить
thread1 thread_struc <>
; и вторую,
thread2 thread_struc <>
; стек первой нити
thread1_stack db 512 dup(?)
; и второй
thread2_stack db 512 dup(?)
    end            start
Как мы видим, этот пример не может работать в Windows 95 и в некоторых других случаях, когда DOS расширяют до более совершенной операционной системы. Фактически в этом примере мы именно этим и занимались — реализовывали фрагмент операционной системы, который отсутствует в DOS.

Действительно, используя механизм обработчиков прерываний, можно создать операционную систему для реального режима, аналогичную DOS, но очень быстро окажется, что для этого придется общаться напрямую с аппаратным обеспечением компьютера, то есть использовать порты ввода-вывода.
Была использована следующая литература:
  1. Абель П. "Язык Ассемблера для IBM PC и программирования". - М.: "Высшая школа", 1992. - 447 с.
  2. Зубков С.В. "Assembler для DOS, Windows и UNIX". - М.: "ДМК Пресс", 2000. - 608 с.:ил.
  3. Джордейн Р. "Справочник программиста персональных компьютеров типа IBM PC, XT и AT". - М.: "Финансы и статистика", 1987. - 544 с.
  4. статья на http://shackmaster.narod.ru/docs.htm#tsr
3
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
27.01.2014, 09:25  [ТС] #48
Флёнов Михаил Евгеньевич — российский программист, работал в журнале "Хакер" (рубрики "Hack-FAQ" и "Кодинг для программистов"), печатался в журналах "Игромания" и "Chip-Россия", автор книг по программированию и работе на компьютере:
  • Библия C#
  • Web-сервер глазами хакера. Безопасность в интернете
  • Искусство программирования игр на С++
  • DirectX и Delphi. Искусство программирования. Программирование графики
  • DirectX и C++. Искусство программирования. Программирование графики
  • Transact-SQL
  • PHP глазами хакера
  • Linux глазами хакера
  • Компьютер глазами хакера. Безопасность
  • Программирование на C++ глазами хакера
  • Программирование в Delphi глазами хакера
  • Библия Delphi


Советы по оптимизации от Михаила Фленова

ЗАКОН # 1

Оптимизировать можно все. Даже там, где вам кажется, что все и так работает быстро, можно сделать еще быстрее.

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

ЗАКОН # 2

Первое, с чего нужно начинать, — поиск самых слабых и медленных мест. Зачем начинать оптимизацию с того, что и так работает достаточно быстро? Если вы будете оптимизировать сильные места, то можете нарваться на неожиданные конфликты. Да и эффект будет минимален.
Когда-то меня посетила одна невероятная идея — написать собственную игру в стиле Doom. Четыре месяца невероятного труда, и нечто похожее на движок уже было готово. Был создан один голый уровень, по которому можно было перемещаться, и я с чувством гордости побежал по коридорам. Предстояло добавить монстров и атрибуты, да еще и наделить все это искусственным интеллектом. Но кому нужен движок, который в "голом виде" тормозит со страшной силой?
Понятно что виртуальный мир нужно было оптимизировать. Целый месяц борьбы с кодом и вылизывания каждого оператора движка. Результат — всё стало прорисовываться на 10% быстрей, но "тормоза" не исчезли. Неожиданно выяснилось, что самое слабое место — вывод на экран. Движок просчитывал сцены достаточно быстро, а "пробоиной" был именно вывод изображения. Пара часов колдовства, и я выжал из видеокарты все возможное. Откомпилировав движок, я снова погрузился в виртуальный мир. Одно нажатие клавиши "вперед", и я очутился у противоположной стены. Никаких тормозов, сумасшедшая скорость просчета и моментальный вывод на экран.
Как видите, ошибка была в неправильном определении слабого места движка. Потрачен месяц на оптимизацию математики, а в результате мизерные 10% прироста в производительности. Но когда было найдено реально слабое звено, то производительность была повышена в несколько раз.
Начинайте оптимизацию со слабых мест. При ускорении работы самого слабого звена вашей программы, возможно, и не понадобится ускорять другие места. Вы можете потратить дни на оптимизацию сильных сторон и увеличить производительность только на 10% (что может оказаться недостаточным), или несколько часов на улучшение слабой части, и получить улучшение в 10 раз!
Слабые места компьютера
Некоторые программисты гонятся за мегагерцами процессора, но сидят на доисторической видеокарте от S3, жестком диске на 5400 оборотов и с 32 Мбайтами памяти. Посмотрите "потроха" своего компьютера и оцените его содержимое. Если вы увидели, что памяти у вас не более 64 Мбайт, то это — самое слабое звено, купите себе 128, а лучше 256, а еще лучше 512 Мбайт памяти и наслаждаетесь ускорением работы Delphi, Photoshop и других "тяжелых" программ.
Наращивание сотни мегагерц у процессора даст более маленький прирост в скорости. Если вы используете тяжелые приложения при нехватке памяти, то процессор начинает тратить слишком много времени на загрузку и выгрузку данных. Если в вашем компьютере достаточно оперативной памяти, то процессор уже занимается только расчетами и не расходуется по лишним загрузкам-выгрузкам.
То же самое с видеоадаптером. Если видеокарта "слабенькая", то процессор будет просчитывать сцены быстрей, чем они будут выводиться на экран. А это грозит простоями и минимальным приростом производительности.

ЗАКОН # 3
Следующим шагом вы должны разобрать все операции по косточкам и выяснить, где происходят регулярно повторяющиеся операции. Начинать оптимизацию нужно именно с них.
Допустим, у вас есть следующая программа:
Код
1. А:=А*2;
2. B:=1;
3. Х:=Х+М[B];
4. B:=B+1;
5. Если B<100 то перейти на шаг 3.
Слабым местом является первая строка, потому что там используется умножение. Это действительно так. Умножение всегда выполняется дольше, и если заменить его на сложение (А:=А+А) или еще лучше на сдвиг, тогда выиграем пару тактов процессорного времени. Но только пару тактов, и для процессора это будет незаметно.
Теперь посмотрите еще раз на наш код. Больше ничего не видно? Программа использует цикл: "Пока B<100, будет выполняться операция Х:=Х+М[B]". Это значит, что процессору придется выполнить 100 переходов с шага 5 на шаг 3. А это немало. Можно ли что-нибудь оптимизировать? Внутри цикла выполняется две строки: 3 и 4. А что, если мы внутри цикла размножим их 2 раза:
Код
1. B:=1;
2. Х:=Х+М[B]+М[B+1];
3. B:=B+2;
4. Если B<100 то перейти на шаг 2;
Сэкономлено 50 операций переходов. Неплохо? А это уже несколько сотен тактов процессорного времени.
А что, если совсем отказаться от операций перехода?
Код
1. Х:=Х+М[1]+М[2]+М[3]+М[4]+М[5]+М[6]+М[7]+М[8]+М[9]+М[10];
2. Х:=Х+М[11]+М[12]+М[13]+М[14]+М[15]+М[16]+М[17]+М[18]+М[19]+М[20];
...
10. Х:=Х+М[91]+М[92]+М[93]+М[94]+М[95]+М[96]+М[97]+М[98]+М[99];
Недостаток этого подхода — увеличился объем программы, зато повысилась скорость, и очень значительно. Этот подход очень хорош, но им не стоит злоупотреблять. В любом деле главное — разумная достаточность. Чем больше вы увеличиваете код ради оптимизации скорости, тем меньше результирующий эффект от оптимизации.
Любую циклическую операцию можно оптимизировать. Допустим, у провайдера есть несколько телефонов доступа. Вы каждый день перезваниваете на каждый из них в надежде найти свободный. Хотя провайдер обязан оптимизировать свои пулы модемов в один, чтобы не надо было трезвонить по всем номерам сразу. Но не у каждого пользователя хорошая связь с любой телефонной станцией города. Поэтому провайдеры держат пулы на разных станциях, чтобы вы могли выбрать тот, с которым связь лучше. Поставьте программу-дозвонщик, которая сама будет перебирать номера телефонов.
Другой пример — вам на 1 час досталась карточка нового провайдера. Заносить ее в программу дозвона не имеет смысла, потому что вы можете больше никогда не позвонить ему. Из-за этой одноразовой операции вам придется перенастраивать свой дозвонщик на нового провайдера и потом обратно, а выигрыш практически нулевой, потому что пока вы меняете настройки, уже можно было дозвониться стандартными средствами Windows.
Вывод — правильно выбирайте средства для выполнения необходимых задач.

ЗАКОН # 4
(Этот закон — расширение предыдущего.)

Оптимизировать одноразовые операции — это только потеря времени. Сто раз подумай, прежде чем начать мучиться с редкими операциями.

Программисту нужно разослать приглашения на свадьбу. Вместо того чтобы набрать их на печатной машинке, программист пишет специальную программу. Написание заняло один день, столько же — отладка программы.
Главная ошибка — неправильная оптимизация своего труда. Легче набрать шаблон в любом текстовом редакторе и потом только менять фамилии приглашенных. Но даже если нет текстового редактора, писать программу действительно нет смысла. Затраты большие, а пользоваться — только один раз.
Получается, что одноразовые операции оптимизировать просто бессмысленно. Затраты в этом случае себя не окупают, поэтому не стоит тратить свои нервы на этот бессмысленный труд.
В данном случае крутым считается не тот, кто целый день промучился и ничего не добился, а тот, кто выполнил свою работу наиболее быстро и эффективно.

ЗАКОН # 5

Нужно знать внутренности компьютера и принципы его работы. Чем лучше вы знаете, каким образом компьютер будет выполнять ваш код, тем лучше вы сможете его оптимизировать.
Тут трудно привести полный набор готовых решений, но некоторые приемы я постараюсь описать.
  1. Старайтесь поменьше использовать вычисления с плавающей запятой. Любые операции с целыми числами выполняются в несколько раз быстрее.
  2. Операции умножения и тем более деления также выполняются достаточно долго. Если вам нужно умножить како-то число на 3, то для процессора будет легче три раза сложить одно и то же число, чем выполнить умножение.
    А как же тогда экономить на делении? Вот тут нужно знать математику. У процессора есть такая операция, как сдвиг. Вы должны знать, что процессор думает в двоичной системе, и числа в компьютере хранятся именно в ней. Например, число 198 для процессора будет выглядеть как 11000110. Теперь посмотрим, как работают операции сдвига.
    Сдвиг вправо — если сдвинуть число 11000110 вправо на одну позицию, то последняя цифра исчезнет, и останется только 1100011. Теперь введите это число в калькулятор и переведите его в десятичную систему. Ваш результат должен быть 99. Как видите — это ровно половина числа 198.
    Вывод: когда вы сдвигаете число вправо на одну позицию, то вы делите его на 2.
    Сдвиг влево — возьмем то же самое число 11000110. Если сдвинуть его влево на одну позицию, то с правой стороны освободится место, которое заполняется нулем — 110001100. Теперь переведите это число в десятичную систему. Должно получится 396. Что оно вам напоминает? Это 198, умноженное на 2.
    Вывод: когда вы сдвигаете число вправо, то вы делите его на 2; когда сдвигаете влево, то умножаете его на 2. Так что используйте эти сдвиги везде, где возможно, потому что сдвиги работают в десятки раз быстрее умножения и деления.
  3. При создании процедур не обременяйте их большим количеством входных параметров. Перед каждым вызовом процедуры ее параметры поднимаются в специальную область памяти (стек), а после входа изымаются оттуда. Чем больше параметров, тем больше расходы на общение со стеком.
    Тут же нужно сказать, что вы должны действовать аккуратно и с самими параметрами. Не вздумайте пересылать процедурам переменные, которые могут содержать данные большого объема в чистом виде. Лучше передать адрес ячейки памяти, где хранятся данные, а внутри процедуры работать с этим адресом. Вот представьте себе ситуацию, когда вам нужно передать текст размером в один том "Войны и мира".... Перед входом в процедуру программа попытается вогнать все это в стек. Если вы не увидите его переполнения, то задержка точно будет значительная.
  4. В самых критичных моментах (как, например, вывод на экран) можно воспользоваться языком Assembler. Даже встроенный в Delphi или C++ ассемблер намного быстрее штатных функций языка. Ну а если скорость в каком-то месте уж слишком критична, то код ассемблера можно вынести в отдельный модуль. Там его нужно откомпилировать с помощью компиляторов TASM или MASM и подключить к своей программе. Ассемблер достаточно быстрая и компактная вещь, но писать достаточно большой проект только на нем — это очень сложно. Поэтому я советую им не увлекаться и использовать его только в самых критичных для скорости местах.


ЗАКОН # 6

Для сложных расчетов можно заготовить таблицы с заранее рассчитанными результатами и потом использовать эти таблицы в реальном режиме времени.
Когда появился первый Doom, игровой мир поразился качеству графики и скорости работы. Это действительно был шедевр программистской мысли, потому что компьютеры того времени не могли рассчитывать трехмерную графику в реальном времени. В те годы еще даже и не думали о ЗD-ускорителях, и видеокарты занимались только отображением информации и не выполняли никаких дополнительных расчетов.
Как же тогда программистам игры Doom удалось создать трехмерный мир? Секрет прост — во время игры микропроцессор не просчитывает сцены, все сложные математические расчеты были сделаны заранее и занесены в базу данных, которая запускается при старте программы. Конечно же занести все возможные результаты невозможно, поэтому база хранила основные результаты. Когда нужно было получить расчет значения, которого не было в заранее рассчитанной таблице, то бралось наиболее приближенное число. Таким образом, Doom получил отличную производительность и достаточное качество ЗD-картинки.
Оцените качество освещения и теней в сценах виртуального мира игры Quake. Совмещение высокого графического качества сцен и в то же время высокой скорости работы игры сделано за счет таблиц с заранее рассчитанными значениями.

ЗАКОН # 7
Лишних проверок не бывает.
Чаще всего оптимизация может привести к нестабильности исполняемого кода, потому что для увеличения производительности некоторые убирают ненужные на первый взгляд проверки. Запомните, что ненужных проверок не бывает! Если вы думаете, что какая-то нестандартная ситуация может и не возникнуть, то она не возникнет только у вас. У пользователя, который будет использовать вашу программу, может возникнуть все, что угодно. Он обязательно нажмет на то, на что не нужно, или введет неправильные данные. Обязательно делайте проверки всего того, что вводит пользователь. Делайте это сразу же и не ждите, когда введенные данные понадобятся.
Не делайте проверки в цикле, а выносите за его пределы. Любые лишние операторы if внутри цикла очень сильно влияют на производительность, поэтому по возможности проверки нужно делать до или после цикла. Циклы — это слабое место любой программы, поэтому оптимизацию надо начинать именно с них и стараться не вставлять в них лишние проверки. Внутри циклических операций не должно выполняться ничего лишнего — ведь это будет повторено много раз!..

Итог

Здесь были изложены только основы оптимизации. Оптимизация — это процесс творческий, и в каждой отдельной ситуации к нему можно подойти с разных сторон.
Для более глубокого познания оптимизации вам нужно изучать принципы работы процессора и операционных систем.
2
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
23.07.2014, 17:16  [ТС] #49
Перевод строки из десятеричных цифр в DWORD

автор The Svin взято здесь
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
atodw proc FORCENOFRAME
    ;----------------------------------------
    ; перевод десятеричной строки в число типа dword
    ; значение возвращается в регистре eax
    ;----------------------------------------
    ;   String EQU [esp+4]
    mov edx, [esp+4]
    xor eax, eax
    cmp BYTE PTR [edx], 2Eh
    sbb [esp+4], edx
    adc edx, eax
    jmp @F
again:
    lea eax, [eax+4*eax]
    inc edx
    lea eax, [ecx+2*eax-30h]
@@: movzx ecx, BYTE PTR [edx]
    cmp BYTE PTR [edx], 30h
    jns again
    add eax, [esp+4]
    xor eax, [esp+4]
    retn 4
atodw ENDP
OPTION PROLOGUE:DefaultOption
OPTION EPILOGUE:DefaultOption


Перевод строки из шестнадцатеричных цифр в число типа DWORD
с использованием MMX


автор bitRAKE, взято здесь
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
StrHex2bin PROC
    _CONST SEGMENT
        lpString db "89aBcDeF"
    _CONST ENDS
    movq mm0,QWORD PTR [lpString]
 
    psubusb  mm0,mxc(<30>) ; "0" = 0
    movq mm1,mm0
 
    pcmpgtb mm1,mxc(<09>) ; letter?
    pand mm1,mxc(<07>)
 
    psubusb mm0,mm1 ; fix letters
 
    movq mm1,mm0         ; 0F0E0D0C0B0A0908
    pand mm0,mxc(<0F00>) ; 0E000C000A000800
    pand mm1,mxc(<000F>) ; 000F000D000B0009
    psrlq mm0,8          ; 000E000C000A0008
 
    packuswb mm1,mm1     ; 0F0D0B09
    packuswb mm0,mm0     ; 0E0C0A08
    psllq mm1,4          ; F0D0B090
    por mm0,mm1          ; FEDCBA98
    movd eax,mm0         ; FEDCBA98
 
    bswap eax            ; 89ABCDEF
    ret
StrHex2bin ENDP


Перевод числа типа DWORD в строку из двоичных цифр
с удалением лидирующих нулей


Автор Sloat, взято здесь
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
buff db 128 dup (?)
number equ 45432
....
 
 
mov eax, number
or eax, eax
jz _exit
bsr edx, eax
mov ecx, 31
sub ecx, edx
shl eax, cl
lea edx, buff
@@:
    shl eax, 1
    setc BYTE PTR[edx]
    add BYTE PTR[edx],"0"
    inc edx
    inc ecx
cmp ecx, 32
jl @B
_exit:


Перевод числа типа QWORD в строку из десятичных цифр
с использованием MMX


Автор The Svin, взято здесь
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
.586
.MMX
.model flat,stdcall
option casemap:none
 
.data
ALIGN 8
mmxb0F dq 0F0F0F0F0F0F0F0Fh
mmxb30 dq 3030303030303030h
 
.code
Q2Ammxf proc uses edi lpBuffer,lpNumber
    mov ecx,lpNumber
    mov edi,lpBuffer
    mov eax,[ecx]
    mov edx,[ecx+4]
 
D05H    equ 045639182h
D05L    equ 044F40000h
D04H    equ 03782DACEh
D04L    equ 09D900000h
D01H    equ 00DE0B6B3h
D01L    equ 0A7640000h
QtoA:
    sub esp,12
    xor ecx,ecx
    sub eax,D01L
    sbb edx,D01H
    jb    @@a01f
    sub eax,D04L
    sbb edx,D04H
    jb    @@a05
    mov cl,05h
    sub eax,D05L
    sbb edx,D05H
    jb    @@a05
    mov cl,15h
    sub eax,D05L
    sbb edx,D05H
    jae  @@l01
    mov cl,10h
@@a05:    add eax,D05L
    adc edx,D05H
@@l01:  inc ecx
    sub eax,D01L
    sbb edx,D01H
    jae  @@l01
    dec ecx
@@a01f:    add eax,D01L
    adc edx,D01H
 
    push edx
    push eax
    fild qword ptr [esp]
    fbstp [esp]
    mov [esp+9],ecx
    mov edx,2
    cmp dword ptr [esp+8],0
    jne @@dg16
    cmp dword ptr [esp+4],1
    sbb edx,1
@@dg16:    bsr ecx,[esp+edx*4]
    je @@zero
    shr ecx,3
    lea eax,[ecx+4*edx]
    lea edx,[eax+eax]
    cmp byte ptr [esp][eax],10h
    sbb edx,-1
 
    movq mm(7),mmxb0F
    movq mm(6),mmxb30
    movq mm(0),[esp]
    movq mm(4),[esp+8]
    movq mm(1),mm(0)
    psrlq mm(1),4
    pand mm(0),mm(7)
    por    mm(0),mm(6)
    pand mm(1),mm(7)
    por mm(1),mm(6)
    movq mm(2),mm(0)
    punpcklbw mm(0),mm(1)
    movq [esp],mm(0)
    punpckhbw mm(2),mm(1)
    movq [esp+8],mm(2)
    movq mm(5),mm(4)
    psrlq mm(5),4
    pand mm(4),mm(7)
    por mm(4),mm(6)
    pand mm(5),mm(7)
    por mm(5),mm(6)
    punpcklbw mm(4),mm(5)
    movd [esp+16],mm(4)
    emms
@@lpS:    mov eax,[esp+edx-3]
    bswap eax
    mov [edi],eax
    add edi,4
    sub edx,4
    jns  @@lpS
    mov byte ptr [edi+edx][1],0
    add esp,20
    ret
@@zero:    mov dword ptr [edi],'0'
    add esp,20
    ret
Q2Ammxf endp
    end


Ещё один вариант перевода числа типа QWORD в строку из десятичных цифр
с использованием MMX


Автор The Svin, взято здесь
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
.586
.MMX
.model flat,stdcall
option casemap:none
.data
ALIGN 8
mmxb0F dq 0F0F0F0F0F0F0F0Fh
mmxb30 dq 3030303030303030h
num dq 12345678901234567890
.data?
buffer db 24 dup (?)
.code
 
    mov edx,dword ptr num+4
    mov eax,dword ptr num
    mov edi, offset buffer
 
N19H EQU 00DE0B6B3H
N19L EQU 0A7640000H
N20H EQU 08AC72304H
N20L EQU 089E80000H
 
Q2Ammx proc uses edi lpBuffer, lpNumber
    mov ecx,lpNumber
    mov eax,[ecx]
    mov edx,[ecx+4]
    mov edi,lpBuffer
    sub esp,12
    xor ecx,ecx
    cmp edx,N19H
    jb     @@nm18
    jne   @@cp20
    cmp eax,N19L
    jb     @@nm18
@@cp20:    cmp edx,N20H
    jb     @@do19
    jne   @@do20
    cmp eax,N20L
    jb     @@do19
@@do20:    mov cl,10h-1
    sub  eax,N20L
    sbb edx,N20H
@@lp19:    inc ecx
@@do19:    sub eax,N19L
    sbb edx,N19H
    jae  @@lp19
    add eax,N19L
    adc edx,N19H
@@nm18:    push edx
    push eax
    fild qword ptr [esp]
    fbstp [esp]
    mov [esp+9],ecx
 
    mov edx,2
    cmp dword ptr [esp+8],0
    jne @@dg16
    cmp dword ptr [esp+4],1
    sbb edx,1
@@dg16:    bsr ecx,[esp+edx*4]
    je @@zero
    shr ecx,3
    lea eax,[ecx+4*edx]
    lea edx,[eax+eax]
    cmp byte ptr [esp+eax],10h
    sbb edx,-1
 
    movq mm(7),mmxb0F
    movq mm(6),mmxb30
    movq mm(0),[esp]
    movq mm(4),[esp+8]
    movq mm(1),mm(0)
    psrlq mm(1),4
    pand mm(0),mm(7)
    por    mm(0),mm(6)
    pand mm(1),mm(7)
    por mm(1),mm(6)
    movq mm(2),mm(0)
    punpcklbw mm(0),mm(1)
    movq [esp],mm(0)
    punpckhbw mm(2),mm(1)
    movq [esp+8],mm(2)
    movq mm(5),mm(4)
    psrlq mm(5),4
    pand mm(4),mm(7)
    por mm(4),mm(6)
    pand mm(5),mm(7)
    por mm(5),mm(6)
    punpcklbw mm(4),mm(5)
    movd [esp+16],mm(4)
    emms
@@lpS:    mov eax,[esp+edx-3]
    bswap eax
    mov [edi],eax
    add edi,4
    sub edx,4
    jns  @@lpS
    mov byte ptr [edi+edx][1],0
    add esp,20
    ret
@@zero:    mov dword ptr [edi],'0'
    add esp,20
    ret
Q2Ammx endp
 
    end


Перевод строки из шестнадцатеричных цифр в число типа DWORD

автор The Svin, взято здесь
Используются только строчные буквы (ABCDEF)
Модуль написан в формате masm32.inc. При использовании описывайте прототип как
htodwc proto :DWORD
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
; #########################################################################
 
    ; --------------------------------------
    ; This procedure was written by Svin
    ; --------------------------------------
 
      .386
      .model flat, stdcall  ; 32 bit memory model
      option casemap :none  ; case sensitive
 
    .code
 
; #########################################################################
OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
 
htodwc         proc FORCENOFRAME
 
 
    push ebx
    xor eax,eax
    mov ebx,[esp+8]
    xor edx,edx
@@: mov dl,[ebx] ;1
    shl eax,4        ;0
    cmp dl,41h   ;1
    inc ebx         ;0
    sbb cl,cl       ;1
    sub dl,'A'-0Ah ;0
    and cl,7        ;1
    add dl,cl      ;1
    cmp byte ptr [ebx],0 ;0
    lea eax,[eax][edx] ;1
    jne @B  ;1
    pop ebx
    ret 4
htodwc         endp
 
OPTION PROLOGUE:DEFAULTOPTION
OPTION EPILOGUE:DEFAULTOPTION
 
; #########################################################################
 
end
2
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
27.07.2014, 06:28  [ТС] #50
Вставка подстроки с адресом lpPattern в строку с адресом lpSource в позицию указанную startpos

Автор eko, взято здесь
оптимизированный вариант процедуры написанной hutch (Instring находится в библиотеке m32lib)
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
InString  proc uses ebx esi edi startpos:DWORD,lpSource:DWORD,lpPattern:DWORD
 
   invoke StrLen,lpPattern ; pattern length
   push eax          ;because of strlen
   invoke StrLen,lpSource ;  mov sLen, eax           ; source length
         mov ebx,startpos
         pop edx
 
         cmp ebx,1
         jb @ER
         cmp eax,ebx
         jg @F
   @ER:         mov eax, -2
         jmp isOut               ; exit if startpos is past end OR exit if startpos not 1 or greater
   @@:     dec ebx ;   ; correct from 1 to 0 based index
          sub eax,edx
          jg @F
         mov eax, -1
         jmp isOut               ; exit if pattern longer than source
   @@:         inc eax
         mov esi, lpSource
         mov sLen, eax
  ; ----------------
  ; setup loop code
  ; ----------------
   add esi,eax
   neg eax                ; invert sign
   mov edi, lpPattern
   dec edx                ;to save dec later
   mov cl,[edi]           ; get 1st char in pattern
   push edx
   add eax,ebx            ;add startpos
;*****************************************************
;the loop
;*****************************************************
Scan_Loop:    cmp cl, [esi+eax]
    je Pre_Match
@set_counter:    inc eax
    jnz Scan_Loop ; the loop set eax to set if not found
    jmp @loopout
 
 Pre_Match:    mov edx,[esp] ; we have done dec edx before the push
    lea ebx,[esi+eax] ;
    mov ch, [edi+edx] ; get the last char of the pattern in here .
 Test_Match:    cmp ch, [ebx+edx] ; stall . but only one time per prematch..
    jne @set_counter            ; jump back on mismatc;
    dec edx
    mov ch, [edi+edx]
    jnz Test_Match
 
    add eax,sLen
    inc eax
 
@loopout:    add esp,4 ; instead of this . make local var and save edx there
    isOut:
 
  ret
InString endp


Вставка подстроки String2 в строку String1 в позицию указанную index,
с использованием MMX


Автор The Svin, взято здесь
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
szInsertString2 proc uses ebx esi edi lpString1,lpString2,len1,len2,index
 
    mov esi,lpString1 ; esi = lpString1
    mov ebx,len1       ; ebx = len1
 
    mov eax,index     ;eax = index
    mov ecx,len2       ;ecx = len2
 
    add esi,eax
    mov edi,ecx
 
    mov edx,esi
    and esi,not 7
 
    and edx,7
    lea edi,[esi+ecx]
 
    neg eax
    push ecx
 
    lea ecx,[eax+ebx+7]
    neg eax
 
    sub ecx,edx
    shr ecx,3
 
@@: movq mm(0),[esi+ecx*8]
    movq [edi+ecx*8],mm(0)
    dec ecx
    jns @B
    emms
    mov edi,lpString1
    pop ecx
 
    mov edx,ecx
    mov esi,lpString2
 
    add edi,eax ;edi = String1+index
    and ecx,not 3
    je @bytes
    add edi,ecx
    add esi,ecx
    neg ecx
@@: mov eax,[esi+ecx]
    mov [edi+ecx],eax
    add ecx,4
    jne @B
 
@bytes:    and edx,3
    je @exit
    add esi,edx
    add edi,edx
    neg edx
@@: mov al,[esi+edx]
    mov [edi+edx],al
    inc edx
    jne @B
@exit:    ret
 
szInsertString2 endp


Вычисление XY с использыванием FPU

Автор The Svin, взято здесь
Assembler
1
2
3
4
5
6
7
8
9
10
11
    fld Y
    fld X
    fyl2x
    fld1
    fld st(1)
    fprem
    f2xm1
    fadd
    fscale
    fxch st(1)
    fstp st;st0 = X^Y
2
Charles Kludge
Клюг
7641 / 3156 / 382
Регистрация: 03.05.2011
Сообщений: 8,382
27.08.2014, 19:23 #51
Ввод/вывод шестнадцатеричных чисел из/в DWORD ДОСъ/winconsole(x86)
some kludges inside.
ДОСъ:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
; FASM
; маленький тестовый кусочек
        org 100h
again:      mov dx, req ; вывод запроса
        mov ah, 9h
        int 21h
        mov ecx, 8  ; макс. кол-во вводимых символов.
        call    _cgetx  ; ввод значения
        jc  quit    ; был нажат <ESC>
        push    eax
        call    crlf    ; переход на новую строку
        call    _cputx  ; выводим значение из EAX
        call    crlf    ; переход на новую строку
        pop eax
        call    _cputux ; выводим значение из EAX
        call    crlf    ; переход на новую строку
        jmp again   ; пока не надоест ;)
quit:       ret     ; исход в ДОСъ
;
; вывод 16-ричного dword, БЕЗ ЛИДИРУЮЩИХ нулей
;
_cputux:    push    eax
        push    ecx
        push    edx
        xor ecx, ecx
        mov dl, 1
;
; some magic
;
    bsr ecx, eax    ; ecx = номер первого бита-единички в eax 
    jz  @F      ; eax = 0, отваливаем
    shr cl, 2       ; делим CL на кол-во битов в ниббле(полубайте)
    inc ecx
    mov edx, ecx    ; EDX = кол-во символов для вывода
    lea     ecx, [ecx*4-20h]
    neg ecx     ; ECX = кол-во бит, на которое надо сдвинуть EAX
    shl eax, cl
;;; опционально - принудительный вывод '0', если первая цифра - 'A'-'F'
; есть маленькая багофича :)
    bt  eax, 1Fh
    jnc @F
    inc dl
    shr eax, 4
;;;
@@:     movzx   ecx, dl
        jmp putx
;
; вывод 16-ричного dword, включая лидирующие нули
;
_cputx:     push    eax
        push    ecx
        push    edx
        mov ecx, 8      ; кол-во цифр
putx:       mov edx, eax
@@:     shld    eax,edx,4
        shl edx,4
;
; ещё немного магии - пребразование 00h - 0Fh в символ '0' - 'F' без переходов
 
        and al, 0Fh     ; только младший ниббл
        add Al, 90h     ; в AL от 90h до 09Fh.
        daa         ; теперь в AL 90h - 99h(флаг CY сброшен) или 00h - 06h (CY взведён).
        adc Al, 40h     ; в AL 0D0h - 0D9h или 41h - 46h.
        daa         ; Oops! В AL теперь "0"-"9" или "A"-"F" :)
        int 29h
        loop    @B
        pop edx
        pop ecx
        pop eax
        ret
;
; get HEX dword from console
;
_cgetx:         pushad
        xor edx, edx
        mov ecx, 8      ; кол-во знаков, хотя бы 1
        mov ebx, ecx
; можно вместо этой строки передавать кол-во в кач-ве параметра:
;   mov eax,    4   ; ввести word 
;   call    _cgetx
;а здесь прописать
;   mov ecx, eax    
@l:
        call    getch       ; читаем сивол с консоли
        or  al,al           ; а символ ли это?
        jz  @l      ; нет, что-то из функциональных клавиш
        cmp al, 1Bh     ; <ESC>?
        jz  cancel      ; да, отваливем
        cmp al, 0Dh         ; <Enter>?
        jz  done        ; да, завершение ввода
        cmp al, 08h         ; <BkSp>?
        jnz @F      ; нет
        call    rubout      ; да, удаляем последнюю цифру
        jmp @l
; пребразование символа из AL в число 0-F без переходов и сравнений
; юзверь просто не сможет ввести нечто иное - при нажатии 'G'/'g' получится '0'и т.д.
; получилось довольно прикольно
@@:
        aam 10h
        rcr ah,2
        bt  ax,8
        sbb al,0
        aad
        and al,0fh
; добавляем AL к результату
        shl edx, 4
        movzx   eax,al          ; убираем из eax лишнее
        or  edx, eax
;
        and al, 0Fh     ; только младший ниббл
        add Al, 90h     ; в AL от 90h до 09Fh.
        daa         ; теперь в AL 90h - 99h(флаг CY сброшен) или 00h - 06h (CY взведён).
        adc Al, 40h     ; в AL 0D0h - 0D9h или 41h - 46h.
        daa         ; Oops! В AL теперь "0"-"9" или "A"-"F" :)
            int 29h
            loop    @l
done:       mov eax, edx        ; результат -> в EAX
        clc         ; сбрасываем CY, отмены ввода не было
        jmp ex      ; выходим
 
cancel:     xor eax, eax
        dec eax             ; EAX = -1
        stc         ; взводим CY, отмена ввода 
ex:     mov [ss:esp+1Ch],eax; заносим EAX на его место в стеке
        popad
        ret              ;  bye
; KEYBOARD - GET KEYSTROKE
getch:      xor     ax, ax
        int 16h
        ret
; вывод <CR><LF>(новая строка) на консоль
crlf:       push    ax
        mov ax, 0A0Dh
        int 29h
        xchg    al,ah
        int 29h
        pop ax
        ret
; забой
rubout:     cmp ebx,ecx
        jbe @F
        shr edx, 4  ; EDX = EDX/16
        call    bksp    ; удаляем последний введённый символ с консоли
        inc ecx
@@:     ret
; курсор взад на 1 позицию
; выводим <BkSp><Space><BkSp> (<возврат на шаг>(курсор влево)<пробел><возврат на шаг>)
; т.е. стираем символ перед курсором
bksp:       mov ax, 0820h
        call    @F
        call    @F
@@:     xchg    ah,al
        int 29h
        ret
 
req:        db  '<Esc> or enter number:$'
winconsole(x86) - почти то же самое.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
; FASM 
format PE console 4.0
 
include 'win32ax.inc'
; кусочек для теста
again:  cinvoke crt_printf, tpts, req
;   mov eax, 4          ; желаемое кол-во цифр
    call    _cgetx
    jc  quit        
    cinvoke crt_printf, tptd, eax, eax
    jmp again
quit:   invoke  crt_exit,0
;
; get HEX dword from console
;
_cgetx:         pushad
        xor edx, edx
        mov ecx, 8      ; кол-во знаков, хотя бы 1
        mov ebx, ecx
; можно вместо этой строки передавать кол-во в кач-ве параметра:
;   mov eax,    4   ; ввести word 
;   call    _cgetx
;а здесь прописать
;   mov ecx, eax    
@l:
        call    getch       ; читаем сивол с консоли
        or  al,al           ; а символ ли это?
        jz  @l      ; нет, что-то из функциональных клавиш
        cmp al, 1Bh     ; <ESC>?
        jz  cancel      ; да, отваливем
        cmp al, 0Dh         ; <Enter>?
        jz  done        ; да, завершение ввода
        cmp al, 08h         ; <BkSp>?
        jnz @F      ; нет
        call    rubout      ; да, удаляем последнюю цифру
        jmp @l
; пребразование символа из AL в число 0-F без переходов и сравнений
; юзверь просто не сможет ввести нечто иное - при нажатии 'G'/'g' получится '0'и т.д.
; получилось довольно прикольно
@@:
        aam 10h
        rcr ah,2
        bt  ax,8
        sbb al,0
        aad
        and al,0fh
; добавляем AL к результату
        shl edx, 4
        movzx   eax,al          ; убираем из eax лишнее
        or  edx, eax
;
        and al, 0Fh     ; только младший ниббл
        add Al, 90h     ; в AL от 90h до 09Fh.
        daa         ; теперь в AL 90h - 99h(флаг CY сброшен) или 00h - 06h (CY взведён).
        adc Al, 40h     ; в AL 0D0h - 0D9h или 41h - 46h.
        daa         ; Oops! В AL теперь "0"-"9" или "A"-"F" :)
@putc:      push    ecx     ;; ECX, EDX загаживаются безвозмездно
        push    edx             ;;
        cinvoke crt_putchar, eax
        pop edx
        pop ecx
            loop    @l
done:       mov eax, edx        ; результат -> в EAX
        clc         ; сбрасываем CY, отмены ввода не было
        jmp ex      ; выходим
 
cancel:     xor eax, eax
        dec eax             ; EAX = -1
        stc         ; взводим CY, отмена ввода 
ex:     mov [ss:esp+1Ch],eax; заносим EAX на его место в стеке
        popad
        ret              ;  bye
; KEYBOARD - GET KEYSTROKE
getch:      push    ecx     ; ECX, EDX загаживаются безвозмездно
        push    edx
        invoke  crt__getch  ; ввод символа
        pop edx
        pop ecx
        ret
; забой
rubout:     cmp ebx,ecx
        jbe @F
        shr edx, 4  ; EDX = EDX/16
        call    bksp    ; удаляем последний введённый символ с консоли
        inc ecx
@@:     ret
; курсор взад на 1 позицию
; выводим <BkSp><Space><BkSp> (<возврат на шаг>(курсор влево)<пробел><возврат на шаг>)
; т.е. стираем символ перед курсором
bksp:       push    ecx
        push    edx
        cinvoke crt_putchar, 08     ; Backspace
        cinvoke crt_putchar, 20h        ; Space
        cinvoke crt_putchar, 08     ; Backspace
        pop edx
        pop ecx
        ret
 
req     db  0dh,0ah,'<Esc> or enter number:',0
tpts        db  '%s',0
tptd        db  0dh,0ah,'Got %08X %lX',0
; import data in the same section
 data import
 
 library msvcrt,'MSVCRT.DLL'
 
 import msvcrt,\
    crt_printf,'printf',\
        crt_exit,'exit',\
    crt__getch, '_getch',\
    crt_putchar, 'putchar'
 
end data
3
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
15.09.2014, 12:26  [ТС] #52
MASM
MASM — Macro Assembler — "Ассемблер с поддержкой Макросов".
MASM — продукт компании Microsoft для создания MS-DOS, а позднее и для Windows 9x/NT. После выхода версии 6.13 MASM на некоторое время затормозил в развитии, но потом здравый смысл взял вверх и последняя версия поддерживает unicode, все SSE/SSEII/SEEIII расширения (объявляемые двумя директивами .686/.XMM), а также архитектуру AMD x86-64. Платформа Intel IA64 не поддерживается, но Microsoft поставляет Intel-ассемблер IAS.EXE.
При помощи макросов MASM покрывает своими возможностями широкий круг задач: повторения однотипных операций с параметризацией (шаблоны), циклические макросы, условное ассемблирование и так далее, по сравнению с которым препроцессор языка Си выглядит жалкой подделкой. Имеется примитивная поддержка основных парадигм ООП хотя ассемблер и ООП концептуально несовместимы. Многие пишут даже без макросов на чистом ассемблере, считая свой путь идеологически наиболее правильным.
Сначала MASM распространялся в виде самостоятельного пакета, позже он был включен в состав DDK, которое вплоть до Windows 2000 DDK раздавалось бесплатно, а сейчас доступно подписчикам MSDN. Вполне полноценное DDK (с ассемблером) для Windows Server 2003 входит в Kernel-Mode Driver Framework, а сам транслятор MASM'а еще и в Visual Studio Express.
MASM'у посвящено множество книг, что упрощает процесс обучения, а в сети можно найти кучу исходных текстов ассемблерных программ и библиотек, освобождающих программиста от необходимости изобретать всё самому заново. Также MASM является выходным языков для многих дизассемблеров (Sourcer, IDA Pro). Все это делает MASM транслятором номером один в программировании под Windows/Intel.
Поддерживаются два выходных формата: 16/32 Microsoft OMF и (16)/32/64 COFF, что позволяет транслировать 16/32-разрядные программы под MS-DOS, работающие в реальном и защищенном режиме, 16-разрядные приложения и драйвера для Windows 3.x, 32-разрядные приложения и драйвера для Windows 9x/NT, а также 64-разрядные приложения и драйвера для Windows NT 64-bit Edition. Для создания бинарных файлов потребуется линкер, который умеет это делать (например, ulink от Юрия Харона). Кстати говоря, последние версии штатного Microsoft Linker'а, входящие в SDK и DDK, утратили способность собирать 16-разрядные файлы под MS-DOS/Windows 3.x и приходится возвращаться к старой версии, которая лежит в папке NTDDK\win_me\bin16.
MASM генерирует отладочную информацию в формате CodeView, которую Microsoft Linker может преобразовывать в PDB-формат, хоть и не документированный, но поддерживаемый библиотекой dbghelp.dll, позволяющей сторонним разработчикам "переваривать" отладочную информацию, поэтому файлы, оттранслированные MASM'ом, можно отлаживать в Soft-Ice, дизассемблировать в IDA Pro и прочих продуктах подобного типа.
Главный недостаток MASM'а — большое количество ошибок. Стоит открыть Knowledge Base, посмотреть на список официально подтвержденных ошибок. Особенно много ошибок в штатной библиотеке. Вот только несколько примеров: dwtoa и atodw_ex не понимают знака и по скорости очень тормозят, хотя в документации написано: "A high speed ascii decimal string to DWORD conversion for applications that require high speed streaming of conversion data"; ucFind не находит в строке подстроку, если длина подстроки равна 1 символу; функции BMHBinsearch и SBMBinSearch (поиск алгоритмом Бойера-Мура) реализованы с ошибками; некоторые функции обрушивают программу (если передать ustr2dw строку длиннее пяти байт - программа падает).
Другой минус — отсутствие поддержки некоторых инструкций и режимов адресации процессора, например, невозможно сделать jmp far seg:offset, а попытка создания смешанного 16/32 разрядного кода — настоящий кошмар, который приходится разгребать руками и всячески извращаться, преодолевая сопротивление "менталитета" транслятора.
MASM — типичный коммерческий продукт с закрытыми исходными текстами. Microsoft интенсивно продвигает высокоуровневое программирование, отказываясь от ассемблера везде, где это только возможно, поэтому не исключено, что через несколько лет MASM прекратит свое существование...
Тем не менее, несмотря на все эти недостатки, MASM остается самым популярным профессиональным транслятором ассемблера при программировании под Windows NT. MASM (Macro Assembler) — стандарт де-факто при программировании под Windows 9x/NT
Ссылки
  • Kernel-Mode Driver Framework: Среда разработки драйверов, бесплатно распространяемая Microsoft, в состав которой входит транслятор MASM'а со всеми необходимыми утилитами: http://www.microsoft.com/whdc/driver/wdf/KMDF_pkg.mspx;
  • Visual Studio Express Edition: Урезанная редакция Visual Studio, распространяемая на бесплатной основе с транслятором MASM'а и прочими необходимыми утилитами у себя внутри: http://msdn.microsoft.com/vstudio/express;
  • MASM docs: Официальная документация по MASM'у на MSDN (на английском языке): http://msdn2.microsoft.com/en-us/library/ms300951.aspx;
MASM32 для Windows
Проект создан в 1998 году Стивом Хатчессоном, который собрал последние версии транслятора MASM'а, линкер от Microsoft, включаемые файлы, библиотеки, обширную документацию, статьи разных авторов, посвященные ассемблеру, и даже простенькую IDE в один дистрибутив, известный под "пакетом хатча" (hutch), бесплатно раздаваемый всем желающим на вполне лицензионной основе, вполне удобный комплект инструментов для программирования под Windows на ассемблере.
Ссылки
  • Последние версии MASM32'а плюс куча разных утилит, документации и того, что может понадобиться при программировании под Windows: http://www.movsd.com/ и http://masm32.com/
TASM
TASM — Turbo Assembler — "Скоростной Ассемблер".
Самый популярный транслятор ассемблера времен MS-DOS, созданный фирмой Borland, и полностью совместимый с MASM'ом вплоть до версий 6.x и поддерживающий свой собственный режим IDEAL с большим количеством улучшений и расширений.
Удобство программирования, скромные системные требования и высокая скорость трансляции обеспечивали TASM'у лидерство на протяжении всего существования MS-DOS. Но с появлением Windows популярность TASM'а стала таять буквально на глазах. Не сумев (или не захотев) добиться совместимости с заголовочными файлами и библиотеками, входящими в комплект SDK/DDK, фирма Borland решила поставлять свой собственный порт. Штатный линкер tlink/tlink32 не поддерживает возможности создания драйверов, а формат выходных файлов (Microsoft OMF, IBM OMF, Phar Lap), не поддерживается текущими версиями линкера от Microsoft (впрочем, 16-битные версии это умеют). Формат отладочной информации несовместим с CodeView и реально поддерживается только TurboDebugger'ом и soft-ice.
Последней версией транслятора стал TASM 5.0, поддерживающий команды вплоть до 80486 процессора. Отдельно был выпущен патч, обновляющий TASM до версии 5.3 и поднимающий его вплоть до Pentium MMX, однако команды Pentium II, например, как SYSENTER не работают. Поддержка уникода отсутствует.
В настоящее время Borland прекратила распространение своего ассемблера. Существует пакет TASM 5+, включающий в себя транслятор, линкер, какое-то подобие документации, несколько заголовочных файлов под Windows и пару демонстрационных примеров. Существует TASM32 фирмы Squak Valley Software — совершенно независимый кроссассемблер, ориентированный на процессоры 6502,6800/6801/68HC11, 6805, TMS32010, TMS320C25, TMS7000, 8048, 8051,8080/8085, Z80, 8096/80C196KC.
Для разработки прикладных приложений под Windows 16/32 и MS-DOS TASM подходит, особенно если вы уже имеете опыт работы с ним и некоторые собственные наработки (библиотеки, макросы), конвертировать TASM-программы под MASM — весьма проблематично.
Ссылки
  • TASM 5+ by Borland Corp.: Неофициальный пакет, содержащий последние версии транслятора TASM со всеми доступными патчами, документаций и прочими утилитами
  • еще одна ссылка на скачивание тасма

LzAsm
LzAsm — Lazy Assembler — "Ассемблер для ленивых" — автор - Половников Степан — реинкарнация TASMа с поддержкой новых команд процессора. Lazy Assembler совместим с режимом IDEAL TASM и поддерживающим команды из наборов MMX, SSE, SSEII, SSEIII, 3DNow!Pro.
Ссылки
FASM
FASM (расшифровывается как Flat Assembler — Ассемблер Плоского Режима) — крайне необычный транслятор с экзотичными возможностями. FASM — ассемблер с предельно упрощенным синтаксисом (никаких offset'ов и прочих захламляющих листинг директив), полной поддержкой всех процессорных команд (в том числе и jmp 0007:00000000), качественным кодогенератором, мощным макропроцессором и гибкой системой управления за форматом выходных файлов.
3
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
15.09.2014, 12:31  [ТС] #53
FASM распространяется в исходных текстах на бесплатной основе и к настоящему моменту перенесен на MS-DOS, Windows 9x/NT, Linux, BSD, поддерживает уникод и все x86-процессоры вплоть до Pentium-4 с наборами мультимедийных инструкций MMX, SSE, SSEII, SSEIII, AMD 3DNow!, а так же платформу AMD x86-64, позволяя генерировать не только Microsoft coff, но и готовые bin, mz, pe и elf файлы. FASM позволяет обходиться без линкера, однако при этом раскладку секций в PE-файле и таблицу импорта приходится создавать "вручную" с помощью специальных директив ассемблера, что выглядит очень заманчиво, но на практике все же намного удобнее сгенерировать coff и скомпоновать его с модулями, написанными на языках высокого уровня.
Макроязык FASM'а мощный, позволяет писать программы на себе самом без единой ассемблерной строки. Ни на что не похожий синтаксис FASM'а напрягает, заставляя вгрызаться в плохо структурированную документацию и небольшое количество демонстрационных примеров, поставляемых вместе с транслятором. Категорическая несовместимость с MASM'ом чрезвычайно затрудняет разработку Windows-драйверов (в большинстве своем создаваемых на основе примеров из DDK). Прикладным задачам в свою очередь требуется SDK и желательно первой свежести. "Математические" задачи, перемножающие матрицы, вычисляющие координаты пересечения кривых в N-мерном пространстве или трансформирующие графику легко пишутся на FASM'е, поскольку не привязаны к конкретной операционной системе, никаких API-функций они не вызывают и вообще не лезут туда, где можно обойтись Си/Си++.
Ссылки
  • Страница FASM'а откуда можно скачать сам транслятор, документацию на английском языке и сопроводительные примеры к нему: http://flatassembler.net
  • Небольшая подборка статей, посвященная FASM'у (на русском языке): http://www.softoplanet.ru/lofiversion/index.php/t4404.html
NASM
NASM — Netwide Assembler — "Расширенный Ассемблер" авторы: Peter Anvin, Simon Tatham, Julian Hall. Имеет MASM-подобный синтаксис, мощную макросистему (несовместимую с MASM'ом и ничего не знающую об union'ах и других полезных вещей), поддерживает всю линейку x86-процессоров вплоть до IA64 в x86-режиме, богатство выходных файлов (bin, aout, aoutb, coff, elf, as86, obj, win32, rdf, ieee), генерация отладочной информации в форматах Borland, STABS и DWARF2 вкупе с портами под MS-DOS, Windows, Linux и BSD. Количество ошибок в трансляторе довольно велико, причем в отличии от работающих продуктов (MASM/TASM) при "хитрых ошибках" NASM не падает, а генерирует ошибочный (по структуре) объектный файл. В последней версии NASM'а, в зависимости от значения ключа -On, код может сгенерироваться в двух или более экземплярах или может пропасть весь экспорт (pubdef'ы).
Минусы NASM'а
  • отсутствие поддержки формата отладочной информации CodeView
  • некоторые странности синтаксиса
. В частности, команда "push 1" не оптимизируется и транслятор умышленно оставляет место для 32-разрядного операнда. Если же мы хотим получить "короткий" вариант, размер операнда необходимо специфицировать явно: "push byte 1" или использовать опцию "-On" для автоматической оптимизации.
Также необходимо принудительно указывать длину переходов short или near, иначе можно нарваться на ругательство "short jump out of range". Существует возможность настроить транслятор на генерацию near-переходов по умолчанию.
NASM не помнит типы объявляемых переменных и не имеет нормальной поддержки структур.
Из мелких недочетов можно называть невозможность автоматического генерации короткого варианта инструкции "push imm8" и отсутствие контроля за соответствием транслируемых инструкций типу указанного процессора (команда "cpuid" под ".486" ассемблируется вполне нормально, а ведь не должна).
Непосредственная трансляция примеров из SDK/DDK под NASM'ом невозможна, так что разрабатывать на нем драйвера под Windows может только очень крутой поклонник или извращенец. NASM - один из лучших ассемблеров под Linux/BSD, а вот под Windows его позиции уже не так сильны (в основном из-за неполной совместимости с MASM'ом).
NASM — ассемблер под Linux/BSD с Intel-синтаксисом.
Ссылки
YASM
YASM — "Yet another assembler", "Yes, it's an assembler", "Your favorite assembler" — усовершенствованный вариант NASM'а, авторы: Peter Johnson, Michael Urman.
Когда развитие NASM'а затормозилось, его исходные тексты легли в основу нового транслятора YASM. Основные отличительные черты YASM'а от его предшественника: поддержка платформы AMD x86-64, большое количество исправленных ошибок, оптимизированный парсер, переваривающий синтаксис как NASM, так и GAS, более полная поддержка COFF (DJGPP) и Win32 obj выходных файлов, генерация отладочной информации в формате CodeView, интернационализация (выполненная через GNU-библиотеку gettext), и прочие мелкие улучшения, которых вполне достаточно, чтобы потеснить NASM особенно в мире UNIX-подобных систем, где GAS-синтаксис по-прежнему играет ведущую роль.
Под Windows же YASM не имеет никаких ощутимых преимуществ перед MASM'ом за исключением того, что поддерживает возможность генерации двоичных файлов, особенно удобных для создания shell-кода, но бесполезных для разработчика драйверов.
Ссылки
  • Страница YASM'а, откуда можно скачать сам транслятор, документацию на английском языке и сопроводительные примеры к нему: http://www.tortall.net/projects/yasm
HLA
HLA (High Level Assembly Language) — автор Randall Hyde, высокоуровневый ассемблер, на любителя. "Академический" проект очень высокоуровневого ассемблера.
HLA - это гибрид, работающий как очень мощный препроцессор для нескольких ассемблеров. частью целевой аудитории HLA являются студенты, которые должны изучить ассемблер и сделать на нем что-нибудь полезное, используя то ограниченное время, которое есть у них в университете.
HLA ассемблер, включающий в себя множество функций свойственных для высокоуровневых языков (таких как C, C++ и Java), которые позволяют быстро освоить основы ассемблера. HLA позволяет писать настоящий низкоуровневый код со всеми преимуществами языков программирования высокого уровня.
ССылки
  • HLA транслятор, документация на английском языке и огромное количество готовых примеров http://webster.cs.ucr.edu
GoAsm
Ассемблер для процессоров семейства x86, создан Jeremy Gordon для написания программ для операционных систем семейства Windows, способен создавать 32- и 64-битных версий, а также программы с поддержкой Unicode. GoAsm является проприетарным ПО и распространяется в бинарном формате.
Цель разработки
GoAsm создавался с целью создать компилятор с простым и ясным синтаксисом, производящий как можно более компактный код, скромными потребностями для обработки скриптов и возможностью добавления расширений.
Особенности GoAsm
  • GoAsm не создаёт 16-разрядный код и способен работать только в model flat (без сегментов), благодаря этому синтаксис очень прост.
  • В качестве формата выходных данных используется COFF Portable Executable format, и для создания исполняемых файлов необходимо использовать дополнительный компоновщик (например — GoLink или ALINK) и компилятор ресурсов (GoRC).
  • GoAsm способен файлы в формате Unicode (UTF-16 или UTF-8).
Синтаксис
Несмотря на то что используется Intel-синтаксис, синтаксис GoAsm несовместим ни с одним из существующих компиляторов.
GoAsm использует препроцессор сходный по синтаксису с препроцессором языка программирования C.
В GoAsm необходимо использовать квадратные скобки для чтения и записи памяти.
Поддерживаемые наборы инструкций
GoAsm поддерживает все стандартные инструкции (за исключением использующихся только в 16-разрядных программах). Инструкции x87 для работы с числами с плавающей точкой. Инструкции и синтаксис для работы с MMX, SSE, SSE2, 3DNow!, 3DNow!, FPU, MMX, XMM, SSE, SSE2 и 3DNow!
Другие инструменты разработанные Jeremy Gordon
  • GoAsm — ассемблер
  • GoLink — компоновщик
  • GoRC — компилятор ресурсов
  • GoBug — отладчик (версия для Windows XP является платной)
  • Paws — IDE
Ссылки
2
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
15.09.2014, 12:32  [ТС] #54
SOLAR Assembler
Автор Bogdan Valentin Ontanu. SOLAR Assembler современный многопроходный макроассемблер, предназначен для компиляции 16/32/64-разрядных программы запускаемых под Windows, Linux, MacOSX и Solar_OS
Особенности
  • Быстро обрабатывает большие и сложные проекты: скорость обработки до 350000 строк в секунду
  • Создает выходные файлы в формате PE32/64, Binary 16/32/64, DLL32/64
  • Создает OBJ-файлы в формате OMF32, COFF32/64, ELF32/64 и MachO32
  • Программы в 16/32/64-разрядном виде
  • Рекурсивные и вложенные макросы
  • Включает в себя богатый набор примитивов:
    • .IF .ELSEIF .ELSE .ENDIF с логическими операциями AND/OR/NOT для нескольких условий
    • PROC, ARGS, LOCALS, USES
    • INVOKE с поддержкой ADDR
    • STRUCT, ENUM, UNION
    • .REPEAT .UNITIL
    • MACRO, MARGS, VARARG, EXITM
    • #if, #ifdef, #if_used, #else
  • не требуются PROTO, проверяет аргументы процедур
  • включает в себя минимальнорасходующий память компилятор ресурсов
  • создает листинг в стандартном текстовом формате
  • создает отладочную информацию в COFF-формате и легкочитаемый текстовый формат
  • многоплатформенный, работает со следующими операционными системами:
    • WIn95, Win98, Windows XP, VISTA, Windows 7 32 и 64 разрядной
    • Mac OS X (10.5 leopard),
    • Unix / Linux и другие unix-подобные операционные системы, которые могут линковаться с ELF libc
Ссылки
Мануал по SOLAR Assembler http://www.oby.ro/sol_asm/docs/sol_asm_manual.htm
Скачать SOLAR Assembler http://www.oby.ro/sol_asm/sol_asm_download_bin.htm
Примеры программ на SOLAR Assembler http://www.oby.ro/sol_asm/sol_asm_download_samples.htm
Ассемблеры под UNIX
Единственный более-менее работающий транслятор под UNIX — GAS (GNU Assembler) завязан на компилятор GCC и имеет такой ужасный синтаксис, что писать на нем могут только мазохисты (примеров программ, запрограммированных на GAS'е практически нет).
Прочие ассемблеры
Существует также множество других видов ассемблера, число которых постоянно растет.
Остальные ассемблеры (типа A86, AS86) не позволяют писать 16/32-разрядный код или раздаются практически без документации.
  • ASM-86 Intel Corporation
  • WASM — Open Watcom Assembler — ассемблер фирмы Watcom http://openwatcom.org
  • JWasm http://www.japheth.de/JWasm.html
  • CodeX Assembler автор Marco Kaufmann свободнораспространяемый ассемблер для x86 платформы, поддерживает все инструкции микропроцессоров Intel до Pentium 4 включительно, а также набор AMD-инструкций 3D Now! Включает в себя интегрированный линкер, генерирует файлы следующих форматов:
    • 16-/32-разрядные Flat Model Binaries
    • 16-/32-разрядные приложения DOS
    • 32-разрядные Windows приложения (Console / GUI) и 32-разрядные Windows DLL-ки
    страница CodeX Assembler http://www.pageofmarco.de/codex/eng/
  • RosAsm ReactOS — 32-битовый Win32 x86 ассемблер, выпущенный согласно лицензии GNU GPL. Согласно своему имени, ассемблер поддерживает ReactOS, хотя проекты RosAsm и ReactOS независимы. RosAsm — IDE с полной интеграцией ассемблера, встроенного линкера, редактора ресурсов, отладчика и дизассемблера. Синтаксис сделан как продолжение NASM’а. RosAsm упаковывает исходный текст, от которого программы собираются непосредственно в пределах портативных исполняемых файлов. Поскольку RosAsm производит файлы PE напрямую, отдельный шаг линкования (компоновки) не требуется. Тем не менее, это означает, что компилятор не позволяет связываться с внешними модулями объекта, отличными от DLL, и он не поддерживает способность произвести программные модули, которые могут быть связаны с другими программами.
  • POasm автор Pelle Orinius. MASM-совместимый ассемблер (POASM) с поддержкой синтаксиса Intel и компоновщик (POLINK) Poasm и Polink можно найти в пакете MASM32
  • NewBasic++ Assembler http://www.cybertrails.com/~fys/newbasic.htm
  • Pass32 http://www.geocities.com/SiliconValley/Bay/3437/
Сводные характеристики разных ассемблеров
КритерийMASMTASMFASMNASMYASM
ЦенаБесплатныйБесплатныйБесплатныйБесплатный
ОткрытостьЗакрытыйЗакрытыйОткрытыйОткрытыйОткрытый
ВладелецMicrosoftBorlandTomasz GrysztarCommunityCommunity
ПопулярностьОгромнаяНизкаяВысокаяУмереннаяУмеренная
Masm-совместимостьХорошаяНизкая Низкая
Архитектурыx86 16/32, x86-64x86 16/32x86 16/32, x86-64x86 16/32x86 16/32, x86-64
SEE и прочееПоддерживаетНе поддерживаетПоддерживаетПоддерживаетПоддерживает
ПлатформыDOS,WINDOS,WINdos,win,linux,bsddos,win,linux,bsddos,win,linux,bsd
Отладочная информацияCodeView, PDBBorlandBorland, STABS, DWARF2Borland, CodeView, STABS, DWARF2
Выходные файлыcoff, ms omfms omf, IBM omf, Phar Lapbin, mz, pe, coff, elfbin, aout, aoutb, coff, elf, as86, obj, win32, rdf, ieeebin, coff, elf
UnicodeПоддерживаетНе поддерживаетПоддерживаетПоддерживаетНе поддерживает
ДокументацияОтличнаяОтличнаяПриемлемаяХорошаяХорошая
ИспользованиеDDK, VC, IDABorland C++

За основу взяты
  1. статья Крис Касперски Сравнение ассемблерных трансляторов
  2. эссе Randall Hyde Which Assembler is the Best?
3
Charles Kludge
Клюг
7641 / 3156 / 382
Регистрация: 03.05.2011
Сообщений: 8,382
15.01.2015, 01:09 #55
Вывод результата вычислений из регистра ST(0) сопроцессора, ДОСъ
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
;FASM
;X=(A^2+B^2)/(D-C)
    org 100h
    fild    dword [A]
    fimul   dword [A]   ;st0=A^2 
    fild    dword [B]   ;st0=B, st1=A^2
    fimul   dword [B]   ;st0=B^2, st1=A^2
    faddp           ;st0=(A^2+B^2)
    fild    dword [D]
    fisub   dword [C]   ;st0=D-C, st1=(A^2+B^2)
    fdivp                   ;st0=(A^2+B^2)/(D-C)
; всё, с вычислениями закончили, начинается самое весёлое
    fst st1     ; дублируем st0 в st1
    fstcw   [FPUCW]         ;сохраняем FPU control word
; выставляем флаг округления при преобразовании в целое:
; отсекать дробную часть
    or  word [FPUCW], 0C00h;set rounding mode: truncate
    fldcw   [FPUCW]     ;загружаем FPU control word
    frndint         ; округляем
    fist    dword [X]   ; сохраняем целую часть
    fsubp   st1, st0    ; получаем дробную часть
;   fabs
    fimul   dword [ths] ; *10000 - 4 знака после запятой, max - 11
    fistp   dword [X_]  ; сохраняем дробную часть
; выводим X=
    mov dx, res     
    mov ah, 9
    int 21h
; выводим целую часть
    mov eax,[X]
    call    _cputl
; выводим точку
    mov al,'.'
    int 29h
; выводим дробную часть
    mov eax,[X_]
; жеёстко задаём кол-во цифр, иначе вместо 5.007 получим 5.7
    mov ecx,4
    call    _cputfc
; ждём нажатия клавиши
    xor ax,ax
    int 16h
gtfo:   ret
; eax - signed long
_cputl:
;;;        push ebx
    mov ebx, eax
    neg ebx
    cmovs   ebx, eax    ; проверка на знак
    jle @F
    mov al, '-'     ; выодим минус, если отрицательное
    int 29h
@@: mov eax, ebx
;;; pop ebx
 
; eax - unsigned long
; выводим целую часть из eax
_cputul:
;;; pushad
    xor ecx, ecx    ; счётчик цифр
    mov ebx, 0Ah    ; делитель
    xor esi, esi    ;
    xor edi, edi        ; esi, edi - аккумулятор
@@: xor edx, edx    ; для деления
    inc ecx             ; прибавляем счётчик
    idiv    ebx     ; делим eax на 10
    shld    edi, esi, 4 ; сдвигаем влево edi:esi на 4 бита
    shl esi, 4      ; сдвигаем влево esi на 4 бита
    or  si, dx      ; в младшие 4 бита esi добавляем остаток от деления
    test    eax, eax    ; пока eax != 0
    jnz @B
; теперь сдвигаем регистры в обратном направлении
    mov edx, esi
@@: mov al, dl
    and al, 0fh     ; младшие 4 бита
    or  al, 30h     ; переводим в ASCII
    shrd    edx, edi, 4 ; сдвигаем вправо edx:edi на 4 бита
    shr edi, 4          ; сдвигаем вправо edi на 4 бита    
    int 29h     ; выводим цифру
    loop    @B      ; пока тот самый счётчик цифр !=0
;;; popad
    ret
; выводим дробную часть из eax
_cputfc:
; см.комменты к _cputul
;;; pushad
    mov ebx, ecx
    xor esi, esi
    xor edi, edi
@@: xor edx, edx
    idiv    dword [ten]
    shld    edi, esi, 4
    shl esi, 4
    or  si, dx
    loop    @B
    mov ecx, ebx
    mov edx, esi
@@: mov al, dl
    and al, 0fh
    or  al, 30h
    shrd    edx, edi, 4
    shr edi, 4
    int 29h
    loop    @B
;;; popad
    ret
 
ten:    dd  10
ths:    dd  10000
FPUCW:  dw  ?
A:  dd  7
B:  dd  12
C:  dd  -15
D:  dd  7
X:  dd  ?
X_: dd  ?
res:    db  0Dh,0Ah,'X=$'
2
Charles Kludge
Клюг
7641 / 3156 / 382
Регистрация: 03.05.2011
Сообщений: 8,382
14.05.2015, 18:12 #56
setlocale для корректного вывода даты на русском через strftime:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
;FASM
format PE console 4.0
 
include 'win32a.inc'
LC_TIME =   5
LC_ALL  =   0
struct  tm   
tm_sec  dd  ?
tm_min  dd  ?
tm_hour dd  ?
tm_mday dd  ?
tm_mon  dd  ?
tm_year dd  ?
tm_wday dd  ?
tm_yday dd  ?
tm_isdst    dd  ?
ends    
    invoke  time,  0            ; получаем текущее время
    mov [ctim], eax         ; сохраняем для localtime
    invoke  localtime, ctim         ; получаем местное время
    mov esi,eax             ; сораняем указатель на структуру tm
    invoke  strftime,buf, bufsz, tpt, eax   ; конвертим в строку согласно текущей локали
    cinvoke printf, tps, buf        ; выводим на консоль
 
    invoke  setlocale, LC_ALL, rus      ; меняем локаль на русскую
    invoke  strftime,buf, bufsz, tpt, esi   ; конвертим в строку согласно текущей локали
    cinvoke printf, tps, buf        ; выводим на консоль                        
gtfo:
    invoke  exit,0              ;
 
ttt tm  <0>
rus db  'Russian_Russia.866',0      ; русская локаль зовётся так
tpt db  '%d-%B-%Y %A',0         ; день-месяц-год день_недели
tps db  '%s', 0Dh, 0Ah, 0
ctim    dd  ?
buf db  30 dup(0)
bufsz   =   $-buf
 
data import
library msvcrt,'msvcrt.dll'
 
import  msvcrt,\
    time,'time',\
    localtime,'localtime',\
    strftime,'strftime',\
    printf,'printf',\
        setlocale,'setlocale',\ 
    exit,'exit'
end data
1
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
04.04.2016, 03:34  [ТС] #57
Архитектура микропроцессоров Intel® 64 и IA-32
Книги по Debugging
Анализ уязвимости
Язык программирования ассемблер
1
Viacheslav78
268 / 16 / 0
Регистрация: 16.02.2016
Сообщений: 29
04.04.2016, 04:39 #58
Электронная библиотека книг братьев Фроловых
Библиотека системного программиста
  1. Операционная система MS-DOS. Том 1, книги 1 - 2
  2. Операционная система MS-DOS. Том 1, книга 3
  3. Аппаратное обеспечение IBM PC.
  4. Программирование видеоадаптеров CGA, EGA и VGA.
  5. Программирование модемов.
  6. Тонкая настройка и оптимизация MS-DOS.
  7. Защищенный режим процессоров Intel 80286/80386/80486.
  8. Локальные сети персональных компьютеров. Монтаж сети, установка программного обеспечения.
  9. Локальные сети персональных компьютеров. Использование протоколов IPX, SPX, NETBIOS.
  10. Локальные сети персональных компьютеров. Работа с сервером Novell NetWare.
  11. Компьютер IBM PC/AT, MS-DOS и Windows. Вопросы и ответы.
  12. Операционная система MS Windows 3.1 для программиста.
  13. Операционная система MS Windows 3.1 для программиста.
  14. Операционная система MS Windows 3.1 для программиста.
  15. Графический интерфейс GDI в Microsoft Windows.
  16. Мультимедиа для Windows.
  17. Модемы и факс-модемы. Программирование для MS-DOS и Windows.
  18. Операционная система MS Windows 3.1 для программиста. Дополнительные главы.
  19. MS-DOS для программиста.
  20. MS-DOS для программиста.
  21. Операционная система OS/2 Warp.
  22. Программирование видеоадаптеров.
  23. Операционная система Windows 95 для программиста.
  24. Глобальные сети компьютеров. Практическое введение в Internet, E-Mail, FTP, WWW и HTML, Windows Sockets.
  25. MS Visual C++ и MFC. Программирование для Windows 95/NT.
  26. Программирование для IBM OS/2.
  27. Программирование для Windows NT. Часть 1
  28. Программирование для Windows NT. Часть 2
  29. MS Visual C++ и MFC. Программирование для Windows 95/NT Часть 2.
  30. Сервер Web своими руками.
  31. MS Visual J++. Создание приложений и аплетов. Часть 1.
  32. Разработка приложений для Internet.
  33. MS Visual J++. Создание приложений и аплетов. Часть 2.
  34. Аппаратное обеспечение ПК
  35. Сценарии JavaScript в активных страницах Web.
Персональный компьютер. Шаг за шагом.
  1. Введение в MS-DOS, MS Windows, MS Word for Windows
  2. Операционная система Microsoft Windows. Руководство пользователя
  3. Сети компьютеров в вашем офисе
  4. Что вы должны знать о своем компьютере
  5. Осторожно: компьютерные вирусы
Статьи и другие проекты
  1. Восстановление данных
  2. Антивирусная защита
  3. Статьи для программистов
  4. Пользователю компьютера
  5. Персональный компьютер. Шаг за шагом.
Другие книги
  1. Всемирная паутина
  2. Электронная почта
  3. Тусовка в Интернете
  4. Базы данных в Интернете
  5. Создание Web-приложений: Практическое руководство
  6. Практика применения Perl, PHP, Apache, MySQL для активных Web-сайтов
  7. Язык C#. Самоучитель
  8. Визуальное проектирование приложений C#
  9. Антивирусная защита: Учебное пособие для защиты информационных ресурсов
  10. Синтез и распознавание речи. Современные решения
Книги по ARM
2
Viacheslav78
268 / 16 / 0
Регистрация: 16.02.2016
Сообщений: 29
17.04.2016, 21:56 #59
Reverse Engineering:

Bell Simon. Building a Honeypot to Research Cyber-Attack Techniques.
Bryant R. Adam. UNDERSTANDING HOW REVERSE ENGINEERS MAKE SENSE OF PROGRAMS FROM ASSEMBLY LANGUAGE REPRESENTATIONS.
Cipresso Teodoro. Software reverse engineering education.
Dyrkolbotn Geir Olav. Reverse Engineering Microprocessor Content Using Electromagnetic Radiation.
Fonseca Jose Manuel Rios. Interactive Decompilation.
Garzon Miguel. Reverse Engineering Object-Oriented Systems into Umple. An Incremental and Rule-Based Approach.
Greevy Orla. Enriching Reverse Engineering with Feature Analysis.
Hauke D. Jonathan. Design Verification Using Reverse Engineering.
Holger Michael Kienle. Building Reverse Engineering Tools with Software Components.
Kenny Wong. The Reverse Engineering Notebook.
Lajos Jeno Fülop. Evaluating and Improving Reverse Engineering Tools.
Lin Feifei (Amy). Analysing Reverse Engineering Techniques for Interactive Systems.
Michael Kiperberg. Preventing Reverse Engineering of Native and Managed Programs.
Morando Federico. SOFTWARE REVERSE ENGINEERING AND OPEN SOURCE SOFTWARE. Do we need more FUD to be satiated.
Portillo Sergio Pastrana. Attacks Against Intrusion Detection Networks. Evasion, Reverse Engineering and Optimal Countermeasures.
Prpic Martin. Reverse engineering of Java Card applets.
Ramasubbu Surendranath. Reverse Software Engineering Large Object Oriented Software Systems using the UML Notation.
Rosenblum Nathan E. THE PROVENANCE HIERARCHY OF COMPUTER PROGRAMS.
Shi Nija. Reverse Engineering of Design Patterns from Java Source Code.
UZELAC VLADIMIR. MICROBENCHMARKS AND MECHANISMS FOR REVERSE ENGINEERING OF MODERN BRANCH PREDICTOR UNITS.
Vinju Jurgen Jordanus. Analysis and Transformation of Source Code by Parsing and Rewriting.
Zhiqiang Lin. Reverse Engineering of Data Structures from Binary.

Software Protection, Code Obfuscation, Malware Detection:

Alam Shahid. A Framework for Metamorphic Malware Analysis and Real-Time Detection.
Alexandre Nuno Vicente Dias. Detecting Computer Viruses using GPUs.
Aniket Kulkarni. Software Protection through Code Obfuscation.
Batchelder R. Michael. JAVA BYTECODE OBFUSCATION.
Bertholon Benoit. Towards Integrity and Software Protection in Cloud Computing Platforms.
Birhanu Mekuria Eshete. Effective Analysis, Characterization, and Detection of Malicious Activities on the Web.
Blaine Alan Nelson. Designing, Implementing, and Analyzing a System for Virus Detection.
Bose Abhijit. Propagation, Detection and Containment of Mobile Malware.
Brian M. Bowen. Design and Analysis of Decoy Systems for Computer Security.
CAPPAERT Jan. Code Obfuscation Techniques for Software Protection.
DAVIDSON RODRIGO BOCCARDO. Context-Sensitive Analysis of x86 Obfuscated Executables.
Drape Stephen. INTELLECTUAL PROPERTY PROTECTION USING OBFUSCATION.
Drape Stephen. Obfuscation of Abstract Data-Types.
Farley, Ryan Joseph. Toward Automated Forensic Analysis of Obfuscated Malware.
Flexeder Andrea. Interprocedural Analysis of Low-Level Code.
Gupta, Divya. Program Obfuscation. Applications and Optimizations.
HILLERT EMILIA.Obfuscate Java bytecode. An evaluation of obfuscating transformations using JBCO.
Javaid Salman. Analysis and Detection of Heap-based Malwares Using Introspection in a Virtualized Environment.
Khaled ElWazeer. DEEP ANALYSIS OF BINARY CODE TO RECOVER PROGRAM STRUCTURE.
Khalid Mohamed Abdelrahman Y Alzarooni. Malware Variant Detection.
Kinder Johannes. Static Analysis of x86 Executables. Statische Analyse von Programmen in x86 Maschinensprache.
Konstantinou Evgenios. Metamorphic Virus. Analysis and Detection.
Lixi Chen. Code Obfuscation Techniques for Software Protection.
Large-Scale Malware Analysis, Detection, and Signature Generation.
Lengyel Tamas Kristof. Malware Collection and Analysis via Hardware Virtualization.
Mayank Varia. Studies in Program Obfuscation.
Mohan R. Vishwath. SOURCE-FREE BINARY MUTATION FOR OFFENSE AND DEFENSE.
Molnar David Alexander. Dynamic Test Generation for Large Binary Programs.
Paleari Roberto. Dealing with next-generation malware.
Paul R. Nathanael. Disk-Level Behavioral Malware Detection.
Povalova Radoslava. Framework for Easy Malware Analysis.
Preda Mila Dalla. Code Obfuscation and Malware Detection by Abstract Interpretation.
Remi Andre B. Valvik. Security API for Java ME. Secure X data.
Richardson Christopher. Virus detection with machine learning.
RIERA FRANCISCO BLAS IZQUIERDO. LO! LLVM Obfuscator An LLVM obfuscator for binary patch generation.
Rompf Tiark. Lightweight Modular Staging and Embedded Compilers. Abstraction without Regret for High-Level High-Performance Programming.
Roundy A. Kevin. HYBRID ANALYSIS AND CONTROL OF MALICIOUS CODE.
Sabu Emmanuel. Software Obfuscation Presentation.
Shakya Sundar Das. Code Obfuscation using Code Splitting with Self-modifying Code.
Sharath K. Udupa, Saumya K. Debray and Matias Madou. Deobfuscation Reverse Engineering Obfuscated Code.
Sharif I. Monirul. ROBUST AND EFFICIENT MALWARE ANALYSIS AND HOST-BASED MONITORING.
SOLODKYY YURIY. SIMPLIFYING THE ANALYSIS OF C++ PROGRAMS.
Solofoarisina Arisoa Randrianasolo. Artficial Intelligence in Computer Security. Detection, Temporary Repair and Defense.
SUN FANGQI. Program Analyses of Web Applications for Detecting Application-Specific Vulnerabilities.
Tian Ronghua. An Integrated Malware Detection and Classification System.
Venkatachalam Sujandharan. DETECTING UNDETECTABLE COMPUTER VIRUSES.
Victor van der Veen. Dynamic Analysis of Android Malware.
Wang Chenxi. A Security Architecture for Survivability Mechanisms.
Wroblewski Gregory. General Method of Program Code Obfuscation (draft).
3
Mikl___
Автор FAQ
11528 / 5967 / 535
Регистрация: 11.11.2010
Сообщений: 10,964
25.04.2016, 05:40  [ТС] #60
ЗАПУСК DOS-ПРОГРАММ ПОД WINDOWS X64
взято здесь
В любой организации может оказаться так, что при обновлении старых компьютеров на новые можно столкнуться с таким фактом, что компьютеры новые, но необходимость использования старых программ никуда не отпала. А одной из старых программ вполне может оказаться какое-нибудь приложение, написанное в 90-ые годы под MS-DOS, которое напрочь отказывается запускаться на современных операционных системах.

В таком случае очень помогает эмулятор MS-DOS под названием DOSBox.

Скачиваем последнюю версию с официального сайта, после устанавливаем. Никаких необычных действий при установке программы не требуется.

Теперь ищем необходимую вам DOS программу. Допустим она располагается на диске D в папке Prog и называется Prog.exe. Переходим в эту папку, и создаем там текстовый файл с любым названием и расширением conf. В него пишем:

Код
[autoexec]
mount c D:\Prog
c:
Prog.exe
exit
  • mount c D:\Prog – монтирует в эмулятор папку D:\Prog как раздел жесткого диска С;
  • c: – осуществляет переход на раздел C жесткого диска;
  • Prog.exe – запускает нужную программу. Вместо exe файла могут быть так же файлы с расширением bat или pif.
  • exit – закрывает DOSBox после завершения работы программы. Работает ТОЛЬКО с exe файлами.
Если вам нужно, чтобы DOSBox закрывался после запуска bat файла, то вместо простого запуска prog.bat пишем:
Код
call prog.bat
Теперь создаем еще один текстовый файл с расширением bat. В него пишем:

Код
start "C:\Program Files (x86)\DOSBox-0.74\DOSBox.exe -conf D:\Prog\prog.conf"
Вместо “C:\Program Files (x86)\DOSBox-0.74” указываем тот путь, куда была установлена программа DOSBox. Вместо D:\Prog указываем путь к нужной DOS программе, и вместо prog.conf указываем имя файла, созданного выше.
Сохраняем, и пробуем запустить. Если все прошло успешно, то появится окошко DOSBox, в котором запустится нужная программа.
Впрочем, момент триумфа может омрачить полное отсутствие русского языка – но это дело поправимое.
Для начала скачиваем с официального сайта официальную локализацию – после чего распаковываем содержимое архива в каталог с программой. Теперь открываем созданный выше conf файл, и дописываем туда в самый верх следующее:
Код
[dosbox]
language="C:\Program Files (x86)\DOSBox-0.74\russian.txt"

[dos]
keyboardlayout=RU
Где “C:\Program Files (x86)\DOSBox-0.74\russian.txt” это путь к распакованному в папку с программой файлу russian.txt.
Если путь к файлу russian.txt содержит пробелы, обязательно закрываем его в кавычки, как в примере выше. Если же пробелов в пути нет, кавычки не нужны, и скорее всего DOSBox не будет корректно работать.
Сохраняем и пробуем запустить программу.
Теперь русские символы отображаются и печатаются.
Переключение языка в DOSBox осуществляется с помощью одновременного нажатия левого Alt и правого Shift.
Если при запуске программы смущает второе окошко DOSBox, которое открывается вместе с основным окном, то можно в bat файл дописать параметр -noconsole, в итоге bat файл для запуска приобритет следующий вид:

Код
start "C:\Program Files (x86)\DOSBox-0.74\DOSBox.exe -conf D:\Prog\prog.conf" -noconsole
3
Миниатюры
FAQ для раздела Assembler, MASM, TASM   FAQ для раздела Assembler, MASM, TASM  
25.04.2016, 05:40
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
25.04.2016, 05:40

Есть ли компиляторы Tasm или Masm для 64-разрядных систем
Есть ли компиляторы Tasm или Masm для 64-разрядной с-мы??? если есть просьба...

Подскажите книгу для программирования под Win32, MASM/TASM с большим количеством примеров
Ув. форумчане, подскажите пожалуйста какую-нибудь обучающую книгу для...

TASM to MASM
Компилил стареньким ТАСМом вот такой вот фрагмент: ;обычное сложение 2-х чисел...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
60
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru