В процессе написания загрузчика 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 байт.
Автор: тащит всю команду