2024/01/03(水) [n年前の日記]
#1 [prog] objcopyについて調べていた
先日、bin2c というツールを使えば C/C++で作ったexeの中にバイナリを含めることができる、と知ったのだけど。
_mieki256's diary - bin2cについて
以下のページで、objcopy というツールを使っても似たようなことができると知った。
_バイナリファイルの埋め込みかた
_メモ帳objcopyとobjdump
_Embedding a File in an Executable, aka Hello World, Version 5967 | Linux Journal
_objcopy(1) manページ
_ゼロからのOS自作入門 5章 osbook_day05c の objcopy のオプションメモ
objcopy は GNU Binutils に含まれているツールで、何かしらのバイナリファイルを、.o (オブジェクトファイル)に変換してくれる。.o になってしまえば、それをリンカに渡して結合することで、exe にそのバイナリを含めることができる。
bin2c的なやり方と比べた場合のメリットして、コンパイル時間を短くできる、ファイルサイズが大きくても結合できる可能性が高まる、等があるらしい。
気になったので試用してみた。環境は Windows10 x64 22H2 + MinGW (gcc 6.3.0) or MSYS2 MINGW64 (gcc 13.2.0)。
_mieki256's diary - bin2cについて
以下のページで、objcopy というツールを使っても似たようなことができると知った。
_バイナリファイルの埋め込みかた
_メモ帳objcopyとobjdump
_Embedding a File in an Executable, aka Hello World, Version 5967 | Linux Journal
_objcopy(1) manページ
_ゼロからのOS自作入門 5章 osbook_day05c の objcopy のオプションメモ
objcopy は GNU Binutils に含まれているツールで、何かしらのバイナリファイルを、.o (オブジェクトファイル)に変換してくれる。.o になってしまえば、それをリンカに渡して結合することで、exe にそのバイナリを含めることができる。
bin2c的なやり方と比べた場合のメリットして、コンパイル時間を短くできる、ファイルサイズが大きくても結合できる可能性が高まる、等があるらしい。
気になったので試用してみた。環境は Windows10 x64 22H2 + MinGW (gcc 6.3.0) or MSYS2 MINGW64 (gcc 13.2.0)。
◎ objcopyがインストールされてるか確認 :
MinGW上で、objcopy がインストールされているのか確認してみる。
MinGW (SourceForge版) の場合、objcopy 2.28 が入ってた。
MSYS2 MINGW64上でも確認。
MSYS2 MINGW64 には、objcopy 2.41 が入ってた。
> which objcopy "D:\MinGW\bin\objcopy.exe" > objcopy --version GNU objcopy (GNU Binutils) 2.28 Copyright (C) 2017 Free Software Foundation, Inc. This program is free software; you may redistribute it under the terms of the GNU General Public License version 3 or (at your option) any later version. This program has absolutely no warranty.
MinGW (SourceForge版) の場合、objcopy 2.28 が入ってた。
MSYS2 MINGW64上でも確認。
$ which objcopy /mingw64/bin/objcopy $ objcopy --version GNU objcopy (GNU Binutils) 2.41 Copyright (C) 2023 Free Software Foundation, Inc. ...
MSYS2 MINGW64 には、objcopy 2.41 が入ってた。
◎ 使ってみる :
例えば、texture.png を texture.o にしてみたい。
Windows10 + MinGW (32bit, i386) 上で使うので…以下のような指定になるのだろうか。elf32-i386 とかそのあたりの指定は一体何なのか分からんけど…。
たしかに texture.o が得られた。中身はどうなっているのだろう。objdump を使うと、.o の情報が得られるらしい。
-t をつければシンボルテーブルだけを確認できる。
「SYMBOL TABLE:」に注目。3つのシンボルが定義されてる。
Windows10 + MinGW (32bit, i386) 上で使うので…以下のような指定になるのだろうか。elf32-i386 とかそのあたりの指定は一体何なのか分からんけど…。
objcopy -I binary -O elf32-i386 -B i386 texture.png texture.o
たしかに texture.o が得られた。中身はどうなっているのだろう。objdump を使うと、.o の情報が得られるらしい。
> objdump -x texture.o texture.o: file format elf32-i386 texture.o architecture: i386, flags 0x00000010: HAS_SYMS start address 0x00000000 Sections: Idx Name Size VMA LMA File off Algn 0 .data 00019328 00000000 00000000 00000034 2**0 CONTENTS, ALLOC, LOAD, DATA SYMBOL TABLE: 00000000 l d .data 00000000 .data 00000000 g .data 00000000 _binary_texture_png_start 00019328 g .data 00000000 _binary_texture_png_end 00019328 g *ABS* 00000000 _binary_texture_png_size
-t をつければシンボルテーブルだけを確認できる。
> objdump -t texture.o texture.o: file format elf32-i386 SYMBOL TABLE: 00000000 l d .data 00000000 .data 00000000 g .data 00000000 _binary_texture_png_start 00019328 g .data 00000000 _binary_texture_png_end 00019328 g *ABS* 00000000 _binary_texture_png_size
「SYMBOL TABLE:」に注目。3つのシンボルが定義されてる。
_binary_texture_png_start // 開始位置を示す _binary_texture_png_end // 終了位置を示す _binary_texture_png_size // データサイズ
◎ Cソースから利用する :
objcopy で変換した .o には3つのシンボルが設定されてるので、.c から利用する際はそのシンボルを使えばいい。例えば以下のような記述になる。
ここでちょっとハマった。各シンボルの先頭の、「_」(underscore)を削除しつつ記述していることに注意。
Linux環境や、MSYS2 MINGW64 の場合は、各シンボルの先頭の「_」を削除しなくてもいいらしいけど…。Windows + MinGW の場合は、「_」を削除しないと 「undefined reference to `_binary_texture_png_start'」(「_binary_texture_png_start なんて定義されてねえよ」)といったエラーが出てしまう。おそらくは、Windows + MinGW だけの罠…。
このあたりについては、以下のやりとりで色々書かれている。
_c - Embedding binary blobs using gcc mingw - Stack Overflow
MinGW を使っているか、MSYS2 MINGW64 を使っているかで、記述を変えないといけないあたりは面倒だなと…。同じソースを利用できないではないか…。
一応、Cコンパイラ(プリプロセッサ)の定義済みマクロを使って、#ifdef - #else - #endif で記述を分けることはできそう。以下のような感じだろうか。
このあたり、他に上手い方法は無いのかなあ…。
余談。各環境の定義済みマクロについては以下を参考にした。
_Qt (c++)でWindowsとLinuxを認識するマクロ - スタック・オーバーフロー
_C言語で定義済みマクロによるコンパイラ及びOS判定 [新石器Wiki]
_定義済みマクロ - おなかすいたWiki!
_Pre-defined Compiler Macros / Wiki / Compilers
extern const unsigned char binary_texture_png_start[]; // start address extern const unsigned char binary_texture_png_end[]; // end address extern const unsigned char binary_texture_png_size[]; // size // ... { const unsigned char *png_ptr = binary_texture_png_start; // start address size_t png_size = (size_t)binary_texture_png_size; // size texture = createTextureFromPngInMemory(png_ptr, png_size); }
ここでちょっとハマった。各シンボルの先頭の、「_」(underscore)を削除しつつ記述していることに注意。
Linux環境や、MSYS2 MINGW64 の場合は、各シンボルの先頭の「_」を削除しなくてもいいらしいけど…。Windows + MinGW の場合は、「_」を削除しないと 「undefined reference to `_binary_texture_png_start'」(「_binary_texture_png_start なんて定義されてねえよ」)といったエラーが出てしまう。おそらくは、Windows + MinGW だけの罠…。
このあたりについては、以下のやりとりで色々書かれている。
_c - Embedding binary blobs using gcc mingw - Stack Overflow
MinGW を使っているか、MSYS2 MINGW64 を使っているかで、記述を変えないといけないあたりは面倒だなと…。同じソースを利用できないではないか…。
一応、Cコンパイラ(プリプロセッサ)の定義済みマクロを使って、#ifdef - #else - #endif で記述を分けることはできそう。以下のような感じだろうか。
#ifdef __MINGW64__ // MSYS2 MINGW64 extern const unsigned char _binary_texture_png_start[]; // binary start address extern const unsigned char _binary_texture_png_end[]; // binary end address extern const unsigned char _binary_texture_png_size[]; // binary size #else // MinGW extern const unsigned char binary_texture_png_start[]; // binary start address extern const unsigned char binary_texture_png_end[]; // binary end address extern const unsigned char binary_texture_png_size[]; // binary size #endif // ... { #ifdef __MINGW64__ // MSYS2 MINGW64 const unsigned char *png_ptr = _binary_texture_png_start; // start address size_t png_size = (size_t)_binary_texture_png_size; // size #else // MinGW const unsigned char *png_ptr = binary_texture_png_start; // start address size_t png_size = (size_t)binary_texture_png_size; // size #endif texture = createTextureFromPngInMemory(png_ptr, png_size); }
このあたり、他に上手い方法は無いのかなあ…。
余談。各環境の定義済みマクロについては以下を参考にした。
_Qt (c++)でWindowsとLinuxを認識するマクロ - スタック・オーバーフロー
_C言語で定義済みマクロによるコンパイラ及びOS判定 [新石器Wiki]
_定義済みマクロ - おなかすいたWiki!
_Pre-defined Compiler Macros / Wiki / Compilers
◎ objcopyの代わりにldを使う :
前述のページの中で、objcopy ではなく ld を使って、何かしらのバイナリファイルを .o にする方法があると知った。
_c - Embedding binary blobs using gcc mingw - Stack Overflow
例えば、texture.png を texture.o にするなら、以下のような感じだろうか。
objdump で .o の情報を確認してみる。
含まれる情報が一気に増えた気もするけど…。シンボルについては objcopy を利用した時と同様に3つ用意されてるので、利用に関しては問題無さそう。
_c - Embedding binary blobs using gcc mingw - Stack Overflow
例えば、texture.png を texture.o にするなら、以下のような感じだろうか。
ld -r -b binary -o texture.o texture.pngi386 がどうとか指定しなくて済むので、こちらのほうがいいかもしれない。
objdump で .o の情報を確認してみる。
$ objdump -x texture.o texture.o: file format pe-x86-64 texture.o architecture: i386:x86-64, flags 0x00000038: HAS_DEBUG, HAS_SYMS, HAS_LOCALS start address 0x0000000000000000 Characteristics 0x5 relocations stripped line numbers stripped Time/Date Thu Jan 01 09:00:00 1970 Magic 0000 MajorLinkerVersion 0 MinorLinkerVersion 0 SizeOfCode 0000000000000000 SizeOfInitializedData 0000000000000000 SizeOfUninitializedData 0000000000000000 AddressOfEntryPoint 0000000000000000 BaseOfCode 0000000000000000 ImageBase 0000000000000000 SectionAlignment 00000000 FileAlignment 00000000 MajorOSystemVersion 0 MinorOSystemVersion 0 MajorImageVersion 0 MinorImageVersion 0 MajorSubsystemVersion 0 MinorSubsystemVersion 0 Win32Version 00000000 SizeOfImage 00000000 SizeOfHeaders 00000000 CheckSum 00000000 Subsystem 00000000 (unspecified) DllCharacteristics 00000000 SizeOfStackReserve 0000000000000000 SizeOfStackCommit 0000000000000000 SizeOfHeapReserve 0000000000000000 SizeOfHeapCommit 0000000000000000 LoaderFlags 00000000 NumberOfRvaAndSizes 00000000 The Data Directory Entry 0 0000000000000000 00000000 Export Directory [.edata (or where ever we found it)] Entry 1 0000000000000000 00000000 Import Directory [parts of .idata] Entry 2 0000000000000000 00000000 Resource Directory [.rsrc] Entry 3 0000000000000000 00000000 Exception Directory [.pdata] Entry 4 0000000000000000 00000000 Security Directory Entry 5 0000000000000000 00000000 Base Relocation Directory [.reloc] Entry 6 0000000000000000 00000000 Debug Directory Entry 7 0000000000000000 00000000 Description Directory Entry 8 0000000000000000 00000000 Special Directory Entry 9 0000000000000000 00000000 Thread Storage Directory [.tls] Entry a 0000000000000000 00000000 Load Configuration Directory Entry b 0000000000000000 00000000 Bound Import Directory Entry c 0000000000000000 00000000 Import Address Table Directory Entry d 0000000000000000 00000000 Delay Import Directory Entry e 0000000000000000 00000000 CLR Runtime Header Entry f 0000000000000000 00000000 Reserved Sections: Idx Name Size VMA LMA File off Algn 0 .data 00019330 0000000000000000 0000000000000000 00000064 2**4 CONTENTS, ALLOC, LOAD, DATA 1 .rdata 00000000 0000000000000000 0000000000000000 00000000 2**4 ALLOC, LOAD, READONLY, DATA SYMBOL TABLE: [ 0](sec 1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000000 _binary_texture_png_start [ 1](sec 1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000019328 _binary_texture_png_end [ 2](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000019328 _binary_texture_png_size
含まれる情報が一気に増えた気もするけど…。シンボルについては objcopy を利用した時と同様に3つ用意されてるので、利用に関しては問題無さそう。
◎ OpenGLで描画するサンプル :
昨日実験したソレと同様に、メモリ上のpngバイナリをstbライブラリで処理してOpenGLのテクスチャとして利用できるのか試した。pngバイナリを exe に含める際、昨日は bin2c を使ったけれど、今回は objcopy もしくは ld を使ってみた。
_メモリ上のpngバイナリをOpenGLのテクスチャとして利用
C言語のソースは以下。ビルドには、freeglut, stb(stb_image.h) が必要。
_04_loadpng_objcopy.c
MinGW用の Makefile は以下。.exe を生成する際、texture.o を追加した。texture.o は objcopy で生成している。
_Makefile
MSYS2 MINGW64用の Makefile は以下。texture.o は ld で生成している。
_Makefile.msys2
make と打てば 04_loadpng_objcopy.exe をビルドできる。実行するとウインドウが開く。
これで、obcopy、もしくは ld を使っても、png画像のバイナリを exe に含めることができると分かった。
_メモリ上のpngバイナリをOpenGLのテクスチャとして利用
C言語のソースは以下。ビルドには、freeglut, stb(stb_image.h) が必要。
_04_loadpng_objcopy.c
// OpenGL + stb sample. PNG image is in memory // use objcopy. png file to object file #include <stdio.h> #include <stdlib.h> #include <GL/freeglut.h> // use stb library #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" // png image file binary #ifdef __MINGW64__ // MSYS2 MINGW64 extern const unsigned char _binary_texture_png_start[]; // binary start address extern const unsigned char _binary_texture_png_end[]; // binary end address extern const unsigned char _binary_texture_png_size[]; // binary size #else // MinGW extern const unsigned char binary_texture_png_start[]; // binary start address extern const unsigned char binary_texture_png_end[]; // binary end address extern const unsigned char binary_texture_png_size[]; // binary size #endif GLuint texture; /** * Load texture from png image on memory * * @param[in] _pngData png binary on memory * @param[in] _pngDataLen png binary size * return GLuint OpenGL texture ID. if 0, process fails */ GLuint createTextureFromPngInMemory(const unsigned char* _pngData, int _pngLen) { GLuint texture; int width = 0, height = 0, bpp = 0; unsigned char *data = NULL; data = stbi_load_from_memory(_pngData, _pngLen, &width, &height, &bpp, 4); // create OpenGL texture glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); // Release allocated all memory stbi_image_free(data); return texture; } // draw OpenGL void display() { glClear(GL_COLOR_BUFFER_BIT); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // enable texture glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture); // draw polygon glBegin(GL_QUADS); GLfloat v = 0.85f; glTexCoord2f(0.0, 0.0); // set texture u, v glVertex2f(-v, v); glTexCoord2f(0.0, 1.0); glVertex2f(-v, -v); glTexCoord2f(1.0, 1.0); glVertex2f(v, -v); glTexCoord2f(1.0, 0.0); glVertex2f(v, v); glEnd(); // disable texture glDisable(GL_TEXTURE_2D); glFlush(); } // Keyboard callback function void keyboard(unsigned char key, int x, int y) { switch (key) { case '\x1B': case 'q': // Exit on escape or 'q' key press glutLeaveMainLoop(); // exit(EXIT_SUCCESS); break; } } int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA); glutInitWindowSize(1280, 720); glutCreateWindow("OpenGL Texture Example"); // create OpenGL texture from PNG image { #ifdef __MINGW64__ // MSYS2 MINGW64 const unsigned char *png_ptr = _binary_texture_png_start; // start address size_t png_size = (size_t)_binary_texture_png_size; // size #else // MinGW const unsigned char *png_ptr = binary_texture_png_start; // start address size_t png_size = (size_t)binary_texture_png_size; // size #endif texture = createTextureFromPngInMemory(png_ptr, png_size); if (!texture) { fprintf(stderr, "Failed create texture\n"); exit(1); } } glClearColor(0.2f, 0.4f, 0.8f, 1.0f); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glutKeyboardFunc(keyboard); glutDisplayFunc(display); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS); glutMainLoop(); return EXIT_SUCCESS; }
MinGW用の Makefile は以下。.exe を生成する際、texture.o を追加した。texture.o は objcopy で生成している。
_Makefile
# MinGW 04_loadpng_objcopy.exe: 04_loadpng_objcopy.c texture.o Makefile gcc 04_loadpng_objcopy.c texture.o -o 04_loadpng_objcopy.exe -D FREEGLUT_STATIC -lfreeglut_static -lopengl32 -lwinmm -lgdi32 texture.o: texture.png Makefile objcopy -I binary -O elf32-i386 -B i386 texture.png texture.o #texture.o: texture.png Makefile # ld -r -b binary -o texture.o texture.png .PHONY: clean clean: rm -f 04_loadpng_objcopy.exe rm -f *.o
MSYS2 MINGW64用の Makefile は以下。texture.o は ld で生成している。
_Makefile.msys2
# MSYS2 04_loadpng_objcopy.exe: 04_loadpng_objcopy.c texture.o Makefile gcc 04_loadpng_objcopy.c texture.o -o 04_loadpng_objcopy.exe -D FREEGLUT_STATIC -lfreeglut -lopengl32 -lwinmm -lgdi32 -static texture.o: texture.png Makefile ld -r -b binary -o texture.o texture.png .PHONY: clean clean: rm -f 04_loadpng_objcopy.exe rm -f *.o
make と打てば 04_loadpng_objcopy.exe をビルドできる。実行するとウインドウが開く。
これで、obcopy、もしくは ld を使っても、png画像のバイナリを exe に含めることができると分かった。
[ ツッコむ ]
以上です。