Интерпретатор ASM: Как хранятся локальные переменные? - PullRequest
1 голос
/ 25 декабря 2011

для домашней работы мне нужно написать очень маленький виртуальный 16-битный ассемблер-интерпретатор на C #. Он моделирует ОЗУ с помощью байтового массива (64 КБ) и регистров с переменными (A, B, C, ...). Теперь мне нужен способ сохранения локальных переменных, Google говорит, что они размещены в стеке.

Но что мне неясно, когда они размещаются в стеке (нажатием ...), как интерпретатор получает к ним доступ, когда они используются позже?

см. Следующие 2 строки:

pi INT 3
mov A, pi

В первой строке pi выделяется в стеке, во второй строке используется pi, но как интерпретатору узнать, где находится pi в стеке для доступа к его данным? (мой стек тоже является байтовым массивом с 2 вспомогательными функциями (push, pop), также есть указатель на вершину стека)

Ответы [ 4 ]

1 голос
/ 25 декабря 2011

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

Если вы реализуете эмулятор ЦП или эмулятор / интерпретатор команд ЦП, вас не волнует большая часть переменных. Что вас волнует, так это инструкции процессора, которые управляют регистрами процессора и памятью, потому что ваша программа выражается в терминах инструкций процессора. Они (инструкции) должны отслеживать все локальные переменные, хранящиеся в стеке, то есть их расположение относительно текущего значения указателя стека.

Например, если вы рассматриваете простую подпрограмму, которая добавляет в стек два 16-разрядных целочисленных значения, она может выглядеть примерно так, например, 16-битная сборка x86:

myadd:
    push bp ; we'll be accessing stack through bp (can't do that through sp because there's no sp-relative memory addressing in 16-bit mode), so, let's save bp first
    mov bp, sp ; bp is equal to the stack pointer
    mov ax, dword ptr [bp + 4] ; load ax with 1st parameter stored at bp+4 (sp+4)
    add ax, dword ptr [bp + 6] ; add to ax 2nd parameter stored at bp+6 (sp+6)
    pop bp ; restore bp
    ret ; near return to the caller at address stored at sp (address after call myadd), the result/sum is in ax

И абонент может выглядеть так:

    push word 2 ; prepare/store 2nd parameter on the stack
    push word 1 ; prepare/store 1st parameter on the stack
    call myadd ; near call, pushes address of next instruction (add), jumps to myadd
    add sp, 4 ; remove myadd's parameters (1 and 2) from the stack
    ; ax should now contain 3
1 голос
/ 25 декабря 2011

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

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

mov bp, sp ;copy stack pointer
sub sp, 4 ;make room for two integer variables

Доступ к локальным переменным осуществляется с использованием копии указателя стека:

mov A, [bp-2] ;get first integer
mov B, [bp] ;get second integer

Когда вы выходите из подпрограммы, вы восстанавливаете указатель стека, чтобы освободить локальные переменные:

mov sp, bp ;restore stack
ret ;exit from subroutine

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

.data
pi int 3 ;declare a label and allocate room for an int in the program
.code
mov A, pi ;use the address of the label to access the int
0 голосов
/ 25 декабря 2011

Ответ: это зависит. Вы, как разработчик языка, должны определить, каковы visibility (если имя переменной определено, в какой части исходного кода доступно имя?) И hiding ( если есть другой объект с таким же именем, определенный в области видимости другого объекта, какое имя выигрывает?) правила переменных. У разных языков разные правила, просто сравните Javascript и C ++.

Итак, я бы сделал это так. (1) Ввести понятие namespace : список имен, видимых в определенной точке исходного файла. (Обратите внимание, что это не то же самое, что понятие пространства имен C ++.) Пространство имен должно иметь возможность разрешать имя в некотором соответствующем объекте. (2) Реализуйте правила для изменения пространств имен, когда ваш интерпретатор переходит от одной процедуры к другой, из одного файла в другой, из одного блока в другой, видит объявление или конец блока и т. Д.

Эти шаги в основном действительны для большинства языков, а не только для ассемблера.

(Я думаю, что ссылка Google на «распределение в стеке» относится к идее обработки каждой подпрограммы в отдельной подпрограмме и переопределения там пространства имен локально, следовательно, «в стеке», поэтому оно будет автоматически выталкиваться при выполнении процедуры отделка.)

0 голосов
/ 25 декабря 2011

'Google говорит, что они размещены в стеке'

это то, как это реализовано на реальных компьютерах, но это еще не все.

Если вы хотите использовать виртуального переводчика, вам нужно использовать структуру данных под названием «Хеш-таблица».

Ну, это домашний вопрос. Так что прямого ответа нет: P Но следующий код объяснит, как использовать хэш-таблицу. Сохраните имена и значения переменных в хэш-таблицах.

using System;
using System.Collections;

class Program
{
    static Hashtable GetHashtable()
    {
    // Create and return new Hashtable.
    Hashtable hashtable = new Hashtable();
    hashtable.Add("Area", 1000);
    hashtable.Add("Perimeter", 55);
    hashtable.Add("Mortgage", 540);
    return hashtable;
    }

    static void Main()
    {
    Hashtable hashtable = GetHashtable();

    // See if the Hashtable contains this key.
    Console.WriteLine(hashtable.ContainsKey("Perimeter"));

    // Test the Contains method. It works the same way.
    Console.WriteLine(hashtable.Contains("Area"));

    // Get value of Area with indexer.
    int value = (int)hashtable["Area"];

    // Write the value of Area.
    Console.WriteLine(value);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...