Однако ассемблер определяет эти адреса во время компиляции или эти адреса определяются, когда программа загружается в память операционной системой?
Это зависит от ОС к ОС и даже зависит от типа переменной:
10 лет назад большинство маршрутизаторов WLAN использовали процессор MIPS с Linux .
В Linux существует тип кода, который называется « позиционно-зависимый » код. Это означает, что код всегда будет загружаться по одному и тому же адресу в памяти. В этом случае один инструмент цепочки инструментов компилятора (компилятор, ассемблер, компоновщик, ...) (*) вычисляет адрес переменной и пишет код «правильно». Пример: если переменная расположена по адресу 0x100024, код будет выглядеть так:
lui t2, 0x10
lw t3, 0x24(t2)
Тогда есть код " независимый от позиции ". Этот тип кода может быть загружен на любой адрес. Предположим, что переменная хранится по адресу X, а метка «nextAddress» расположена по адресу Y. Адреса X и Y могут изменяться, однако разница (X-Y) является фиксированной. Допустим, X-Y = 0x100024:
bgezal zero, nextAddress
nop
nextAddress:
; Now the address "nextAddress" is in register "ra"
lui t2, 0x10
addu t2, t2, ra
; The next instruction will access address X+0x100024
lw t3, 0x24(t2)
Конечно, разница (X-Y) вычисляется инструментом цепочки инструментов компилятора во время компиляции.
Были также карманные устройства (аналогичные смартфонам), работающие Windows CE на процессорах MIPS.
В этой операционной системе используется так называемая «базовая таблица перемещений»: файл .EXE или .DLL содержит «требуемый» адрес. Код в файле выглядит как «зависимый от позиции» код, описанный выше. Когда файл загружается по «желаемому» адресу, никаких действий предпринимать не нужно.
«Таблица базовых перемещений » содержит информацию об адресах в файле. Например, список всех lui
инструкций в файле. Допустим, «желаемый» адрес для загрузки файла - 0x80000, но Windows должна загрузить файл по адресу 0xA0000. Это означает, что адрес всех переменных в файле изменяется на 0x20000. Windows обработает информацию в «базовой таблице перемещений» и добавит 0x20 к последним 16 битам каждой lui
инструкции в файле, поэтому lui t2, 0x10
становится lui t2, 0x30
в примере для "кода, зависимого от позиции" выше.
Таким образом, адрес переменной сначала вычисляется цепочкой инструментов компилятора, но позже он изменяется операционной системой.
Следующей возможностью являются " глобальные таблицы смещений " (Linux) и " таблицы импорта " (Windows). Обычно они используются для доступа к переменным в файлах DLL.
Таблица глобальных смещений содержит адреса фактических переменных. В языке программирования C это можно объяснить следующим образом:
Фактический код C:
static int staticVariable;
...
staticVariable = 1234;
Что в действительности делает полученный ассемблерный код:
static int staticVariable;
static int * staticVariable_got = &staticVariable;
...
(*staticVariable_got) = 1234;
Код сборки:
lui t2, 0x10
lw t3, 0x24(t2)
; Now t3 contains the address of the variable
sw t4, (t3)
В этом случае цепочка инструментов компилятора вычислит адрес вспомогательной «переменной» (staticVariable_got
). Однако адрес фактической переменной (staticVariable
) известен только ОС. ОС записывает этот адрес в «переменную» staticVariable_got
.
(*) Обратите внимание, что это не обязательно должен быть компоновщик; есть также компиляторы, которые напрямую пишут исполняемые файлы (без необходимости ассемблера или компоновщика), и есть компоновщики, чьи выходные данные обрабатываются другим инструментом.