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

[ARM][STM32][Linker] - LINKER - skrypt, tutoriale

24 Maj 2013 23:36 5346 23
  • Poziom 9  
    Od niedawna wróciłem do zabawy z ARMami. Mam płytkę ewaluacyną z procesorem: STM32F103VBT6.

    Oczywiście przeszperałem forum, poczytałem, skonfigurowałem środowisko i odpaliłem (z flasha) klasyczne miganie diodą zrobione przez Freddiego - za co dzięki. Kompilacja, linkowanie i flashowanie bez problemów. Uruchomiło się i działa. Potem kolejne coraz bardziej skomplikowane projekty. Podmiana maina, swoje includy kompilujesz flaszujesz działa - super.

    Nie lubię jednak używać czegoś, czego nie rozumiem. Magię lubię w książkach fantasy, a "rób tak bo tak jest dobrze, tak trzeba bo wszyscy tak robią" to mam na okrągło w telewizji.

    Przeglądałem pliki linkera i plik z wektorami. Tak jak plik z wektorami jest prosty do zrozumienia tak plik linkera już mniej.

    Widziałem w necie inne pliki linkera i tutoriale:

    np.
    http://www.triplespark.net/,
    http://fun-tech.se,
    http://developers.stf12.net

    Są ładne, kolorowe, co bardzo ważne - zrozumiałe, niemal trywialne i niestety niedziałające.

    Domyślam się też, że startup.S jest dolinkowywany na początku binarki i od tego zaczyna się wykonywanie załadowanego do flasha kodu - potem z niego wołany jest main etc. Skrypt startowy też jest zrozumiały.

    Ale np. w skrypcie linkera - co to jest glue_7, exidx_end, etc?

    Możecie polecić jakąś literaturę (jestem w trakcie czytania dokumentacji linkera) która pomoże zrozumieć co w tym pliku siedzi? Dlaczego jest tak skonstruowany i dlaczego jest taki długi? Jak działa i co robi?

    Nadmienię jeszcze, że nie używam CodeSourcery - bo, żeby ściągnąć darmową wersję trzeba się rejestrować, przekopać przez gąszcz odnośników, zakręcić wilczym ogonem i splunąć przez lewe ramię - używam GNU Tools for ARM Embedded Processors - proste przyjemne w instalacji (wystarczy rozpakować do /opt'a i dodać ścieżkę do PATHa) na licencji BSD.
  • PCBway
  • Specjalista - Mikrokontrolery
    tyrreus napisał:
    Nie lubię jednak używać czegoś, czego nie rozumiem. Magię lubię w książkach fantasy, a "rób tak bo tak jest dobrze, tak trzeba bo wszyscy tak robią" to mam na okrągło w telewizji.

    Czy jak piszesz program na PC to też używasz swojego - w pełni zrozumianego - skryptu linkera? A jak pisałeś kiedyś (zapewne) program na AVR? Przecież tam też są skrypty linkera, tyle że zaszyte w toolchainie, więc mało widoczne. Nikogo wtedy nie interesują specjalnie i nikomu to nie przeszkadza, dopiero przy ARM wszyscy chcą wszystko rozumieć (korzystając np. w tym samym momencie z SPLa...).

    tyrreus napisał:
    Ale np. w skrypcie linkera - co to jest glue_7, exidx_end, etc?

    Pierwsza jest potrzebna dla ARM7 w którym jest jednocześnie kod ARM i Thumb, a druga nazwa to koniec sekcji zawierającej wpisy związane z obsługą wyjątków C++. Proponuję nie usuwać żadnej, bo właśnie dzięki tym wszystkim wpisom ten plik jest uniwersalny i działa na KAŻDYM mikrokontrolerze ARM (po zmianie adresów i rozmiarów pamięci),.

    tyrreus napisał:
    na licencji BSD.

    Chyba raczej niezbyt...

    A tak BTW to ja tego skryptu linkera też w pełni nie rozumiem [;

    4\/3!!
  • Poziom 9  
    No ok, będę zatem szukał dalej ... właśnie z tymi wszystkimi uniwersalnymi jest tak, ze jest masa rzeczy która nie dotyczy konkretnego procesora skąd bierze się potem utrudnienie w zrozumieniu.

    A w kontekście licencji to na stronie jest napisane:

    Simplified BSD Licence, GNU GPL v2, GNU GPL v3, GNU LGPL v2.1, GNU LGPL v3, MIT / X / Expat Licence

    Czyli mimo iż może nie czyste BSD to i tak otwarte i darmowe.
  • Pomocny post
    Specjalista - Mikrokontrolery
    tyrreus napisał:
    A w kontekście licencji to na stronie jest napisane:

    Simplified BSD Licence, GNU GPL v2, GNU GPL v3, GNU LGPL v2.1, GNU LGPL v3, MIT / X / Expat Licence

    Czyli mimo iż może nie czyste BSD to i tak otwarte i darmowe.

    Ale zrozum, że żaden "packager" nie może zmienić licencji komponentów. Licencja pakietu CodeSourcery jest identyczna jak dowolnego innego pakietu zawierającego GCC, GDB itd. - BSD to co najwyżej jest część tego kodu (np. newlib), bo same binarki są w większości GPL.

    tyrreus napisał:
    No ok, będę zatem szukał dalej ... właśnie z tymi wszystkimi uniwersalnymi jest tak, ze jest masa rzeczy która nie dotyczy konkretnego procesora skąd bierze się potem utrudnienie w zrozumieniu.

    Niewątpliwie, tylko trzeba sobie zadać pytanie jaki jest cel tego zrozumienia (;

    Wątpię abyś znalazł jakąkolwiek dokumentację, bo (wg mnie) taka nie istnieje - wiele z tych sekcji jest tam, bo kompilator takie generuje, ale nigdzie nie znajdziesz żadnego opisu (no chyba że w źródłach GCC). W związku z tym (ponownie - wg mnie) nie ma tu specjalnie co rozumieć, bo po prostu "tak jest" i już, nie ma tu żadnej specjalnej logiki czy filozofii. Sekcja jest bo kompilator sobie taką wymyślił i już [;

    4\/3!!
  • Poziom 9  
    Ok, dzieki zatem za odpowiedź.

    Pozdrawiam
  • Poziom 26  
    Dobra, to ja się podczepię:

    odpaliłem w końcu semihosting, natomiast nie bardzo wiem, co zrobić ze skryptem linkera - bo nie do końca łapię pewne rzeczy:
    Code:

    /* Linker script to place sections and symbol values. Should be used together
     * with other linker script that defines memory regions FLASH and RAM.
     * It references following symbols, which must be defined in code:
     *   Reset_Handler : Entry of reset handler
     *
     * It defines following symbols, which code can use without definition:
     *   __exidx_start
     *   __exidx_end
     *   __etext
     *   __data_start__
     *   __preinit_array_start
     *   __preinit_array_end
     *   __init_array_start
     *   __init_array_end
     *   __fini_array_start
     *   __fini_array_end
     *   __data_end__
     *   __bss_start__
     *   __bss_end__
     *   __end__
     *   end
     *   __HeapLimit
     *   __StackLimit
     *   __StackTop
     *   __stack
     */
    ENTRY(Reset_Handler)

    SECTIONS
    {
       .text :
       {
          KEEP(*(.isr_vector))
          *(.text*)

          KEEP(*(.init))
          KEEP(*(.fini))

          /* .ctors */
          *crtbegin.o(.ctors)
          *crtbegin?.o(.ctors)
          *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
          *(SORT(.ctors.*))
          *(.ctors)

          /* .dtors */
           *crtbegin.o(.dtors)
           *crtbegin?.o(.dtors)
           *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
           *(SORT(.dtors.*))
           *(.dtors)

          *(.rodata*)

          KEEP(*(.eh_frame*))
       } > FLASH

       .ARM.extab :
       {
          *(.ARM.extab* .gnu.linkonce.armextab.*)
       } > FLASH

       __exidx_start = .;
       .ARM.exidx :
       {
          *(.ARM.exidx* .gnu.linkonce.armexidx.*)
       } > FLASH
       __exidx_end = .;

       __etext = .;
          
       .data : AT (__etext)
       {
          __data_start__ = .;
          *(vtable)
          *(.data*)

          . = ALIGN(4);
          /* preinit data */
          PROVIDE_HIDDEN (__preinit_array_start = .);
          KEEP(*(.preinit_array))
          PROVIDE_HIDDEN (__preinit_array_end = .);

          . = ALIGN(4);
          /* init data */
          PROVIDE_HIDDEN (__init_array_start = .);
          KEEP(*(SORT(.init_array.*)))
          KEEP(*(.init_array))
          PROVIDE_HIDDEN (__init_array_end = .);


          . = ALIGN(4);
          /* finit data */
          PROVIDE_HIDDEN (__fini_array_start = .);
          KEEP(*(SORT(.fini_array.*)))
          KEEP(*(.fini_array))
          PROVIDE_HIDDEN (__fini_array_end = .);

          KEEP(*(.jcr*))
          . = ALIGN(4);
          /* All data end */
          __data_end__ = .;

       } > RAM

       .bss :
       {
          . = ALIGN(4);
          __bss_start__ = .;
          *(.bss*)
          *(COMMON)
          . = ALIGN(4);
          __bss_end__ = .;
       } > RAM
       
       .heap (COPY):
       {
          __end__ = .;
          end = __end__;
          *(.heap*)
          __HeapLimit = .;
       } > RAM

       /* .stack_dummy section doesn't contains any symbols. It is only
        * used for linker to calculate size of stack sections, and assign
        * values to stack symbols later */
       .stack_dummy (COPY):
       {
          *(.stack*)
       } > RAM

       /* Set stack top to end of RAM, and stack limit move down by
        * size of stack_dummy section */
       __StackTop = ORIGIN(RAM) + LENGTH(RAM);
       __StackLimit = __StackTop - SIZEOF(.stack_dummy);
       PROVIDE(__stack = __StackTop);
       
       /* Check if data + heap + stack exceeds RAM limit */
       ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack")
    }



    ENTRY - jasne.
    .text { - jasne
    .ctors i .dtors - rozumiem, że tu chodzi konstruktory i destruktory C++, gdzie to jest u Ciebie Freddie? Bo widziałem jedynie ich wywołanie w startupie.

    .extab - exception table, jasne
    .exidx, jasne. .data i .bss także.

    i teraz zaczynają się schody:
    .HEAP - sterta zamieniona ze stosem miejscami. Ok, to łapię. Ale już:
    .stack - rozumiem, że to jest stos typu "narastający w dół" (descending), u Ciebie Freddie są dwa+ich rozmiar jest ustalony. Rozmiar z tego co zauważyłem jest w startupie ustawiany, prawda? Na to przynajmniej wygląda:
    Kod: asm
    Zaloguj się, aby zobaczyć kod

    I takie pytanie jeszcze - rozumiem, że u Ciebie na heap przeznaczane jest całe wolne miejsce, jakie zostało po stosie?

    No i w które miejsce powiniennem u Ciebie w skrypcie wstawić end ? Pytam bo jest mi to potrzebne do linkowania z librdimon, a niezbyt łapię, co w tym skrypcie robi to end (poza byciem na początku (?) sterty)
  • PCBway
  • Specjalista - Mikrokontrolery
    alagner napisał:
    .ctors i .dtors - rozumiem, że tu chodzi konstruktory i destruktory C++, gdzie to jest u Ciebie Freddie? Bo widziałem jedynie ich wywołanie w startupie.

    Zbędne - to jest stara metoda, obecnie wszystko jest robione przez init_array.

    alagner napisał:
    .stack - rozumiem, że to jest stos typu "narastający w dół" (descending), u Ciebie Freddie są dwa+ich rozmiar jest ustalony. Rozmiar z tego co zauważyłem jest w startupie ustawiany, prawda? Na to przynajmniej wygląda:

    W Cortex-M może być równie dobrze jeden stos.

    alagner napisał:
    I takie pytanie jeszcze - rozumiem, że u Ciebie na heap przeznaczane jest całe wolne miejsce, jakie zostało po stosie?

    tak

    alagner napisał:
    No i w które miejsce powiniennem u Ciebie w skrypcie wstawić end ? Pytam bo jest mi to potrzebne do linkowania z librdimon, a niezbyt łapię, co w tym skrypcie robi to end (poza byciem na początku (?) sterty)

    Symbol "end" to właśnie początek sterty (generalnie chodzi o koniec używanej pamięci), wiec wystarczy jak dodasz:
    PROVIDE(end = __heap_start);

    Problem jest tylko taki, że jeśli chcesz korzystać z mojego skryptu to być może będziesz musiał zamienić stos i stertę miejscami, aby faktycznie sterta była przed stosem. Istnieje pewna szansa, że implementacja sbrk() z tej biblioteki którą chcesz wykorzystać porównuje SP z aktualnym końcem stosu i zwraca błąd jeśli SP jest poniżej. Jeśli stos jest "przed" stertą, to zawsze będzie poniżej... Warto zajrzeć do źródeł lub po prostu podstawić swoje sbrk(), wtedy zapewne definicja symbolu "end" nie będzie potrzebna...

    4\/3!!
  • Poziom 26  
    Jeżeli proc ląduje mi w HardFault'cie to chyba ta szansa jest duża ;)
    No nic, trza będzie sbrk sobie napisać, dzięki ;)

    Pzdr.
  • Specjalista - Mikrokontrolery
    alagner napisał:
    No nic, trza będzie sbrk sobie napisać

    Przecież już został napisany i udostępniony dawno temu... (;

    alagner napisał:
    Jeżeli proc ląduje mi w HardFault'cie to chyba ta szansa jest duża

    Może warto sprawdzać wartości zwracane przez malloc()? <:

    4\/3!!
  • Poziom 26  
    Hmmmm. Obejrzałem te wszystkie sbrk _sbrk itd. ze źródeł i w zasadzie od Twoich wersji się toto nie różni za bardzo. Pokombinuję z tym jeszcze, dam znać w razie czego ;)

    Debuger rzuca się o próbę dostępu do pamięci której nie ma - w okolicach górnych zakresów (pełno F-ów)

    Pzdr.

    EDIT: to chyba mamy winnego: malloc zwraca zawsze 0.
  • Specjalista - Mikrokontrolery
    Może właśnie dlatego, że sterta wyszła "poza" pamięć?

    Dodam tylko, że w bleeding-edge-toolchain można sobie debuggować funkcje biblioteczne, bo nie są "stripped".

    4\/3!!
  • Poziom 26  
    To może faktycznie spróbuję z Twoim toolchainem i zamelduję co dalej.

    BTW - sbrk nie mogę sobie napisać od nowa/użyć Twoich syscallsów, bo ta funkcja już jest na stałe w librdimon zabudowana.

    Dobra, obejrzałem te syscallsy z newliba i faktycznie jest tam ten nieszczęsny warunek:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Tylko teraz pytanie - czy jeżeli wolę organizację pamięci jaka jest u Ciebie, to jedynym wyjściem jest rekompilacja newlib?

    Pzdr.
  • Specjalista - Mikrokontrolery
    alagner napisał:
    BTW - sbrk nie mogę sobie napisać od nowa/użyć Twoich syscallsów, bo ta funkcja już jest na stałe w librdimon zabudowana.

    Wydaje mi się, że jeśli napiszesz swoją i w procesie linkowania będzie ona WCZEŚNIEJ niż ta z biblioteki, to ją zastąpisz. W końcu tym sposobem można zastąpić praktycznie dowolną funkcję biblioteczną, co dosyć często się stosuje (np. __gnu_cxx::__verbose_terminate_handler(), __cxa_pure_virtual(), itd.)

    4\/3!!
  • Poziom 12  
    Podciągnę jeszcze temat z ciekawości. Dlaczego w skrypcie linkera ustawiona jest wartość 0 dla rozmiaru głównego stosu? Ten stos (w modelu z dwoma stosami) wykorzystywany jest przez przerwania, to nie powinien mieć jakiegoś rozmiaru?
  • Specjalista - Mikrokontrolery
  • Poziom 12  
    Ok, nie było pytania. O tym nie pomyślałem, ja szukałem jakiejś sprytnej sztuczki :)
  • Poziom 26  
    Znów się podczepię: jeśli chciałbym użyć pojedynczego stosu, to wtedy:

    w skrypcie linkera robię np. tak:
    Code:


    __stack_size = 2048;
    PROVIDE(__stack_size = __stack_size);

    (ciach)

       .stack :
       {
          . = ALIGN(8);
          __stack_start = .;
          PROVIDE(__stack_start = __stack_start);
          . += __stack_size;

          . = ALIGN(8);
          __stack_end = .;
          PROVIDE(__stack_end = __stack_end);
       } > ram AT > ram



    Następnie ze startupu wykopuję to:
    Code:

       ldr      r0, =__process_stack_end
       msr      PSP, r0


       movs   r0, #CONTROL_ALTERNATE_STACK
       msr      CONTROL, r0
       isb


    oraz podmieniam nazwę zmiennej w vectors.c na stosowną do tej z końcem stosu, czy tak?

    I pytanie drugie:
    czy jeżeli zamienię sobie miejscami heap i stack, np. tak jak tu:
    Code:

       .heap :
       {
          . = ALIGN(4);
          __heap_start = .;
          PROVIDE(__heap_start = __heap_start);
          *(.heap*)
          . = ALIGN(4);
          __heap_end = .;
          PROVIDE(__heap_end = __heap_end);
       } > ram AT > ram


       .stack :
       {
         
         
          *(.stack*)
          
       } > ram AT > ram
        PROVIDE(__stack_start = __ram_end - __stack_size);      
        __stack_end = __ram_end;
        PROVIDE(__stack_end = __stack_end);

    to czy linker policzy sobie rozmiar sterty i wszystko zatrybi? Bo, że analogicznie do stosu mogę sobie rozmiar ustalać, to wiem :)

    Z góry dzięki za odpowiedzi.

    Pozdrawiam

    EDIT: ok, zagapiłem się, poprawiam drugie pytanie ;)
  • Specjalista - Mikrokontrolery
    alagner napisał:
    jeśli chciałbym użyć pojedynczego stosu

    Proponuję po prostu w skrypcie linkera ustawić rozmiar odpowiedniego stosu (bodajże main), a drugi dać na zero, w startupie usunąć linijki które pokazałeś i nic więcej nie zmieniać.

    alagner napisał:
    czy linker policzy sobie rozmiar sterty i wszystko zatrybi?

    Nie, rozmiar musi być jakoś podany, ale jest to kwestia kilku odejmowań. Swoją droga jak szukasz lepszej konfiguracji, to naprawdę najlepszą jest stack - data - bss - heap - wyjście poza stos lub poza stertę generuje wtedy sprzętowy wyjątek, bo to sprawdzanie czy _sbrk() wjechał już w stos jest w ogóle bezsensowne.

    4\/3!!
  • Poziom 26  
    Wiesz co, generalnie kombinuję z różnymi konfigami. Stos na dole i sterta u góry faktycznie ma sens, sprawdzę to też.

    A to sprawdzanie czy _sbrk() wjechał w stos też mi nie leży. Ale zwróć uwagę, jak to jest rozwiązane domyślnie w wielu standardowych bibliotekach (co nie znaczy że jest to dobra metoda. Na pewno jakaś :P).
    Mogę oczywiście syscallsy sobie podmienić, teoretycznie to nie problem. Ale sam wiesz, że najpierw najlepiej dojść do momentu, że coś działa, a potem kombinować, także póki co wgryzam się w dokumentację gcc i staram się zrozumieć co jak i po co;)
  • Specjalista - Mikrokontrolery
    alagner napisał:
    A to sprawdzanie czy _sbrk() wjechał w stos też mi nie leży. Ale zwróć uwagę, jak to jest rozwiązane domyślnie w wielu standardowych bibliotekach (co nie znaczy że jest to dobra metoda. Na pewno jakaś ).

    W wielu przypadkach jest to tak rozwiązane, ale w tych "wielu przypadkach" (czyli na PC) programy korzystają z dynamicznej alokacji znacznie częściej niż się to robi na mikrokontrolerach, więc taki test ma pewien sens... Na mikrokontrolerach zaś to zwykle jest kilka alokacji na początku i potem może sporadycznie jakaś - na tym koniec. W takim przypadku ten test jest po prostu bezsensowny. Dołóż do tego fakt, że _sbrk() alokuje bloki po kilka kB (4?), dopiero potem te bloki są dzielone na mniejsze fragmenty w malloc(), więc równie dobrze w programie możesz mieć JEDNO wywołanie _sbrk() przez cały czas działania - na samym początku, przy pierwszej alokacji i potem już nic. A stos może "wjechać" w stertę wielokrotnie - nic tego nie wykryje.

    4\/3!!
  • Poziom 10  
    Jak wypełnisz na początku cały RAM znanym "patternem" to potem możesz stwierdzić jaka była minimalna "odległość" stery od stosu. Jeśli było ona poniżej powiedzmy 16 to jest duża szansa że wystąpiło nadpisanie.
  • Poziom 26  
    Odkopuję, szybkie pytanie:
    czy jeżeli chcę zrobić sobie układ stack-data-bss-heap, to taki skrypt będzie ok (bazuję na przykładach Freddiego, pokazuję jedynie modyfikacje, żeby tego za dużo nie było)
    Code:


       .ARM.exidx :
       {
          *(.ARM.exidx* .gnu.linkonce.armexidx.*);
       } > rom AT > rom                        /* index entries for section unwinding */  /*to po staremu */

       . = ALIGN(4);
       __exidx_end = .;
       PROVIDE(__exidx_end = __exidx_end);


    /* to juz nowosc */
       . = ALIGN(4);
       . = __data_init_start ;
       PROVIDE(__data_init_start = __data_init_start ) ;


    .stack :
       {
          . = ALIGN(8);
          __stack_start = .;
          PROVIDE(__stack_start = __stack_start);

          . = ALIGN(8);
          __main_stack_start = .;
          PROVIDE(__main_stack_start = __main_stack_start);

          . += __main_stack_size;

          . = ALIGN(8);
          __main_stack_end = .;
          PROVIDE(__main_stack_end = __main_stack_end);

          . = ALIGN(8);
          __process_stack_start = .;
          PROVIDE(__process_stack_start = __process_stack_start);

          . += __process_stack_size;

          . = ALIGN(8);
          __process_stack_end = .;
          PROVIDE(__process_stack_end = __process_stack_end);

          . = ALIGN(8);
          __stack_end = .;
          PROVIDE(__stack_end = __stack_end);
       } > ram AT > ram

    .data : AT (__data_init_start)
       {


          __data_start = .;
          PROVIDE(__data_start = __data_start);

          . = ALIGN(4);
          *(.data .data.* .gnu.linkonce.d.*)

          . = ALIGN(4);
          __data_end = .;
          PROVIDE(__data_end = __data_end);
       } > ram
       
       
    /* dalej bss i reszta po staremu */


    Nic się nie wysypuje (jeszcze), generalnie wydaje się, że jest ok. Pytanie - tak to ma być?
  • Specjalista - Mikrokontrolery
    Niezbyt... Czemu zmieniłeś sposób inicjalizacji sekcji .data? Ten który był oryginalnie jest naprawdę lepszy wielokrotnie (moim zdaniem oczywiście) od tego "popularnego", który właśnie zrobiłeś powyżej, w którym trzeba się przejmować wieloma dodatkowymi rzeczami, przy czym nie ma żadnej korzyści...

    Weź oryginalny skrypt, po prostu wytnij sekcję którą chcesz przenieść z oryginalnego miejsca i wstaw w inne.

    4\/3!!