GCC가있는 C / C ++ : 실행 파일 / 라이브러리에 리소스 파일을 정적으로 추가


94

누구든지 GCC를 사용하여 리소스 파일을 실행 파일이나 공유 라이브러리 파일로 정적으로 컴파일하는 방법을 알고 있습니까?

예를 들어, 절대 변경되지 않는 이미지 파일을 추가하고 싶습니다 (그렇다면 어쨌든 파일을 교체해야합니다). 파일 시스템에있는 것을 원하지 않습니다.

이것이 가능하다면 (그리고 Windows 용 Visual C ++도이 작업을 수행 할 수 있기 때문이라고 생각합니다), 자체 바이너리에 저장된 파일을 어떻게로드 할 수 있습니까? 실행 파일 자체를 구문 분석하고 파일을 찾아서 데이터를 추출합니까?

아직 보지 못한 GCC 옵션이있을 수 있습니다. 검색 엔진을 사용한다고해서 제대로 된 정보가 나오지 않았습니다.

공유 라이브러리와 일반 ELF 실행 파일에서 작동하려면 이것이 필요합니다.

도움을 주시면 감사하겠습니다



blueberryfields가 지적한 질문의 objcopy 링크도 이것에 대한 훌륭하고 일반적인 솔루션입니다
Flexo

@blueberryfields : 복제해서 죄송합니다. 네가 옳아. 일반적으로 나는 중복으로 닫기에 투표합니다. 하지만 모두 좋은 답변을 올렸기 때문에 하나만 받아 들일 게요.
Atmocreations

John Ripley의 방법이 정렬이라는 큰 이유 때문에 여기에서 아마도 가장 좋은 방법이라고 덧붙일 수 있습니까? 표준 objcopy 또는 "ld -r -b binary -o foo.o foo.txt"를 수행 한 다음 objdump -x로 결과 객체를 보면 블록 정렬이 0으로 설정된 것처럼 보입니다. 원하는 경우 char 이외의 이진 데이터에 대해 올바르게 정렬하면 이것이 좋은 것이라고 상상할 수 없습니다.
carveone

답변:


49

ImageMagick이 :

convert file.png data.h

다음과 같은 것을 제공합니다.

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

....

다른 코드와의 호환성을 위해 당신은 다음 중 하나를 사용할 수 fmemopen는 "일반"얻을 FILE *대안으로 객체를, 또는 std::stringstream를 만들기 위해 iostream. std::stringstream그래도 좋지는 않으며 물론 반복기를 사용할 수있는 곳이면 어디에서나 포인터를 사용할 수 있습니다.

automake와 함께 이것을 사용하는 경우 BUILT_SOURCES를 적절하게 설정 하는 것을 잊지 마십시오 .

이렇게하면 좋은 점은 다음과 같습니다.

  1. 텍스트를 출력하므로 버전 관리 및 패치를 현명하게 할 수 있습니다.
  2. 모든 플랫폼에서 이식 가능하고 잘 정의되어 있습니다.

2
블라 그! 그게 제가 생각한 해결책입니다. 왜 누구든지 이것을하고 싶어하는 이유는 저 밖에 있습니다. 잘 정의 된 네임 스페이스에 데이터 조각을 저장하는 것이 파일 시스템의 목적입니다.
Omnifarious

35
때때로 파일 시스템이 없거나 운영 체제가없는 곳에서 실행되는 실행 파일이 있습니다. 또는 알고리즘에 조회를 위해 미리 계산 된 테이블이 필요합니다. 그리고 프로그램의 저장 데이터를 보낼 때 반드시 많은 경우 작정가 나는 많은 감각을.
ndim

15
이 변환의 사용은 정확히 동일합니다xxd -i infile.bin outfile.h
greyfade

5
이 접근 방식의 한 가지 단점은 이미지가 특히 크면 일부 컴파일러가 이러한 거대한 정적 배열을 처리 할 수 ​​없다는 것입니다. 이 문제를 해결하는 방법은 ndim이 제안한 것처럼 objcopy이진 데이터를 객체 파일로 직접 변환하는 데 사용 하는 것입니다. 그러나 이것은 거의 문제가되지 않습니다.
Adam Rosenfield

3
이와 같이 헤더에 정의하면이를 포함하는 각 파일이 자체 사본을 갖게됩니다. 헤더에서 extern으로 선언 한 다음 cpp에서 정의하는 것이 좋습니다. 여기 예
Nicholas Smith

90

업데이트 저는 John Ripley의 어셈블리 .incbin기반 솔루션이 제공 하는 컨트롤을 선호하도록 성장 했으며 이제 이에 대한 변형을 사용합니다.

objcopy (GNU binutils)를 사용하여 foo-data.bin 파일의 바이너리 데이터를 실행 파일의 데이터 섹션에 연결했습니다.

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

그러면 foo-data.o실행 파일에 연결할 수 있는 개체 파일 이 제공 됩니다. C 인터페이스는 다음과 같습니다.

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

그래서 당신은 같은 일을 할 수 있습니다

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

또는

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

대상 아키텍처에 상수 및 가변 데이터가 저장되는 위치에 대한 특별한 제약이 있거나 해당 데이터를 .text세그먼트에 저장 하여 프로그램 코드와 동일한 메모리 유형에 맞도록하려면 objcopy매개 변수를 좀 더 사용할 수 있습니다 .


좋은 생각! 제 경우에는별로 유용하지 않습니다. 하지만 이것은 제가 스 니펫 컬렉션에 실제로 넣을 것입니다. 공유 해주셔서 감사합니다!
Atmocreations

2
ld출력 형식이 여기에 암시 되어 있으므로 사용하기가 조금 더 쉽습니다 ( stackoverflow.com/a/4158997/201725 참조) .
Jan Hudec 2014 년

52

ld링커를 사용하여 실행 파일에 바이너리 파일을 포함 할 수 있습니다 . 예를 들어 파일이있는 경우 foo.bar다음 명령을 추가하여 실행 파일에 포함 할 수 있습니다.ld

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

ld통해 호출하는 경우 gcc다음을 추가해야합니다.-Wl

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

여기 --format=binary에서는 링커에게 다음 파일이 이진 파일이고 --format=default기본 입력 형식으로 다시 전환 된다는 것을 알려줍니다 (이후에 다른 입력 파일을 지정하려는 경우 유용합니다 foo.bar).

그런 다음 코드에서 파일 콘텐츠에 액세스 할 수 있습니다.

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

라는 기호도 "_binary_foo_bar_size"있습니다. 유형이라고 생각 uintptr_t하지만 확인하지 않았습니다.


매우 흥미로운 댓글입니다. 공유 해주셔서 감사합니다!
Atmocreations

1
잘 했어! 한 가지 질문 : 왜 data_end포인터 가 아니라 배열입니까? (또는 이것은 관용적 인 C입니까?)
xtofl

2
@xtofl, data_end포인터가 있으면 컴파일러는 파일 내용 뒤에 저장된 포인터가 있다고 생각합니다. 유사하게, 유형을 data포인터로 변경 하면 시작 포인터 대신 파일의 첫 번째 바이트로 구성된 포인터를 얻게됩니다. 나도 그렇게 생각해.
Simon

1
+1 : 귀하의 답변은 사용자 정의 자바 런처를 구축하기 위해 자바 클래스 로더와 Jar를 exe에 포함시킬 수있게 해줍니다
Aubin

2
@xtofl-포인터로 만들려면 const pointer. 컴파일러를 사용하면 상수가 아닌 포인터의 값을 변경할 수 있지만 배열 인 경우 값을 변경할 수 없습니다. 따라서 배열 구문을 사용하는 것이 더 적을 것입니다.
Jesse Chisholm

40

모든 리소스를 ZIP 파일 에 넣고 실행 파일 끝에 추가 할 수 있습니다 .

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

a) 대부분의 실행 가능 이미지 형식은 이미지 뒤에 추가 데이터가 있는지 상관하지 않고 b) zip 은 zip 파일끝에 파일 서명을 저장하기 때문에 작동 합니다 . 즉, 실행 파일은 이후의 일반 zip 파일 (zip이 처리 할 수있는 선행 실행 파일 제외)이며 libzip으로 열고 읽을 수 있습니다.


7
foo0과 resources.zip을 foo에 조인하려면 cat 명령 줄에 두 입력을 모두 제공하면>가 필요합니다. (이미 foo에있는 항목에 추가하고 싶지 않기 때문에)
Nordic Mainframe

1
아 그래, 내 실수. 나는 처음 읽었을 때 이름에서 0을 제대로 발견하지 못했습니다.
Flexo

이것은 매우 영리합니다. +1.
Linuxios dec

1
+1 Wonderful, 특히 miniz
mvp

이는 잘못된 바이너리 (최소한 Mac 및 Linux에서는)를 생성하며 install_name_tool. 그 외에도 바이너리는 여전히 실행 파일로 작동합니다.
Andy Li

36

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

최근에 실행 파일에 파일을 삽입해야했습니다. 내가 gcc, et al을 사용하여 명령 줄에서 작업하고 있기 때문에 모든 것이 마술처럼 일어나게하는 멋진 RAD 도구를 사용하지 않기 때문에이 작업을 수행하는 방법이 즉시 명확하지 않았습니다. 인터넷에서 약간의 검색을 통해 기본적으로 실행 파일의 끝 부분을 찾아 내고 내가 알고 싶지 않은 많은 정보를 기반으로 어디에 있는지 해독하는 해킹을 발견했습니다. 더 나은 방법이 있어야 할 것 같았습니다 ...

그리고 구출을위한 objcopy입니다. objcopy는 개체 파일 또는 실행 파일을 한 형식에서 다른 형식으로 변환합니다. 이해하는 형식 중 하나는 기본적으로 이해하는 다른 형식 중 하나가 아닌 모든 파일 인 "이진"입니다. 그래서 여러분은 아마도 아이디어를 구상했을 것입니다. 우리가 포함하고 싶은 파일을 객체 파일로 변환하면 나머지 코드와 간단히 연결될 수 있습니다.

실행 파일에 포함하려는 파일 이름 data.txt가 있다고 가정 해 보겠습니다.

# cat data.txt
Hello world

이것을 프로그램과 연결할 수있는 객체 파일로 변환하려면 objcopy를 사용하여 ".o"파일을 생성합니다.

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

이것은 objcopy에게 입력 파일이 "바이너리"형식이고 출력 파일이 "elf32-i386"형식 (x86의 객체 파일)이어야 함을 알려줍니다. --binary-architecture 옵션은 출력 파일이 x86에서 "실행"되도록 objcopy에 알려줍니다. 이는 ld가 x86 용 다른 파일과의 링크를 위해 파일을 허용하기 위해 필요합니다. 출력 형식을 "elf32-i386"으로 지정하면이를 의미한다고 생각할 수 있지만 그렇지 않습니다.

이제 개체 파일이 생겼으므로 링커를 실행할 때만 포함하면됩니다.

# gcc main.c data.o

결과를 실행하면 출력을 위해기도합니다.

# ./a.out
Hello world

물론, 나는 아직 전체 이야기를 말하지 않았고 당신에게 main.c를 보여주지 않았습니다. objcopy가 위의 변환을 수행 할 때 변환 된 개체 파일에 "링커"기호를 추가합니다.

_binary_data_txt_start
_binary_data_txt_end

링크 후 이러한 기호는 포함 된 파일의 시작과 끝을 지정합니다. 기호 이름은 바이너리 앞에 추가하고 파일 이름에 _start 또는 _end를 추가하여 구성됩니다 . 파일 이름에 기호 이름에 유효하지 않은 문자가 포함되어 있으면 밑줄로 변환됩니다 (예 : data.txt가 data_txt가 됨). 이러한 기호를 사용하여 링크 할 때 확인되지 않은 이름이 표시되면 개체 파일에서 hexdump -C를 수행하고 objcopy가 선택한 이름에 대한 덤프 끝을 확인합니다.

포함 된 파일을 실제로 사용하는 코드는 이제 상당히 명확해야합니다.

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

한 가지 중요하고 미묘한 점은 개체 파일에 추가 된 기호가 "변수"가 아니라는 것입니다. 그들은 데이터를 포함하지 않고 주소가 가치입니다. 이 예제에서는 편리하기 때문에 char 형식으로 선언합니다. 포함 된 데이터는 문자 데이터입니다. 그러나 데이터가 정수 배열이면 int로 선언하고 데이터가 foo 막대 배열이면 struct foo_bar_t로 선언 할 수 있습니다. 포함 된 데이터가 균일하지 않은 경우 char이 가장 ​​편리 할 것입니다. 데이터를 탐색 할 때 주소를 가져와 적절한 유형으로 포인터를 캐스팅합니다.


36

정확한 심볼 이름과 리소스 배치를 제어하려면 GNU 어셈블러 (실제로 gcc의 일부가 아님)를 사용 (또는 스크립트)하여 전체 바이너리 파일을 가져올 수 있습니다. 이 시도:

어셈블리 (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

씨:

#include <stdio.h>

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

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

무엇을 사용하든 모든 리소스를 생성하고 모든 것에 대해 멋지고 균일 한 기호 이름을 갖는 스크립트를 만드는 것이 가장 좋습니다.

데이터 및 시스템 특성에 따라 다른 정렬 값 ( .balign이동성 을 위해 사용하는 것이 좋음 ) 또는에 대해 다른 크기의 정수 유형을 사용 thing_size하거나 thing[]배열에 대해 다른 요소 유형 을 사용해야 할 수 있습니다 .


공유해 주셔서 감사합니다! 확실히 흥미로워 보이지만 이번에는 내가 찾고있는 것이 아닙니다 =) 안부
Atmocreations

1
내가 찾던 바로 그것. 크기가 4로 나눌 수없는 파일에서도 괜찮은지 확인할 수 있습니다. thing_size에 추가 패딩 바이트가 포함되는 것 같습니다.
Pavel P

사물을 지역 상징으로 만들고 싶다면 어떻게해야합니까? 내 어셈블리와 함께 컴파일러 출력을 분류 할 수 있지만 더 좋은 방법이 있습니까?
user877329

기록을 위해 : My edit는 @Pavel이 언급 한 추가 패딩 바이트 문제를 해결합니다.
ndim

4

여기와 인터넷에서 모든 게시물을 읽으면서 리소스 도구가 없다는 결론을 내 렸습니다.

1) 코드에서 사용하기 쉽습니다.

2) 자동화 됨 (cmake / make에 쉽게 포함).

3) 크로스 플랫폼.

도구를 직접 작성하기로 결정했습니다. 코드는 여기에서 확인할 수 있습니다. https://github.com/orex/cpp_rsc

cmake와 함께 사용하는 것은 매우 쉽습니다.

이러한 코드를 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})

접근 방식을 사용한 실제 예제는 여기에서 다운로드 할 수 있습니다. https://bitbucket.org/orex/periodic_table


더 많은 사람들에게 도움이 되려면 더 나은 설명이 필요하다고 생각합니다.
kyb
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.