Pliki wykonywalne/obiektowe

Co to?

Uruchomienie programu to czynność oczywista, która polega, z grubsza (w zależności od systemu operacyjnego), na dwukliku na ikonę „pliczek.exe”, wykonania w terminalu polecenia „./kodzik” lub wybrania odpowiedniej pozycji z menu telefonu. Niewiele osób zastanawia się, co tak naprawdę kryje się za wspomnianymi plikami, które powodują uruchomienie programu – nie jest to też wcale takie oczywiste.

Pliki te nazywa się najczęściej wykonywalnymi. Niestety, jak to często bywa, nie ma ogólnoprzyjętego zestawu nazewnictwa i definicji, stąd liczne nieporozumienia między określeniem „plik wykonywalny”, a „plik obiektowym” (polska nazwa jest dla mnie dziwna, po angielsku to object file) – spróbujmy to jakoś ogarnąć. Plikiem obiektowym, najwygodniej jest nazywać każdy plik, który zawiera uporządkowane sekcje kodu maszynowego (składającego się z instrukcji). Dodatkowo, w takim pliku mogą, ale nie muszą, znajdować się dane oraz metadane. Plik wykonywalny, to natomiast rodzaj pliku obiektowego, który może być bezpośrednio wykonany pod kontrolą systemu operacyjnego. Innym przykładem pliku obiektowego może być np. biblioteka dynamiczna/współdzielona.

Wszystkie pliki obiektowe są zapisane na dysku w pewnym ustalonym formacie, zależnym od systemu operacyjnego, w którym mają być uruchamiane. Format ten definiuje w jaki sposób w pliku zapisane są instrukcje, dane i metadane. Instrukcje to po prostu kod maszynowy, dane to wszelkie zmienne i struktury, które są zdefiniowane bezpośrednio w źródle (np. zmienne statyczne, łańcuchy znaków). Metadane to natomiast bardziej skomplikowana sprawa. To, co wchodzi w ich skład zależy przede wszystkim od samego formatu – jego przeznaczenia. Metadanymi są np.: identyfikator formatu (który często jest po prostu „magicznymi liczbami”) umieszczony w nagłówku, wskazówki na temat relokacji, informacje dla debuggera, tablica symboli. To na podstawie metadanych pliku wykonywalnego system operacyjny „wie”, jak rozmieścić kod i dane programu w pamięci i skąd w zasadzie zacząć jego wykonywanie. Metadane pliku wykonywalnego powinny być kompletne, tzn. powinny zawierać pełen opis, pozwalający na uruchomienie programu tak, aby przeniesienie go na analogiczny system operacyjny nie wymagało dodatkowych czynności. Można powiedzieć, iż metadane są instrukcją obsługi do załączonego kodu i danych, dla systemu operacyjnego.

Przykłady

Jak to w życiu bywa – standardy – każdy ma własne ;) Stąd formatów plików obiektowych jest bardzo wiele, może nawet tyle co formatów audio, czy obrazów. Poniżej przedstawiłem króciótką charakterystykę kilku z nich:

  • a.out – pierwotny format plików wykonywalnych UNIXa, który pojawił się tam już w pierwszej wersji, jeszcze na komputery PDP-7. Jest niezwykle prosty i popularny, ale w nowych rozwiązaniach raczej nieużywany. Teraz nietrudno zgadnąć, skąd wzięła się domyślna nazwa programów kompilowanych pod UNIXem :)
  • COFF (Common Object File Format) – format COFF zastąpił a.out na pozycji domyślnego formatu plików  obiektowych (sam został już jednak zastąpiony przez format ELF). W formacie COFF można przechowywać pojedyncze funkcje, symbole, fragmenty programów, biblioteki oraz może być on bezpośrednio wykonywany.
  • ELF (Executable and Linking Format) – ELF zastąpił natomiast COFF na pozycji lidera w systemach uniksopodobnych (i nie doczekał się jeszcze zdetronizowania). Mimo kilku wad, jest dobrze udokumentowany i bardzo popularny. Został opracowany w Unix System Laboratories podczas prac nad sławnym Systemem V R4.
  • COM – wyjątkowo prosty format używany przez MS-DOS. Można powiedzieć, że jest on „płaską binarką” – nie zawiera żadnych nagłówków. W związku z tym, iż pliki o formacie COM wykonywane były w trybie rzeczywistym, są ograniczone co do rozmiaru do wielkości jednego segmentu trybu rzeczywistego.
  • MZ – format MZ zastąpił COM w MS-DOS 2.0 i jest zdecydowanie bardziej zaawansowany ;) Na uwagę zasługuje fakt, że jego nazwa (i jednocześnie identyfikator zawarty w pierwszych dwóch bajtach pliku – 0x4D (M) 0x5A (Z)) wzięła się od inżyniera Marka Zbikowskiego – rodzimo brzmiące nazwisko, prawda? :)
  • NE (New Executable) – format NE zastąpił natomiast MZ i został wprowadzony w Windowsie 3.x. Niestety posiada on limity rozmiaru takie jak MZ oraz również jest 16 bitowy.
  • PE (Portable Executable) – w końcu, współczesny format plików wykonywalnych dla systemu Windows, wprowadzony wraz z wersją 95. Oczywiście jest on 32 bitowy i posiada wiele udogodnień w stosunku do poprzedników. Warto też wspomnieć, iż jest on kontenerem dla binarek .NETa.

Podsumowanie

Celem stosowania formatów jest ustandaryzowanie pewnych kwestii. Jak widać na powyższych przykładach, nie jest to łatwe – każdy chce mieć coś do powiedzenia :) Stosowanie różnych formatów plików obiektowych na różnych systemach operacyjnych sprawia iż „binarki” (pliki wykonywalne) nie są przenośne – uruchomienie skompilowanego, nawet prostego programu typu hello world, nie jest możliwe na obu systemach. Stąd powstały koncepcje wielu emulatorów, jak choćby WINE czy liczne klony „pegasusa” :), które starają się ten problem usunąć.

Nawiązując do projektowania systemów operacyjnych z formatami plików obiektowych spotkać można się np. przy tworzeniu interfejsu binarnego aplikacji czy też kompilując jądro do formatu łatwego do przeczytania przez konkretny bootloader.