внутреннее перемещение не исправлено

Недавно я начал программировать на ассемблере для ядер рук. Мои первые небольшие демонстрации, только с разделом .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

by

 .org .

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

Может быть, кто-то из вас, знатоков, поможет мне обрести мудрость.


person user1146332    schedule 10.04.2012    source источник


Ответы (3)


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

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

person tangrs    schedule 16.04.2012
comment
Спасибо за объяснение и пример. Это или что-то подобное должно быть в разделе arm документации binutils. Я запомню, что компоновщик выполняет крупномасштабное перемещение, а кодер — мелкомасштабное перемещение. Если у меня будет дополнительное время, я изучу дизассемблированные c-программы и надеюсь получить больше информации... - person user1146332; 16.04.2012

Я не хочу, чтобы это был исключительный ответ, но он предлагает больше информации, а также предлагает неудобное решение для использования только одной инструкции 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 (r-x), вы не можете использовать для него str по умолчанию. Это очень небольшой барьер, просто используйте опцию -N для ld, и ваш раздел .text теперь будет rwx. Я уверен, что последнее предложение разозлит богов stackoverflow, иди ко мне;)

person XlogicX    schedule 15.11.2018

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

В коде, который должен компилироваться нормально, это часто происходит, когда проект компилируется в другой цепочке инструментов с другим расширением для файлов ассемблера, так что директивы .include могут включать неправильный файл (например, file.asm.s вместо file.asm), что приводит к отсутствию определяет.

person Dmitry Grigoryev    schedule 15.09.2019