Wiki
Clone wikichocos / 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:
.header
Sekcja header zawiera definicję program headera, który jest wykorzystany w:
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 rozmiaremoC_Program_Header_t
SectionsDefinitionSize
- rozmiar sekcji.sections
MagicString
- Łańcuch kontrolny. Powinien zawierać napisChocoOS
Version
- Wersja systemu dla której został skompilowany programMachineName
- Nazwa architekturySystemCalls
- Adres tablicy z funkcjami systemowymiNumberOfSystemCalls
- rozmiar tablicySystemCallDefinitionSize
- rozmiar elementu w tablicy z systemowymi zawołaniamiProgramRegistration
- 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:
__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:
.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