C / C ++ mit GCC: Fügen Sie der ausführbaren Datei / Bibliothek statisch Ressourcendateien hinzu


94

Hat jemand eine Idee, wie eine Ressourcendatei mithilfe von GCC statisch direkt in die ausführbare Datei oder die gemeinsam genutzte Bibliotheksdatei kompiliert werden kann?

Zum Beispiel möchte ich Bilddateien hinzufügen, die sich nie ändern (und wenn ja, müsste ich die Datei trotzdem ersetzen) und möchte nicht, dass sie im Dateisystem herumliegen.

Wenn dies möglich ist (und ich denke, es liegt daran, dass Visual C ++ für Windows dies auch kann), wie kann ich die Dateien laden, die in der eigenen Binärdatei gespeichert sind? Analysiert die ausführbare Datei selbst, findet die Datei und extrahiert die Daten daraus?

Vielleicht gibt es eine Option für GCC, die ich noch nicht gesehen habe. Die Verwendung von Suchmaschinen hat nicht wirklich das Richtige ausgespuckt.

Ich würde dies benötigen, um für gemeinsam genutzte Bibliotheken und normale ausführbare ELF-Dateien zu arbeiten.

Jede Hilfe wird geschätzt



Der objcopy Link in der Frage, auf die blueberryfields verwiesen hat, ist auch hier eine gute, generische Lösung
Flexo

@blueberryfields: Entschuldigung für das Duplizieren. Du hast recht. Normalerweise würde ich für das Schließen als Duplikat stimmen. Aber weil sie alle so nette Antworten gepostet haben, werde ich nur eine akzeptieren.
Atmocreations

Kann ich hinzufügen, dass John Ripleys Methode aus einem großen Grund wahrscheinlich die beste ist - Ausrichtung. Wenn Sie ein Standardobjekt oder "ld -r -b binary -o foo.o foo.txt" ausführen und dann das resultierende Objekt mit objdump -x betrachten, sieht es so aus, als ob die Ausrichtung für den Block auf 0 gesetzt ist. Wenn Sie möchten Die Ausrichtung muss für andere Binärdaten als char korrekt sein. Ich kann mir nicht vorstellen, dass dies eine gute Sache ist.
Carveone

1
jww

Antworten:


49

Mit imagemagick :

convert file.png data.h

Gibt etwas wie:

/*
  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, 

....

Aus Gründen der Kompatibilität mit anderem Code können Sie dann entweder fmemopenein "normales" FILE *Objekt abrufen oder alternativ std::stringstreamein Objekt erstellen iostream. std::stringstreamist jedoch nicht besonders gut dafür und Sie können natürlich einfach einen Zeiger überall dort verwenden, wo Sie einen Iterator verwenden können.

Wenn Sie dies mit automake verwenden, vergessen Sie nicht, BUILT_SOURCES entsprechend festzulegen .

Das Schöne daran ist:

  1. Sie erhalten Text heraus, so dass er in der Versionskontrolle und in Patches sinnvoll sein kann
  2. Es ist portabel und auf jeder Plattform gut definiert

2
Bleahg! Das ist die Lösung, an die ich auch gedacht habe. Warum irgendjemand dies jemals tun möchte, ist mir ein Rätsel. Das Speichern von Daten in einem genau definierten Namespace ist das Ziel von Dateisystemen.
Omnifarious

35
Gelegentlich haben Sie eine ausführbare Datei, die dort ausgeführt wird, wo kein Dateisystem oder sogar kein Betriebssystem vorhanden ist. Oder Ihr Algorithmus benötigt eine vorberechnete Tabelle für die Suche. Und ich bin sicher, dass es viel mehr Fälle gibt, in denen das Speichern von Daten im Programm sehr sinnvoll ist.
ndim

15
Diese Verwendung von konvertieren ist genau die gleiche wiexxd -i infile.bin outfile.h
greyfade

5
Ein Nachteil dieses Ansatzes ist, dass einige Compiler solche enormen statischen Arrays nicht verarbeiten können, wenn Ihre Bilder besonders groß sind. Der Weg, dies zu umgehen , besteht, wie ndim vorschlägt, objcopydarin, die Binärdaten direkt in eine Objektdatei zu konvertieren. Dies ist jedoch selten ein Problem.
Adam Rosenfield

3
Beachten Sie, dass das Definieren in einem solchen Header bedeutet, dass jede Datei, die es enthält, eine eigene Kopie erhält. Es ist besser, es im Header als extern zu deklarieren und dann in einem CPP zu definieren. Beispiel hier
Nicholas Smith

90

Update Ich bin gewachsen, um die Steuerung zu bevorzugen, die John Ripleys Assembler- .incbinbasierte Lösung anbietet, und verwende jetzt eine Variante dafür.

Ich habe objcopy (GNU binutils) verwendet, um die Binärdaten aus einer Datei foo-data.bin mit dem Datenabschnitt der ausführbaren Datei zu verknüpfen:

objcopy -B i386 -I binary -O elf32-i386 foo-data.bin foo-data.o

Dadurch erhalten Sie eine foo-data.oObjektdatei, die Sie mit Ihrer ausführbaren Datei verknüpfen können. Die C-Schnittstelle sieht ungefähr so ​​aus

/** 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");

so können Sie Sachen wie tun

for (uint8_t *byte=foo_data; byte<foo_data_end; ++byte) {
    transmit_single_byte(*byte);
}

oder

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);

Wenn Ihre Zielarchitektur besondere Einschränkungen hinsichtlich der Speicherung von konstanten und variablen Daten aufweist oder Sie diese Daten im .textSegment speichern möchten, damit sie in denselben Speichertyp wie Ihr Programmcode passen, können Sie mit den objcopyParametern noch mehr spielen.


gute Idee! In meinem Fall ist es nicht sehr nützlich. Aber das ist etwas, das ich wirklich in meine Snippet-Sammlung aufnehmen werde. Danke, dass du das geteilt hast!
Atmocreations

2
Die Verwendung ist etwas einfacher, ldda das Ausgabeformat dort impliziert ist (siehe stackoverflow.com/a/4158997/201725) .
Jan Hudec

52

Sie können Binärdateien mit dem ldLinker in die ausführbare Datei einbetten . Wenn Sie beispielsweise eine Datei haben foo.bar, können Sie diese in eine ausführbare Datei einbetten und die folgenden Befehle hinzufügenld

--format=binary foo.bar --format=default

Wenn Sie den Aufruf sind ldbis gccdann müssen Sie hinzufügen-Wl

-Wl,--format=binary -Wl,foo.bar -Wl,--format=default

Hier --format=binaryteilt der Linker mit, dass die folgende Datei binär ist und --format=defaultzum Standardeingabeformat zurückschaltet (dies ist nützlich, wenn Sie danach andere Eingabedateien angeben foo.bar).

Dann können Sie über den Code auf den Inhalt Ihrer Datei zugreifen:

extern uint8_t data[]     asm("_binary_foo_bar_start");
extern uint8_t data_end[] asm("_binary_foo_bar_end");

Es gibt auch ein Symbol mit dem Namen "_binary_foo_bar_size". Ich denke, es ist vom Typ, uintptr_taber ich habe es nicht überprüft.


Sehr interessanter Kommentar. Danke, dass du das geteilt hast!
Atmocreations

1
Schön! Nur eine Frage: Warum ist data_endein Array kein Zeiger? (Oder ist das idiomatische C?)
xtofl

2
@xtofl, wenn data_endes sich um einen Zeiger handelt, denkt der Compiler, dass nach dem Dateiinhalt ein Zeiger gespeichert ist. Ähnlich, wenn Sie den Typ dataeines Zeigers ändern, erhalten Sie einen Zeiger, der aus den ersten Bytes einer Datei besteht, anstatt auf den Anfang. Ich glaube schon.
Simon

1
+1: Ihre Antwort ermöglicht es mir, einen Java-Klassenlader und ein Jar in eine Exe einzubetten, um einen benutzerdefinierten Java-Launcher zu erstellen
Aubin

2
@xtofl - Wenn Sie es zu einem Zeiger machen wollen, machen Sie es zu einem const pointer. Mit dem Compiler können Sie den Wert von Nicht-Konstanten-Zeigern ändern. Wenn es sich um ein Array handelt, können Sie den Wert nicht ändern. Daher ist es möglicherweise weniger tippend, die Array-Syntax zu verwenden.
Jesse Chisholm

40

Sie können alle Ihre Ressourcen in eine ZIP-Datei einfügen und diese an das Ende der ausführbaren Datei anhängen :

g++ foo.c -o foo0
zip -r resources.zip resources/
cat foo0 resources.zip >foo

Dies funktioniert, da a) den meisten ausführbaren Bildformaten egal ist, ob sich hinter dem Bild zusätzliche Daten befinden, und b) zip die Dateisignatur am Ende der zip-Datei speichert . Dies bedeutet, dass Ihre ausführbare Datei danach eine reguläre Zip-Datei ist (mit Ausnahme Ihrer ausführbaren Upfront-Datei, die von Zip verarbeitet werden kann), die mit libzip geöffnet und gelesen werden kann.


7
Wenn ich foo0 und resources.zip zu foo verbinden möchte, brauche ich>, wenn ich beide Eingaben in der Befehlszeile von cat gebe. (weil ich nicht an das anhängen möchte, was bereits in foo ist)
Nordic Mainframe

1
Ach ja, mein Fehler. Ich habe die 0 dort im Namen beim ersten
Durchlesen

Das ist sehr klug. +1.
Linuxios

1
+1 Wunderbar, besonders wenn es mit Miniz
MVP

Dies erzeugt eine ungültige Binärdatei (zumindest unter Mac und Linux), die von Tools wie nicht verarbeitet werden kann install_name_tool. Außerdem funktioniert die Binärdatei weiterhin als ausführbare Datei.
Andy Li

36

Von http://www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967 :

Ich hatte vor kurzem die Notwendigkeit, eine Datei in eine ausführbare Datei einzubetten. Da ich mit gcc et al. An der Kommandozeile arbeite und nicht mit einem ausgefallenen RAD-Tool, das alles auf magische Weise ermöglicht, war mir nicht sofort klar, wie ich dies erreichen kann. Ein bisschen im Internet gesucht hat einen Hack gefunden, um ihn im Wesentlichen an das Ende der ausführbaren Datei zu kopieren und dann zu entschlüsseln, wo er auf einer Reihe von Informationen basiert, über die ich nichts wissen wollte. Es schien, als ob es einen besseren Weg geben sollte ...

Und es gibt ein Objekt zur Rettung. objcopy konvertiert Objektdateien oder ausführbare Dateien von einem Format in ein anderes. Eines der Formate, die es versteht, ist "binär". Dies ist im Grunde jede Datei, die nicht in einem der anderen Formate vorliegt, die es versteht. Sie haben sich also wahrscheinlich die Idee vorgestellt: Konvertieren Sie die Datei, die wir in eine Objektdatei einbetten möchten, und verknüpfen Sie sie einfach mit dem Rest unseres Codes.

Angenommen, wir haben einen Dateinamen data.txt, den wir in unsere ausführbare Datei einbetten möchten:

# cat data.txt
Hello world

Um dies in eine Objektdatei zu konvertieren, die wir mit unserem Programm verknüpfen können, verwenden wir einfach objcopy, um eine ".o" -Datei zu erstellen:

# objcopy --input binary \
--output elf32-i386 \
--binary-architecture i386 data.txt data.o

Dies teilt objcopy mit, dass unsere Eingabedatei im "binären" Format vorliegt, dass unsere Ausgabedatei im "elf32-i386" -Format vorliegen sollte (Objektdateien auf dem x86). Die Option --binary-Architektur teilt objcopy mit, dass die Ausgabedatei auf einem x86 "ausgeführt" werden soll. Dies ist erforderlich, damit ld die Datei zum Verknüpfen mit anderen Dateien für x86 akzeptiert. Man würde denken, dass die Angabe des Ausgabeformats als "elf32-i386" dies implizieren würde, aber dies ist nicht der Fall.

Nachdem wir eine Objektdatei haben, müssen wir sie nur noch einschließen, wenn wir den Linker ausführen:

# gcc main.c data.o

Wenn wir das Ergebnis ausführen, erhalten wir das Gebet für die Ausgabe:

# ./a.out
Hello world

Natürlich habe ich noch nicht die ganze Geschichte erzählt und dir main.c. Wenn objcopy die obige Konvertierung durchführt, werden der konvertierten Objektdatei einige "Linker" -Symbole hinzugefügt:

_binary_data_txt_start
_binary_data_txt_end

Nach dem Verknüpfen geben diese Symbole den Anfang und das Ende der eingebetteten Datei an. Die Symbolnamen werden gebildet, indem Binärdateien vorangestellt und _start oder _end an den Dateinamen angehängt werden. Wenn der Dateiname Zeichen enthält, die in einem Symbolnamen ungültig wären, werden sie in Unterstriche konvertiert (z. B. wird data.txt zu data_txt). Wenn Sie beim Verknüpfen mit diesen Symbolen ungelöste Namen erhalten, führen Sie einen Hexdump -C für die Objektdatei aus und suchen Sie am Ende des Speicherauszugs nach den Namen, die objcopy ausgewählt hat.

Der Code zur tatsächlichen Verwendung der eingebetteten Datei sollte jetzt ziemlich offensichtlich sein:

#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++);
}

Eine wichtige und subtile Sache ist, dass die der Objektdatei hinzugefügten Symbole keine "Variablen" sind. Sie enthalten keine Daten, sondern ihre Adresse ist ihr Wert. Ich deklariere sie als Typ char, weil es für dieses Beispiel praktisch ist: Die eingebetteten Daten sind Zeichendaten. Sie können sie jedoch als alles deklarieren, als int, wenn die Daten ein Array von Ganzzahlen sind, oder als struct foo_bar_t, wenn die Daten ein Array von foo-Balken sind. Wenn die eingebetteten Daten nicht einheitlich sind, ist char wahrscheinlich am bequemsten: Nehmen Sie die Adresse und setzen Sie den Zeiger auf den richtigen Typ, während Sie die Daten durchlaufen.


36

Wenn Sie die Kontrolle über den genauen Symbolnamen und die Platzierung von Ressourcen haben möchten, können Sie den GNU-Assembler (der nicht wirklich Teil von gcc ist) verwenden (oder skripten), um ganze Binärdateien zu importieren. Versuche dies:

Montage (x86 / Arm):

    .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

C:

#include <stdio.h>

extern const char thing[];
extern const unsigned thing_size;

int main() {
  printf("%p %u\n", thing, thing_size);
  return 0;
}

Was auch immer Sie verwenden, es ist wahrscheinlich am besten, ein Skript zu erstellen, um alle Ressourcen zu generieren, und schöne / einheitliche Symbolnamen für alles zu haben.

Abhängig von Ihren Daten und den Systemspezifikationen müssen Sie möglicherweise unterschiedliche Ausrichtungswerte (vorzugsweise mit .balignaus Gründen der Portabilität) oder ganzzahlige Typen unterschiedlicher Größe für thing_sizeoder einen anderen Elementtyp für das thing[]Array verwenden.


danke für das Teilen! sieht auf jeden Fall interessant aus, aber diesmal ist es nicht das, wonach ich suche =) Grüße
Atmocreations

1
Genau das, wonach ich gesucht habe. Vielleicht können Sie überprüfen, ob es auch für Dateien mit Größen geeignet ist, die nicht durch 4 teilbar sind. Es sieht so aus, als würde thing_size die zusätzlichen Füllbytes enthalten.
Pavel P

Was ist, wenn ich möchte, dass etwas ein lokales Symbol ist? Ich kann die Compilerausgabe wahrscheinlich zusammen mit meiner eigenen Assembly katzen, aber gibt es einen besseren Weg?
user877329

Für die Aufzeichnung: Meine Bearbeitung behebt das Problem der zusätzlichen Auffüllbytes @Pavel, die notiert wurden.
ndim

4

Beim Lesen aller Beiträge hier und im Internet bin ich zu dem Schluss gekommen, dass es kein Tool für Ressourcen gibt, nämlich:

1) Einfach im Code zu verwenden.

2) Automatisiert (um einfach in cmake / make enthalten zu sein).

3) Plattformübergreifend.

Ich habe beschlossen, das Tool selbst zu schreiben. Der Code ist hier verfügbar. https://github.com/orex/cpp_rsc

Die Verwendung mit cmake ist sehr einfach.

Sie sollten Ihrer CMakeLists.txt-Datei solchen Code hinzufügen.

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})

Das eigentliche Beispiel für diesen Ansatz kann hier heruntergeladen werden: https://bitbucket.org/orex/periodic_table


Ich denke, Ihre Antwort braucht eine bessere Erklärung, um für mehr Menschen nützlich zu werden.
Kyb
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.