A karom, a karom! Cortex-M0 kiadásban – egy kis linkeskedés (3. rész)

Az előző rész valahol ott maradt abba, hogy az alap² (alap×alap, alap a négyzeten) tesztek lefordulnak, majd a mikrokontrollerbe töltve futnak is, amit még debugolni is lehet. De nagyon fontos részek hiányoznak, emiatt még normális C-s programot nem lehet írni. Lassan (ismét eltellett egy év..?!) ideje lenne ennek is utána járni... Jelenleg a szimpla C érdekel, a C++ annyira nem. „Csak ezért” nem akarnék olyat csinálni, ami a pluszplusz használatát direkt gátolja, de majd kialakul.

Két olyan hiányosság is van, ami miatt a rendes programírás / -végrehajtás meghiúsul, mindkettő ugyanarra vezethető vissza, az eddig túl egyszerűre vett linker-script-re. Egyrészt nincs külön memóriatartomány definiálva a változóknak, ezen definíció hiányában a fordító a program utasításai mögé pakolná őket, de az ROM, nem írható. Másrészt a fordító a „gyári” inicializációs kódot (crt0C RunTime 0?) se tudja belepakolni a végeredménybe, szintén nem nevesített címek miatt.

Következő lépésnek ezek megoldása épp jó is lesz! De ide kívánkozik egy vastagon aláhúzott megjegyzés: Jelenleg éppen a megfelelő információkat szedném össze, bőven előfordulhat a későbbiekben akár tárgyi tévedés is! :) (Nem haragszom, ha ki leszek javítva.) Szóval ez is ilyen „jegyzet, magamnak”...

A tesztkód lehet az első LED kapcsolgatós változat, csak kerüljön bele a crt0 is. Ehhez a fordításkor a -nostartfiles paraméter kimarad a parancsból:

$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -T NUC140.ld main.c -o main.elf

Jelenleg persze nem fordul:

Van bőven hiányosság, ilyenek nincsenek definiálva mint __bss_start__, __bss_end__, _exit, meg még ki tudja mi.

De először talán a változókat kellene helyrerakni... Az előző tesztek közül a harmadik, a „boot-ROM”-os verzió linker-script-je tartalmaz címállítást; ez használható lenne itt is. De azzal a fajta megoldással két probléma is akad. Az egyik, hogy több ilyen terület definiálásakor a linker-script-ben „össze-vissza” pozícióban lesznek konkrét címek megadva, amiben egy idő után jól el lehet keveredni. De ez csak „esztétikai” probléma, szemben a másikkal: a µC-ben a különböző memóriáknak nem csak kezdőcíme, hanem hossza is van. :) Ha csak a címmutatót állítgatom ide / oda, a linker nem fogja tudni ellenőrizni, hogy az adott kód bele fér-e annyi ROM-ba / RAM-ba, amennyi az adott mikrovezérlőben van! (Maximum annyit tud vizsgálni, hogy átfedés két terület között nincs-e, de az ilyen esetben sovány.) A memóriaterületek (memóriarégiók) definiálása viszont megoldható, a linker ad erre lehetőséget:

MEMORY
{
  FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 128K
  SRAM  (w)  : ORIGIN = 0x20000000, LENGTH = 16K
  STACK (w)  : ORIGIN = ORIGIN(SRAM)+LENGTH(SRAM), LENGTH = 0K
}

Az elején a terület neve szerepel, ezek teljesen önkényesen megadottak, akár lehetne BEJLA meg GEJZA is. Annyi a szerepük, hogy a későbbiekben ilyen néven lehet rájuk hivatkozni. (A neten talált linker-script példák zömükben ilyen megnevezéseket használnak, maradok én is ezeknél.) Utána jön (zárójelben) némi attribútum, itt az rx a Read-Only tartományt, plusz a futtathatóságot jelzi, ez nyilván ROM. A w az Read/Write területet jelent, ez – értelemszerűen – már RAM lesz. (A netes példákban is van ilyen definíció, ott sokszor rw attribútum szerepel sima w helyett. Ez látszólag ellentmondás; de itt talán arról van szó, hogy a linker az ilyen memóriarégióba pakolhat csak olvasható és írható / olvasható területet igénylő végeredményt is. Már persze ha jól értem a leírást.)

Az ORIGIN az adott terület kezdőcímét adja meg, a LENGTH meg a hosszát. Később, ha egy rész ezen területek valamelyikében lesz elhelyezve, akkor a linker már tud kukorékolni, ha kicsúszik belőle a belevaló.

Ennél a MEMORY definíciónál tulajdonképpen annyi történik, hogy az egy-egy sor által leírt memóriaterülethez a linker hozzárendel egy „virtuális címet”, aminek a kezdőcímét beállítja az ORIGIN értékére. Amikor az adott területre bekerül majd valami, akkor ezt a „virtuális címet” növeli a kívánt értékkel automatikusan. A későbbiekben egy-egy területhez több lépésben is lehet adatokat hozzáfűzni, mindig a kiválasztott terület „virtuális címét” fogja használni / módosítani.

A fenti példában három definíció szerepel, a ROM-hoz egy FLASH nevű, ami 128 KBYTE-os terület, a RAM-hoz egy SRAM nevű, ami 16 KBYTE. STACK néven a verem kezdőcím definíciója is ide került, de ez nem kötelező itt, megadható később a RAM-hoz képest is. Jelenleg az SRAM végére van állítva, a vermet onnantól visszafele tölti adatokkal. (A Cortex-M0 esetén ez talán így van, de egyéb CPU architektúrák esetén nem szükségszerű, hogy ilyen irányba töltődjön a verem! Van olyan ISA, ahol ezt a programozó dönti el!) Azért került ez most ide, hogy ezek a címek „egy blokkban legyenek”, ugyanis a különböző memória-konfigurációjú µC-ek között csak ez lesz a különbség, a linker-script többi része így lehet állandó. A verem meg nem szükségszerűen kell hogy a memória végére kerüljön. (Lehet előnye annak, ha máshol van...)

Következne a SECTIONS rész, de az, hogy itt milyen részeket kellene beállítani, arra nem találtam közvetlenül semmi használható leírást. :( Itt azoknak a részeknek kellene területet definiálni, amik a kódban külön választhatóan előfordulnak. Az egyszerű linker-script-em két területet definiált, a .vectors illetve a .text részt, azt is a .text területen belül. (Ö... :) ) Általában négy területről van szó a leírásokban:

  • .text – ide kerül a lefordított kód maga, ez az, ami fut
  • .rodata – ide kerülnek az állandók
  • .data – ide kerülnek az inicializált adatok
  • .bss – ide kerülnek a nem inicializált adatok, ez a terület a program indulásakor törlendő

Egyrészt a .vectors sehol sincs, de úgy tűnik, hogy ezt a fordító direkt nem is igényli! (Ezt még érdemes lesz körbejárni; valahonnan csak „tudja”, hogy azon a részen hogyan kell az adatokat lepakolni. Első ránézésre a terület neve nem számít!) A .text rész tiszta sor, az megy a ROM területre. A .rodata rész tartalmazza a konstansként definiált változókat, ez szintén lehet ROM-ban. A .data viszont változókat tárol, annak RAM-ban a helye, de erre a területre valahonnan oda kellene kerülnie azoknak az értékeknek, amikkel inicializálni kell a program indulásakor őket. A fennmaradó .bss RAM kell hogy legyen, ennek a törlését, illetve az inicializált adatok ROM-ból RAM-ba másolását viszont meg kell oldani. Reményeim szerint ezt el kellene végeznie a crt0-nak, annak a beillesztése ezért lenne érdekes.

A teszteléshez módosítani kell a main.c-t, hogy legyen benne inicializált illetve inicializálatlan változó használat. Meg kerülhet bele const is némi szöveggel, mivel ez teszt. De ha már módosul a forráskód, akkor itt bepróbálkozok az első #include-dal is... A C nyelvnek van egy olyan (igencsak hülye) tulajdonsága, hogy bizonyos típusú változók értéktartománya implementációfüggő. Ilyen például az int, ami lehet 16 meg 32 (és így tovább..?) bites is. Itt ugyan 32 bites, de ha ezen gondolkozni kell, az nem a legszerencsésebb. Ezen „probléma” kiküszöbölésére remek megoldás az stdint.h, ebben olyan típusdefiníciók vannak, amik platformfüggetlenül fix méretűek. A későbbiekben erre az inklúdra úgyis szükség lesz, ezért a többi példa ennek a használatával készül. A kódban kell .data, .rodata illetve a .bss részhez tartozó adatokat is használni, hogy lehessen látni, a fordító mit is csinál velük. Ha a LED kapcsolgatós tesztkód kiegészül egy inicializált meg egy nem inicializált változóval illetve némi konstanssal, az kb. ezt a célt meg is valósítja. A két változó egy-egy GPIO portra ki lesz írva, így megnézhető, hogy az inicializálás megtörtént-e valójában. (De itt még a működőképesség se fontos, csak az, hogy lássam, a fordító mit is csinál.) A tesztkód main.c tehát így néz ki:

#include <stdint.h>

#define GPIOA_PMD  ((volatile uint32_t *) 0x50004000)
#define GPIOA_DOUT ((volatile uint32_t *) 0x50004008)
#define GPIOA_PIN  ((volatile uint32_t *) 0x50004010)
#define GPIOC_PMD  ((volatile uint32_t *) 0x50004080)
#define GPIOC_DOUT ((volatile uint32_t *) 0x50004088)
#define GPIOD_PMD  ((volatile uint32_t *) 0x500040c0)
#define GPIOD_DOUT ((volatile uint32_t *) 0x500040c8)

const uint32_t con1 = 0x98765432;
const uint8_t txt1[] = "Hello!";

uint32_t var1 = 0x12345678;
uint32_t var2;

int main (void) {
  *GPIOA_PMD = 0x0000000d;    // PORTA 15..2 Input, B1 QuasiBiDir, B0 Output
  *GPIOC_PMD = 0x55555555;    // PORTC 15..0: Output
  *GPIOD_PMD = 0x55555555;    // PORTD 15..0: Output
  while (1) {
    *GPIOC_DOUT = var1;
    *GPIOD_DOUT = var2;
    if ((*GPIOA_PIN & 0x00000002) != 0) {
      *GPIOA_DOUT &= 0xfffffffe;
    } else {
      *GPIOA_DOUT |= 0x00000001;
      var1++;
      var2++;
    }
  }
}

extern uint32_t __stack;
extern uint32_t _mainCRTStartup;

uint32_t *myvectors[2] __attribute__ ((section(".vectors"))) = {
  (uint32_t *) &__stack,          // SP
  (uint32_t *) &_mainCRTStartup   // Main
};

A komplett GPIOC illetve GPIOD most ki lett nevezve a teszt idejére kimenetnek, amire a két változó (var1 ill. var2) kiíródik, illetve a kapcsoló állapotától függően ezen változók növelődnek. A tesztkód végén a vektortáblában a veremmutató alapértéke a linker-script-ben kiszámolt cím lesz. Az, hogy ez elérhető legyen itt, ahhoz a következő sor kell még oda is:

PROVIDE (__stack = ORIGIN(STACK));

Ez __stack néven elérhetővé teszi a külvilág számára a MEMORY részben definiált STACK címét. Erre a main.c-ben egy extern után lehet hivatkozni. Ezen felül kell még a szakaszok definíciója:

SECTIONS
{
  .text : {
    *(.vectors)
    *(.text)
  } > FLASH
  .rodata : {
    *(.rodata)
  } > FLASH
  .data : {
    *(.data)
    *(.bss)
  } > SRAM
}

Egy adag próbálgatás után ez jött össze. Ezen linker-script-tel a vektorok, a kód illetve a „csak olvasható” adatok a FLASH elejétől jönnek, az inicializált adatok, utána az inicializálatlanok meg az SRAM kezdetétől. (Furcsaság: ha a .rodata részt csak simán a *(.text) után rakom, nem külön blokkba, akkor is a megfelelő helyre kerülnek a csak olvasható adatok, de az .elf-ben nem lesznek a nevükön nevezve. A .bss bezzeg igen! :) )

A fordítás továbbra is az eddigi parancs:

$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -g -nostartfiles -T NUC140.ld main.c -o main.elf

A -nostartfiles nélkül továbbra se fordul, a hiányzó címek egyelőre még nincsenek definiálva, de – remélem – az is jön hamarosan. A „foglalt” memóriamennyiségekről szintén a szokásos parancs tájékoztat:

$ arm-none-eabi-size -tAx main.elf

main.elf  :
section            size         addr
.text              0x9c          0x0
.rodata             0xb         0x9c
.data               0x4   0x20000000
.debug_info       0x142          0x0
.debug_abbrev      0x95          0x0
.debug_aranges     0x20          0x0
.debug_line        0xa7          0x0
.debug_str        0x10c          0x0
.comment           0x27          0x0
.ARM.attributes    0x31          0x0
.debug_frame       0x2c          0x0
.bss                0x4   0x20000004
Total             0x4dd

A .text 0x00000000-n kezdődik, 156 BYTE hosszú, ez akár még reális is lehet. Utána jön közvetlenül a .rodata 0x0000009C-től, ami 11 BYTE hosszú. (Van egy 32 bites szám 4 BYTE-ban, meg egy „Hello!” string a végét lezáró 0-val, az 7 BYTE, összesen 11. Jé, ez is kijött... :) ) A .data 0x20000000-n kezdődik, tartozik hozzá 4 BYTE-nyi adat, a definiált alapérték, ez is rendben. Meg a .bss, 0x20000004-től szintén 4 BYTE. Ez érdekes módon azt csinálja eddig, amit kell... :-D A többi cucc az .elf fájlban van csak, ezek a µC-be programozandó firmware-be majd nem kerülnek bele. Az eddig használt objdump parancs csak a .text részét diszasszemblálja az .elf-nek, de az

$ arm-none-eabi-objdump -D main.elf

parancs megpróbál mindent. (-d helyett -D a paraméter.) Az adatokat ugyan nem érdemes visszakódolni mnemonikokra, de látszik mellette a nyers adat is, hogy miből is készült. A végeredményben ott látszik az összes .debug* rész is, az most teljesen érdektelen. Néhány érdekes részlet azért akad:

Disassembly of section .text:

00000000 <myvectors>:
   0:	20004000 	andcs	r4, r0, r0
   4:	00000009 	andeq	r0, r0, r9

00000008 <main>:
   8:	b580      	push	{r7, lr}
   a:	af00      	add	r7, sp, #0
   ...

Ez az eleje, a vektorokkal meg a kezdéssel. Aztán:

Disassembly of section .rodata:

0000009c <con1>:
  9c:	98765432 	ldmdals	r6!, {r1, r4, r5, sl, ip, lr}^

000000a0 <txt1>:
  a0:	6c6c6548 	cfstr64vs	mvdx6, [ip], #-288	; 0xfffffee0
  a4:	Address 0x00000000000000a4 is out of bounds.
  ...

Ide került a futtatandó kód után a két állandó, a második a „Hello!” szöveg, az eleje még látszik is. :) Van még két rész:

Disassembly of section .data:

20000000 <var1>:
20000000:	12345678 	eorsne	r5, r4, #120, 12	; 0x7800000
  ...
Disassembly of section .bss:

20000004 <var2>:
20000004:	00000000 	andeq	r0, r0, r0
  ...

Ez az inicializált / a nélküli változók helye, ezek már a RAM területére kerülnek, a helyükre. A címek azok rendben vannak, de a .data tartalma nem ide kell, hanem a ROM-ba, a felhasznált területek utánra, az inicializáció onnan másolja majd ki ide, a RAM-ba a main() indítása előtt. Az eddig megszokott objcopy parancs is egy röpke 512 MBYTE-os kimeneti fájlt produkál, (0x20000000 = 512 Mega, ) ami szintén nem a „legszebb”. Ahhoz, hogy a .data részbe került adatok alapállapota majd a ROM területre kerüljön, az alábbi módosítást célszerű eszközölni a linker-script-ben:

  .data : {
    *(.data)
  } > SRAM AT > FLASH
  .bss : {
    *(.bss)
  } > SRAM

A .data tartalma „át lett irányítva” a ROM eddig még nem használt területére, plusz ebből a részből kikerült a .bss szekció, az külön lett definiálva (mivel az a terület 0-val lesz feltöltve, fölösleges eltárolni).

Az objdump címei továbbra is a RAM-ba mutatnak, így nem biztos hogy érdemes annak a kimenetét vizsgálni ebből a szempontból... :( De az objcopy ismét normális méretű fájlt generál, amibe most „bambán” belenézve:

A kurzor a 0x98765432 értékű 32 bites konstans elején áll, utána jön a „Hello!” szöveg a lezáró 0-val a végén. Ezek a .rodata rész tartalma. Utána jön a 0x12345678 érték, ez már a .data inicializációs adata, pontosan ez volt az elvárás! Csakhogy... Van itt egy kis bibi. A Cortex-M0 a 32 bites adatokat, amik 4 BYTE-os csoportok, 4-re kerek címekről tudja csak olvasni. (Egy kis kitérő: az ARM a 32 bites adatokat hívja WORD-nek, azaz szónak. A 16 biteseket HALFWORD-nek (félszónak), míg a 8 bitesek – szerencsére – maradtak BYTE-ok. Nekem a 68K LONG / WORD / BYTE megnevezései szimpatikusabbak, de sebaj.) A .data viszont oda került most itt, „ahogy kijött”. A rendes, RAM-beli címükkel ugyan semmi baj sincs, viszont innen oda át kell majd másolni. Valójában ez nem egy nagy trauma, mert az inicializációs rutin másolhatja ezt BYTE-onként is, ami ugyan jóval lassabb; az inicializáció úgyis csak egyszer fut induláskor, ott ez még elnézhető is lenne. De ezen egy egyszerű igazítással lehet segíteni, a linker-script ide vágó része így változik:

  .rodata : {
    *(.rodata)
    . = ALIGN (4);
  } > FLASH

Ez a .rodata utáni első nem használt címet 4-gyel oszthatóra kerekíti. (Ha kell, beszúr 1,2 vagy 3 db. 0x00 BYTE-ot.) Ezzel leforgatva:

A megfelelő helyen megjelent egy 0x00, a cím „kikerekedett”. Ezután már mehet a 32 bites adatok másolása, de hogy a későbbiekben se legyen ilyen kavarodás, a .data és a .bss méretét is érdemes így kiigazítani. (A fordítóról feltételezem, :) hogy nem fog egy 32 bites adatot neki nem „kerek” címre pakolni, de ha az adott blokkban nem 32 bites az utolsó változó, akkor nem kerek címre kerül a vége. Így kimásoláskor / törléskor a hozzá tartozó algoritmus vagy „túlír” a tartományon, vagy a legvégét nem másolja / törli. Jó az, ha kerek, na!) Sőt: én még a .text végére is beraknék egy igazítást; eddig az 32 bitre kerek volt, de – mivel az M0 utasításai nagyrészt 16 bitesek – nem tuti, hogy mindig úgy is lesz!

A kezdeti, egyszerű linker-script-em kezd egy kissé elbonyolítódni, de a címekhez tartozó nevek, amik a startup kódhoz kellenek, még nincsenek sehol... Ezt a részt a crt0 tartalmazza, ennek a forrása megtalálható a newlib fájljai között. (A forrásfán belül a newlib/libc/sys/arm/crt0.S, ha jól nézem.) Elég szépen meg van pakolva #if-fel... Az alapból hiányzó két címke (__bss_start__ / __bss_end__) egyértelmű, a linker-script megfelelő része ilyen lesz:

.bss : {
  __bss_start__ = . ;
  *(.bss)
  . = ALIGN (4);
  __bss_end__ = . ;
} > SRAM

Ezen felül hiányolja még az _exit címkét, na de ne má'... :) Egy mikrokontrolleres környezetben nem lesz úgyse kilépés. Ennek a beállítását a fenti módon berakva a .text terület végére „behazudva”, – dobpergés, – lefordul!

No de... Izé... Egyelőre nincs a .data másolata sehol nevesítve! Ez így nem fogja a RAM-ba pakolni az adatokat induláskor... A végeredmény bináris fájl 2376 BYTE lett, ami egy kissé húzós az eddigi 172 BYTE-hoz képest. Meg a vektorok... Hol is lenne ennek a crt0-nak a belépési pontja? Az objdump által generált listát nézve talán a _mainCRTStartup lenne az (ebben van main-ra hivatkozás), de egyelőre az ARM/Thumb utasításkészletet „nem olvasom”. Ennek a címét egy extern-nel láthatóvá lehet tenni a C-s forrásban, a fenti példában a „main” vektora le is lett cserélve erre. No de van-e egyáltalán értelme ezt kipróbálnom rendes vason? Meg mi lett ez a több mint 2 KBYTE? A több mint 1K-nyi RAM-foglalásról nem is beszélve. Á... Van egy olyan érzésem, megint sikerült besétálni az erdőbe. A newlib nem kifejezetten µC-es környezetre van optimalizálva, kizárt, hogy erre a több mint 2 KBYTE-ra szükségem lenne. (Ha valami funkcióhoz a későbbiekben mégis kellene ezen rutinok közül valami, a linker jó esetben úgyis szólni fog, akkor majd visszatérek rá.) Pláne, ha most csak amolyan „magasabb szintű assembly”-nek akarnám használni a C-t... Mindegy, ez a próbálkozás most már itt marad. A Cortex M0-t amúgy is azzal „reklámozta” az ARM, hogy ez egy olyan mikrovezérlő architektúra, ahol a programozónak egyáltalán nincs szüksége semmit assemblyben megírni. Az adatmásolást + bss törlést meg tudom talán én is csinálni, de valószínűleg :) ezt már megírták páran helyettem.

Tehát van crt0-m – de minek, jöjjön egy rakás #include! A fenti tesztkód elején van pár definíció, ami a µC-ben levő hardverek címeit tartalmazza. Viszonylag „munkás” lenne a feladat, hogy az éppen érdekes részek paramétereit az ember kikeresse az adatlapból, aztán – lehetőleg – hiba nélkül beírja a programjába. De nem is kell, erre a célra szoktak a gyártók adni egy olyan definíciós fájlt, amiben a perifériák, meg ami még érdekes, az „nevesítve van”, ezt behúzza a programozó a programjába, aztán lehet nevekkel hivatkozni ezekre, mindenféle mágikus számok helyett. Az első meglepetés itt ért, ugyanis itt is van ilyen. De a komplett mikrovezérlő szériához egy darab, közös fájl(csoport) tartozik! Amivel eddig találkoztam (8 bitesek...), azoknál minden típushoz van „saját”, amiben csak olyan perifériák vannak definiálva, ami az adott tokban létezik is. Itt ömlesztve megvan minden. Igazából ez csak „esztétikai” kifogás a részemről; de itt simán le fog fordulni egy mondjuk USB perifériát megvalósító program egy olyan típusnál is, amiben nincs is USB perifériavezérlő hardver! (Persze: miért is akarnék én ilyet csinálni? :) A 8 bites társaknál ez mondjuk érthető: kicsi a címtér, az azonos architektúrájú, de különböző típusokban ugyanazon a címen lehet más fajta periféria is. Itt – gondolom – fixek a perifériacímek, ha valami nincs jelen az adott tokban, ott „üres” marad az a terület, aztán kész.)

A gyári inklúdoknak még az előző rész után nekiugrottam, voltak is vele sikerek. De azóta eltellett egy év (valamit említettem a nyögvenyelős haladásomról a témában...), úgyhogy nekiállhatok újra. Ezek a „header” fájlok szerepelnek a fejlesztői csomaghoz kapott CD-n is, de célszerű lenne ezekből a legfrissebb verziót használni, abban nagyobb eséllyel kevesebb hiba lesz. Tavaly még csak-csak találtam valamit a gyártó oldalán, de most a tipusra (Nu-EVB) keresve konkrétan 0 (nulla) találat van erről a devkitről. (Igen, az interneten minden megmarad. Ja, persze.) De talán nem is szükséges a konkrét fejlesztői lap, ami most kell, azt az adott típusú mikrovezérlő családhoz tartozó BSP (Board Support Package) tartalmazza. Amit valami miatt a driver oldalon kell keresni. :) Itt a NUC100/120 Series BSP lesz elvileg a jó csomag, ami szerencsére megtalálható a githubon is.

Viszont! (Ez megér egy új bekezdést...) A gyári oldalról (most, amikor ezt írom, 2019.02.12.) a V3.00.004 verziószámú csomag jön le. A github-os oldalon nincs egyelőre kész „release” csomag, csak az éppen aktuális snapshot van, de az abban levő doksi is ugyanezt a verziószámot mondja magáról. A két pakk viszont mégsem egyezik... A gh-os csomag ugyanis tartalmaz egy adag GCC nevű könyvtárat, a SampleCode-ban ráadásul eclipse projektfájlok is akadnak az eddigi KEIL meg IAR fájlok mellett. Nofene! Mit is látni a gyári oldalon fejlesztői környezetnek? Ott a NuEclipse elsőnek, ráadásul van belőle Linux-os verzió is! Ez aztán a meglepetés! Egyelőre kipróbálni még nem mertem. :-D De a BSP archívban körülnézve, a Library könyvtár mélyén is akad GCC-s könyvtár, benne két érdekes fájllal. Az egyik a startup_NUC100Series.S, ami pont az, amit „kergetek”: vektortáblástól, .data-másolóstól, .bss-törléssel egybepakolt main() indító. A másik neve gcc_arm.ld, ami meg pontosan az, amivel itt fent küzdök, egy megfelelő linker-script, amivel a helyére tud kerülni minden. Ej, de sok minden történt az elmúlt egy évben... :-D De a GCC-s részek egyike sincs benne a gyártó oldaláról letölthető pakkban! (Amit kb. 1 évvel ezelőtt letöltöttem, az ugyanez. A github-on 2018.10.21. dátummal szerepel a GCC-s módosítás, szóval nem olyan régi dolog ez!)

A jelenlegi állás szerint a fenti linker-script-es küzdést akár ki is törölhetném, van egy kész konzerv megoldás, lehet azt használni, aztán ennyi. Amiatt talán mégis megéri itt hagyni, hogy a fenti próbálkozással legalább lett valami apró fogalmam arról, mi is történik itt. Illetve jó lesz ez a további tesztekhez is, mert innen el lehet indulni két felé. Az egyik a „gyári minden” használata, ez marad későbbre, a másik a jelenleg elért állapot tovább faragása, amivel talán sikerül azt az állapotot tartani, hogy a µC-be tényleg csak olyan kód van, ami tudom is, hogy mit csinál. :) Mindkettőben lesznek nekem újdonságok, ebben biztos vagyok.

Következő lépésnek jöhetne a fenti tesztprogram átalakítása úgy, hogy a gyári regiszterdefiníciós fájlt használja. A fejlesztőkészlet CD-n van egy NUC1xx.h nevű fájl, ami pont ez lenne, de önmagában kevés, behúz még pár headert, CMSIS meg egyéb kötődéssel. Pedig ez a fájl még „emészthető” is nagyjából, de legalább jó régi. Inkább a gh-ról letöltött BSP-ben levő mostani verzióval lesz érdemes próbálkozni, ha már. De hogy ezt „hivatalosan” hogyan is kellene használni... Akkor most egy kicsit tákolok. :)

A tesztkód mellé készül egy Include könyvtár, először abba jöhet a NUC100Series.h. (Ez egy 780KB-os (!) fájl, nagy része megjegyzés, a komplett dokumentáció bele van pakolva ilyen formában. Tele van „fura” karakterekkel; biztos van olyan megjelenítő / editor, amiben ez szépen, formázva látszik...) Ezt beinklúdolva a forrásba, persze egyből hiányolja még a core_cm0.h-t. Ez CMSIS-es definíciós fájl, ezt is mellé másolva kezdődik a bonyodalom. :) Ugyanis ez további #include-okat tartalmaz, de nem idézőjeles nevekkel (#include "valami"), hanem kisebb/nagyobb jellel (#include <valami>). Így a „telepítési” útvonalakon keresztül keresi a fordító őket, az meg (egyelőre?) nem fog eredményre vezetni. Vagy átírom idézőjelesre az adott sor(oka)t, vagy kap a fordítás egy külön paramétert, hogy az esetleges inklúdokat merre keresse. Talán az utóbbi lenne a célravezetőbb, gyaníthatóan az IDE-k is ezt teszik.

Az lesz, hogy: új Include directory, ezen belül lesz még két könyvtár, az egyik a CMSIS, ebbe három fájl kell végül: core_cm0.h, core_cmFunc.h, illetve core_cmInstr.h. (Ezek a BSP archív Library/CMSIS/Include könyvtárából másolhatók.) A másik könyvtár legyen a System, ide kell a NUC100Series.h, meg az, amit még beilleszt. Ebből az egyik a system_NUC100Series.h. (Ez a kettő a BSP Library/Device/... alatt vannak.) Ezen felül van egy rakás, a perifériák használatát segítő lib, ezek a Library/StdDriver/inc-ben találhatók, (a ../src alatt a forrásokkal,) az itteni összes header mehet az Include/System alá. (A main.c-ben a megfelelő sor az #include <NUC100Series.h> változatra módosul.) Az így összeállított két extra könyvtárat a fordítónak megadva már majdnem jó is a végeredmény:

$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -g -nostartfiles -I Include/System -I Include/CMSIS -T NUC140.ld main.c -o main.elf

Azt mondja, nincs SYS.h. És valóban, a sys.h fájl lett átmásolva... Azért Linuxos NuEclipse ide, GCC-s fájlok oda, csak nem bírnak elszakadni a Windows-tól, úgy látom. :-D A 11105-ös sor a fájl vége körül van, az összes driver nagybetűs névvel szerepel, szemben a lemezen levő kisbetűs elnevezéssel. Ezeket mindet kijavítva, lefordul a tesztkód! Ugyan használva még nincs semmi a definíciókból, de legalább nincs hiba... Jöhet a teszt átírása! Egy kicsit egyszerűbb lett:

#include <NUC100Series.h>

const uint32_t con1 = 0x98765432;
const uint8_t txt1[] = "Hello!";

uint32_t var1 = 0x12345678;
uint32_t var2;

int main (void) {
  PA->PMD = 0x0000000d;    // PORTA 15..2 Input, B1 QuasiBiDir, B0 Output
  PC->PMD = 0x55555555;    // PORTC 15..0: Output
  PD->PMD = 0x55555555;    // PORTD 15..0: Output
  while (1) {
    PC->DOUT = var1;
    PD->DOUT = var2;
    if ((PA->PIN & 0x00000002) != 0) {
      PA->DOUT &= 0xfffffffe;
    } else {
      PA->DOUT |= 0x00000001;
      var1++;
      var2++;
    }
  }
}

A vektorok egyelőre a szokásos módon vannak beillesztve, de a GPIO kezelése a gyári struktúradefiníciók használatával megy. Érdekes módon a lefordított bináris 172 helyett 156 BYTE hosszú. Ugyan még mindig nincs optimalizáció bekapcsolva, de valami mégis csak változott. :) Itt egy részlet:

  52:	4b07      	ldr	r3, [pc, #28]	; (70 <main+0x68>)
  54:	2101      	movs	r1, #1
  56:	430a      	orrs	r2, r1
  58:	609a      	str	r2, [r3, #8]
  ...
  70:	50004000 	.word	0x50004000

Ez az 0x50004008-ra írás, címnek 0x50004000 (GPIO PA báziscím) van betöltve, ahhoz képest 8-as indexszel (báziscím + 8 = DOUT) történik meg a kiírás. Így elég csak a báziscímet eltárolni, a struktúra elemeinek az elérése megy az utasításkódba rakott indexszel. Az előző tesztnél minden perifériaregiszterhez külön cím tartozott, azok listája lett így rövidebb. Valószínűleg ez gyorsabban is fut, mivel nem kell minden alkalommal új címet felolvasni a programnak. (Egy kicsit érdemes lesz azért ezt az ARM Thumb2 assembly-t áttanulmányozni. Ez az utasításkódba pakolt indexelés gyanúsan erre a struktúrában elemcímzésre van kihegyezve. :) )

Na, ez legalább nem volt túl bonyolult. Van egy olyan érzésem, hogy a rendes tesztkód ismét nem most fog elkészülni... :( De két feladat még ide kívánkozik. Az egyik: ideje lenne most már megcsinálni a „minimál starter” programot. Az egyszerűség kedvéért ezt is C-ben, legyen valami ilyesmi:

#include <stdint.h>

extern uint32_t __stack;
extern uint32_t __etext;
extern uint32_t __data_start__;
extern uint32_t __data_end__;
extern uint32_t __bss_start__;
extern uint32_t __bss_end__;

int main(void);

void startupRoutine(void) {
  uint32_t *rdptr;
  uint32_t *wrptr;
  rdptr = &__etext;
  wrptr = &__data_start__;
  while (wrptr < &__data_end__) {
    *wrptr = *rdptr;
    rdptr++;
    wrptr++;
  }
  wrptr = &__bss_start__;
  while (wrptr < &__bss_end__) {
  *wrptr = 0;
  wrptr++;
  }
  main();
  while(1) {
  }
}

uint32_t *myvectors[2] __attribute__ ((section(".vectors"))) = {
  (uint32_t *) &__stack, // SP
  (uint32_t *) &startupRoutine // Startup
};

Vajon mennyi sületlenséget írtam ide? :) Mert hogy látszólag az történik, amit akartam. Az elején a rakás extern a linker-script-ben kiszámolt címeket teszi láthatóvá. Az első while ciklus az inicializált adatokat másolja át a RAM-ba, a második a .bss területet törli. (A ciklusok 32 bites adatokkal dolgoznak / számolnak, de a címek ekkorára vannak kerekítve, így jónak kell lennie így ennek.) Azután meghívódik a main() függvény, majd ha ez véletlenül véget érne, egy végtelen-ciklus lesz a „befejezés”. A ROM-ból kimásolandó adatok kezdőcíme __etext néven lett definiálva a linker-script-ben, a .data eleje / vége meg __data_start__ ill. __data_end__ néven. (Mindegyik ismerős már az eddig látott linker-script-ekből, de a „frissen talált”-ban is ilyenek vannak.) A main.c az maradt a fenti, annyi módosulással, hogy kikerült belőle a vektortábla, mivel az átköltözött ide. Fordítani így kell:

$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -g -nostartfiles -I Include/System -I Include/CMSIS -T NUC140.ld startup.c main.c -o main.elf

A kimásolt végeredmény egy 240 BYTE-os fájl, ami nem épp' kicsi, de „normális”. Viszont futtatva, a .bss törléssel mintha baj lenne... :| Némi nyomozás után kiderült, hogy a __bss_end__ értéke megegyezik a __bss_start__ értékével, a linker ugyan számolja a változók címét, de nem foglalja le a helyet. (?) Puskázva a „talált” linker-script-ből, a következő lett a .bss definíciója:

  .bss : {
    __bss_start__ = . ;
    *(.bss)
    *(COMMON)
    . = ALIGN (4);
    __bss_end__ = . ;
  } > SRAM

Az eddigiekhez képesti különbség a *(COMMON) beillesztése. Érdekes módon ezek után már jó a végének a címe, működik a törlés is.

A másik feladat ugyanez a tesztkód, csak a gyári linker-script / startup párossal... Most már ez sem tűnik akkora varázslatnak. Egyrészt a BSP-ből jön a gcc_arm.ld, mint linker-script, aztán ugyanonnan a startup_NUC100Series.S assembly forrásfájl, ezek mennek a main.c mellé. Ezen felül kelleni fog még a system_NUC100Series.c, ez is ugyanoda kerül. A fordítás a következő:

$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -I Include/System -I Include/CMSIS -T gcc_arm.ld system_NUC100Series.c startup_NUC100Series.S main.c -o main.elf

Igen, ebből kimarad a -nostartfiles paraméter, mert anélkül nem fordul holmi hiányzó _start címke miatt. Viszont azzal elkészül, kimásolva lesz belőle egy 3244 BYTE-os (!) fájl. Hát, hogy is mondjam, nem kevés... :-D Igen, ebben „mindkét” startup benne van, az, amit a newlib hoz magával, és az is, amit a csiphez a gyártó ad. Az utóbbi mérete se túl kicsi, ahogy számolgatok... No de miért is „idegesít” ez a 3.2 K, mikor a tokban 128 K FLASH memória található a programom tárolására..? Mondanám is, hogy tulajdonképpen „elfér”, így ez igaz is. De ha – teszem azt – bootloader-t szándékoznék írni, ahhoz a tokban összesen 4 KBYTE FLASH tartozik. Ott – csak úgy – nem dobnék ki 3.2K-t... A NUC start kódja is meg van pakolva #if-fel, át kell azt is vizsgálni, mit lehet rajt' konfigurálni, mert van benne pár érdekesség.

Azt hiszem ismét sikerült elveszni a részletekben. Ideje lenne némi konklúziónak, plusz pár kiegészítésnek:

  • Ez a linker-script téma – úgy látom – egy külön tudományág. Megnézve a gyári változatot, bőven van benne olyan, amire még csak tippem sincs, hogy mi az a sok minden! Az is valószínű, hogy egyelőre meglennék a nagy részük nélkül; amit én faragtam össze, az is le tudja fedni a jelenlegi igényeim, de ez tényleg akkor fog majd kiderülni, hogy ha elkészül a „rendes” tesztprogramom.
  • A githubon talált „gyártói” startupnak tényleg megörültem, de ilyen – természetesen – van a fejlesztői eszközhöz kapott lemezen, két fajta is. Csakhogy azok egyike sem fordítható a GCC/as párossal, mivel más a szintaktikájuk. A felhasználáshoz portolni kellett volna valamelyiket, de ezt inkább rábíztam volna valaki hozzáértőre. (De régebben is létezett már GCC-s eszköz ehhez a µC családhoz, így ezt biztosan megcsinálták már.) Végignézve a forrást, nem mondom, hogy világosan értek benne mindent... :)
  • Maradva még a NUC startup-nál: alapból nem véletlenül nem fordul le, ha a crt0 nincs berakva a végeredménybe. Saját maga a .bss területet nem törli, azt meghagyja a newlib megfelelő részének. Viszont feltűnően sok benne a feltételes fordítási ág, valahogy lehet ez kívülről konfigurálni, mert a forrásban amúgy van .bss törlés. Meg még pár egyéb dolog. A fent hiányolt _start címke is ilyen, ha lenne „kívülről” __START definiálva, azt használná. Amúgy a _start tényleg a _mainCRTStartup címre fordul le, ez lesz a crt0 belépési pontja.
  • A fellelt Linuxos NuEclipse szintén meglepetés, attól függetlenül, hogy kipróbálni még nem mertem. :) (Először talán egy virtuális gépben lesz majd megvizsgálva...) Kérdés, hogy az első részben emlegetett NuLink-et tudja-e használni Linux alól is, ez is megvizsgálás tárgya még. Mivel a tok felprogramozása egyelőre megoldottnak tűnik, annyira mondjuk nem is izgat...
  • Szintén az első részben gondolkoztam a LOCK biten, meg a másodikban, hogy azt vajon az OpenOCD – a nyakatekertnek tűnő programozási megoldás miatt – ki tudja-e majd törölni? Ez közben kiderült: ki tudja. Még 2011 táján, amikor ez az egész nálam elkezdődött, egy másik fajta – ebből a szériából való – µC-t felterveztem egy áramkörre amolyan másodlagos megvalósításnak, de aztán az a rész használva sose lett. Egy ilyen áramköröm van így, egy NuMicro-val összeszerelve; azon ki mertem próbálni a LOCK bekapcsolását. Miután a konfigurációs „fuse” bitekben ez a bit beégetésre kerül, minden marad a régiben. :) Az első áramtalanításig, legalábbis. Onnantól csak ezt a konfigurációs szót, meg az azonosítót lehet a tokból kiolvasni. A chip_erase parancs viszont teszi a dolgát, törli az egész programmemóriát, a konfigurációs „fuse” bitekkel, köztük a LOCK-kal együtt.
  • Az előző részben beszóltam egy kicsit a csip tervezőinek a RESET-kor a GPIO-kon automatikusan aktiválódó „Quasi-Bidirectional” vonalakért. Ez még most is „áll”, annyi kiegészítéssel, hogy a µC szériában vannak olyan változatok, ahol ez a viselkedés konfigurációs „fuse” bittel szabályozható, át lehet kapcsolni „RESET-kor sima bemenet” verzióra. Az a tok, ami a devkit-en van, az speciel pont nem ilyen. (Vagy csak a dokumentáció hiányos..?)
  • A linker-script-ek egyik eddig nem tárgyalt része az ENTRY(main) sor. Ez az .elf fájl indítási pontja lenne, ha jól gondolom. Viszont itt jelentősége nincs, mivel a µC programját a megfelelően beállított start-vektor fogja indítani, erről viszont a fordító / linker mit sem sejt. Azért senkit se bántanak, ha normálisan be van állítva...

A fentiekből is készült egy tesztkód-gyűjtemény, az előző folytatásaként. A testcode4 az új, bővített, de még saját linker-script-es verzió, ebbe befordul a crt0, amúgy az előző LED-kapcsolgatós példa kiegészítve az állandókkal, meg az inicializált és nem inicializált változóval. Használni még ezt sem érdemes, mivel nem állítja be az inicializált változókat! (Mint kiderült, a crt0 csak a .bss terület törlését csinálja meg.) Illetve valamiért hibásan indul, programozás után jó, de egy áramtalanítás után nem indul el! A miértre egyelőre nincs magyarázat... A testcode5 az crt0 nélküli verzió, de ebbe már használva van a gyártótól kapott definíciós fájl-pakk, a periféria-regiszterek címzése már „azon keresztül” van implementálva. Illetve ez tartalmaz egy saját, minimalista startup-kódot, ami az inicializált változókat helyrerakja, illetve törli a .bss területet a main() indítása előtt. Ez elvileg már valamennyire jó kell hogy legyen. A testcode6 a jelenlegi „legteljesebb” változat, crt0-val, gyártói startup-kóddal és periféria regiszterdefiníciókkal. (Ebben a változatban nem tapasztaltam az áramtalanítás utáni el-nem-indulást... :) ) Ez azért már talán ajánlható felhasználásra. :-D

Mi is van (majd) még hátra? A „rendes” tesztprogram, ami egy billentyűmátrix kelezése lesz:

Ez a billentyűzet egy régi, 286-os laptop mechanikus (!) billentyűzete. (Minden kapcsoló diódázott! :) ) Sajnos messze nem olyan jó, mint kellene, de azért tesztnek megfelel. Nem perifériát akarok belőle faragni, csak egy sima mátrix-lekérdezés lesz, a lenyomás/felengedéshez rendelt kódokat meg küldözgesse majd soros porton kifele. (Ha majd, esetleg összejönne egy USB HID mintapélda kipróbálása, akkor lehet hogy lesz belőle periféria is, de ez tényleg távlati terv.) Ezen felül (Vagy ezzel együtt?) kellene egy kis makeorológia, mert egyelőre az sincs meg. :| Pár projektnél már használtam, de az mind valamilyen minta minimális módosítása volt csak. Aztán – hiába a fellelt fejlesztői környezet – csak össze kellene rakni valami egyszerűbb IDE-t, nekem az Eclipse egy kissé nagy.

„Irodalom”, van bőven (ami hasznosnak tűnt bármennyire, ide került):

balagesz

---

2019.02.17.
2019.12.22. D8 + elírások jav.

Hozzászólások

Ezt már nem olvastam végig, de a kitartásod példamutató. Egy olyan deszkával tanulni, amihez szinte semmi támogatás nincs már, ez az igazi kihívás!
(Egyben rejtett sub)

Címlapon.

Mindamellett, hogy alapos írás, jól látszik, hogy a HUP-on rendelkezésre álló formázási eszközökkel is lehet igényesen kinéző írást készíteni. Csak nem kutyaütőnek kell hozzá lenni.

Grat!

(PS: esetleg a képek jöhetnének HTTPS-ről és akkor az sem törne, de ez már csak az én szemem bántja :)

--
trey @ gépház

Köszönöm!

Itt jegyezném meg félve, mert nem teszteltem: remélem az új HUP alatt is lesz nem WYSIWYG szerkesztő. :) A képeket tároló tárhely (amit ezúton is köszönök) egyelőre nem érhető el HTTPS-sel, de közvetlen ráhatásom nincs is, azért remélem egyszer megoldódik.

bazi idegesito, amikor beagyazol egy youtube videot, es mixed content lesz tole.

Pedig csak egy ceg bemutatkozo oldala. Nincs rajta login, semmi se.

---
Saying a programming language is good because it works on all platforms is like saying anal sex is good because it works on all genders....

A HTTPS a kétirányú kommunikációban nem csak azt tudja biztosítani, hogy más nem látja az adatokat. Hanem azt is, hogy más nem tudja úgy módosítani, hogy ne vedd észre.
(persze ehhez az egész PKI infrastruktúrában meg kell bízni).

Példa: jogtárból letöltesz egy törvényt, nincs HTTPS, valaki MITM pedig módosítja az adatfolyamot, és a törvény számodra lényegi része, ami lehet, hogy csak egy mondat, lehet, hogy egy bekezdés.
Te azt hiszed, hogy tényleg azt látod, amit a másik oldal küldött, miközben nincs módod arra, hogy erről megbizonyosodj.
Pedig maga a törvény egy publikus információ, logint nem igényel a megtekintése.

> A HTTPS a kétirányú kommunikációban nem csak azt tudja biztosítani, hogy más nem látja az adatokat.

Pontosítsunk: azt se tudja biztosítani...

> Hanem azt is, hogy más nem tudja úgy módosítani, hogy ne vedd észre.

Miért, a szolgáltatók olyan gyakran szórakoznak ezzel? Nekem ez nem tűnt még föl...

> (persze ehhez az egész PKI infrastruktúrában meg kell bízni).

Mert egy rnd() kínai vagy üzbég cég által kiállított cert az a 100% garancia arra, hogy minden secure és megízható. HypeTTPS...

1. A Heartbleed nem a TLS protokoll hibája, hanem az OpenSSL Implementáció hibája. Például a Windows szerverek, köszönik, jól megvannak nélküle.

2. Nem, nem a szolgáltatóról van szó, hanem bárki másról, aki támadó lehet,aki nem feltétlenül téged támad, hanem az ellenoldali szervert.

3. Az egész TLS azon alapszik, hogy megbízol a PKI infrastruktúrában. De ezen kívül te döntheted el, melyik certekben bízol meg.

1. Én ezt értem, de mi a garancia, hogy abban az implementációban nincs hiba?

2. Ha a szervert támadja, akkor az azt jelenti, hogy beépült a szerver és a világ közé és a szerver átfolyó forgalmába injektálja a szemetét. Hát ennyi erővel defacelheti is a szervert, nem?

3. Én nem nagyon. http://www.tahina.priv.at/~cm/talks/pki-sucks.pdf https://readplaintext.com/web-security-is-totally-totally-broken-b603c705f88

Puszta kiváncsiságból: miért newlib, ha az eredményeid szerint túl nagy a kapott bináris? Az uClibc vagy még inkább a musl nem fordul/fut Cortex-M0-on?

Egyelőre azért a newlib, mert az jött a fordítóval. :) (Mondjuk CentOS alá én forgattam, de ezt most nem árulom el. :-D ) Ez a "bőbeszédűség" nem csak nekem tűnt fel, mivel létezik olyan is, hogy newlib-nano. Amúgy ott még nem tartok, hogy ezen nagyon fennakadjak, bőven van még itt is olyan, ami annyira homály, hogy ne ennek a library-nek a cseréjével próbálkozzak. De ami késik, az eljön!

A két említett libc azt mondja magáról, hogy: "for developing embedded Linux systems". Ez nekem baj? :-D Merthogy egyelőre nem akarok µC-n linuxot futtatni. (Azért megjegyzem őket!)

Az egyáltalán nem biztos, hogy önállóan nem használhatók! :) Csak a jelenlegi ismereteim kevesek ennek az eldöntésére.

Amúgy belenézve az ARM-tól letöltött toolchain-be, mintha abban is látnék newlib illetve newlib-nano részeket. (Akár választható lenne..?) Körbe akartam ezt is egy kicsit járni, de így is jó hosszú lett. Legalább marad a következő részre is... :-D

Most utánaolvasáskor én is csak Linuxos példát, meg utalást találtam, szóval szerintem ha használhatóak is önállóan, ahhoz hekkelni kell őket. :/

Egyébként most rápillantottam az LLVM/CLang párosra is, de a jelek szerint a Cortex-M0 pont nem támogatott, úgyhogy csak a GNU-s toolchaint tudod használni, pedig lehet a CLangot jobban be bírnád idomítani, hogy mit szórjon ki. Ennek a körbejárása esetleg egy még későbbi részben...? :)

> rápillantottam az LLVM/CLang párosra is, de a jelek szerint a Cortex-M0 pont nem támogatott

??? Ezt pontosan miből veszed? Egy 1604-es Uborkán (5.0.0-s llc-vel) ezt kapom (sok felesleges infó eldobása után):

$ llc --version
Registered Targets:
arm

$ llc --march=arm --mattr=help
Available CPUs for this target:
cortex-m0 - Select the cortex-m0 processor.

Szóval nekem úgy tűnik, hogy de, támogatott a Cortex-M0 Clang/LLVM használatakor is.

=====
tl;dr
Egy-két mondatban leírnátok, hogy lehet ellopni egy bitcoin-t?

Llvm+cortex-m0: az Arm hivatalos fizetős színes-szagos kompájlere llvm alapú egy ideje. Úgy hívják, hogy ARMClang.

Newlib és a kódméret:
A legtöbb fordító saját stdlib - bel jön, kivéve a GCC. Így a fizetős fordítok egyik legjobb versenyelőnye a saját könyvtáruk. A nanospec segít, de ha igazán kicsit szeretnél, az pénzbe kerül. Vagy fejlesztési idővel fizeted meg, vagy bankókkal.

GCC-s "körökben" hagyomány, hogy az assembly fajlokra ráeresztik a C preprocesszort. Ha GCC - n keresztül hívod az assemblert és nagy s a kiterjesztés akkor ez (elvileg) automatikus. A startup fájlok konfigurálását ezen a környéken keresd.
Amúgy bármi ami a C kódon kívül van az mindenféle szabványosságot nélkülöz. Így a startup kód, az stdlib és a linker viszonya egyedi. Pl attól is függhet, milyen kapcsolokkal fordítod a Newlibet.

Hát, nem tudom. Fogtam az Ubuntu 19.04 alpha (épp az volt feltelepítve) arm-none-eabi-gcc -jét a default stdlib-jével és lefordítottam egy általam írt egyszerű ledvillogtató C forrását STM32F1 mikrovezérlőre.


arm-none-eabi-size progi.elf
   text	   data	    bss	    dec	    hex	filename
     92	      0	      0	     92	     5c	progi.elf

$ ls -l
-rwxrwxr-x 1 stm32 stm32    92 febr  20 22:19 progi.bin

Ha kicsi kódméret a cél, max nem használok sprintf()-et és hasonló libc-s dolgokat. Egyébként 128 kB flash van ebben a mikrovezérlőben, ami szerintem elég lesz. Ha nem, akkor MB-os méretben is van olcsón.

Azért annyira ne lepődjünk meg, hogy a szükségtelen dolgok nem foglalnak helyet, bár az ld fordítási egységekben gondolkodik. Ez alap esetben egy forrás fájl. Ha a .c fájlból te csak egy függvényt használsz simán bekerülhet a binárisba pár nem használt dolog. Tessék kicsit kutakodni a -ffunction-sections és a -Wl,--gc-sectionskörnyékén.

Én is tudok apró programot fordítani GCC -vel, főleg, ha nem kell csinálnia semmit :) A gondok ott kezdődnek, ha kellene az az sprintf, vagy egy atoi, de hely az már nem nagyon van. Ilyenkor jön az, hogy vagy fejlesztési időben vagy pénzben fizetsz. Tudom, hogy a sprintf nem nagy ördöngösség, de ha toronyóra+lánccal kell (tesztelve, review -zva, stb...), akkor azért 1-2 hetet simán el lehet szöszmötölni egy ilyennel.

A gyári startup-nak itt is ".S" a kiterjesztése. És tényleg tele van #ifdef és társaival. Egyelőre az ilyen definiált dolgok "láthatósága" nem tiszta (hol is kellene #define-olnom valamit, hogy az itt látsszon). Persze csinálhatok "bambán" egy

sajátstartup.S

-t, amibe berakom a definíciókat, majd beinklúdolom a

gyáristartup.S

-t. De van egy olyan sejtésem, hogy ezt nem pont így gondolták. :)