Пример программы на ассемблере

По своей профессии я не сталкиваюсь с низкоуровневым программированием: занимаюсь программированием на скриптовых языках. Но поскольку душа требует разнообразия, расширения горизонтов знаний или просто понимания, как работает машина на низком уровне, я занимаюсь программированием на языках, отличающихся от тех, с помощью которых зарабатываю деньги – такое у меня хобби.

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

  • Моя программа не должна быть программой под DOS. Слишком много примеров ориентировано на нее в связи с простым API. Моя программа обязательно должна была запускаться на современных ОС.
  • Программа должна использовать кучу – получать в свое распоряжение динамически распределяемую память.
  • Чтобы не быть слишком сложной, программа должна работать с целыми беззнаковыми числами без использования переносов.

Код я писал с упором больше на стиль и понятность, чем на скорость его выполнения. К примеру, обнуление регистра я проводил не с помощью xor eax, eax , а с помощью mov eax, 0 в связи с более подходящей семантикой инструкции. Я решил, что поскольку программа преследует исключительно учебные цели, можно распоясаться и заниматься погоней за стилем кода в ассемблере.

Как я стал программистом на ассемблере / #itstory2019

Итак, посмотрим, что получилось.

С чего начать?

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

Так же встает вопрос, каким образом на таком низком уровне реализуется обмен данными между внутренним миром программы и внешней средой. Тут на сцену выходит API операционной системы. В DOS, как уже было упомянуто, интерфейс был достаточно простой. К примеру, программа «Hello, world» выглядела так:

SECTION .text org 0x100 mov ah, 0x9 mov dx, hello int 0x21 mov ax, 0x4c00 int 0x21 SECTION .data hello: db «Hello, world!», 0xD, 0xA, ‘$’

В Windows же для этих целей используется Win32 API, соответственно, программа должна использовать методы соответствующих библиотек:

%include «win32n.inc» extern MessageBoxA import MessageBoxA user32.dll extern ExitProcess import ExitProcess kernel32.dll SECTION code use32 ..start: push UINT MB_OK push LPCTSTR window_title push LPCTSTR banner push HWND NULL call [MessageBoxA] push UINT NULL call [ExitProcess] SECTION data use32 banner: db «Hello, world!», 0xD, 0xA, 0 window_title: db «Hello», 0

Здесь используется файл win32n.inc, где определены макросы, сокращающие код для работы с Win32 API.

Я решил не использовать напрямую API ОС и выбрал путь использования функций из библиотеки Си. Так же это открыло возможность компиляции программы в Linux (и, скорее всего, в других ОС) – не слишком большое и нужное этой программе достижение, но приятное достижение.

Как написать программу на ассемблере 64 бит

Вызов подпрограмм

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

Подпрограммы представляют собой метку, по которой располагается код. Заканчивается подпрограмма инструкцией ret . К примеру, вот такая подпрограмма в DOS выводит в консоль строку «Hello, world»:

print_hello: mov ah, 0x9 mov dx, hello int 0x21 ret

Для ее вызова нужно было бы использовать инструкцию call :

call print_hello

Для себя я решил передавать аргументы подпрограммам через регистры и указывать в комментариях, в каких регистрах какие аргументы должны быть, но в языках высокого уровня аргументы передаются через стек. К примеру, вот так вызывается функция printf из библиотеки Си:

push hello call _printf add esp, 4

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

При входе в подпрограмму необходимо создать новый стековый кадр. Делается это следующим образом:

print_hello: push ebp ;сохраняем указатель начала стекового кадра на стеке mov ebp, esp ;теперь началом кадра является вершина предыдущего

Соответственно, перед выходом нужно восстановить прежнее состояние стека:

mov esp, ebp pop ebp ret

Для локальных переменных так же используется стек, на котором после создания нового кадра выделяется нужное количество байт:

print_hello: push ebp mov ebp, esp sub esp, 8 ;опускаем указатель вершины стека на 8 байт, чтобы выделить память

Так же архитектура x86 предоставляет специальные инструкции, с помощью которых можно более лаконично реализовать эти действия:

print_hello: enter 8, 0 ;создать новый кадр, выделить 8 байт для локальных переменных leave ;восстановить стек ret

Второй параметр инструкции enter – уровень вложенности подпрограммы. Он нужен для линковки с языками высокого уровня, поддерживающими такую методику организации подпрограмм. В нашем случае это значение можно оставить нулевым.

Непосредственно программа

  • main.asm – главный файл,
  • functions.asm – подпрограммы,
  • string_constants.asm – определения строковых констант,
  • Makefile – сценарий сборки

main.asm

%define SUCCESS 0 %define MIN_MAX_NUMBER 3 %define MAX_MAX_NUMBER 4294967294 global _main extern _printf extern _scanf extern _malloc extern _free SECTION .text _main: enter 0, 0 ;ввод максимального числа call input_max_number cmp edx, SUCCESS jne .custom_exit mov [max_number], eax ;выделяем память для массива флагов mov eax, [max_number] call allocate_flags_memory cmp edx, SUCCESS jne .custom_exit mov [primes_pointer], eax ;отсеять составные числа mov eax, [primes_pointer] mov ebx, [max_number] call find_primes_with_eratosthenes_sieve ;вывести числа mov eax, [primes_pointer] mov ebx, [max_number] call print_primes ;освободить память от массива флагов mov eax, [primes_pointer] call free_flags_memory ;выход .success: push str_exit_success call _printf jmp .return .custom_exit: push edx call _printf .return: mov eax, SUCCESS leave ret %include «functions.asm» SECTION .data max_number: dd 0 primes_pointer: dd 0 %include «string_constants.asm»

  1. input_max_number — с помощью консоли запрашивает у пользователя максимальное число, до которого производится поиск простых; во избежание ошибок значение ограничено константами MIN_MAX_NUMBER и MAX_MAX_NUMBER
  2. allocate_flags_memory — запросить у ОС выделение памяти для массива пометок чисел (простое/составное) в куче; в случае успеха возвращает указатель на выделенную память через регистр eax
  3. find_primes_with_eratosthenes_sieve — отсеять составные числа с помощью классического решета Эратосфена;
  4. print_primes — вывести в консоль список простых чисел;
  5. free_flags_memory — освободить память, выделенную для флагов
Читайте также:
К программа анализ кала что показывает

Файл string_constants.asm содержит определение строковых переменных, значения которых, как намекает название файла, менять не предполагается. Только ради этих переменных было сделано исключение к правилу «не использовать глобальные переменные». Я так и не нашел более удобного способа доставлять строковые константы функциям ввода-вывода – подумывал даже записывать на стек непосредственно перед вызовами функций, но решил, что эта идея куда хуже идеи с глобальными переменными.

string_constants.asm

;подписи ввода-вывода, форматы str_max_number_label: db «Max number (>=3): «, 0 str_max_number_input_format: db «%u», 0 str_max_number_output_format: db «Using max number %u», 0xD, 0xA, 0 str_print_primes_label: db «Primes:», 0xD, 0xA, 0 str_prime: db «%u», 0x9, 0 str_cr_lf: db 0xD, 0xA, 0 ;сообщения выхода str_exit_success: db «Success!», 0xD, 0xA, 0 str_error_max_num_too_little: db «Max number is too little!», 0xD, 0xA, 0 str_error_max_num_too_big: db «Max number is too big!», 0xD, 0xA, 0 str_error_malloc_failed: db «Can’t allocate memory!», 0xD, 0xA, 0

Для сборки применяется такой сценарий:

Makefile

ifdef SystemRoot format = win32 rm = del ext = .exe else format = elf rm = rm -f ext = endif all: primes.o gcc primes.o -o primes$(ext) $(rm) primes.o primes.o: nasm -f $(format) main.asm -o primes.o

Подпрограммы (функции)

input_max_number

Код подпрограммы

; Ввести максимальное число ; Результат: EAX — максимальное число input_max_number: ;создать стек-фрейм, ;4 байта для локальных переменных enter 4, 1 ;показываем подпись push str_max_number_label ;см. string_constants.asm call _printf add esp, 4 ;вызываем scanf mov eax, ebp sub eax, 4 push eax push str_max_number_input_format ;см. string_constants.asm call _scanf add esp, 8 mov eax, [ebp-4] ;проверка cmp eax, MIN_MAX_NUMBER jb .number_too_little cmp eax, MAX_MAX_NUMBER ja .number_too_big jmp .success ;выход .number_too_little: mov edx, str_error_max_num_too_little ;см. string_constants.asm jmp .return .number_too_big: mov edx, str_error_max_num_too_big ;см. string_constants.asm jmp .return .success: push eax push str_max_number_output_format ;см. string_constants.asm call _printf add esp, 4 pop eax mov edx, SUCCESS .return: leave ret

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

mov eax, ebp sub eax, 4 push eax push str_max_number_input_format ;см. string_constants.asm call _scanf add esp, 8 mov eax, [ebp-4]

Таким образом, сначала в eax записывается адрес памяти на 4 байта ниже указателя базы стека. Это память, выделенная для локальных нужд подпрограммы. Указатель на эту память передается функции scanf как цель для записи данных, введенных с клавиатуры.

После вызова функции, в eax из памяти перемещается введенное значение.

allocate_flags_memory и free_flags_memory

Код подпрограмм

; Выделить память для массива флагов ; Аргумент: EAX — максимальное число ; Результат: EAX — указатель на память allocate_flags_memory: enter 8, 1 ;выделить EAX+1 байт inc eax mov [ebp-4], eax push eax call _malloc add esp, 4 ;проверка cmp eax, 0 je .fail mov [ebp-8], eax ;инициализация mov byte [eax], 0 cld mov edi, eax inc edi mov edx, [ebp-4] add edx, eax mov al, 1 .write_true: stosb cmp edi, edx jb .write_true ;выход mov eax, [ebp-8] jmp .success .fail: mov edx, str_error_malloc_failed ;см. string_constants.asm jmp .return .success: mov edx, SUCCESS .return: leave ret ; Освободить память от массива флагов ; Аргумент: EAX — указатель на память free_flags_memory: enter 0, 1 push eax call _free add esp, 4 leave ret

Ключевыми местами этих подпрограмм являются вызовы функций malloc и free из библиотеки Си.

malloc в случае удачи возвращает через регистр eax адрес выделенной памяти, в случае неудачи этот регистр содержит 0 . Это самое узкое место программы касательно максимального числа. 32 бит вполне достаточно для поиска простых чисел до 4 294 967 295, но выделить разом столько памяти не получится.

find_primes_with_eratosthenes_sieve

Код подпрограммы

;Найти простые числа с помощью решета Эратосфена ;Аргументы: EAX — указатель на массив флагов, EBX — максимальное число find_primes_with_eratosthenes_sieve: enter 8, 1 mov [ebp-4], eax add eax, ebx inc eax mov [ebp-8], eax ;вычеркиваем составные числа cld mov edx, 2 ;p = 2 mov ecx, 2 ;множитель с = 2 .strike_out_cycle: ;x = c*p mov eax, edx push edx mul ecx pop edx cmp eax, ebx jbe .strike_out_number jmp .increase_p .strike_out_number: mov edi, [ebp-4] add edi, eax mov byte [edi], 0 inc ecx ;c = c + 1 jmp .strike_out_cycle .increase_p: mov esi, [ebp-4] add esi, edx inc esi mov ecx, edx inc ecx .check_current_number: mov eax, ecx mul eax cmp eax, ebx ja .return lodsb inc ecx cmp al, 0 jne .new_p_found jmp .check_current_number .new_p_found: mov edx, ecx dec edx mov ecx, 2 jmp .strike_out_cycle .return: leave ret

Подпрограмма реализует классический алгоритм для вычеркивания составных чисел, решето Эратосфена, на языке ассемблера x86. Приятна тем, что не использует вызовы внешних функций и не требует обработки ошибок 🙂

print_primes

Код подпрограммы

; Вывести простые числа ; Параметры: EAX — указатель на массив флагов, EBX — максимальное число print_primes: enter 12, 1 mov [ebp-4], eax mov [ebp-8], ebx push str_print_primes_label call _printf add esp, 4 cld mov esi, [ebp-4] mov edx, esi add edx, [ebp-8] inc edx mov [ebp-12], edx mov ecx, 0 .print_cycle: lodsb cmp al, 0 jne .print jmp .check_finish .print: push esi push ecx push str_prime ;см. string_constants.asm call _printf add esp, 4 pop ecx pop esi mov edx, [ebp-12] .check_finish: inc ecx cmp esi, edx jb .print_cycle push str_cr_lf call _printf add esp, 4 leave ret

Подпрограмма выводит в консоль простые числа. Ключевым моментом тут является вызов функции printf из библиотеки Си.

Заключение

Что ж, программа отвечает всем сформулированным требованиям и, кажется, проста для понимания. Хочется надеяться, кому-нибудь ее разбор поможет вникнуть в программирование на низком уровне и он получит от него такое же удовольствие, какое получил я.

Могу так же привести интересный факт. Поскольку с детства нас учили, что программы на языке ассемблера выполняются быстрее, я решил сравнить скорость выполнения этой программы со скоростью программы на C++, которую я писал когда-то и которая искала простые числа с помощью Решета Аткина. Программа на С++, скомпилированная в Visual Studio с /O2 выполняла поиск до числа 2 30 примерно за 25 секунд на моей машине. Программа же на ассемблере показала 15 секунд с Решетом Эратосфена.

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

Полезные ссылки

  1. Список ресурсов для изучения ассемблера
  2. Организация памяти
  3. Решето Эратосфена
  4. Решето Аткина
  5. Стек
  6. Стековый кадр

Источник: habr.com

2.3. Примеры программ на ассемблере

Пример 1. Программа выводит на экран три символа ‘*’, выдает приветствие и издает три звуковых сигнала. Данный пример демонстрирует структуру программы, дает образец правильного оформления программы на ассемблере.

Читайте также:
Сколько ярлыков может быть у одной программы

S SEGMENT STACK ; начало сегмента

DW 64 DUP(0ABCDh) ; Минимальный стек

;(64 слова). Требуется для работы

S ENDS ; Окончание сегмента

D SEGMENT ; Начало сегмента

N equ 6 ; Задание константы с помощью

X DB N DUP(4) ; Резервирование N байт памяти, ;инициализированных числом 4. ;Первый байт имеет имя X, второй ;X+1 и т.д.

Y db ‘Hello, dear friend. ‘,0Dh, 0Ah,’$’

; Задание константы-строки. В конце строки — символы CR

; и NL, а также признак конца строки при выводе ее на

; экран по функции №9 прерывания 21h.

Z DW N DUP(6B0Dh) ; N слов, инициализированных

D ENDS ; Конец сегмента

ASSUME SS:S,CS:C, DS:D

; Директива устанавливает соответствие между

; сегментами и сегментными регистрами. Может

; располагаться в любом месте, но до первого

; исполняемого оператора (обычно помещают в начале

MMM PROC FAR ; код оформляется как

; процедура, которую будет

; Выполнение соглашений DOS

PUSH DS ;Запись содержимое DS в стек,

; т.к. в него будут загружены свои данные

SUB AX,AX ; Обнуление регистра AX

PUSH AX ; Запись ноля в стек

; установка верного значения в регистре DS (адреса

;сегмента данных). Регистры CS и SS устанавливаются

; непосредственно сама программа

; вывод на экран 3 звездочек (посимвольно, функция №2) ; и переход на следующую строку

MOV AH, 2 ; размещение в AH номера функции

;прерывания (02 — вывод символа на экран)

MOV DL,’*’ ; размещение в DL выводимого символа

INT 21H ; вызов прерывания номер 21h

; Выдача звукового сигнала (3 раза). Выводится как

MOV DL,07H ; размещение кода звукового

; сигнала в регистре данных

; Вывод строки-приветствия с помощью функции вывода

; Признаком конца строки является знак $

; Смещение начала строки передается через регистр

MOV AH,9 ; размещение номера функции прерывания

MOV DX, OFFSET Y ; размещение в DX смещения

; строки Y относительно

INT 21H ; Прерывание

; Возврат в DOS и окончание работы программы

RET ;Возврат в DOS

MMM ENDP ;Конец процедуры MMM

C ENDS ;Конец сегмента

END MMM ;Конец программы.

Пример 2. Программа для иллюстрации различных способов пересылки данных.

S SEGMENT STACK

X DB 0Bh ; 1 байт 0Bh

XX DB 3 dup (0CBh) ; 3 байта 0CBh

Z DD 0FAFBFEFFh

YYY DD 0FFFFFFFFh

M DB ? ; неинициализированный байт

MM DW 10 dup (?) ; 10 неинициализированных слов

MMW DW 10 dup (0FFFFh) ; 10 инициализированных

MMB DB 10 dup (0CCh) ; 10 инициализированных байт

digits DB ‘0123456789’ ; строка с цифрами

; Выполнение соглашений DOS и настройка регистра DS

; операции пересылки (MOV)

; Пересылка непосредственного операнда:

; в регистр (размер пересылаемого задает первый операнд)

MOV AX, 0234h ; слово

;MOV BL, 600 ошибочная операция – выдает

; сообщение «error A2050: Value out of range» число 600

; занимает места больше чем байт

MOV DL, 17 ; байт

MOV CH, 34 ; байт

MOV SI, 234 ; слово

; MOV DS, 234 ошибочная операция в регистр

; DS загружен адрес сегмента данных

; Пересылка непосредственного операнда:

; в память (размер пересылаемого задает первый операнд)

MOV X, 34h ; байт

MOV MM, 1722h ; слово

MOV MM+3, 34CCh ; слово со смещением 3 байта

; от начала переменной

MOV MMW+2, 0 ; слово со смещением 2 байта

от начала переменной

MOV MMW+4, 0 ; слово со смещением 4 байта

от начала переменной

MOV MMB, 0 ; байт

MOV MMB+2, 0 ; байт со смещением 2 байта от

; Пересылка из регистра:

; в регистр (размеры операндов должны совпадать)

MOV DI, SI ; слово

MOV DH, DL ; байт

MOV ES, SI ; слово

; MOV CS, SI ошибка – в CS находится адрес сегмента

MOV MM, DS ; слово

MOV MM+4, AX ; слово со смещением 4 байта от

; Пересылка из памяти:

; только в регистр (размеры операндов должны совпадать)

MOV DI, MM ; слово

MOV DH, X+1 ; байт

MOV ES, MM+2 ; слово

; Примеры использования оператора PTR

MOV AX, WORD PTR X

MOV BL, BYTE PTR MM

Пример 3. Программа для иллюстрации арифметических операции на языке ассемблера.

S SEGMENT STACK

; Сегмент кода – сегмент данных отсутствует, т.к.

; в программе нет переменных

; Выполнение соглашений DOS и настройка регистра DS

;Операции сложения и вычитания

; пример обычного сложения-вычитания

; Переполнение при сложении

SUB AX, AX ; очистка AX. флаг ZF=1

MOV AL, 7Ch ; 124, 01111100 — почти

;максимальное положительное число

ADD AL, 3 ; 127, 01111111

ADD AL, 2 ; =129 беззнаковое, =–127 знаковое

; 10000001 — переполнение (OF=1,

; Перенос при сложении

MOV AH, 0FAh ; пересылки значений флагов

ADD AH, 3 ;SF=1 (старший бит — 1)

ADD AH, 3 ;CF=1 (сумма не поместилась

; Перенос при вычитании

SUB CX, 1 ; сброс всех флагов

; (не 0, положит., переноса и переполнения нет)

SUB CX, 13 ; CF=1 и SF=1 (старший бит — 1)

; Переполнение при вычитании

; 127 — (-3) = 130, в 7 битах не поместится

; и окажется = –126

SUB DL, CL ; OF=1 CF=1 SF=1

; (127-253, если рассматривать как беззнаковое)

Источник: studfile.net

Программирование на Ассемблере для начинающих с примерами программ

Многие считают, что Assembler – уже устаревший и нигде не используемый язык, однако в основном это молодые люди, которые не занимаются профессионально системным программированием. Разработка ПО, конечно, хорошо, но в отличие от высокоуровневых языков программирования, Ассемблер научит глубоко понимать работу компьютера, оптимизировать работку с аппаратными ресурсами, а также программировать любую технику, тем самым развиваясь в направлении машинного обучения. Для понимания этого древнего ЯП, для начала стоит попрактиковаться с простыми программами, которые лучше всего объясняют функционал Ассемблера.

IDE для Assembler

Первый вопрос: в какой среде разработки программировать на Ассемблере? Ответ однозначный – MASM32. Это стандартная программа, которую используют для данного ЯП. Скачать её можно на официальном сайте masm32.com в виде архива, который нужно будет распаковать и после запустить инсталлятор install.exe. Как альтернативу можно использовать FASM, однако для него код будет значительно отличаться.

Перед работой главное не забыть дописать в системную переменную PATH строчку:

С:masm32bin

Программа «Hello world» на ассемблере

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

.386 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib .data msg_title db «Title», 0 msg_message db «Hello world», 0 .code start: invoke MessageBox, 0, addr msg_message, addr msg_title, MB_OK invoke ExitProcess, 0 end start

Для начала запускаем редактор qeditor.exe в папке с установленной MASM32, и в нём пишем код программы. После сохраняем его в виде файла с расширением «.asm», и билдим программу с помощью пункта меню «Project» → «Build all». Если в коде нет ошибок, программа успешно скомпилируется, и на выходе мы получим готовый exe-файл, который покажет окно Windows с надписью «Hello world».

Читайте также:
Программа продажи отечественных автомобилей

Сложение двух чисел на assembler

В этом случае мы смотрим, равна ли сумма чисел нулю, или же нет. Если да, то на экране появляется соответствующее сообщение об этом, и, если же нет – появляется иное уведомление.

.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data .code start: mov eax, 123 mov ebx, -90 add eax, ebx test eax, eax jz zero invoke MessageBox, 0, chr$(«В eax не 0!»), chr$(«Info»), 0 jmp lexit zero: invoke MessageBox, 0, chr$(«В eax 0!»), chr$(«Info»), 0 lexit: invoke ExitProcess, 0 end start

Здесь мы используем так называемые метки и специальные команды с их использованием (jz, jmp, test). Разберём подробнее:

  • test – используется для логического сравнения переменных (операндов) в виде байтов, слов, или двойных слов. Для сравнения команда использует логическое умножение, и смотрит на биты: если они равны 1, то и бит результата будет равен 1, в противном случае – 0. Если мы получили 0, ставятся флаги совместно с ZF (zero flag), которые будут равны 1. Далее результаты анализируются на основе ZF.
  • jnz – в случае, если флаг ZF нигде не был поставлен, производится переход по данной метке. Зачастую эта команда применяется, если в программе есть операции сравнения, которые как-либо влияют на результат ZF. К таким как раз и относятся test и cmp.
  • jz – если флаг ZF всё же был установлен, выполняется переход по метке.
  • jmp – независимо от того, есть ZF, или же нет, производится переход по метке.

Программа суммы чисел на ассемблере

Примитивная программа, которая показывает процесс суммирования двух переменных:

.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data msg_title db «Title», 0 A DB 1h B DB 2h buffer db 128 dup(?) format db «%d»,0 .code start: MOV AL, A ADD AL, B invoke wsprintf, addr buffer, addr format, eax invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0 end start

В Ассемблере для того, чтобы вычислить сумму, потребуется провести немало действий, потому как язык программирования работает напрямую с системной памятью. Здесь мы по большей частью манипулируем ресурсами, и самостоятельно указываем, сколько выделить под переменную, в каком виде воспринимать числа, и куда их девать.

Получение значения из командной строки на ассемблере

Одно из важных основных действий в программировании – это получить данные из консоли для их дальнейшей обработки. В данном случае мы их получаем из командной строки и выводим в окне Windows:

.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data .code start: call GetCommandLine ; результат будет помещен в eax push 0 push chr$(«Command Line») push eax ; текст для вывода берем из eax push 0 call MessageBox push 0 call ExitProcess end start

Также можно воспользоваться альтернативным методом:

.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data .code start: call GetCommandLine ; результат будет помещен в eax invoke GetCommandLine invoke MessageBox, 0, eax, chr$(«Command Line»), 0 invoke ExitProcess, 0 push 0 call ExitProcess end start

Здесь используется invoke – специальный макрос, с помощью которого упрощается код программы. Во время компиляции макрос-команды преобразовываются в команды Ассемблера. Так или иначе, мы пользуемся стеком – примитивным способом хранения данных, но в тоже время очень удобным. По соглашению stdcall, во всех WinAPI-функциях переменные передаются через стек, только в обратном порядке, и помещаются в соответствующий регистр eax.

Циклы в ассемблере

.data msg_title db «Title», 0 A DB 1h buffer db 128 dup(?) format db «%d»,0 .code start: mov AL, A .REPEAT inc AL .UNTIL AL==7 invoke wsprintf, addr buffer, addr format, AL invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0 end start
.data msg_title db «Title», 0 buffer db 128 dup(?) format db «%d»,0 .code start: mov eax, 1 mov edx, 1 .WHILE edx==1 inc eax .IF eax==7 .BREAK .ENDIF .ENDW invoke wsprintf, addr buffer, addr format, eax invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0

Для создания цикла используется команда repeat.

Далее с помощью inc увеличивается значение переменной на 1, независимо от того, находится она в оперативной памяти, или же в самом процессоре. Для того, чтобы прервать работу цикла, используется директива «.BREAK». Она может как останавливать цикл, так и продолжать его действие после «паузы». Также можно прервать выполнение кода программы и проверить условие repeat и while с помощью директивы «.CONTINUE».

Сумма элементов массива на assembler

Здесь мы суммируем значения переменных в массиве, используя цикл «for»:

.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data msg_title db «Title», 0 A DB 1h x dd 0,1,2,3,4,5,6,7,8,9,10,11 n dd 12 buffer db 128 dup(?) format db «%d»,0 .code start: mov eax, 0 mov ecx, n mov ebx, 0 L: add eax, x[ebx] add ebx, type x dec ecx cmp ecx, 0 jne L invoke wsprintf, addr buffer, addr format, eax invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0 end start

Команда dec, как и inc, меняет значение операнда на единицу, только в противоположную сторону, на -1. А вот cmp сравнивает переменные методом вычитания: отнимает одно значение из второго, и, в зависимости от результата ставит соответствующие флаги.

С помощью команды jne выполняется переход по метке, основываясь на результате сравнения переменных. Если он отрицательный – происходит переход, а если операнды не равняются друг другу, переход не осуществляется.

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

Источник: evilinside.ru

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru