GCC ile C / C ++: Yürütülebilir / kitaplığa kaynak dosyalarını statik olarak ekleyin


94

GCC'yi kullanarak herhangi bir kaynak dosyasını statik olarak yürütülebilir veya paylaşılan kitaplık dosyasına nasıl derleyeceğine dair bir fikri olan var mı?

Örneğin, asla değişmeyen (ve değiştirirlerse, yine de dosyayı değiştirmek zorunda kalacağım) ve dosya sisteminde yer almalarını istemeyen görüntü dosyaları eklemek istiyorum.

Bu mümkünse (ve bence Windows için Visual C ++ bunu da yapabilir), kendi ikili dosyasında depolanan dosyaları nasıl yükleyebilirim? Yürütülebilir dosya kendini ayrıştırıyor mu, dosyayı buluyor ve içindeki verileri alıyor mu?

Belki GCC için henüz görmediğim bir seçenek vardır. Arama motorlarını kullanmak gerçekten doğru şeyleri ortaya çıkarmadı.

Paylaşılan kitaplıklar ve normal ELF çalıştırılabilir dosyalar için çalışmak için buna ihtiyacım olacak.

Herhangi bir yardım takdir edilmektedir



Blueberryfields'ın işaret ettiği sorudaki objcopy bağlantısı, buna da iyi, genel bir çözüm
Flexo

@ blueberryfields: çoğalttığım için özür dilerim. Haklısın. Normalde mükerrer olarak yakına oy verirdim. Ama hepsi çok güzel cevaplar verdikleri için, sadece birini kabul edeceğim.
Atmocreations

John Ripley'in yönteminin büyük bir nedenden ötürü muhtemelen en iyisi olduğunu ekleyebilir miyim - hizalama. Standart bir objcopy veya "ld -r -b binary -o foo.o foo.txt" yaparsanız ve sonra objdump -x ile elde edilen nesneye bakarsanız, bloğun hizalaması 0 olarak ayarlanmış gibi görünür. hizalamanın char dışındaki ikili veriler için doğru olması için, bunun iyi bir şey olduğunu hayal edemiyorum.
carveone

Yanıtlar:


51

İle imagemagic'den :

convert file.png data.h

Şöyle bir şey verir:

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

....

Diğer kodlarla uyumluluk için, ya fmemopen"normal" bir FILE *nesne elde etmek için ya da alternatif std::stringstreamolarak bir iostream. std::stringstreamyine de bunun için harika değil ve elbette bir yineleyici kullanabileceğiniz her yerde bir işaretçi kullanabilirsiniz.

Bunu automake ile kullanıyorsanız, BUILT_SOURCES'i uygun şekilde ayarlamayı unutmayın .

Bu şekilde yapmanın güzel yanı şudur:

  1. Metni çıkarırsınız, böylece sürüm kontrolünde ve mantıklı bir şekilde yamalar olabilir
  2. Taşınabilir ve her platformda iyi tanımlanmıştır

2
Bleahg! Benim de düşündüğüm çözüm bu. Birinin bunu neden yapmak isteyeceği beni aşıyor. Veri parçalarını iyi tanımlanmış bir ad alanında depolamak, dosya sistemlerinin ne içindir.
Omnifarious

36
Bazen, dosya sisteminin olmadığı veya hatta işletim sisteminin olmadığı yerlerde çalışan bir yürütülebilir dosyanız vardır. Veya algoritmanızın aramalar için önceden hesaplanmış bir tabloya ihtiyacı var. Ve programda veri depolamanın çok mantıklı olduğu için daha fazla durum olduğundan eminim .
ndim

16
Bu dönüştürme kullanımıxxd -i infile.bin outfile.h
greyfade

5
Bu yaklaşımın bir dezavantajı, eğer resimleriniz özellikle büyükse, bazı derleyicilerin bu kadar büyük statik dizileri işleyememesidir; bunun etrafından dolaşmanın yolu, ndim'in önerdiği gibi objcopy, ikili verileri doğrudan bir nesne dosyasına dönüştürmek için kullanmaktır ; ancak bu nadiren bir endişe kaynağıdır.
Adam Rosenfield

3
Bunu bir başlıkta tanımlamanın, onu içeren her dosyanın kendi kopyasını alacağı anlamına geldiğini unutmayın. Bunu başlıkta extern olarak bildirmek ve sonra bir cpp'de tanımlamak daha iyidir. Örnek burada
Nicholas Smith

90

Güncelleme John Ripley'in montaj tabanlı çözümünü kontrol etmek için büyüdüm.incbin önerilerini ve şimdi bunun bir çeşidini kullanıyorum.

Bir foo-data.bin dosyasındaki ikili verileri çalıştırılabilir dosyanın veri bölümüne bağlamak için objcopy (GNU binutils) kullandım:

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

Bu foo-data.osize yürütülebilir dosyanıza bağlayabileceğiniz bir nesne dosyası verir . C arayüzü şuna benzer:

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

böylece gibi şeyler yapabilirsin

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

veya

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

Hedef mimariniz, sabit ve değişken verilerin nerede saklandığına dair özel kısıtlamalara sahipse veya bu verileri .text, program kodunuzla aynı bellek türüne sığdırmak için segmentte depolamak istiyorsanız , objcopyparametrelerle biraz daha oynayabilirsiniz .


İyi bir fikir! Benim durumumda pek kullanışlı değil. Ama bu, pasaj koleksiyonuma gerçekten koyacağım bir şey. Bunu paylaştığınız için teşekkürler!
Atmocreations

2
Burada ldçıktı formatı ima edildiği için kullanımı biraz daha kolaydır , bkz. Stackoverflow.com/a/4158997/201725 .
Jan Hudec

52

ldBağlayıcı kullanarak çalıştırılabilir dosyalara ikili dosyaları gömebilirsiniz . Örneğin, dosyanız varsa foo.bar, aşağıdaki komutları ekleyerek yürütülebilir dosyaya yerleştirebilirsiniz.ld

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

Eğer ldaracılığıyla çağırıyorsanız gcc, eklemeniz gerekecek-Wl

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

Burada --format=binarybağlayıcıya aşağıdaki dosyanın ikili olduğunu ve --format=defaultvarsayılan girdi biçimine geri döndüğünü söyler (daha sonra başka girdi dosyalarını belirtecekseniz bu yararlıdır foo.bar).

Ardından dosyanızın içeriğine koddan erişebilirsiniz:

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

Bir de sembol adı var "_binary_foo_bar_size". Tipik olduğunu düşünüyorum uintptr_tama kontrol etmedim.


Çok ilginç bir yorum. Bunu paylaştığınız için teşekkürler!
Atmocreations

1
Güzel bir! Tek bir soru: neden data_endbir dizi, işaretçi değil? (Yoksa bu deyimsel C mi?)
xtofl

2
@xtofl, eğer data_endbir işaretçi olacaksa, derleyici dosya içeriğinden sonra depolanan bir işaretçi olduğunu düşünecektir. Benzer şekilde, türünü databir işaretçiye çevirirseniz, başlangıcına işaretçi yerine bir dosyanın ilk baytlarından oluşan bir işaretçi alırsınız. Ben öyle düşünüyorum.
Simon

1
+1: Cevabınız, özel bir java başlatıcısı oluşturmak için bir java sınıfı yükleyici ve bir Jar'ı bir exe'ye yerleştirmeme izin veriyor
Aubin

2
@xtofl - Eğer onu bir işaretçi yapacaksanız, bir const pointer. Derleyici, const olmayan işaretçilerin değerini değiştirmenize izin verir, bir dizi ise değeri değiştirmenize izin vermez. Yani dizi sözdizimini kullanmak belki de daha az yazmaktır.
Jesse Chisholm

41

Tüm kaynaklarınızı bir ZIP dosyasına koyabilir ve bunu yürütülebilir dosyanın sonuna ekleyebilirsiniz :

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

Bu işe yarar, çünkü a) Çoğu yürütülebilir görüntü biçimi, görüntünün arkasında fazladan veri olup olmadığını umursamaz ve b) zip, dosya imzasını zip dosyasının sonunda saklar . Bu, çalıştırılabilir dosyanızın bundan sonra normal bir zip dosyası olduğu anlamına gelir (zip'in işleyebileceği ön çalıştırılabilir dosyanız hariç), libzip ile açılıp okunabilir.


7
Foo0 ve resource.zip'i foo'ya eklemek istersem, o zaman> cat komut satırında her iki girişi de verirsem ihtiyacım var. (çünkü zaten foo'da olanı eklemek istemiyorum)
Nordic Mainframe

1
ah evet, benim hatam. İlk
Flexo

Bu çok zekice. +1.
Linuxios

1
+1 Harika, özellikle miniz
mvp

Bu, gibi araçlar tarafından işlenemeyen geçersiz bir ikili (en azından Mac ve Linux'ta) üretecektir install_name_tool. Bunun yanında ikili hala çalıştırılabilir olarak çalışıyor.
Andy Li

37

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

Son zamanlarda bir dosyayı bir yürütülebilir dosyaya yerleştirme ihtiyacım vardı. Komut satırında gcc ve diğerleri ile çalıştığım ve her şeyin sihirli bir şekilde gerçekleşmesini sağlayan süslü bir RAD aracıyla çalışmadığım için, bunun nasıl yapılacağı hemen benim için açık değildi. İnternette biraz araştırma yapmak, esasen yürütülebilir dosyanın sonuna yerleştirmek ve daha sonra bilmek istemediğim bir sürü bilgiye dayanarak nerede olduğunu deşifre etmek için bir hack buldu. Görünüşe göre daha iyi bir yolu olmalı ...

Ve orada, kurtarmaya objcopy var. objcopy, nesne dosyalarını veya yürütülebilir dosyaları bir biçimden diğerine dönüştürür. Anladığı biçimlerden biri "ikili" dir, temelde anladığı diğer biçimlerden birinde olmayan herhangi bir dosyadır. Muhtemelen şu fikri düşünmüşsünüzdür: gömmek istediğimiz dosyayı bir nesne dosyasına dönüştürün, sonra kodumuzun geri kalanıyla basitçe bağlanabilir.

Çalıştırılabilir dosyamıza yerleştirmek istediğimiz data.txt adlı bir dosya adımız olduğunu varsayalım:

# cat data.txt
Hello world

Bunu programımıza bağlayabileceğimiz bir nesne dosyasına dönüştürmek için objcopy kullanıp bir ".o" dosyası oluşturuyoruz:

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

Bu objcopy'ye girdi dosyamızın "ikili" biçimde olduğunu, çıktı dosyamızın "elf32-i386" biçiminde olması gerektiğini (x86'daki nesne dosyaları) söyler. --Binary-architecture seçeneği objcopy'ye çıktı dosyasının bir x86 üzerinde "çalışması" gerektiğini söyler. Bu, ld'nin dosyayı x86 için diğer dosyalara bağlanmak üzere kabul etmesi için gereklidir. Çıktı formatını "elf32-i386" olarak belirlemenin bunu ima edeceğini düşünebilirsiniz, ama öyle değil.

Artık bir nesne dosyamız olduğuna göre, onu yalnızca bağlayıcıyı çalıştırdığımızda eklememiz gerekir:

# gcc main.c data.o

Sonucu çalıştırdığımızda çıktı için dua ederiz:

# ./a.out
Hello world

Elbette, tüm hikayeyi henüz anlatmadım, size ana kısmı göstermedim. C. Objcopy yukarıdaki dönüştürmeyi yaptığında, dönüştürülen nesne dosyasına bazı "bağlayıcı" semboller ekler:

_binary_data_txt_start
_binary_data_txt_end

Bağlandıktan sonra, bu semboller gömülü dosyanın başlangıcını ve sonunu belirtir. Sembol isimleri prepending oluşturduğu ikili ve dosya adına _start veya _end ekleme. Dosya adı, bir sembol adında geçersiz olabilecek herhangi bir karakter içeriyorsa, bunlar alt çizgilere dönüştürülür (örneğin, data.txt, data_txt olur). Bu sembolleri kullanarak bağlantı kurarken çözülmemiş isimler alırsanız, nesne dosyasında bir onaltılık döküm -C yapın ve objcopy'nin seçtiği isimler için dökümün sonuna bakın.

Gömülü dosyayı gerçekten kullanacak kod artık makul ölçüde açık olmalıdır:

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

Unutulmaması gereken önemli ve incelikli bir şey, nesne dosyasına eklenen sembollerin "değişkenler" olmamasıdır. Herhangi bir veri içermezler, adresleri onların değeridir. Bunları char türü olarak tanımlıyorum çünkü bu örnek için uygun: gömülü veri karakter verisidir. Bununla birlikte, bunları herhangi bir şey olarak, veriler bir tamsayı dizisiyse int olarak veya veriler herhangi bir foo bar dizisi ise struct foo_bar_t olarak bildirebilirsiniz. Gömülü veriler tek tip değilse, char muhtemelen en uygun olanıdır: adresini alın ve veriyi dolaşırken işaretçiyi uygun türe çevirin.


36

Tam sembol adı ve kaynakların yerleşimi üzerinde kontrol istiyorsanız, tüm ikili dosyaları içe aktarmak için GNU assembler'ı (gerçekten gcc'nin bir parçası değil) kullanabilir (veya komut dosyasını) kullanabilirsiniz. Bunu dene:

Montaj (x86 / kol):

    .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;
}

Ne kullanırsanız kullanın, muhtemelen tüm kaynakları oluşturmak için bir komut dosyası yapmak ve her şey için güzel / tek tip sembol adlarına sahip olmak en iyisidir.

Verilerinize ve sistem özelliklerine bağlı olarak, farklı hizalama değerleri (tercihen .baligntaşınabilirlik için) veya dizi için farklı boyutta tam sayı türleri thing_sizeveya farklı bir öğe türü kullanmanız gerekebilir thing[].


paylaşım için teşekkürler! kesinlikle ilginç görünüyor, ama bu sefer aradığım bu değil =) Saygılarımla
Atmocreations

1
Tam olarak aradığım şey. Belki 4'e kadar devredilemeyen boyutlara sahip dosyalar için de uygun olduğunu doğrulayabilirsiniz.
Pavel P

Ya bir şeyin yerel bir sembol olmasını istersem? Derleyici çıktısını muhtemelen kendi derlememle birlikte oluşturabilirim, ancak daha iyi bir yol var mı?
user877329

Kayıt için: Düzenlemem, @Pavel'in not ettiği fazladan doldurma baytları sorununu adresler.
ndim

4

Tüm gönderileri burada ve internette okurken, kaynaklar için bir araç olmadığı sonucuna vardım, yani:

1) Kodda kullanımı kolaydır.

2) Otomatik (cmake / make'e dahil edilmesi kolay).

3) Çapraz platform.

Aracı kendim yazmaya karar verdim. Kod burada mevcuttur. https://github.com/orex/cpp_rsc

Cmake ile kullanmak çok kolaydır.

CMakeLists.txt dosyanıza böyle bir kod eklemelisiniz.

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

Yaklaşımı kullanan gerçek örnek buradan indirilebilir, https://bitbucket.org/orex/periodic_table


1
Bence cevabınızın daha fazla insan için faydalı olması için daha iyi bir açıklamaya ihtiyacı var.
kyb
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.