Elektroda.pl
Elektroda.pl
X

Search our partners

Find the latest content on electronic components. Datasheets.com
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

Wywołanie printf() skutkujące wywołaniem obsługi wyjątku Hard Fault

y0yster 29 May 2019 14:14 441 7
  • #1
    y0yster
    Level 19  
    Aplikacja, na której wykonywałem testy pracuje na mikrokontrolerze
    STM32F303CC wraz z FreeRTOSem. Optymalizacja -O0.

    Jakiś czas temu spotkałem się z ciekawą sytuacją. Dotyczy ona wywołania
    funkcji printf(), które skutkuje wyjątkiem Hard Fault.
    Wyjście printf() przekierowane jest na UARTa za pomocą redefinicji
    _write().

    Jeśli w zadaniu FreeRTOSa wywołam funkcję printf(), to po zatrzymaniu
    procesora ląduję w funkcji obsługi od Hard Faulta. Dodatkowo
    informacja o stanie systemu pokazuje informację, że wystąpił błąd

    Bus, memory management or usage fault (FORCED)

    Analizując dalej przyczyna błędu to



    Imprecise data access violation (IMPRECISERR)

    Próbowałem przejść do linii kodu, która była odpowiedzialna za błąd,
    ale niestety mam do dyspozycji wyłącznie adres i można dokonać
    deasemblacji. Wygląda na to, że jest to jakaś funkcja
    dołączona z biblioteki, co jest zrozumiałe, bo de facto błąd występuje
    przy wywołaniu printf().

    Sprawdziłem czy zmiana dotycząca wyłączenia bufora zapisu
    (DISDEFWBUF w ACTLR) coś zmieni. Wedle przypuszczenia dostaję
    już błąd

    Precise data access violation (PRECISERR)

    niestety wciąż w obrębie kodu, który nie ma implementacji, jedynie
    deasemblacja wchodzi w grę.

    Szukając jakiś informacji na ten temat natknąłem się, że faktycznie
    coś jest nie tak z obsługą funkcji printf(). Jednakże, nie znalazłem
    jednoznacznej odpowiedzi. Najprościej było by wykorzystać jakąś
    inną implementację i problem z głowy. Obecnie wykorzystuję implementację
    z newlib-nano. Warto było by sprawdzić, jak zachowa się aplikacja
    przy standardowej implementacji, poza wzrostem wielkości kodu wynikowego.

    Najciekawsza rzecz na sam koniec. Otóż, problem został w pewien sposób
    "rozwiązany". Jeśli wywołam printf() przed stworzeniem zadań FreeRTOSa
    (pewnie przed samym uruchomieniem planisty również by zadziałało)
    to problem już nie występuje. Co po części sugeruje, że może coś
    jest nie tak z zadaniem w FreeRTOSie. Sprawdziłem watermarki i stos
    nie wydaje się być przekroczony. Problem jednak nie został rozwiązany.
  • #2
    Freddie Chopin
    MCUs specialist
    Może choć pokazałbyś to wywołanie które jest problematyczne, bo pewnie nie jest to `printf("Hello world!");`. Masz te wywołania w więcej niż jednym wątku? Włączyłeś wsparcie dla newliba w FreeRTOSie (struktura reent)?
  • #3
    y0yster
    Level 19  
    Wywołanie printfa jest trywialne. Było z jednym parametrem. Zmieniłem na zwykłe bez żadnych dodatkowych parametrów
    Code:
    printf("Next\r\n");


    Tylko jeden wątek wykorzystuje printfy.

    Przeprowadziłem kilka innych testów. Włączyłem configUSE_NEWLIB_REENTRANT niestety to nie rozwiązało problemu.
    Również nie ma znaczenia, czy linkuję z newlibem, czy z standardową implementacją problem cały czas występuje.

    Jak na razie kluczowe jest wywołanie printfa przed włączeniem planisty.
  • #4
    Freddie Chopin
    MCUs specialist
    Jeśli chcesz to zdebuggować, to możesz się "zaopatrzyć" w bleeding-edge-toolchain ( https://github.com/FreddieChopin/bleeding-edge-toolchain ). Jest on kompilowany praktycznie tak samo jak toolchain ARMowy, z tym że biblioteki zawierają wszystkie symbole, a więc można je spokojnie debuggować i widzisz co się dzieje.

    Z Twojego opisu wynika, że problem występuje z tzw. "inicjalizacją stdio" w newlibie, która jest wykonywana podczas pierwszego użycia jakiejś funkcji z stdio.h (np. printf()). Z jakiegoś powodu inicjalizacja działa gdy zrobisz ją przed startem wątku. Pytałem o inne wątki, gdyż funkcje newliba są zabezpieczone przed wielowątkowym dostępem tylko jeśli takie wsparcie dostarczysz Ty (albo framework). FreeRTOS takiego wsparcia zapewne nie ma, więc nawet wielowątkowe użycie malloc() jest problemem. Jeśli np. masz we FreeRTOSie dynamiczne wątki, które są kasowane w wątku idle, to już masz potencjalny problem, gdyż ta "inicjalizacja stdio" również alokuje pamięć.

    Niezbyt wiem co Ci doradzić, gdyż akurat nigdy nie spotkałem się z takim problemem i gdy potrzebowałem, to działało to zawsze jak trzeba. Działało zarówno dawno temu gdy sam używałem FreeRTOSa, działa również w moim własnym projekcie distortos ( https://github.com/DISTORTEC/distortos ). Jeśli nie masz oporów przed C++ i używasz STM32 który jest przez distortos obsługiwany (tu może być pewien problem, bo akurat STM32F3 obsługiwany jeszcze nie jest, co nie znaczy że takiego wsparcia nie da się w miarę szybko dodać), to zawsze możesz przekonać się, jak by to działało tam. Jeśli C++ nie jest dla Ciebie albo jesteś wielkim miłośnikiem FreeRTOSa, to możesz sprawdzić jak zrobiona jest integracja newlibowych "locków" w distortos ( https://github.com/DISTORTEC/distortos/blob/master/source/newlib/locking.cpp + inne pliki z tego samego folderu) i przeszczepić to do swojego projektu - jest spora szansa, że pomoże na problem który opisujesz.

    Na szybko sprawdź też w kodzie czy "stdout" ma jakąś poprawną wartość i prawidłowy adres (w sensie w RAMie, a nie jakiś śmietnik). Np tak:

    void* volatile pointer = stdout;
    asm ("bkpt 0");

    I w debuggerze zobacz jaką ma wartość zmienna pointer.
  • #5
    szelus
    Level 34  
    Freddie Chopin wrote:
    Z Twojego opisu wynika, że problem występuje z tzw. "inicjalizacją stdio" w newlibie, która jest wykonywana podczas pierwszego użycia jakiejś funkcji z stdio.h (np. printf()). Z jakiegoś powodu inicjalizacja działa gdy zrobisz ją przed startem wątku.

    A może za mały stos w wątku?
  • #6
    Freddie Chopin
    MCUs specialist
    szelus wrote:
    A może za mały stos w wątku?

    Pisał, że "Sprawdziłem watermarki i stos nie wydaje się być przekroczony."

    Niemniej jednak jeśli używasz printf(), to moje prywatne zalecenie to minimum 2 kB stosu. Najlepiej używać iprintf(), który nie wspiera liczb zmiennoprzecinkowych, ale takie pewnie Ci i tak niepotrzebne w printf(), więc szkoda marnować pamięć.
  • #7
    y0yster
    Level 19  
    @Freddie Chopin

    Zrobiłem tak jak sugerowałeś, ale niestety nie przyniosło to zamierzonego efektu, ale jest zmiana.
    Otóż, wykorzystałem Twój toolchain i o dziwo zmieniło to działanie programu na tyle, że
    nie wchodzi on w obsługę Hard Faulta. Zatem ciężko jest zdiagnozować co się dzieje.
    Wykonałem dwa testy.

    1. Wywoływanie printfów wyłącznie w wątku (bez pierwszego wywołania printfa przed
    planistą). Efekt jest taki, że za każdym razem, gdy wywołuję printf()/iprintf() to
    wypisywany jest dokładnie jeden znak z łańcucha znaków. Nic więcej się nie pojawia.

    2. Podobnie, jak w 1, ale printf() jest wywoływany raz przed planistą.
    Wszystko działa tak, jak należy; podobnie, jak to miało miejsce w poprzednim przypadku
    (domyślny toolchain, od Atollica).

    Przychylam się ku pomysłowi, że jest to związane z pierwszą
    inicjalizacją buforów w printfie. Bez analizy kodu raczej się tego nie ominie.
  • #8
    Freddie Chopin
    MCUs specialist
    y0yster wrote:
    Zrobiłem tak jak sugerowałeś, ale niestety nie przyniosło to zamierzonego efektu, ale jest zmiana.

    Sugerowałem więcej niż jedną rzecz pewnie, więc napisz czego spróbowałeś dokładnie (;

    Sam znasz swoją aplikację - jeśli np. wywołuje ona wielowątkowo dynamiczne alokacje (inicjalizacja stdio robi takową kilkukrotnie), to bez odpowiedniego zabezpieczenia newliba na 100% masz w niej "tykającą bombę".