Свободное программное обеспечение для бизнеса и дома.
Страницы блога
пятница, 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
И перейдите в неё:
Если вы работаете много в 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