
гос / sp-lect (1)
.pdfб) код підпрограми також потребує використання регістрів і при значній кількості переданих в регістрах параметрів буде вимушений зберігати їх в стеку (пам’яті);
в) обмеження на розмір параметрів, що можуть передаватися за значенням, пов’язане з розрядністю регістрів;
г) абсолютна апаратна залежність виклику.
Приклад 5.1 Приклад демонструє програмування з використанням власних підпрограм, аналогічних за своїми функціональними можливостями стандартним функціям мови С/С++.
.486 ;для bswap
.model flat, stdcall
option casemap :none
.data |
|
src |
db "0123456789",0 |
.data? |
|
dst |
db 1+lengthof src dup (?) |
.code |
|
strlen |
proc ;size_t strlen(const char* String); |
;Вхід: |
EDI – адреса рядка |
;Вихід: |
EAX – довжина рядка |
|
push ecx |
|
push edi |
|
mov ecx,-1 |
|
xor al,al |
|
repnz scasb |
|
neg ecx |
|
lea eax,[ecx-2] |
|
pop edi |
|
pop ecx |
|
ret |
strlen endp
memcpy proc ;void *memcpy(void *dst, const void *src, size_t n)
;Вхід: |
EDI – |
адреса рядка приймача |
; |
ESI – |
адреса рядка джерела |
; |
ECX – кількість |
|
;Вихід: |
немає |
|
|
push esi |
|
|
push edi |
|
|
push ecx |
|
|
push ecx |
|
|
shr ecx,2 |
|
|
rep movsd |
|
|
pop ecx |
|
|
and ecx,3 |
|
|
rep movsb |
|
|
pop ecx |
|
|
pop edi |
|
|
pop esi |
|
|
ret |
|
memcpy |
endp |
|
memset |
proc ;void *memset( void *dest, int c, size_t count ); |
|
;Вхід: EDI – |
адреса рядка, AL – символ, ECX – кількість |
|
;Вихід: |
немає |
|
push edi push ebx mov ah,al mov bx,ax bswap eax mov ax,bx mov ebx,ecx shr ecx,2 rep stosd mov ecx,ebx
and ecx,3 rep stosb pop ebx pop edi ret
memset endp start: lea edi,src call strlen
mov ecx,eax lea esi,src lea edi,dst call memcpy
mov al,-1 ; EDI не змінилося
mov ecx,lengthof dst call memset
ret end start
5.2.2 Передавання параметрів через стек
При передаванні параметрів через стек процесору код, що передує виклику підпрограми розміщує параметри в стеку в наперед визначеному порядку і виконує виклик за допомогою команди CALL.
Команда CALL розміщує на верхівці стеку услід за параметрами інформацію, яка використовується для повернення з підпрограми. При ближньому переході на верхівці стеку розміщується адреса наступної за CALL команди, тобто значення, яке при поверненні із підпрограми командою RET стане значенням регістру RIP|EIP.
Для доступу до параметрів необхідно «пірнути» в глибину стеку не знімаючи з верхівки адресу повернення. Для цього, зазвичай, використовується базова адресація зі зміщенням з базою в регістрі RBP|EBP (нагадаємо, що при використанні EBP у якості базового регістру, у якості сегментної складової за
замовченням використовується регістр SS, тобто виконується звернення до стеку).
Після збереження в стеку за допомогою PUSH RBP|EBP значення RBP|EBP яке буде змінюватися, і копіювання адреси верхівки стеку за допомогою MOV RBP|EBP , RSP|ESP залишається лише відрахувати зміщення від RBP|EBP до розміщених в стеку параметрів підпрограми.
Після фіксації стану стеку в RBP|EBP, виконаної розглянутими вище двома командами, стає можливим використання команд для роботи зі стеком, які змінюють RSP|ESP. Після фіксації стану стеку параметри зі стеку можна забирати коли в цьому виникне потреба. При використанні в підпрограмі команд для роботи зі стеком необхідно забезпечити його стан перед виконанням команди RET таким, яким він був при вході в підпрограму.
Після повернення з підпрограми виконанням команди RET в стеку залишаються параметри, які підпрограма отримувала при виклику. Вирівнювання стеку в той самий стан, який він мав до виклику підпрограми може бути виконано як в коді, що викликав підпрограму, так і безпосередньо у коді самої підпрограми.
Вирівнювання стеку в коді, що викликав підпрограму виконується за допомогою команди ADD RSP|ESP,N після команди CALL. Для вирівнювання стеку безпосередньо у коді самої підпрограми достатньо завершити підпрограму командою RET N. В обох випадках число N дорівнює загальному розміру в байтах усіх параметрів підпрограми в стеку. Зважаючи на те, що система команд процесору не дозволяє заносити в стек байт, число N завжди парне.
Передавання параметрів через стек процесору має наступні переваги:
а) знижує апаратну залежність виклику і спрощує реалізацію компіляторів мов високого рівня;
б) знижує (практично знімає) обмеження на кількість параметрів;
в) знімає обмеження на розмір параметрів, що можуть передаватися за значенням;
г) знижує прив’язку коду підпрограми до регістрів, бо параметри зі стеку можна забирати коли в цьому виникне потреба.
Передавання параметрів через стек має і певні недоліки:
а) уповільнює код виклику через необхідність попереднього розміщення параметрів в стеку, який є частиною пам’яті;
б) збільшує код підпрограми і зменшує швидкість виконання підпрограми через необхідність доступу до параметрів в стеку, який є частиною пам’яті.
Приклад 5.2 Цей приклад відрізняється від попереднього (Приклад 5.1) лише іншою реалізацією передавання параметрів в підпрограми – через стек.
.486 ;для bswap
.model flat, stdcall
option casemap :none
.data |
|
src |
db "0123456789",0 |
.data? |
|
dst |
db 1+lengthof src dup (?) |
.code |
|
strlen |
proc ;size_t strlen(const char* String) |
;Вхід: |
адреса рядка в стеку, |
;Вихід: |
EAX – довжина рядка |
|
push ecx |
|
push edi |
|
mov edi,[esp+12] |
|
mov ecx,-1 |
|
xor al,al |
|
repnz scasb |
|
neg ecx |
|
lea eax,[ecx-2] |
|
pop edi |
|
|
pop ecx |
|
|
ret 4 |
|
strlen |
endp |
|
memcpy |
proc ;void *memcpy(void *dst, const void *src, size_t n) |
|
|
push ebp |
|
|
mov ebp,esp |
|
|
push edi |
|
|
push esi |
|
|
mov edi,[ebp+8] |
|
|
mov esi,[ebp+12] |
|
|
mov ecx,[ebp+16] |
|
|
shr ecx,2 |
|
|
rep movsd |
|
|
mov ecx,[ebp+16] |
|
|
and ecx,3 |
|
|
rep movsb |
|
|
pop ebp |
|
|
pop esi |
|
|
pop edi |
|
|
ret |
|
memcpy |
endp |
|
memset |
proc |
;void *memset(void *dest, int c, size_t count ); |
|
push ebp |
|
mov ebp,esp push edi push ebx
mov edi,[ebp+14] mov al,[ebp+12] mov ecx,[ebp+8] mov ah,al
mov bx,ax bswap eax
mov ax,bx mov ebx,ecx
shr ecx,2 rep stosd and ecx,3 rep stosb pop ebx pop edi pop ebp ret 10
memset endp
start: push offset src |
|
call strlen |
|
push eax |
; в EAX довжина рядка від strlen |
push offset src |
|
push offset dst |
|
call memcpy |
|
add esp,12 |
|
push offset dst mov al,-1 movzx ax,al push ax
push lengthof dst call memset
ret end start
6 МАКРОЗАСОБИ MICROSOFT MACRO ASSEMBLER
Макрозасоби надають можливість розробляти гарно стилізований і структурований код. Використання макрозасобів дозволяє спростити і скоротити вихідний текст програми шляхом запобігання дублювання одних і тих самих фрагментів коду як у межах однієї програми, так і від програми до

програми, зробивши його при цьому більш зрозумілим і зменшивши кількість можливих помилок кодування. Макрозасоби являють собою потужний мовний засіб асемблерів і роблять програмування на асемблері більш наочним, легким, витонченішим і схожим на програмування мовами високого рівня.
Макрозасоби – це певний набір сервісних послуг асемблера, який реалізується програмними засобами транслятора. Для асемблерів які підтримують макрозасоби транслятор складається з двох частин – препроцесора (макроасемблера) і власне транслятора (асемблера).
Вихідний текст програми, що використовує макрозасоби, може бути взагалі не схожим на асемблерну програму. Спочатку цей вихідний текст обробляться препроцесором, що виконує так звану макрогенерацію, готуючи технологічний лістинг для подальшої обробки транслятором. В процесі макрогенерації виконується «переклад» з мови препроцесора на мову, зрозумілу транслятору.
Набір макрозасобів (мова препроцесора) різниться для різних пакетів асемблерів, хоча існує певна їх частина, яка підтримується більшістю пакетів в незмінному синтаксисі. Мабуть найбільшу кількість власних директив і операторів має препроцесор FASM, однак не дуже відстає від нього і MASM, повна назва якого Microsoft Macro Assembler підкреслює його орієнтованість на використання макрозасобів і директив компіляції високо рівня.
Макрозасоби MASM можна розділити на декілька основних груп
Рисунок 6.1
Кожна наступна група макрозасобів зображених на рисунку в порядку зліва направо розширюватиме наші можливості щодо макропрограмування.
6.1 Текстові макро
Текстовий макро дозволяє призначити ім’я (скорочення, аліас) певній послідовності символів, а потім використати це ім'я замість тексту у вихідному коді. Визначається текстовий макро за допомогою директиви TEXTEQU в одній із наступних форм
name TEXTEQU <Текст>
name TEXTEQU %Константний вираз
name TEXTEQU Інший текстовий макро
name TEXTEQU Макрофункція
Символи «<», «>», «%» є макро операторами препроцесора. «Лапки» з символів < > визначають літерали, вказуючи препроцесору, що він повинен сприймати щось укладене у них як єдиний рядок символів, навіть якщо він містить коми, пробіли та будь-які інші символи. При цьому самі символи «<» і «>» в рядок не потрапляють.
Якщо права частина текстового макро містить символи «<», «>», «,» «"», «'», «%», «;» які можуть мати функціональність в тому, чи іншому вираженні їх слід екранувати (затінити, відмінити) за допомогою макрооператора «!», який повідомляє транслятору про те, що наступний за ним символ слід уважати просто символом, навіть якщо це кома, кутова дужка або щось інше.
В константних арифметичних виразах можуть використовуватися оператори LENGTH, SIZE, WIDTH, MASK, LENGTHOF, SIZEOF, унарні «+» і «-», знаки бінарних операцій «+»,«-», «*», «/», MOD, оператори побітового зрушення SHL, SHR, логічні побітові операції NOT, AND, OR, XOR, дужки «(»
та «)».
Операндами арифметичних виразів можуть виступати числа, попередньо визначені макрозмінні, макропараметри. В арифметичних виразах не повинно
бути нічого, що препроцесор не може обчислити на етапі трансляції. Препроцесор MASM не розрізняє знакові і без знакові двійкові і шістнадцяткові числа, значення числа не повинно виходити за діапазон подвійного слова, препроцесор ніяк не контролює переповнення.
Макрооперанд «%» використовується для того, щоб повідомити препроцесору про те, що весь наступний за ним текст являє собою константний вираз або текстовий макро, який потрібно попередньо обчислити або розгорнути, і надалі використовувати результат у текстовому вигляді.
6.2 Макровизначення
Використання текстових макро певним чином підвищує наочність вихідного тексту програми, але з їх допомогою в програмі можна замінити не більше одного рядка. Подальшим розвитком механізму макропідстановок і макрогенерації є використання макровизначень.
За допомогою макровизначень до вихідного тексту програми можна вставляти цілі послідовності машинних команд, директив визначення даних, коментарів та будь-яких інших синтаксичних конструкцій асемблера, що можуть зустрічатися в програмі. Найпростіше макровизначення має наступний вигляд:
name MACRO
Макровизначення
ENDM
Макровизначення розміщують на початку вихідного тексту програми або в окремому файлі. При розміщенні тексту макровизначення в окремому файлі на початку програми необхідно задати директиву INCLUDE [[диск:\]шлях\]ім'я файла. Зустрівши директиву INCLUDE, препроцесор знайде зазначений файл і вставить увесь його текст до технологічного лістингу програми, після чого продовжить подальше оброблення. Таким чином, директиву INCLUDE можна вважати певним розширенням текстових макро. За допомогою INCLUDE до