Необычное поведение программы сборки ARM

#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