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

Свободное программное обеспечение для бизнеса и дома.

Страницы блога

пятница, 23 ноября 2012 г.

Основы Assembler начало и вновь конец.

В современных операционных система задания по ВССиТ выполнять, мягко говоря проблематично. Для написания контрольных, да и просто освоения начальных навыков необходимо разобраться с компилятором MASM или TASM которые работают из под DOS.
Сами компиляторы, линковщики и все другие приблуды можно найти, например, на http://kalashnikoff.ru/ или http://www.wasm.ru/. А тут будет рассказываться о том, что ближе к телу.

Необходимые инструменты и их настройка.

И так, что нам нужно. Нам нужен эмулятор DOS-терминала и компилятор. Компилятор я выбрал MASM 6.11, так как им до этого пользовался, и кое что в моей памяти уже было.Как DOS-эмулятор я советую DOSBox. Версии есть и под Windows и под Linux. Под Linux его можно установить из стандартного репозитория. Для Ubuntu:

$sudo apt-get install dosbox
или из Центра приложений.

Hello World на Ассемблере (x86)


После запуска вы увидите экран с приглашением :
Смонтируйте диск C: для этого наберите:
Z:>mount c /home/user/folder_prodject
И перейдите в неё:

Окно DOSBox с приглашением в Ubuntu 12.10

Если вы работаете много в DOS-эмуляторе, то такой способ может оказаться не очень удобным. Но моно настроить автоматическое монтирование диска C:
Открываем /home/user/.dosbox/dosbox-0.74.conf в редакторе, и ищем строки:

[autoexec] # Lines in this section will be run at startup. # Yju can put your MOUNT lines here.
и пишем тут что-то типа:
mount C /home/user/asm PATH=%PATH%;C:masm611bin C:

При старте будет монтироваться C: диск в /home/user/asm, при этом эта директория должна существовать. PATH назначает пути поиска программ, это нужно что бы нам не мусорить в одной директории, а была возможность разнести компилятор и проект в разные. В конце стоит C: что бы DOSBox самостоятельно переводил нас на диск C: и берег наши телодвижения.

На последнем рисунке показана команда cd и dir, обратите внимание на то как отражаются имена файлов написанных русскими буквами. Возможно это и лечиться, но нужно ли это нам? И не забывайте, что MS-DOS использовал название файлов в формате 8.3. То есть, на имя отводилось 8 символов, потом шла точка (.), а затем расширение файла. При других названиях тоже могут возникнуть проблемы.

Как использовать MASM 6.11.

Теперь разберем как нам компилировать код, который мы напишем.

C:>ml /с имя_файла.asm
получим файл имя_файла.obj Ключ /c говорит компилятору не проводить линковку.
C:>link имя_файла.obj
получим исполняемый файл.
Но можно сделать всё быстрее:
C:>ml имя_файла.asm /AT

При этом мы получим и .obj файл и исполняемый .com. Флаг /AT говорит компилятору вызвать линковщик и передает ему флаг /T который обозначает модель TINY.
Ещё часто бывает нужен файл листинга программы, для этого используйте ключ /Fl:

C:>ml имя_файла.asm /Fl
Файл листинга будет записан в имя_файла.lst

Пишем программку.

Всё, на этом самое сложное закончилось. Теперь начинаем писать программу на ассемблере.
Так как в задачах часто просят вывести решение формул на экран, то и будем разбирать подобный пример. Сумма делений с x= от 1 до 10:

Читайте также:
Как фоновые программы работают на компьютере

Основные команды которые будем использовать

Регистры, ох уж эти регистры. Краткий экскурс.

Сейчас, что бы начать читать код, достаточно знать регистры общего назначения:
Они могут быть 8 битные (например, al, ah), 16 битные (например, ax) и 32 битные (например, eax).
Все 16 битные регистры делятся на на младший и старший, например, ax делиться на al и ah. Делиться значит состоит, а не математическое действие.
eax, ax, al — очень часто выступают как приёмник значений, например, при умножении и делении.
ebx, bx. bl, bh — свободный регистр, который можно использовать в хвост и гриву.
edx, dx — часто используется для пересылки дополнительной информации или остатка от деления.
ecx, cx — называется счетчик, используется в циклах.
Всё остальное в комментариях программы. Кстати точка с запятой (;) используется как команда начать комментарий, и всё что написано за ней в строке не интерпретируется компилятором

Тренируемся

Первое что нам нужно — это разложить наш пример на простые составляющие, что бы составить алгоритм. К примеру: x в кубе — это x*x*x. Вспоминаем: умножение это mul, то есть мы можем это записать:

mov ax,1 ; присвоили значение mul ax ; умножили первый раз, то есть возвели в квадрат mul ax ; умножили второй раз, то есть возвели в куб
Вот, так пошагово это и происходит.

А теперь сам листинг решения примера:

Вывод приложения

И вновь начало

Это был очень краткий экскурс в язык ассемблер. Я не ставил перед собой цели детального описания. Всё что тут написано служит только помощью в написании контрольной работы. Если кому-то (например, как мне) интересен язык, и хочется не только получить оценку по ВССиТ, но и что-нибудь начать понимать, воспользуйтесь дополнительной литературой.

Источник: infineconomics.blogspot.com

Записки программиста

Написание и отладка кода на ассемблере x86/x64 в Linux

17 августа 2016

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

Если вы знаете ассемблер, то любая программа для вас — open source.

Народная мудрость.

Введение

Существует два широко используемых ассемблерных синтаксиса — так называемые ATT, а синтаксис Intel встречается крайне редко (например, он используется в утилите perf). Поскольку Windows, как известно, не существует, далее мы сосредоточимся на правильном ATHello, world!n»
. set len , . — msg

. globl _start
_start :
# write
mov $ 4 , % eax
mov $ 1 , % ebx
mov $msg , % ecx
mov $len , % edx
int $ 0x80

# exit
mov $ 1 , % eax
xor % ebx , % ebx
int $ 0x80

# Или: gcc -m32 -c hello-int80.s
as —32 hello-int80.s -o hello-int80.o
ld -melf_i386 -s hello-int80.o -o hello-int80

Коротко рассмотрим первые несколько действий, выполняемых программой: (1) программа начинает выполнение с метки _start, (2) в регистр eax кладется значение 4, (3) в регистр ebx помещается значение 1, (4) в регистр ecx кладется адрес строки, (5) в регистр edx кладется ее длина, (6) происходит прерывание 0 x80. Так в мире Linux традиционно происходит выполнение системных вызовов. Конкретно int 0 x80 считается устаревшим и медленным, но из соображений обратной совместимости он все еще работает. Далее мы рассмотрим и более новые механизмы.

Читайте также:
Чем отличается программа от инструкции

Нетрудно догадаться, что eax — это номер системного вызова, а ebx, ecx и edx — его аргументы. Какой системный вызов имеет какой номер можно подсмотреть в файлах:

# для x86
/ usr / include / x86_64-linux-gnu / asm / unistd_32.h
# для x64
/ usr / include / x86_64-linux-gnu / asm / unistd_64.h

Следующая строчка из файла unistd_32.h:

#define __NR_write 4

… как бы намекает нам, что производится вызов write. В свою очередь, из man 2 write мы можем узнать, какие аргументы этот системный вызов принимает:

ssize_t write ( int fd , const void * buf , size_t count ) ;

То есть, рассмотренный код эквивалентен:

// напомню, что stdout == 1
write ( stdout , «Hello, world! n » , 14 )

Затем аналогичным образом производится вызов:

// команда `xor %ebx, %ebx` обнуляет регистр %ebx
exit ( 0 )

Совсем не сложно!

В общем случае системный вызов через 0 x80 производится по следующим правилам. Регистру eax присваивается номер системного вызова из unistd_32.h. До шести аргументов помещаются в регистры ebx, ecx, edx, esi, edi и ebp. Возвращаемое значение помещается в регистр eax. Значения остальных регистров при возвращении из системного вызова остаются прежними.

Выполнение системного вызова через sysenter

Начиная с i586 появилась инструкция sysenter, специально предназначенная (чего нельзя сказать об инструкции int) для выполнения системных вызовов.

Рассмотрим пример использования ее на Linux:

.data
msg :
. ascii «Hello, world!n»
len = . — msg

. text
. globl _start

_start :
# write
mov $ 4 , % eax
mov $ 1 , % ebx
mov $msg , % ecx
mov $len , % edx
push $write_ret
push % ecx
push % edx
push % ebp
mov % esp , % ebp
sysenter

write_ret :
# exit
mov $ 1 , % eax
xor % ebx , % ebx
push $exit_ret
push % ecx
push % edx
push % ebp
mov % esp , % ebp
sysenter

Сборка осуществляется аналогично сборке предыдущего примера.

Как видите, принцип тот же, что при использовании int 0 x80, только перед выполнением sysenter требуются поместить в стек адрес, по которому следует вернуть управление, а также совершить кое-какие дополнительные манипуляции с регистрами. Причины этого более подробно объясняются здесь.

Инструкция sysenter работает быстрее int 0 x80 и является предпочтительным способом совершения системных вызовов на x86.

Выполнение системного вызова через syscall

До сих пор речь шла о 32-х битных программах. На x64 выполнение системных вызовов осуществляется так:

.data
msg :
. ascii «Hello, world!n»
. set len , . — msg

. globl _start
_start :
# write
mov $ 1 , % rax
mov $ 1 , % rdi
mov $msg , % rsi
mov $len , % rdx
syscall

# exit
mov $ 60 , % rax
xor % rdi , % rdi
syscall

Собирается программа таким образом:

as —64 hello-syscall.s -o hello-syscall.o
ld -melf_x86_64 -s hello-syscall.o -o hello-syscall

Принцип все тот же, но есть важные отличия. Номера системных вызовов нужно брать из unistd_64.h, а не из unistd_32.h. Как видите, они совершенно другие. Так как это 64-х битный код, то и регистры мы используем 64-х битные. Номер системного вызова помещается в rax. До шести аргументов передается через регистры rdi, rsi, rdx, r10, r8 и r9.

Возвращаемое значение помещается в регистр rax. Значения, сохраненные в остальных регистрах, при возвращении из системного вызова остаются прежними, за исключением регистров rcx и r11.

Интересно, что в программе под x64 можно одновременно использовать системные вызовы как через syscall, так и через int 0 x80.

Отладка ассемблерного кода в GDB

Статья была бы не полной, если бы мы не затронули вопрос отладки всего этого хозяйства. Так как мы все равно очень плотно сидим на GNU-стэке, в качестве отладчика воспользуемся GDB. По большому счету, отладка не сильно отличается от отладки обычного кода на C, но есть нюансы.

Читайте также:
Температура ПК программа на русском

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

Источник: eax.me

Hello World на Ассемблере

После примеров простых программ на Паскале и С++ вы, наверно, не ожидали что я сразу перепрыгну на Ассемблер. Но вот перепрыгнул. И сегодня мы поприветствуем мир на языке ассемблера.

Итак, вот сразу пример, а потом его рассмотрим:

.model tiny .code ORG 100h begin: MOV AH, 9 ; Функция вывода строки на экран MOV DX, OFFSET Msg ; Адрес строки INT 21h ; Выполнить функцию RET ; Вернуться в операционную систему Msg DB ‘Hello, World. $’ ; Строка для вывода END begin

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

С другой стороны, это не так уж и страшно, как иногда думают те, кто никогда не программировал на Ассемблере.

Во всех подробностях разбирать этот код не будем — подробности в другой раз. Рассмотрим только основные инструкции.

Программа начинается с метки begin . В отличие, например, от Паскаля, это слово может быть каким угодно, например, start . Это всего лишь метка, которая обозначает начало какого-то участка кода.

К конце программы мы видим END begin . Инструкция END говорит ассемблеру, что следующее за ней слово означает конец блока кода, обозначенного этим словом. В нашем примере это означает, что здесь кончается блок кода, начинающийся со слова begin .

Далее уже начинается программа. Сначала в регистр АН мы записываем номер функции, которую собираемся потом выполнить. Номер 9 — это функция BIOS, которая выполняет вывод на устройство вывода. По умолчанию это монитор.

Затем в регистр DX мы записываем адрес строки. Адрес вычисляется с помощью оператора OFFSET . Например:

Здесь мы получаем адрес первого байта блока данных, обозначенного идентификатором Msg .

Затем мы вызываем прерывание 21h . Это прерывание выполняет функцию, номер которой записан в регистре АН. Поскольку у нас там записана функция 9, то прерывание выведет на экран строку.

Команда RET выполняет выход из процедуры или из программы. В нашем случае из программы. Таким образом программа завершается и мы возвращаемся в операционную систему.

Ещё несколько слов об объявлении строки:

Msg DB ‘Hello, World. $’

Вначале мы записываем идентификатор (в нашем случае Msg , но может быть и любой дугой), чтобы было проще работать со строкой. Затем пишем DB — Define Byte — Определить Байт. В нашем случае это будет массив байтов, в котором каждый элемент имеет размер один байт.

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

А в эмуляторах это вообще может считаться ошибкой и вывода не будет.

Ну вот мы и написали свою первую программу на языке ассемблера. Если что-то пропустили, то посмотрите видео:

Источник: av-assembler.ru

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