Программирование на ассемблере для Linux (консольный ввод-вывод). Статья 2 | Old Programmer | Яндекс Дзен

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

Немного информации…

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

В наших следующих статьях мы будем разбирать ассемблеровские вставки для C++, а сегодня мы просто ознакомимся с последовательностью символов.

Строки в Ассемблере

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

   явное содержание длины строки;

   использование сигнального символа.

Мы можем явно хранить длину строки, используя символ счетчика местоположения $, который предоставляет текущее значение счетчика местоположения строки. Например:

msg  db  ‘Hello, world!’,0xa  ; наша строка

len  equ  $msg             ; длина нашей строки

Символ $ указывает на byte после последнего символа строковой переменной msg. Следовательно, $ — msg указывает на длину строки. Мы также можем написать:

msgdb‘Hello, world!’,0xa  ; наша строка

lenequ13                  ; длина нашей строки

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

messageDB‘I am loving it!’,0

Мои прошлые статьи по ассемблеру

Ну не прошло и недели и мы снова говорим об ассемблере. Как вы уже поняли, язык ассемблера очень детальный и в этом его сложность. Вместо переменных приходится работать с ячейками памяти и регистрами процессора. Это напрягает, потому что одно действие на языке высокого уровня может на ассемблере записываться десятками команд. И, конечно, время разработки программы на ассемблере довольно велико по сравнению с другими языками. Конечно, можно использовать библиотеки языков высокого уровня (например C), что ускоряет разработку и мы об этом в будущем будем говорить.

Строковые инструкции

Каждая строковая инструкция может требовать исходного операнда и операнда назначения. Для 32-битных сегментов строковые инструкции используют регистры ESI и EDI, чтобы указать на операнды источника и назначения, соответственно.

Однако для 16-битных сегментов, чтобы указать на источник и место назначения, используются другие регистры: SI и DI.

Существует 5 основных инструкций для работы со строками в Ассемблере:

   MOVS — эта инструкция перемещает 1 byte, word или doubleword данных из одной ячейки памяти в другую;

   LODS — эта инструкция загружается из памяти. Если операндом является значение типа byte, то оно загружается в регистр AL, если типа word — загружается в регистр AX, если типа doubleword — загружается в регистр EAX;

   STOS — эта инструкция сохраняет данные из регистра (AL, AX или EAX) в память;

   CMPS — эта инструкция сравнивает два элемента данных в памяти. Данные могут быть размера byte, word или doubleword;

   SCAS — эта инструкция сравнивает содержимое регистра (AL, AX или EAX) с содержимым элемента, находящегося в памяти.

Каждая из вышеприведенных инструкций имеет версии byte, word или doubleword, а строковые инструкции могут повторяться с использованием префикса повторения.

Эти инструкции используют пары регистров ES:DI и DS:SI, где регистры DI и SI содержат валидные адреса смещения, которые относятся к байтам, хранящимся в памяти. SI обычно ассоциируется с DS (сегмент данных), а DI всегда ассоциируется с ES (дополнительный сегмент).

Регистры DS:SI (или ESI) и ES:DI (или EDI) указывают на операнды источника и назначения, соответственно. Предполагается, что операндом-источником является DS:SI (или ESI), а операндом назначения — место в памяти, на которое указывает пара ES:DI (или EDI).

Для 16-битных адресов используются регистры SI и DI, а для 32-битных адресов используются регистры ESI и EDI.

В следующей таблице представлены различные версии строковых инструкций и предполагаемое место операндов:

Основная инструкция Операнды в: Операция byte Операция word Операция doubleword
MOVS ES:DI, DS:SI MOVSB MOVSW MOVSD
LODS AX, DS:SI LODSB LODSW LODSD
STOS ES:DI, AX STOSB STOSW STOSD
CMPS DS:SI, ES:DI CMPSB CMPSW CMPSD
SCAS ES:DI, AX SCASB SCASW SCASD

Инструкция MOVS

Инструкция MOVS используется для копирования элемента данных (byte, word или doubleword) из исходной строки в строку назначения. Исходная строка указывается с помощью DS:SI, а строка назначения указывается с помощью ES:DI.

Рассмотрим это на практике:

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

section.text

   global_start        ; должно быть объявлено для использования gcc

_start:                 ; сообщаем линкеру входную точку

   movecx,len

   movesi,s1

   movedi,s2

   cld

   repmovsb

   movedx,20         ; длина сообщения

   movecx,s2         ; сообщение для вывода на экран

   movebx,1         ; файловый дескриптор (stdout)

   moveax,4         ; номер системного вызова (sys_write)

   int0x80         ; вызов ядра

   moveax,1         ; номер системного вызова (sys_exit)

   int0x80         ; вызов ядра

section.data

s1db‘Hello, world!’,0; первая строка

lenequ$s1

section.bss

s2resb20              ; назначение

Результат:

Hello, world!

Основная программа

Начнем:

.386 .model flat, stdcall option casemap:none include ..INCLUDEuser32.inc include ..INCLUDEkernel32.inc includelib ..LIBkernel32.lib includelib ..LIBuser32.lib BSIZE equ 15

Те, кто уже не первую статью читают, знают, что все эти строки нужны, так как мы работаем на masm32, а также подключаем стандартные библиотеки.
Идем дальше:

.data buf BYTE BSIZE dup(?) stdout DWORD ? cWritten DWORD ?CRLF WORD ?first BYTE » CodeTown.ru — practical examples for programming!» second BYTE » Join us!»

В разделе переменные мы объявляем уже знакомые нам переменные для вывода на экран(первые 3 строчки в разделе data).
Далее идет переменная CRLF, она нам понадобится для перевода строки. Как она работает вы увидите чуть дальше.

А вот затем идет объявления так называемых массивов (массивы first и second), в данном случае массивы у нас строковые, и каждый символ занимает 1 байт, об этом говорит запись BYTE после имени массивов. В кавычках мы записали некоторый текст. По сути так объявляются массивы, и ничего сложного в понимании нет.

Далее мы выведем на экран наши массивы:

.code start: invoke GetStdHandle, -11 mov stdout, eax mov esi, offset first ;берем адрес 1 элемента mov edi, offset second ; массивов для выводаinvoke WriteConsoleA, stdout, esi, 50, ADDR cWritten, 0mov CRLF, 0d0ahinvoke WriteConsoleA, stdout, ADDR CRLF, 2, ADDR cWritten,0 ; переводим каретку на новую строкуinvoke WriteConsoleA, stdout, edi, 10, ADDR cWritten, 0invoke ExitProcess, 0 end start

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

В функции WriteConsoleA записываем регистр, который содержит этот адрес, а затем записываем число, сколько символов хотим вывести, в данном примере для 1 массива это 50, а для второго 10.

И еще кое что: вы, наверное, заметили, что мы трижды использовали функцию вывода. Так вот, во 2 функции мы как раз используем переменную CRLF, которую по сути и выводим. Предварительно в эту переменную мы записали 0d0ah, это говорит Assembler, что мы хотим перейти на новую строку.

Повторим запуск программы

После того как мы записали код, и сохранили его в файл с расширением .asm(у меня это sixth.asm), посмотрим как он сработал:

Открываем командную строку, переходим в папку BIN(напомню, что файл нужно поместить туда, где хранится файл запуска amake.bat) с помощью команды cd BIN.
Далее прописываем amake.bat sixth(вы, естественно, напишите свое имя файла). А затем, запускаем файл sixth.exe
И вот, что должно выйти:

sixth
Если вы не совсем поняли как запускать файлы .asm, то советую вам посмотреть наши предыдущие статьи по Assembler, там более подробно рассказано об этом.

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

Скачать исходники

Инструкция CMPS

Инструкция CMPS сравнивает две строки. Эта инструкция сравнивает два элемента данных одного byte, word или doubleword, на которые указывают регистры DS:SI и ES:DI, и устанавливает соответствующие флаги. Вы также можете использовать инструкции прыжков вместе с этой инструкцией.

В следующем примере мы будем сравнивать две строки, используя инструкцию CMPS:

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

section.text

   global_start            ; должно быть объявлено для использования gcc

_start:                     ; сообщаем линкеру входную точку

   movesi,s1

   movedi,s2

   movecx,lens2

   cld

   repe  cmpsb

   jecxz  equal             ; выполняем прыжок, когда ECX равен нулю

   ; Если не равно, то выполняем следующий код

   moveax,4

   movebx,1

   movecx,msg_neq

   movedx,len_neq

   int80h

   jmpexit

equal:

   moveax,4

   movebx,1

   movecx,msg_eq

   movedx,len_eq

   int80h

exit:

   moveax,1

   movebx,0

   int80h

section.data

s1db‘Hello, world!’,0      ; наша первая строка

lens1equ$s1

s2db‘Hello, there!’,0     ; наша вторая строка

lens2equ$s2

msg_eqdb‘Strings are equal!’,0xa

len_eq  equ$msg_eq

msg_neqdb‘Strings are not equal!’

len_neqequ$msg_neq

Результат:

Strings are not equal!

Инструкция SCAS

Инструкция SCAS используется для поиска определенного символа или набора символов в строке. Искомый элемент данных должен находиться в регистрах AL (для SCASB), AX (для SCASW) или EAX (для SCASD). Искомая строка должна находиться в памяти, и на нее должны указывать ES:DI (или EDI).

Рассмотрим использование инструкции SCAS на практике:

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

section.text

   global_start        ; должно быть объявлено для использования gcc

_start:                 ; сообщаем линкеру входную точку

   movecx,len

   movedi,my_string

   moval,‘e’

   cld

   repnescasb

   jefound; когда нашли

   ; Если не нашли, то выполняем следующее

   moveax,4

   movebx,1

   movecx,msg_notfound

   movedx,len_notfound

   int80h

   jmpexit

found:

   moveax,4

   movebx,1

   movecx,msg_found

   movedx,len_found

   int80h

exit:

   moveax,1

   movebx,0

   int80h

section.data

my_stringdb‘hello world’,0

lenequ$my_string  

msg_founddb‘found!’,0xa

len_foundequ$msg_found

msg_notfounddb‘not found!’

len_notfoundequ$msg_notfound

Результат:

found!

Префиксы повторения

Префикс REP, если он установлен перед строковой инструкцией (например, REP MOVSB), вызывает повторение инструкции на основе счетчика, размещенного в регистре CX. REP выполняет инструкцию, уменьшает CX на 1 и проверяет, равен ли CX нулю. Он повторяет обработку инструкций, пока CX не станет равным нулю.

Флаг направления (DF) определяет направление операции:

   Используйте CLD (англ. «Clear Direction Flag» = «Сбросить флаг направления», DF = 0), чтобы выполнить операцию слева направо.

   Используйте STD (англ. «Set Direction Flag» = «Установить флаг направления», DF = 1), чтобы выполнить операцию справа налево.

Префикс REP также имеет следующие вариации:

   REP — это безусловное повторение, которое повторяет операцию, пока CX не станет равным нулю.

   REPE или REPZ — это условное повторение, которое повторяет операцию до тех пор, пока нулевой флаг (ZF) указывает на равенство нулю результата (сам ZF установлен в единицу). Повторение останавливается, когда ZF указывает на неравенство результата нулю (ZF сброшен в ноль), или когда CX равен нулю.

   REPNE или REPNZ — это также условное повторение, которое повторяет операцию до тех пор, пока нулевой флаг указывает на неравенство нулю результата. Повторение останавливается, когда ZF указывает на равенство нулю результата, или когда CX уменьшается до нуля.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3
Звёзд: 4Звёзд: 5

(

13

оценок, среднее:

4,38

из 5)

loading.gif

Загрузка…

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: