Что такое Relocatable и абсолютный машинный код?
изучая ассемблеры, я столкнулся с этими терминами.Идея, которую я получил, такова: в перемещаемом машинном коде код не зависит от статического местоположения ОЗУ. Ассемблер определяет потребности в ОЗУ для моей программы. Память может быть помещена туда, где компоновщик найдет для них место.
- Это правильная идея? Если да, то как это делает ассемблер?
и, каковы некоторые примеры абсолютного машинного кода?
5 ответов
многие / большинство наборов инструкций имеют относительную адресацию ПК, то есть берут адрес счетчика программы, который связан с адресом выполняемой вами инструкции, а затем добавляют смещение к этому и используют это для доступа к памяти или ветвлению или что-то в этом роде. это было бы то, что вы называете relocatable. Потому что независимо от того, где эта инструкция находится в адресном пространстве, то, к чему вы хотите перейти, относительно. Переместить весь блок кода и данных в другой блок адрес, и они все равно будут находиться на одинаковом расстоянии друг от друга, поэтому относительная адресация все равно будет работать. Если равно пропустить следующую инструкцию работает везде, где эти три инструкции (если пропустить, один пропускается и один после пропуска).
Абсолют использует абсолютные адреса, перейти к этому точному адресу, читать с этого точного адреса. Если равно, то ветвь до 0x1000.
ассемблер не делает этого компилятор и / или программист. Обычно, в конечном итоге скомпилированный код будет иметь абсолютную адресацию, в частности, если ваш код состоит из отдельных объектов, связанных вместе. Во время компиляции компилятор не может знать, где объект окажется, и невозможно узнать, где находятся внешние ссылки или как далеко, поэтому он не может обычно предполагать, что они будут достаточно близки для относительной адресации ПК (которая обычно имеет предел диапазона). Таким образом, компиляторы часто генерируют заполнитель для компоновщика для заполнения абсолютный адрес. Это зависит от операции и набора инструкций и некоторых других факторов относительно того, как эта проблема внешнего адреса решена. В конце концов, хотя на основе размера проекта, компоновщик в конечном итоге будет иметь некоторую абсолютную адресацию. Таким образом, не-default обычно является опцией командной строки для создания независимого от позиции кода-PIC, например, может быть чем-то, что поддерживает ваш компилятор. затем компилятор и компоновщик должны выполнить дополнительную работу, чтобы сделать эти элементы независимыми. - язык ассемблера программист должен делать все это сам, ассемблер обычно не участвует в этом, он просто создает машинный код для инструкций, которые вы говорите ему генерировать.
novectors.s:
.globl _start
_start:
b reset
reset:
mov sp,#0xD8000000
bl notmain
ldr r0,=notmain
blx r0
hang: b hang
.globl dummy
dummy:
bx lr
Здравствуйте.c
extern void dummy ( unsigned int );
int notmain ( void )
{
unsigned int ra;
for(ra=0;ra<1000;ra++) dummy(ra);
return(0);
}
memap (сценарий компоновщика) ПАМЯТЬ { ОЗУ: ORIGIN = 0xD6000000, длина = 0x4000 } РАЗДЕЛЫ { .текст : { (.текст) } > ram } Файл Makefile
ARMGNU = arm-none-eabi
COPS = -Wall -O2 -nostdlib -nostartfiles -ffreestanding
all : hello_world.bin
clean :
rm -f *.o
rm -f *.bin
rm -f *.elf
rm -f *.list
novectors.o : novectors.s
$(ARMGNU)-as novectors.s -o novectors.o
hello.o : hello.c
$(ARMGNU)-gcc $(COPS) -c hello.c -o hello.o
hello_world.bin : memmap novectors.o hello.o
$(ARMGNU)-ld novectors.o hello.o -T memmap -o hello_world.elf
$(ARMGNU)-objdump -D hello_world.elf > hello_world.list
$(ARMGNU)-objcopy hello_world.elf -O binary hello_world.bin
она.список (части, которые мы заботимся о)
Disassembly of section .text:
d6000000 <_start>:
d6000000: eaffffff b d6000004 <reset>
d6000004 <reset>:
d6000004: e3a0d336 mov sp, #-671088640 ; 0xd8000000
d6000008: eb000004 bl d6000020 <notmain>
d600000c: e59f0008 ldr r0, [pc, #8] ; d600001c <dummy+0x4>
d6000010: e12fff30 blx r0
d6000014 <hang>:
d6000014: eafffffe b d6000014 <hang>
d6000018 <dummy>:
d6000018: e12fff1e bx lr
d600001c: d6000020 strle r0, [r0], -r0, lsr #32
d6000020 <notmain>:
d6000020: e92d4010 push {r4, lr}
d6000024: e3a04000 mov r4, #0
d6000028: e1a00004 mov r0, r4
d600002c: e2844001 add r4, r4, #1
d6000030: ebfffff8 bl d6000018 <dummy>
d6000034: e3540ffa cmp r4, #1000 ; 0x3e8
d6000038: 1afffffa bne d6000028 <notmain+0x8>
d600003c: e3a00000 mov r0, #0
d6000040: e8bd4010 pop {r4, lr}
d6000044: e12fff1e bx lr
то, что я показываю здесь, - это смесь независимых от положения инструкций и зависимых от положения инструкций.
эти две инструкции, например, являются ярлыком для принудительного добавления ассемблера .расположение памяти стиля word, которое компоновщик должен заполнить для нас.
ldr r0,=notmain
blx r0
0xD600001c это место.
d600000c: e59f0008 ldr r0, [pc, #8] ; d600001c <dummy+0x4>
d6000010: e12fff30 blx r0
...
d600001c: d6000020 strle r0, [r0], -r0, lsr #32
и он заполнен адрес 0xD6000020, который является абсолютным адресом, так что код для работы notmain функция должна быть по адресу 0xD6000020 это не перемещаемый. но эта часть примера также демонстрирует некоторый независимый от позиции код, а также
ldr r0, [pc, #8]
является ли относительная адресация ПК я говорил о том, как этот набор инструкций работает во время выполнения ПК на две инструкции вперед или в основном в этом случае, если инструкция находится на 0xD600000c в память тогда ПК будет 0xD6000014 при выполнении, затем добавьте 8 к этому, как говорится в инструкции, и вы получите 0xD600001C. Но если мы переместили ту же инструкцию машинного кода на адрес 0x1000 и мы перемещаем все окружающие двоичные файлы, включая то, что он читает (0xD6000020). в основном сделайте это:
1000: e59f0008 ldr r0, [pc, #8]
1004: e12fff30 blx r0
...
1010: d6000020
и эти инструкции, этот машинный код все равно будет работать, его не нужно повторно собирать или повторно связывать. в 0xD6000020 sitll код должен быть на этом фиксированном адресе бит ldr pc и blx dont.
хотя дизассемблер показывает их с 0xd6... основанные адреса bl и bne также являются относительными ПК, которые вы можете узнать, просмотрев инструкцию set documentation
d6000030: ebfffff8 bl d6000018 <dummy>
d6000034: e3540ffa cmp r4, #1000 ; 0x3e8
d6000038: 1afffffa bne d6000028 <notmain+0x8>
0xD6000030 будет иметь ПК 0xD6000038 при выполнении и 0xD6000038-0xD6000018 = 0x20, что составляет 8 инструкций. А минус 8 в двойках - 0xFFF..FFFF8, вы можете увидеть большую часть этого машинного кода ebfffff8 - это ffff8, который является расширенным знаком и добавлен в счетчик программы, чтобы в основном сказать ветвь назад 8 instrucitons. То же самое касается ffffa в 1afffffa это означает, что если не равно, то ветвь назад 6 инструкций. Помните, что этот набор инструкций (arm) предполагает, что ПК-это две инструкции вперед, так что назад 6 означает вперед два, затем назад 6 или эффективно назад 4.
если убрать
d600000c: e59f0008 ldr r0, [pc, #8] ; d600001c <dummy+0x4>
d6000010: e12fff30 blx r0
тогда вся эта программа заканчивается позицией независимый, случайно, если хотите (я знал, что это произойдет), но не потому, что я сказал инструментам сделать это, а просто потому, что я сделал все близко и не использовал абсолютную адресацию.
наконец, когда вы говорите "везде, где линкер находит им" если вы заметили в моем линкер скрипт я сказать компоновщику, чтобы положить все, начиная с 0xD6000000, я не указать любые имена файлов или функции, так что если не сказано иначе этот компоновщик размещает предметы в порядке их указываются в командной строке. привет.код C второе после линкер разместил novectors.s-код, затем, где бы у компоновщика не было комнаты, сразу после этого, привет.код c начинается с 0xD6000020.
и простой способ увидеть, что является независимым от позиции, а что нет, не исследуя каждую инструкцию, было бы изменить сценарий компоновщика, чтобы поместить код на какой-то другой адрес.
MEMORY
{
ram : ORIGIN = 0x1000, LENGTH = 0x4000
}
SECTIONS
{
.text : { *(.text*) } > ram
}
и посмотрите, какой машинный код изменяется, если таковой имеется, и что doesnt.
00001000 <_start>:
1000: eaffffff b 1004 <reset>
00001004 <reset>:
1004: e3a0d336 mov sp, #-671088640 ; 0xd8000000
1008: eb000004 bl 1020 <notmain>
100c: e59f0008 ldr r0, [pc, #8] ; 101c <dummy+0x4>
1010: e12fff30 blx r0
00001014 <hang>:
1014: eafffffe b 1014 <hang>
00001018 <dummy>:
1018: e12fff1e bx lr
101c: 00001020 andeq r1, r0, r0, lsr #32
00001020 <notmain>:
1020: e92d4010 push {r4, lr}
1024: e3a04000 mov r4, #0
1028: e1a00004 mov r0, r4
102c: e2844001 add r4, r4, #1
1030: ebfffff8 bl 1018 <dummy>
1034: e3540ffa cmp r4, #1000 ; 0x3e8
1038: 1afffffa bne 1028 <notmain+0x8>
103c: e3a00000 mov r0, #0
1040: e8bd4010 pop {r4, lr}
1044: e12fff1e bx lr
все, что фактически содержит адрес внутри кода, имеет абсолютный адрес. Программы, которые не содержат адресов в коде (все делается с относительными адресами), могут быть запущены с любого адреса.
ассемблер этого не делает, это делает программист. В прошлом я делал кое-что из этого, для небольших вещей это обычно легко, как только вы выходите за пределы диапазона относительного прыжка, это становится довольно больно. IIRC единственные два подхода-скользить относительные прыжки в между подпрограммами или для добавления известного смещения к текущему адресу нажмите его, а затем верните. В старые времена был третий подход вычисления и записи его в код, но это больше не приемлемо. Прошло достаточно много времени, и я не могу поклясться, что нет других подходов.
IIRC единственный способ "вызвать" что-то без абсолютных адресов-это нажать адрес, который вы хотите вернуть, вычислить адрес, нажать его и вернуться.
обратите внимание, что в практика вы обычно используете гибридный подход. Ассемблер и компоновщик хранят информацию, необходимую для внесения корректировок, когда программа загружается в память, она модифицируется для запуска по любому адресу, по которому она была загружена. Фактический образ в памяти, таким образом, абсолютен, но файл на диске работает как относительный, но без всех головных болей, которые обычно вводят. (Обратите внимание, что тот же подход используется со всеми языками более высокого уровня, которые фактически производят собственный код.)
в принципе, "абсолютный" режим означает, что код и Ram переменные будут размещены именно там, где вы говорите ассемблер будет, а "мобильные" означает ассемблер строит код чанков и определяет ОЗУ потребностей, которые могут быть размещены там, где компоновщик находит места для них.
Я не уверен, что принятый ответ здесь обязательно правильный. Существует фундаментальное различие между перемещаемым кодом и тем, что считается независимым от позиции кодом.
теперь я кодирую сборку долгое время и на многих разных архитектурах, и я всегда думал о машинном коде как о трех конкретных ароматизаторы: -
- Позиционно-Независимый Код
- Мобильные-Код
- Абсолютная-Код
давайте сначала обсудим установки-независимая код. Это код, который при сборке имеет все свои инструкции относительно друг друга. Например, ветви указывают смещение от текущего указателя инструкции (или счетчика программы, как вы хотите его назвать). Код, независимый от позиции, будет состоять только из одного кода сегмент кода и его данные также содержатся в этом сегменте (или разделе). Существуют исключения для данных, внедренных в один и тот же сегмент, но эти преимущества обычно передаются операционной системой или загрузчиком.
Это очень полезный тип кода, потому что это означает, что операционной системе не нужно выполнять какие-либо операции после загрузки на нем, чтобы иметь возможность начать выполнение. Он будет просто работать везде, где он загружен в память. Конечно, этот тип у кода также есть свои проблемы, а именно такие вещи, как невозможность разделения кода и данных, которые могут быть пригодны для разных типов памяти и ограничений по размеру, прежде чем родственники начнут выходить из диапазона и т. д. назову лишь несколько.
Мобильные-Код во многом похож на позиционно-независимый код, но имеет очень тонкую разницу. Как следует из названия, этот тип кода перемещается в том коде, который может быть загружен в любом месте памяти, но обычно имеет перемещено или исправлено перед исполняемым файлом. Фактически, некоторые архитектуры, которые используют этот тип кода, встраивают такие вещи, как разделы" reloc", именно для этой цели исправления перемещаемых частей кода. Недостатком этого типа кода является то, что как только он перемещается и фиксируется, он почти становится абсолютным по своей природе и фиксируется по своему адресу.
Что дает relocatable коду свое главное преимущество и причина почему это самый превалирующий код вокруг что он позволяет коду быть легко разбивается на секции. Каждый раздел может быть загружен в любом месте памяти в соответствии с его требованиями, а затем во время перемещения любой код, который ссылается на другой раздел, может быть исправлен с помощью таблицы перемещения, и, таким образом, разделы могут быть связаны вместе красиво. Сам код обычно относителен (как и в архитектуре x86), но это не обязательно, так как все, что может быть вне диапазона, может быть собрано как перемещаемая инструкция, состоящая из смещения, добавленного к его нагрузке адрес. Это также означает, что ограничения, налагаемые относительной адресацией, больше не являются проблемой.
окончательный тип кода -Абсолютная-Код. Этот код, который собран для работы по одному конкретному адресу и будет только работа при загрузке по этому конкретному адресу. Инструкции ветви и перехода все содержат фиксированный точный (абсолютный) адрес. Это тип кода, обычно встречающийся во встроенных системах, благодаря которому можно гарантировать, что часть кода будет загружается по этому конкретному адресу, так как это единственное, что загружается там. На современном компьютере такой абсолютный код не будет работать, потому что код должен быть загружен везде, где есть свободная память, и нет никакой гарантии, что определенный диапазон памяти будет доступен. Однако у абсолютного кода есть свои преимущества, главным образом в том, что он обычно является самым быстрым исполнением, но это может зависеть от платформы.
"relocatable" означает, что ассемблер создает фрагменты кода и определяет потребности в ОЗУ, которые могут быть размещены там, где компоновщик находит для них место.