mieki256's diary



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

objcopyがインストールされてるか確認 :

MinGW上で、objcopy がインストールされているのか確認してみる。
> 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 とかそのあたりの指定は一体何なのか分からんけど…。
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 から利用する際はそのシンボルを使えばいい。例えば以下のような記述になる。

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 にするなら、以下のような感じだろうか。
ld -r -b binary -o texture.o texture.png
i386 がどうとか指定しなくて済むので、こちらのほうがいいかもしれない。

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
// 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 をビルドできる。実行するとウインドウが開く。

04_loadpng_objcopy_ss.png


これで、obcopy、もしくは ld を使っても、png画像のバイナリを exe に含めることができると分かった。

#2 [prog] Makefileの中で処理を分けたい

ここ数日、MinGW用と、MSYS2 MINGW64用の、2つのMakefileを書きながら実験してるけど、なんだか面倒臭くなってきた。make -f Makefile.msys2 と打つべき場面で make と打ってしまってエラーが出てきたり…。ちょっと面倒臭い。

1つの Makefile の中で、MinGW上で使おうとしているのか、MSYS2 MINGW64上で使おうとしているのか判別して、処理を分けることはできないのだろうか。

使ってるのは GNU Make。3.81 と 4.4.1。

処理を分けることもできるらしい :

調べてみたら、Makefile の中でも、ifeq - else - endif や ifdef - else - endif といった書き方ができるらしい。

また、MSYS2 上で作業する場合、環境変数 MSYSTEM に、MINGW32 とか MINGW64 といった文字列が入るらしい。
$ echo $MSYSTEM
MINGW64

MinGW上では、何も入らない。
> echo %MSYSTEM%
%MSYSTEM%

であれば、こういう書き方ができるかな…。
ifeq ($(MSYSTEM),MINGW64)

# MSYS2 MINGW64

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

else

# 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

endif

.PHONY: clean
clean:
    rm -f 04_loadpng_objcopy.exe
    rm -f *.o

サンプルについてくる Makefile としては、ちょっと分かりづらくなるな…。でもまあ、フツーはコンパイルの仕方を書かないでソースだけ紹介するサンプルが大半だし。まだ Makefile があるだけマシかも。

余談。特殊変数について :

余談。本来 Makefile を書くときは、$@ や $< 等の変数を使うのが当たり前だけど。

_Makefile の特殊変数・自動変数の一覧 | 晴耕雨読
_トリビアなmakefile入門

そのあたりを書いちゃうと、一見どういうことをしてるのか分からなくなりそうなので、あえてベタ書きしてます。ただ、こういう書き方をしてしまうなら、いっそのことbatファイルでもええやん、とも…。

以上、1 日分です。

過去ログ表示

Prev - 2024/01 - Next
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project