Форум программистов, компьютерный форум, киберфорум
Наши страницы

Assembler, MASM, TASM

Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 415, средняя оценка - 4.79
Mikl___
Автор FAQ
11021 / 5795 / 511
Регистрация: 11.11.2010
Сообщений: 10,802
19.09.2013, 06:17  [ТС] #31
Блокируем клавиатуру в Windows
Для блокировки клавиатуры существует несколько путей
  1. функцией BlockInput
  2. блокировкой немаскированных прерываний командой CLI
  3. посылкой в контроллер клавиатуры команды 0xAD
  4. отправим контроллеру клавиатуры адрес, а данные забываем
  5. через реестр
  1. в user32.dll есть функция BlockInput, ниже программа которая отключает клавиатуру на 5 секунд
    Assembler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    ; masm windows gui #
    .686
    .model flat
    include windows.inc           
    includelib \masm32\lib\kernel32.lib
    includelib \masm32\lib\user32.lib
    extern _imp__BlockInput@4:dword
    extern _imp__Sleep@4:dword
    .code
    start:  push TRUE
        call _imp__BlockInput@4; отключить клаву
        push 5000
        call _imp__Sleep@4;выждать 5 секунд
        push FALSE
        call _imp__BlockInput@4; включить
        ret
    end start
    BlockInput блокирует мышь и почти всю клавиатуру, но имеет недостаток: все это разблокируется при нажатии Ctrl-Alt-Del, внезапном завершении вызвавшей программы, или при критической системной ошибке.
  2. драйвер, который блокирует немаскированные прерывания (главным образом клавиатуру) на 5 секунд.
    Идем сюда, копируем текст драйвера scp00.sys, выкидываем из него всё ненужное пока не получится
    Assembler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    ; masm windows native #
    ;написано на основе драйвера режима ядра beeper из «KmdTutor by Four-F»
    .686P
    .model flat
    include ntstatus.inc
    include ntddk.inc
    includelib hal.lib
    extern _imp__KeStallExecutionProcessor@4:dword
    .code
    DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
        cli
        mov ecx,1000; 50 мкСек * 1000 = 5 Сек
    @@: push ecx
        push 50; максимальное количество мкСек для функции KeStallExecutionProcessor
        call _imp__KeStallExecutionProcessor@4
        pop ecx
        loop @b    
        sti
        mov eax, STATUS_DEVICE_CONFIGURATION_ERROR;возвращаем код ошибки, для 
        ret     ;того, чтобы система удалила драйвер из памяти
    DriverEntry endp
    запускаем драйвер одним из трех описанных способов
  3. команда 0хAD блокирует клавиатуру до момента разблокировки командой 0хАЕ
    Assembler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    ; masm windows native #
    .686P
    .model flat
    include ntstatus.inc
    include ntddk.inc
    includelib hal.lib
    extern _imp__KeStallExecutionProcessor@4:dword
    .code
    DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
        mov al,0ADh
        out 64h,al; блокируем
        mov ecx,1000; 50 мкСек * 1000 = 5 Сек
    @@: push ecx
        push 50; максимальное количество мкСек для функции KeStallExecutionProcessor
        call _imp__KeStallExecutionProcessor@4
        pop ecx
        loop @b    
        mov al,0AEh
        out 64h,al;снимаем блокировку
        mov eax, STATUS_DEVICE_CONFIGURATION_ERROR;возвращаем код ошибки, для 
        ret     ;того, чтобы система удалила драйвер из памяти
    DriverEntry endp
    действуем аналогично второму способу
  4. контроллер клавиатуры использует один и тот же порт, как для задания адреса, так и для чтения и записи данных. После передачи кода команды контроллер клавиатуры не будет обрабатывать нажатие клавиш, пока не будет передан байт данных
    Assembler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    ; masm windows native #
    .686P
    .model flat
    include ntstatus.inc
    include ntddk.inc
    includelib hal.lib
    extern _imp__KeStallExecutionProcessor@4:dword
    .code
    DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
        mov al,0F3h
        out 60h,al; блокируем
        mov ecx,1000; 50 мкСек * 1000 = 5 Сек
    @@: push ecx
        push 50; максимальное количество мкСек для функции KeStallExecutionProcessor
        call _imp__KeStallExecutionProcessor@4
        pop ecx
        loop @b    
        mov al,0F4h
        out 60h,al;снимаем блокировку
        mov eax, STATUS_DEVICE_CONFIGURATION_ERROR;возвращаем код ошибки, для 
        ret     ;того, чтобы система удалила драйвер из памяти
    DriverEntry endp
    аналогично способу два
3
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
19.09.2013, 06:17
Я подобрал для вас темы с готовыми решениями и ответами на вопрос FAQ для раздела Assembler, MASM, TASM (Assembler):

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

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

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

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

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

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

62
murderer
3309 / 1456 / 80
Регистрация: 06.10.2010
Сообщений: 3,203
22.09.2013, 09:08 #32
Особенности кодинга под x64
Программирование под x64 не сильно отличается от уже привычного x86, но некоторые отличия всё-же имеются. Как всегда для того, чтобы узнать все эти мелочи приходится собирать инфу в нескольких источниках. Я попытаюсь собрать воедино эти мелочи из руководств Intel, MSDN и руководства FASM.
По части инструкций
Запилено:
  1. 8 xmm-регистров (xmm8-xmm15).
  2. 8 регистров общего назначения (r8-r15)
  3. Теперь для относительной адресации используется регистр rip.
  4. Теперь можно обращаться к младшим байтам индексных регистров (bpl, spl, dil, sil)

Выпилено:
  1. Инструкции для работы с двоично-десятичными числами (aaa, aad, aam, aas, daa и das)
  2. bound
  3. pusha/popa
  4. inc и dec с однобайтовой кодировкой

Ограничения:
  1. В архитектуре x86 присутствует ограничение на длину инструкции в 15 байт. Из этого вытекает невозможность записи инструкций вида
    Assembler
    1
    
    mov [mem64],imm64
    Так как это займёт больше 15 байт (8 байт адреса + 8 байт непосредственного операнда + опкод). Поэтому в x64 можно делать только так
    Assembler
    1
    
    mov [mem64],imm32
    При этом imm32 будет преобразован в qword с учётом знака (movsx).
  2. Инструкция не может ссылаться одновременно на ah,bh,dh,ch и младший байт новых регистров. То есть "mov ah,dl" - допустимо, а "mov ah,r8b" - не допустимо.
  3. Операции с 32 битными операндами обнуляют старшие 4 байта результата. То есть "add eax,ebx" обнулят старшую часть rax. К 8 и 16 битным операндам это не относится.

Соглашение fastcall x64
Передача параметров
Первые 4 параметра передаются через регистры rcx, rdx, r8, r9, остальные параметры передаются в обратном порядке через стек. При этом в стеке резервируется дополнительно 32 байта для первых 4 параметров даже если у функции вообще нет параметров. Параметры с плавающей точкой передаются через регистры xmm0, xmm1, xmm2, xmm3. Если параметр занимает больше 8 байт, он передаётся через указатель. Стек освобождается вызывающей стороной.

Выравнивание стека
Перед вызовом функции rsp должен быть кратен 16. Таким образом если ваша функция имеет чётное количество параметров и количество параметров больше 4, при передаче ей управления rsp будет кратен 8, иначе rsp кратен 16. В FASM макросы .code/.end автоматически добавляют "sub rsp,8" в точке входа для выравнивания стека.

Не изменяемые регистры
Функции не должны изменять содержимое регистров rsi, rdi, rbp, rbx, r12-r15, xmm6-xmm15.

Общие рекомендации
  1. Старайтесь обходиться 32-битными инструкциями, они занимают меньше места, чем 64-битные т.к. для кодирования 64-битных инструкций используется префикс REX. Не стоит забывать, что регистры rbl, spl, dil, sil также кодируются через префикс.
  2. В FASM существует макрос frame/endf, который позволяет выделять и освобождать место в стеке для вызова сразу нескольких API-функций.
    Рассмотрим такой ничего не значащий код
    Assembler
    1
    2
    
    invoke MessageBoxW,rcx,rdx,r8,r9
    invoke ExitProcess,rcx
    Он будет скомпилирован так
    Assembler
    1
    2
    3
    4
    5
    6
    
    sub  rsp,32
    call [MessageBoxW]
    add  rsp,32
    sub  rsp,32
    call [ExitProcess]
    add  rsp,32
    Теперь применим макрос
    Assembler
    1
    2
    3
    4
    
    frame
    invoke MessageBoxW,rcx,rdx,r8,r9
    invoke ExitProcess,rcx
    endf
    И всё заметно улучшится
    Assembler
    1
    2
    3
    4
    
    sub  rsp,32
    call [MessageBoxW]
    call [ExitProcess]
    add  rsp,32
    Кстати обратите внимание: умный макрос invoke не генерирует лишний код
    Assembler
    1
    2
    3
    4
    
    mov rcx,rcx
    mov rdx,rdx
    mov r8,r8
    mov r9,r9
    Можно учитывать это при написании программ.
  3. До появления ollydbg x64 можно использовать PEBrowseDbg64 - вполне годный отладчик. При запуске отладки происходит остановка в ntdll.dll, нажимаем F5 и попадаем на точку входа нашего PE. Присутствует небольшой баг: если начать отладку и просто закрыть отладчик, то он останется висеть в памяти. Для корректного выхода нужно нажать Shift+F5 (завершить отладку), а уже после этого закрывать отладчик.
8
Полный 30h
Эксперт быдлокодинга
1532 / 445 / 60
Регистрация: 04.11.2010
Сообщений: 1,219
22.09.2013, 23:25 #33
murderer,
Assembler
1
2
Start:
  sub rsp, 8
0
Mikl___
Автор FAQ
11021 / 5795 / 511
Регистрация: 11.11.2010
Сообщений: 10,802
23.09.2013, 05:04  [ТС] #34
Особенности кодинга под x64
(часть вторая)
За основу взята статья Крис Касперски "Архитектура x86-64 под скальпелем ассемблерщика"

За подробным описанием x86-64 архитектуры лучше всего обратится к фирменной документации "AMD64 Technology - AMD64 Architecture Programmer's Manual Volume 1:Application Programming" (http://www.amd.com/us-en/assets/cont...docs/24593.pdf). Мы ограничимся только беглым обзором основных нововведений.
К восьми регистрам общего назначения добавилось еще восемь, в результате чего их общее количество достигло 16.
Старые регистры, расширенные до 64-бит, получили имена RAX, RBX, RCX, RDX, RBP, RSI, RDI, RSP, RIP и RFLAGS. Новые регистры остались безымянными и просто пронумерованы от R8 до R15. Для обращения к младшим 8-, 16- и 32-битам новых регистров можно использовать суффиксы b, w и d соответственно. Например, R9 - это 64-разрядный регистр, R9b - его младший байт (по аналогии с AL), а R9w - младшее слово (то же самое, что AX в EAX). Прямых наследников AH, к сожалению, не наблюдается и для манипуляции со средней частью регистров приходится извращаться со сдвигами и математическими операциями.

Регистры, доступные в x86-64 режиме

Регистр-указатель команд RIP теперь адресуется точно так же, как и все остальные регистры общего назначения.
Простейший пример: загрузить в регистр AL опкод следующей машинной команды. На x86 приходится поступать так. Если мы знаем адрес следующей команды, тогда это выглядит так
Assembler
1
2
        mov al,ds:[40430h]
        nop
если адрес неизвестен
Assembler
1
2
3
4
        call $ + 5; в стек адрес следующей команды и передать на нее управление
        pop ebx; вытолкнуть из стека адрес возврата, скорректировать адрес на размер 
        mov al, [ebx+7]  ;команд pop/mov, теперь AL содержит опкод команды NOP
        NOP            ; команда, опкод которой мы хотим загрузить в AL
Загрузка опкода следующей машинной команды в классическом x86

А теперь перепишем тот же самый пример на x86-64:
Assembler
1
2
       mov al,[rip]   ; загружаем опкод следующей машинной команды
        NOP            ; команда, чем опкод мы хотим загрузить в AL
Загрузка опкода следующей машинной команды на x86-64.

Только следует помнить, что RIP всегда указывает на следующую, а не на текущую инструкцию. К сожалению, ни Jx RIP, ни CALL RIP не работают. Таких команд в x86-64 просто нет. Исчезла абсолютная адресация. Если нам надо изменить содержимое ячейки памяти по конкретному адресу, на x86 мы поступаем приблизительно так:
Assembler
1
       dec byte ptr ds:[666h]  ; уменьшить содержимое байта по адресу 666h на единицу
Абсолютная адресация в классическом x86

Под x86-64 транслятор выдает ошибку ассемблирования, вынуждая нас прибегать к фиктивному базированию:
Assembler
1
2
        xor r9, r9              ; обнулить регистр r9
        dec byte ptr [r9+666h]  ; уменьшить содержимое байта по адресу 666h на единицу
Использование фиктивного базирования на x86-64 для абсолютной адресации

Есть и другие отличия от x86, но они не столь принципиальны. Важно то, что в режиме совместимости с x86 (Legacy Mode) ни 64-битные регистры, ни новые методы адресации недоступны! Никакими средствами дотянуться до них нельзя и прежде чем что-то сделать, необходимо перевести процессор в режим "long mode", который делится на два под-режима:
  • режим совместимости с x86 "compatibility mode"
  • 64-битный режим (64-bit mode)
Режим совместимости предусмотрен только для того, чтобы 64-разрядная операционная система могла выполнять старые 32-битные приложения. Никакие 64-битные регистры здесь и не ночевали, поэтому нам этот режим фиолетов.
Реальная 64-битность обитает только в 64-bit long mode, о котором мы и будем говорить!

Режимы работы процессора AMD-64 и их особенности

"Hello world" на x86-64

Программирование под 64-битную версию Windows мало чем отличается от традиционного, только все операнды и адреса по умолчанию 64-разрядные, а параметры API-функций передаются через регистры, а не через стек. Первые четыре аргумента всех API-функций передаются в регистрах RCX, RDX, R8 и R9 (регистры перечислены в порядке следования аргументов, крайний левый аргумент помещается в RCX). Остальные параметры кладутся на стек. Все это называется x86-64 fast calling conversion (соглашение о быстрой передаче параметров для x86-64), подробное описание которой можно найти в статье "The history of calling conventions, part 5 amd64" (http://blogs.msdn.com/oldnewthing/archive/2004/01/14/58579.aspx). Также нелишне заглянуть на страничку бесплатного компилятора Free PASCAL и поднять документацию по способам вызова API: http://www.freepascal.org/wiki/index.php/Win64/AMD64_API.
В частности, вызов функции с пятью аргументами API_func(1,2,3,4,5) выглядит так:
Assembler
1
2
3
4
5
6
        mov     dword ptr [rsp+20h], 5 ; кладем на стек пятый слева аргумент
        mov     r9d, 4                 ; передаем четвертый слева аргумент
        mov     r8d, 3                 ; передаем третий слева аргумент
        mov     edx, 2                 ; передаем второй слева аргумент
        mov     ecx, 1                 ; передаем первый слева аргумент
        call    API_func
Пример вызова API-функции с пятью параметрами по соглашению x86-64.

Смещение пятого аргумента относительно верхушки стека требует пояснений. Почему оно равно 20h? Ведь адрес возврата занимает только 8 байт. Кто съел все остальные? Оказывается, они "резервируются" для первых четырех аргументов, переданных через регистры. "Зарезервированные" ячейки содержат неинициализированный мусор и называются "spill", что переводится как "затычка" или "потеря".
Вот минимум знаний, необходимых для выживания в мире 64-битной Windows при программировании на ассемблере. Остается разобрать самую малость. Как эти самые 64-бита заполучить?! Для перевода FASM'а в x86-64 режим достаточно указать директиву "use64" и программировать, как обычно.
Ниже идет пример простейшей x86-64 программы, которая не делает ничего, только возвращает в регистре RAX значение "ноль".
Assembler
1
2
3
4
5
6
; сообщаем FASM'у, что мы хотим программировать на x86-64
        use64
 
        xor r9,r9        ; обнуляем регистр r9
        mov rax,r9       ; пересылаем в rax,r9 (можно сразу mov rax,0, но неинтересно)
        ret              ; выходим туда, откуда пришли
Простейшая 64-битная программа.

Никаких дополнительных аргументов командной строки указывать не надо, просто сказать
Код
fasm file-name.asm
Через несколько секунд образуется файл file-name.bin, который в hex-представлении выглядит следующим образом:
Код
4D 31 C9        xor        r9, r9
4C 89 C8        mov        rax, r9
C3              retn
Дизассемблерный листинг простейшей 64-битной программы.

Формально, это типичный com-файл
Начиная с версии 1.64, ассемблер FASM поддерживает специальную директиву "format PE64", автоматически формирующую 64-разрядный PE-файл (директиву "use64" в этом случае указывать уже не нужно), а в каталоге EXAMPLES можно найти готовый пример PE64DEMO, в котором показано, как ее использовать на практике.
Ниже приведен пример x86-64 программы "hello, world" с комментариями:
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
; пример 64-битного PE файла
; для его выполнения необходимо иметь Windows XP 64-bit edition
; указываем формат
format PE64 GUI
; указываем точку входа
entry start
; создать кодовую секцию с атрибутами на чтение и исполнение
section '.code' code readable executable
start:  mov        r9d,0                   ; uType == MB_OK (кнопка по умолчанию)
                                           ; аргументы по соглашению x86-64
                                           ; передаются через регистры, не через стек!
                                           ; префикс d задает регистр размером в слово,
                                           ; можно использовать и mov r9,0, но тогда
                                           ; машинный код будет на байт длиннее
        lea        r8,[_caption]           ; lpCaption, передаем смещение
                                           ; команда lea занимает всего 7 байт,
                                           ; а mov reg,offset - целых 11, так что
                                           ; lea намного более предпочтительна
        lea        rdx,[_message]          ; lpText, передаем смещение выводимой строки
        mov        rcx,0                   ; hWnd, передам дескриптор окна-владельца
                                           ; (можно также использовать xor rcx,rcx
                                           ; что на три байта короче)
        call       [MessageBox]            ; вызываем функцию MessageBox
        mov        ecx,eax                 ; заносим в ecx результат возврата
                                           ; (Функция ExitProcess ожидает 32-битный параметр;
                                           ; можно использовать и mov rcx,rax, но это будет
                                           ; на байт длиннее)
        call       [ExitProcess]           ; вызываем функцию ExitProcess
; создать секцию данных с атрибутами на чтение и запись
; (вообще-то, в данном случае атрибут на запись необязателен,
; поскольку мы ничего не пишем, а только читаем)
section '.data' data readable writeable
  _caption db 'PENUMBRA is awesome!',0     ; ASCIIZ-строка заголовка окна
  _message db 'Hello World!',0             ; ASCIIZ-строка выводимая на экран
; создать секцию импорта с атрибутами на чтение и запись
; (здесь атрибут на запись обязателен, поскольку при загрузке PE-Файла
; в секцию импорта будут записываться фактические адреса API-функций)
section '.idata' import data readable writeable
        dd 0,0,0,RVA kernel_name,RVA kernel_table
        dd 0,0,0,RVA user_name,RVA user_table
        dd 0,0,0,0,0     ; завершаем список двумя 64-разряными нулеми!!!
kernel_table:
        ExitProcess dq RVA _ExitProcess
        dq 0                        ; завершаем список 64-разряным нулем!!!
user_table:
        MessageBox dq RVA _MessageBoxA
        dq 0
kernel_name db 'KERNEL32.DLL',0
user_name db 'USER32.DLL',0
_ExitProcess dw 0
        db 'ExitProcess',0
_MessageBoxA dw 0
        db 'MessageBoxA',0
64-битное приложение "hello, world" под Windows на FASM'е
Код
.code:0000000000401000 41 B9 00 00 00 00      mov        r9d, 0
.code:0000000000401006 4C 8D 05 F3 0F 00 00   lea        r8, aPENUMBRA
.code:000000000040100D 48 8D 15 03 10 00 00   lea        rdx, aHelloWorld ; "Hello World!"
.code:0000000000401014 48 C7 C1 00 00 00 00   mov        rcx, 0
.code:000000000040101B FF 15 2B 20 00 00      call       cs:MessageBoxA
.code:0000000000401021 89 C1                  mov        ecx, eax
.code:0000000000401023 FF 15 13 20 00 00      call       cs:ExitProcess
Дизассемблерный листинг 64-битного приложения "Hello, world!"

приложение в стиле MASM. Никаких радикальных отличий не наблюдается:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
; объявляем внешние API-функции, которые мы будем вызывать
extrn MessageBoxA: PROC
extrn ExitProcess: PROC
; секция данных с атрибутами по умолчанию (чтение и запись)
.data
mytit db 'PENUMBRA is awesome!', 0
mymsg db 'Hello World!', 0
; секция кода с атрибутами по умолчанию (чтение и исполнение)
.code
Main:   mov r9d, 0          ; uType = MB_OK
        lea r8,  mytit      ; LPCSTR lpCaption
        lea rdx, mymsg      ; LPCSTR lpText
        mov rcx, 0          ; hWnd = HWND_DESKTOP
        call MessageBoxA
        mov ecx, eax        ; uExitCode = MessageBox(...)
        call ExitProcess
End Main
64-битное приложение "Hello, world" под Windows на MASM'е

Ассемблирование и линковка проходит так:
Код
ml64 XXX.asm /link /subsystem:windows /defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main
в результате чего образуется готовый к употреблению exe-файл
9
Mikl___
Автор FAQ
11021 / 5795 / 511
Регистрация: 11.11.2010
Сообщений: 10,802
23.09.2013, 18:05  [ТС] #35
Особенности кодинга под x64
(часть третья)
Простое оконное приложение на Win64
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
format PE64 GUI 5.0
entry start
 
include 'win64a.inc'
 
section '.data' data readable writeable
 
main_hwnd   dq ?
msg     MSG
wc      WNDCLASS
    
hInst       dq ?
szTitleName db 'Window work sample Win64',0
szClassName db 'ASMCLASS32',0
 
button_class    db 'BUTTON',0
AboutTitle  db 'About',0
AboutText   db 'First win64 window program',0;
ExitTitle   db 'Exit',0
 
AboutBtnHandle  dq ?
ExitBtnHandle   dq ?
 
section '.code' code executable readable
 
start:  sub rsp, 8*5           ; align stack and alloc space for 4  parameters
 
    xor rcx, rcx
    call [GetModuleHandle]
 
    mov [hInst], rax
    mov [wc.style], CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS
    mov rbx, WndProc
    mov [wc.lpfnWndProc],  rbx
    mov [wc.cbClsExtra], 0
    mov [wc.cbWndExtra], 0
    mov [wc.hInstance], rax
 
    mov rdx, IDI_APPLICATION
    xor rcx, rcx
    call [LoadIcon]
    mov [wc.hIcon], rax
 
    mov rdx, IDC_ARROW
    xor rcx, rcx
    call [LoadCursor]
    mov [wc.hCursor], rax
 
    mov [wc.hbrBackground], COLOR_BACKGROUND+1
    mov qword [wc.lpszMenuName], 0
    mov rbx, szClassName
    mov qword [wc.lpszClassName], rbx
 
    mov rcx, wc
    call [RegisterClass]
 
    sub rsp, 8*8    ; alloc place in stack for 8 parameters
 
    xor rcx, rcx
    mov rdx, szClassName
    mov r8, szTitleName
    mov r9, WS_OVERLAPPEDWINDOW
    mov qword [rsp+8*4], 50
    mov qword [rsp+8*5], 50
    mov qword [rsp+8*6], 300
    mov qword [rsp+8*7], 250
    mov qword [rsp+8*8], rcx
    mov qword [rsp+8*9], rcx
    mov rbx, [hInst]
    mov [rsp+8*10], rbx
    mov [rsp+8*11], rcx
    call [CreateWindowEx]
    mov [main_hwnd], rax
 
    xor rcx, rcx
    mov rdx, button_class
    mov r8, AboutTitle
    mov r9, WS_CHILD
    mov qword [rsp+8*4], 50
    mov qword [rsp+8*5], 50
    mov qword [rsp+8*6], 200
    mov qword [rsp+8*7], 50
    mov rbx, [main_hwnd]
    mov qword [rsp+8*8], rbx
    mov qword [rsp+8*9], rcx
    mov rbx, [hInst]
    mov [rsp+8*10], rbx
    mov [rsp+8*11], rcx
    call [CreateWindowEx]
    mov [AboutBtnHandle], rax
 
    xor rcx, rcx
    mov rdx, button_class
    mov r8, ExitTitle
    mov r9, WS_CHILD
    mov qword [rsp+8*4], 50
    mov qword [rsp+8*5], 150
    mov qword [rsp+8*6], 200
    mov qword [rsp+8*7], 50
    mov rbx, [main_hwnd]
    mov qword [rsp+8*8], rbx
    mov qword [rsp+8*9], rcx
    mov rbx, [hInst]
    mov [rsp+8*10], rbx
    mov [rsp+8*11], rcx
    call [CreateWindowEx]
 
    mov [ExitBtnHandle], rax
 
    add rsp, 8*8         ; free place in stack
 
    mov rdx, SW_SHOWNORMAL
    mov rcx, [main_hwnd]
    call [ShowWindow]
 
    mov rcx, [main_hwnd]
    call [UpdateWindow]
 
    mov rdx, SW_SHOWNORMAL
    mov rcx, [AboutBtnHandle]
    call [ShowWindow]
 
    mov rdx, SW_SHOWNORMAL
    mov rcx, [ExitBtnHandle]
    call [ShowWindow]
 
  msg_loop: xor r9, r9
    xor r8, r8
    xor rdx, rdx
    mov rcx, msg
    call  [GetMessage]
 
    cmp rax,1
    jb  end_loop
    jne msg_loop
 
    mov rcx,  msg
    call [TranslateMessage]
    mov rcx,  msg
    call [DispatchMessage]
    jmp msg_loop
 
  end_loop:
    xor rcx, rcx
    call [ExitProcess]
Код не содержит ничего нового, кроме вызова fastcall-функций. В самом начале
Assembler
1
sub rsp,8*5
резервируется место в стеке для четырех параметров и происходит выравнивание стека на границу 16 байт (32 байта для параметров + 8 байт для выравнивания = 40 байт). Перед использованием функции CreateWindowsEx в стеке резервируется дополнительно 64 байта под восемь параметров для этой функции (функция принимает двенадцать параметров; место под четыре параметра уже зарезервировано).
Основной код программы написан — теперь осталось добавить функцию окна
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
proc WndProc hwnd, wmsg, wparam, lparam
    ;
    ; rdx = wmsg
    ; r8  = wparam
    ; r9  = lparam
    ; stack aligned!
    sub rsp, 8*4  ;  alloc space for 4  parameters
 
    cmp rdx, WM_DESTROY
    je  .wmdestroy
    cmp rdx, WM_COMMAND
    jne .default
    mov rax, r8
    shr rax, 16
    cmp rax, BN_CLICKED
    jne .default
    cmp r9, [AboutBtnHandle]
    je  .about
    cmp r9, [ExitBtnHandle]
    je  .wmdestroy
 
.default:   call [DefWindowProc]
    jmp .finish
 
.about: xor rcx, rcx
    mov rdx, AboutText
    mov r8, AboutTitle
    xor r9, r9
    call [MessageBox]
    jmp .finish
 
.wmdestroy: xor rcx, rcx
    call [ExitProcess]
.finish:
    add rsp, 8*4  ; restore stack
    ret
endp
В оконной функции для вызова функции DefWindowProc и MessageBox сразу резервируется в стеке место для четырех параметров. В начале оконной функции происходит проверка кода сообщения (код содержится в регистре RDX), и, если этот код не равен ни WM_DESTROY, ни WM_COMMAND, то происходит вызов функции DefWindowProc. При этом нет необходимости передавать ей параметры — все параметры уже находятся на своих местах, так как и функция окна и функция DefWindowProc используют одни и те же параметры.
В оконной функции нет необходимости выравнивать указатель стека на 16 байт, так как системная функция, которая вызывает оконную функцию, использует выровненный стек.
Завершаем программу описанием импортируемых функций
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
section '.relocs' fixups readable writeable
 
section '.idata' import data readable writeable
 
  library kernel,'KERNEL32.DLL',\
      user,'USER32.DLL'
 
  import kernel,\
     GetModuleHandle,'GetModuleHandleA',\
     ExitProcess,'ExitProcess'
 
  import user,\
     RegisterClass,'RegisterClassA',\
     CreateWindowEx,'CreateWindowExA',\
     DefWindowProc,'DefWindowProcA',\
     GetMessage,'GetMessageA',\
     TranslateMessage,'TranslateMessage',\
     DispatchMessage,'DispatchMessageA',\
     LoadCursor,'LoadCursorA',\
     LoadIcon,'LoadIconA',\
     ShowWindow,'ShowWindow',\
     UpdateWindow,'UpdateWindow',\
     MessageBox,'MessageBoxA'
4
Mikl___
Автор FAQ
11021 / 5795 / 511
Регистрация: 11.11.2010
Сообщений: 10,802
29.09.2013, 09:31  [ТС] #36
Где скачать компилятор для ассемблера?
Компиляторы языков высокого уровня (Си, Паскаль) в определенной степени совместимы между собой и хотя исходный текст, предназначенный для одного компилятора, не всегда без переделок транслируется на другом, синтаксис и прочие языковые концепции остаются неизменными, позволяя "летать" между MS VC, Intel C++, GCC, Open WATCOM, сравнивая полноту поддержки Стандарта, скорость трансляции, качество кодогенерации, популярность компилятора и вытекающее отсюда изобилие (недостаток) библиотек и компонентов к нему.
С трансляторами ассемблера все обстоит иначе. Казалось бы, x86 ассемблер — он и в Африке ассемблер, но нет! Кроме поддержки мнемоник машинных команд, каждый транслятор обладает своим собственным набором директив и макросредств, зачастую ни с чем не совместимых. Ассемблерный листинг под MASM, бесполезно переносить на FASM, поскольку возможности, предоставляемые макросредствами, у них неодинаковые.
Человеку, упорно игнорирующего существование Linux/BSD, абсолютно все равно, на какое количество платформ был перенесен данный транслятор. А для кого-то это вопрос первостепенной важности!
Тем не менее, существует ряд основополагающих критериев, существенных для всех категорий программистов. Начнем с генерации отладочной информации, без которой отладка программы сложнее, чем "hello, word", превращается в настоящую пытку. Кое-кто попытается возразить, что ассемблерам, в отличии от языков высокого уровня, отладочная информация не нужна, ведь мнемоники машинных команд, что в листинге, что в отладчике — одни и те же. А метки?! А структуры?! А имена функций?! Уберите их — и код станет совершенно не читаемым! Можно воспользоваться отладочной печатью (просто вставлять макрос, выводящий значение регистров/переменных на экран/файл в указанных местах). Еще можно отлаживать программу, держа перед носом распечатку исходных текстов.
Проблема в том, что формат отладочной информации не стандартизован и различные трансляторы используют различные форматы, что ограничивает в выборе отладчиков или вынуждает использовать конвертеры сторонних производителей, FASM вообще не генерирует отладочной информации.
Если формат отладочной информации — это, скорее, проблема для профессионалов, то формат выходных файлов касается всех. Обыкновенный obj, из которого с помощью линкера можно изготовить все, что угодно — от exe до dll. Объектные файлы делятся на
  • omf (в редакциях от Microsoft и IBM)
  • coff, elf, aout
  • кучу разной экзотики в стиле as86, rdf, ieee и т.д
Также заслуживает внимания возможность "сквозной" генерации двоичных файлов, не требующая помощи со стороны линкера. А некоторые ассемблеры (например, FASM) даже позволяют "вручную" генерировать исполняемые файлы и динамические библиотеки различных форматов, полностью контролируя процесс их создания и заполняя ключевые поля по своему усмотрению. Впрочем, программы, целиком написанные на ассемблере — это либо вирусы, либо демки, либо учебные. Обычно на ассемблере пишутся лишь системно-зависимые компоненты или модули, критичные к быстродействию, которые затем линкуются к основному проекту и, если ассемблер генерирует только omf, а компилятор — coff, тогда возникает проблема сборки "разнокалиберных" форматов воедино. Линкер, умеющий это делать — ulink от Юрия Харона, он же обеспечивает возможности по сборке файлов "вручную", так что выбор конкретного ассемблерного транслятора целиком лежит на совести (и компетенции) программиста, но все-таки лучше, чтобы и ассемблер, и компилятор генерировали одинаковые форматы объектных файлов.
Другой немаловажный критерий — количество поддерживаемых процессорных архитектур, которых в линейке x86 набралось уже больше десятка. Конечно, недостающие команды можно реализовать с помощью макросов или запрограммировать непосредственно в машинном коде через директиву DB, но... если так рассуждать, то зачем вообще нужны ассемблеры, когда есть hex-редакторы?! Особое внимание следует обратить на платформы AMD x86-64 и Intel IA64. 64-разрядные архитектуры вытесняют x86, поэтому учиться программировать под них обязательно, так что поддержка со стороны транслятора должна быть обеспечена уже сейчас!
Ни один из трансляторов не поддерживает набор команд x86-процессоров в полном объеме. Например, на MASM'е невозможно написать jmp 0007h:00000000h и приходится прибегать к различным ухищрениям: либо реализовать команду через DB, что ненаглядно и неудобно, либо заталкивать в стек сегмент/смещение, а потом делать retf, но это длинно и к тому же воздействует на стек, которого у нас может и не быть.
Программирование на смеси 16- и 32-разрядного кода с кратковременным переходом в защищенный режим и возвращением обратно в реальный — на MASM'е скорее умрешь, чем такое запрограммируешь, но большинству программистов подобное трюкачество просто не нужно!
А вот что реально нужно большинству — так это интеграция в мировое сообщество. Свои собственные оси обычно пишут новички, только что начитавшиеся Юрова/Зубкова и открывшие для себя защищенный режим с его безграничными возможностями. В учебном плане это очень даже хорошо, но коммерческим программистам обычно приходится программировать под уже существующие системы — ту же Windows, например. И, если в состав DDK входит MASM в кучей исходных текстов драйверов, то пытаться собрать их под другим транслятором означает впустую убивать время. Опять-таки, если компилятору Microsoft Visual C++ задать ключ /FA, то он выдаст ассемблерный листинг в стиле MASM, точно так же поступит и IDA Pro, Borland C++ выберет TASM, а GCC - GNU Assembler (он же GAS). Вот это и называется интеграцией в среду программирования. Чистый ассемблер сам по себе мало кому интересен и практически всегда становится приложением к чему-то еще. Если вы пишите драйвера под Windows на Microsoft Visual C++, то разумнее всего остановить свой выбор на MASM'е, поклонникам же Borland C++ лучше TASM'а ничего не найти. Под Linux/BSD рулит GAS (GNU Assembler) уже хотя бы за счет того, что ассемблерные программы можно транслировать с помощью компилятора GCC, используя Си-сопроцессор с одной стороны и освобождаясь от головной боли с поиском стартового кода и библиотек, однако GAS использует AT&T-синтаксис, являющийся полной противоположностью Intel-синтаксису, которого придерживаются MASM, TASM, FASM, NASM/YASM. Разработчики вирусов и просто маленьких ассемблерных программ, написанных из любви к искусству, намного меньше ограничены в своем выборе и могут использовать все, что им по душе, вне зависимости от степени "поддержки".
Качество документирования играет весьма важную роль и еще важнее — кому принадлежит проект. Трансляторы, созданные энтузиастами-одиночками, могут умереть в любой момент, поэтому закладываться на них в долговременной перспективе не стоит. Коммерческие ассемблеры крупных компаний выглядят намного более стабильными и непоколебимыми, однако никаких гарантий, что в один "прекрасный" момент компания не забросит на своей продукт, у нас нет (достаточно вспомнить историю с TASM'ом). Открытые трансляторы, поддерживаемые независимой группой лиц, наиболее живучи. Стоило коллективу NASM'а чуть-чуть приостановить его развитие, как тут же появился YASM — "позаимствовавший" исходные тексты и добавивший все необходимое (поддержку x86-64, формат отладочной информации CodeView и так далее).
Последнее, на чем хотелось бы заострить внимание — это макросредства. Отношение к ним у программистов двоякое. Одни во всю пользуются плодами прогресса, программируя на смеси ассемблера, бейсика и препроцессора Си (существует даже проект HLA: High Level Assembler — Ассемблер Высокого Уровня), другие — презирают их, ратуя за чистоту ассемблерного кода. Макросы упрощают программирование, зачастую позволяя невозможное (например, шифровать код программы еще на стадии ассемблирования!), но переносимость программы от этого резко ухудшается и переход на другой транслятор становится труднореализуемым. Но как бы там ни было, поддержка макросов совсем не обязывает этими макросами пользоваться!
15
Мотороллер
431 / 242 / 38
Регистрация: 05.08.2013
Сообщений: 1,659
Завершенные тесты: 1
06.10.2013, 18:27 #37
сортировка вставками.
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
format PE console
include 'include\win32a.inc'
entry start
 
section '.data' data readable writeable
        array dd 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -100500
        size=($-array)/4
 
section '.code' code readable executable
start:
        stdcall insert_sort, array, size
        invoke ExitProcess, 0
 
insert_sort:
i equ ecx
j equ edx
        push ebp
        mov ebp, esp
        pushad
 
        mov esi, [ebp+8]     ;esi=array
        mov i, 1             ;i=1
is1:    cmp i, [ebp+12]      ;i<size
        ja is_exit
        mov eax, [esi+i*4]    ;eax=key=array[i]
        mov j, i              ;j=i-1
        dec j
is2:    cmp j, 0              ;j>=o
        jl is3
        cmp [esi+j*4], eax    ;array[j]>key
        jl is3
        mov ebx, [esi+j*4]    ;array[j+1]=array[j]
        mov [esi+j*4+4], ebx
        dec j                 ;j-=1
        jmp is2
is3:
        mov [esi+j*4+4], eax  ;array[j+1]=key
        inc i
        jmp is1
is_exit:
        popad
        leave
        ret 8
 
section '.idata' import data readable writeable
library KERNEL32, 'KERNEL32.DLL'
 
import KERNEL32,\
       ExitProcess, 'ExitProcess'
C
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
#include <stdio.h>
#include <stdlib.h>
 
void print_array(int*, int);
void ins_sort(int*, int);
 
int main(int argc, char *argv[])
{
  int array[]={9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -500};
  print_array(array, sizeof(array)/sizeof(int));
  putchar('\n');
  ins_sort(array, sizeof(array)/sizeof(int));
  print_array(array, sizeof(array)/sizeof(int));
  getchar();
  return 0;
}
 
 
void print_array(int* array, int size)
{
     int i;
     for (i=0; i<size; ++i)
         printf("%d ", array[i]);
     
}
 
void ins_sort(int* array, int size)
{
     int i, j, key;
     for (i=1; i<size; ++i)
     {
         key=array[i];
         j=i-1;
         while (j>=0 && array[j]>key)
         {
               array[j+1]=array[j];
               j-=1;
         }
         array[j+1]=key;
     }
}
4
Not at all!
108 / 190 / 18
Регистрация: 06.10.2013
Сообщений: 360
13.11.2013, 12:24 #38
Как известно, ml64.exe не имеет встроенного макроса invoke, поэтому код сильно разбухает из-за необходимости передавать аргументы функций вручную. Представляю макрос, который облегчит труд программиста.

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
invoke macro function,args:VARARG
local txt,arg,arg1
txt textequ <>
cnt = 0
 
for arg,<args>
txt catstr <arg>,<!,>,txt
cnt = cnt+1
endm
 
n = cnt
k=n mod 2
 
IF n gt 4
sub rsp,(n+k)*8
ELSE
sub rsp,20h
ENDIF
 
% for arg,<txt>
cnt = cnt-1
m=0
 
len sizestr <arg>
addr_ instr 1,<arg>,<addr>
offset_ instr 1,<arg>,<offset>
 
IF addr_ eq 1
arg1 substr <arg>,5,len-4
lea rax,arg1
m=1
 
ELSEIF offset_ eq 1
IF cnt gt 4
arg1 substr <arg>,7,len-6
lea rax,arg1
m=1
ENDIF
ENDIF
 
IF cnt eq 0
IF m eq 0
mov rcx,arg
ELSE
mov rcx,rax
ENDIF
 
ELSEIF cnt eq 1
IF m eq 0
mov rdx,arg
ELSE
MOV rdx,rax
ENDIF
 
ELSEIF  cnt eq 2
IF m eq 0
mov r8,arg
ELSE
mov r8,rax
ENDIF
 
ELSEIF cnt eq 3
IF m eq 0
mov r9,arg
ELSE
mov r9,rax
ENDIF
 
ELSE
IF m eq 1
mov [rsp+(cnt)*8],rax
ELSE
mov qword ptr [rsp+(cnt)*8],arg
ENDIF
ENDIF
endm
 
call function
IF n gt 4
add rsp,(n+k)*8
ELSE
add rsp,20h
ENDIF
endm
Пример использования:
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
includelib f:\masm32\lib64\kernel32.lib
include f:\masm32\include64\win64.inc
include f:\masm32\include64\rtl.inc
include f:\masm32\macros\x64.mac
 
extrn CreateProcessA:proc
extrn CloseHandle:proc
 
.data
cmd db 'c:\windows\system32\cmd.exe',0
pi PROCESS_INFORMATION <>
 
.code
start proc uses rbx rdi rsi
local stin:STARTUPINFO
 
invoke CreateProcessA,offset cmd,0,0,0,0,0,0,0,        addr           stin,   offset  pi
 
invoke CloseHandle,pi.hThread
invoke CloseHandle,pi.hProcess
 
ret
start endp
end
Добавлено через 1 минуту
Addr и offset можно использовать как для глобальных переменных, так и для стековых.

Добавлено через 1 час 21 минуту
Макросы, позволяющие обработать исключение на проблемном участке код, устанавливающие и снимающие обработчик SEH. Условие многократного использования одного обработчика исключений - наличие глобальной переменной, для записи смещения, на которое будет передано управление после обработки исключения. Макрос except должен находиться вне процедуры, в которой может возникнуть исключение.

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
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
try macro
 
push offset Ex_handler
push fs:[0]
mov fs:[0],esp
 
endm
 
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
except macro Finally_
Ex_handler::
 
push ebp
mov ebp,esp
 
mov eax,[ebp+16]
mov ecx,Finally_
mov [eax+0b8h],ecx
 
pop ebp
xor eax,eax
ret 16
 
endm
 
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
finally macro
 
pop fs:[0]
add esp,4
 
endm
 
; -------------------------------------------------------------------------
Пример использования:

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
.686
.model flat,stdcall
option casemap:none
 
include \masm32\macros\du.mac
 
.data
 
_finally dd 0
 
.code
assume fs:nothing
start:
 
mov _finally,@f
try
 
xor eax,eax
mov [eax],eax ; здесь будет вызвано исключение, которое будет обработано в процедуре except
 
@@:
finally
 
mov _finally,@f
try
 
xor eax,eax
mov [eax],eax ; здесь будет вызвано исключение, которое будет обработано в процедуре except
 
@@:
finally
 
ret
 
except _finally
end start
5
Not at all!
108 / 190 / 18
Регистрация: 06.10.2013
Сообщений: 360
21.11.2013, 10:26 #39
Тема на одном из форумов натолкнула меня на написание драйвера, позволяющего установить глобальный хук на всё время работы системы до перезагрузки. В данном случае будет изменена функция MessageBoxA из библиотеки user32.dll. Это пример для Windows x32, но и на х64 можно сделать подобное. Требуется только цифровая подпись драйвера.


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
UNICODE__STRING struct
Length_     dw ?
MaxLength   dw ?
Buffer      dd ?
UNICODE__STRING ends
 
unis macro name,string
local s,e
len sizestr <string>
 
align 4
sstr substr <string>,2,len-2
 
s label word
 
% forc n,<sstr>
dw '&n'
endm
e dw 0
align 4
k=(e-s)
name UNICODE__STRING {k,k+2,s}
endm
 
 
.686P
.model flat,stdcall
option casemap:none
 
include \masm32\include\ntstatus.inc
include \masm32\include\ntddk.inc
include \masm32\include\ntoskrnl.inc
include \masm32\include\native.inc
 
includelib \masm32\lib\ntoskrnl.lib
include \masm32\macros\strings.mac
include \masm32\macros\du.mac
 
.const
 
function db 'MessageBoxA',0
unis ndirectory,'\KnownDlls'
unis ndll,'user32.dll'
fin dd fin_
 
.code
assume fs:nothing
 
DriverUnload proc uses ebx edi esi pdrobject:dword
 
invoke DbgPrint,$CTA0("UNLOADING")
 
mov eax,STATUS_SUCCESS
 
ret
DriverUnload endp
 
; -------------------------------------------------------------------------
 
.code; INIT
 
driver_entry proc uses ebx edi esi pdrobject:dword,pregpath:dword
local oa:OBJECT_ATTRIBUTES
local iob:IO_STATUS_BLOCK
local li:LARGE_INTEGER
local hdll,pmem,hdir,hsec,cbytes:dword
local pspace:dword
 
and pmem,0
and li.LowPart,0
and li.HighPart,0
 
invoke RtlZeroMemory,addr oa,sizeof oa
mov oa._Length,sizeof oa
lea eax,ndirectory
mov oa.ObjectName,eax
mov oa.Attributes,OBJ_KERNEL_HANDLE
 
invoke ZwOpenDirectoryObject,addr hdir,0fh,addr oa   ; получаем описатель директории \KnownDlls
test eax,eax
js @1
invoke DbgPrint,$CTA0("Handle directory 0x%X"),hdir
 
push hdir
pop oa.RootDirectory
lea eax,ndll
mov oa.ObjectName,eax
invoke ZwOpenSection,addr hsec,SECTION_ALL_ACCESS,addr oa       ; открываем секцию user32.dll  
test eax,eax
js @f
 
invoke NtClose,hdir
 
invoke DbgPrint,$CTA0("Handle section 0x%x"),hsec
                                  ; отображаем в адресное пространство своего процесса
invoke ZwMapViewOfSection,hsec,-1,addr pmem,0,li.LowPart,0,addr li,1,0,PAGE_EXECUTE_READWRITE
test eax,eax
js @f
 
push pmem
call get_address
test eax,eax
js @f
 
mov ebx,eax
 
push pmem
call get_space
 
mov pspace,eax
mov eax,(get_space-my_messagebox)
cmp ecx,eax
ja @ok
 
invoke DbgPrint,$CTA0("Free space is too small, fail...")
jmp @un
 
@ok:
try
cli
mov eax,cr0                 ; сбрасываем бит WP в регистре CR0, тем самым разрешая запись 
and eax,0fffeffffh          ; на readonly страницы
mov cr0,eax
sti
 
mov eax,[ebx]                   ; несмотря на то, что запись разрешена, попытка записи без предварительного 
mov byte ptr [ebx],0e8h     ;чтения приведёт  к краху 
mov eax,pspace                 ; правим функцию MessageBoxA
sub eax,5
sub eax,ebx
mov [ebx+1],eax
 
mov edi,pspace
mov eax,[edi]
mov ecx,(get_space-my_messagebox)
mov esi,offset my_messagebox
rep movsb                   ; теперь до перезагрузки системы любой диалог, выданный функцией MessageBoxA
                            ; будет иметь загловок и текст "Hook!"
 
cli
mov eax,cr0
or eax,10000h
mov cr0,eax
sti
 
invoke DbgPrint,$CTA0("MessageBox address 0x%X"),ebx
fin_::
finally
 
@un:
invoke ZwUnmapViewOfSection,-1,pmem            ; закрываем проекцию секции, при этом в ней сохранятся
                                                                      ; все сделанные изменения.
@@:
invoke NtClose,hsec
 
@1:
 
invoke DbgPrint,$CTA0("Last Error 0x%X"),eax
 
mov eax,pdrobject
 
mov (DRIVER_OBJECT ptr [eax]).DriverUnload,offset DriverUnload
 
mov eax,STATUS_SUCCESS
ret
driver_entry endp
 
except fin
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
my_messagebox proc
 
call @f
@@:
pop eax
sub eax,offset @b
add eax,offset message
mov [esp+10h],eax
mov [esp+0ch],eax
mov eax,[esp]
add esp,4
push ebp
mov ebp,esp
jmp eax
message db 'Hook!',0
 
my_messagebox endp
 
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
get_space proc uses ebx edi esi psection
 
mov ebx,psection
push ebx
 
add ebx,(IMAGE_DOS_HEADER ptr [ebx]).e_lfanew
 
movzx esi,(IMAGE_NT_HEADERS ptr [ebx]).FileHeader.SizeOfOptionalHeader
movzx ecx,(IMAGE_NT_HEADERS ptr [ebx]).FileHeader.NumberOfSections
add esi,ebx
lea esi,[esi+sizeof IMAGE_FILE_HEADER+4]
mov edi,(IMAGE_NT_HEADERS ptr [ebx]).OptionalHeader.SizeOfCode
mov ebx,(IMAGE_NT_HEADERS ptr [ebx]).OptionalHeader.BaseOfCode
 
pop eax
add ebx,eax
invoke DbgPrint,$CTA0("ImageBase 0x%X, Base of Code 0x%X"),psection,ebx
 
@@:
cmp edi,(IMAGE_SECTION_HEADER ptr [esi]).SizeOfRawData
je @f
add esi,sizeof IMAGE_SECTION_HEADER
loop @b
xor eax,eax
jmp @ret
 
@@:
mov esi,(IMAGE_SECTION_HEADER ptr [esi]).Misc.VirtualSize
 
sub edi,esi
add ebx,esi
invoke DbgPrint,$CTA0("Base of space 0x%X, Size 0x%X"),ebx,edi
 
mov eax,ebx
mov ecx,edi
@ret:
ret
get_space endp
 
; -------------------------------------------------------------------------
 
get_address proc uses ebx edi esi psection
 
mov eax,psection
mov ebx,eax
 
add ebx,(IMAGE_DOS_HEADER ptr [eax]).e_lfanew
lea ebx,(IMAGE_NT_HEADERS ptr [ebx]).OptionalHeader
 
mov ebx,(IMAGE_OPTIONAL_HEADER ptr [ebx]).DataDirectory.VirtualAddress
add ebx,eax
 
lea esi,function
mov edi,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfNames
 
push ebx
mov ebx,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).NumberOfNames
 
lea edi,[edi+eax-4]
mov edx,-1
 
@@:
add edi,4
push edi
mov edi,[edi]
add edi,eax
 
inc edx
push esi
mov ecx,sizeof function-1
repe cmpsb
pop esi
pop edi 
je @f
dec ebx
jne @b
pop ebx
mov eax,-1
jmp @ret
 
@@:
pop ebx
mov esi,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfNameOrdinals
shl edx,1
add edx,esi
add edx,eax
movzx edx,word ptr [edx]
mov ebx,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfFunctions
add ebx,eax
shl edx,2
add edx,ebx
mov edx,[edx]
add eax,edx
 
@ret:
ret
get_address endp
end driver_entry
Коды драйверов под х32 и х64, позволяющие найти адрес неэкспортируемой ядром функции, в данных примерах это ZwProtectVirtualMemory. Сначала в библиотеке ntdll.dll находится номер соответствующего системного сервиса, затем, от адреса экспортируемой функции ядра по определённому смещению, фиксированному как в х32, так и в х64 системах проверяются номера системных сервисов до совпадения с требуемым. Имя функции, адрес которой взят за точку отсчёта, определено в дизассембелере. Источник вдохновения - та же тема на том же форуме, название которого здесь нельзя привести.

Кликните здесь для просмотра всего текста
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
.686P
.model flat,stdcall
option casemap:none
 
include \masm32\include\ntstatus.inc
include \masm32\include\ntddk.inc
include \masm32\include\ntoskrnl.inc
include \masm32\include\native.inc
 
includelib \masm32\lib\ntoskrnl.lib
include \masm32\macros\strings.mac
include \masm32\macros\du.mac
 
.const
 
nfunction db 'ZwProtectVirtualMemory',0
unis function,'ZwPulseEvent'
unis nfile,'\SystemRoot\system32\ntdll.dll'
 
.code
assume fs:nothing
 
DriverUnload proc uses ebx edi esi pdrobject:dword
 
invoke DbgPrint,$CTA0("UNLOADING")
 
mov eax,STATUS_SUCCESS
 
ret
DriverUnload endp
 
; -------------------------------------------------------------------------
 
.code; INIT
 
driver_entry proc uses ebx edi esi pdrobject:dword,pregpath:dword
local oa:OBJECT_ATTRIBUTES
local iob:IO_STATUS_BLOCK
local li:LARGE_INTEGER
local hfile,pmem,hdo,hsec:dword
 
and pmem,0
and li.LowPart,0
and li.HighPart,0
 
invoke RtlZeroMemory,addr oa,sizeof oa
mov oa._Length,sizeof oa
lea eax,nfile
mov oa.ObjectName,eax
mov oa.Attributes,OBJ_KERNEL_HANDLE
 
invoke ZwCreateFile,addr hfile,FILE_READ_DATA+FILE_EXECUTE,addr oa,addr iob,0,0,FILE_SHARE_READ,FILE_OPEN,0,0,0
test eax,eax
js @1
invoke DbgPrint,$CTA0("Handle file 0x%X"),hfile
 
mov oa.ObjectName,0
invoke ZwCreateSection,addr hsec,SECTION_MAP_EXECUTE+SECTION_MAP_READ,addr oa,0,PAGE_EXECUTE_READ,SEC_IMAGE,hfile
push eax
invoke NtClose,hfile
pop eax
test eax,eax
js @1
 
invoke DbgPrint,$CTA0("Handle section 0x%x"),hsec
 
invoke ZwMapViewOfSection,hsec,-1,addr pmem,0,li.LowPart,0,addr li,1,0,PAGE_EXECUTE_READ
test eax,eax
js @f
 
push pmem
call get_service_number
test eax,eax
js @f
 
mov ebx,[eax]
mov eax,ebx
shr eax,8
 
invoke DbgPrint,$CTA0("Service number 0x%X"),eax
invoke ZwUnmapViewOfSection,-1,pmem
 
call search
test eax,eax
jz @1
invoke DbgPrint,$CTA0("Kernel address 0x%X"),eax
 
@@:
invoke NtClose,hsec
 
@1:
 
invoke DbgPrint,$CTA0("Last Error 0x%X"),eax
 
mov eax,pdrobject
 
mov (DRIVER_OBJECT ptr [eax]).DriverUnload,offset DriverUnload
 
mov eax,STATUS_SUCCESS
ret
driver_entry endp
 
; -------------------------------------------------------------------------
search proc uses ebx edi esi
local psearch:dword
 
invoke MmGetSystemRoutineAddress,addr function
test eax,eax
js @r
 
mov psearch,eax
mov esi,eax
mov edx,14h
mov edi,2
 
@1:
mov ecx,30
 
@@:
mov eax,[esi]
cmp eax,ebx
je @f
add esi,edx
loop @b
 
mov esi,psearch
neg edx
dec edi
jnz @1
 
xor eax,eax
jmp @r
 
@@:
mov eax,esi
@r:
ret
search endp
 
; -------------------------------------------------------------------------
 
get_service_number proc uses ebx edi esi psection
 
mov eax,psection
mov ebx,eax
 
add ebx,(IMAGE_DOS_HEADER ptr [eax]).e_lfanew
lea ebx,(IMAGE_NT_HEADERS ptr [ebx]).OptionalHeader
 
mov ebx,(IMAGE_OPTIONAL_HEADER ptr [ebx]).DataDirectory.VirtualAddress
add ebx,eax
 
lea esi,nfunction
mov edi,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfNames
 
push ebx
mov ebx,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).NumberOfNames
 
lea edi,[edi+eax-4]
mov edx,-1
 
@@:
add edi,4
push edi
mov edi,[edi]
add edi,eax
 
inc edx
push esi
mov ecx,sizeof nfunction-1
repe cmpsb
pop esi
pop edi 
je @f
dec ebx
jne @b
pop ebx
mov eax,-1
jmp @ret
 
@@:
pop ebx
mov esi,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfNameOrdinals
shl edx,1
add edx,esi
add edx,eax
movzx edx,word ptr [edx]
mov ebx,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfFunctions
add ebx,eax
shl edx,2
add edx,ebx
mov edx,[edx]
add eax,edx
 
@ret:
ret
get_service_number endp
end driver_entry
 
;x64
 
include f:\masm32\include64\win64.inc
include f:\masm32\include64\ntddk.inc
include f:\masm32\macros\strings.mac
include f:\masm32\include64\ntdef.inc
include f:\masm32\include64\ntstatus.inc
include f:\masm32\include64\rtl.inc
 
includelib f:\masm32\lib64\ntoskrnl.lib
include f:\masm32\macros\x64.mac
 
extrn DbgPrint:proc
extrn RtlZeroMemory:proc
extrn ZwCreateFile:proc
extrn ZwCreateSection:proc
extrn ZwMapViewOfSection:proc
extrn ZwUnmapViewOfSection:proc
extrn NtClose:proc
extrn MmGetSystemRoutineAddress:proc
 
.const
 
nfunction db 'ZwProtectVirtualMemory',0
unis function,'ZwQuerySection'
unis nfile,'\SystemRoot\system32\ntdll.dll'
 
.code
 
DriverUnload proc uses rbx rdi pdrobject:qword
mov pdrobject,rcx
 
invoke DbgPrint,$CTA0("Unload")
 
mov rax,STATUS_SUCCESS
ret
DriverUnload endp
 
; -------------------------------------------------------------------------
 
.code
 
driver_entry proc uses rbx rdi rsi pdrobject:qword,pregpath:qword
local oa:OBJECT_ATTRIBUTES
local iob:IO_STATUS_BLOCK
local li:LARGE_INTEGER
local hfile:qword
local hsec:qword
local hdo:qword
local pmem:qword
 
mov pdrobject,rcx
mov pregpath,rdx
 
and pmem,0
and li.LowPart,0
and li.HighPart,0
 
invoke RtlZeroMemory,addr oa,sizeof oa
mov oa._Length,sizeof oa
lea rax,nfile
mov oa.ObjectName,rax
mov oa.Attributes,OBJ_KERNEL_HANDLE
 
invoke ZwCreateFile,addr hfile,FILE_READ_DATA+FILE_EXECUTE,addr oa,addr iob,0,0,FILE_SHARE_READ,FILE_OPEN,0,0,0
test eax,eax
js @1
invoke DbgPrint,$CTA0("Handle file 0x%X"),hfile
 
mov oa.ObjectName,0
invoke ZwCreateSection,addr hsec,SECTION_MAP_EXECUTE+SECTION_MAP_READ,addr oa,0,PAGE_EXECUTE_READ,SEC_IMAGE,hfile
push rax
 
invoke NtClose,hfile
pop rax
test eax,eax
js @1
 
invoke DbgPrint,$CTA0("Handle section 0x%x"),hsec
 
invoke ZwMapViewOfSection,hsec,-1,addr pmem,0,li.QuadPart,0,addr li,1,0,PAGE_EXECUTE_READ
test eax,eax
js @f
 
invoke DbgPrint,$CTA0("Memory address 0x%X"),pmem
mov rcx,pmem
sub rsp,20h
call get_service_number
add rsp,20h
 
test eax,eax
js @f
 
add eax,3
mov ebx,[eax]
mov eax,ebx
shr eax,8
 
invoke DbgPrint,$CTA0("Service number 0x%X"),rax
invoke ZwUnmapViewOfSection,-1,pmem
 
sub rsp,20h
call search
add rsp,20h
test eax,eax
jz @1
mov rbx,rax
shr rax,32
invoke DbgPrint,$CTA0("ZwProtectVirtualMemory address 0x%X%X"),rax,rbx
 
@@:
invoke NtClose,hsec
 
@1:
 
invoke DbgPrint,$CTA0("Last Error 0x%X"),rax
 
mov rax,pdrobject
lea rcx,DriverUnload
mov (DRIVER_OBJECT ptr [rax]).DriverUnload,rcx
mov rax,STATUS_SUCCESS
ret
driver_entry endp
 
; -------------------------------------------------------------------------
search proc uses rbx rdi rsi
local psearch:qword
 
invoke MmGetSystemRoutineAddress,addr function
test eax,eax
jz @r
push rax
mov rcx,rax
shr rax,32
invoke DbgPrint,$CTA0("ZwQuerySection address 0x%X%X"),rax,rcx
 
pop rax
add rax,14h
 
mov psearch,rax
mov rsi,rax
mov rdx,20h
mov rdi,2
 
@1:
mov rcx,50
 
@@:
mov rax,[rsi]
cmp ax,bx
je @f
add rsi,rdx
loop @b
 
mov rsi,psearch
neg rdx
dec rdi
jnz @1
 
xor eax,eax
jmp @r
 
@@:
mov rax,rsi
sub rax,14h
@r:
ret
search endp
 
; -------------------------------------------------------------------------
 
get_service_number proc uses rbx rdi rsi psection:qword
mov psection,rcx
mov rax,rcx
mov rbx,rax
 
and rdi,0
add ebx,(IMAGE_DOS_HEADER ptr [rax]).e_lfanew
lea rbx,(IMAGE_NT_HEADERS ptr [ebx]).OptionalHeader
 
mov ebx,(IMAGE_OPTIONAL_HEADER ptr [rbx]).DataDirectory.VirtualAddress
add ebx,eax
 
lea rsi,nfunction
mov edi,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfNames
 
push rbx
and rbx,0ffffffffh
mov ebx,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).NumberOfNames
 
lea edi,[edi+eax-4]
mov rdx,-1
 
@@:
add rdi,4
push rdi
mov edi,[rdi]
add edi,eax
 
inc edx
push rsi
mov ecx,sizeof nfunction-1
repe cmpsb
pop rsi
pop rdi 
je @f
 
dec ebx
jne @b
pop rbx
mov eax,-1
jmp @ret
 
@@:
pop rbx
mov esi,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfNameOrdinals
shl rdx,1
add edx,esi
add edx,eax
movzx edx,word ptr [edx]
mov ebx,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfFunctions
add ebx,eax
shl edx,2
add edx,ebx
mov rdx,[edx]
add eax,edx
 
@ret:
ret 
get_service_number endp
end

Макрос unis для х64.
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
UNICODE__STRING struct
Length_     dw ?
MaxLength   dw ?
Reserve     dd 0
Buffer      dq ?
UNICODE__STRING ends
 
unis macro name,string
local s,e
len sizestr <string>
 
align 4
sstr substr <string>,2,len-2
 
s label word
 
% forc n,<sstr>
dw '&n'
endm
e dw 0
align 8
k=(e-s)
name UNICODE__STRING {k,k+2,0,s}
endm
4
Mikl___
Автор FAQ
11021 / 5795 / 511
Регистрация: 11.11.2010
Сообщений: 10,802
17.12.2013, 06:18  [ТС] #40
  1. Встроенный ассемблер реализует
    • команды для i8086 по умолчанию
    • команды i286 при наличии директивы {$G+}
    • команды сопроцессора при наличии директив {$G+} {$N+} {$E+}
    • 32-разрядные команды не поддерживаются
  2. для всех переменных, меток и комментариев используется синтаксис языка Pascal
  3. к встроенному ассемблеру обращаются через оператор
    Pascal
    1
    2
    3
    
    ASM
     команды ассемблера
    END;
  4. встроенный ассемблер не может (не должен) изменять содержимое регистров BP, SP, SS и DS
  5. встроенный ассемблер поддерживает только директивы DB, DW, DD
  6. все выражения вычисляются как 32-разрядные целые от -2147483648 до 4294967295
  7. значения выражений в вещественой форме не поддерживается
  8. Pascal воспринимает числа только в десятичной системе счисления или в шестнадцатеричном виде (префикс $)
  9. строковые константы в стиле языка Паскаль
  10. интерпретация параметра var как 32-разрядного указателя es:[регистр]. Требуется сначала параметр var командой les загрузить в регистр
    Assembler
    1
    
    les регистр,var-параметр
    а потом использовать как es:[регистр]
  11. в паскале есть директива assembler, которая сравнима с директивой external. Эта директива позволяет строить на Паскале ассемблерные процедуры по тем же правилам, что и внешние процедуры и функции
Например, требуется реализовать функцию Sum=x+y, переменные Sum, x и y определены как integer используем специальную переменную Паскаля @Result для возврата из функции значений
  1. на "чистом" паскале
    Pascal
    1
    2
    3
    4
    5
    6
    
    var 
    x,y,s : integer
    Function Sum (x,y:integer):integer;
    Begin
     Sum := x+y;
    End;
  2. передача параметров по значению
    Pascal
    1
    2
    3
    4
    5
    6
    7
    8
    
    Function Sum (x,y:integer):integer;
    begin
     asm
       mov ax,x
       add ax,y
       mov @Result,ax
     end;
    end;
  3. передача параметров по ссылке
    Pascal
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    Function Sum (var x,y:integer):integer;
    begin
     asm
      les di,x
      les bx,y
      mov ax,es:[di]  
      add ax,es:[bx]
       mov @Result,ax
     end;
    end;
  4. ассемблерная функция
Pascal
1
2
3
4
5
Function Sum (x,y:integer):integer; assembler;
asm
  mov ax,x
  add ax,y
end;
результат
Pascal
1
2
3
4
5
6
7
8
begin
 writeln('Вычислить: Sum = x + y для данных типа integer');
 write('Введите значение x');
 read(x);
 write('Введите значение y');
 read(y);
 s:=Sum(x,y);
 writeln(' ',x, ' + ',y,' = ',s);
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int FuncAsmI (int x)// эквивалент С-функции int FuncCI (int x) { return x/3;}
{ asm
   { mov ax,x
      cwd
      mov cx,3
      idiv cx
    }
}
unsigned int funcAsmU (unsigned int x)// эквивалент С-функции int FuncCu (unsigned int x) { return x/3;}
{ asm
   { mov ax,x
      xor dx,dx
      mov cx,3
      div cx
   }
}
  1. учитывайте, что имеете дело с 32-разрядной системой int — 32-разряда, short int — 16-разрядов
  2. оператор asm должен быть с двумя поддчерками __asm
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
short int FuncAsmI (short int x)
{ __asm
   { mov ax,x
      cwd
      mov cx,3
      idiv cx
    }
}
unsigned short int funcAsmU (unsigned short int x)
{ __asm
   { mov ax,x
      xor dx,dx
      mov cx,3
      div cx
   }
}
пример ассемблерной вставки, складывающей два числа
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
main()// обычный код на C++
{      int a = 1; // объявляем переменную a и кладем туда значение 1
        int b = 2; // объявляем переменную a и кладем туда значение 2
        int c;     // объявляем переменную c, но не инициализируем ее
        // а тут ассемблерная вставка
        __asm{
                mov eax, a   // загружаем значение переменной a в регистр EAX
                add eax, b // складываем a с b, записывая результат в EAX
                mov c, eax   // загружаем значение EAX в переменную c
        } // конец ассемблерной вставки
        // а потом снова обычный код на C++
        // выводим содержимое c на экран с помощью Сишной функции printf
        printf("a + b = %x + %x = %x\n", a, b, c);
}
Или, что то же самое, так:
C++
1
2
3
4
// ассемблерная вставка
__asm mov eax, a
__asm add eax, b
__asm mov c, eax
А можно вообще в одну строчку:
C++
1
2
// ассемблерная вставка
__asm mov eax, a __asm add eax, b __asm mov c, eax
Во встроенном ассемблере Visual C++ можно использовать все инструкции вплоть до Pentium 4 и AMD Athlon. Поддерживается также MMX. Поддержки Itanium и x64 пока нет .

По синтаксису встроенный ассемблер Visual C++ частично совпадает с MASM. Например, как и в MASM можно строить выражения с операндами и использовать offset с глобальными переменными. Как и в MASM, можно использовать глобальные метки и принудительно определять короткие переходы. Однако определить локальную метку с помощью @@ во встроенном ассемблере Visual C++ не получится – замена lab: на @@lab: в предыдущем примере вызовет ошибку компиляции. Есть и другие отличия от MASM. Например, во встроенном ассемблере Visual C++ нет никаких средств для объявления переменных, поэтому о привычных DB, DW, DD, DQ, DT, DF, DUP и THIS можно забыть. Зато можно использовать переменные, объявленные в программе на C++.

Во встроенном ассемблере также нельзя объявлять структуры — директивы STRUC, RECORD, WIDTH и MASK недопустимы. Вместо этого можно использовать структуры, объявленные в программе на C++. Кроме того, в ассемблере можно использовать комментарии и HEX-числа в стиле C++.

В принципе, все отличия подробно описаны в MSDN (идет в комплекте с Visual Studio).

Обычно в Visual C++ ассемблер используется для написания функций. Как правило, функции, написанные на ассемблере, объявляют с использованием директивы _stdcall. В этом случае параметры передаются в функцию на стеке в обратном порядке, а результат работы возвращается в eax.

Для примера рассмотрим функцию, которая находит длину ASCIIZ-строки:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int _stdcall get_str_length(char *inputstr)
{// чаще всего функция целиком состоит из одной большой ассемблерной вставки
__asm{
; можно свободно обращаться к переменным, переданным в функцию
    mov edi, inputstr
    mov esi, edi
    or ecx, -1
    xor al, al
    cld
    repne scasb
    sub edi, esi
; результат работы функции следует возвращать в eax
    mov eax, edi
    dec eax
   }
}
Использовать эту функцию можно так:
C++
1
2
3
4
5
6
7
int main()
{
   char str_1[]="Hello, world!";
   int i;
   i = get_str_length(str_1);
   printf("String: %s\nLength: %d", str_1, i); 
}
При написании кода на ассемблере Visual C++ следует помнить некоторые моменты. Во-первых, значения регистров не передаются между ассемблерными вставками. Например, если в ассемблерной вставке ты установил eax в 1, то в следующей ассемблерной вставке eax не обязательно будет равно 1.
Во-вторых, нужно быть осторожным с регистрами и стеком. В середине ассемблерных вставок нельзя менять регистры ds, ss, sp, bp и флаги. Если эти регистры все-таки меняются, перед выходом из ассемблерной вставки их нужно обязательно восстановить. Что касается стека, то тут нужно соблюдать правило, которое гласит: если в ассемблерной вставке нечто ложится на стек, то в той же ассемблерной вставке это «нечто» должно со стека сниматься.
Рассмотрим, например, такой код:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
// наша функция на ассемблере
void _stdcall Test()
{
__asm{
; кладем на стек eax
push eax
 
; перед тем как ассемблерная вставка кончится, нужно
; снять eax со стека (например, с помощью pop), но мы забыли это сделать ;)
         }
}
int main()
{ 
 
// вызываем функцию... и любуемся сообщением об ошибке :)
Test();
}
И, наконец, если пишется не драйвер, а обычное Win32-приложение, в ассемблерной вставке не должно быть привилегированных инструкций. Тем, кто хочет узнать больше — почитайте MSDN — там есть вся необходимая информация.
6
Charles Kludge
Клюг
7641 / 3156 / 366
Регистрация: 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
Mikl___
Автор FAQ
11021 / 5795 / 511
Регистрация: 11.11.2010
Сообщений: 10,802
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 / 366
Регистрация: 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
11021 / 5795 / 511
Регистрация: 11.11.2010
Сообщений: 10,802
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
11021 / 5795 / 511
Регистрация: 11.11.2010
Сообщений: 10,802
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
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
18.01.2014, 03:54
Привет! Вот еще темы с ответами:

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

Какие существуют среды программирования Assebler'a для процессоров ix86 помимо tasm, fasm, masm - Assembler
Какие существуют среды программирования Assebler'a для процессоров ix86 помимо tasm, fasm, masm? Посоветуйте литературу по этому вопросу?...

MASM,TASM - Assembler
Очевидно, что программирование на макроассемблере легче для восприятия чем на уровне команд процессору, но значит ли это что екзешники в...

TASM-MASM - Assembler
Здравствуйте стоит задача, написать программу на MASM32 ( Отображение списка файлов в каталоге ). С MASM'ом я раньше не сталкивался...


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

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

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