Косвенно-регистровый режим со смещением

Адрес операнда образуется путем сложения регистра и адресного поля команды. Этот режим наиболее богат возможностями и, в зависимости от стиля использования, имеет много других названий, например, базовая адресация или индексная адресация. Адресное поле необязательно содержит полноценный адрес и может быть укороченным. Команды ld/st процессора SPARC, используемые в примере 2.2, реализуют косвенно-регистровую адресацию с 13-разрядным смещением.

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

В другом случае, в регистре может храниться указатель на структуру данных, а смещение может означать смещение конкретного поля относительно начала структуры. Еще один вариант — регистр хранит указатель на стековый кадр или блок параметров процедуры, а смещение — адрес локальной переменной в этом кадре или определенного параметра. В этих случаях можно использовать (и обычно используется) укороченное смещение.

Стековый кадр

Стековый кадр является стандартным способом выделения памяти под локальные переменные в алголоподобных процедурных языках (C, C++, Pascal) и других языках, допускающих рекурсивные вызовы.

Семантика рекурсивного вызова в алголоподобных языках требует, чтобы каждая из рекурсивно вызванных процедур имела собственную копию локальных переменных. В SPARC это достигается сдвигом регистрового окна по регистровому файлу (рис. 2.9), но большинство других процессоров такой роскоши лишены и вынуждены выделять память под локальные переменные в стеке, размещаемом в ОЗУ.

Для этого вызванная процедура уменьшает (если стек растет вниз) указатель стека на количество байтов, достаточное, чтобы разместить переменные. Адресация этих переменных у некоторых процессоров (например, у PDP-11) происходит относительно указателя стека, а у большинства — например, у МС680хО и VAX, с большим количеством регистров или у х86, указатель стека которого нельзя использовать для адресации со смещением — для этой цели выделяется отдельный регистр.

Пример 2.4. Формирование, использование и уничтожение стекового кадра. Код на языке С и результат его обработки GNU С 2.7.2.1 (комментарии автора)

#include <stdio.h>
#include <strings.h>

/* Фрагмент примитивной реализации сервера SMTP (RFC822) */
int parse_line(FILE * socket)
{
    /* Согласно RFC822, команда имеет длину не более 4 байт, а вся строка — не более 255 байт */
    char cmd[5], args[255];
    fscanf(socket, "%s %s\n", cmd, args);

    if (stricmp(cmd, "HELO") == 0) {
        fprintf(socket, "200 Hello, %s, glad to meet you\n", args);
        return 200;
    }

    /* etc */
    fprintf(socket, "500 Unknown command %s\n", cmd);
    return 500;
}
.section .data
LC0:
    .ascii "%s %s\12\0"
LC1:
    .ascii "HELO"
LC2:
    .ascii "200 Hello, %s, glad to meet you\12\0"
LC3:
    .ascii "500 Unknown command %s\12\0"

.section .text
.globl parse_line

parse_line:
    push ebp                 ; Сохраняем базовый указатель стека
    mov ebp, esp             ; Устанавливаем новый базовый указатель стека

    sub esp, 260             ; Выделяем память под локальные переменные (4 + 255 + 1 для '\0')

    lea eax, [ebp - 4]       ; Загружаем адрес переменной cmd
    lea ebx, [ebp - 259]     ; Загружаем адрес переменной args

    push ebx                 ; Аргументы для fscanf
    push eax
    push dword ptr [ebp + 8] ; Аргумент socket для fscanf
    push LC0                 ; Форматная строка для fscanf
    call fscanf              ; Вызов функции fscanf

    push eax                 ; Результат функции fscanf
    push LC1                 ; Строка "HELO"
    push [ebp - 4]           ; Переменная cmd
    call stricmp             ; Вызов функции stricmp

    test eax, eax            ; Проверка результата stricmp
    jnz not_helo             ; Если результат не 0, переходим к метке not_helo

    ; Если cmd == "HELO"
    push [ebp - 259]         ; Переменная args
    push LC2                 ; Форматная строка
    push dword ptr [ebp + 8] ; Аргумент socket для fprintf
    call fprintf             ; Вызов функции fprintf
    add esp, 12              ; Освобождаем память стека

    mov eax, 200             ; Возвращаемое значение 200
    jmp end_function         ; Переходим к завершению функции

not_helo:
    push [ebp - 4]           ; Переменная cmd
    push LC3                 ; Форматная строка
    push dword ptr [ebp + 8] ; Аргумент socket для fprintf
    call fprintf             ; Вызов функции fprintf
    add esp, 12              ; Освобождаем память стека

    mov eax, 500             ; Возвращаемое значение 500

end_function:
    mov esp, ebp             ; Восстанавливаем указатель стека
    pop ebp                  ; Восстанавливаем базовый указатель стека
    ret                      ; Завершение функции

Примечание
Обратите внимание, что программа из примера 2.4 содержит серьезнейшую ошибку. В комментариях сказано, что команда обязана иметь длину не более 4 байт, а вся строка вместе с аргументами не более 255. Если программа-клиент на другом конце сокета (сетевого соединения) соответствует RFC822 [RFC822], так оно и будет. Но если программа требованиям этого документа не соответствует, нас ждет беда: нам могут предложить более длинную команду и/или более длинную строку. Последствия, к которым это может привести, будут более подробно разбираться в главе 12.

Но вернемся к стековым кадрам.

Стековые кадры в системе команд SPARC

Микропроцессоры SPARC также не могут обойтись без стекового кадра. Во-первых, не всегда локальные переменные процедуры помещаются в восьми 32-разрядных локальных регистрах. Именно такая процедура приведена в примере 2.4. Во-вторых, нередко возникают ситуации, когда в качестве параметров необходимо передать по значению структуры, для которых 6 регистров-параметров может оказаться недостаточным. В-третьих, глубина регистрового файла ограничена, и при работе с рекурсивными или глубоко вложенными процедурами она может исчерпаться. В-четвертых, в многозадачной системе регистровый файл может использоваться несколькими задачами одновременно. Все эти проблемы решаются с помощью создания стекового кадра [www.sparc.com v9].

Для этой цели в системе SPARC используются регистры %sp (o6) и %fp (i6). Команда save %sp, -96 %sp выполняет следующие действия: она складывает первые два операнда, сдвигает стековый кадр и помещает результат сложения в третий операнд. Таким образом, старый %sp становится %fp, а результат сложения записывается в новый %sp.

Основную роль стековые кадры играют при обработке переполнений регистрового файла. Регистровый файл SPARC представляет собой кольцевой буфер, доступ к различным участкам которого контролируется с помощью привилегированных регистров CANSAVE и CANRESTORE. Окна, которые располагаются между значениями этих двух регистров, доступны текущей программе (см. рис. 2.11). На рисунке показано состояние регистрового файла, где текущий процесс может восстановить один стековый кадр (CANRESTORE=1) и сохранить три (CANSAVE=3). Регистр OTHERWIN показывает количество регистровых окон, занятых другим процессом. Всего в регистровом файле должно быть NWINDOWS окон, и чтобы было соблюдено равенство CANSAVE + CANRESTORE + OTHERWIN = NWINDOWS - 2.

Регистры CANSAVE и CANRESTORE (цит. по [www.sparc.com v9])

Если программа попытается сдвинуть свое регистровое окно за установленные границы (как показано на рис. 2.11, это может произойти после вызова четырех вложенных процедур или после возврата из двух процедур), будут генерироваться исключения "заполнение окна" (window fill) и "сброс окна" (window spill). При этом вызывается системная процедура, которая обрабатывает окна, сохраняя их содержимое в соответствующих стековых кадрах.

В многозадачной системе заполнение и сброс окна может произойти в любой момент времени. По этой причине каждая программа должна иметь отдельный стековый кадр для каждого используемого ею регистрового окна. Указатель на соответствующий стековый кадр всегда должен находиться в %sp. Одновременно с этим, создание стекового кадра и сдвиг регистрового окна должны выполняться одной и той же командой.