最小のARM:コンパイルとリンカーの微妙さ



ARMのゼロからの開発に関する一連の記事を続けて、今日はGNU ldのリンカースクリプトの記述について説明します。 このトピックは、組み込みシステムで作業する人だけでなく、実行可能ファイルの構造をよりよく理解したい人にも役立ちます。 例はarm-none-eabiツールチェーンに何らかの形で基づいていますが、レイアウトの本質は、たとえばVisual Studioリンカーと同じです。



前の記事:





記事のコード例: https : //github.com/farcaller/arm-demos







ソースファイルをコンパイルすると、出力でオブジェクトファイルを取得します。通常、オブジェクトファイルには、データを含むいくつかのセクションが含まれています。 最も一般的な4つのセクションは次のとおりです。





このサイクルの一部として作業するバイナリファイルでは、多くの場合、さらに2つのセクションがあります。







セクションに加えて、オブジェクトファイルには、シンボルテーブルという別の重要なエンティティがあります。 これは一種のハッシュです:名前-アドレス(および追加の属性)。 たとえば、シンボルテーブルでは、エクスポートされたすべての関数とそのアドレス(.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番目の部分はセクション構成です。 一般に、これは特定のソースセクションの1つのプロトバフから別のプロトバフに出力セクションにコピーすること意味します。



ソースセクションは_(_)



形式で指定され、 *



文字は標準的な方法で動作するため、エントリ*(.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に感謝します。



クリエイテイブコモンルスライセンス この作品は、Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unportedの下ライセンスされています。 例のプログラムテキストは、 ライセンスライセンスの下で使用できます(ファイルヘッダーで明示的に指定されている場合を除く)。 この作品は、教育目的のみのために書かれており、著者の現在または以前の雇用主とは一切関係ありません。



All Articles