Почему инструкции ADR с прямой ссылкой собираются с четными смещениями в коде Thumb?

Для bx функции Thumb необходимо установить младший бит адреса. GNU as документация заявляет, как это работает, когда адрес генерируется из псевдо-инструкция adr:

adr ‹register› ‹label›

Эта инструкция загрузит адрес метки в указанный регистр. [...]

Если метка является символом функции большого пальца, и взаимодействие большого пальца было разрешено с помощью опции -mthumb-interwork, то будет установлен нижний бит значения, хранящегося в регистре. Это позволяет следующей последовательности работать должным образом:

adr r0, thumb_function

blx r0

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

Например, сборка и компоновка:

.syntax unified
.thumb

.align 2
table:
    .4byte f1
    .4byte f2
    .4byte f3

.align 2
.type f1, %function
.thumb_func
f1:
    adr r1, f1
    adr r2, f2
    adr r3, f3
    bx r1

.align 2
.type f2, %function
.thumb_func
f2:
    adr r1, f1
    adr r2, f2
    adr r3, f3
    bx r2

.align 2
.type f3, %function
.thumb_func
f3:
    adr r1, f1
    adr r2, f2
    adr r3, f3
    bx r3

С участием:

arm-none-eabi-as adr_test.s -mthumb -mthumb-interwork -o adr_test.o
arm-none-eabi-ld adr_test.o

И проверяя с arm-none-eabi-objdump -D a.out, я получаю:

00008000 <table>:
    8000:   0000800d    .word   0x0000800d
    8004:   00008019    .word   0x00008019
    8008:   00008025    .word   0x00008025

0000800c <f1>:
    800c:   f2af 0103   subw    r1, pc, #3
    8010:   a201        add r2, pc, #4  ; (adr r2, 8018 <f2>)
    8012:   a304        add r3, pc, #16 ; (adr r3, 8024 <f3>)
    8014:   4708        bx  r1
    8016:   46c0        nop         ; (mov r8, r8)

00008018 <f2>:
    8018:   f2af 010f   subw    r1, pc, #15
    801c:   f2af 0207   subw    r2, pc, #7
    8020:   a300        add r3, pc, #0  ; (adr r3, 8024 <f3>)
    8022:   4710        bx  r2

00008024 <f3>:
    8024:   f2af 011b   subw    r1, pc, #27
    8028:   f2af 0213   subw    r2, pc, #19
    802c:   f2af 030b   subw    r3, pc, #11
    8030:   4718        bx  r3
    8032:   46c0        nop         ; (mov r8, r8)

Следует отметить несколько моментов:

  1. В table, как и ожидалось, абсолютные адреса f1, f2 и f3 нечетные. Итак, очевидно, что ассемблер и компоновщик знают, что эти три функции должны быть Thumb.
  2. Для обратных ссылок, где псевдо-инструкция adr собирается до subw, смещение, как и ожидалось, нечетное.
  3. Но для прямых ссылок, где псевдо-инструкция adr собирается в add, смещение будет четным.

Что мне не хватает?


person Maxpm    schedule 29.11.2019    source источник
comment
Вы пробовали смотреть на вывод GCC для некоторого кода C (например, для статического или локального массива указателей на функции), чтобы увидеть, есть ли дополнительные директивы, которые он использует, чтобы убедиться, что ассемблер знает, что это символ функции? Или, если окажется, что в этом случае asm все-таки не получает нечетных адресов?   -  person Peter Cordes    schedule 29.11.2019
comment
Помогает ли установка .type f3, %function ранее?   -  person Peter Cordes    schedule 29.11.2019
comment
Зарегистрируйте это с помощью binutils / gnu folks. Это может быть проблема с документацией в документации по руке. сейчас тестирую.   -  person old_timer    schedule 30.11.2019
comment
Кодировка T1 действительно работает, как описано в расширенном знаке immed8: '00 '. Зарегистрируйте это с помощью gnu binutils, ребята. Я пробовал его с 2.32, и он дает версию T1 с -mthumb-interwork и без нее. Даже изменение инструкции на adr.w создает вариант thumb2, который не работает.   -  person old_timer    schedule 30.11.2019
comment
adr.w создает вариант T3, как описано в документации armv7-m. Это позволяет использовать любое смещение от 0 до 4095, не ограничиваясь степенями четырех, как в кодировке T1.   -  person old_timer    schedule 30.11.2019
comment
без единого синтаксиса он генерирует для меня кодировку t1, а не t3.   -  person old_timer    schedule 30.11.2019
comment
Я никогда не использую такие инструкции, как adr на любом языке ассемблера, если я хочу сделать что-то подобное в руке, я бы сказал ldr r0, = label, и он отлично работает вперед или назад, иногда он сжигает слово, большая часть время. Теперь я специально запомню, чтобы не использовать его в руке.   -  person old_timer    schedule 30.11.2019
comment
извините, ноль расширен immed8: знак "00" не расширен.   -  person old_timer    schedule 30.11.2019
comment
@PeterCordes, компиляторы C не генерируют инструкцию adr, они решают это другими способами, либо словом, которое заполняется компоновщиком и используется с загрузкой, относящейся к компьютеру, либо батутом (у gnu ld действительно есть проблема с батутами, поэтому не полагайтесь на их). Или, если вам просто нужен адрес в коде C снова, слово откладывается и исправляется компоновщиком, и используется загрузка, связанная с компьютером. Есть ошибки / проблемы с компилятором C при использовании указателей на функции, как это было замечено здесь, в SO.   -  person old_timer    schedule 30.11.2019
comment
Что касается самомодифицирующегося кода, который не является хорошо написанным / скомпилированным кодом.   -  person old_timer    schedule 30.11.2019
comment
ldr pc, = используется gas в качестве псевдо-инструкции в том смысле, что он может быть реализован либо как mov, либо как значение, помещенное в соседний пул, тогда используется относительный ldr pc. Кажется, недостаточно умен, чтобы добавить версию инструкции adr.w в этом случае. Возможно, отправьте это как запрос функции. Он достаточно умен, чтобы распознавать метки, отмеченные как функции или не функции, и выдавать правильное значение.   -  person old_timer    schedule 30.11.2019
comment
также должна быть запятая между adr и label, поскольку газ жалуется, если у вас его там нет, поэтому есть ошибка документации по этому поводу.   -  person old_timer    schedule 30.11.2019
comment
для поддержки последовательности adr / bx, как описано в документации, для cortex-m0 необходимо будет рассматривать adr как псевдоинструкцию и генерировать две инструкции, для cortex-m3 - m7 он мог бы сделать это с помощью двух 16-битных инструкций большого пальца или одиночное удлинение большого пальца 2. Пока ничего из этого не происходит.   -  person old_timer    schedule 30.11.2019
comment
вы отправили это в файл GNU? что они сказали? вы можете разместить ссылку на отчет об ошибке?   -  person old_timer    schedule 13.12.2019
comment
@old_timer У меня есть. По состоянию на 13 декабря 2019 г. ответа нет. sourceware.org/bugzilla/show_bug.cgi?id = 25235   -  person Maxpm    schedule 13.12.2019


Ответы (2)


Вам не хватает этой строки из документации ARM для Псевдо-инструкция ADR:

Если вы используете ADR для генерации цели для инструкции BX или BLX, вы обязаны установить бит Thumb (бит 0) адреса, если цель содержит инструкции Thumb.

Инструкции ADR с прямым обращением используют 16-битную форму Thumb "ADD Rd, pc, #imm" инструкции ADD. Непосредственное значение этой инструкции находится в диапазоне 0-1020 и должно быть выровнено по словам (т. Е. Закодировано 8-битным полем и умножено на 4.) Используемое значение PC также имеет младшие два бита, установленные в 0, поэтому он не может генерировать нечетный адрес.

Принуждение ассемблера всегда использовать 32-битную инструкцию Thumb с ADR.W должно привести к тому, что он всегда будет генерировать нечетный адрес при использовании метки функции, но я не знаю, можете ли вы на это положиться. Вероятно, было бы лучше просто явно установить младший бит.

person Ross Ridge    schedule 29.11.2019
comment
Я видел эту строку из документации ARM, но я прочитал свой отрывок из документации GNU как заменяющий это - тем более, что они используют adr r0, thumb_function; blx r0 как пример того, что работает, как ожидалось. Эти подробности о кодировке инструкций интересны. Если на самом деле архитектурно невозможно, чтобы узкая инструкция ADR работала должным образом в Thumb, тогда документация должна предупредить об этом. Я постараюсь внести это в список рассылки. - person Maxpm; 30.11.2019
comment
@Maxpm К сожалению, документация по GNU Assembler не очень надежный источник информации о том, как на самом деле работает GNU Assembler. Я предполагаю, что в прошлом ассемблер действительно работал в соответствии с документацией и всегда использовал 32-битную кодировку, способную генерировать нечетные адреса, но в какой-то момент был исправлен для соответствия официальному поведению ARM без обновления документации. - person Ross Ridge; 30.11.2019
comment
Звучит правдоподобно. Я зарегистрировал это в Bugzilla, так что, думаю, мы увидим. - person Maxpm; 30.11.2019
comment
gas, похоже, не работает для ARM, T16 и T32, код проверяет символ до того, как символ был найден / определен в коде, и в результате не вносит корректировку. Итак, кто-то пытался это сделать, но не завершил задание. уже определенные метки, похоже, работают, но вперед, еще не определенные, нет. - person old_timer; 30.12.2019
comment
Мой опыт работы с ошибками против людей GNU не настолько велик, что они тоже не хотят на это смотреть. У вас нет опыта, чтобы понять это и просто закрыть как не ошибку. Или, что еще лучше, годы спустя, когда кто-то другой нашел это, мое, поданное много лет назад, было помечено как дубликат поданного позже. Понимание того, что GNU - это большая группа людей, использующих множество инструментов, и вы получаете то, что получаете в определенный день. - person old_timer; 30.12.2019
comment
У меня дежавю, интересно, была ли эта ошибка уже найдена и зарегистрирована и не устранена ... - person old_timer; 30.12.2019

Возвращаясь к этому вопросу. Ошибка действительно такая простая:

  if (inst.relocs[0].exp.X_op == O_symbol
      && inst.relocs[0].exp.X_add_symbol != NULL
      && S_IS_DEFINED (inst.relocs[0].exp.X_add_symbol)
      && THUMB_IS_FUNC (inst.relocs[0].exp.X_add_symbol))
    inst.relocs[0].exp.X_add_number += 1;

в функции do_t_adr ().

S_IS_DEFINED проверяет, определен ли символ, при выполнении прямой ссылки в этот момент времени символ не определен, поэтому эта строка не проходит, он не добавляет один, что очень мешает чистоте, он должен ORR один , но что угодно. Для обратной ссылки обозначен символ, поэтому настройка выполняется. (Естественно, THUMB_IS_FUNC не будет работать без определенного символа)

ADR преобразуется в BFD_RELOC_ARM_THUMB_ADD. Что приводит нас сюда

case BFD_RELOC_ARM_THUMB_ADD:
  /* This is a complicated relocation, since we use it for all of
 the following immediate relocations:

    3bit ADD/SUB
    8bit ADD/SUB
    9bit ADD/SUB SP word-aligned
   10bit ADD PC/SP word-aligned

 The type of instruction being processed is encoded in the
 instruction field:

   0x8000  SUB
   0x00F0  Rd
   0x000F  Rs
  */

и внутри этого здесь

else if (rs == REG_PC || rs == REG_SP)
  {
    /* PR gas/18541.  If the addition is for a defined symbol
       within range of an ADR instruction then accept it.  */

И тот код, который происходит на более позднем проходе (после того, как символ был определен и может быть найден), не исправляет немедленное / смещение.

Я нахожу еще более тревожным / ошибочным то, что он не может справиться с этим без унифицированного .syntax.

.thumb
.thumb_func
zero:
    adr r0,zero

даже с унифицированным .syntax они не закончили внедрение ADR для T16. Просто поместите туда ошибку и объявите, что все готово. (Конечно, может быть реализовано, например, в T16 add rx, pc, # 0, sub rx, # offset.)

Даже если бы они исправили это, я бы избежал инструкции ADR. Но ясно, что они не удосужились завершить реализацию этой псевдо-инструкции.

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

  if (support_interwork
      && inst.relocs[0].exp.X_op == O_symbol
      && inst.relocs[0].exp.X_add_symbol != NULL
      && S_IS_DEFINED (inst.relocs[0].exp.X_add_symbol)
      && THUMB_IS_FUNC (inst.relocs[0].exp.X_add_symbol))
    inst.relocs[0].exp.X_add_number |= 1;

Обратите внимание, что ORR не ДОБАВЛЯЕТ одного, лучшего / другого автора, но не совсем продумывал это решение.

Если я удалю проверки S_IS_DEFINED и THUMB_IS_FUNC

.arm
zero:
    adr r0,two
.thumb
.thumb_func
two:
    nop

идет от

00000000 <zero>:
   0:   e24f0004    sub r0, pc, #4

00000004 <two>:
   4:   46c0        nop         ; (mov r8, r8)
   6:   46c0        nop         ; (mov r8, r8)

to

00000000 <zero>:
   0:   e24f0003    sub r0, pc, #3

00000004 <two>:
   4:   46c0        nop         ; (mov r8, r8)
   6:   46c0        nop         ; (mov r8, r8)

так же

.syntax unified

.thumb
    adr r0,two
    nop
    nop
.thumb_func
two:
    nop

дает

00000000 <two-0x8>:
   0:   f20f 0005   addw    r0, pc, #5
   4:   46c0        nop         ; (mov r8, r8)
   6:   46c0        nop         ; (mov r8, r8)

00000008 <two>:
   8:   46c0        nop         ; (mov r8, r8)

Обратите внимание, что это можно было бы легко реализовать с помощью инструкций T16 (использует 4 байта, как и решение T32), но это, как уже упоминалось, еще одна ошибка:

.syntax unified
.cpu cortex-m0
.thumb
    adr r0,two
    nop
    nop
.thumb_func
two:
    nop

/path/so.s: Assembler messages:
/path/so.s:5: Error: invalid immediate for address calculation (value = 0x00000003)

(и эта ошибка находится в том же разделе кода, где есть указанная вами ошибка)

Было бы интересно посмотреть, что, во-первых, говорится в документации для других ассемблеров в отношении ADR и thumb, а во-вторых, если они действительно реализуют это в соответствии с этой документацией и / или выручают с ошибкой или предупреждением.

person old_timer    schedule 30.12.2019
comment
В документации ARM говорится, что для прямых ссылок ADR без .W всегда генерирует 16-битную инструкцию в коде Thumb, даже если это приводит к сбою адреса, который может быть сгенерирован в 32-битной инструкции Thumb-2 ADD, поэтому ваша ошибка fix ломает это. - person Ross Ridge; 30.12.2019
comment
Это обратная ссылка, которая не генерирует T16 в любом случае для GAS, а не прямая, прямая генерирует T16 без .w, но немедленное неверно. - person old_timer; 31.12.2019
comment
размер инструкции здесь не является проблемой, эта псевдо-инструкция может быть правильно построена для всех вариантов большого пальца, используя инструкции T32 или T16, может технически поддерживать любой диапазон, если они захотят. Документация ARM требует доработки в этой конкретной области, чтобы понять, в чем заключалась их цель, вместо того, чтобы выбирать ассемблер. К сожалению, мы вынуждены полагаться на ассемблер или, что еще лучше, просто не использовать ADR, без которого легко жить. - person old_timer; 31.12.2019
comment
Нет, это правильно согласно документации ARM. Документация ARM не требует, чтобы он генерировал нечетный адрес, но требует, чтобы была сгенерирована одна 16-битная инструкция для прямых ссылок. - person Ross Ridge; 31.12.2019
comment
Версия документа @RossRidge и номер абзаца, пожалуйста. - person old_timer; 31.12.2019
comment
Я связал это в своем ответе и процитировал слово в слово. - person Ross Ridge; 31.12.2019
comment
Это ассемблер руки, а не архитектура, поэтому, как я уже отмечал, ассемблер пытается решить эту проблему нечеткости в документации по архитектуре. У инструментов «рука» один способ, у газа - другой. Такова природа ассемблера и псевдоинструкций. Мы вне всякого сомнения знаем, что GAS не соответствует требованиям RealView, газ предшествует RealView на много лет - person old_timer; 31.12.2019
comment
Я думаю, что газ добавил руку во время рекламы, тогда что это было, а затем реальный просмотр, когда они приобрели allant. Таким образом, у них было три сборщика, в течение срока службы газовой руки - person old_timer; 31.12.2019
comment
язык ассемблера определяется инструментом, ассемблер, а не архитектура, машинный код должен соответствовать архитектуре, если архитектура расплывчата / неполна, то ассемблер должен заполнить пробел - person old_timer; 31.12.2019
comment
Похоже, эта ошибка все еще открыта. У меня нет опыта, чтобы должным образом проверить ваш комментарий по этому поводу, но похоже, что краткий ответ на мой вопрос -, что это ошибка газа, поскольку у них есть распознал его как таковой на своем трекере. - person Maxpm; 09.01.2021
comment
Я рад принять этот ответ, если вы хотите добавить к нему вступление, например: Архитектура ARM оставляет это неоднозначным, но если вы используете ассемблер GNU, в его документации конкретно говорится, что он должен работать. Люди, занимающиеся сборкой GNU, признали тот факт, что это не работает как ошибку. Далее следует мой комментарий относительно того, что, по моему мнению, является подробным описанием ошибки. - person Maxpm; 09.01.2021