SDS-C: SCB

Tato stránka popisuje programovací jazyk SDS-C, který je dostupný na vybraných zařízeních SDS. 
Některá zařízení SDS používají FULL-C, pro který máme návody jinde na této WiKi.


Soubor .SCB

Soubor .SCB obsahuje výstup překladu vašeho zdrojového kódu jazyka SDS-C.

Provedení překladu pomocí aplikace SDSC.exe v příkazovém řádku:

C:\SDSC\SDSC.EXE compile test.c

Tento příklad vezme zdrojový kód v jazyce SDS-C v textovém souboru test.c , provede překlad, a vygeneruje binární soubor test.c.SCB .

Soubor SCB lze přímo poslat pomocí HTTP POST do všech zařízení SDS řady ST.


Formát souboru .SCB

Soubor se skládá z datových bloků o typicky pevné velikosti (jen poslední blok může být kratší).

Na začátku každého bloku je hlavička, a následuje datový obsah.

Bloky jsou těsně na sobě, za sebou, uspořádány v SCB souboru. Obsahem SCB souboru není nic jiného než tyto bloky.

Do SDS se soubor SCB přenáší kompletně v původním stavu, tak jak je zapsán na disku, beze změn. Modul SDS provádí interně kontrolu přijatého obsahu souboru SCB, a při jakékoliv odchylce celý soubor odmítne.

Obsah souboru SCB, který obsahuje "n" bloků:

BLOK 0
BLOK 1
...
BLOK n-1

Počet bloků ("n") je určen překladačem, a prakticky (logicky) odpovídá velikosti programu.

Interní datový obsah každého bloku tvoří jednotlivé části přeloženého SDS-C programu, viz dále.

Nejprve je potřeba si popsat hlavičku každého bloku:

OBSAH BLOK x

 offset | length  |  detail
 -------+---------+------------------------------------------
   0    |     2 B |   a general block position ("bpoz")
   2    |     1 B |   reserved (by default set to 0xAA)
   3    |     1 B |   block 8-bit checksum ("c")
   4    |     2 B |   block length ("blen")
 -------+---------+------------------------------------------
   5    |  blen B |   internal data   

Každý blok má svou pozici - číslování začíná na nule. Podmínkou je umístit bloky do souboru SCB přesně za sebou, tak jak se jejich pozice zvyšuje. Stejně tak jsou zasílány do SDS. Pokud SDS detekuje neplatný (očekávaný) údaj pozice, je celé nahrávání SCB přerušeno a zamítnuto.

Délka bloku "blen" musí být v rozmezí 0 až 264 (včetně). Typicky všechny bloky mají délku 264 bajtů, s možnou vyjímkou jen pro úplně poslední blok, který může být jako jediný kratší.

Interní Data bloku jsou už samotným kouskem obsahu výsledku překladu SDS-C. Překladač tedy vytvoří celý binární obsah, který je rozkouskouván do bloků (kterým je přidána hlavička). Po nahrání SDS-C do modulu SDS dojde k zahození hlavičky, úpravy a kontrole interních dat a jejich celkovému opětovnému sloučení (složeny pěkně za sebou bez přerušení). To co si SDS v sobě uchová jsou už vyčištěná interní data, tedy "binární kód SDS-C".

Každý blok má svá Interní Data jednoduše upravena, což má umožnit jejich kontrolu a detekovat chybu při přenosu. Nejedná se o šifrování (to je v plné míře a správným způsobem aplikováno až u FULL-C), ale o jednoduchou logickou operaci zaměřenou na detekci bitových chyb.

Algoritmus úpravy (je už obsažen v SDSC.EXE a jeho výsledek je tedy už obsahem .SCB souboru) je celkově jednoduchý.

uint32_t i;
uint8_t yx, ck;
if (blen > 264)
{
  error: invalid blen -> exit
}
yx = c ^ 0x27;
ck = 0;
for (i = 0; i < blen; i++)
{
  block_internal_data[i] ^= yx;
  ck += block_internal_data[i];
}
if (ck != c)
{
  error - checksum not valid -> exit
} else
{
  checksum for this block IS valid -> continue to next block
}

Tento pseudokód je v principu obousměrný, lze jej použít jak pro vytvoření zabezpečeného bloku, tak pro ověření zabezpečení a odstranění tohoto zabezpečení.

Protože se vždy předpokládá přenos přes IP vrstvu (UDP, nebo TCP pro HTTP POST), není v tomto případě zabezpečení na této úrovni dále zvyšováno (v rámci IP vrstvy jsou pakety kontrolovány pomocí CRC32, což pro takto malou datovou dávku je dostatečné).


Postupy

Postup při překladu:

+-------------+                                                                                                                  +-------------+
| source code |                              +-------------+                                                                     | output file |
|             | ------->  COMPILER  -------> | binary code | --> SPLIT TO BLOCKS -> MODIFY INTERNAL DATA -> WRITE ALL BLOCKS --> |             |
|  test.c     |                              +-------------+                        FOR EACH BLOCK          TO .SCB              |  test.c.SCB |
+-------------+                                                                     + ADD HEADERS                                +-------------+

Postup při nahrávání do SDS:

+---------------+                                                                                                            
| compiled file |                               
|               | ------->  HTTP POST  -------> CHECK BLOCK HEADERS -> STRIP HEADERS -> UNMODIFY INTERNAL DATA -> WRITE INTERNAL DATA TO DATAFLASH --> RUN SDS-C
|  test.c.SCB   |          (authorized)                                                                           (= SDS-C BINARY CODE)
+---------------+                                                                                                            

Lze vidět že proces je přímočarý a z hlediska klienta (ten kdo nahrává do SDS) jednoduchý - pošle se obsah .SCB souboru beze změny, tak jak je na disku 1:1 do SDS.


Binární kód SDS-C

Binární kód je datový blob, který je výstupem překladu zdrojového kódu jazyka SDS-C. Tento binární kód je různým způsobem transportován do SDS, jeden za způsobů (rozklad do bloků a jejich zabezpečení) je popsán výše.

Původní SDS které prováděli přeložený SDS-C program byly založeny na tehdejším mimořádně omezeném dostupném hardware, zejména z hlediska pracovní paměti (RAM v desítkách kB). Přesto se povedlo do takového prostoru dostat všechny funkce SDS a k tomu provádění SDS-C programu. Tuto informaci uvádím primárně pro to, že vysvětluje celou řadu návrhových rozhodnutí, které jsou zde dále popsány. Nešlo si dovolit plýtvat pamětí a místem, a mnohdy se hledal kompromis. Teprve FULL-C tato omezení nemá a je plnohodnotný (včetně plného kryptografického zabezpečení).

Za zmínku stojí i to, že původně byl SDS-C program zadáván přímo ve zdrojovém textu ve webovém rozhraní zařízení SDS, což ale bylo následně předěláno (na zadávání a překlad na PC) primárně z důvodu zvětšení možného rozsahu (velikosti) programu, a také hlavně díky přesunu na PC došlo k možnosti emulovat program (ladění a simulace v SDSC.exe).


Přímý výstup překladu, tedy ještě před předáním do transportu (tedy např. rozklad na bloky do .SCB), má následující binární obsah (složený ze čtyř těsně navazujících částí):

MASTER HEADER
 [ header ]
STRINGS HEADER
 [ OFSLEN(0), OFSLEN(1), ... OFSLEN(n-1) ]
STRINGS BLOB
 [ STR(0), STR(1), ... STR(n-1) ]
PROCEDURES (CODE BLOBS)
 [ PROC(X) = " TYPE, NUMBC, BYTECODE[] " ]

Tyto čtyři základní binární části jsou umístěny těsně za sebou, a jako celek tvoří spustitelný přeložený SDS-C program, který spustí a provede kterékoliv SDS První Produktové Řady.

Naprosto všechny prvky jsou zapsány jako 16bit slova (v případě potřeby uložit do bytecode např. konstanty o větší bitové šířce, je to provedeno kombinací více 16bit slov).

Jedná se plně kustomizovaný způsob zápisu binárního kódu programu, který privátně vytvořil vývoj AN-D (už to bude téměř 20 let existence SDS-C), dále jej udržuje, a který je použit jedině pro moduly SDS (OnlineTechnology). Zde popsaný návrh (a vše související) není legálně možné použít v jiném komerčním výrobku - autor k tomu nedává svolení).


MASTER HEADER

Základní hlavička obsahuje odkazy na jednotlivé další hlavní prvky v rámci celého binárního obsahu.

MASTER HEADER

 offset |  length  |  detail
 -------+----------+------------------------------------------
   0    |      2 B |   total binary size
   2    |      2 B |   offset -> STRINGS HEADER
   4    |      2 B |   offset -> STRINGS BLOB
   6    |      2 B |   STRING BLOB size
   8    |      2 B |   STRINGS count
  10    |      2 B |   PROCEDURES count (max 96)
  12    |      2 B |   VARIABLES count (max 144)
  14    | 96 x 2 B |   offset -> PROCEDURE

Hodnota 96 (konstanta) udává aktuální maximální počet procedur v rámci dané verze SDS-C.

Návrh hlavičky ukazuje i další podstatnou informaci - a to maximální velikosti celého přeloženého SDS-C programu, která nesmí překročit 64kB. To bylo opět dáno tehdejším hardware a jeho možnostmi. Proto vznikl jazyk FULL-C, který tato omezení nemá.


STRINGS HEADER

Pro každý (překladem detekovaný) unikátní text, je proveden jeho zápis do STRING BLOBu, a informace o pozici v rámci STRING BLOBu je umístěna právě v hlavičce (STRINGS HEADER).

Pozice textu v BLOBu je současně jeho referencí "sr" (platná hodnota: 0 až "STRINGS count" - 1 ), na kterou se pak odkazuje výkonný kód programu.

Obsahem této hlavičky je tedy pole o následujících dvou položkách:

 offset       |  length  |  detail
 -------------+----------+------------------------------------------
  sr*4 + 0    |      2 B |   offset to blob
  sr*4 + 2    |      2 B |   string length (without 0x00)

Pozor, je zcela platné, aby offset jednoho stringu byl namířen do části jiného. Příklad: mějme dva stringy: "hello" , "lo". Pro tyto dva nemá význam dělat dva samostatné záznamy, když můžeme druhý string "lo" vzít z předchozího (když už v paměti je). Překladač umí i další pokročilejší funkce sestavení celého STRING BLOBu, kde setřídí všechny stringy tak, aby celý blob měl co nejmenší velikost.


STRINGS BLOB

Všechny extrahované texty (stringy) z programu jsou uloženy v jednom společném binárním blobu. Každý jednotlivý string je zakončen 0x00, a celkově tyto stringy na sebe navazují. Pomocí STRINGS HEADER dokaže SDS rychle najít odkaz do tohoto STRINGS BLOBu na každý jednotlivý string (program si v bytecode udržuje reference do STRINGS HEADER, odkud se vezme offset do STRINGS BLOB a následně se už přečte požadovaný text).

Klíčové je použití pokročilého algoritmu pro sestavení STRINGS BLOBu tak, aby se nalezly všechny opakující se, a jakkoliv vnořené texty (a jejich nejvýhodnější kombinace) s cílem získat STRINGS BLOB co nejmenší. Toto bylo klíčové zejména pro úplně první typy modulů SDS kde byl program v malé EEPROM paměti (původně jen 2kB ! a přesto se to povedlo).


PROCEDURES

Bytecode je vždy uložen v jednotlivých funkcích (PROCEDURES). Při vykonávání SDS-C programu provádí SDS právě jednotlivé procedury, tedy jejich vnitřní obsah (bytecode). SDS vždy začne od procedury označené názvem jako "main" (toto identifikuje překladač a nastaví potřebný typ dále do binárního kódu). Jednotlivé procedury mohou ze svého bytecode zavolat jiné procedury, které se po svém provedení vrátí hned za místo původního volání (klasický očekávaný způsob běhu programu).

Tento blob tedy obsahuje 1 až 96 (dle verze SDS-C) sub-blobů, které mají vždy stejný design (logicky se liší jen samotným obsahem v položkách).

Offset na každou proceduru lze přečíst z MASTER HEADER.

 offset  |  length           |  detail
 --------+-------------------+------------------------------------------
    0    |               2 B |   procedure type
    2    |               2 B |   numbytecode (max 32768)
    4    | numbytecode * 2 B |   BYTECODE ( 16bit words )


Hodnoty v "procedure type":

   0 = default
   1 = reserved (procedure ignored)
   2 = init()
   3 = reserved (procedure ignored)
   4 = main()

Je úkolem překladače identifikovat funkce init() a main() a nastavit pro jejich bytecode správný typ.

Bytecode je složen z (v hlavičce) uvedeného počtu 16bit slov. Tento bytecode je postupně vykonáván, od začátku až po konec procedury. Pokud se dojde na konec procedury, je procedura považována za ukončenou, a přejde se zpět na proceduru která tu vykonávanou zavolala. Pozor - pokud se jedná o funkci main(), tak zde dojde k opětovnému spuštění funkce main(). Odsud lze vidět i funkci funkce init(), která je zavolaná prioritně (a jen jednou) při prvním startu programu, a po jejím ukončení se jde ihned do funkce main(), která je pak ponechána jako hlavní.

Z celého popisu je nejsložitější popis bytecode samotného.

bytecode

Vždy sestava 16bit čísel. Skládá se z malých bloků dat. Na začátku každého bloku je vždy řídící informace, následovaná doplňujícími informacemi. Tyto bloky jsou těsně za sebou.

SDS vykonává bytecode tak, že postupně čte 16bit čísla, a provádí je. Čtení je vždy v rámci právě prováděné procedury. Začíná se vždy od začátku procedury (začátek celého blobu bytecode v dané proceduře). Čtení je ukončeno jakmile se dosáhne konce procedury. Samozřejmě, příkaz "return;" může vykonávání procedury ukončit dříve, než se dojde na konec jejího bytecode.

V rámci vykonávání bytecode může dojít na skok na "label" pomocí "goto". Tento skok lze provést i mezi jednotlivými procedurami. Skok je zpracován překladačem a tento zapíše do bytecode jak číslo procedury (do které se skáče), tak cílovou pozici bytecode v rámci dané procedury. To znamená, že skočit (goto label) lze provést kamkoliv v rámci (platné základní pozice) bytecode kterékoliv procedury (stejné nebo jiné).

Základní provozní smyčka programu:
1. inicializovat program, začít s procedurou "init()" (pokud je přítomna, jinak rovnou "main()")
2. začít provádět instrukce (bytecode) z dané procedury
3. jakmile jsme na konci procedury:
2a. pokud jsme na konci, provést návrat (dle zásobníku volání)
2b. v případě vyčerpání zásobníku znovu spustit proceduru patřící funkci main()

Základní provozní smyčka pro vykonání procedury:
postupně čte jednotlivé 16bit položky v bytecode a vykonává je.
Každý blok pro jednotlivou instrukci, začíná kódovým slovem určujícím typ instrukce, a je identifikován 0x8000.

pn = pozice procedury v seznamu 
cp = pozice v rámci bytecode této procedury
bc = aktuální 16bit položka z bytecode (postupně čteno z obsahu)

while (cp <= procs[pn].proc.num_byte_code-1)
{
  
  bc = precti bytecode ; 
  
  // test na platny zacatek bloku instrukce
  if (0x8000 != (bc & 0x8000))
  {
    continue; 
  }
  
  // provedeni tohoto bloku (+ odmaskovat identifikaci)
  switch (bc & (~0x8000))
  {
    
    case bc_call_func :
    {
      // precti o jakou funkci se jedna
      typ_funkce = precti bytecode ;
   
      // vykonej (pokud je to platna hodnota)
      switch (typ_funkce)
      {
        case cid_echo: vykonej funkci echo(); break;
        ...
        ....
        ...
      } 

    }; break;
    
    case bc_call_process:
    {
      // ulozi stav do zasobniku pro navrat, a "zavola" novou proceduru tak jak je zapsano uvnitr nasledujiciho bytecode
      do_instruction_call_process();

    }; break;

    case bc_operator :
    {
      // provede operator 
      do_instruction_operator();
    }; break;

    case bc_if :
    {
      // provede cely blok "if" (vyhodnoceni a provedeni vysledku)
      do_instruction_IF();

    }; break;

    case bc_jump :
    {
       // provede "goto label"
        
       // precti z bytecode kam se bude skakat
       jump_pn = precti bytecode ;
       jump_cp = precti bytecode ;
        
       ... overit ze jump_pn a jump_cp jsou platne ...
       
       // a provedeme skok
       pn = jump_pn;
       cp = jump_cp;

    }; break;

  } // switch

} // while

Výše uvedený kód přesně vystihuje proces uvnitř SDS. Samozřejmě se jedná o zjednodušený zápis, protože SDS uvnitř má ještě řadu verifikačních kroků (na správnosti parametrů a obsah bytecode).

Seznam identifikátorů:

IS_INSTRUCTION  =  0x8000
INVALID_OP      =  0xFFFF
 
bc_call_func    =  100  (decimal)
bc_call_process =  101
bc_operator     =  102
bc_if           =  103   
bc_jump         =  104

A seznam identifikátorů integrovaných funkcí:

0 = cid_nop 
1 = cid_echo
...


Stránka se postupně doplňuje.

Konec