Czy ktoś ma pomysł, jak statycznie skompilować dowolny plik zasobów bezpośrednio do pliku wykonywalnego lub pliku biblioteki współdzielonej za pomocą GCC?
Na przykład chciałbym dodać pliki graficzne, które nigdy się nie zmieniają (a gdyby tak się stało, i tak musiałbym zastąpić ten plik) i nie chciałbym, aby leżały w systemie plików.
Jeśli jest to możliwe (i myślę, że dzieje się tak dlatego, że Visual C ++ dla Windows też to potrafi), jak mogę załadować pliki, które są przechowywane we własnym pliku binarnym? Czy plik wykonywalny analizuje sam siebie, znajduje plik i wyodrębnia z niego dane?
Może jest opcja dla GCC, której jeszcze nie widziałem. Korzystanie z wyszukiwarek nie wypluwało właściwych rzeczy.
Potrzebowałbym tego do pracy dla bibliotek współdzielonych i normalnych plików wykonywalnych ELF.
Każda pomoc jest mile widziana
Odpowiedzi:
Z imagemagick :
convert file.png data.h
Daje coś takiego:
/* data.h (PNM). */ static unsigned char MagickImage[] = { 0x50, 0x36, 0x0A, 0x23, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4D, 0x50, 0x0A, 0x32, 0x37, 0x37, 0x20, 0x31, 0x36, 0x32, 0x0A, 0x32, 0x35, 0x35, 0x0A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, ....
Aby
fmemopen
zachować zgodność z innym kodem, możesz użyć albo do uzyskania „zwykłego”FILE *
obiektu, albo alternatywniestd::stringstream
do utworzenia plikuiostream
.std::stringstream
nie jest jednak do tego świetny i oczywiście możesz po prostu użyć wskaźnika w dowolnym miejscu, w którym możesz użyć iteratora.Jeśli używasz tego z automake, nie zapomnij odpowiednio ustawić BUILT_SOURCES .
Zaletą robienia tego w ten sposób jest:
źródło
xxd -i infile.bin outfile.h
objcopy
do konwersji danych binarnych bezpośrednio do pliku obiektowego; jednak rzadko jest to problem.Uaktualnienie Zdecydowałem się preferować sterowanie oferowane przez
.incbin
rozwiązanie oparte na montażu Johna Ripleya, a teraz używam innego wariantu.Użyłem objcopy (GNU binutils) do połączenia danych binarnych z pliku foo-data.bin z sekcją danych pliku wykonywalnego:
objcopy -B i386 -I binary -O elf32-i386 foo-data.bin foo-data.o
W ten sposób otrzymasz
foo-data.o
plik obiektu, który możesz połączyć z plikiem wykonywalnym. Interfejs C wygląda mniej więcej tak/** created from binary via objcopy */ extern uint8_t foo_data[] asm("_binary_foo_data_bin_start"); extern uint8_t foo_data_size[] asm("_binary_foo_data_bin_size"); extern uint8_t foo_data_end[] asm("_binary_foo_data_bin_end");
więc możesz robić takie rzeczy
for (uint8_t *byte=foo_data; byte<foo_data_end; ++byte) { transmit_single_byte(*byte); }
lub
size_t foo_size = (size_t)((void *)foo_data_size); void *foo_copy = malloc(foo_size); assert(foo_copy); memcpy(foo_copy, foo_data, foo_size);
Jeśli twoja architektura docelowa ma specjalne ograniczenia co do miejsca przechowywania stałych i zmiennych danych lub chcesz przechowywać te dane w
.text
segmencie, aby pasowały do tego samego typu pamięci co kod programu, możeszobjcopy
trochę więcej pobawić się parametrami.źródło
ld
ponieważ sugeruje się tam format wyjściowy, patrz stackoverflow.com/a/4158997/201725 .Możesz osadzać pliki binarne w pliku wykonywalnym za pomocą
ld
konsolidatora. Na przykład, jeśli masz plikfoo.bar
, możesz go osadzić w pliku wykonywalnym, dodając następujące polecenia dold
--format=binary foo.bar --format=default
Jeśli wywołujesz
ld
przezgcc
, będziesz musiał dodać-Wl
-Wl,--format=binary -Wl,foo.bar -Wl,--format=default
Tutaj
--format=binary
mówi konsolidatorowi, że następujący plik jest binarny i--format=default
przełącza się z powrotem na domyślny format wejściowy (jest to przydatne, jeśli określisz inne pliki wejściowe pofoo.bar
).Następnie możesz uzyskać dostęp do zawartości pliku z kodu:
extern uint8_t data[] asm("_binary_foo_bar_start"); extern uint8_t data_end[] asm("_binary_foo_bar_end");
Znajduje się tam również symbol nazwany
"_binary_foo_bar_size"
. Myślę, że to typowe,uintptr_t
ale nie sprawdziłem tego.źródło
data_end
tablicą, a nie wskaźnikiem? (A może to idiomatyczne C?)data_end
będzie wskaźnikiem, kompilator pomyśli, że po zawartości pliku znajduje się wskaźnik. Podobnie, jeśli zmienisz typdata
na wskaźnik, otrzymasz wskaźnik składający się z pierwszych bajtów pliku zamiast wskaźnika do jego początku. Chyba tak.const pointer
. Kompilator pozwala zmienić wartość wskaźników innych niż const, nie pozwala zmienić wartości, jeśli jest to tablica. Tak więc użycie składni tablicowej wymaga mniej pisania.Możesz umieścić wszystkie swoje zasoby w pliku ZIP i dołączyć go na końcu pliku wykonywalnego :
g++ foo.c -o foo0 zip -r resources.zip resources/ cat foo0 resources.zip >foo
To działa, ponieważ a) Większość wykonywalnych formatów obrazu nie przejmuje się tym, czy za obrazem znajdują się dodatkowe dane oraz b) zip przechowuje podpis pliku na końcu pliku zip . Oznacza to, że twój plik wykonywalny jest później zwykłym plikiem zip (z wyjątkiem pliku wykonywalnego z góry, który zip może obsłużyć), który można otworzyć i odczytać za pomocą libzip.
źródło
install_name_tool
. Poza tym plik binarny nadal działa jako plik wykonywalny.Z http://www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967 :
Niedawno miałem potrzebę osadzenia pliku w pliku wykonywalnym. Ponieważ pracuję w wierszu poleceń z gcc, et al, a nie z fantazyjnym narzędziem RAD, które sprawia, że wszystko dzieje się w magiczny sposób, nie od razu było dla mnie oczywiste, jak to zrobić. Trochę przeszukiwania sieci znalazło hack, który zasadniczo zakotował go na końcu pliku wykonywalnego, a następnie rozszyfrował, gdzie był oparty na zbiorze informacji, o których nie chciałem wiedzieć. Wydawało się, że powinien być lepszy sposób ...
I jest, to objcopy na ratunek. objcopy konwertuje pliki obiektowe lub pliki wykonywalne z jednego formatu na inny. Jeden z formatów, które rozumie, to „binarny”, czyli zasadniczo każdy plik, który nie jest w żadnym z innych formatów, które rozumie. Więc prawdopodobnie wyobraziłeś sobie pomysł: przekonwertuj plik, który chcemy osadzić w pliku obiektowym, a następnie można go po prostu połączyć z resztą naszego kodu.
Powiedzmy, że mamy nazwę pliku data.txt, którą chcemy osadzić w naszym pliku wykonywalnym:
Aby przekonwertować to na plik obiektowy, który możemy połączyć z naszym programem, używamy po prostu objcopy do utworzenia pliku „.o”:
To mówi objcopy, że nasz plik wejściowy jest w formacie "binarnym", a nasz plik wyjściowy powinien być w formacie "elf32-i386" (pliki obiektowe na x86). Opcja --binary-architecture mówi objcopy, że plik wyjściowy ma "działać" na x86. Jest to potrzebne, aby ld zaakceptował plik do łączenia z innymi plikami dla x86. Można by pomyśleć, że określenie formatu wyjściowego jako „elf32-i386” oznaczałoby to, ale tak nie jest.
Teraz, gdy mamy plik obiektowy, musimy go dołączyć tylko wtedy, gdy uruchamiamy konsolidator:
Kiedy uruchomimy wynik, otrzymamy modlony o wyjście:
Oczywiście nie opowiedziałem jeszcze całej historii, ani nie pokazałem ci main.c. Kiedy objcopy wykonuje powyższą konwersję, dodaje kilka symboli „konsolidatora” do przekonwertowanego pliku obiektowego:
Po połączeniu symbole te określają początek i koniec osadzonego pliku. Nazwy symboli są tworzone przez poprzedzające je binarne i dodanie _start lub _end do nazwy pliku. Jeśli nazwa pliku zawiera jakiekolwiek znaki, które byłyby nieprawidłowe w nazwie symbolu, są one konwertowane na podkreślenia (np. Data.txt staje się data_txt). Jeśli otrzymujesz nierozwiązane nazwy podczas łączenia przy użyciu tych symboli, wykonaj zrzut heksowy -C na pliku obiektowym i spójrz na koniec zrzutu, aby znaleźć nazwy wybrane przez objcopy.
Kod do faktycznego korzystania z osadzonego pliku powinien być teraz dość oczywisty:
#include <stdio.h> extern char _binary_data_txt_start; extern char _binary_data_txt_end; main() { char* p = &_binary_data_txt_start; while ( p != &_binary_data_txt_end ) putchar(*p++); }
Ważną i subtelną rzeczą, na którą należy zwrócić uwagę, jest to, że symbole dodane do pliku obiektowego nie są „zmiennymi”. Nie zawierają żadnych danych, raczej ich adres jest ich wartością. Deklaruję je jako typ char, ponieważ jest to wygodne w tym przykładzie: osadzone dane to dane znakowe. Jednak możesz zadeklarować je jako dowolne, jako int, jeśli dane są tablicą liczb całkowitych, lub jako struct foo_bar_t, jeśli dane byłyby dowolną tablicą słupków foo. Jeśli osadzone dane nie są jednolite, prawdopodobnie najwygodniejszy jest znak char: weź jego adres i rzuć wskaźnik na odpowiedni typ podczas przeglądania danych.
źródło
Jeśli chcesz mieć kontrolę nad dokładną nazwą symbolu i rozmieszczeniem zasobów, możesz użyć (lub skryptu) asemblera GNU (nie będącego częścią gcc) do importowania całych plików binarnych. Spróbuj tego:
Montaż (x86 / ramię):
.section .rodata .global thing .type thing, @object .balign 4 thing: .incbin "meh.bin" thing_end: .global thing_size .type thing_size, @object .balign 4 thing_size: .int thing_end - thing
DO:
#include <stdio.h> extern const char thing[]; extern const unsigned thing_size; int main() { printf("%p %u\n", thing, thing_size); return 0; }
Czegokolwiek używasz, prawdopodobnie najlepiej jest stworzyć skrypt generujący wszystkie zasoby i mieć ładne / jednolite nazwy symboli dla wszystkiego.
W zależności od danych i specyfiki systemu może być konieczne użycie różnych wartości wyrównania (najlepiej z
.balign
dla przenośności) lub typów całkowitych o różnym rozmiarzething_size
lub innego typu elementuthing[]
tablicy.źródło
Czytając cały post tutaj iw Internecie doszedłem do wniosku, że nie ma narzędzia do zasobów, jakim jest:
1) Łatwy w użyciu w kodzie.
2) Zautomatyzowany (aby można go było łatwo uwzględnić w cmake / make).
3) Wiele platform.
Zdecydowałem się napisać to narzędzie samodzielnie. Kod jest dostępny tutaj. https://github.com/orex/cpp_rsc
Używanie go z cmake jest bardzo łatwe.
Należy dodać taki kod do pliku CMakeLists.txt.
file(DOWNLOAD https://raw.github.com/orex/cpp_rsc/master/cmake/modules/cpp_resource.cmake ${CMAKE_BINARY_DIR}/cmake/modules/cpp_resource.cmake) set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}/cmake/modules) include(cpp_resource) find_resource_compiler() add_resource(pt_rsc) #Add target pt_rsc link_resource_file(pt_rsc FILE <file_name1> VARIABLE <variable_name1> [TEXT]) #Adds resource files link_resource_file(pt_rsc FILE <file_name2> VARIABLE <variable_name2> [TEXT]) ... #Get file to link and "resource.h" folder #Unfortunately it is not possible with CMake add custom target in add_executable files list. get_property(RSC_CPP_FILE TARGET pt_rsc PROPERTY _AR_SRC_FILE) get_property(RSC_H_DIR TARGET pt_rsc PROPERTY _AR_H_DIR) add_executable(<your_executable> <your_source_files> ${RSC_CPP_FILE})
Prawdziwy przykład z zastosowaniem tego podejścia można pobrać tutaj, https://bitbucket.org/orex/periodic_table
źródło