Pamięć nadpisuje stos (choć powinno by

Walczę teraz z pewną dziwną zagadką, która teoretycznie nie powinna mieć miejsca. Chodzi o jeden z moich projektów retro na 6502, ale teoretycznie nie powinno mieć to nic wspólnego z procesorem.

Oprogramowanie napisane w C i skompilowane kompilatorem CC65. Kod źródłowy jest dostępny tutaj:

formatting link
Wszystko zaczęło się od tego, gdy dodałem do projektu funkcje do obsługi przycisków - funkcje, które w innym projekcie (na takiej samej płytce, tylko z nieco innymi peryferiami) działały bez problemu, a tutaj nie chciały. Po ich dodaniu układ zaczął się zachowywać skrajnie nie stabilnie - samoczynne resety co kilkadziesiąt sekund, co chwilę detekcja fałszywych wciśnięć przycisków itp.

Dość długo szukałem błędu w kodzie, ale nie mogłem go znaleźć. Upewniłem się, że winy nie ponosi uszkodzony RAM albo procesor. Aż w końcu zacząłem eksperymentować ze zmiennymi - zmniejszyłem rozmiar niektórych buforów, część zmiennych zmieniłem z globalnych na lokalne. Pomogło! Samoczynne resety ustały, fałszywe wciśnięcia przycisków występują, ale bardzo rzadko.

Sprawa jest jednak bardzo dziwna, bo wygląda to tak, jakbym teraz był na skraju maksymalnego wykorzystani RAM-u. A tak nie powinno być. Układ ma

8kB pamięci RAM, więc naprawdę dużo jak na taki "kontroler". W pamięci nie ma za dużo zmiennych globalnych. Są tylko dwa większe bufory (po 256 bajtów każdy), poza tym parę mniejszych buforów, trochę stosunkowo niewielkich struktur i umiarkowna ilość zmiennych. Nie ma szansy, żeby to nagle zajęło zdecydowaną większość RAM-u. Nie widzę też niczego, co mogłoby pożerać stos - nie używam nigdzie rekurencji ani nie stosuję większych zmiennych globalnych, zbyt zagnieżdżonych wywołań funkcji też nie ma.

Gdyby ktoś wpadł na pomysł, że to wina niewielkiego sprzętowego stosu

6502 (zaledwie 256 bajtów w pierwszej stronie pamięci) to tłumaczę, że CC65 korzysta z programowego stosu, który działa tak, jak we współczesnych MCU, zapisując dane od końca pamięci w dół.

Fragment pliku .map wygenerowanego podczas linkowania projektu potwierdza, że zmienne globalne zajmują bardzo małą część pamięci:

Segment list:

------------- Name Start End Size Align

---------------------------------------------------- ZEROPAGE 000000 000019 00001A 00001 DATA 000200 00024A 00004B 00001 BSS 00024B 0004FF 0002B5 00001 STARTUP 00804B 008066 00001C 00001 ONCE 008067 008072 00000C 00001 CODE 008073 00A022 001FB0 00001 RODATA 00A023 00A1D6 0001B4 00001 VECTORS 00FFFA 00FFFF 000006 00001

Plik cfg wygląda następująco:

MEMORY { ZP: start = $0, size = $100, type = rw, define = yes; RAM: start = $200, size = $1E00, define = yes; ROM: start = $8000, size = $8000, file = %O; }

SEGMENTS { ZEROPAGE: load = ZP, type = zp, define = yes; DATA: load = ROM, type = rw, define = yes, run = RAM; BSS: load = RAM, type = bss, define = yes; HEAP: load = RAM, type = bss, optional = yes; STARTUP: load = ROM, type = ro; ONCE: load = ROM, type = ro, optional = yes; CODE: load = ROM, type = ro; RODATA: load = ROM, type = ro; VECTORS: load = ROM, type = ro, start = $FFFA; }

FEATURES { CONDES: segment = STARTUP, type = constructor, label = __CONSTRUCTOR_TABLE__, count = __CONSTRUCTOR_COUNT__; CONDES: segment = STARTUP, type = destructor, label = __DESTRUCTOR_TABLE__, count = __DESTRUCTOR_COUNT__; }

SYMBOLS { # Define the stack size for the application

__STACKSIZE__: value = $0200, type = weak; }

Ktoś ma jakiś pomysł co do tego, gdzie można szukać przyczyny? Na chwilę obecną urządzenie działa w miarę stabilnie, jednak chciałem dodać jeszcze kilka funkcji, które będą wymagały trochę RAM-u, więc przedtem muszę rozwiązać ten problem.

Reply to
Atlantis
Loading thread data ...

Nie myślałeś aby napisać mały emulator cpu (ba, ukraśc gotowca, jest ich zilion) + trywialny emulator hardware (tych przycisków) i debugować in vitro, co tam się dzieje? Jak już masz transakcje cpu typu "zapisz bajt" i "odczytaj bajt" to napisanie kawałka kodu który udaje że jest przyciskiem, nie jest chyba jakieś strasznie trudne.

Powoli myslę nad zrobieniem małego komputera na CP/M (i mam nadzieje GEMa, jak mi się uda), ale wiem że pracę zaczne *wirtualnie*, czyli piszać emulator cpu, hardware i to będzie moje miejsce do debugu, nie fizyczne hardware, ono powstanie później lub równolegle...

Reply to
heby

To może wskazywać np. na to że nie wracasz z przerwania (masz jakieś?) lub się nie wyrabiasz i po jakims czasie przekręca się stos sprzętowy. Taka luźna myśl. Co prawda widzę CLI na początku maina, ale może cos przeoczyłem.

Jeszcze jedno: nie przestawiasz gdzieś procesora w tryb BCD?

I jeszcze jedno: Masz gdzieś podpięte SO (pin cpu)?

Sorry, ale review kodu nie zrobie... za duży :)

Reply to
heby

Systemy na 6502 mają tylko jeden wektor przerwań. Linia INT jest zwierana do masy przez poszczególne urządzenia zgłaszające przerwania, za pomocą wyjść typu OC. W kodzie obsługi przerwania trzeba po kolei odpytać wszystkie urządzenia i zdjąć flagi. Jest więc tylko jedna procedura obsługująca przerwanie, zakończona instrukcją RTI, więc program wraca z przerwania. Na początku procedury zapisuję na stosie zawartość rejestrów A oraz X (tylko z nich korzystam) a przed powrotem je przywracam.

Wszystkie funkcjonalności związane z przerwaniami działają poprawnie.

Co masz na myśli?

W 6502 CLI to instrukcja "clear interrupt DISABLE", natomiast SEI oznacza "set interrupt DISABLE". Czyli CLI włącza przerwania, SEI je wyłącza. Odwrotnie niż w AVR-ach czy PIC-ach.

Świadomie nie. Cały program zawiera tylko kilka asemblerowych fragmentów (m.in. procedura obsługi przerwań) i jestem pewien, że w żadnym z nich nie stosowałem instrukcji SED.

Podciągnięty do plusa.

Reply to
Atlantis

Przerwanie jest przerywane przerwaniem. Dośc częsta wpadka generująca rózne problemy, podobne do opisanych.

Wiem, ale nie byłem pewny czy z przerwań korzystasz.

Reply to
heby

No niezupełnie, ogólnie rzecz biorąc jest IRQ i NMI, mają osobne wektory.

Reply to
heby

Nawet w przypadku 6502? Jest jedno wejście INT. Jeśli kolejne urządzenie zgłosi przerwanie przed skasowaniem flagi poprzedniego nic się nie stanie, bo system nie będzie miał jak wiedzieć, że przyszło kolejne przerwanie. Dopiero po opuszczeniu procedury obsługi przerwania zostanie ona wywołana ponownie.

Nie jestem pewien co do sytuacji, kiedy kolejne przerwania przychodzi dopiero po zdjęciu flagi poprzedniego (urządzenie peryferyjne odłącza wejście INT od masy) ale przed wykonaniem RTI.

Kwestię NMT pomijam, bo wcale z niego nie korzystam.

Swoją drogą, jest jakiś prosty sposób na ocenienie użycia stosu sprzętowego 6502 przez wywołania funkcji C? Może faktyczne mam za dużo zagnieżdżeń? Niby argumenty i zmienne lokalne są przechowywane na stosie programowym, ale jednak dresy powrotu ciągle trafiają na sprzętowy...

Tylko z drugiej strony w takim wypadku manipulowanie rozmiarem buforów nie miałoby wpływu na sytuację...

Reply to
Atlantis

Codzi o zagnieżdzenie NMI/IRQ. Jeśli nie używasz jednego z nich to może to fałszywy trop.

Ale oceń jaki możesz mieć najdłuższy stos głównego programu i jaki możesz miec najdłuższy stos w przerwaniu, shit happens.

Emulatorem, własnym.

Np, jeden z ziliona:

formatting link
Weryfikacja formalna, jesli masz przerwania, jest skrajnie kłopotliwa, ale weryfikacja runtime, przez unit testy na emulatorze, raczej prosta i odwdzięczy się na wiele różnych sposobów poźniej.

Narzędzi do formalnej weryfikacji stosu na 6502 nie znam, wydaje mi się jednak że widziałem takie do clanga jako ciekawostkę. Ale to Ci się nie przyda, poza jakimiś hobbystycznymi eksperymentami nie ma backendu do

6502 w clangu.

Możliwe że problem tylko tak się objawia, to nie jest przyczyna.

Na ile ufasz, że kod generowany przez kompilator, jest prawidłowy?

Reply to
heby

Też tak myślę. Zupełnie nie znam 6502 ani tego, jak Twój kompilator alokuje pamięć, ale jesteś w stanie zrobić i wpleść w newralgiczne miejsca test, który wypluje na dowolne wyjście informację, jeśli stos (wskaźnik stosu) niebezpiecznie zbliży się do wskaźnika sterty?

Używasz tam w ogóle dynamicznej alokacji pamięci, czy wszystko jest statycznie? Jeśli używasz dynamicznej alokacji, to masz pewność, że nie masz żadnych wycieków?

Takie problemy, o jakich mówisz (wszystko działa, dodaję prostą funkcjonalność i wszystko zaczyna się losowo sypać), miałem w niewielkim projekcie na AVR i wynikały właśnie z nadpisywania sterty przez stos. Projekt niewielki, ale okazało się, że wystarczający.

Reply to
Arnold Ziffel

Ok. Wygląda na to, że udało mi się namierzyć źródło problemu. Kod obsługujący przerwanie wywoływał funkcję. która zawierała polecenia rozkazy SEI/CLI (zabezpieczenie przed uszkodzeniem zawartości zmiennej przez przerwanie). Jednak w przypadku 6502 użycie tych rozkazów w trakcie obsługi może spowodować wystąpienie kolejnego przerwania, a w efekcie nadpisanie stosu sprzętowego i niestabilne działanie.

Przepisałem kod w ten sposób, żeby pozbyć się wywołania tej funkcji, a więc i SEI/CLI. Teraz urządzenie zdaje się działać poprawnie.

W drugim projekcie tego kodu nie było, więc działało poprawnie.

Reply to
Atlantis

Użytkownik "Atlantis" napisał w wiadomości grup dyskusyjnych:5f749a85$0$544$ snipped-for-privacy@news.neostrada.pl...

Jesli dobrze rozumiem - to niechcacy odblokowalo przerwania w procedurze obslugi przerwania? I sie przerwania nakladaly, i stos rosl ?

To by tlumaczylo zachowanie, ale ... obsluga przerwania powinna byc krotka, i szansa zlapania drugiego przerwania niewielka, a przeciez dwa jeszcze nie powinny wywrocic programu. Musialo sie to czesciej zdarzac, czyli obsluga przerwania za dluga, i/lub przerwania czeste.

No chyba, ze krytyczne bylo samo przerwanie obslugi i niekontrolowana zmiana uzywanych tam zmiennych.

Pamiec mi juz zawodzi - ta flaga nie jest w statusie 6502 ? Moze da sie zamiast CLI przywrocic poprzedni stan flagi ...

J.

Reply to
J.F.

Pewnie tak, stos wywołań rósł, stos używany przez kompilator na zmienne lokalne funkcji obsługi przerwań też rósł...

Jeśli tam jest jedno przerwanie do wszystkiego, to może nie może być tak bardzo krótka, a i przerwanie często się pojawia (np. od timera?).

Może ta obsługa klawiatury jest w przerwaniu. Do tej pory się wyrabiało, ale doszło trochę cykli i przestało.

Tzn. ISR nie było wielobieżne (reentrant)? Tak też może być...

Reply to
Arnold Ziffel

Zapewne tak właśnie było. Konkretnie winę za przyrost programowego stosu musiała ponosić t sama wywoływana funkcja, w której znalazły się polecenia SEI/CLI. Wygląda na to, że wprowadzenie tej jednej zmiany usunęło problem. Urządzenie chodzi już przez pół dnia i nie widzę żadnych przejawów niestabilnego działania. Nie było fałszywych wciśnięć przycisków i nie zresetowało się ani razu. ;)

Tak, tm jest jedno przerwanie od wszystkiego, bo system nie posiada żadnego kontrolera priorytetów przerwań. W tym jednym przerwaniu trzeba odpytać po kolei wszystkie urządzenia. W chwili obecnej korzystają z tego trzy urządzenia: RTC (raz na sekundę), timer (co 20 ms) i UART (kiedy przyjdzie nowy znak).

Reply to
Atlantis

Trzy przerwania sprzetowe polaczone bramka OR czy tam AND, AND "na drucie" ?

Pomysl czy tam nie bylo sytuacji:

-jeden z ukladow wysterowuje linie IRQ,

-w trakcie obslugi, zanim obsluzysz wlasciwy chip, wykonuje sie instrukcja CLI,

-poniewaz linia IRQ jest ciagle aktywna - procesor natychmiast wywoluje obsluge przerwania,

-i tak w kolko, dopoki program obslugi nie dojdzie do wlasciwego chipu i nie zgasi przyczyny przerwania, lub chip sam nie zdeaktywuje.

Tylko to raczej blyskawicznie by wywalalo program, a nie tak raz na pewien czas ...

J.

Reply to
J.F.

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.