Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

epd629

.pdf
Скачиваний:
17
Добавлен:
02.05.2015
Размер:
1.05 Mб
Скачать

-или отсутствие ends или endp.

7.3Программист забывает о возврате в DOS

Имеется несколько вариантов возврата в DOS. Правильно завершать работу будет, например, программа, использующая команду ret или функцию 4Ch (см. учеб.пособ., ч.1). Об ошибках при возврате из процедуры в вызвавшую программу см. подраздел 7.19.

7.4. Вызов процедуры, которая портит содержимое нужных регистров

При разработке программы на ассемблере нередко подразумевают, что при обращении к другим процедурам регистры остаются неизмененными. На самом деле это не так. Каждая процедура может сохранить или уничтожить содержимое любого из регистров (см. учеб.пособ., ч.1, 2 и 6).

7.5. Неопределенные символические имена

Ассемблер генерирует сообщение о неопределенном символическом имени, если имя не определено в поле метки какойлибо строки ассемблерной программы. Неправильная запись имени является наиболее вероятной причиной такой ошибки (см. учеб.пособ., ч.1).

7.6. Повторное определение символического имени

Ассемблер формирует сообщение об ошибке повторного определения символического имени в тех случаях, когда одно и то же имя или метка два и более раз повторяются в поле метки ассемблерных строк программы (см. учеб.пособ., ч.1).

7.7. Неправильный порядок операндов

Многие программисты ошибаются и изменяют порядок операндов в инструкциях процессора 8086 на обратный (см. учеб.пособ., ч.1).

7.8. Неправильное использование операндов

51

7.8.1. В языке ассемблера первый операнд всегда используется в качестве приемника (кроме синтаксиса АТ&T для UNIX и Linux, см. учеб.пособ., ч.12). Операнд может быть записан в регистре общего назначения, регистре сегмента или ячейке памяти. Генерируется сообщение об ошибке при использовании непосредственных данных в качестве операнда-приемника:

cmp З,al; нельзя заменить значение 3 содержимым регистраal. Правильная запись:

cmp al,3 ; (содержимое регистра al станет равным 3).

7.8.2. Нельзя задавать 8- и 16-разрядный (а для процессора 80386 также 8- и 32-разрядный или 16- и 32-разрядный) операнды одной команды (это правило не распространяется на команды movsx и movzx, см. учеб.пособ., ч.7). Ошибочны следующие команды (несоответствие типов):

mov al, w1 ; al – однобайтовый регистр;

;w1 – однословная переменная памяти ( два байта);

mov ax, b1 ; ax – однословный регистр;

;b1 – однобайтовая переменная.

7.8.3.Операции со стеком не должны содержать переменные памяти (разрешаются – только однословные регистры). Если 8- разрядную или 16-разрядную переменную указывают для работы со стеком (push/pop), то выдается сообщение об ошибке. Примеры:

а) push w1 ; правильно будет: mov ax,w1 и затем push ax б) pop w1 ; правильно будет: pop ax и затем mov w1,ax

в) push b1

; правильно: mov al,b1 и затем xor ah,ah и push

ax

 

г) pop b1

; правильно будет: pop ax и затем mov al,b1/

Примечание. Использование стека для временного хранения однобайтовых переменных (см. примеры в) и г)) не эффективно.

7.9. Потеря содержимого регистра при умножении

Всегда нужно помнить о том, что при использовании инструкций mul и imul изменяется (не сохраняется) содержимое не только регистров al, ax или eax, но также и ah, dx или edx (см. учеб.пособ.,

ч. 1).

7.10. Не подготовлены регистры при делении

52

Распространенной ошибкой для случаев, когда делимое по размеру совпадает с делителем и размещено в al, ax или eax, является то, что программист не обнуляет регистры ah, dx, edx (см. учеб.пособ.,

ч.1).

7.11. Неправильное использование регистра после деления

Не учитывается, что регистры ah и dx содержат остаток, и используют их в качестве старших разрядов частного (см. учеб.пособ., ч. 1).

Пример ошибочного использования регистра edx после деления учетверенного слова на двойное слово приведен в учеб.пособ., ч.7.

7.12. Потеря содержимого регистра при делении

Потеря содержимого регистра при делении заключается в том, что теряется значение, помещенное в регистр до операции деления (значение регистров al, ah , ax, dx, eax, edx,, помещенное в регистр до операции деления, теряется, т.к. в регистр будет помещено частное (в регистры al, ax, eax) или остаток от деления (в регистры ah , dx, edx), см. учеб.пособ., ч. 1 и 7.

7.13.Неправильное использование регистров

вкомандах cbw и cwd

Неправильное использование регистров при преобразованиях заключается либо в ошибочном размещении исходного значения, либо в не использовании в качестве результата регистров ax, (dx:ax). Для cbw и cwd исходные значения должны быть размещеныв регистрах al и ax соответственно. Программисты же иногда ошибочно помещают исходное значение в какой-нибудь другой регистр (например, в bl или bx).

7.14.Применение команд cbw и cwd

кбеззнаковым данным

При применении команд cbw и cwd к беззнаковым данным может возникнуть ошибка. Когда знаковый бит равен единице, старший байт или слово соответственно заполняется единицами, а не

53

нулями, как требуется в этом случае. Это надо учитывать при выполнении логических операций и операций сдвига и вместо инструкций cbw и cwd применять зануление старшего байта или слова командой xor (см. учеб.пособ., ч.1).

7.15. Изменение отдельными инструкциями флага переноса

Некоторые инструкции не влияют на те флаги, изменения которых вы ожидаете. Например, инструкция inc в случае слишком большого для операнда-приемника результата на флаг cf не влияет.

То же самое имеет место для инструкций dec, loop, loopz и loopnz (не влияют на состояние флагов, см. учеб.пособ., ч.6 и 7).

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

7.16. Программист долго не использует состояние флагов

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

7.17. Ошибки при использовании регистров

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

7.17.1.Нельзя задавать 8- и 16-разрядный (а для процессора 80386 также 8- и 32-разрядный или 16- и 32-разрядный) регистры для записи двух операндов одной команды (это правило не распространяется на команды movsx и movzx, см. учеб.пособ., ч.7).

7.17.2.Операции со стеком должны осуществляться только в шестнадцатибитовых кодах. Если 8-разрядный регистр указывают для работы со стеком (push/pop), то выдается сообщение об ошибке.

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

54

могут быть использованы либо как источники операндов, либо как приемники операндов, но никак не одновременно (см. учеб.пособ., ч. 1).

7.18. Выход из диапазона адресов

Все команды условного перехода используют режим относительной адресации с 8-битовым смещением. Если относительный адрес символа, используемого в качестве операндаадреса перехода, выходит за диапазон + 127 байтов - 128 байтов от конца команды условного перехода, выдается сообщение об ошибке (см. учеб.пособ., ч. 1)

7.19. Программист забывает об инструкции ret

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

в) возврата из подпрограммы в вызывающую программу.

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

7.20. Генерация неверного типа возврата

Директива proc, во-первых, определяет имя, по которому будет вызываться процедура, и, во-вторых, управляет типом (ближним или дальним) процедуры.

Тип процедуры используется ассемблером для определения того, какой тип вызовов нужно генерировать при вызове процедуры из того же исходного файла и для определения типа инструкции ret, которая выполняется, когда процедура возвращает управление. Например:

; Подпрограмма ближнего типа для сдвига dx:ax вправо на 2 байта.

LongShiftRight2 proc near

shr

dx,1

 

rcr

ax,1

; сдвиг dx:ax вправо на 1 бит

shr

dx,1

 

rcr

ax,1

; сдвиг dx:ax вправо еще на 1 бит

 

 

55

ret LongShiftRight2 endp

Ассемблер обеспечивает, что инструкция ret будет ближнего типа, так как LongShiftRight2 это процедура ближнего типа (near). Однако, если директиву proc изменить следующим образом:

LongShiftRight2 proc far,

то будет генерироваться инструкция ret дальнего типа (far).

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

Например:

; Подпрограмма дальнего типа для сдвига dx:ax на 2 бита

LongShiftRight2 proc far

 

call

LongShiftRight

; сдвиг dx:ax вправо на 1 бит

call

LongShiftRight

; сдвиг dx:ax вправо еще на 1 бит

ret

 

 

 

LongShiftRight:

 

 

shr

dx,1

 

 

rcr

ax,1

; сдвиг dx:ax вправо на 1 бит

ret

 

 

 

LongShiftRight2 endp

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

Хорошим решением здесь будет наличие в каждой подпрограмме директивы proc. Вложенные директивы proc прекрасно работают.

; Подпрограмма дальнего типа для сдвига dx:ax на 2 бита.

;

LongShiftRight2

proc far

 

call

LongShiftRight

; сдвиг dx:ax вправо на 1 бит

call

LongShiftRight

; сдвиг dx:ax вправо еще на 1 бит

ret

 

 

 

LongShiftRight

proc near

 

shr

dx,1

 

56

 

 

 

rcr ax,1

; сдвиг dx:ax вправо на 1 бит

ret

 

LongShiftRight2

endp

LongShiftRight

endp

Также, как и последующие процедуры:

; Подпрограмма дальнего типа для сдвига dx:ax на 2 бита.

;

LongShiftRight2

proc far

call

LongShiftRight; сдвиг dx:ax вправо на 1 бит

call

LongShiftRight; сдвиг dx:ax вправо еще на 1 бит

ret

 

 

LongShiftRight2

endp

LongShiftRight

proc near

shr

dx,1

 

rcr

ax,1 ; сдвиг dx:ax вправо на 1 бит

ret

 

 

LongShiftRight

endp

Для явной генерации ближнего или дальнего возврата можно использовать соответственно инструкции retn и retf. Вы можете обеспечить с их помощью корректную генерацию инструкций возврата.

7.21. Ошибки при использовании условных переходов

Использование в языке ассемблера инструкций условных переходов (je, jne, jc, jnc, ja, jb, jg и т.д.) обеспечивает большую гибкость в программировании, но при этом очень просто ошибиться, выбрав неверный переход. Типичные ошибки:

7.21.1.Использование инструкций ja, jb, jae или jbe для сравнения значений со знаком или, соответственно, инструкций jg, jl, jge или jle для сравнения беззнаковых значений.

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

7.21.3.Использование инвертированной логики, например, применение инструкции js там, где нужно использовать jns.

Один из подходов, позволяющий минимизировать ошибки при использовании условных переходов состоит в комментировании

57

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

;

; if { Length > MaxLength } {

;

mov

ax,[Length]

cmp

ax,[MaxLength]

jng

LengthIsLessThanMax

. . .

 

jng

EndMaxLengthTest

;

 

;} else {

;

LengthIsLessThanMax:

. . .

;

;}

;

EndMaxLengthTest:

7.22. Путают операнды в памяти и непосредственные

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

Например, смещение переменной в памяти размером в слово MemLoc представляет собой константу 5002, которую можно получить с помощью оператора OFFSET. Например, инструкция

mov bx,OFFSET MemLoc

загружает значение 5002h в регистр bx. Значение 5002h представляет собой непосредственный операнд. Другими словами, оно встроено непосредственно в инструкцию и не изменяется.

Значением MemLoc является 1234h. Оно считывается из памяти со смещением 5002h в сегменте данных. Один из способов считывания данного значения состоит в загрузке в регистр bx, si, di или bp смещения MemLoc и использовании данного регистра для адресации к памяти. Инструкции

mov bx,OFFSET MemLoc mov ax,[bx]

загружают значение MemLoc в регистр ax. Значение MemLoc можно также загрузить непосредственно в ax с помощью инструкции

mov ax,MemLoc

58

или

mov ax,[MemLoc]

Здесь значение 1234h получается как прямой, а не как непосредственный операнд: инструкция mov использует встроенное в нее смещение 5002h и загружает в ax значение по смещению 5002h, которое в данном случае равно 1234h.

Часто встречающейся ошибкой является то, что часто забывают использовать операцию offset, например:

mov si,MemLoc,

где нужно использовать смещение MemLoc. На первый взгляд, данная строка не выглядит неправильной, и так как MemLoc это переменная размером в слово, то эта строка не приведет к ошибке ассемблирования. Однако при выполнении в si будут загружены содержащиеся в переменной MemLoc данные (1234h), а не ее смещение (5002h) и результаты будут непредсказуемы.

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

mov si,offset MemLoc

и

mov si,[MemLoc]

становится совершенно понятной, в то время как инструкция mov si,MemLoc

будет настораживать.

Библиографический список

1.Зубков С.В. Assembler для DOS, Windows и UNIX. / С.В. Зубков 11-е изд.,

стер. М.: ДМК Пресс; СПб.: Питер, 2010. 640 с.

2.Бурдаев О.В. Ассемблер в задачах защиты информации / О.В. Бурдаев, М.А. Иванов, И.И. Тетерин Под ред. И.Ю. Жукова. – 3-е изд., стер. – М.: КУДИЦ-

ОБРАЗ, 2008. – 544 с.

59

3. Абель Питер. Язык и программирование для IBM PC. / Питер Абель. Издво КОРОНА-Век, 2009.

60

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]