Язык программирования ассемблер (черновик конспекта лекций) Состоит из нескольких уровней: 1. мнемоники описания команд 2. мнемоники описания данных 3. метки 4. организующие директивы 5. макропроцессор 6. комментарии Мнемоники команд соответствуют аппаратным возможностям архитектуры процессора. Их естественно группируют по назначению. Иногда часть аппаратных команд не документируется или не поддерживается всеми ассемблерами. Особая категория команд --- это вызовы системы, позволяющие взаимодействовать с другой аппаратурой компьютера через интерфейс ОС. Мнемоники описания данных задают размеры данных, а также при необходимости определяют начальные значения для них --- осуществляют инициализацию. Метки позволяют избежать использования числовых адресов для ссылки на команды и данные. Это обычно идентификаторы в смысле си. Организующие директивы позволяют управлять размещением генерируемого машинного кода. Могут рассматриваться как часть макропроцессора. Макропроцессор имеет функции, подобные препроцессору языка си, но, как правило, более развитые. Поддерживаются обычно константы, макросы с аргументами, условная компиляция, включение текстовых и бинарных файлов, управление транслятором. Комментарии обычно от знака точка с запятой до конца строки. В простейших ассемблерах часть уровней может быть не реализована. Для ассемблеров характерна многопроходная трансляция, что делает ненужным предварительные описания меток. Скомпилированные программы можно отлаживать как непосредственно, так и используя символьную информацию подобно программам на языках высокого уровня. Но не все ассемблеры могут генерировать символьную информацию, требуемую отладчиком в последнем случае. Исторически большое значение имели ассемблеры IBM System/360, DEC PDP-11, Intel 8080, Mostec 6502, Zilog z80, Intel 80x86, Motorola 680x0, Apple/IBM/Motorola (AIM) PowerPC, Acorn ARM. Первоначально системы команд процессора старались делать максимально удобными для использования непосредственно программистом. Команды были, как правило, вызовами подпрограмм микрокода и исполнялись довольно долго, например, несколько сотен тактов работы процессора. Такая архитектура получила название CISC --- Complex instruction set computing -- вычисление со сложными инструкциями. С появлением микропроцессоров системы команд стали сначала из-за их дешевизны упрощаться, но затем стали использовать всё более сложные и медленные команды. Ситуацию изменило появление микропроцессоров ARM с архитектурой RISC --- Reduced instruction set computing -- вычисление с упрощёнными инструкциями, которые оказались на порядок дешевле, на порядок быстрее и с меньшим энергопотреблением. Для такой архитектуры характерно исполнять любую команду за один такт процессора. Крупные производители процессоров смогли перенять технологию RISC на микроуровне, не отказываясь от сложившийся системы команд. Используя распараллеливание вычислений и другие технологические приёмы, удалось добиться выполнения ряда команд за доли такта процессора. Архитектура VLIW (Very long instruction word) позволяет соединять несколько инструкций, исполняемых параллельно, в одной команде. В отечественных процессорах Эльбрус может исполняться на одном ядре более двух десятков команд за один такт. Такая архитектура требует использования специальных компиляторов. Ее вариант --- это Intel Itanium. Процессоры семейства Intel 80x86 - это 8086, 80186, 80286, 80386, 80486, Pentium (i586), P6 (i686), Core, ... Совместимые с Intel 80x86 процессоры выпускались и другими фирмами: NEC, IBM, ... Но только фирмы AMD и VIA сохранили свои позиции как производители таких процессоров. Как правило, все процессоры используют общую терминологию при описании своей архитектуры. Типичные термины: порядок байт, режимы работы, регистры, слово состояния, флаги, стек, способы адресации. Используется порядок байт от младшего к старшему (Little Endian - LE), т.е. 16-е число 10f записывается сначала байтом f, а затем 1. Режимы работы: реальный, нереальный (unreal), защищённый, длинный, виртуальный 8086, ... Кроме того, режимом определяется разрядность адреса по умолчанию для команд и, отдельно, для данных. В реальном режиме для адресации доступен только 1-й мегабайт памяти. Он соответствует возможностям первых процессоров рассматриваемого семейства 8086, 8088. Используются физические адреса. В режиме виртуального 8086 также доступен только первый мегабайт, но используются виртуальные адреса, что позволяет запускать несколько приложений в этом режиме на одном компьютере. В нереальном, официально недокументированном режиме можно адресовать до 4 гигабайт памяти, используя простые схемы адресации, не требующие поддержки со стороны ОС. Используется BIOS, но в развитых ОС типа Linux или современных Microsoft Windows его использовать нельзя. Защищенный режим требует значительной поддержки со стороны ПО и имеет несколько вариантов настройки. Длинный режим (long mode) для 64-разрядных систем также требует значительной поддержки со стороны ПО и существует в нескольких вариантах. Регистры --- это самая быстрая память процессора. К регистрам обращаются по их именам. Часть регистров свободны для любого использования, часть используется процессором и их использование ограниченно. Количество и размер регистров зависит от разрядности режима работы. Регистры общего назначения 8 битные: AL, AH, BL, BH, CL, CH, DL, DH, SIL, DIL, BPL, SPL, R8B, ..., R15B 16 битные: AX, BX, CX, DX, SI, DI, BP, SP, R8W, ..., R15W 32 битные: EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, R8D, ..., R16D 64 битные: RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8, R9, ..., R15 AL --- это меньший (low) байт AX, а AH больший (high). SIL --- SI low, ... R8B --- R8 low byte, ... AX --- это младшие 16 разрядов EAX, а EAX --- это младшие 32 разряда RAX. Это верно для всех подобных случаев (BX, CX, ...). R8W --- R8 low word, ... R8D --- R8 low double word, ... Регистр SP (Stack Pointer) используется для организации вызовов подпрограмм и прерываний, как правило, его напрямую не меняют. Регистр AL, AX, EAX, RAX называют аккумулятором. Регистр CL, CX, ECX, RCX --- счётчиком (counter). Регистр BX, EBX, RBX --- базовым (base). Регистр BP (Base Pointer) используется для вызовов подпрограмм с параметрами, поэтому в теле таких подпрограмм его не следует менять. Регистры SI и DI называют индексными (source/destination index). Только они могут использоваться в специальных операциях со строками. Регистры BX, BP и SP также называют индексными. Регистр/слово состояния процессора (SR --- Status Register) Состоит из набора битовых флагов, определяющих текущее состояние процессора. Флаги делятся на арифметические, установки режима и системные. Арифметические флаги: перенос (carry, CY) --- устанавливается, если в результате операции произошёл перенос за старший разряд операнда, например, mov al,1 add al,0ffh установит перенос в 1. Это флаг для беззнаковой арифметики. полуперенос (auxilary carry, AC) --- устанавливается, если в результате операции произошёл перенос в 4-й разряд (нумерация с 0). Используется для организации двоично-десятичной арифметики (BCD --- Binary-Coded Decimal), в которой, например, 16-е число 98 означает это же 10-е. переполнение (overflow, V, OF) --- устанавливается при выполнении операций со знаком, например, если сложить байты, числа 15h (21) и 71h (113), то получится байт 86h или число -122 в дополнительном коде, что и означает переполнение. отрицательного знака (sign, SF) --- равен старшему разряду операнда. чётности (parity, PF) --- устанавливается, если в результате чётное число бит. Учитывается только младший байт результата. Флаги установки режима: направления (direction, DF) --- выбирает способ работы со строками, увеличивать (=0) или уменьшать индексные регистры после обработки очередного знака строки прерываний (interrupt, IF) --- запрещает (=0) или разрешает прерывания. Системные флаги: пошаговый режим (trap, TF) --- при включении такого режима (=1) после исполнения каждой команды происходит отладочное прерывание 1, используется для организации трассировки. режим виртуального 8086 (virtual mode, VM). флаг идентификации (ID) --- только для чтения, если установлен, то процессор поддерживает команду CPUID. флаг уровня привелегий ввода-вывода (I/O privilege level, IOPL) --- это два бита, число от 0 до 3. Число 0 означает максимум привелегий, а 3 --- минимум. Большинство ОС используют только два уровня: один для ядра ОС и другой для прочего ПО. Есть и другие системные флаги. Большая часть флагов регистра состояний зарезервирована для будущего использования. Счётчик команд (PC --- Program Counter, IP/EIP/RIP --- Instruction Pointer) Содержит адрес инструкции для выполнения. Команда перехода --- это присваивание этому регистру. Пример Hello World. FASM, YASM и GAS. Сегментные регистры/селекторы Их всего 6: CS (Code) --- кодовый сегмент, SS (Stack) -- стековый сегмент, DS (Data) --- сегмент данных, ES (extra) - сегмент дополнительных данных, FS - сегмент данных F, GS - сегмент данных G. В реальном и виртуальном режимах задают базовый 20-разрядный адрес, к которому добавляется 16-разрядное смещение операнда, что формирует полный адрес. В защищенном режиме эти регистры являются селекторами к дескрипторами (описателями) сегментов памяти. Они выбирают 8-байтные дескрипторы из таблиц, адреса которых определяются регистрами TR, IDTR, GDTR, LDTR. В 32-разрядном режиме принято, как правило, использовать "линейную" (flat) память, когда сегментные регистры после начальной установки ОС в дальнейшем явно не используются. В длинном режиме сегментные регистры не используются. Управляющие регистры Это CR0, CR1, CR2, CR3, CR4, CR8 (Control Register). Младшее слово CR0 называют также MSW (Machine Status Word). CR0 как и SR состоит из битовых флагов разрешения/запрещения: страничной виртуальной памяти, защищенного режима, кэша, арифметического сопроцессора и др. Регистр CR1 зарезервирован. Регистры CR2 и CR3 используются для организации страничной виртуальной памяти. CR4 используется только в защищенном режиме и, как и CR0, состоит из флагов. CR8 используется только в 64-разрядном режиме для лучшего контроля над прерываниями. Отладочные и тестовые регистры Регистры DR0-DR3 устанавливают адреса 4-х точек останова, DR6 сообщает информацию в виде битовых флагов о текущем статусе отладки, DR7 устанавливает детали работы точек останова. Останов может производиться не только по IP, но и при любом обращении к заданному адресу, что позволяет организовывать простые выражения останова. Регистры TR3-TR7 можно использовать для тестирования кэш-памяти. Аппаратные расширения процессора Процессоры 80x86 включают в себя сопроцессор для обработки вещественных чисел. Такой сопроцессор - это независимый процессор со своими регистрами и системой команд. Кроме того, существуют десятки расширений к стандартной системе команд, в частности, для обработки мультимедия информации, поддержки работы систем виртуализации, векторные инструкции, работа с 128-, 256- и 512-битными данными, вычисления CRC и по AES, ... Стек Со стеком работают через регистр (E/R)SP, задающий адрес вершины стека. Используются данные сегмента SS. Регистр BP также по умолчанию работает с SS. Вместо SS можно использовать любой другой сегмент через команды-префиксы сегментов. Основные команды при работе со стеком --- это PUSH и POP. Стек используется также командами CALL/INT и RET/IRET для вызова подпрограмм/прерываний и возврата из них. Есть и другие команды, использующие стек. При занесении данных в стек SP уменьшается, а при извлечении --- увеличивается. Системные вызовы В Linux в 32-разрядном режиме такие вызовы производятся через прерывание 128, т.е. команду INT 80h (0x80). В 64-разрядном режиме --- через команду syscall. Параметры во-втором случае передаются через регистры в порядке RAX (номер вызова), RDI, RSI, RDX, RCX, R8, R9. Если нужно больше параметров, то используется стек. Результаты возвращаются через RAX и, если необходимо, RDX. Регистр RCX меняется. Описания вызовов можно найти в файлах каталога include: asm/unistd_32.h или asm/unistd_64.h Пример расчёта факториала рекурсией и итерацией. Способы адресации 1. Непосредственный --- операнд-константа задаётся в инструкции процессору, например, MOV AX,2 ;присвоить AX значение 2, 1-й операнд использует регистровую адресацию MOV byte [1],0 ;присвоить 0 байту памяти с адресом 1, 1-й операнд использует прямую адресацию 2. Регистровый, например, MOV EAX,EBX ;присвоить EAX значение EBX 3. Косвенная: 1. Через регистр --- адрес в памяти задается значением регистра, например, MOV EAX,[RBX] ;присвоить EAX двойное слово с адреса в регистре RBX MOV CX,[FS:BX] ;присвоить CX 16-битное слово с адреса в регистре BX в сегменте FS 2. Прямая --- прямым указанием адреса-константы в памяти, может потребовать уточнения размера данных, например, MOV AX,[VAR] ;присвоить AX значение слова по адресу VAR VAR DW 77 ;DW (Data Word) директива ассемблеру выделить слово и инициализировать заданным значением 3. Базовая или индексная --- адрес формируется сложением значения заданного регистра и числового смещения-константы, например, MOV RBX,TAB MOV AH,[RBX+2] ;присвоить AH значение байта по адресу TAB[2], т.е. 5 - можно было написать и MOV AH,[TAB+2] TAB DB 2,3,5,7,11 ;DB (Data Byte) - выделить байты и инициализировать заданными значениеми 4. Базово-индексная --- адрес формируется сложением значения двух регистров, например, MOV RBX,TAB MOV ESI,1 MOV AL,[RBX+RSI] ;AL <- TAB[1] = 3 5. Базово-индексная со смещением --- адрес формируется сложением значения заданных регистров и числового смещения, например, MOV RBX,1 MOV EDI,3 MOV AL,[TAB+RBX+RDI] ;AL <- TAB[4] = 11 6. Базовоиндексная с масштабированием и опциональным смещением --- самый общий случай --- адрес формируется сложением значения двух регистров, умноженных на коэфициент 2, 4 или 8, и необязательного числового смещения. В сумме можно использовать и один регистр. Например, MOV RBX,TAB MOV EDI,1 MOV AL,[RBX+RDI*2] ;AL <- TAB[2] = 5 MOV EBX,EDI ;1 MOV AL,[TAB+RBX*2+EBX] ;AL <- TAB[3] = 7 4. Битовая --- для обращение к отдельным битам, числа от 0 до 63. 5. Относительно IP, перемещаемая --- адрес формируется сложением IP и заданного операнда, рассматриваемого как знаковое число, например, XOR AX,AX L: OR AL,AL JZ .L1 ;переход если результат нулевой JMP L ;безусловный переход .L1: Другой пример, MOV AL,[RIP+12]. Используется в командах близких переходов и в длинном режиме для адресации данных. VAR, TAB, L, .L1 --- это метки. Их синтаксис меняется в зависимости от ассемблера. В FASM метки для переходов должны заканчиваться двоеточием, а локальные метки должны начинаться с точки. Локальная метка рассматривается как дополнение к ближайшей глобальной, поэтому их можно использовать после каждой глобальной метки заново. В примере метка .L1 --- это фактически L.L1. Обычно инструкции используют два операнда, но есть инструкции без операндов, с одним или тремя операндами. Как правило, один из операндов является регистром. Основная память прямого доступа в десятки раз медленнее процессора, поэтому нужно как можно больше операции производить без её использования. Все инструкции процессора естественно разделить на группы по назначению. 1. Команды присваивания и пересылки Двухоперандная команда MOV реализует присваивания следующих типов R <- R/M R/M <- R R/M <- I SR <- R/M R/M <- SR, где R - регистр, R/M - регистр или память, SR - сегментный регистр, I - константа. Команды MOVSX и MOVZX --- особые варианты MOV, расширяющие старшие биты операнда-приемника знаковым разрядом операнда-источника или нулем соответственно. Например, MOVSX EAX,AL заполнит старшие три байта EAX 8-м битом AL. Мнемоники условных присваиваний получаются добавлением суффикса условия к слову CMOV --- Conditional Move, например, MOV EAX,10 CMP AL,10 CMOVZ BL,20 ;BL<-20, так как ZF установлен Однооперандная команда PUSH помещает в стек элемент данных типа R/M, SR, I, а команда POP извлекает элемент данные типов R/M или SR. Есть безоперандный вариант этих команд для загрузки в стек и выгрузки из него всех регистров общего назначения --- PUSHA/POPA. Команда PUSH R равносильна последовательности команд SUB SP,sizeof R MOV [SP],R В стек нельзя помещать байты. Двухоперандная команда XCHG обеспечивает обмен данными типа R <-> R/M. Безоперандная команда XLAT равносильна невозможной MOV AL,[(E/R)BX+AL]. BSWAP переставляет байты в регистре-операнде. LEA загружает адрес в операнд-приемник, например, LEA RAX,[RSI] эквивалентно MOV RAX,RSI. LDS, LSS, LES, LFS, LGS загрузка в регистр и сегментные регистр из памяти, например, LDS SI,[DI]. IN/OUT --- это команды ввода-вывода из портов, особого адресного пространства. Адрес может быть байтом-константой или указываться в регистре DX. Данные передаются через аккумулятор. Команды этой группы не меняют флагов. 2. Арифметика Сложение реализуется командой ADD, работающей по схемам R <- R + R/M R/M <- R/M + I, например, ADD word [RSI],7 или ADD RAX,RBX. Команда ADC отличается от предыдущей только тем, что к сумме добавляется перенос. XADD меняет содержимое R и R/M, складывает их и помещает результат в R/M. Вычитание SUB и вычитание с заемом SBB (borrow) аналогичны по формату использования сложению. Команда CMP используется для сравнения операндов, она аналогична команде SUB, но не меняет аргумента, только устанавливает флаги. CMPXCHG сравнивает аккумулятор и R/M и если они равны, то производится пересылка R/M <- A, а если неравны, то A <- R/M. Команды INC и DEC реализуют инкремент или декремент операнда типа R/M. Не устанавливают перенос. Беззнаковое умножение MUL имеет следующие форматы: AX <- AL*R/M[8] DX:AX <- AX*R/M[16] EDX:EAX <- EAX*R/M[32] RDX:RAX <- RAX*R/M[64] Знаковое умножение IMUL имеет следующие форматы R[n] <- R[n]*R/M[n] R[n] <- R/M[n]*I[n], n = 8, 16, 32 В последнем случае IMUL имеет три операнда. Результат IMUL имеет ту же разрядность, что и аргументы, поэтому старшие разряды результата могут теряться. Беззнаковое деление DIV имеет форматы, обратные MUL, например, AL,AH <- AX/ R/M[8] ;AL - частное, AH - остаток AX,DX <- DX:AX/ R/M[16] ;AX - частное, DX - остаток Знаковое деление IDIV имеет такие же форматы как и DIV. Команды деления при делении на ноль и даже при переполнении генерируют исключения. Команда NEG меняет знак, преобразует в дополнительные код, операнд типа R/M. CBW, CWDE, CWD, CDQ --- преобразование знаковым расширением соответственно AL -> AX AX -> EAX AX -> DX:AX EAX -> EDX:EAX Получается, что, например, CWDE эквивалентно MOVSX EAX,AX AAA, AAS, AAM, AAD --- это команды коррекции результатов операций (+, -, *, /) в AL для аргументов десятичных чисел. DAA, DAS --- корректируют после + или - аккумулятор для аргументов в формате BCD. Команды 10-й коррекции отсутствуют в длинном режиме. 3. Поразрядные операции Инструкции AND, OR, XOR имею формат такой же формат как и ADD, например, AND AX,7 OR AX,15 ;установят AX в 15 Команда TEST эквивалентна AND, но не меняет операнда, только устанавливает флаги, используется для проверки значений отдельных бит. Например, проверим чётность значения аккумулятора TEST EAX,1 JNZ ODD ;jump not equal ;EVEN ODD: Однооперандная команда NOT поразрядно инвертирует аргумент. Инструкция SHL/SAL (shift left) --- это сдвиг влево заданное число раз, например, MOV AL,3 SHL AL,2 ;AL <- 12, CY <-0 Тип первого операнда R/M. Тип второго --- числовая константа или регистр CL. Сдвиг проходит через перенос. Инструкция SHR (shift right) --- сдвиг вправо, например, MOV AX,15 SHR AX,1 ;AX <- 7, CY <- 1 Инструкция SAR (shift arithmetical right) --- сдвиг вправо арифметический, т.е. с сохранением знака, например, MOV AL,0xF6 SAR AL,1 ;AL <- 0xFB, CY <- 0 Команда SHLD (shift left double) позволяет сдвигать влево сразу два однотипных значения. Первое задается типом R/M, второе регистром. Число сдвигов определяется третьим аргументом-числом или CL, например, MOV AX,5 MOV BX,0x8000 SHLD AX,BX,2 ;AX <- 22, CY <- 0 Последняя инструкция эквивалентна PUSH BX SHL BX RCL AX SHL BX RCL AX POP BX Второй операнд не меняется. Сдвиг проходит через перенос. Этой командой нельзя сдвигать байты. Команда SHRD (shift right double) аналогична предыдущей, но сдвигает вправо. Команды ROL и ROR (rotate left/right) вращают операнд влево/вправо. Выдвигающийся бит попадает в перенос [схема]. Аргументы задаются как и в SHL/SHR. Например, MOV AL,0x77 ROL AL,2 ;AL <- 0xdd, CY <- 1 ROR AL,1 ;AL <- 0xee, CY <- 0 Команды RCL и RCR --- это вращения через перенос [схема], например, MOV AL,0x77 RСL AL,2 ;AL <- 0xdс, CY <- 1 RСR AL,1 ;AL <- 0xee, CY <- 0 4. Битовые операции Инструкции BT, BTC, BTR, BTS работают только с одним битом первого операнда, задаваемого типом R/M. Номер бита определяется вторым операндом-константой или регистром. Устанавливают перенос равным значению выбранного бита. BT - bit test - только проверяет бит. BTC - bit complement - инвертирует бит. BTR - bit reset - сбрасывает бит в 0. BTS - bit set - установка бита в 1. Пример. MOV AX,0x55 BT AX,0 ;CY <-1 BTC AX,1 ;AX <- 0x57, CY <- 0 BTR AX,2 ;AX <- 0x53, CY <- 1 BTS AX,3 ;AX <- 0x5b, CY <- 0 Инструкции BSF (bit scan forward) и BSR (bit scan reversed) ищут во-втором операнде типа R/M первый ненулевой бит соответственно справа и слева. Если единица найдена, то устанавливается флаг нуля в ноль и номер найденной единицы заносится в первый операнд-регистр, если не найдена, то ZF устанавливается в 1, а первый операнд получает неопределённое значение. Операнды должны быть одного размера, но не байта. Например, MOV RAX,0x100E BSF RDX,RAX ;DX <- 1, ZF <- 0 BSR RDX,RAX ;DX <- 12, ZF <- 0 5. Строковые команды У этих команд нет операндов. Данные строки-приёмника всегда берутся по адресу в регистре SI/ESI/RSI в сегменте DS или в любом указанном. Строка-источник задаётся адресом в DI/EDI/RDI сегмента ES. В зависимости от значения DF индексные регистры при выполнении операции уменьшаются или увеличиваются (при DF=0, типично). Команда LODS --- load string --- загружает в зависимости от варианта AL/AX/EAX/RAX, например, STRING DB 1,2,3,4,5,6,7,8,0,0,0,0,0,0,0 MOV RSI,STRING LODSB ;AL <- 1, RSI++ LODSW ;AX <- 0x302, RSI += 2 LODSD ;EAX <- 0x7060504, RSI += 4 LODSQ ;RSI += 8, RAX <- 0x...08 Команда STOS ---- store string --- выгружает аккумулятор в память, например, MOV AL,0x25 MOV RDI,STRING STOSB ;[STRING] <- 0x25 Команда MOVS --- move string --- это пересылка типа память-память, например, MOV RSI,STRING1 MOV RDI,STRING2 MOVSB ;копирует первый байт STRING1 в STRING2, RSI++, RDI++ MOVSQ ;копирует 8 байт с позиции STRING1+1 в STRING2+1, RSI+=8, RDI+=8 С MOVS, STOS, LODS, INS и OUTS можно использовать префикс REP (repeat), задающим через регистр RCX число повторений операции, например, MOV RDI,STRING XOR RAX,RAX MOV RCX,10 REP STOSQ ;зануление первых 80 байт строки STRING, RCX <- 0 MOV RSI,STRING1 MOV RCX,11 REP MOVSB ;копирует 11 байт из строки STRING1 в STRING Команды INS и OUTS --- in/out string ---- это соответственно ввод и вывод строки через порт, задаваемый регистром DX, например, INSB ;ввод в [RDI], RDI++ OUTSW ;запись слова c [RSI], RSI+=2 MOV RCX,7 REP OUTSD ;запись 7 раз по 4 байта с [RSI], RSI+=28, CX <- 0 Команду SCAS --- scan string --- можно использовать для поиска в строке, устанавливает ZF в 1, если текущий символ строки совпал с искомым, задаваемым аккумулятором. С ней и с CMPS можно использовать префиксы REPZ/REPE (zero/equal) или REPNZ/REPNE (non-zero/not equal), которые отличаются от REP только тем, что продолжают повторы только при выполнении указанного условия, например, STRING DB 2,3,5,7,11,13,17 STRING2 DB 8,8,8,8,8,7 MOV RDI,STRING MOV AL,7 MOV RCX,20 REPNZ SCASB ;RDI <- STRING+3, ZF <- 1, RCX <- 16 MOV RDI,STRING2 INC AL REPZ SCASB ;RDI <- STRING+5, ZF <- 0, RCX <- 10 Команда CMPS --- compare string --- для сравнения строк, например, STR1 DB "hood" STR2 DB "hook" MOV RSI,STR1 MOV RDI,STR2 MOV RCX,4 REPE CMPSB ;RSI <- STR1+3, RDI <- STR2+3, ZF <- 0, RCX <- 0 6. Переходы Команда JMP --- jmp --- это безусловный переход к заданному адресу, аналог GOTO языков высокого уровня, например, label1: JMP label1 ;бесконечный цикл MOV RSI,label1 JMP RSI Эту команду и CALL можно использовать для организации специальных межзадачных переходов. Есть более десятка команд переходов, выполняемых только при некоторых условиях, определяемых флагами: CF, SF, OF, ZF, PF. Рассмотрим переходы при работе с беззнаковыми числами: JA/JNBE --- above --- not below or equal --- при больше JAE/JNB/JNC --- above or equal --- not below --- not carry --- при больше или равно JB/JNAE/JC --- при меньше JBE/JNA --- при меньше или равно Пример, MOV AX,55 CMP AX,66 JB label1 ;будет переход, 55 < 66 При учёте знака возможны следующие переходы: JG/JNLE --- greater --- not less or equal --- при больше JGE/JNL --- при больше или равно JL/JNGE --- при меньше JLE/JNG --- при меньше или равно Пример, MOV AX,55 CMP AX,66 JL label1 ;будет переход, 55 < 66 Другие условные переходы: JE/JZ --- equal --- zero --- при равенстве, при ZF=1 JNE/JNZ --- при неравенстве, ZF=0 JP/JPE --- parity even --- при четности, PF=1 JNP/JPO --- no parity --- parity odd --- при нечетности JS --- sign --- при отрицательности, старший бит результата равен 1, SF=1 JNS --- no sign --- при неотрицательности, SF=0 JO --- overflow --- при переполнении, OF=1 JNO --- при отсутствии переполнения JCXZ/JECXZ/JRCXZ --- при CX/ECX/RCX = 0. Пример, MOV AX,55 CMP AX,66 JS label2 ;будет переход, 55 - 66 < 0 lavel2: JNO label3 ;будет переход, переполнения не было label3: JC label4 ;будет переход, был перенос label4: JPE label5 ;будет переход, 55 - 66 = -11 = 0xfff5 и в 0xf5 четное число 1-ц Есть специальные переходы для организации циклов, которые сначала уменьшают CX/ECX/RCX на 1, а затем при выполнении условия производят переход: LOOP --- при CX/ECX/RCX!=0 LOOPE/LOOPZ --- при CX/ECX/RCX!=0 и ZF=1 LOOPNE/LOOONZ --- при CX/ECX/RCX!=0 и ZF=0 Пример, XOR AX,AX MOV CX,10 l1: ADD AX,3 LOOP l1 ;AX=30, CX=0 7. Подпрограммы Для работы с ними служат инструкции: CALL --- вызов подпрограммы: адрес следующей инструкции запоминается в стеке и производится переход, т.~е. CALL addr next: эквивалентно PUSH next JMP addr RET --- return --- возврат из подпрограммы, адрес извлекается из стека и по нему производится переход. ENTER --- создание кадра подпрограммы. LEAVE --- уничтожение кадра подпрограммы. 8. Прерывания При вызове прерывания в стеке сохраняются адрес возврата и слово состояния (флажки) процессора. Прерывания могут быть аппаратными (клавиатура, мышь, ...) и генерирумыми программно. Команда INT --- interrupt --- общий случай генерации программного прерывания. Ее аргумент --- это число от 0 до 255. INT n эквивалентно в реальном режиме PUSHF; CALL [4*n] Команды INTO и INT3 частные однобайтные версии предыдущей двубайтной команды. INTO производит вызов прерывания 4 только, если OF=1. INT3 используется для организации отладки. Инструкция IRET --- interrupt return --- возврат из прерывания. Из стека извлекается SR, CS и IP. При возврате из аппаратного прерывания нужно, как правило, еще производить дополнительные действия. CLI --- clear interrupt flag --- устанавливает IF=0, запрещает маскируемые прерывания. Бывают немаскируемые --- NMI --- non-maskable interrupts. STI --- set IF. 9. Работа с флажками Команда LAHF --- load AH by flags --- грузит AH младшим байтом SR, а команда SAHF --- save ... --- наоборот. POPF/PUSHF --- стековая загрузка/выгрузка SR. CLC --- clear carry --- CY <- 0 STC --- set carry --- CY <- 1 CMC --- complement carry --- CY <- not CY CLD/STD --- D <- 0/1 10. Установка байт Используя те же названия условий, что и в условных переходах, можно формировать команды установки байта, например, SETZ AH --- установит все биты AH в 0 при ZF=0 и в 1 при ZF=1. Можно в качестве аргумента использовать любой однобайтный регистр или байт в памяти. 11. Управление процессором, префиксы и некоторые другие команды BOUND --- проверка значения на принадлежность заданному диапазону --- поддержка языков программирования NOP --- no operation --- ничего не делает HLT --- halt --- останов процессора, процессор может продолжить работу только по внешнему сигналу MOV --- пересылка из системных регистров или в них, например, MOV EAX,CR0 WAIT --- ожидание сопроцессора, средство синхронизации CPUID --- позволяет получить информацию о процессоре, её аргументы - это регистры EAX и редко ECX, результат возвращается через регистры. Команды HLT и MOV являются привилегированными и могут исполняться только на уровне ядра ОС. Далее основные префиксы: LOCK --- блокировать шину для следующей команды, процессор генерирует специальный сигнал SEG --- segment --- это либо CS, DS, ES, SS, GS, FS --- заменяет сегментный регистр по умолчанию для следующей команды AS --- address size --- установить тип адреса для следующей команды: 16/32 OS --- operand size --- установить тип данных для следующей команды: 16/32 O64 --- для инструкций 64-разрядного режима REP/REPE/REPZ/REPNE/REPNZ --- префиксы строковых операций подсказки для условных переходов --- будет переход или нет --- оптимизация. Префиксы AS, OS, O64 обычно устанавливаются автоматически. 12. Защита памяти Используются команды: SGDT/LGDT, SLDT/LLDT, SIDT/LIDT --- save/load global/local/interrup descriptor table --- запись/чтение регистра таблицы дескрипторов глобальных/локальных/прерываний STR/LTR --- save/load task register --- запись/чтение регистра задачи SMSW/LMSW --- save/load machine status word --- запись/чтение слова состояния машины, младших 16-и разрядов CR0 VERR/VERW --- verify read/write --- проверка доступности для чтения/записи заданного сегмента ARPL --- adjust requested priviledge level --- корректировка запрошенного уровня привилегий LAR --- load access right --- загрузка байта разрешения доступа LSL --- load segment limit --- загрузка границы сегмента Организующие директивы и макропроцессор Зависят от ассемблера. Размещение и инициализация данных производится директивами название тип бит db байт byte 8 dw слово word 16 dd двойное слово dword 32 double word dq счетверённое слово qword 64 quadruple word Например, data db "Hello World",0 big dq 77,0x88,data,big,data+1,'z' len dw big-data-1 MOV AL,[data] ;AL <- 'H' MOV AH,byte [big] ;AH <- 77, нужно указать, что нужен именно байт Размещение данных производится директивами соответственно rb, rw, rd, rq. Например, bytearray: rb 1000 ;массив из 1000 байт qwordarray rq 200 ;массив из 200 64-битовых целых Метки без двоеточия нельзя использовать перед инструкциями машинного языка. Наиболее гибкий способ для введения меток --- это директива label, например, alpha db 8,10 label beta word at alpha MOV AX,[beta] ;AX <- 2568 MOV AX,word [alpha] ;эквиваленто предыдущему Таким образом можно вводить структуры данных, соответствующие вариантной части записи паскаля или объединению си, например, number dq 0x102030405060708 label number_low dword at number label number_high dword at number+4 label number_word0 word at number label number_word1 word at number+2 label number_word2 word at number_high label number_word3 word at number+6 MOV AX,[number_word1] ;AX <- 0x506 Знак равенства можно использовать для введения числовых констант, например, count = 25*34 MOV CX,count CMP EAX,count Средства ассемблера позволяют использовать арифметические действия, сдвиги и т.п., т.е. + - * / mod and or xor shr shl not. Счётчик размещений обозначается $, он соответствует текущей позиции размещаемого кода. message dw "Сообщение в ассемблере" message_len = $ - message Условная компиляция организуется похожими на препроцессор си++ средствами, например, рассмотрим конструкцию для быстрого присваивания (копирования) строки с длиной в переменной count. if count & ~ count mod 8 mov cx,count/8 rep movsq else if count>8 mov cx,count/8 rep movsq mov cx,count mod 8 rep movsb else mov cx,count rep movsb end if Для сравнения используются операции =, <>, >=, <=, <, >. Логические операции И, ИЛИ, НЕ обозначаются как &, | и ~. Можно также как и в си++ использовать оператор defined. Кроме того, можно применять операцию in, например, a in <3,7,25,ax> будет истинной при значении a равном одному из элементов заданного множества. Операция eq позволяет сравнивать строковые выраждения, в частности, пустые, например, var eq истинно, если значение var пусто. Операция eqtype сравнивает типы выражений, например, var eqtype "" истинно, если var --- это строковая константа. Директива repeat позволяет компактно описывать идущие подряд повторения, например, repeat 8 mov byte [bx],% inc bx end repeat знак % заменяется на числа от 1 до 8, т.е. будут сгенерированны коды mov byte [bx],1 inc bx mov byte [bx],2 inc bx ... Если бы нужна была последовательность от нуля до 7, то вместо % надо было бы писать %-1. Внутри блока repeat можно использовать break для досрочного прекращения генерации кода Директива align устанавливает выравнивание для счётчика размещений, например, после align 4 гарантируется, что следующий адрес будет кратен 4. Если, например, данные типа word размещены по нечётному адресу, то это замедляет работу с ними. Директива display печатает сообщение во время ассемблирования, например, display 10,'пока ошибок нет',10,10 Включать тексты можно директивой include, например, include 'macros.inc' Директива macro позволяет делать разнообразные текстовые подстановки, макросы, например, macro clr arg {xor arg,arg} будет заменять clr AX на XOR AX,AX. При вызове макроса можно указывать меньшее, чем в его определении число аргументов, но если после имени формального аргумента ставить *, то такой фактический аргумент указывать необходимо. Можно давать аргументам предопределённые значения, например, macro clr arg=ax {xor arg,arg} будет заменять clr на XOR AX,AX. С макросами можно использовать неограниченное число аргументов, используя [], например, macro stoschar [char] { mov al,char stosb } заменит stoschar 2,"w",5 на mov al,2 stosb mov al,"w" stosb mov al,5 stosb Внутри макроопределений можно использовать локальные переменные, определенные после директивы local. Такие переменные необходимы при использовании меток. Операцию # можно использовать для склейки лексем --- она соответствует операции ## си++. Директивы common, forward и reverse делят макроопределение на соответствующие части. Блок common соответствует аргументам вне [] и обрабатывается один раз. Блоки forward и reverse соответствуют аргументу в [] и обрабатываются заданное числом фактических аргументов раз. Директива reverse перебирает аргументы в обратном порядке, а forward --- в прямом. Пример. macro std32call proc,arg*,[args] { common local stacksz stacksz = 4 reverse if ~ args eq push args stacksz = stacksz+4 end if common push arg call proc add esp,stacksz } stdcall printf,fmt,101 fmt db "%d\n",0 Здесь макровызов раскроется в push 101 push fmt call printf add esp,8 Реальный режим В этом режиме процессор начинает работать после включения питания или сброса (сигнала RESET). Доступ к памяти производится через обращение к смещению внутри выбираемого сегмента. В реальном режиме все сегменты имеют фиксированные размеры в 64 КБ. Базовый адрес сегмента определяется значением 16 бит соответствующего сегментного регистра, которые задают старшие биты 20-разрядного адреса. Например, если регистр DS имеет значение 0x8000, регистр ES --- 0x8400, SS --- 0x8200, BX --- 0x4000, BP --- 0x2000, то следующие значения эквиваленты BX, ES:0, BP, SS:0x2000, 0x4000, DS:BP+0x2000, ES:BP-0x2000 --- это обращения по адресу 0x84000. Таким образом, для прямой адресации доступно 6*64=384 КБ из общего объема 1 МБ доступной памяти. Используя специальные технологии можно использовать ещё около 64 КБ: если база сегмента равна 0xffff, то максимальный доступный адрес 0xffff0+0xffff=0x10ffef. Адреса прерываний определяются с адреса 0 в 1 КБ таблице. По каждому вектору прерывания хранится 4 байта: смещение и базовый сегментый адрес. При вызове прерывания сегментный адрес загружается в CS, а смещение в IP. После включения или сброса средства BIOS тестируют и инициализируют аппаратуру, заполняют таблицу прерываний, загружают начальный (boot) сектор диска на адрес 0x7c00 и передают управление на этот адрес. Средства BIOS поддерживают ряд системных переменных и структур: таймер, буфер клавиатуры, состояния устройств и т.п. Данные BIOS начинаются сразу после таблицы прерываний по адресу 0x400 и обычно заканчиваются к адресу 0x540. Средства BIOS --- это функции, которые доступны через программные прерывания, например, прерывание 0x10 предоставляет ряд функций для вывода данных на экран, 0x13 --- для низкоуровневой работы с дисками, 0x16 --- для ввода с клавиатуры, ... Процессор стартует с адреса 0xffff0 при своей инициализации. По адресу 0xffff5 в BIOS расположена дата его выпуска. Как правило, при старте работы загрузчика SP устанавливается так, чтобы гарантировать работу с небольшим стеком, CS=0. Кроме того, DL содержит номер диска, согласованный для использования функцией 0x13 BIOS. Преимущества реального режима: более быстрый ввод-вывод, простая настройка, доступность прямой работы с аппаратурой, функциями BIOS и прерываниями для прикладных программ. В реальном режиме нельзя использовать виртуальную память и постраничную трансляцию памяти. Нереальный режим Позволяет использовать преимущества реального режима и прямую адресацию до 4 ГБ памяти. В реальном режиме размер смещения при доступе к сегменту памяти ограничен 64 КБ. Это ограничение реализуется установкой соответствующего поля дескриптора сегмента, которое можно менять в защищенном режиме. Переход в нереальный режим производится переключением в защищенный режим, установкой значений упомянутого поля для выбранных сегментных регистров и возвратом в реальный режим. При работе с адресами большими 1 МБ может возникнуть проблема 20-й линии адреса. Она может быть заблокирована (фиксирована значением 0) для совместимости с 8086. Если она заблокирована, то можно будет работать только с чётными мегабайтами. Есть несколько технологий для её разблокировки. В защищённом режиме сегментные регистры являются селекторами дескрипторов сегментов памяти. Старшие 13 бит сегментного регистра образуют индекс дескриптора, 2-й бит определяет тип дескриптора (0 --- глобальный или 1 --- локальный), а два младших бита запрашиваемый уровень привилегий. Таким образом, в таблице дескрипторов может быть до 8192 описателей сегментов. Глобальный дескриптор --- это 64 бита данных: 1) биты 16..39 и 56..63 содержат 32-разрядный базовый адрес сегмента; 2) биты 0..15 и 48..51 --- 20-разрядное ограничение размера сегмента; 3) бит 55 (G, granularity, дробности) --- единицу измерения ограничения размера сегмента. G=0 означает байт, G=1 --- страницу, 4 КБ. В первом случае размер сегмента может быть до 1 МБ, во втором --- до 4 ГБ; 4) бит 54 --- разрядность по умолчанию. Значение 0 соответствует генерации кодов после директивы USE16 FASM, т.е. адреса-смещения в кодах без префиксов AS и данные в кодах без префиксов OS 16-разрядные. Значение 1 --- USE32, т.е. адреса-смещения в кодах без префиксов AS и данные в кодах без префиксов OS 32-разрядные. Префиксы AS/OS меняют поведение по умолчанию и должны генерироваться автоматически согласно инструкции ассемблера, например, в режиме USE16 команда MOV EAX,EBX --- это команда MOV AX,BX с префиксом OS, MOV AX,[EBX] --- это MOV AX,[BX] с префиксом AS, MOV EAX,[EBX] --- это MOV AX,[BX] с префиксами OS и AS. В некоторых случаях нужно давать ассемблеру уточнения для правильной генерации кода, например, при USE16 RETD --- это RET с OS, PUSHAD --- PUSHA c OS, CMP byte [ESI],[EDI] --- CMPSB c AS. Этот бит имеет значение только для кодовых сегментов. Для 64 битных кодов должен быть равен 0; 5) бит 53 --- длинный режим. Признак 64-битного кода, если установлен. 6) бит 47 --- присутствия. При отсутствие сегмента в памяти он равен 0. Если в сегментный регистр загрузить селектор такого дескриптора, то будет вызвано соответствующее прерывание --- это позволяет организовать сегментную виртуальную память. 7) биты 45, 46 --- уровень привилегий сегмента (0 - наивысший); 8) бит 44 --- это системный бит, 1 для дескрипторов кода и данных и 0 для специальных дескрипторов (локальных, задач, шлюзов для входа в другие задачи и др.); 9) бит 43 --- выполнимости. Значении 1 соответствует сегменту кода. В сегмент со значением этого бита 0 нельзя передать управление. Кодовый сегмент нельзя изменять. Значение 0 соответствует сегменту данных; 10) бит 41 --- чтения/записи. Значение 1 разрешает чтение данных в кодовом сегменте или запись в сегменте данных. Значение 0 разрешает только исполненние в кодовом сегменте или только чтение в сегменте данных; 11) бит 40 --- использованности. При обращении к сегменту устанавливается в 1. При использовании сегментной виртуальной памяти позволяет выявить редко используемые сегменты. Система периодически должна проверять и обнулять этот бит и собирать соответствующую статистику для алгоритма по замещению сегментов. //42,52 Таким образом, если нужен дескриптор сегмента данных для нереального режима, то нужно установить базовый адрес в 0, ограничитель в 0xfffff, биты дробности, чтения/записи, присутствия и системный в 1, а биты выполнимости и привилегий в 0. Для дескриптора кодового сегмента бит выполнимости должен быть установлен в 1, а разрядности соответствовать используемому коду. Для установки текущей таблицы глобальных дескрипторов используется команда LGDT с единственным операндом-адресом, указывающем на структуру из 16-битного размера - 1 таблицы и 32-битный адрес GTD. LGDT и LIDT единственные команды, загружающие адрес напрямую в защищенном режиме, минуя сегментные регистры, но в реальном режиме сегментные регистры используются. LIDT для установки текущей таблицы прерываний по формату не отличима от LGDT. Нулевой дескриптор GDT никогда не используется: можно загружать в сегментные регистры селектор 0, но попытка обращения к сегменту с таким дескриптором вызовет ошибку. Для перехода в защищенный режим нужно установить бит 0 регистра CR0 или MSW, а для выхода из него сбросить этот бит в CR0. Кроме того, при выходе из защищенного режима желательно сделать длинный переход для сброса очереди команд в процессоре. Обычно ограничение на размер сегмента снимается только для сегментных регистров для работы с данными (big unreal mode), но можно снять это ограничение и для кода (huge unreal mode). В последнем случае возникает проблема прерываний --- прерывания в реальном режиме сохраняют только 16-разрядное смещение или младшие 16 бит EIP. В режиме USE16 нужно избегать использования обработчиков прерываний, изменяющих старшие разряды EIP. Для USE32 эту проблему можно решить заменой всех используемых обработчиков прерываний. Защищенный режим 16 бит Это подмножество возможностей 32-разрядного режима для совместимости с программами для процессора 80286. Эта совместимость предполагает использование сегментов, не больших 64 КБ, только сегментной виртуальной памяти и т.п. В 32-разрядном режиме можно естественным образом вызывать программы, написанные для 16-разрядного. В сегментах с 16-разрядным кодом можно использовать прерывания BIOS, не меняющие сегметных регистров, подстановкой векторов из таблицы прерываний в IDT. Можно для каждого прерывания BIOS сделать "обёртку" (wrapper) переводящую процессор в реальный режим и затем возвращающую обратно. Защищенный режим 32 бита Требует для использования прерываний загрузки регистра базы IDT командой LIDT адресом специальной таблицы дескрипторов-шлюзов. Шлюзы --- gates --- это специальные дескрипторы со сброшенным системным битом. Для каждого прерывания нужен свой шлюз: для всех 256 прерываний потребуется 2 КБ таблица. Прерывания вызываются процессорным исключением, внешним аппаратным сигналом, инструкциями типа INT. Первые 32 прерывания зарезервированы для исключений процессора. Рассмотрим некоторые из них: 0) ошибка деления --- при делении на 0 или переполнении; 1) отладочное --- при установленном TF или при достижении аппаратной точки останова; 2) не маскируемое --- non-maskable interrupt (NMI) --- по внешнему сигналу. Нельзя запретить установкой IF в 0, но можно специальными средствами; 3) INT3; 4) INTO; 5) ошибка проверки границ массива командой BOUND; 6) неправильная инструкция --- такое прерывание позволяет программно эмулировать инструкции, не поддерживаемые заданным процессором; 7) не доступен арифметический сопроцессор. Генерируется, например, по инструкции WAIT; 8) двойная ошибка --- возникает при обработке некоторых исключений, внутри которых возникают исключения; 11) отсутствие сегмента --- обращение к сегменту, бит присутствия которого в дескрипторе 0 --- виртуальная память; 12) ошибка обращения к стеку --- при нарушении границы стека --- позволяет динамически создавать новые сегменты для стека; 13) нарушение защиты памяти; 14) отсутствие страницы --- при работе со страничной виртуальной памятью; 16) ошибка арифметического сопроцессора; 17) ошибка выравнивания --- только в защищенном режиме при установленном контроле за выравниванием. Выравненный адрес данных кратен размеру этих данных, например, выравненный адрес двойных слов кратен 4. Исключения разделяют на три категории: неуспехи (faults) --- в стеке сохраняется адрес команды, вызвавшей неуспех, что позволяет перевыполнить эту команду, например, деления, неизвестную или находящуюся в недоступном сегменте; ловушки (traps) --- в стеке сохраняется адрес следующей команды, полезны при отладке, к этой категории относят и программные прерывания; аварии (aborts) --- крах аппаратуры. Процессорные исключения и команды отличает от внешних прерываний их синхронность с работой процессора. Прерывания по внешнему сигналу --- это NMI и маскируемые, вызываемые по сигналу IRQ (Interrupt Request). Архитектура стандарта IBM PC поддерживает до 16 источников IRQ. После получения сигнала IRQ при разрешении прерываний (IF=1) процессор запрашивает номер прерывания, которое сообщается внешним устройством. Установками BIOS таймер вызывает прерывание 8, клавиатура --- 9, жёсткий диск --- 0x76, ... Настройкой аппаратуры можно запрещать (маскировать) отдельные маскируемые прерывания. В Linux статистическую и техническую информацию по прерываниям в системе можно получить командой cat /proc/interrupts, показывающей идентификатор IRQ, число вызовов, контроллер и устройство. Дескрипторы-шлюзы для вызова кодов для обработки запроса на прерывание бывают трёх типов: шлюзы-прерывания, шлюзы-ловушки и шлюзы-задачи. Отличие ловушки от прерывания только в том, что при её вызове автоматически не сбрасывается бит IF, что позволяет происходить следующим прерываниям. Шлюзы-задачи позволяют использовать аппаратный механизм поддержки многозадачности, выделяя для обработки прерывания отдельный процесс. Формат записи в IDT отличается от формата записи в GDT следующими полями: биты 0..15 и 48..63 задают 32-разрядное смещение обработчика прерываний; биты 16..31 задают значение селектора сегмента, в котором расположен обработчик; биты 32..39 должны быть нулями; биты 40..43 определяют тип шлюза: 5 --- шлюз-задача, 6/0xE --- шлюз-прерывание 16/32-разрядное, 7/0xF --- шлюз-ловушка 16/32-разрядная. Таким образом, для поддержки прерываний в защищенном режиме следует сначала подготовить таблицу IDT, установить её текущей командой LIDT, настроить при необходимости аппаратуру и разрешить прерывания. Часть обработчиков прерываний, например, ловушки можно сделать доступными для использования программами пользователя установкой низкого приоритета в соответствующем поле дескриптора-шлюза. В защищенном режиме использование страничной трансляции памяти опционально. Использование локальных таблиц дескрипторов (LDT) позволяет в 16/32-битной системе иметь до 1/64 ГБ виртуальной памяти для одной задачи. LDT практически используются только в 16-разрядных программах. Страничная трансляция Длинный режим требует использования страничной трансляции памяти. Страничная трансляция памяти производится на последнем этапе обращения к физической памяти и может использоваться для: 1) размещения программ и данных в памяти по фиксированным линейным адресам --- разные задачи могут иметь одинаковые линейные адреса; 2) организации множественных заданий в режиме виртуального 8086; 3) страничной защиты памяти; 4) страничной виртуальной памяти с 32-битным линейным адресом объемом до 4 ГБ на задачу. Используя расширение PSE-36, можно увеличить общий объём физической памяти прямого доступа до 64 ГБ; 5) страничной виртуальной памяти с 64-битным линейным адресом, из которого по существующим технологиям используется до 48 бит, т.~е. общим объемом до 256 ТБ на задачу. Страничная трансляция переводит так называемый линейный адрес, т.~е. адрес в программе, в физический. Существует несколько вариантов страничной трансляции: базовый, с большими страницами --- PSE (Page Size Extension) и PSE-36/40, расширенный --- PAE (Physical Address Extension). В базовом варианте страница имеет размер 4 КБ, а линейный 32-разрядный адрес рассматривается состоящим из трёх частей: адреса в таблице каталога страниц (биты 22..31), адреса в таблице страниц (12..21) и смещения в странице (0..11). Первый адрес определяет используемую таблицу страниц, а 2-й --- используемую страницу. Такая схема позволяет адресовать до 2^10*2^10*2^12 = 2^32 Б = 4 ГБ памяти. Каждая таблица занимает одну страницу и состоит из 32-битных записей, т.~е. в такой таблице 1024 записи. Получается, что для использования 4 ГБ памяти потребуется 4 КБ + 4 МБ памяти для трансляционных таблиц. Для ускорения работы с таблицами используется специальная табличная кэш-память. Адрес таблицы (младшие 12 бит --- нулевые) каталога страниц хранится в регистре CR3 --- его содержимое может меняться, что допускает наличие, например, отдельных наборов страниц для каждой задачи. В регистре CR2 хранится адрес страницы, вызвавшей исключение отсутствия при обращении к ней --- это часть аппарата поддержки виртуальной памяти. Записи в таблицах обоих типов имеют идентичный формат: старшие 20 бит задают адрес страницы, а младшие 12 содержат её атрибуты. Рассмотрим некоторые атрибуты: 1) бит 0 --- это бит присутствия страницы в памяти, если он равен 0, то страницы в памяти нет и остальные 31 бит система может использовать для любых целей. Служит для организации виртуальной памяти; 2) бит 1 --- чтение/запись, если он равен 0, то разрешено только чтение, а если 1, то и запись; 3) бит 2 --- система/пользователь, если равен 0, то к странице допускаются только программы с уровнем привилегий 0..2, а если 1, то и программы с наименьшим уровнем привилегий 3. Бит чтения/записи имеет значение только для программ с уровнем привилегий 3; 4) бит 5 --- доступа, автоматически устанавливается в 1 при обращении к странице; 5) бит 6 --- грязный (dirty), устанавливается автоматически при записи в страницу. Последние два бита помогают системе в организации виртуальной памяти, а первые два обеспечивают организацию страничной защиты памяти. Включение/выключение страничной трансляции любого режима производится установкой/сбросом соответствующего бита (PG, 31-го) в CR0. В варианте с большими страницами в записях таблицы каталога страниц можно использовать бит 7 --- его установка в 1 будет означать, что запись указывает не на таблицу страниц, а на страницу физической памяти размером 4 МБ. Установка бита 7 в 1 означает, что в линейном адресе старшие 10 бит --- это адрес таблицы, а младшие 22 --- смещение. Расширение варианта с большими страницами (PSE-36) использует в записи таблицы каталога для большой страницы дополнительно 4 бита (13..16) как старшие биты адреса большой страницы, что позволяет формировать 36-битный адрес и расширить адресное пространство до 64 ГБ. В записях таблицы каталога больших страниц образуется 10 свободных бит (12..21), так как для задания выровненного адреса большой страницы достаточно 10 бит. Существует также вариант PSE-40 для 64-битных процессоров, использующий биты 13..20 и позволяющий использовать до 1 ТБ физической памяти. PSE проще в использовании, чем PAE, но используется очень редко. Недостаток PSE в худшей производительности и невозможности использовать маленькие страницы за пределами адресного пространства в 4 ГБ. PSE как и PAE устанавливается битом CR4. В режиме PAE размер страницы 4 КБ, но записи в таблицах страниц становятся 64-битными, что уменьшает их количество на странице вдвое до 512. Трансляция становится 3-уровневой, добавляется таблица указателей на каталог страниц (PDPT --- Page Directory Pointer Table), на которую указывает CR3. Теперь два старших бита 32-разрядного линейного адреса указывают на запись в PDPT, следующие 9 бит на запись в таблице каталога, следующие --- на запись в таблице страниц, последние 12 бит --- это по-прежнему смещение. При использовании PSE запись таблицы каталога сразу указывает на базовый физический адрес, а последние 21 бит образуют смещение к заданной базе. Таким образом, размер больших страниц уменьшается до 2 МБ. Режим PAE необходим для длинного режима. В этом режиме трансляция становится 4-уровневой, добавляется соответствующая таблица (PML4T --- Page Map Level 4 Table), на которую должен указывать CR3. Каждая из 4-х таблиц содержит 512 записей, а смещение остается 12-битным. Такая схема трансляции позволяет использовать до 2^(9*4+12) = 2^48 байт = 256 ТБ с линейным адресом. Подключение режима PSE в длинном режиме невозможно. В записях таблицы страниц используется бит NX (No-eXecute, 63-й) --- его установка запрещает выполнять код в соответствующей странице. Длинный режим 64 бита В нем отсутствует поддержка сегментации. Войти в этот режим можно только из защищённого. В GDT дескрипторе для CS при входе в режим 64 битных кодов бит 54 (разрядности) должен быть равен 0 (как для 16-разрядных), а бит 53 (длинный режим) --- 1. Биты размера сегмента, дробности и чтения/записи игнорируются. В длинном режиме значение регистров CS, DS, ES, SS игнорируется, а регистры GS и FS можно использовать специальным образом. Записи в таблице IDT в длинном режиме расширяются до 16 байт, младшие 4 из 8 новых байт задают старшие 32 бита адреса прерывания. GDT практически не используется. Если системный бит в её дескрипторе 0, то размер записи 16 байт. В длинном режиме не все инструкции имеют 64-битные эквиваленты, например, отсутствует возможность пересылки непосредственного значения в память. Для перехода в длинный режим надо установить режим PAE, подготовить страничную трансляцию, установить флаг длинного режима в специальном регистре, включить страничную трансляцию и сделать длинный переход с селектором дескриптора длинного сегмента. Не выходя из длинного режима, можно переключаться в подрежим совместимости с 16/32-битными программами защищенного режима (legacy mode) и возвращаться обратно. Длинный режим не совместим с реальным/нереальным режимами и с режимом виртуального 8086. Перемещаемость кода Машинные инструкции делятся на перемещаемые и нет. Первые работают одинаково, будучи расположенными по любому адресу, например, CLC MOV AX,4 INT 0x16 L: ADD AX,10 CMP AX,BX JZ L Вторые правильно работают только, если расположены по заданному адресу, например, MOV RCX,0x1234567 JMP RCX JMP 8:FARLABEL Есть несколько технологий загрузки программ: 1) использовать только перемещаемую адресацию, т.~е. все адреса в операндах инструкций должны быть смещениями относительно текущего значения IP. Архитектура x86_64, в отличие от x86, предоставляет с ограничениями такую возможность, ценой отказа от ряда инструкций. Перемещаемые команды обычно занимают меньше памяти, в примере команда JZ использует только байт (вместо 8) для хранения адреса перехода; 2) использование загрузчика. Каждая программа снабжается загрузчиком или информацией для стандартного загрузчика. Загрузчик, имея адрес размещения программы в памяти, корректирует все неперемещаемые адреса в инструциях. В программе все такие адреса должны быть относительными, представляя собой смещения относительно начала программы; 3) использование сегментой модели. Вся адресация опирается на базовые адреса, задаваемые сегментными регистрами или дескрипторами сегментов; 4) использование страничной трансляции. Как правило, использование загрузчика является обязательным и при использовании других технологий загрузки.