Differences between revisions 13 and 14
Revision 13 as of 2019-04-12 14:09:34
Size: 27059
Editor: FrBrGeorge
Comment:
Revision 14 as of 2019-05-17 12:23:15
Size: 27152
Editor: FrBrGeorge
Comment:
Deletions are marked like this. Additions are marked like this.
Line 136: Line 136:
{{{ {{{#!highlight nasm
Line 150: Line 150:
{{{ {{{#!highlight nasm
Line 215: Line 215:
 . Назначение инструкций типа '''trap:''' Назначение инструкций типа '''trap:'''
Line 218: Line 218:
 {{{  {{{#!highlight nasm
Line 255: Line 255:
 {{{  {{{#!highlight nasm
Line 268: Line 268:
 {{{  {{{#!highlight nasm
Line 294: Line 294:
 {{{  {{{#!highlight nasm

Исключительные ситуации

Демонстрация calltrace с помощью macros.inc

Программные исключения

Базовая лекция

Обработка событий в ЭВМ

Что такое «событие»?

  • Происходит в процессе работы (какой-либо программы)
  • Происходит по какой-то причине, явно в программе не заданной

    • дополнительно: программная имитация события

  • Вызывает изменение потока вычислений
    • Вариант реализации: меняется значение счётчика команд
    • Скорее всего, обрабатывается ядром ОС

    Свойства события
  • Асинхронное / активированное синхронно
  • Неожиданное / предполагаемое
  • Требующее возврата к прежнему вычислительному процессу / финальное Терминология плавающая. В архитектуре Intel принято все события называть «прерываниями» В MIPS:
  • прерывания — это события, вызванные внешними причинами (аппаратные)

  • исключения — события, вызванные внутренними причинами (программные)

Тип события

Место в программе известно

Предусмотрено
программой

Источник

Термин

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

да

нет

Внутренний

Исключение (exception)

Имитация нештатной ситуации («система, разбирайся»). В MIPS — исключение TRAP_EXCEPTION

да

да

Внутренний

Ловушка (trap)

Обращение к функции операционной системы

да

да

Внутренний

Системный вызов (system call)

Запрос внешнего устройства

нет

нет

Внешний

Прерывание (interrupt)

Сбой внешнего устройства

иногда

нет

Внешний

Прерывание (interrupt)

Строго говоря, системный вызов — не событие, т. к. всегда явно вызывается программой, но также требует обработки ядром. Общая идея обработки событий.

  • Аппаратура процессора обнаруживает событие и осуществляет передачу управления.
    • на фиксированный адрес (как в MIPS, адрес обработчика 0x80000180)
    • с помощью «вектора прерываний» (он же таблица переходов) — аппаратно поддерживаемая таблица адресов. Предположим, что она находится по адресу 0x80000100

Адрес

Содержимое

Пояснение

0x80000100

0x80000180

адрес обработчика исключения № 0

0x80000104

0x800007ac

адрес обработчика исключения № 1

0x80000108

0x800015b0

адрес обработчика исключения № 2

0x80000120

0x80000e54

адрес обработчика исключения № 8

  • Таблица переходов в MIPS отсутствует:

    • Аппаратная поддержка: двойная косвенность.
    • При получении исключения вычисляется адрес ячейки, из неё берётся адрес обработчика (+операция доступа к памяти).
  • Программная обработка события
    • Аппаратная поддержка: запрет и/или дисциплина обработки повторных событий (событие внутри события); возможно, специальный режим работы процессора

    • Аппаратная поддержка: тип события (выставляется следящей аппаратурой)
  • Возврат к нормальному порядку исполнения инструкций
    • Аппаратная поддержка: адрес возврата запоминается аппаратурой
    • Возможны сложности с конвейером (см. лекцию про конвейер)

Обработка исключний в MIPS

Следит за аппаратурой, устанавливает и хранит дополнительные данные о событиях специальный управляющий сопроцессор MIPS (coprocessor0).

MARS имитирует основные элементы механизма MIPS32 исключений Регистры сопроцессора 0 MARS:

Название

Номер

Назначение

BadVAddr

8

Адрес, при обращении к которому произошло исключение (при записи/чтении по защищённому адресу)

Status

12

Состояние: маска прерываний, биты разрешений, ...

Cause

13

Тип («причина») исключения и биты отложенных прерываний

EPC

14

Адрес инструкции, которая вызвала исключение (или во время выполнения которой произошло прерывание)

Инструкции для работы с регистрами управляющего сопроцессора:

  1. mfc0 Rdest, C0src — загрузить содержимое управляющего регистра C0src в регистр общего назначения Rdest
  2. mtc0 Rsrc, C0dest — загрузить содержимое регистра общего назначения C0src в управляющий регистр Rdest
  3. eret — вернуться из обработчика исключительной ситуации Когда происходит исключение, сопроцессор совершает следующие действия:
  4. Устанавливает бит 1 управляющего регистра $12 (EXception Level, EXL).
  5. Устанавливает биты 2-6 управляющего регистра $13(причина) согласно типу исключения
  6. Устанавливает управляющий регистр $14 (EPC). В регистре сохраняется адрес инструкции, вызвавшей исключение.
  7. Если исключение было вызвано обращением к неправильному адресу памяти, управляющий регистр $8 (BadVaddr) устанавливается на этот неверный адрес.

  8. Поток выполнения переключается с текущей инструкции MIPS на адрес 0x8000180. Этот адрес в сегменте текста ядра (.ktext) является стандартным для расположением обработчика исключений MIPS32.

  9. Обработчик исключений может вернуть управление программе, используя команду eret. Это поместит значение из "EPC" (регистр $14) в счетчик команд("PC"). Кроме того, eret очищает бит EXL (см. далее) Замечание: Увеличение регистра $14 на 4 перед возвращением позволит пропустить инструкцию, вызвавшую исключение.

Лень и надувательство (см. спойлер):

Регистр Status:

bits

31-16

15-8

7-5

4

3,2

1

0

target

unused

Int. mask

unused

K/U

unused

Exception level

Int enable

  • Interrupt mask - Mаска прерываний. Соответствующий прерыванию бит равен 1, если данное прерывание разрешено. Если бит равен 0, соответствующее прерывание не обрабатывается (запрещено)
  • K/U - Kernel Mode / User Mode. User mode не эмулируется Mars; всегда 1.
  • Exception level - устанавливается автоматически при исключении; предотвращает повторный вход.
  • Interrupt enable - глобальное разрешение прерываний (0 - отключить). В других версиях MIPS биты 0-5 регистра Status используются по-иному:
  • бит 1 отображает состояние Kernel(=0)/User(=1) mode (то есть работает ли в данный момент процессор в режиме Kernel mode или в режиме User mode)
  • бит 0 отображает резрешение (=1) прерываний
  • когда происходит обработка исключений, биты 3-2 записываются в биты 5-4, биты 1-0 — в биты 3-2, а в биты 1-0 записывается текущее состояние процессора
  • когда происходит выход из обработчика исключений, восстанавливаются предыдущие значения: 3-2 → 1-0, 5-4 → 3-2

Таким образом, в регистре Status формируется трёхуровневый стек для хранения состояний. Если обработка прерывания в прерывании запрещена, достаточно двух уровней, но есть, например, программные ловушки (trap).

Регистр Cause:

31

30-16

15-8

7

6-2

1-0

Br

unused

Pending interrupts

unused

Exception code

unused

  • Br = 1, если исключение произошло на инструкции в слоте задержки перехода (это деталь конвейера, не обращайте внимания пока:)
  • Pending interrupts - ожидающие прерывания. Прерывания асинхронны и вызываются внешними причинами, так что одновременно их вполне может произойти несколько.

  • Exception code - код исключения.

Обработчик исключений

Типы исключений (не обязательно реализованы):

  • ADDRESS_EXCEPTION_LOAD (4)
  • ADDRESS_EXCEPTION_STORE (5)
  • SYSCALL_EXCEPTION (8) (в MARS — исключение внутри системного вызова),

  • BREAKPOINT_EXCEPTION (9) (в MARS — DIVIDE_BY_ZERO_EXCEPTION),

  • RESERVED_INSTRUCTION_EXCEPTION (10),
  • ARITHMETIC_OVERFLOW_EXCEPTION (12),
  • TRAP_EXCEPTION ( 13),
  • DIVIDE_BY_ZERO_EXCEPTION (15),
  • FLOATING_POINT_OVERFLOW (16),
  • FLOATING_POINT_UNDERFLOW (17).

При написании обработчика можно без сохранения использовать только регистры $k0 и $k1, т. к. прерывания могут возникнуть когда угодно, а после возвращения ничего (!) не должно меняться.

⇒ В «больших» системах предусматривается отдельный стек ядра (пользовательский стек может быть испорчен).

Пример тривиального обработчика (пройти под отладчиком Mars):

   1 .text
   2         nop
   3         lw    $t0, ($zero)      # Попытка чтения по адресу 0
   4         li    $v0 10
   5         syscall
   6 
   7 .ktext  0x80000180
   8         mfc0    $k0 $14         # В регистре EPC — адрес инструкции, где произошло прерывание
   9         addi    $k0 $k0,4       # Добавим к этому адресу 4
  10         mtc0    $k0 $14         # Запишем обратно в EPС
  11         eret                    # Продолжим работу программы

Пример обработчика. Заметим, что коду обработчика разрешается менять только два регистра — $k0 и $k1, все остальные регистры следует сохранять, если они используется в коде.

   1 .text
   2         lui     $t0 0x7fff
   3         addi    $t0 $t0 0xffff
   4         addi    $t0 $t0 0xffff  # Целочисленное переполнение
   5         sw      $t0 0x400       # Обращение к недоступному адресу памяти
   6         divu    $t0 $t0 $zero   # Деление на 0
   7         teq     $zero $zero     # Программная имитация (ловушка)
   8         li      $v0 10
   9         syscall
  10 .kdata
  11 msg:    .asciiz "Exception "
  12 .ktext  0x80000180
  13         move    $k0 $v0         # Сохраняем $v0
  14         move    $k1 $a0         # Сохраняем $a0
  15         la      $a0 msg         # Выводим сообщение
  16         li      $v0 4
  17         syscall
  18         mfc0    $a0 $13         # Регистр Cause
  19         srl     $a0 $a0 2       # Выделяем в нём поле «причина»
  20         andi    $a0 $a0 0x1f
  21         li      $v0 1           # Выводим как целое
  22         syscall
  23         li      $a0 10
  24         li      $v0 11          # Выводим перевод строки
  25         syscall
  26 
  27         move    $v0 $k0         # Восстанавливаем $v0
  28         move    $a0 $k1         # Восстанавливаем $a0
  29 
  30         li      $k0 0
  31         mtc0    $k0 $13         # Затираем регистр Cause
  32         mfc0    $k0 $14         # В регистре EPC — адрес инструкции, где произошло прерывание
  33         addi    $k0 $k0,4       # Добавим к этому адресу 4
  34         mtc0    $k0 $14         # Запишем обратно в EPС
  35         eret                    # Продолжим работу программы

Вопрос: какой регистр мы всё-таки испортили в этом обработчике?

Замечание: в Mars исключение №9 — это деление на 0, а точки останова реализованы «аппаратно», т. е. внутри самого MARS

Пример полностью программного обработчика прерываний и исключений в проекте SPIM

Имитация исключений

С одной стороны, на все случаи жизни исключения предусмотреть нельзя.

С другой стороны, это очень эффективный механизм обращения к ОС почти (или совсем) без дополнительных действий, в отличие от syscall. Системный обработчик по адресу 0x80000180 вызывается немедленно одной инструкцией, атомарно с операцией сравнения.

teq $t1,$t2

Trap if equal

Trap if $t1 is equal to $t2

teqi $t1,-100

Trap if equal to immediate

Trap if $t1 is equal to sign-extended 16 bit immediate

tge $t1,$t2

Trap if greater or equal

Trap if $t1 is greater than or equal to $t2

tgei $t1,-100

Trap if greater than or equal to immediate

Trap if $t1 greater than or equal to sign-extended 16 bit immediate

tgeiu $t1,-100

Trap if greater or equal to immediate unsigned

Trap if $t1 greater than or equal to sign-extended 16 bit immediate, unsigned comparison

tgeu $t1,$t2

Trap if greater or equal unsigned

Trap if $t1 is greater than or equal to $t2 using unsigned comparision

tlt $t1,$t2

Trap if less than

Trap if $t1 less than $t2

tlti $t1,-100

Trap if less than immediate

Trap if $t1 less than sign-extended 16-bit immediate

tltiu $t1,-100

Trap if less than immediate unsigned

Trap if $t1 less than sign-extended 16-bit immediate, unsigned comparison

tltu $t1,$t2

Trap if less than unsigned

Trap if $t1 less than $t2, unsigned comparison

tne $t1,$t2

Trap if not equal

Trap if $t1 is not equal to $t2

tnei $t1,-100

Trap if not equal to immediate

Trap if $t1 is not equal to sign-extended 16 bit immediate

Таким образом гарантируется, что за время, прошедшее между сравнением и обращением к системе, никакие данные не изменятся (потому что этого времени нет :) ).

Назначение инструкций типа trap:

  • Отладочный останов программы (т. н. assertion). Допустим, в регистре не должно получаться 0, а если ошибочно получится, то лучше пускай система обработает эту ошибку, чем программа будет дальше считать. Например, можно проверить, что изменение счётчика в цикле вообще происходит:
       1         teqi    $t1 0
       2         subu    $t0 $t0 $t1
       3         bgez    $t0 loop
    
  • Обработка исключительной ситуации, если аппаратного обнаружения её нет. Обычно используется команда R-типа, в которой можно в неиспользуемом аппаратно поле хранить причину исключения. Ср. архитектура RISCore:

    TEQ - Ловушка по равенству

    31

    25

    20

    15

    5

    SPECIAL 000000

    rs

    rt

    code

    TEQ 110100

Содержимое поля code игнорируется системой и может быть использовано для кодированиия информации для системного ПО. Для получения этой информации программа должна загрузить слово команды из памяти (адрес хранится в регистре EPC сопроцессора 0). В ассемблере Mars эта возможность отсутствует, обидно.

Многофайловая сборка

базовая часть лекции

Свойства макроассемблера MARS

Замечания авторов MARS относительно их макроассемблера:

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

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

  • Вложенные макросы не поддерживаются, т. е. внутри макроопределения не может встречаться директива .macro

  • Внутри макроопределения, как и в тексте программы, могут встречаться только ранее определённые макрокоманды, искать их определения далее по тексту никто не будет
  • Все метки меняются в процессе макроподстановки, превращаясь в метка_M№

    • (Замечание от меня: нет, на все! если передать метку в качестве параметра, а потом написать что-то вроде %label: , _M№ к ней не припишется. Не знаю, как и зачем это можно использовать…)

  • Несколько макроса с одинаковым именем, но разным количеством параметров, считаются различными, и их можно использовать все
  • Повторное определение макроса с тем же именем и тем же количеством параметров игнорируется, макрокоманда раскрывается в первое определение
  • Параметром макроса (в силу ограниченной реализации) может быть только атомарная лексема языка ассемблера. Например, параметром не может быть "4($t0)", потому что это две лексемы, а не одна

  • Макросредства ассемблера не входят ни в какой стандарт и остаются на усмотрение авторов ассемблера

    В больших многофайловых проектах принято все макросы складывать в отдельный файл и включать их в код программы с помощью директивы .include файл_с_макросами . Подпрограммы при этом складываются в другой файл (возможно. не один), т. н. «библиотеку», и подключаются посредством многофайловой сборки. На предыдущем примере:

    Файл с программой prog.asm:

       1 .include        "macro.inc"
       2 .globl  main
       3 .text
       4 main:
       5         input   "First input" $t0
       6         input   "Second input" $t1
       7         print   "First result" $t0
       8         print   "Second result" $t1
       9         exit
    

    Файл с подпрограммами lib.asm:

       1 .globl  _print _input
       2 
       3 .text
       4 _input: # $a0message / $v0input value
       5         li      $v0 4
       6         syscall
       7         li      $v0 5
       8         syscall
       9         jr      $ra
      10 
      11 _print: # $a0message, $a1number
      12         li      $v0 4
      13         syscall
      14         move    $a0 $a1
      15         li      $v0 1
      16         syscall
      17         li      $a0 10
      18         li      $v0 11
      19         syscall
      20         jr      $ra
    

    На забываем метки всех подпрограмм, которые понадобятся в других файлах, объявлять как .globl

    Файл с макросами macro.inc (имя файла не заканчивается на .asm в знак того, что его не нужно транслировать отдельно):

       1 .macro  input   %msg %reg
       2 .data
       3 msg:    .ascii  %msg
       4         .asciiz ": "
       5 .text
       6         la      $a0 msg
       7         jal     _input
       8         move    %reg $v0
       9 .end_macro
      10 
      11 .macro  print   %msg %reg
      12 .data
      13 msg:    .ascii  %msg
      14         .asciiz ": "
      15 .text
      16         la      $a0 msg
      17         move    $a1 %reg
      18         jal     _print
      19 .end_macro
      20 
      21 .macro  exit
      22         li      $v0 10
      23         syscall
      24 .end_macro
    

Д/З

  1. EJudge: NoErrors 'Без ошибок'

    Написать полную программу, которая пытается вводить целые числа и обрабатывает ошибки ввода до тех пор, пока не введёт 10 штук целых. Затем она их выводит, каждое с новой строки.

    Input:

    zzz
    20
    fwewefqwe
    .654
    71
    -124
    0.1
    82
    6.
    334423
    -94
    VII
    7535
    6
    .
    -
    17
    8968
    Output:

    20
    71
    -124
    82
    334423
    -94
    7535
    6
    17
    8968
  2. EJudge: ExCalc 'Простой калькулятор'

    Написать полную программу — простой целочисленный калькулятор с обработкой ошибок ввода, деления и переполнения.

    • Работа калькулятора:
      1. Ввод первого числа
      2. Ввод знака арифметической операции
      3. Ввод второго числа
      4. Вывод результата (применение операции к первому и второму числу). Результат выводится, даже если была ошибка.
      5. Результат становится первым числом для следующей операции
      6. Переход на п. 2
    • Поддерживаемые действия: "+", "-", "*", "/"
    • Дополнительно (обязательно с использованием обработчика исключений) отслеживаются:
      1. Ввод нечислового значения вместо числа (исключение 8). В этом случае:
        • выводится сообщение "Invalid input"
        • число вводится заново (и так до тех пор, пока не будет введено число)
      2. Деление на 0 (в Mars — исключение 9; обратите внимание на то, как разворачивается псевдоинструкция div $t0 $t1 %t2, и только она). В этом случае

        • выводится сообщение "Division by zero"
        • результат операции должен быть равен 0
      3. Знаковое переполнение (исключение 12). В этом случае
        • выводится сообщение "Error"
        • результат операции равен второму слагаемому
    • Ввод заканчивается, если введённый знак действия не поддерживается (пустая строка, точка, пробел и т п.)
    Input:

    22
    *
    lll
    3
    /
    0
    +
    43545
    +
    2147483647
    .
    Output:

    Invalid input
    66
    Division by zero
    0
    43545
    Error
    2147483647

TODO

  1. ???

LecturesCMC/ArchitectureAssembler2019/07_Exceptions (last edited 2019-05-17 12:23:15 by FrBrGeorge)