Меня смущает поведение ассемблера RISC-V (rv64). Обратите внимание, что этот вопрос касается того, как должна вести себя удобочитаемая сборка, а не того, как ведут себя машинные инструкции (это ясно).
Кажется, что beq rs1, rs2, x
устанавливает PC ← x
, если R[rs1] = R[rs2]
, а не PC ← PC + x
. Это отлично. Однако, похоже, существует несоответствие в том, устанавливает ли j x
PC ← x
или PC ← PC + x
.
Вот последовательность, которая привела к этому вопросу:
Рассмотрим следующую ассемблерную программу:
addi x1, x1, 1
beq x1, x1, bob
addi x2, x2, 3
bob:
addi x3, x3, 4
addi x4, x4, 5
Если собрать, а потом разобрать, то получим:
addi ra,ra,1
beq ra,ra,0xc
addi sp,sp,3
addi gp,gp,4
addi tp,tp,5 # 0x5
Так я пришел к выводу, что beq
использует абсолютную адресацию. Теперь, если мы возьмем эту программу, ассемблируем и дизассемблируем ее, то получим:
addi ra,ra,1
bne ra,ra,0xc
j 0x8
addi sp,sp,3
addi gp,gp,4
addi tp,tp,5 # 0x5
Следовательно, похоже, что j 0x8
должно использоваться относительное смещение. Он должен пропустить две инструкции вперед, чтобы добраться до нужной нам строки добавления 4. Если бы здесь использовалась абсолютная адресация, он бы застрял в бесконечном цикле сам с собой.
Я был доволен здесь, пока не посмотрел вот эту программу:
addi x1, x1, 1
j bob
addi x2, x2, 3
bob:
addi x3, x3, 4
addi x4, x4, 5
Если собрать и разобрать это, то получим:
addi ra,ra,1
j 0xc
addi sp,sp,3
addi gp,gp,4
addi tp,tp,5 # 0x5
Если j 0xc
действительно использует относительное смещение, как и раньше, то переход к метке bob:
пропустит инструкцию, в которой она находится, и просто выполнит addi tp,tp,5
.
Это не имеет смысла. И чтобы убедиться, что это не так, я использовал встроенную сборку gcc
, поместил значения этих регистров в переменные и распечатал их, и на самом деле инструкция addi x3, x3, 4
действительно выполняется, подразумевая, что j 0xc
использует абсолютное смещение, противоречит тому, что мы видели ранее.
Извините, если это много, но может ли кто-нибудь объяснить это очевидное несоответствие? (Практически я понимаю, что ответ: «Просто используйте ярлыки», но мне все равно хотелось бы понять)
Редактировать:
Вот результат разборки с адресами .s
с beq ra,ra,0xc
.
0000000000000000 <.text>:
0: 00108093 addi ra,ra,1
4: 00109463 bne ra,ra,c <.text+0xc>
8: 0000006f j 8 <.text+0x8>
c: 00310113 addi sp,sp,3
10: 00418193 addi gp,gp,4
14: 00520213 addi tp,tp,5 # 5 <.text+0x5>
Кажется, несоответствие все еще существует.
Вы не можете «разобрать» файл .s
, поскольку он уже собран (текст ascii). Я предполагаю, что ваш последний пример — это дизассемблирование файла .o
. Обязательно обратите внимание на перемещения, так как компоновщик применит их к байтам кода как часть связывания.
Вы, кажется, запутались в этой инструкции:
8: 0000006f j 8 <.text+0x8>
Если вы посмотрите на таблицы декодирования инструкций RISCV, вы увидите, что это значение (0x0000006f) на самом деле является инструкцией JAL (код операции 0b1101111) с назначением X0 (игнорируется) и смещением 0. Итак, если вы действительно выполнили эту инструкцию , это был бы бесконечный цикл, прыгающий сам на себя.
Здесь происходит то, что на самом деле это «частичная» инструкция, в которую компоновщик должен заполнить некоторые биты. Где-то в таблице символов объектного файла или в перемещениях будет ссылка на этот адрес инструкции (.text + 8), которая указывает символ, к которому должна перейти эта ветвь, и тип перемещения относительной ветви. Когда компоновщик связывает этот код, он заполняет старшие биты инструкции смещением этого символа, чтобы создать окончательную инструкцию для запуска.
Я понимаю. Это имеет смысл, спасибо. (Про редактирование я имел в виду разборку сборки той самой сборки). Я поиграюсь с этим еще
Обязательно указывайте адреса инструкций, когда вы просматриваете машинный код/дизассемблирование. Если вы посмотрите только на объектный код, а не на исполняемый код, вы можете упустить из виду, что некоторые перемещения представляют собой проблемы, которых вы не видите.