Elektroda.pl
Elektroda.pl
X
Elektroda.pl
Computer Controls
Proszę, dodaj wyjątek dla www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

ARM C startup na przykładzie wygenerowanego przez Cube

19 Sty 2019 23:08 1386 33
  • Poziom 1  
  • Computer Controls
  • Pomocny post
    Specjalista - Mikrokontrolery
    stmx napisał:
    Generalnie (bardzo uproszczając) jest ona po to aby zainicjalizować bibloteki (czyli wykonać funkcje, które powinny się wykonać przed skokiem do punkyu wejścia do programu). Pominę tu parę mało istotnych rzeczy, które ta funkcja robi, ale następnie wywołuje ona funkcje, wskaźniki do których są zapisane w segmencie .preinit_array i .init_array

    Warto też dodać, że jest to mechanizm przy pomocy którego wywoływane są konstruktory globalnych obiektów w języku C++ (oczywiście takich obiektów, które takiej konstrukcji w runtime z jakiegoś powodu wymagają - reszta jest inicjalizowana podobnie jak dowolna zwyczajna zmienna z sekcji .data).
  • Poziom 1  
  • Specjalista - Mikrokontrolery
    To sortowanie dotyczy nazw sekcji, a nie nazw plików - w końcu nazwy plików to jest ta pierwsza gwiazdka, na zewnątrz SORT(). Generalnie (prawie) tak ma być, przypuszczam że priorytet jaki nadasz funkcjom z atrybutem constructor i destructor (czego w przykładach nie pokazałeś, ale można tak zrobić) przekłada się po prostu na dodatkowy numerek doczepiony do nazwy sekcji.

    W folderze toolchaina gcc można znaleźć coś na styl "oficjalnych skryptów linkera". Mają one mnóstwo niepotrzebnych rzeczy, a do tego brakuje im kilku kluczowych (np. definicji MEMORY, ale to już jest specyficzne dla konkretnego układu), niemniej jednak można je traktować jako wzór jak zrobić resztę. I tam jest to bardzo podobnie:

    arm-none-eabi-gcc-8.2.0-180815/arm-none-eabi/lib/ldscripts/armelf.x
    Code:
      .init_array     :
    
      {
        PROVIDE_HIDDEN (__init_array_start = .);
        KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
        KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
        PROVIDE_HIDDEN (__init_array_end = .);
      }
      .fini_array     :
      {
        PROVIDE_HIDDEN (__fini_array_start = .);
        KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
        KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
        PROVIDE_HIDDEN (__fini_array_end = .);
      }
  • Moderator Projektowanie
    Tak dla Twojej ciekawostki, kompilator to podła maszyna, zwłaszcza na O3. Dlaczego? Jak pisałem sobie kod na freestanding (czyli zero użycia bibliotek standardowych) to i tak wykrył kod z ustawiania .data i zerowania .bss jako wspaniałe miejsce do wykorzystania memcpy, dopiero dociśniecie go kolejną flagą go naprostowało

    Ogólnie standard języka bardzo mało rzeczy mówi a niektóre nie muszą być nawet spełniane (np podstawowe typy, nie muszą być spełnione. Przykład c2000 texasa char ma zawsze 16bit bo i bajt na tej architekturze ma 16bit, ale to jest zgodne z standardem).
    Tak samo o działaniu call'a czy działania branchy, standard o tym nic nie powie bo odpowiedzialnym za takie rzeczy jest standard ABI.

    Za to standard języka mówi o takich mega potrzebnych rzeczach jak trójznaki. Będę hipsterem i od dziś będę wklejać kod na forum tylko z wykorzystaniem trójznaków! Ogólnie jak na razie mnóstwo niepotrzebnych rzeczy się dowiedziałem z standardu. Może dalej będzie lepiej.


    Ogólnie to znalazłem ciekawą pozycję w książkach:
    "The Definitive Guide to ARM® Cortex®-M3 and Cortex®-M4 Processors"

    Już ją zamówiłem i jak znajdę czas to wrzucę krótką recenzję tej książki.
  • Computer Controls
  • Specjalista - Mikrokontrolery
    _lazor_ napisał:
    Ogólnie standard języka bardzo mało rzeczy mówi a niektóre nie muszą być nawet spełniane (np podstawowe typy, nie muszą być spełnione. Przykład c2000 texasa char ma zawsze 16bit bo i bajt na tej architekturze ma 16bit, ale to jest zgodne z standardem).

    Tłumacząc na polski - "niektóre rzeczy są niezgodne ze standardem, np. char ma 16-bitów, co jest zgodne ze standardem."

    https://en.cppreference.com/w/cpp/language/types
    Cytat:
    Character types
    ...
    Besides the minimal bit counts, the C++ Standard guarantees that
    Code:
    1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long).

    Note: this allows the extreme case in which bytes are sized 64 bits, all types (including char) are 64 bits wide, and sizeof returns 1 for every type.


    Bez obrazy, ale co ma Twój post wspólnego z tym tematem? Jakimkolwiek nawiązaniem do startupu jest w zasadzie jedynie opis, że jak zrobiłeś kopiowanie w pętli to kompilator zmienił Ci to na memcpy(), co nie powinno być dla Ciebie żadnym zaskoczeniem i w dokumentacji gcc jest jasno napisane, że tak właśnie się stanie, nawet na freestanding.

    Pierwsza strona na której jest napisane cokolwiek o freestanding:
    Cytat:
    Most of the compiler support routines used by GCC are present in ‘libgcc’, but there
    are a few exceptions. GCC requires the freestanding environment provide memcpy, memmove,
    memset and memcmp.
    Finally, if __builtin_trap is used, and the target does not implement
    the trap pattern, then GCC emits a call to abort.


    Reszta posta nie ma nic wspólnego ze startupem albo nawet i z ARM, więc...?
  • Poziom 1  
  • Moderator Projektowanie
    Może wzmianka o trójznakach była dygresją, jednak zaproponowana książka jak najbardziej jest związana z startupem, gdyż opisuje architekturę dla ARM i wszystkie elementy specyficzne dla architektury, które nie są zawarte w standardzie języka.

    Co do char i 16bitów, już Tobie rozwijam temat (chociaż jest to offtop)

    standard opisuje byte jako:
    "addressable unit of data storage large enough to hold any member of the basic character
    set of the execution environment"
    16bit jak najbardziej to spełnia.

    Jednak mamy punkt 5.2.4.2.1
    gdzie mamy opisane limity, a tutaj między innymi:
    UCHAR_MAX 255
    Co już 16bit char nie spełnia. Ogólnie jeszcze mieszasz C i C++, który w tym przypadku się różni...


    A kto powiedział, że chce używać libgcc? Daje linkerowi -nodefaultlibs i nie mam takiego problemu, tylko wtedy faktycznie trzeba wiedzieć co się robi, gdyż nie mogę korzystać z konstrukcji wywołującej cokolwiek z aeabi, gdyż to faktycznie wywali błąd linkera. W C++ pisanie w ten sposób jest naprawdę dramatyczne, ale w C to nie jest większy problem.
  • Poziom 1  
  • Specjalista - Mikrokontrolery
    Ciekawostką jest to, że ARM jest jedną z niewielu, jeśli nie wręcz jedyną architekturą, w której moduł startowy może być napisany całkowicie w C, bez jednej linii kodu asemblerowego. Drugą ciekawostką jest to, że jedynym znanym mi producentem, który z tego skorzystał, był NXP (zdaje się, że jednak obecnie start dloa NXP jest częściej pisany w asemblerze) - inni uparli się na asembler. Kiedyś nawet popełniłem moduł startowy w C...
  • Poziom 1  
  • Specjalista - Mikrokontrolery
    W C typ char może być oczywiście dłuższy niż 8 bitów, i taki jest we wszystkich architekturach, w których bajty są dłuższe niż 8 bitów - np. 16 lub 24 bitowe bajty mamy w niektórych sygnałowych.
  • Specjalista - Mikrokontrolery
    BlueDraco napisał:
    Ciekawostką jest to, że ARM jest jedną z niewielu, jeśli nie wręcz jedyną architekturą, w której moduł startowy może być napisany całkowicie w C, bez jednej linii kodu asemblerowego. Drugą ciekawostką jest to, że jedynym znanym mi producentem, który z tego skorzystał, był NXP (zdaje się, że jednak obecnie start dloa NXP jest częściej pisany w asemblerze) - inni uparli się na asembler.

    Do startupa napisanego w assemblerze definitywnie prościej dołożyć rzeczy których w C nie napiszesz - jedną z takich operacji jest wymuszenie użycia dwóch stosów i następująca po tym instrukcja synchronizacyjna. Bez kombinacji myślę że kod w C takiej operacji nie przeżyje. Druga sprawa jest taka, że pisząc startup w C od razu na dzień-dobry marnujesz część RAMu, bo zgodnie z ABI funkcja zapisuje jakiś tam stan na stosie, choć ten stan absolutnie nigdy nie będzie potrzebny (bo niby gdzie ten Reset_Handler() miałby wrócić?). Można dopisać atrybut "noreturn", ale wtedy to już i tak nie jest "całkowicie w C". Dodatkowo startup naprawdę jest taką rzeczą, że pisze się raz (kopiuje się raz, zrzyna się raz, itd.) i używa się zawsze identycznego, więc dla 99,666% ludzi równie wygodna byłaby forma pliku binarnego albo tablicy z hexami instrukcji [; No i to o czym pisał _lazor_ - pisząc w assemblerze na pewno nie zamieni nam pętli inicjalizacyjnej/zerującej na memcpy/memset <:
  • Poziom 1  
  • Poziom 20  
    stmx napisał:
    Wszystkie dywagacje będą dotyczyć C i najbardziej popularnego wśród hobbystów i nie tylko gcc.

    Freddie Chopin napisał:
    Do startupa napisanego w assemblerze definitywnie prościej dołożyć rzeczy których w C nie napiszesz - jedną z takich operacji jest wymuszenie użycia dwóch stosów i następująca po tym instrukcja synchronizacyjna. Bez kombinacji myślę że kod w C takiej operacji nie przeżyje.

    Rozumiem, że nie dopuszczasz tutaj rozwiązań oferowanych przez CMSIS, które z poziomu pliku startupa są chyba jednak funkcjami "C".
  • Specjalista - Mikrokontrolery
    rajszym napisał:
    Rozumiem, że nie dopuszczasz tutaj rozwiązań oferowanych przez CMSIS, które z poziomu pliku startupa są chyba jednak funkcjami "C".

    Chodzi Ci o te funkcje inline, wrappery na instrukcje assemblera? Jak kod w C może "przeżyć" że mu podmieniasz nagle stos na zupełnie inny? Nie żebym to sprawdzał, niemniej jednak wydaje mi się, że dowolne czary nie są w stanie spowodować, żeby coś takiego zadziałało, poza oczywiście bardzo specyficznymi przypadkami (funkcja Reset_Handler() nie używa stosu do absolutnie niczego) w które niezbyt chce mi się bawić, bo pewnie będą zależeć od bardzo specyficznego zestawu flag kompilatora i jeszcze od konkretnej wersji toolchaina. Napisanie (oczywiście solidnie wzorując się na innych kodach) tego startupa - nawet nie znając zbytnio assemblera - nie jest żadną filozofią, a do tego naprawdę można go sobie ściągnąć z miliona miejsc, więc gdzie problem? W pierwszym poście jest taki startup, zupełnie wystarczający do 99,666% przypadków. 22 instrukcje assemblera, wszystkie podstawowe i żadnych czarów, bo co tam jest użyte - load, store, move i branch.

    A tak czy siak nie oszukujmy się, że wstawka assemblera opakowana w funkcję inline jest nagle "całkowicie w C" (;
  • Poziom 20  
    Poprzednio pisałeś o wymuszeniu użycia dwóch stosów, co w startupie (Reset_Handler) nie jest żadnym problemem w 99,99%. Zupełnie inną rzeczą jest podmiana stosu - tutaj ryzyko rzeczywiście jest zbyt duże. Jednak w startupie podmiany stosu się raczej nie robi.
  • Specjalista - Mikrokontrolery
    rajszym napisał:
    Poprzednio pisałeś o wymuszeniu użycia dwóch stosów, co w startupie (Reset_Handler) nie jest żadnym problemem w 99,99%. Zupełnie inną rzeczą jest podmiana stosu - tutaj ryzyko rzeczywiście jest zbyt duże. Jednak w startupie podmiany stosu się raczej nie robi.

    Jeśli wymuszenie użycia dwóch stosów jest robione w startupie, to jest ono jednoznaczne z natychmiastową podmianą aktywnego stosu (stąd potrzeba dorzucenia instrukcji synchronizacji po zmianie). W rejestrze CONTROL (a właśnie o jego zmianę tutaj chodzi) ustawiasz zasadniczo jaki stos jest używany przez tryb "thread" (tryb "handler" zawsze używa MSP). Handler resetu jest JEDYNYM handlerem, który _NIE_ jest odpalany w trybie "handler", tylko właśnie w "thread". Tego się nie da ominąć.

    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0179b/ar01s02s06.html

    Cytat:
    The processor supports two operation modes, Thread mode and Handler mode. Thread mode is entered on reset and normally on return from an exception. When in Thread mode, code can be executed as either Privileged or Unprivileged.
  • Poziom 20  
    Oczywiście masz rację :)
    Pisząc, że jest to bezpieczne miałem na myśli:
    Freddie Chopin napisał:
    funkcja Reset_Handler() nie używa stosu do absolutnie niczego
  • Specjalista - Mikrokontrolery
    Tyle że nie jesteś w stanie tego w żaden przenośny sposób zagwarantować, przy niskiej/wyłączonej optymalizacji funkcja Reset_Handler z pewnością stosu jednak używa, a jeśli pętle zamienione zostaną na wywołania memcpy/memset (a zapewne będą przy włączonej optymalizacji), to również ten stos będzie użyty. Więc sam widzisz, że jest to tyle kombinacji, że niekoniecznie są one warte uniknięcia odrobiny assemblera, zwłaszcza że nie ma żadnej potrzeby pisać go na nowo do każdego projektu.
  • Poziom 1  
  • Specjalista - Mikrokontrolery
    stmx napisał:
    @Freddie Chopin No to dosc latwo ominąć

    No i zobacz ile trzeba się nakombinować, żeby uzyskać coś co wcale nie jest lepsze niż startup w assemblerze, który już masz (; Bo definitywnie nie jest to w czystym C.

    No ale co kto lubi, jak ktoś czuje potrzebę napisać startup w C to w istocie na Cortex-M się da to zrobić (pod pewnymi warunkami). Po prostu - jak przy innym wątku - nie nazywajmy startupa w assemblerze jakąś patologią bez uzasadnienia.
  • Moderator Projektowanie
    Jeszcze trzeba dodać array (czy strukturę), która pod adres 0x00000000 da SP oraz pod 0x00000004 pointer na Reset_Handler. Bo język to jedno, ale jeszcze trzeba zaspokoić architekturę. Na szczęście na cortex-m mamy tylko ISA z thumb + thumb 2 technology i nie trzeba przełączać się jeszcze na drugi set instrukcji - ARM.
  • Specjalista - Mikrokontrolery
    Jedyne zaklęcie poza standardowym C, jakie jest potrzebne w typowym module startowym - to atrybut wymuszający sekcję położoną na początku pamięci dla struktury z adresami.

    Freddie: nie żebym się jakoś szczególnie upierał, ale nie uważam, że parę dodatkowych zaklęć koniecznych przy przełączeniu wskaźnika stosu było czymś mniej eleganckim, niż pisanie całości w asemblerze. Napisałem w życiu przynajmniej paręset kilobajtów kodu w różnych językach asemblerowych, ale moim zdaniem jeśli architektura daje możliwość stworzenia całości kodu w C bez nadmiernej gimnastyki, to głupio z tego nie skorzystać.
    No i właściwie dlaczego przełączenia stosu dla thread nie można zrobić w obsłudze przerwania, np. dedykowanego tylko do startu RTOSa i zgłaszanego programowo? Byłoby bez zaklęć - pomyśl o tym... ;)

    Pokazywałem tu kiedyś miganie diodą na LPC w czystym C na 32 bajtach.
  • Moderator Projektowanie
  • Specjalista - Mikrokontrolery
    BlueDraco napisał:
    No i właściwie dlaczego przełączenia stosu dla thread nie można zrobić w obsłudze przerwania, np. dedykowanego tylko do startu RTOSa i zgłaszanego programowo? Byłoby bez zaklęć - pomyśl o tym... ;)

    Tylko po co? Chyba tylko po to, żeby całość skomplikować. Przełączenie tego w startupie - ze 3 instrukcje assemblera. Przełączenie tego w sposób opisany przez Ciebie - sam sprawdź (ja obstawiam ~10x więcej), dodatkowo chętnie zobaczę jak zrealizujesz odtworzenie kontekstu w trybie "thread" po takiej zmianie.

    Ja już mam startup w assemblerze gotowy. Od lat [; Więc dla mnie to przerobienie go na C jest dodatkową robotą.
  • Poziom 1  
  • Pomocny post
    Moderator Projektowanie
    Ogólnie sobie przeglądałem CMSIS'a i w sumie jest tam startup zrealizowany w C dla GCC łącznie z linkerem. Musiałem skryptowi linkera zmienić rozszerzenie, bo .ld nie dało się dodać jako załącznik...
  • Poziom 1  
  • Specjalista - Mikrokontrolery
    stmx napisał:
    @_lazor_ i przy okazji pozwala zobaczyć jak sobie zrobić inicjalizacje i zerowanie zmiennych umieszczonych w innych obszarach pamięci (np CCM)

    Tyle można równie dobrze zobaczyć w moim projekcie RTOSa albo i w przykładzie dla LPC4330 który wrzuciłem do netu 6 lat temu [;

    https://github.com/DISTORTEC/distortos/blob/m..._STM32F4DISCOVERY/ST_STM32F4DISCOVERY.ld#L192
    https://github.com/FreddieChopin/lpc4330_blink_led/blob/master/LPC4330_50_SPIFI_4MB.ld#L145