Работа со стеком. Стековые передачи • • • PUSH op ; (E)SP - 2 (4) в зависимости от атрибута размера адреса use 16 или use 32, op – mem, reg, 16, 32 разряда ; op [ SS: SP (ESP) ], помещает операнд в вершину стека. Непосредств. оп. 8, 16, 32 бита ; 8 –ми битовый операнд расширяется до 16 (32) бит. Пр. PUSH AX push word ptr MEM push esp ; включает в стек то значение ESP (SP), которое было до выполнения команды ( для i 8086 push sp – новое- (SP-2). PUSH A ; включает в стек содержимое регистров AX, CX, DX, BX, SP, BP, SI, DI PUSHAD ; “------” EAX, EBX, ……. , EDI PUSHF ; SP – 2: FLAGS [SS: SP] PUSHFD ; ESP – 4 ; EFLAGS [ SS: ESP ] Выполнение команды PUSH и её разновидностей не влияет на флаги в FLAGS.
Команды извлечения из стека • • POP op ; [ SS: SP (ESP) ] op , SP (ESP) + 2 (4). op – mem, reg 16, 32 бита ; содержимое из вершины стека помещается в операнд (16, 32) , указатель стека увеличивается на 2 ( 4 ). POPA ; извлечение из стека в DI, SI, BP, SP, BX, DX, CX, AX (обратно PUSHA ) POPAD ; извлече 6 ние из стека в EDI, ESI, ……, ECX, EAX ( обратно PUSHAD ) POPF ; [ SS: SP(ESP) ] FLAGS , SP(ESP) +2; ( обратно ком. PUSHF ) POPFD ; [ SS: ESP] EFLAGS , ESP + 4; ( обратно ком. PUSHFD ) • Поскольку стек работает в режиме LIFO ( last input – first output ) , то при записи и выборке из стека это необходимо учитывать: • • P 1 PROC push ax push ds …. . …… pop ds pop ax ENDP
Атрибутные операторы, возвращающие значение • • • • • LENGTH <имя переменной> ; возвращает число единиц переменных в конструкции DUP, если она является первым операндом после DB, DW, DD и 1 в остальных случаях. SIZE < имя переменной > ; возвращает число байт, соответствующих массиву. OFFSET < метка или переменная > ; Возвращает эффективный адрес (полное смещение в сегменте). SEG < метка или переменная > ; Возвращает соответствующий сегментный адрес. Пример: data segment para public ‘d 1’ A dw 125 B dw 100 DUP (? ) data ends code segment ‘c 1’ ……………. mov cx, LENGTH B ; cx = 100 mov dx, SIZE B ; dx = 200 mov bx , offset B ; bx = 2 ( lea bx, B ) mov ax , seg B ; ax = data ( адрес сегмента из ds ) …………….
Процедуры • Это некоторая последовательность действий, которая может быть выделена как самостоятельная программа, и к которой можно обращаться из любого места программы любое число раз ( аналог п/п или функций в ЯВУ). • В основной программе процедуры обычно размещают либо в начале сегмента кода перед командой, с которой начинается программа ( точка входа), либо в конце сегмента команд. • Описание процедур: имя PROC < дистанция > • < тело процедуры > • имя ENDP • Имя процедуры ассемблер как метку на первую команду процедуры. Процедура может быть описана как ближняя ( NEAR ) в поле дистанции или дальняя ( FAR ). К процедурам типа NEAR можно обращаться только из того сегмента кода, где она описана, а к процедурам типа FAR из любого сегмента кода. По умолчанию процедура считается ближней ( NEAR ). Имена и метки, описанные в процедуре не локализуются внутри её, т. е. Не должны совпадать с другими идентификаторами программы. В больших программах процедуры часто размещают в отдельных сегментах кода и к ним обращение осуществляется через полный указатель ( CS: IP(EIP) – дальний указатель FAR ). • Обращение к процедурам можно осуществлять с помощью команды безусловного перехода (JAMP near ( far)), предварительно предусмотрев передачу параметров и точку возврата в вызывающую программу. Для упрощения работы с процедурами в систему команд процессора включены команды вызова ( CALL ) возврата из процедуры ( RET ).
Команды вызова и возврата процедур • • • • Относятся к командам безусловной передачи управления. Вызовы выполняются так же как и командой JAMP, но вначале они запоминают текущее значение указателя команд (IP(EIP), или CS: IP(EIP) в стеке, что позволяет вернуть управление из процедуры на следующую команду после CALL. Команды возвратов осуществляют эту передачу управления, восстанавливая из стека адрес точки возврата в указателе команды. Вызовы и возвраты – средства организации процедур. Так же, как и команда переходов, вызовы и возвраты бывают двух видов: внутрисегментные и межсегментные. Форматы команд: CALL < метка > ; внутрисегментный прямой вызов, SP=SP-2; IP [SS: SP]; IP+ met IP met – смещение между меткой и командой после CALL word ptr MEM ; внутрисегментный косвенный вызов, SP=SP-2; IP [SS: SP]; в MEM эффективный адрес (смещение) к имени процедуры. [MEM] IP. CALL far <метка> ; межсегментный прямой вызов, SP=SP-2; CS SS: SP; SP=SP-2, IP SS: SP; IP offset <метка>, CS seg < метка >. CALL dword ptr MEM 1 ; межсегментный косвенный вызов, SP=SP-2; CS SS: SP; SP=SP-2, IP SS: SP; IP [MEM 1], CS [MEM 1+2]. RET ; Внутрисегментный возврат, [SS: SP] IP, SP = SP+2. RET n ; Внутрисегментный возврат, [SS: SP] IP, SP = SP+2, SP= SP+n. RET ; межсегментный возврат, [SS: SP] IP, SP = SP+2, [SS: SP] CS, SP = SP+2. RET n ; межсегментный возврат, [SS: SP] IP, SP = SP+2; [SS: SP] CS, SP = SP+2+n.
Передача параметров процедур • • • • Можно разделить на две группы: 1) В каком виде их передаём: - по значению, - по ссылке ( передача адреса) 2) Через чего передаётся , т. е. Где размещаются параметры перед вызовом процедуры: - регистры, - память. Передача параметров через регистры – самый простой способ, при этом процедура не будет тратить время на извлечение их из памяти. Пример: mov ax, A mov bx, offset mas 1 call proc 12 Память – чаще всего используется передача параметров через стек, особенно когда параметров много. Пример2: push A push offset mas 1 call proc 11 В этом случае после передачи управления процедуре proc 11 в вершине стека будет находиться адрес точки возврата (значение IP), по адресу [SP+2] offset mas 1, а по адресу [SP+4] значение переменной А.
Локальные переменные процедур • Если по алгоритму в процедуре требуются локальные переменные, то место под них, как правило отводятся в стеке. • Пример: Пусть требуется выделить память в стеке под m переменных длиной WORD • pr 1 proc • push bp • mov bp, sp • sub sp, m*2 • mov [bp-2], 100 • ……… • mov sp, bp • pop bp • ret • pr 1 endp • Таким образом доступ к локальным переменным осуществляется через регистр bp. • Все действия программы, связанные с подготовкой стека для работы с процедурой называются прологом, а все действия, которые выполняются по восстановлению регистров и освобождению стека, называются эпилогом.
Модульное программирование • Под модулем понимается часть программы, решающая некоторую задачу, и которая может быть оттранслирована отдельно. Частным случае модуля может служить процедура. Существует два основных способа объединения модулей: • 1. Все модули объединяются в одну программу, которая проходит этапы компиляции и редактирования связей ( т. е. имеют одинаковое имя сегмента кода и тип public). M 1 M 2 Компиляция MASM Редактирование LINK M. exe M 3 2. Модули пишутся и компилируются раздельно. Объединяются на этапе редактирования M 1 Компиляция MASM M 2 Компиляция MASM M 3 Компиляция MASM Редактирование LINK M. exe
Структура любого модуля близка к структуре программы, то есть это последовательность • предложений, оканчивающаяся директивой END. У головной программы END с именем точки входа. Все имена в модуле локализуются. • Для связи переменных и меток из разных модулей существуют две директивы. Для указания внешних переменных и меток предназначена директива EXTRN: • Формат: EXTRN < имя >: < тип >, …, < имя > : < тип > • Где тип – BYTE, WORD, DWORD – для внешних переменных, NEAR, FAR – для меток и имен процедур, ABS – стандартная константа со значением 0. Данная директива может указываться любое число раз в любом месте модуля. • Например: EXTRN A: WORD, PR 1: FAR, K: ABS • Чтобы имена и метки были доступны из модулей, где они описаны, необходимо использовать директиву PUBLIC < имя >, …, < имя > • Пример: Модуль1 Модуль2 • extrn A 1: WORD, B 1: FAR DATA segment • code segment A 1 DW 55 • ………. DATA ends • mov ax, A 1 public A 1, B 1 • ………. • call B 1 proc far • ……. . ………. • code ends B 1 endp
Определение сегментов Одной из задач ассемблера при трансляции программ в машинный код заключается в назначении смещений меткам и переменным. Ассемблер должен также передавать редактору связей (через объектные модули) всю информацию, необходимую для объединения различных сегментов и модулей в законченную программу. Для этого предусмотрено несколько директив. Чтобы назначать смещения переменным и меткам, ассемблер должен знать точную структуру каждого сегмента. Для определения сегмента предназначена директивы SEGMENT и ENDS. Структуры сегментов данных, стека и кода были рассмотрены ранее. Кроме конструкции сегментов ассемблер должен знать точное соответствие между сегментами и сегментными регистрами. Назначение сегментов сегментным регистрам осуществляется директивой ASSUME. Директива определения сегмента: Имя SEGMENT [выравнивание (1)] [тип объединения (2) ] [разрядность(3) ] [‘класс ‘ (4) ] (1) – параметр определяет границу, на которую выравнивается сегмент (начало сегмента): BYTE, WORD, DWORD, PARA ( кратно 16 байт, используется по умолчанию), PAGE ( *256). (2) - ассемблер позволяет редактору связей управлять организацией сегментов из различных объектных модулей. Сегменты с различными именами объединять нельзя. Типы объединения: PUBLIC – сегменты с одинаковыми именами но разными классами сцепляются в один сегмент; COMMON – сегменты с одинаковыми именами перекрываются, то есть имеют один и тот же начальный адрес. Размер сформированного сегмента равен размеру наибольшего из сегментов; STACK – одноименные сегменты объединяются в один суммарной длины. SP – конец сегмента; AT выражение – значение выражения задает начальный адрес сегмента (Н: AT 0 B 800 h ); MEMORY – вызывает размещение в конце загрузочного модуля. (3) USE 16 или USE 32 – определяют размер ( 64 Кб или до 4 Гб ) сегмента и размер адреса (смещения) в сегменте. (4) - определяет порядок следования сегментов при компановке программы из нескольких модулей.