Внутреннее перемещение не исправлено
Я недавно начал программировать на ассемблере для ядер. Мои первые маленькие демонстрации, только с разделом.text, прошли без проблем.
В качестве логического расширения я хотел структурировать ассемблерный код в обычные разделы: .text, .data, .bss .
Итак, я написал следующую простую программу:
.globl _start
.section .text
_start:
b main
b .
b .
b .
b .
b .
b .
b .
main:
ldr r0, x
nop
.section .data
x: .word 0xf0f0f0f0
.end
Но
/opt/arm/bin/arm-as -ggdb -mcpu=arm7tdmi demo.s -o demo.o
выходит с ошибкой
prog.s: Assembler messages:
prog.s:17: Error: internal_relocation (type: OFFSET_IMM) not fixed up
make: *** [prog.o] Error 1
Я понятия не имею, почему ассемблер жалуется на перемещение, потому что я думал, что это задача компоновщика. Я мог бы предположить, что должен сказать ассемблеру, что мой раздел.data не находится в последней позиции памяти на этапе сборки, но я не могу найти ничего связанного.
Хотя я нашел способ правильно собрать код, заменив
.section .data
от
.org .
это не удовлетворительное решение. Особенно ввиду того, что в газовой документации подчеркнут смысл этого раздела.
Может быть, кто-то из вас, экспертов, может помочь мне обрести мудрость
2 ответа
Кажется, единственный способ сделать это - получить адрес переменной и загрузить значение с этого адреса.
ldr r1,=x ; get address of x
ldr r0,[r1] ; load from that address
В некотором смысле, это также имеет смысл. В конце концов, что, если адрес x (после связывания) слишком далеко для относительного доступа к ПК? Поскольку компилятор (который не выполняет связывание) не знает, насколько далеко может быть раздел данных от текстового раздела, он откажется компилировать этот код на тот случай, если он недоступен.
Используя этот косвенный способ доступа к переменной, гарантируется, что переменная будет достижимой (или, по крайней мере, компилятор может быть уверен, достижима ли переменная или нет).
Код адаптирован с http://www.zap.org.au/elec2041-cdrom/examples/intro/pseudo.s
Я не намерен, чтобы это был исключительный ответ, но он действительно дает некоторое понимание, а также предлагает неудобное решение для использования только одной инструкции ldr.
При использовании этого двухэтапного метода ldr ассемблер фактически добавляет еще 4 байта данных после вашего кода! Даже в разделе.text эти 4 байта являются реальным адресом вашей переменной.data. Первая инструкция ldr затем фактически указывает на этот адрес, затем вы используете следующий ldr для использования реального адреса. Как обсуждал tangrs, этот двойной указатель может быть способом обеспечения того, чтобы ваши переменные / константы были доступны, особенно с разделом.data, находящимся дальше (64 КБ в моем последнем запуске).
Глядя на некоторый пример кода правильного способа сделать это:
.text
.global _start
_start:
ldr r0, =x
ldr r0, [r0]
mov r7, #1
swi #0
nop
.data
x: .word 0xf0f0f0f0
Ассемблер фактически производит это:
00010074 <_start>:
10074: e59f000c ldr r0, [pc, #12] ; 10088 <_start+0x14>
10078: e5900000 ldr r0, [r0]
1007c: e3a07001 mov r7, #1
10080: ef000000 svc 0x00000000
10084: e1a00000 nop ; (mov r0, r0)
10088: 0002008c andeq r0, r2, ip, lsl #1
Disassembly of section .data:
0002008c <x>:
2008c: f0f0f0f0 ; <UNDEFINED> instruction: 0xf0f0f0f0
Первый ldr указывает 12 байт после счетчика программы (считается текущая инструкция + еще восемь). Это указывает на адрес 0x10088 (как отмечено objdump), который указывает на инструкцию andeq (не настоящая инструкция в этом контексте). Это действительно адрес 0x0002008c, который указывает на наш правильный адрес в разделе.data для нашей переменной x. Теперь, когда у нас есть адрес нашей переменной в r0, мы можем использовать ldr по этому адресу, чтобы получить реальное значение. Следует отметить, что хотя второй операнд в исходном файле для обеих этих инструкций ldr выглядит очень по-разному, машинное кодирование предназначено для одной и той же кодировки ldr; они оба являются LDR Immediate (хотя первый вариант ldr также считается LDR Literal, это просто LDR Immediate с 'Rn', жестко закодированным в '1111', который в любом случае является просто регистром ПК).
Имея все это в виду, хотя это неудобно, мы можем найти способ просто использовать форму LDR Immediate(Literal) один раз. Все, что нам нужно сделать, это убедиться, что мы получили правильное немедленное значение (смещение), соответствующее нашим реальным данным. Проще сделать, чем сказать:
.text
.global _start
_start:
ldr r0, [pc, #8]
mov r7, #1
swi #0
nop
x: .word 0xf0f0f0f0
Помимо необходимости использовать одну инструкцию LDR для достижения того же результата, в этой версии источника есть еще одно тонкое отличие: раздела.data нет. Это может быть сделано с разделом данных, но это поместит наши данные в гораздо более высокий адрес, делая наше смещение настолько большим, что нам, возможно, придется использовать дополнительные инструкции, чтобы получить правильное смещение. Другое примечание связано с тем, что он находится в разделе.text (rx), по умолчанию вы не можете использовать для него str. Это очень маленький барьер, просто используйте опцию -N для ld, и ваш раздел.text теперь rwx. Я уверен, что последнее предложение рассердит богов переполнения стека, иди ко мне;)
Это не относится к коду в вопросе, но в целом эта ошибка часто означает, что вы забыли определить константу, которую загружаете с помощью ldr
инструкция.
В коде, который должен хорошо компилироваться, это часто происходит, когда проект компилируется в другой инструментальной цепочке с другим расширением для файлов ассемблера, так что .include
директивы могут включать неправильный файл (например, file.asm.s
вместо того file.asm
), что приводит к отсутствию определений.