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 に含めることができると分かった。
[ ツッコむ ]
以上です。
