Поведение указателя стека сборки MSP430

Пытаясь проанализировать простой файл сборки, созданный с помощью msp430-gcc, я наткнулся на набор инструкций, которые я не понимаю, касающихся указателя кадра и указателя стека MSP430.

Программа С:

#include "msp430g2553.h"

int main()
{
  int i;

  for(i = 0; i < 3; i++);

}

Директивы минус сборки:

main:
        mov     r1, r4 ;Stores address of stack pointer in r4(frame pointer)
        add     #2, r4 ; ?
        sub     #2, r1 ; subtract 2 to allocate int i
        mov     #0, -4(r4) ; assign 0 to i
        jmp     .L2 ; start loop
.L3:
        add     #1, -4(r4) ; Adds one to int i
.L2:
        cmp     #3, -4(r4) ; Compare to #3
        jl      .L3 ; jump to .L3 if true
        add     #2, r1 ; deallocate int i
.Lfe1:
        .size   main,.Lfe1-main

Я попытался прокомментировать код, чтобы проследить выполнение программы, но я не понимаю строку add #2, r4. Что именно здесь происходит и почему int i упоминается в -4(r4)?


person Alan W    schedule 15.10.2014    source источник


Ответы (1)


Обычно первое, что вы делаете в функции, это:

push r4

чтобы сохранить текущий указатель кадра в стеке, чтобы вы могли установить новый указатель кадра для функции, которую вы собираетесь вызвать, а затем восстановить старый. push автоматически уменьшит указатель стека на 2, поэтому, когда вы выполните:

mov r1, r4

адрес, помещенный в r4, будет выше значения, которое вы только что поместили в стек ("выше" здесь в том смысле, что стек растет вниз - на самом деле он ниже с точки зрения числовых адресов памяти). Вы хотите, чтобы указатель фрейма на самом деле указывал ниже значения, которое вы только что поместили в стек, поэтому вы увеличиваете его на два, чтобы добиться этого с помощью:

add #2, r4

Поскольку main() — это первая выполняемая функция, у вас нет существующего указателя кадра для сохранения, поэтому вы видите здесь mov и add при отсутствии push.

Это будет иметь больше смысла, когда вы сделаете реальный вызов функции, и вы увидите все это:

push r4
mov  r1, r4
add  #2, r4

После того, как вы это сделаете, -2(r4) будет ссылаться на предыдущее значение указателя фрейма, которое вы только что поместили в стек, и, поскольку вы не добавили два к значению указателя стека, -2(r4) тоже будет равно этому.

Теперь, когда вы выделяете 16 бит для вашей локальной переменной i, вам придется вычесть 2 из указателя стека, чтобы освободить место для него, и поэтому адрес i будет -4(r4).

Пример

В качестве примера предположим, что указатель стека содержит 0x200, а указатель фрейма содержит 0x202, и затем вы хотите вызвать функцию. Вы начинаете с таким стеком:

      r4 --> 0x202    ---------------------
                             <empty>
      r1 --> 0x200    ---------------------

После того, как вы вернетесь из своей функции, вы захотите восстановить текущее значение указателя кадра, поэтому первое, что вы делаете, это помещаете его в стек, чтобы сохранить. Таким образом, после push r4 значение 0x202 помещается в ячейку памяти 0x200 (т. е. на вершину стека, на которую указывает r1), а указатель стека уменьшается на 2, чтобы освободить для него место, поэтому вы получаете:

      r4 --> 0x202    ---------------------
                             <empty>
             0x200    ---------------------
                              0x202
      r1 --> 0x1FE    ---------------------

Поместив предыдущее значение указателя фрейма в стек, вы теперь хотите установить текущее значение указателя фрейма на базу вашего текущего фрейма стека, поэтому вы начинаете это с перемещения нового указателя стека в r4, и вы получить:

             0x202    ---------------------
                             <empty>
             0x200    ---------------------
                              0x202
r1 == r4 --> 0x1FE    ---------------------

Старое значение указателя кадра — это первое значение в текущем кадре стека, поэтому вы хотите, чтобы r4 указывало перед ним, а не после него. Для этого вы добавляете 2 к r4, и вы получаете:

             0x202    ---------------------
                              <empty>
      r4 --> 0x200    ---------------------
                               0x202
      r1 --> 0x1FE    ---------------------

Позиция, в которой вы сейчас находитесь, такова, что r4 указывает на нижнюю часть вашего стекового фрейма, а r1 указывает на его верхнюю часть, где вы и хотите быть. Единственное, что на самом деле находится в вашем текущем кадре стека на данный момент, — это предыдущее значение указателя кадра, которое вы вставили в него в начале функции.

Затем вы уменьшаете указатель стека на 2, чтобы освободить место для вашей новой локальной переменной, и в итоге вы получаете:

             0x202    ---------------------
                             <empty>
      r4 --> 0x200    ---------------------
                              0x202
             0x1FE    ---------------------
                        <uninitialized i>
      r1 --> 0x1FC    ---------------------

и вы можете видеть, что i хранится в 0x1FC, то есть -4(r4). Вы снова находитесь в позиции, где r4 указывает на нижнюю часть фрейма стека, а r1 указывает на его вершину, но теперь у вас есть два 16-битных значения в текущем фрейме стека, поэтому два указателя находятся на расстоянии 4 байта друг от друга. .

Когда вы будете готовы вернуться в конце своей функции, вы должны add #2, r1 "освободить" память для вашей локальной переменной i. Это даст вам:

             0x202    ---------------------
                              <empty>
      r4 --> 0x200    ---------------------
                               0x202
      r1 --> 0x1FE    ---------------------

Затем вы pop r4 вытолкнете последнее значение из стека (теперь это 0x202, исходное значение указателя кадра), поместите его в r4 и увеличите указатель стека на 2, чтобы отразить, что это значение было удалено. из стека, который оставит вас в:

      r4 --> 0x202    ---------------------
                             <empty>
      r1 --> 0x200    ---------------------

это именно то, с чего вы начали, и вы идеально очистили стек после вызова функции.

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

person Crowman    schedule 15.10.2014
comment
Огромное спасибо. Ваше объяснение было удивительным. - person Alan W; 15.10.2014