Open Design Computer Project

オリジナルCPUから作る本格的自作コンピューター

ユーザ用ツール

サイト用ツール


software:porting_gcc

新アーキテクチャに GCC を移植

gcc を新たな独自命令セットのプロセッサアーキテクチャ向けに対応・移植させるためのドキュメントです。

mist32 アーキテクチャ (RISC, 32bit, 固定長) 向けに gcc を移植した一例を書き留めておきます。

まだまだ全て書ききれていませんが、今後新たなアーキテクチャが開発された時、コンパイラの移植の際の参考になればと思います。

前提

まず、GNU Binutils がすでにそのターゲットに移植されていることが前提となります。

Binutils の移植については以下をご覧ください。

ドキュメントは、以下の GCC Internals が参考にできる唯一と言っても良い資料です。

といっても、全部読んでいられないですし、これだけでは移植はほぼ不可能です。

GCC Internals を読みならがら、他のアーキテクチャのコードを読んで、参考にして移植していきましょう。(i386 などはぐちゃぐちゃなので、moxie, rx, m32r などが読みやすいです)

あと、GCC のコンパイル方法についてある程度前提知識を持ってると楽です。RTL とか。

mist32 の移植例

以下のリポジトリで公開しています。参考にしてください。バグがいくつかあるので、まだまだ…

RTL Template

はじめは、RTL のテンプレートファイルから書いていくのが仕組みをつかめて良いと思います。

GCC はコンパイル時に C のコードを RTL に変換してからアセンブラを出力していきます。

この中間の RTL をアセンブラに出力部分がターゲット依存となるので、アーキテクチャが変わった場合に書き換えなければいけません。

RTL テンプレートのサンプルは以下のようになります。

(define_insn "addsi3"
  [(set (match_operand:SI 0 "register_operand"          "=r,r")
        (plus:SI (match_operand:SI 1 "register_operand" "%0,0")
                 (match_operand:SI 2 "nonmemory_operand" "r,I")))]
  ""
  "@
   add\t%0, %2
   add\t%0, %2"
)

この例は足し算命令を定義しています。

RTL テンプレートの定義の仕方はにはいくつかあるのですが、標準で生成されるパターンに名前が付けられています。

例えば、この例では addsi3 というパターンの変換を記述しています。(add, SI 32bit, 3 operands)

なんとなく感覚がつかめたでしょうか?

RTL テンプレートの書き方は少し癖があるので、Canonicalization についての以下の資料を見ながら書くことをおすすめします。

gcc/config/mist32/mist32.md

実際の RTL テンプレートは、上記の md ファイルに記述されています。

define_insn, define_expand, define_split などを駆使して記述しましょう。

Standard Names に記述されているパターンすべてを記述する必要はありません。

define_insn

define_insn は Standard Names で定義された操作を RTL へ展開するため、RTL を実際のアセンブラ命令に変換するため、これら2つのために使われます。

無名または * から始まったり、Standard Names に定義されていない名前の define_insn は、定義された RTL からアセンブラ命令に変換するためだけに使われます。

基本的に、アセンブラ1命令で記述できるような RTL パターンを実際の命令に変換するために使います。つまり、アセンブラ命令の動作を RTL パターンで記述すれば良いという事でもあります。

define_expand

define_expand は Standard Names から RTL へ展開するために使います。

例えば、Standard Names に記述されている動作が、アセンブラでは1命令で定義できないような場合、define_expand でその動作を RTL へ変換して、define_insn に当てはめていく事になります。

define_expand は、実際のアセンブラ命令に変換するためには使われませんが、C のコードを書くことができるので、その中で emit_insn をしたり、いろいろな動作をさせることもできます。

define_split

define_split は RTL を分割するために使います。

例えば、32bit な constant を register に入れるためには、普通の RISC マシンでは2命令必要です。そのような場合に、define_split を活用するとよいでしょう。

gcc/config/mist32/predicates.md

predicates は register_operand とか immediate_operand とか、そういうものです。

まずは、ここで指定された条件にマッチする RTL テンプレートが選択されることになります。

予め、用意されている predicates もありますが、独自に predicates を定義する際にこのファイルに記述するのが普通です。

このファイルを mist32.md ファイルから include することで使用します。

gcc/config/mist32/constraints.md

constraints は、オペランドの制約を記述します。

predicates と似ているのですが、constraints は1つのテンプレートで複数の constraints を記述することができ、どれにマッチしたかで出力する命令を振り分けることができます。

例えば、上記の addsi3 の命令では、2 番目のオペランドが r, I と constraints が記述されています。

@ で始まる複数行で記述されているアセンブラの出力フォーマットのどれを出力するかを、この constraints で振り分けます。

上記の例では、命令が2行とも同じ物になっていますが、r (register) の場合には1行目、I (11bit immediate) の場合には2行目の命令が出力されます。

constraints も predicates と同様に、予め定義されているものがあります。

独自に新たに記述する場合、特に即値などはアーキテクチャに寄って幅や種類が変わるのでそれらを記述するのに constraints.md 使い、include します。

Target Description Macros and Functions

gcc/config/mist32/mist32.h

次に、ターゲットのヘッダファイルに手を付けてみるのが良いと思います。

とにかく、他のアーキテクチャを読んで雰囲気を感じ取ってください。

色々読み比べてみるのが良いです。あと、比較的新しく移植されたもののほうが読みやすいです。

ビット数やエンディアン、レジスタの定義などを記述します。

gcc/config/mist32/mist32.c

最後に、コンパイルに必要な関数、フレームの計算や関数のはじめと終わりの処理、各種雑用関数などを定義します。

とにかく、コピペできるところはコピペしてみましょう。やってることがだんだん見えてきたら修正していきましょう。

function value

関数の戻り値関係。戻り値をどのレジスタで返すかなどを指定する。

最初のうちは、モードに関係なくレジスタ戻しが普通だと思うので、gen_rtx_REG で生成すれば良い。

  • TARGET_FUNCTION_VALUE mist32_function_value
  • TARGET_LIBCALL_VALUE mist32_libcall_value
  • TARGET_FUNCTION_VALUE_REGNO_P mist32_function_value_regno_p
static rtx
mist32_function_value (const_tree valtype,
                     const_tree fntype_or_decli ATTRIBUTE_UNUSED,
                     bool outgoing ATTRIBUTE_UNUSED)
{
  return gen_rtx_REG (TYPE_MODE (valtype), GP_RETURN);
}
 
static rtx
mist32_libcall_value (enum machine_mode mode,
                    const_rtx fun ATTRIBUTE_UNUSED)
{
  return gen_rtx_REG (mode, GP_RETURN);
}
 
static bool
mist32_function_value_regno_p (const unsigned int regno)
{
  return (regno == GP_RETURN);
}

オペランドを表示するためのもの。

  • TARGET_PRINT_OPERAND mist32_print_operand
  • TARGET_PRINT_OPERAND_ADDRESS mist32_print_operand_address

Trampoline

C の nested function に必要な Trampoline の生成に関する関数を生成する。 また、nested function の実体に static chain pointer を渡す処理も考えなくてはいけません。

参考情報

Trampoline については、以下のブログが参考になります。中の“アドレスX” が、static chain pointer です。

基本的には、TARGET_TRAMPOLINE_INIT を定義して trampoline を動的に生成または、テンプレートをコピーすることになります。 static chain pointer をレジスタで渡せる場合には、STATIC_CHAIN_REGNUM も定義します。

テンプレートを使う場合には TARGET_ASM_TRAMPOLINE_TEMPLATE を定義するとよい。 assemble_trampoline_template() が使えます。 emit_block_move() などで、Trampoline テンプレートをスタック上にコピーして、関数のアドレスと chain_value だけど動的にテンプレートに上書きして、Trampoline を生成します。

動的にポインタを入れ込んだ命令を生成するほうがコストにならないこともあるので、そうであればテンプレートは定義しません。 その代わり、trampoline_init で、機械語を直接 GEN_INT して積みます。 32bit の即値が扱える場合には、こちらのほうがやりやすいと思います。 どちらにしても、RISC 的なマシンだとちょっとやりづらいです。

x86 は、動的にスタックフレームから直接レジスタへ move するコードを吐きますが、どうやるかわかりませんw (相当大変なはずです、chain_value は rtx で渡ってくるので、これを解析する必要があると思います?)

セットアップルーチン

-nostdlib でコンパイル

最初は、普通にコンパイルしようと思っても、Standard Library (libc, newlib, etc…) がないので当たり前ですが、いつも通りにはいきません。Standard Libarary など、余計なものをリンクしないでコンパイルするには -nostdlib をオプションとしてつけます。とりあえず初めはこれでテストしましょう。

但し、(当たり前ですが)スタックなどは準備されていないので sp をセットする必要がありますし、エントリーポイントも main ではなく _start になります。つまり、_start を書いたアセンブラでスタックなどをセットして main を用意すると良いと思います。

ずっとこれを使うわけではなく、newlib (またはその他の Standard Library) を移植できるまでのつなぎなので、適当でよいでしょう。コンパイル結果のテストと、簡単なプログラムならこれでOKです。

crti.o と crtn.o

標準ライブラリを移植し始めると、crt (C runtime) を準備しなければいけません。それに必要なものが、crti.o と crtn.o です。

crt0.o はよく見たことがある人もいるかもしれません、main を呼び出しているのは基本的にこいつなので。ただ、crt0 は newlib 側のものであって、gcc が crtbegin, crtend というものを用意しています。

crti と crtn は crtbegin, crtend の .init と .fini セクションに使われます。それらのセクションのヘッダーが crti で、フッターが crtn です。つまり、gcc が用意する .init, .fini セクションが、crti と crtn で定義した .init, .fini セクションで挟まれることになります。

こちらの資料が詳しいです。http://wiki.osdev.org/Creating_a_C_Library#crtbegin.o.2C_crtend.o.2C_crti.o.2C_and_crtn.o

gcc/config/mist32/ の下に crti.asm と crtn.asm を作成することになります。大体の場合、prologue, epilogue 的なことを書けば良いです。(スタック・ベースポインタの退避とか、リンクレジスタの退避とか)

gcc のデバッグ

主なコンパイルの通らない原因

  • [target]-elf/libgcc の下のテストコード
    • config.log を見ると、何で落ちてるかがわかる
  • _lshrdi3 などのソフトウェアエミュレーション系関数
  • その他いろいろ…

RTL を出力したい場合

gcc, xgcc, cc1 で -fdump-rtl-all をつけると、内部の RTL の展開の模様を全部ダンプしてくれる。また、-fdump-tree-all をつけると、コンパイラのフロントエンドが解析したツリーもダンプしてくれる。これを辿りながら、コンパイラ内部でどのような命令が発行されているかを解析する。

デバッグオプションについて詳しくはこちらを参照すると良い。 http://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html

debug_rtx()

ある関数で Segumentation Fault で落ちたり、実際に処理される RTL を見ながらデバッグしたいこともあるだろう。そう言う時は、debug_rtx(rtx) を使うと非常に楽になる。

基本的に .md の中であればどこでも使える。また、gcc のソースに直接手を入れて debug_rtx() をつけまくると、printf デバッグのようなことができる。

他にも gcc/rtl.h あたりを見るといくつか便利な関数が存在する。とてもお便利なので、これらを使いこなせるようになると良い。

gcc を gdb でデバッグする

gcc の porting をしていると、何が原因でどこで落ちているのか、さっぱりわからないことがよくある。エラーメッセージも不親切である。なので、考えるより地道に gdb にかけた方が早いこともある。

/home/hirotaka/src/build/gcc-mist32/./gcc/xgcc -B/home/hirotaka/src/build/gcc-mist32/./gcc/ -nostdinc -B/home/hirotaka/src/build/gcc-mist32/mist32-elf/newlib/ -isystem /home/hirotaka/src/build/gcc-mist32/mist32-elf/newlib/targ-include -isystem /home/hirotaka/src/gcc-mist32/newlib/libc/include -B/home/hirotaka/src/build/gcc-mist32/mist32-elf/libgloss/mist32 -L/home/hirotaka/src/build/gcc-mist32/mist32-elf/libgloss/libnosys -L/home/hirotaka/src/gcc-mist32/libgloss/mist32 -B/home/hirotaka/usr/mist32-elf/bin/ -B/home/hirotaka/usr/mist32-elf/lib/ -isystem /home/hirotaka/usr/mist32-elf/include -isystem /home/hirotaka/usr/mist32-elf/sys-include    -g -O2 -O2  -g -O2 -DIN_GCC -DCROSS_DIRECTORY_STRUCTURE  -W -Wall -Wwrite-strings -Wcast-qual -Wstrict-prototypes -Wmissing-prototypes -Wold-style-definition  -isystem ./include   -g  -DIN_LIBGCC2 -D__GCC_FLOAT_NOT_NEEDED -fno-stack-protector -Dinhibit_libc  -I. -I. -I../.././gcc -I../../../../gcc-mist32/libgcc -I../../../../gcc-mist32/libgcc/. -I../../../../gcc-mist32/libgcc/../gcc -I../../../../gcc-mist32/libgcc/../include  -DHAVE_CC_TLS -DUSE_EMUTLS -o _lshrdi3.o -MT _lshrdi3.o -MD -MP -MF _lshrdi3.dep -DL_lshrdi3 -c ../../../../gcc-mist32/libgcc/../gcc/libgcc2.c \
          
../../../../gcc-mist32/libgcc/../gcc/libgcc2.c: 関数 ‘__lshrdi3’ 内:
../../../../gcc-mist32/libgcc/../gcc/libgcc2.c:427:1: コンパイラ内部エラー: gen_reg_rtx 内、位置 emit-rtl.c:862
完全なバグ報告を送って下さい。
適切ならば前処理後のソースをつけてください。
<http://gcc.gnu.org/bugs.html> を見れば方法が書いてあります。
make: *** [_lshrdi3.o] エラー 1

上記のような xgcc を叩くコマンドで、コンパイル失敗になっている場合、-v をつけると、cc1 の内部での実行コマンドラインが出力される。

/home/hirotaka/src/build/gcc-mist32/./gcc/specs から spec を読み込んでいます
COLLECT_GCC=/home/hirotaka/src/build/gcc-mist32/./gcc/xgcc
COLLECT_LTO_WRAPPER=/home/hirotaka/src/build/gcc-mist32/./gcc/lto-wrapper
ターゲット: mist32-elf
configure 設定: ../../gcc-mist32/configure --enable-languages=c --prefix=/home/hirotaka/usr --with-gnu-as --with-gnu-ld --with-newlib --disable-shared --with-system-zlib --without-included-gettext --disable-libssp --enable-nls --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=mist32-elf
スレッドモデル: single
gcc バージョン 4.6.4 (GCC) 
COLLECT_GCC_OPTIONS='-v' '-B' '/home/hirotaka/src/build/gcc-mist32/./gcc/' '-nostdinc' '-B' '/home/hirotaka/src/build/gcc-mist32/mist32-elf/newlib/' '-isystem' '/home/hirotaka/src/build/gcc-mist32/mist32-elf/newlib/targ-include' '-isystem' '/home/hirotaka/src/gcc-mist32/newlib/libc/include' '-B' '/home/hirotaka/src/build/gcc-mist32/mist32-elf/libgloss/mist32' '-L/home/hirotaka/src/build/gcc-mist32/mist32-elf/libgloss/libnosys' '-L/home/hirotaka/src/gcc-mist32/libgloss/mist32' '-B' '/home/hirotaka/usr/mist32-elf/bin/' '-B' '/home/hirotaka/usr/mist32-elf/lib/' '-isystem' '/home/hirotaka/usr/mist32-elf/include' '-isystem' '/home/hirotaka/usr/mist32-elf/sys-include' '-g' '-O2' '-O2' '-g' '-O2' '-D' 'IN_GCC' '-D' 'CROSS_DIRECTORY_STRUCTURE' '-Wextra' '-Wall' '-Wwrite-strings' '-Wcast-qual' '-Wstrict-prototypes' '-Wmissing-prototypes' '-Wold-style-definition' '-isystem' './include' '-g' '-D' 'IN_LIBGCC2' '-D' '__GCC_FLOAT_NOT_NEEDED' '-fno-stack-protector' '-D' 'inhibit_libc' '-I' '.' '-I' '.' '-I' '../.././gcc' '-I' '../../../../gcc-mist32/libgcc' '-I' '../../../../gcc-mist32/libgcc/.' '-I' '../../../../gcc-mist32/libgcc/../gcc' '-I' '../../../../gcc-mist32/libgcc/../include' '-D' 'HAVE_CC_TLS' '-D' 'USE_EMUTLS' '-o' '_lshrdi3.o' '-MT' '_lshrdi3.o' '-MD' '-MP' '-MF' '_lshrdi3.dep' '-D' 'L_lshrdi3' '-c'
 /home/hirotaka/src/build/gcc-mist32/./gcc/cc1 -quiet -nostdinc -v -I . -I . -I ../.././gcc -I ../../../../gcc-mist32/libgcc -I ../../../../gcc-mist32/libgcc/. -I ../../../../gcc-mist32/libgcc/../gcc -I ../../../../gcc-mist32/libgcc/../include -iprefix /home/hirotaka/src/build/gcc-mist32/gcc/../lib/gcc/mist32-elf/4.6.4/ -isystem /home/hirotaka/src/build/gcc-mist32/./gcc/include -isystem /home/hirotaka/src/build/gcc-mist32/./gcc/include-fixed -MD _lshrdi3.d -MF _lshrdi3.dep -MP -MT _lshrdi3.o -D IN_GCC -D CROSS_DIRECTORY_STRUCTURE -D IN_LIBGCC2 -D __GCC_FLOAT_NOT_NEEDED -D inhibit_libc -D HAVE_CC_TLS -D USE_EMUTLS -D L_lshrdi3 -isystem /home/hirotaka/src/build/gcc-mist32/mist32-elf/newlib/targ-include -isystem /home/hirotaka/src/gcc-mist32/newlib/libc/include -isystem /home/hirotaka/usr/mist32-elf/include -isystem /home/hirotaka/usr/mist32-elf/sys-include -isystem ./include ../../../../gcc-mist32/libgcc/../gcc/libgcc2.c -quiet -dumpbase libgcc2.c -auxbase-strip _lshrdi3.o -g -g -g -O2 -O2 -O2 -Wextra -Wall -Wwrite-strings -Wcast-qual -Wstrict-prototypes -Wmissing-prototypes -Wold-style-definition -version -fno-stack-protector -o /tmp/ccjZjoCa.s
GNU C (GCC) version 4.6.4 (mist32-elf)
        compiled by GNU C version 4.7.2, GMP version 5.0.5, MPFR version 3.1.0-p10, MPC version 0.9
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
...

gdb でトレースしたい場合には、この cc1 のコマンドラインを利用する。

デフォルトで -O2 でコンパイルされているので、もし必要であれば configure の -O2 をすべて -O0 に書き換えれば良い。(普通は問題ない)

“コンパイラ内部エラー” で落ちた場合、通常 fancy_abort() という関数で落ちている。なので、これをブレークポイントに設定して、スタックトレースを表示させると原因究明がしやすい。

(gdb) bt
#0  trim_filename (name=name@entry=0x95c368 "../../../gcc-mist32/gcc/emit-rtl.c") at ../../../gcc-mist32/gcc/diagnostic.c:568
#1  0x0000000000542fe0 in fancy_abort (file=file@entry=0x95c368 "../../../gcc-mist32/gcc/emit-rtl.c", line=line@entry=862, function=function@entry=0x95cc12 "gen_reg_rtx") at ../../../gcc-mist32/gcc/diagnostic.c:893
#2  0x000000000057575e in gen_reg_rtx (mode=mode@entry=SImode) at ../../../gcc-mist32/gcc/emit-rtl.c:862
#3  0x0000000000581d4e in force_reg (x=0x7ffff5716678, mode=SImode) at ../../../gcc-mist32/gcc/explow.c:669
#4  force_reg (mode=mode@entry=SImode, x=0x7ffff5716678) at ../../../gcc-mist32/gcc/explow.c:650
#5  0x0000000000837831 in output_53 (operands=0xcd4f60, insn=<optimized out>) at ../../../gcc-mist32/gcc/config/mist32/mist32.md:789
#6  0x00000000005a4d67 in final_scan_insn (insn=0x7ffff553f050, file=file@entry=0xd29f00, optimize_p=optimize_p@entry=2, nopeepholes=nopeepholes@entry=0, seen=seen@entry=0x7fffffffe01c) at ../../../gcc-mist32/gcc/final.c:2614
#7  0x00000000005a5453 in final (first=0x7ffff5746b40, file=0xd29f00, optimize_p=2) at ../../../gcc-mist32/gcc/final.c:1723
#8  0x00000000005a5526 in rest_of_handle_final () at ../../../gcc-mist32/gcc/final.c:4270
#9  0x000000000065cb86 in execute_one_pass (pass=pass@entry=0xc72e80) at ../../../gcc-mist32/gcc/passes.c:1556
#10 0x000000000065ce55 in execute_pass_list (pass=0xc72e80) at ../../../gcc-mist32/gcc/passes.c:1611
#11 0x000000000065ce67 in execute_pass_list (pass=0xc737c0) at ../../../gcc-mist32/gcc/passes.c:1612
#12 0x000000000065ce67 in execute_pass_list (pass=0xc73820) at ../../../gcc-mist32/gcc/passes.c:1612
#13 0x00000000006ffac7 in tree_rest_of_compilation (fndecl=fndecl@entry=0x7ffff5739600) at ../../../gcc-mist32/gcc/tree-optimize.c:422
#14 0x0000000000808eb4 in cgraph_expand_function (node=0x7ffff574a420) at ../../../gcc-mist32/gcc/cgraphunit.c:1576
#15 0x000000000080ab5c in cgraph_expand_all_functions () at ../../../gcc-mist32/gcc/cgraphunit.c:1635
#16 cgraph_optimize () at ../../../gcc-mist32/gcc/cgraphunit.c:1899
#17 0x000000000080afba in cgraph_finalize_compilation_unit () at ../../../gcc-mist32/gcc/cgraphunit.c:1096
#18 0x000000000047dd2c in c_write_global_declarations () at ../../../gcc-mist32/gcc/c-decl.c:9878
#19 0x00000000006c3474 in compile_file () at ../../../gcc-mist32/gcc/toplev.c:591
#20 do_compile () at ../../../gcc-mist32/gcc/toplev.c:1907
#21 toplev_main (argc=81, argv=0x7fffffffe338) at ../../../gcc-mist32/gcc/toplev.c:1970
#22 0x00007ffff6028ead in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
#23 0x000000000046d2d5 in _start ()

#5 0x0000000000837831 in output_53 (operands=0xcd4f60, insn=<optimized out>) at ../../../gcc-mist32/gcc/config/mist32/mist32.md:789

と表示されている通り、上記の例では md ファイルのどの行で落ちているかまでわかる。

ここまで来れば、あとはすぐに解決できるが、もっとまともな方法がありそうだが…

gcc testsuite

gcc には testsuite がついてます。鬼畜コードで port したコードが正しく動くかチェックしてもらえます。

http://gcc.gnu.org/install/test.html

基本的には、$ make -k check や $ make check-gcc すればよいです。

gcc/testsuite/gcc/gcc.sum に Summary が出力されていますので、まずはこれを確認しましょう。gcc.log に、詳細なログが残っているので、再現して自分で確かめてみます。

gcc.sum

FAIL: gcc.c-torture/compile/20001226-1.c  -O0  (test for excess errors)
PASS: gcc.c-torture/compile/20001226-1.c  -O1  (test for excess errors)
FAIL: gcc.c-torture/compile/20001226-1.c  -O2  (test for excess errors)
FAIL: gcc.c-torture/compile/20001226-1.c  -O3 -fomit-frame-pointer  (test for excess errors)
FAIL: gcc.c-torture/compile/20001226-1.c  -O3 -g  (test for excess errors)

                === gcc Summary ===

# of expected passes            40863
# of unexpected failures        14408
# of unexpected successes       3
# of expected failures          124
# of unresolved testcases       13690
# of unsupported tests          1127
/home/hirotaka/src/build/gcc-mist32/gcc/xgcc  version 4.6.4 (GCC)

テストケースの結果にはそれぞれ、PASS や FAIL の他に、XFAIL や UNSUPPORTED などがありますが、このターゲットはこのテストケースに対応してない事などを示すために、テストケース中に以下のようなものを書くことができます。

/* { dg-xfail-if "jump beyond 64K not supported" { mist32-*-* } { "*" } { "" } } */                                    

これを書いておくと、意図的に落ちることを明示できます。(上記の例で fail した場合、XFAIL, PASS した場合 XPASS になります)

テストケース全部通すのは大変ですが、バグつぶしに非常に有効なので一度流してみることをおすすめします。

software/porting_gcc.txt · 最終更新: 2015/05/09 00:08 by hktechno