Нарушаем стек в STM8

В процессе написания загрузчика STM8uLoader для микроконтроллеров STM8 возникла необходимость измерить глубину стека.

Зададимся вопросами:

  • Что будет если попытаться поместить в стек больше информации чем его глубина?
  • Что будет если попытаться извлечь из стека больше информации чем поместили?
  • Что будет если инициализировать указатель стека SP адресом выходящим за границы стека?

Объем памяти RAM и глубина стека у разных моделей STM8 может отличаться.
Для исследования была выбрана модель STM8S103F3.

Документация на STM8S103F3 дает следующие данные:
— глубина стека 513 байт;
— при сбросе указатель SP инициализируется величиной 0x03FF (RAM END);
— стек растет в сторону уменьшения адресов.

Расчет показывает, что нижняя граница стека равна:

        0x03FF - 513 = 0x01FF

Чтобы нарушить эту границу необходимо поместить в стек несколько больше, чем 513 байт.

Само содержимое стека нас не интересует. Достаточно знать содержимое указателя стека SP в котором должен находиться адрес следующей не занятой стеком ячейки памяти RAM.
Будем последовательно помещать байты любой командой «push» (напр. «push A») и перед каждым шагом отправлять в UART содержимого старшего SPH и младшего SPL байта указателя стека SP.

Алгоритм процедуры:

1 Инициализируем указатель стека величиной 0x03FF и настраиваем UART;
2 Ждем любой байт от терминальной программы;
3 Байт принят;
4 Отправляем в UART содержимое указателя SP;
5 Помещаем в стек командой «push A» содержимое аккумулятора;
6 Если циклов отправки менее 64, переходим к пункту 4;
7 Если циклов отправки 64, переходим к пункту 2.

; настраиваем UART 9600/8N1
    mov    UART1_BRR2, #$00  ; эту строку можно закомментировать
    mov    UART1_BRR1, #$0D
; разрешаем передачу/прием
    mov    UART1_CR2, #%00001100
; инициализируем указатель SP величиной $03FF
    ldw     X, #$03FF    ; X <= RAM END
    ldw     SP, X        ; SP <= X
; ожидаем получения любого байта
wait_rx_byte:
    btjf    UART1_SR, #5, wait_rx_byte	;
    ld      A, UART1_DR
; включаем светодиод	
    bset   PB_DDR,#5
    bset   PB_CR1,#5 
	
    ldw     Y, #64        ; Y <= 64
stack_cycle:
    ldw    X, SP        ; X <= SP
; отправляем SPH в UART
;    rlwa   X            ; A <- XH <- XL <- A
    ld      A, XH        ; A <- XH
    ld      UART1_DR, A  ; UART1_DR <= A
wait_tx_byte_XH:
    btjf    UART1_SR, #7, wait_tx_byte_XH
; отправляем SPL в UART
;    rlwa   X            ; A <- XH <- XL <- A
    ld       A, XL        ; A <- XL
    ld       UART1_DR, A  ; UART1_DR <= A
wait_tx_byte_XL:
    btjf     UART1_SR, #7, wait_tx_byte_XL
; помещаем A  в стек
    push    A          ; M(SP--) <= A
    decw    Y
    jrne     stack_cycle
; выключаем светодиод	
    bres     PB_DDR,#5
    bres     PB_CR1,#5 
	
    jra       wait_rx_byte

Наблюдаем, как терминальная программ последовательно принимает содержимое указателя SP начиная с 0x03FF:

 03 FF 03 FE 03 FD 03 FC 03 FB 03 FA 03 F9 03 F8 
 03 F7 03 F6 03 F5 03 F4 03 F3 03 F2 03 F1 03 F0 
 03 EF 03 EE 03 ED 03 EC 03 EB 03 EA 03 E9 03 E8 
 03 E7 03 E6 03 E5 03 E4 03 E3 03 E2 03 E1 03 E0 
 03 DF 03 DE 03 DD 03 DC 03 DB 03 DA 03 D9 03 D8 

После того как значение достигло 0x01FF (ранее рассчитанная граница стека)
указатель SP опять принял величину 0x03FF (стек замкнулся в кольцо)
и начал затирать наиболее старые данные

 02 0F 02 0E 02 0D 02 0C 02 0B 02 0A 02 09 02 08 
 02 07 02 06 02 05 02 04 02 03 02 02 02 01 02 00 
 01 FF 03 FF 03 FE 03 FD 03 FC 03 FB 03 FA 03 F9 
 03 F8 03 F7 03 F6 03 F5 03 F4 03 F3 03 F2 03 F1 
 03 F0 03 EF 03 EE 03 ED 03 EC 03 EB 03 EA 03 E9 

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

Алгоритм процедуры:

1 Инициализируем указатель стека величиной 0x03FF и настраиваем UART;
2 Ждем любой байт от терминальной программы;
3 Байт принят;
4 Извлекаем содержимое из стека командой «pop A» в аккумулятор;
5 Отправляет в UART содержимое указателя SP;
6 Если циклов отправки менее 64, переходим к пункту 3;
7 Если циклов отправки 64, переходим к пункту 2.

Поменялись местами пункты 4 и 5 алгоритма и команда «push A» на команду «pop A».
Не смотря на то, что мы инициализировали указатель SP значением 0x03FF уже после первой команды «pop A» указатель принял значение 0x01FF и продолжил увеличиваться в сторону 0x03FF.

 01 FF 02 00 02 01 02 02 02 03 02 04 02 05 02 06 
 02 07 02 08 02 09 02 0A 02 0B 02 0C 02 0D 02 0E 
 02 0F 02 10 02 11 02 12 02 13 02 14 02 15 02 16 
 02 17 02 18 02 19 02 1A 02 1B 02 1C 02 1D 02 1E 
 02 1F 02 20 02 21 02 22 02 23 02 24 02 25 02 26 

Дойдя до величины 0x03FF. после очередной команды «pop A» указатель опять принял значение 0x01FF и продолжил увеличиваться в сторону 0x03FF.

 03 EF 03 F0 03 F1 03 F2 03 F3 03 F4 03 F5 03 F6 
 03 F7 03 F8 03 F9 03 FA 03 FB 03 FC 03 FD 03 FE 
 03 FF 01 FF 02 00 02 01 02 02 02 03 02 04 02 05 
 02 06 02 07 02 08 02 09 02 0A 02 0B 02 0C 02 0D 
 02 0E 02 0F 02 10 02 11 02 12 02 13 02 14 02 15 

В обратном направлении при излишнем количестве команд «pop(w)» стек также замкнут в кольцо длиной 513 байт.

Стек в STM8S103F3 является линейным, пока вы не нарушите одну из его границ 0x01FF или 0x03FF.

Как только вы нарушаете одну из границ, стек становится кольцом длиной 513 байт.
Не важно, где в кольце (в адресах 0x01FF...0x03FF) будет находиться вершина/дно стека, в стек мы сможем помещать неограниченное количество байт, но извлечь сможем не более 513 не поврежденных байт (самых «свежих»).

Теперь, когда стек локализован в адресах 0x01FF...0x03FF, пришло время нарушить этот диапазон при инициализации указателя SP.

В пункте 1 первой процедуры заменим величину 0x03FF инициализации указателя SP на величину 0x01FE.

Наблюдаем, как стек от адреса 0x01FE направился в сторону уменьшения адресов.

 01 FE 01 FD 01 FC 01 FB 01 FA 01 F9 01 F8 01 F7
 01 F6 01 F5 01 F4 01 F3 01 F2 01 F1 01 F0 01 EF
 01 EE 01 ED 01 EC 01 EB 01 EA 01 E9 01 E8 01 E7
 01 E6 01 E5 01 E4 01 E3 01 E2 01 E1 01 E0 01 DF
 01 DE 01 DD 01 DC 01 DB 01 DA 01 D9 01 D8 01 D7

Достигнув адреса 0x0000 стек вышел из памяти RAM и проник в недоступные для STM8S103F3 ячейки «памяти» FLASH.

 00 16 00 15 00 14 00 13 00 12 00 11 00 10 00 0F
 00 0E 00 0D 00 0C 00 0B 00 0A 00 09 00 08 00 07
 00 06 00 05 00 04 00 03 00 02 00 01 00 00 FF FF
 FF FE FF FD FF FC FF FB FF FA FF F9 FF F8 FF F7
 FF F6 FF F5 FF F4 FF F3 FF F2 FF F1 FF F0 FF EF

Ни о каких вызовах подпрограмм и прерываний не может быть и речи после выхода указателя из памяти RAM. Правда, где-то в глубине стека еще пока остались наиболее «древние» данные, которым посчастливилось сохраниться в памяти RAM.

Теперь попытаемся извлекать данные из стека при «запретной» (вне диапазона 0x01FF...0x03FF) инициализации указателя SP.

Начнем с адресов вне RAM. В пункте 1 второй процедуры заменим величину 0x03FF инициализации указателя SP на величину 0xFFF8.

Наблюдаем как стек во шел в память RAM.

 FF E9 FF EA FF EB FF EC FF ED FF EE FF EF FF F0 
 FF F1 FF F2 FF F3 FF F4 FF F5 FF F6 FF F7 FF F8 
 FF F9 FF FA FF FB FF FC FF FD FF FE FF FF 00 00 
 00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 
 00 09 00 0A 00 0B 00 0C 00 0D 00 0E 00 0F 00 10 

Пересекая нижнюю границу 0x01FF, стек вошел на свою территорию.

 01 E9 01 EA 01 EB 01 EC 01 ED 01 EE 01 EF 01 F0 
 01 F1 01 F2 01 F3 01 F4 01 F5 01 F6 01 F7 01 F8 
 01 F9 01 FA 01 FB 01 FC 01 FD 01 FE 01 FF 02 00 
 02 01 02 02 02 03 02 04 02 05 02 06 02 07 02 08 
 02 09 02 0A 02 0B 02 0C 02 0D 02 0E 02 0F 02 10 

Достигнув адреса 0x03FF, стек замкнулся в кольцо.

 03 E9 03 EA 03 EB 03 EC 03 ED 03 EE 03 EF 03 F0 
 03 F1 03 F2 03 F3 03 F4 03 F5 03 F6 03 F7 03 F8 
 03 F9 03 FA 03 FB 03 FC 03 FD 03 FE 03 FF 01 FF 
 02 00 02 01 02 02 02 03 02 04 02 05 02 06 02 07 
 02 08 02 09 02 0A 02 0B 02 0C 02 0D 02 0E 02 0F 

Выводы:

Стек в STM8S103F3 способен выполнять свои обязанности только внутри диапазона 0x01FF...0x03FF.

Чтобы получить наибольшую линейную глубину указатель стека SP в STM8S103F3 необходимо инициализировать величиной 0x03FF.

Стек в STM8S103F3 является линейным, пока вы не нарушите нижнюю границу 0x01FF.
Как только вы нарушаете нижнюю границу, стек становится кольцом длиной 513 байт.
  • +16
  • 3,4k
  • 7
Поделиться публикацией
Похожие публикации
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 7
    +2
    Метод проверки качества граблей с помощью наступания на них. Современные процессоры в состоянии делать это миллионы раз в секунду. Если взять не 8 разрядный, а что нибудь вроде core i7 то и миллиарды. Но тема в принципе забавная, порадовали. Правда очень редко, когда бывает нужна подобная информация и никто не гарантирует, что в следующей ревизии будет идентичное поведение
      +1
      Скорее огорчает, что так бездарно распорядились транзисторами. В 6205 никаких подобных проблем и быть не могло потому что старшая половина адреса стека была зафиксирована «намертво» и только нижняя хранилась в регистре. Ради чего в STM8 сделано иначе (и при этого гораздо более сложно как для реализации, так и для программиста?) — ради одного лишнего байта?
    +3
    где ссылка на даташит? это вроде как штатное поведение стека.
    RM0016 Reference manual стр. 31-32.
    3.1.2 Stack handling
      +4

      Ждём продолжения. Например что будет если stm8 паять при температуре 500°С

        +2
        Расчет показывает, что нижняя граница стека равна:
        0x03FF — 513 = 0x01FF

        0x3FF — 513 = 0x1FE
          –3
          В контексте статьи и 0x3FF-1=0x3FF.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое