Wiki

Clone wiki

chocos / CBIN

Wstęp

CBIN jest formatem pliku wykonywalnego stworzonego na potrzeby ChocoOS. Jego założeniem jest pozwolenie na skompilowanie programu niezależnie od reszty systemu oraz dynamiczne załadowanie go i zlinkowanie na etapie przygotowywania procesu, ale bez zbędnego narzutu standardów takich jak ELF. Dzięki temu rozmiar pliku CBIN jest możliwie najmniejszy.

Struktura pliku

CBIN składa się z 7 sekcji (4 standardowe + 3 sekcje specyficzne dla *.cbin). Grafika poniżej przedstawia te sekcje:

sections.PNG

.header

Sekcja header zawiera definicję program headera, który jest wykorzystany w:

oc_program_data.c:127

Natomiast header zdefiniowany jest jako: oc_program_types.h

Czyli:

typedef struct
{
    uint32_t                        HeaderSize;   
    uint32_t                        SectionsDefinitionSize;
    char                            MagicString[PROGRAM_HEADER_MAGIC_STRING_SIZE];
    uint32_t                        Version;
    oC_Machine_Name_t               MachineName;
    const oC_SystemCall_t *         SystemCalls;
    uint32_t                        NumberOfSystemCalls;
    uint32_t                        SystemCallDefinitionSize;
    oC_Program_Registration_t       ProgramRegistration;
} oC_Program_Header_t;

Gdzie:

  • HeaderSize jest rozmiarem oC_Program_Header_t
  • SectionsDefinitionSize - rozmiar sekcji .sections
  • MagicString - Łańcuch kontrolny. Powinien zawierać napis ChocoOS
  • Version - Wersja systemu dla której został skompilowany program
  • MachineName - Nazwa architektury
  • SystemCalls - Adres tablicy z funkcjami systemowymi
  • NumberOfSystemCalls - rozmiar tablicy
  • SystemCallDefinitionSize - rozmiar elementu w tablicy z systemowymi zawołaniami
  • ProgramRegistration - dane rejestracji programu .sections : { LONG(__text_start); LONG(SIZEOF(.text)); LONG(__data_start); LONG(SIZEOF(.data)); LONG(__bss_start); LONG(SIZEOF(.bss)); LONG(__syscalls_start); LONG(SIZEOF(.syscalls)); LONG(__got_start); LONG(SIZEOF(.got)); LONG(__bin_size); }

Poniżej znajdują się szczegóły rejestracji programu, jednak nazwy raczej są intuicyjne, więc nie ma sensu omawiać tego szczegółowo:

typedef struct
{
    uint32_t                    Priority;
    oC_Program_Name_t           Name;
    oC_Program_MainFunction_t   MainFunction;
    char *                      StdInName;
    char *                      StdOutName;
    char *                      StdErrName;
    uint32_t                    HeapMapSize;
    int32_t                     ThreadStackSize;
    uint32_t                    AllocationLimit;
    bool                        TrackAllocations;
    char *                      ArgumentsFormatFilePath; // Ścieżka do pliku z opisem formatu argumentów programu
} oC_Program_Registration_t;

.syscalls

Sekcja .syscalls jest specjalną sekcją zdefiniowaną na potrzeby formatu .cbin. Zawiera ona listę dostępnych funkcji systemowych. Miejsce na nią jest zarezerwowane w skrypcie linkera programu:

program.ld:12

__bin_size
  .syscalls :
  {
    __syscalls_start = .;
    PROVIDE(__syscalls_start = __syscalls_start);
    KEEP(*(.syscalls))
    __syscalls_end = .;
    PROVIDE(__syscalls_end = __syscalls_end);
    __syscalls_size = __syscalls_end - __syscalls_start;
  }

Zawartość tej sekcji natomiast jest zdefiniowana w pliku oc_program_data.c w następujący sposób:

oC_SystemCall_t SystemCalls[] __attribute__ ((section (".syscalls"))) = {
#define ADD_FUNCTION(NAME)      { .Name = #NAME , .CallAddress = (void*)0xDEADBABA } ,
POSIX_FUNCTIONS_LIST(ADD_FUNCTION)
#undef ADD_FUNCTION
};

Jak widać jest to tablica zawierająca informacje na temat każdej funkcji systemowej, która ma być dostępna dla programów uruchamianych w formacie *.cbin. Elementy tej tablicy przygotowywane są na etapie kompilacji pliku oc_program_data.c - na tym etapie nie są jeszcze znane adresy funkcji, ale są już znane ich nazwy - jest to wykorzystywane aby zarezerwować miejsce dla każdej z nich w sekcji .syscalls. Każdy element tablicy jest zdefiniowany według następującego formatu:

// Maksymalny rozmiar nazwy funkcji
#define SYSTEM_CALL_NAME_STRING_SIZE          50

// Typ przechowujący napis z nazwą funkcji
typedef char oC_SystemCall_Name_t[SYSTEM_CALL_NAME_STRING_SIZE];

// Struktura przechowująca wpis informujący o funkcji systemowej
typedef struct
{
    void *                      CallAddress;       // Adres funkcji w systemie operacyjnym
    oC_SystemCall_Name_t        Name;              // Nazwa funkcji
    char                        NullTerminator;    // '\0' - null terminator
} oC_SystemCall_t;

System operacyjny wypełnia tę tablicę prawdziwymi adresami funkcji na etapie ładowania programu do pamięci RAM:

    for( uint32_t i = 0; i < NumberOfCalls; i++ , SystemCalls += SystemCallSize )
    {
        oC_SystemCall_t * systemCall = SystemCalls;

        systemCall->CallAddress = NULL;

#define ADD_FUNCTION(NAME)      if(strcmp(#NAME,systemCall->Name) == 0) \
                                { \
                                    systemCall->CallAddress = NAME;\
                                }

        POSIX_FUNCTIONS_LIST(ADD_FUNCTION);

#undef ADD_FUNCTION

        if(systemCall->CallAddress == NULL)
        {
            debuglog( oC_LogType_Error, "Unknown system call '%s'\n", systemCall->Name );
            errorCode = oC_ErrorCode_UnknownSystemCall;
            break;
        }
    }

Dla każdej funkcji systemowej stworzona jest globalna zmienna przechowująca adres funkcji właściwej:

#       define EXTERNAL_FUNC(NAME,PROTO)        (*NAME) PROTO

Dla przykładu funkcja printf jest zdefiniowana następująco:

EXTERNAL_PREFIX int      EXTERNAL_FUNC( printf               ,   ( const char * Format , ... )                                           );

Czyli globalnie będziemy widzieć adres funkcji w następującym formacie:

int (*printf) ( const char* Format, ... );

Następnie, tuż przed startem funkcji main programu, odpalana jest funkcja inicjalizująca:

void __attribute__((optimize("O0"))) InitializeProgram( void )
{
#define ADD_FUNCTION(NAME)      NAME = SystemCalls[oC_Posix_CallId_##NAME].CallAddress;
    POSIX_FUNCTIONS_LIST(ADD_FUNCTION);
#undef ADD_FUNCTION

}

static int MainFunction( int Argc , const char ** Argv )
{
    InitializeProgram();
    return main(Argc,Argv);
}

która kopiuje adresy z sekcji .syscalls do zmiennych globalnych, dzięki czemu kiedy użytkownik zawoła na przykład:

printf("Hello World!\n");

To w rzeczywistości odwoła się do globalnej zmiennej o nazwie printf, której wartość została przypisana podczas inicjalizacji programu.

.sections

Ostatnia sekcja przechowuje informacje na temat sekcji dostępnych w pliku *.cbin. Jest ona wypełniona w skrypcie linkera:

program.ld:110

    .sections : 
    {
       LONG(__text_start);
       LONG(SIZEOF(.text));
       LONG(__data_start);
       LONG(SIZEOF(.data));
       LONG(__bss_start);
       LONG(SIZEOF(.bss));
       LONG(__syscalls_start);
       LONG(SIZEOF(.syscalls));
       LONG(__got_start);
       LONG(SIZEOF(.got));
       LONG(__bin_size);
    }

Natomiast zdefiniowana w następujący sposób:

typedef struct
{
    struct
    {
        uint32_t            Offset;
        uint32_t            Size;
    } text , data , bss , syscalls, got;
    uint32_t        BinSize;
} oC_Program_Sections_t;

Jak widać najpierw zdefiniowane są offsety i rozmiary dla każdej sekcji, a następnie wypisany jest rozmiar całego pliku binarnego (bez sekcji .sections).

Updated