4 какие действия выполняют команды передачи управления
Перейти к содержимому

4 какие действия выполняют команды передачи управления

  • автор:

Архитектура компьютера

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

Jxx , где xx определяется условием перехода.

В качестве источника условия может использоваться регистр cx, любая арифметическая или логическая команда или специальная команда сравнения cmp :

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

Таблица 1. Перечень команд условного перехода для команды cmp ,

Типы операндов Команда Критерий перехода Значения флагов
Любые je операнд_1 = операнд_2 zf = 1
Любые jne операнд_1<>операнд_2 zf = 0
Со знаком jl / jnge операнд_1 < операнд_2 sf <> of
Со знаком jle / jng операнд_1 sf <> of or zf = 1
Со знаком jg / jnle операнд_1 > операнд_2 sf = of and zf = 0
Со знаком jge / jnl операнд_1 => операнд_2 sf = of
Без знака jb / jnae операнд_1 < операнд_2 cf = 1
Без знака jbe / jna операнд_1 cf = 1 or zf=1
Без знака ja / jnbe операнд_1 > операнд_2 cf = 0 and zf = 0
Без знака jae / jnb операнд_1 => операнд_2 cf = 0

Таблица 2. Команды перехода по состоянию флагов

Команда Флаг Команда Флаг

4 какие действия выполняют команды передачи управления

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

В зависимости от типа перехода различают:

  • переход типа short (короткий переход) — если адрес перехода находится в пределах от -127 до +128 байт от команды JMP;
  • переход типа near (ближний переход) — если адрес перехода находится в том же сегменте памяти, что и команда JMP;
  • переход типа far (дальний переход) — если адрес перехода находится в другом сегменте. Дальний переход может выполняться и в тот же самый сегмент, если в сегментной части операнда указано число, совпадающее с текущим значением CS;
  • переход с переключением задачи — передача управления другой задаче в многозадачной среде. Этот вариант будет рассмотрен в главе, посвященной защищенному режиму.

При выполнении переходов типа short и near команда JMP фактически изменяет значение регистра EIP (или IP), изменяя тем самым смещение следующей исполняемой команды относительно начала сегмента кода. Если операнд — регистр или переменная в памяти, то его значение просто копируется в EIP, как если бы это была команда MOV. Если операнд для JMP — непосредственно указанное число, то его значение суммируется с содержимым EIP, приводя к относительному переходу. В ассемблерных программах в качестве операнда обычно указывают имена меток, но на уровне исполнимого кода ассемблер вычисляет и записывает именно относительные смещения.

Выполняя дальний переход в реальном режиме, виртуальном режиме и в защищенном режиме (при переходе в сегмент с теми же привилегиями), команда JMP просто загружает новое значение в EIP и новый селектор сегмента кода в CS, используя старшие 16 бит операнда как новое значение для CS и младшие 16 или 32 — как значение IP или EIP.

Это набор команд, каждая из которых выполняет переход (типа short или near), если удовлетворяется соответствующее условие. Условием в каждом случае реально является состояние тех или иных флагов, но, если команда из набора Jcc используется сразу после СМР, условия приобретают формулировки, соответствующие отношениям между операндами СМР (см. табл. 7). Например, если операнды СМР были равны, то команда JE, выполненная сразу после этого СМР, осуществит переход. Операнд для всех команд из набора Jcc — 8-битное или 32-битное смешение относительно текущей команды.

Слова «выше» и «ниже» в таблице относятся к сравнению чисел без знака, слова «больше» и «меньше» учитывают знак.

Таблица 7. Варианты команды Jcc

Код команды Реальное условие Условие для CMP
JA
JNBE
CF = 0 и ZF = 0 если выше
если не ниже или равно
JAE
JNB
JNC
CF = 0 если выше или равно
если не ниже
если нет переноса
JB
JNAE
JC
CF = 1 если ниже
если не выше или равно
если перенос
JBE
JNA
CF = 1 и ZF = 1 если ниже или равно
если не выше
JE
JZ
ZF = 1 если равно
если ноль
JG
JNLE
ZF = 0 и SF = OF если больше
если не меньше или равно
JGE
JNL
SF = OF если больше или равно
если не меньше
JL
JNGE
SF <> OF если меньше
если не больше или равно
JLE
JNG
ZF = 1 и SF <> OF если меньше или равно
если не больше
JNE
JNZ
ZF = 0 если не равно
если не ноль
JNO OF = 0 если нет переполнения
JO OF = 1 если есть переполнение
JNP
JPO
PF = 0 если нет четности
если нечетное
JP
JPE
PF = 1 если есть четность
если четное
JNS SF = 0 если нет знака
JS SF = 1 если есть знак

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

cmp ах,0 jne local_1 jmp far_label ; переход, если АХ = 0 lосаl_1:

Выполняет ближний переход на указанную метку, если регистр CX или ECX (для JCXZ и JECXZ соответственно) равен нулю. Так же как и команды из серии Jcc, JCXZ и JECXZ не могут выполнять дальних переходов. Проверка равенства СХ нулю, например, может потребоваться в начале цикла, организованного командой LOOPNE, — если в него войти с СХ = 0, то он будет выполнен 65 535 раз.

Уменьшает регистр ЕСХ на 1 и выполняет переход типа short на метку (которая не может быть дальше, чем на расстоянии от -128 до +127 байт от команды LOOP), если ЕСХ не равен нулю. Эта команда используется для организации циклов, в которых регистр ЕСХ (или СХ при 16-битной адресации) играет роль счетчика. Так, в следующем фрагменте команда ADD выполнится 10 раз:

mov cx,0Ah loop_start: add ax,cx loop loop_start

Команда LOOP полностью эквивалентна паре команд

dec ecx jz метка

Но LOOP короче этих двух команд на один байт и не изменяет значения флагов.

Все эти команды уменьшают регистр ЕСХ на один, после чего выполняют переход типа short, если ЕСХ не равен нулю и если выполняется условие. Для команд LOOPE и LOOPZ условием является равенство единице флага ZF, для команд LOOPNE и LOOPNZ — равенство флага ZF нулю. Сами команды LOOPcc не изменяют значений флагов, так что ZF должен быть установлен (или сброшен) предшествующей командой. Например, следующий фрагмент копирует строку из DS:SI в строку в ES:DI (см. описание команд работы со строками), пока не кончится строка (СХ = 0) или пока не встретится символ с ASCII-кодом 13 (конец строки):

mov cx,str_length move_loop: stosb lodsb cmp al,13 loopnz move_loop

Сохраняет текущий адрес в стеке и передает управление по адресу, указанному в операнде. Операндом может быть непосредственное значение адреса (метка в ассемблерных программах), регистр или переменная, содержащие адрес перехода. Если в качестве адреса перехода указано только смещение, считается, что адрес расположен в том же сегменте, что и команда CALL. При этом, так же как и в случае с JMP, выполняется ближний вызов процедуры. Процессор помещает значение регистра EIP (IP при 16-битной адресации), соответствующее следующей за CALL команде, в стек и загружает в EIP новое значение, осуществляя тем самым передачу управления. Если операнд CALL — регистр или переменная, то его значение рассматривается как абсолютное смещение, если операнд — метка в программе, то ассемблер указывает ее относительное смещение. Чтобы осуществить дальний CALL в реальном режиме, режиме V86 или в защищенном режиме при переходе в сегмент с теми же привилегиями, процессор помещает в стек значения регистров CS и EIP (IP при 16-битной адресации) и выполняет дальний переход аналогично команде JMP.

RETN считывает из стека слово (или двойное слово, в зависимости от режима адресации) и загружает его в IP (или EIP), выполняя тем самым действия, обратные ближнему вызову процедуры командой, CALL. RETF соответственно загружает из стека IP (EIP) и CS, возвращаясь из дальней процедуры. Если в ассемблерной программе указана команда RET, ассемблер заменит ее на RETN или RETF в зависимости от того, как была описана процедура, которую эта команда завершает. Операнд для RET необязателен, но, если он присутствует, после считывания адреса возврата из стека будет удалено указанное количество байт — это бывает нужно, если при вызове процедуры ей передавались параметры через стек.

INT помещает в стек содержимое регистров EFLAGS, CS и EIP, после чего передает управление программе, называемой «обработчик прерывания» с указанным в качестве операнда номером (число от 0 до 0FFh), аналогично команде CALL. В реальном режиме адреса обработчиков прерываний считываются из таблицы, начинающейся в памяти по адресу 0000h:0000h. Адрес каждого обработчика занимает 4 байта, так что, например, адрес обработчика прерывания 10h находится в памяти по адресу 0000h:0040h. В защищенном режиме адреса обработчиков прерываний находятся в таблице IDT и обычно недоступны для прямого чтения или записи, так что для установки собственного обработчика программа должна обращаться к операционной системе. В DOS вызовы прерываний используются для выполнения большинства системных функций — работы с файлами, вводом/выводом и т.д. Например, следующий фрагмент кода завершает выполнение программы и возвращает управление DOS:

mov ax,4C01h int 21h

Возврат управления из обработчика прерывания или исключения. IRЕТ загружает из стека значения IP, CS и FLAGS, a IRETD — EIP, CS и EFLAGS соответственно. Единственное отличие IRET от RETF состоит в том, что восстанавливается значение регистра флагов, из-за чего многим обработчикам прерываний приходится изменять значение EFLAGS, находящегося в стеке, чтобы, например, вернуть флаг CF установленным в случае ошибки.

Размер этой команды — один байт (код 0CCh), что делает ее удобной для отладки программ отладчиками, работающими в реальном режиме. Такие отладчики записывают этот байт вместо первого байта команды, перед которой требуется точка останова, и переопределяют адрес обработчика прерывания 3 на соответствующую процедуру внутри отладчика.

INTO — еще одна специальная форма команды INT. Эта команда вызывает обработчик прерывания 4, если флаг OF установлен в 1.

BOUND проверяет, не выходит ли значение первого операнда (регистр), взятое как число со знаком, за границы, указанные во втором операнде (переменная). Границы — два слова или двойных слова (в зависимости от разрядности операндов), рассматриваемые как целые со знаком, расположенные в памяти подряд. Первая граница считается нижней, вторая — верхней. Если индекс меньше нижней границы или больше верхней, вызывается прерывание 5 (или исключение #BR), причем адрес возврата указывает не на следующую команду, а на BOUND, так что обработчик должен исправить значение индекса или границ, прежде чем выполнять команду IRET.

Команда ENTER создает стековый кадр заданного размера и уровня вложенности (оба операнда — числа; уровень вложенности может принимать значения только от 0 до 31) для вызова процедуры, использующей динамическое распределение памяти в стеке для своих локальных переменных. Так, команда

enter 2048,3

помещает в стек указатели на стековый кадр текущей процедуры и той, из которой вызывалась текущая, создает стековый кадр размером 2 килобайта для вызываемой процедуры и помещает в ЕВР адрес начала кадра. Пусть процедура MAIN имеет уровень вложенности 0, процедура PROCA запускается из MAIN и имеет уровень вложенности 1, и PROCB запускается из PROCA с уровнем вложенности 2. Тогда стек при входе в процедуру MAIN имеет вид, показанный на рис. 10.

Рис. 10. Стековый кадр процедуры 0-го уровня (MAIN)

Теперь процедура MAIN может определять свои локальные переменные в памяти, используя текущее значение ЕВР.

На первом уровне вложенности процедура PROCA, как показано на рис. 11, может создавать свои локальные переменные, используя текущее значение EBP, и получает доступ к локальным переменным процедуры MAIN, используя значение ЕВР для MAIN, помещенное в стек командой ENTER.

Рис. 11. Стековый кадр процедуры 1-го уровня (PROCA)

Процедура PROCB на втором уровне вложенности (рис. 12) получает доступ как к локальным переменным процедуры PROCA, используя значение ЕВР для PROCA, так и к локальным переменным процедуры МАIN, используя значение ЕВР для MAIN.

Рис. 12. Стековый кадр процедуры 2-го уровня (PROCB)

Команда LEAVE выполняет действия, обратные команде ENTER. Фактически эта команда только копирует содержимое ЕВР в ESP, тем самым выбрасывая из стека весь кадр, созданный последней выполненной командой ENTER, и считывает из стека ЕВР для предыдущей процедуры, что одновременно восстанавливает и значение, которое имел ESP до вызова последней команды ENTER.

25. Команды передачи управления

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

  • безусловным — в данной точке необходимо передать управление не той команде, которая идет следующей, а другой, которая находится на некотором удалении от текущей команды;
  • условным — решение о том, какая команда будет выполняться следующей, принимается на основе анализа некоторых условий или данных.
  • cs — сегментный регистр кода, в котором находится физический (базовый) адрес текущего сегмента кода;
  • eip/ip — регистр указателя команды, в котором находится значение, представляющее собой смещение в памяти следующей команды, подлежащей выполнению, относительно начала текущего сегмента кода.
  1. Команды безусловной передачи управления:
  • команда безусловного перехода;
  • вызова процедуры и возврата из процедуры;
  • вызова программных прерываний и возврата из программных прерываний.
  • команды перехода по результату команды сравнения cmp;
  • команды перехода по состоянию определенного флага;
  • команды перехода по содержимому регистра ecx/cx.
  • команда организации цикла со счетчиком ecx/cx;
  • команда организации цикла со счетчиком ecx/cx с возможностью досрочного выхода из цикла по дополнительному условию.

Безусловные переходы

  • от типа операнда в команде безусловного перехода (ближний или дальний);
  • от указания перед адресом перехода (в команде перехода) модификатора; при этом сам адрес перехода может находиться либо непосредственно в команде (прямой переход), либо в регистре или ячейке памяти (косвенный переход).
  • near ptr — прямой переход на метку внутри текущего сегмента кода. Модифицируется только регистр eip/ip (в зависимости от заданного типа сегмента кода use16 или use32) на основе указанного в команде адреса (метки) или выражения, использующего символ извлечения значения СчА — $;
  • far ptr — прямой переход на метку в другом сегменте кода. Адрес перехода задается в виде непосредственного операнда или адреса (метки) и состоит из 16-битного селектора и 16/32-битного смещения, которые загружаются, соответственно, в регистры cs и ip/eip;
  • word ptr — косвенный переход на метку внутри текущего сегмента кода. Модифицируется (значением смещения из памяти по указанному в команде адресу, или из регистра) только eip/ip. Размер смещения 16 или 32 бит;
  • dword ptr — косвенный переход на метку в другом сегменте кода. Модифицируются (значением из памяти — и только из памяти, из регистра нельзя) оба регистра, cs и eip/ip. Первое слово/двойное слово этого адреса представляет смещение и загружается в ip/eip; второе/третье слово загружается в cs.

Команды передачи управления

6.1Классификация команд передачи управления По принципу действия команды микропроцессора можно разделить на три группы: 1. Команды безусловной передачи управления: команда безусловного перехода; вызов процедуры и возврата из процедуры; вызов программных прерываний и возврат из программных прерываний. 2. Команды условной передачи управления: команды перехода по результату команды сравнения; команды перехода по состоянию определенного флага; команды перехода по содержимому регистра есх/СХ. 3. Команды управления циклом: команда организации цикла со счетчиком есх/сх; команда организации цикла со счетчиком есх/сх с возможностью досрочного выхода из цикла; по дополнительному условию. Место, куда необходимо передать управление, делается с помощью меток. Метка символическое имя, обозначающее определенную ячейку памяти, предназначенное для использования в качестве операнда в командах передачи управления. Любой метке можно присвоить три атрибута:

  • имя сегмента кода, где метка описана;
  • смещение — расстояние в байтах от начала сегмента кода, в котором описана метка;
  • тип метки, или атрибут расстояния. Последний атрибут может принимать два значения:
    • Near — переход на метку возможен только в пределах сегмента кода, где эта метка описана;
    • Far — переход па эту метку возможен в результате межсегментной передачи управления.

Метку можно определить двумя способами: Символическое_имя : команда_ассемблера Символическое_имя label тип_метки Введем важное понятие ассемблера — счетчик адреса команд. Транслятор ассемблера обрабатывает исходную программу последовательно — команду за командой. При этом он ведет счетчик адреса команд, который для первой исполняемой команды равен 0, а далее, по ходу обработки очередной команды транслятором, он увеличивается па длину этой команды. Счетчик адреса команд — смещение конкретной команды относительно начала сегмента кода. Каждая команда имеет адрес, равный значению счетчика адреса команд. Транслятор ассемблера обеспечивает нам две возможности работы с этим счетчиком:

  • Использование меток, атрибуту смещения которых транслятор присваивает значение счетчика адреса той команды, перед которой они появились.
  • Применение специального символа $ для обозначения счетчика адреса команд. Этот символ позволяет в любом месте программы использовать численное значение счетчика адреса. Возможна установка счетчика адреса в нужное абсолютное значение:

Org выражение — задает значение счетчика адреса. Выражение должно быть таким, чтобы ассемблер мот преобразовать его к абсолютному числу.

    1. Команды безусловной передачи управления

Синтаксис команды: Jmp [модификатор] адрес_перехода — безусловный переход без сохранения информации о точке возврата. Адрес_перехода представляет собой адрес в виде метки либо адрес области памяти, в которой находится указатель перехода. Модификатор может принимать следующие значения: Near ptr — прямой переход на метку внутри текущего сегмента кода. Far ptr — прямой переход на метку в другом сегменте кода. Word ptr — косвенный переход на метку внутри текущего сегмента кода. Dword ptr — косвенный переход на метку в другом сегменте кода. Команда безусловного перехода может использоваться в 5 разновидностях. Переход может быть: прямым коротким (в пределах -128. +127 байтов); прямым ближним (в пределах текущего сегмента команд); прямым дальним (в другой сегмент команд); косвенным ближним (в пределах текущего сегмента команд через ячейку с адресом перехода); косвенным дальним (в другой сегмент команд через ячейку с адресом перехода). Рассмотрим структуры программы с переходами разного вида:

        1. Прямой короткий переход.

seg segment ‘code’ .. jmp short сont .. cont: .. seg ends

        1. Прямой ближний (внутрисегментный) переход.

seg segment ‘code’ .. jmp cont .. cont: .. seg ends

        1. Прямой дальний (межсегментный) переход.

segl segment ‘code’ .. jmp far ptr cont .. segl ends seg2 segment ‘code’ .. cont: .. seg2 ends

        1. Косвенный ближний (внутрисегментный) переход,

segl segment ‘code’ .. jmp DS: contadr .. cont: .. segl ends dat segment .. contadr dw conl .. dat ends 5. Косвенный дальний (межсегментный) переход. segl segment ‘code’ .. jmp DS: contadr .. segl ends seg2 segment ‘code’ .. cont: .. seg2 ends dat segment .. contadr dd cont .. dat ends 6.3 Понятие процедуры в языке ассемблера Процедура (подпрограмма) — это основная функциональная единица декомпозиции (разделения на несколько частей) некоторой задачи. Процедуру можно определить как правильным образом оформленную совокупность команд, которая при необходимости может быть вызвана в любом месте программы. Синтаксис: Имя_процедурыPROC [[модификатор языка] язык] [расстояние] заголо- [ARG список_аргументов] вок [RETURN список_элементов] проц. команды, тело директивы ассемблера проц. … конец [имя_процедуры] ENDP проц. Процедура может располагаться в программе: — в начале программы (до первой исполняемой команды);

  • в конце (после команды, возвращающей управление операционной системе);
  • промежуточный вариант — тело процедуры располагается внутри другой процедуры или основной программы;
  • в другом модуле.

Call [модификатор] имя_процедуры — вызов процедуры. Ret [число] — возвратить управление вызывающей программе. Команда считывает адрес возврата из стека и загружает его в регистры cs и ip/eip, тем самым возвращая управление на команду, следующую в программе за командой call, [число] — количество элементов, удаляемых из стека при возврате из процедуры. Команда вызова подпрограммы может использоваться в 4 разновидностях. Вызов может быть:

  • прямым ближним (в пределах текущего сегмента команд);
  • прямым дальним (в другой сегмент команд);
  • косвенным ближним (в пределах текущего сегмента команд через ячейку с адресом перехода);

— косвенным дальним (в другой сегмент команд через ячейку с адресом перехода). Рассмотрим структуру программных комплексов: 1. Прямой ближний вызов. seg segment ‘code’ mymain proc … call sub … mumain endp sub proc near … ret sub endp seg ends 2. Прямой дальний вызов. segl segment ‘code’ mymain proc … call far ptr sub … mumain endp segl ends seg2 segment ‘code’ sub proc far … ret sub endp seg2 ends

  1. Косвенный ближний вызов.

seg segment ‘code’ mymain proc … call word ptr subadr mumain endp sub proc near … ret sub endp dat segment … subadr dw sub … dat ends

  1. Косвенный дальний вызов.

Segl segment ‘code’ mymain proc call dword ptr subadr … mumain endp segl ends seg2 segment ‘code’ sub proc far … ret sub endp seg2 ends dat segment … subadr dd sub … dat ends

    1. Команды условной передачи управления

Эти команды позволяют проверить:

  • отношение между операндами со знаком (больше — меньше);
  • отношение между операндами без знака (выше — ниже);
  • состояниями арифметических флагов zf, sf, cf, of, pf.

Синтаксис: Jcc метка_перехода ее — определяет конкретное условие, анализируемое командой. Источниками условия могут быть:

  • любая команда, изменяющая состояние арифметических флагов;
  • команда сравнения cmp, сравнивающая значения двух операндов;
  • состояние регистра есх/сх.

Синтаксис команды cmp: cmp операнд1, операнд2 — сравнивает два операнда и по результатам сравнения устанавливает флаги. Таблица 9. Перечень команд условного перехода.

Типы операндов Мнемокод Критерий перехода
Любые Je Операнд 1 =операнд2
Любые Jne Операнд 1 <>операнд2
Со знаком Л / Jnge Операнд!
Со знаком Jle / Jng Операнд 1
Со знаком Jg / Me Операнд 1>операнд2
Со знаком Jge / Jnl Операнд 1 =>операнд2
Без знака Jb / Jnae Операнд 1
Без знака Jbe / Jna Операнд 1
Без знака Ja / Jnbe Операнд 1 >операнд2
Без знака Jae / Jnb Операнд 1 =>операнд2

Таблица 10. Команды условного перехода и флаги.

Название флага Номер бита Команда условного перехода Значение флага для осуществления перехода
Флаг перноса cf 1 Jc Cf=l
Флаг четности pf 2 Jp Pf=l
Флаг нуля zf 6 Jz Zf=l
Флаг знака sf 7 Js Sf=l
Флаг переполнения of 11 Jo Of=l
Флаг переноса cf 1 Jnc Cf=0
Флаг четности pf 2 Jnp Pf=0
Флаг нуля zf 6 Jnz Zf=0
Флаг знака sf 7 Jns Sf=0
Флаг переполнения of 11 Jno Of=0
  • декремента регистра есх/сх;
  • сравнения регистра есх/сх с нулем;
  • декремента регистра есх/сх;
  • сравнения регистра есх/сх с нулем;
  • анализа состояния флага нуля zf;
  • декремента регистра есх/сх;
  • сравнения регистра есх/сх с нулем;
  • анализа состояния флага нуля zf;
  1. Написать программу, которая задает вопрос: «Сейчас день? (Да/Нет)». По результату ответа на экран выводится одно из двух сообщений: «Добрый день» или «Добрый вечер».
  2. Написать программу преобразования строчных букв в прописные.
  3. Создать текстовый символьный массив (64 символа, от символа пробела до символа _). Вывести созданную строку на экран.
  4. Просмотреть изображения символов второй половины кодовой таблицы (коды 128 – 255, всего 128 символов). Для этого создать символьный массив, состоящий из кодов этих символов, и вывести их на экран.
  5. Создать программную задержку. Определить значение параметров программы, позволяющие получить задержки 10с.
  6. Во введенной с клавиатуры строки подсчитать количество слов.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *