![](https://habrastorage.org/storage2/438/be0/c60/438be0c60295fa01fbec0ec54947c008.jpg)
ARMのゼロからの開発に関する一連の記事を続けて、今日はGNU ldのリンカースクリプトの記述について説明します。 このトピックは、組み込みシステムで作業する人だけでなく、実行可能ファイルの構造をよりよく理解したい人にも役立ちます。 例はarm-none-eabiツールチェーンに何らかの形で基づいていますが、レイアウトの本質は、たとえばVisual Studioリンカーと同じです。
前の記事:
記事のコード例: https : //github.com/farcaller/arm-demos
ソースファイルをコンパイルすると、出力でオブジェクトファイルを取得します。通常、オブジェクトファイルには、データを含むいくつかのセクションが含まれています。 最も一般的な4つのセクションは次のとおりです。
- .text-コンパイルされたマシンコード。
- .data-グローバル変数および静的変数。
-
.data
不変データの.data
類似体。 - .bss-起動時にnull値を含むグローバル変数および静的変数。
このサイクルの一部として作業するバイナリファイルでは、多くの場合、さらに2つのセクションがあります。
- .comment-コンパイラーのバージョンに関する情報。
- .ARM.attributes -ARM固有のファイル属性。
セクションに加えて、オブジェクトファイルには、シンボルテーブルという別の重要なエンティティがあります。 これは一種のハッシュです:名前-アドレス(および追加の属性)。 たとえば、シンボルテーブルでは、エクスポートされたすべての関数とそのアドレス(.textセクションのどこかを示します)が示されます。
これらのファイルのいくつかを取得した後、リンカが使用されます。リンカは、指定されたルールに従って、すべてのセクションを収集し、不要なセクションを破棄して、最終的な実行可能ファイルを作成します。 「標準」OSの場合、配置する場所にルールが定義されますが、マイクロコントローラーの場合、通常はすべてをフラッシュとRAMに手動でプッシュする必要があります。
中を見てください
最初の例として、次のCコードを
module_a.c
ます:
module_a.c
:
static int local_function(); int external_counter; static int counter; static int preset_counter = 5; const int constant = 10; int public_function() { volatile int i = 3 + constant; ++external_counter; return local_function() * i; } static int local_function() { ++counter; ++preset_counter; return counter + preset_counter; }
コンパイルして、取得したセクションを確認します。
% rake 'show:sections[a]' arm-none-eabi-gcc -mthumb -O2 -mcpu=cortex-m0 -c module_a.c -o build/module_a.o arm-none-eabi-objdump build/module_a.o -h build/module_a.o: file format elf32-littlearm Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000034 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 1 .data 00000004 00000000 00000000 00000068 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000004 00000000 00000000 0000006c 2**2 ALLOC 3 .rodata 00000004 00000000 00000000 0000006c 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .comment 00000071 00000000 00000000 00000070 2**0 CONTENTS, READONLY 5 .ARM.attributes 00000031 00000000 00000000 000000e1 2**0 CONTENTS, READONLY
ご覧のとおり、6つのセクションがあり、その目的は多かれ少なかれ認識されています。 2行目はセクション属性であり、リンク時に後でより興味深いものになります。 これらのセクションで定義されている文字を見てみましょう。
% rake 'show:symbols:text[a]' arm-none-eabi-objdump build/module_a.o -j .text -t build/module_a.o: file format elf32-littlearm SYMBOL TABLE: 00000000 ld .text 00000000 .text 00000000 g F .text 00000034 public_function
相談のためにobjdumpでmanを開いてみましょう。 このセクションでは、2つの文字が表示され
.text
public_function
は、セクションの先頭を指すデバッグ文字です
public_function
は、関数を指す文字です。 関数は
static
として宣言されて
static
、つまり、オブジェクトファイルの外部にエクスポートされないため、
local_function
シンボルはありません。
% rake 'show:symbols:data[a]' arm-none-eabi-objdump build/module_a.o -j .data -j .bss -t build/module_a.o: file format elf32-littlearm SYMBOL TABLE: 00000000 ld .data 00000000 .data 00000000 ld .bss 00000000 .bss 00000000 l O .data 00000004 preset_counter 00000000 l O .bss 00000004 counter
.data
および
.bss
セクションには、2つのカウンター、
preset_counter
および
counter
ます。
preset_counter
の初期値は
.data
格納されているため、これらは異なるセクションにあります。
% rake 'show:contents[a,.data]' arm-none-eabi-objdump build/module_a.o -j .data -s build/module_a.o: file format elf32-littlearm Contents of section .data: 0000 05000000
counter
値
counter
ないため、ゼロに初期化され、
.bss
セクションで終了します。
.bss
セクション自体は、ファイル内に物理的に存在しません。その内容は常に固定されているため、これらはゼロです。 コードで
char buffer[1024]
を宣言した場合、コンパイラーはオブジェクトファイルにキロバイトの空きスペースを書き込む必要がありますが、これは意味がありません。
この時点で、質問があるかもしれません-external_counterはどこに行きましたか?
% rake 'show:symbols:all[a]' arm-none-eabi-objdump build/module_a.o -t build/module_a.o: file format elf32-littlearm SYMBOL TABLE: 00000000 l df *ABS* 00000000 module_a.c 00000000 ld .text 00000000 .text 00000000 ld .data 00000000 .data 00000000 ld .bss 00000000 .bss 00000000 ld .rodata 00000000 .rodata 00000000 l O .data 00000004 preset_counter 00000000 l O .bss 00000004 counter 00000000 ld .comment 00000000 .comment 00000000 ld .ARM.attributes 00000000 .ARM.attributes 00000000 g F .text 00000034 public_function 00000004 O *COM* 00000004 external_counter 00000000 g O .rodata 00000004 constant
external_counter
は
*COM*
セクションに移動しました。 この場合、これは、このオブジェクトファイルの外部にある可能性があることを意味します。 すでにリンクの段階で、 ldはシンボルが別のファイルで宣言されているか、それとも自分で作成する必要があるか(この場合は
.bss
セクションで)を判断します。 また、
const int constant
.rodata
ことに注意してください。 コンパイラは、コードがこのアドレスの値を変更する必要がないことを保証します。これにより、リンカが安全にフラッシュメモリに配置できます。
.comment
を見ることができます:
% rake 'show:contents[a,.comment]' arm-none-eabi-objdump build/module_a.o -j .comment -s build/module_a.o: file format elf32-littlearm Contents of section .comment: 0000 00474343 3a202847 4e552054 6f6f6c73 .GCC: (GNU Tools 0010 20666f72 2041524d 20456d62 65646465 for ARM Embedde 0020 64205072 6f636573 736f7273 2920342e d Processors) 4. 0030 372e3320 32303133 30333132 20287265 7.3 20130312 (re 0040 6c656173 6529205b 41524d2f 656d6265 lease) [ARM/embe 0050 64646564 2d345f37 2d627261 6e636820 dded-4_7-branch 0060 72657669 73696f6e 20313936 3631355d revision 196615] 0070 00
コンパイラのバージョンは実際にここに書かれています。
.ARM.attributes
調べることもできますが、このためにはobjdumpではなくreadelfを使用する価値があります。
% rake 'show:attrs[a]' arm-none-eabi-readelf build/module_a.o -A Attribute Section: aeabi File Attributes Tag_CPU_name: "Cortex-M0" Tag_CPU_arch: v6S-M Tag_CPU_arch_profile: Microcontroller Tag_THUMB_ISA_use: Thumb-1 Tag_ABI_PCS_wchar_t: 4 Tag_ABI_FP_denormal: Needed Tag_ABI_FP_exceptions: Needed Tag_ABI_FP_number_model: IEEE 754 Tag_ABI_align_needed: 8-byte Tag_ABI_align_preserved: 8-byte, except leaf SP Tag_ABI_enum_size: small Tag_ABI_optimization_goals: Aggressive Speed
パブリックタグのドキュメントは、 ARM Info Centerで表示できます。
すべてをまとめる
オブジェクトファイルの内部を確認したので、 ldがそれらを1つの成功したアプリケーションに収集する方法を見てみましょう。
ldの主な作業は、最初の部分で見たメモリカードを中心に展開します。 レイアウトを大幅に簡略化するために、オブジェクトファイルからセクションを切り取り、指定されたアドレスに展開し、相互参照を修正するプロセスです。 「標準」オペレーティングシステムでは、カーネルは出力ファイルを読み取り、予想される仮想アドレスでメモリにセクションをロードできます。 ダイナミックリンカーも同様のジョブを実行し、外部ライブラリを特定のメモリ位置にロードし、それらへの相互参照を設定します。
組み込みシステムでは、ファームウェアプログラムがバイナリファイルを取得し、USBフラッシュドライブにそのままアップロードします。 彼はマッチョやエルフを気にせず、バイナリダンプで作業します。
簡単なリンカースクリプトを取り、それを分解します。
layout.ld:
MEMORY { rom(RX) : ORIGIN = 0x00000000, LENGTH = 0x8000 ram(WAIL) : ORIGIN = 0x10000000, LENGTH = 0x2000 } ENTRY(public_function) SECTIONS { .text : { *(.text) } > rom _data_start = .; .data : { *(.data) } > ram AT> rom _bss_start = .; .bss : { *(.bss) } > ram _bss_end = .; }
デフォルトのリンカー構成では、使用可能なすべてのメモリ(32ビットARMの場合は0xFFFFFFFFバイト前後)を使用できます。 使用できるメモリ領域を定義することから始めましょう:
rom
と
ram
。 括弧内の文字は、読み取り、書き込み、実行、メモリ割り当ての属性を定義します。 スクリプトで明示的に指定されていないセクションは、適切な属性を持つ領域に自動的に分散されます。 セクションにスペースがない場合、リンカは動作を拒否し、その動作を次のように主張します:
error: no memory region specified for loadable section `.data'
。
ORIGIN
と
LENGTH
2つのパラメーターは、それぞれ領域の開始と長さを指定しますが、オプション
org
、
o
、
len
、および
l
見つけることができますが、これらは同等です。 値は式です。つまり、算術演算を実行したり、サフィックス
K
、
M
などを使用したりできます。 たとえば、
LENGTH = 0x8000
書き込みは、次のように行うこともできます:
l = 32K
。
ファイルの2番目の部分はセクション構成です。 一般に、これは
ソースセクションは
_(_)
形式で指定され、
*
文字は標準的な方法で動作するため、エントリ
*(.text)
は、すべてのファイルの
.text
セクションを意味します。
このセクションには、LMA(Load Memory Address)-ロード元とVMA(Virtual Memory Address)-仮想メモリで使用可能なアドレスの2つのアドレスがあります。 簡単に言えば、LMAはバイナリファイルで表示される場所であり、VMAは文字がリダイレクトされる場所です。つまり、コード内の文字へのポインタはVMAアドレスを参照します。
コード、データ、データの3つのセクションに興味があります。これらはデフォルトではゼロです。 したがって、コード(
.text
)をフラッシュメモリにコピーし、データ(
.data
)をフラッシュメモリにコピーし
.text
が、それらはRAMで使用可能であり、
.bss
はRAMで使用可能であると仮定しています。
.bss
場合、一般に初期化は必要ありません( UPD :ジャバーで何が必要かを教えてくれます。何らかの理由で現れたゴミではなく、ゼロであることを保証する必要があります)。メモリなど、おそらくゼロにリセットされます。 しかし、
.data
と、
.data
があり、問題は2つの性質に起因します。 一方では、特定のデータがそこに保存されます(
preset_counter
開始値)ので、フラッシュメモリにある必要があります。 一方、これは書き込み可能なセクションなので、RAMに配置する必要があります。 この問題は、異なるLMAとVMA、および追加のCコードによって解決されます。Cコードは、起動時にLMAからVMAにコンテンツをコピーします。 通常は
.rodata
セクションにある定数データの場合、このような手順は必要ありませんが、フラッシュメモリから直接安全に読み取ることができます。
リンカーにはカーソルの概念があります-これが現在のLMAです。 SECTIONSブロックの先頭では、カーソルはゼロであり、新しいセクションが追加されると徐々に増加します。 現在のカーソル値は変数に格納されます
.
(期間)。
リンカを実行して、その作業の結果を見てみましょう。
% rake 'show:map[a]' arm-none-eabi-ld -T layout.ld -M -o build/out.elf build/module_a.o Allocating common symbols Common symbol size file external_counter 0x4 build/module_a.o Memory Configuration Name Origin Length Attributes rom 0x0000000000000000 0x0000000000008000 xr ram 0x0000000010000000 0x0000000000002000 awl *default* 0x0000000000000000 0xffffffffffffffff
最初に、リンカが「共通」の
external_counter
シンボルを別のカテゴリに配置する方法を確認します。 次に、メモリ構成がロードされ、デフォルト構成(アドレス空間全体が割り当てられる)に追加されていることがわかります。
Linker script and memory map .text 0x0000000000000000 0x34 *(.text) .text 0x0000000000000000 0x34 build/module_a.o 0x0000000000000000 public_function 0x0000000000000034 _data_start = .
次に、リンカーは、まず
.text
で指定したセクションをメモリに配置し
.text
。
.rodata 0x0000000000000034 0x4 .rodata 0x0000000000000034 0x4 build/module_a.o 0x0000000000000034 constant .glue_7 0x0000000000000038 0x0 .glue_7 0x0000000000000000 0x0 linker stubs .glue_7t 0x0000000000000038 0x0 .glue_7t 0x0000000000000000 0x0 linker stubs .vfp11_veneer 0x0000000000000038 0x0 .vfp11_veneer 0x0000000000000000 0x0 linker stubs .v4_bx 0x0000000000000038 0x0 .v4_bx 0x0000000000000000 0x0 linker stubs .iplt 0x0000000000000038 0x0 .iplt 0x0000000000000000 0x0 build/module_a.o .rel.dyn 0x0000000000000038 0x0 .rel.iplt 0x0000000000000000 0x0 build/module_a.o
以下は、-.
.glue_7
、
.glue_7t
、
.vfp11_veneer
、
.v4_bx
、
.iplt
、
.rel.dyn
、
.iplt
明示的に示していないセクションです。
.rodata
すると、すべてが明確になり、
constant
が4バイトで格納されます。 残りのセクションについては、それらの存在は、たとえばARMからThumbへのジャンプなど、パフォーマンスのあらゆる種類のサポートによるものです。 これらのセクションはすべて空であり、最終的な画像には含まれません。
.data 0x0000000010000000 0x4 load address 0x0000000000000038 *(.data) .data 0x0000000010000000 0x4 build/module_a.o 0x0000000010000004 _data_end = .
.data
とおり、
.data
セクションは
0x10000000
にありますが、物理的には
0x38
(つまり
.rodata
直後)に格納されています。 ここで、カーソル
_data_end
から読み取った変数の値を確認します。
.igot.plt 0x0000000010000004 0x0 load address 0x000000000000003c .igot.plt 0x0000000000000000 0x0 build/module_a.o .bss 0x0000000010000004 0x8 load address 0x000000000000003c *(.bss) .bss 0x0000000010000004 0x4 build/module_a.o COMMON 0x0000000010000008 0x4 build/module_a.o 0x0000000010000008 external_counter 0x000000001000000c _bss_end = .
別の空のセクション。その後に
.bss
続きます。
LOAD build/module_a.o OUTPUT(build/out.elf elf32-littlearm) .comment 0x0000000000000000 0x70 .comment 0x0000000000000000 0x70 build/module_a.o 0x71 (size before relaxing) .ARM.attributes 0x0000000000000000 0x31 .ARM.attributes 0x0000000000000000 0x31 build/module_a.o
最後に、 ldは出力ファイルを生成し、不要なセクションを破棄します。 すべてが好きですか?
0x0000000000000034 _data_start = . ... .data 0x0000000010000000 0x4 load address 0x0000000000000038
.data
の先頭を指す変数は、実際には間違った場所を指し示しています! しかし、真実は、
.text
後のカーソルがその終わりを示しているということです。 変数を正しく設定するには、出力セクションの説明内に移動する必要があります。
.data : { _data_start = .; *(.data) _data_end = .; } > ram AT> rom
作成し、何が変更されたかを確認します。
% rake 'show:map[a]' SCRIPT=layout2.ld arm-none-eabi-ld -T layout2.ld -M -o build/module_a.elf build/module_a.o ... .data 0x0000000010000000 0x4 load address 0x0000000000000038 0x0000000010000000 _data_start = . *(.data) .data 0x0000000010000000 0x4 build/module_a.o 0x0000000010000004 _data_end = . ...
素晴らしい、今ではすべてが整っています。
疑問に思うかもしれませんが、私たちの問題は何ですか、
.data
は
.data
ありますか? 覚えているように、データは物理的にフラッシュに保存され、RAMからデータを操作する必要があります。 このため、
.data
をRAMにコピーするブートコードを
.data
する必要があります。これらの変数は、セクションを移動する特定のアドレスを見つけるのに役立ちます。
タスクを複雑にしましょう
1つのモジュールを見つけました。 2番目のファイルを追加して、どのような変更が行われるかを見てみましょう。 2番目のファイルには、既知の
external_counter
といくつかのC ++コードが含まれます:
module_b.cpp
int external_counter; extern "C" int public_function(); void function_b() { external_counter += public_function(); } void function_c() { } void function_d() { }
ご存知のように、C ++コードをコンパイルするとき、関数、メソッドの名前は「マングリング」を経ます。引数のタイプ、クラス、名前空間の名前は名前にエンコードされます:
% rake 'show:symbols:text[b]' arm-none-eabi-gcc -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -mthumb -O2 -mcpu=cortex-m0 -c module_b.cpp -o build/module_b.o arm-none-eabi-objdump build/module_b.o -j .text -t build/module_b.o: file format elf32-littlearm SYMBOL TABLE: 00000000 ld .text 00000000 .text 00000000 g F .text 00000014 _Z10function_bv 00000014 g F .text 00000002 _Z10function_cv 00000018 g F .text 00000002 _Z10function_dv
フラグ
-fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables
を使用してコードをコンパイルし、例外処理に関連する追加セクションの出現を回避します。 関数名はそれに応じてエンコードされています。
このモジュールのマップを生成することはできません。それは、それ自体では構築できないため、モジュール
a
public_function
関数に依存しています。 両方のモジュールを一度に構成します。
% rake 'show:map[a|b]' SCRIPT=layout2.ld arm-none-eabi-ld -T layout2.ld -M -o build/out.elf build/module_a.o build/module_b.o ... .text 0x0000000000000000 0x34 build/module_a.o 0x0000000000000000 public_function .text 0x0000000000000034 0x1c build/module_b.o 0x0000000000000034 function_b() 0x0000000000000048 function_c() 0x000000000000004c function_d() ...
共通文字のブロックはなくなり、すべての文字は対応するモジュールにあります。
.text
セクションとその他のセクションは、次々に配置されます。
ゴミを集めよう!
組み込みアプリケーションの場合、出力ファイルのサイズはこれまで以上に重要であるため、不必要なデータとデッドコードの最大量が削除されていることを確認する必要があります。 リンカは、参照されておらず、ビルドスクリプトで必要に応じて明示的に指定されていないセクションを削除できます。 これは非常に簡単に行われます
--gc-sections
フラグを使用:
% rake 'show:map[a|b]' SCRIPT=layout2.ld GC=1 arm-none-eabi-ld --gc-sections -T layout2.ld -M -o build/out.elf build/module_a.o build/module_b.o Discarded input sections .rodata 0x0000000000000000 0x4 build/module_a.o COMMON 0x0000000000000000 0x0 build/module_a.o .text 0x0000000000000000 0x1c build/module_b.o .data 0x0000000000000000 0x0 build/module_b.o ... .text 0x0000000000000000 0x34 *(.text) .text 0x0000000000000000 0x34 build/module_a.o 0x0000000000000000 public_function ...
ご覧のとおり、
build/module_b.o
.text
セクションは完全に削除されています。これは、役に立たない関数が含まれている
build/module_b.o
です! 同時に、リンカは最初のモジュールから未使用の定数をスローしました。
実際、簡単な実験で簡単にわかるように、この最適化は完全ではありません
module_c.cpp
参照してください
void function_b(); extern "C" int public_function() { function_b(); }
モジュール
a
をモジュール
c
置き換え、リンカーがセクションを削除できるかどうかを確認します。
% rake 'show:map[b|c]' SCRIPT=layout2.ld GC=1 arm-none-eabi-gcc -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -mthumb -O2 -mcpu=cortex-m0 -c module_c.cpp -o build/module_c.o arm-none-eabi-ld --gc-sections -T layout2.ld -M -o build/out.elf build/module_b.o build/module_c.o Discarded input sections .data 0x0000000000000000 0x0 build/module_b.o .data 0x0000000000000000 0x0 build/module_c.o .bss 0x0000000000000000 0x0 build/module_c.o ... .text 0x0000000000000000 0x24 *(.text) .text 0x0000000000000000 0x1c build/module_b.o 0x0000000000000000 function_b() 0x0000000000000014 function_c() 0x0000000000000018 function_d() .text 0x000000000000001c 0x8 build/module_c.o 0x000000000000001c public_function
セクションの一部を空にすることは可能ですが(空のセクションも)、関数
function_c()
および
function_d()
貴重なバイトが失われ、必要な
function_b()
と同じセクションになりました。 コンパイラフラグが助けになり、関数とデータを異なるセクションに
-ffunction-sections
ます:
-ffunction-sections
と
-fdata-sections
:
% rake clean && rake 'show:symbols:all[b]' SPLIT_SECTIONS=1 arm-none-eabi-gcc -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -ffunction-sections -fdata-sections -mthumb -O2 -mcpu=cortex-m0 -c module_b.cpp -o build/module_b.o arm-none-eabi-objdump build/module_b.o -t build/module_b.o: file format elf32-littlearm SYMBOL TABLE: 00000000 l df *ABS* 00000000 module_b.cpp 00000000 ld .text 00000000 .text 00000000 ld .data 00000000 .data 00000000 ld .bss 00000000 .bss 00000000 ld .text._Z10function_bv 00000000 .text._Z10function_bv 00000000 ld .text._Z10function_cv 00000000 .text._Z10function_cv 00000000 ld .text._Z10function_dv 00000000 .text._Z10function_dv 00000000 ld .bss.external_counter 00000000 .bss.external_counter 00000000 ld .comment 00000000 .comment 00000000 ld .ARM.attributes 00000000 .ARM.attributes 00000000 g F .text._Z10function_bv 00000014 _Z10function_bv 00000000 *UND* 00000000 public_function 00000000 g F .text._Z10function_cv 00000002 _Z10function_cv 00000000 g F .text._Z10function_dv 00000002 _Z10function_dv 00000000 g O .bss.external_counter 00000004 external_counter
各関数とオブジェクトは独立したセクションに配置されたので、リンカーはそれらを削除できます。
% rake clean && rake 'show:map[b|c]' SCRIPT=layout2.ld GC=1 SPLIT_SECTIONS=1 arm-none-eabi-gcc -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -ffunction-sections -fdata-sections -mthumb -O2 -mcpu=cortex-m0 -c module_b.cpp -o build/module_b.o arm-none-eabi-gcc -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -ffunction-sections -fdata-sections -mthumb -O2 -mcpu=cortex-m0 -c module_c.cpp -o build/module_c.o arm-none-eabi-ld --gc-sections -T layout2.ld -M -o build/out.elf build/module_b.o build/module_c.o Discarded input sections .text 0x0000000000000000 0x0 build/module_b.o .data 0x0000000000000000 0x0 build/module_b.o .bss 0x0000000000000000 0x0 build/module_b.o .text._Z10function_cv 0x0000000000000000 0x4 build/module_b.o .text._Z10function_dv 0x0000000000000000 0x4 build/module_b.o .text 0x0000000000000000 0x0 build/module_c.o .data 0x0000000000000000 0x0 build/module_c.o .bss 0x0000000000000000 0x0 build/module_c.o ...
結論の代わりに
繰り返しになりますが、記事の量は増え続けており、今では最初の部分の2倍になっています。 残念ながら、レイアウトは複雑なトピックであり、「エントリ」を習得することは困難です。 1週間以内に、リンカーを引き続き調査し、組み込みアプリケーション用の本格的なビルドスクリプトを作成します。
PSいつものように、テキストを校正してくれたpfactumに感謝します。