#assembly #arm #undefined-behavior
#сборка #arm #неопределенное поведение
Вопрос:
Я писал JIT-компилятор ARM для простых выражений. Моему компилятору Just-in-time присваиваются адреса для функций и переменных, которые будут использоваться в выражении и в самом выражении. И он генерирует код ARM, который вычисляет выражение.
Это результат моего компилятора для выражения inc(1) 1
:
start:
push {r4} //saving r4
ldr r0, [pc] //writing the constant into r0
b skip0 //skipping data line
.word 0x1
skip0:
push {r0} //saving the constant
pop {r0} //fucntion time. Let's pop the argument
ldr r4, [pc] //Let's get function address
b skip1 //skipping data line
.word 0x13050
skip1:
bx r4 //jumping to function
push {r0} //saving the output of it
ldr r0, [pc] //writing the constant into r0
b skip2 //skipping data line
.word 0x1
skip2:
push {r0} //saving the constant
pop {r0-r1} //getting function result and the constant
add r0, r1, r0 //adding them to each other
push {r0} //saving the result
pop {r0} //work done, popping the result to r0 in order to return it
pop {r4} //placing r4 back
bx lr
inc(x)
это внешняя функция, которая просто возвращает x
. 0x13050
является адресом функции в момент выполнения.
Проблема в том, что результат равен 2, но он должен быть 3. Я не могу найти ошибку, не могли бы вы мне помочь?
Забавный факт: если я изменяю bx
на blx
, я получаю ошибку segfault
Комментарии:
1. Большинство процессоров ARM поддерживают
movk
/movt
для построения 32-байтового значения в 2 инструкциях. Вероятно, лучше использовать это вместо того, чтобы тратить размер кода, перепрыгивая через.word
загружаемый вами. (И тогда это простая оптимизация для вашего джиттера, чтобы заметить константы, которые могут быть закодированы с помощью одного обычногоmov
.)2. Я бы, конечно, не согласился с «большинством процессоров arm» …. нам нужно знать, что это за ядро (таким образом, какая архитектура, таким образом, какой набор инструкций).
Ответ №1:
Ответ находится в последней строке: blx
перезаписывает lr
И поскольку вы не сохранили lr
в начале, bx lr
в конце это приведет к ответвлению обратно на строку push {r0}
после blx r4
, вызывая бесконечный цикл.
И поскольку вы нажимаете на одну итерацию больше, чем нажимаете на каждую итерацию, указатель стека будет указывать на недопустимый адрес некоторое время => segfault
При bx r4
этом вспомогательная функция inc(x)
будет возвращаться не к вашей функции, а к вызывающей функции с возвращаемым значением 2 в дополнение к НЕВЕРНОМУ указателю стека.
start:
push {r4, lr} //saving r4 AND lr
ldr r0, [pc] //writing the constant into r0
b skip0 //skipping data line
.word 0x1
skip0:
push {r0} //saving the constant
pop {r0} //fucntion time. Let's pop the argument
ldr r4, [pc] //Let's get function address
b skip1 //skipping data line
.word 0x13050
skip1:
blx r4 //jumping to function
push {r0} //saving the output of it
ldr r0, [pc] //writing the constant into r0
b skip2 //skipping data line
.word 0x1
skip2:
push {r0} //saving the constant
pop {r0-r1} //getting function result and the constant
add r0, r1, r0 //adding them to each other
push {r0} //saving the result
pop {r0} //work done, popping the result to r0 in order to return it
pop {r4, pc} //placing r4 back AND return