exeGCC から exeClang への移行ガイド

もともと SOLID-OS と exeGCC は Clang コンパイラをサポートしているので、ソースコードの修正は不要です。Clang コンパイラのバージョンが変わると警告が厳しくなったり、場合によってはエラーになることはありますが、たいていはコンパイラが出力するメッセージに従って容易に修正が可能です。

問題になりやすいのはリンカスクリプトです。

リンカスクリプトの移行ガイド

基本的には exeGCC の GNU ld リンカと exeClang の LLVM lld リンカは高い互換性があるので、リンカスクリプトの修正は不要です。

ただし、 MEMORY コマンドを使用している場合のみ、注意が必要です。

lld では、 リンカスクリプト内で MEMORY コマンドを使用している場合、アウトプットセクション(以後「アウトプット」を省略)の定義の外側でロケーションカウンタ( . )を変更しても、セクション定義の内側でロケーションカウンタを参照した時、外側での変更は反映されません。セクションは MEMORY コマンド内で定義された、いずれかのメモリーリージョン(以後「メモリー」を省略)にアサインされていて、それぞれのリージョンが個別のロケーションカウンタを保持しているからです。

つまり、ロケーションカウンタ( . )の参照や変更は、原則としてアウトプットセクションの定義の内側でのみ行うように修正してください、という結論になります。

しかし、この説明だけではなかなか理解が難しいと思うので、以下に SOLID-OS で実際に問題になった例と、正しい(期待通りに動作する)書き方を示します。

注釈

GNU ld のマニュアルMEMORY コマンドに関する記述は簡素で、付属のリンカスクリプトも MEMORY コマンドを使用していないなど、公式の情報源は乏しいです。そのため本マニュアルの解説は GNU ld および LLVM lld の公式の見解ではなく、あくまでも本ドキュメントの執筆者がマニュアルから読み解いた仕様と実際の動作に基づいたものにすぎない、ということに注意してください。

例 1

SOLID-OS のリンカスクリプトには、以下のようにロケーションカウンタをセクションの外側で変更している記述が至る所にありましたが、lld では期待通りに動作しませんでした。

注釈

MEMORY コマンドを使用していない場合は問題なく動作します。

MEMORY {
    solid_ram (RWX) : ORIGIN = _smm_SOLID_PhysicalAddress, LENGTH = _smm_SOLID_Size
}

(省略)

SECTIONS {

(省略)

    . = ALIGN(8); /* セクション定義の外側でロケーションカウンタを変更しても */

    .data : {

        /* 定義の内側で . を参照した時、8 バイトアラインされていません */

    } > solid_ram

GNU ld のマニュアルには明記されていませんが、lld のこの挙動は、 MEMORY コマンドの意味を考えると妥当と思われます。

MEMORY コマンドでリージョンを定義した場合、ロケーションカウンタは 1 つではなく、 定義されたリージョンの数だけ存在すると考えられるからです。

GNU ld のマニュアルにはそのような用語は記載されていませんが、ここで便宜上、「グローバルな」ロケーションカウンタの他に、リージョンの数だけ「リージョンローカルな」ロケーションカウンタが存在すると考えてみると、アウトプットセクションの外側での「グローバルな」(つまり、どのリージョンにも所属していない、どのリージョンにも影響を与えない)ロケーションカウンタの変更が、リージョンの(ローカルな)ロケーションカウンタに影響を与えないのは、妥当で正しい挙動に思われます。

(そもそも、 MEMORY コマンドを使用した場合、グローバルなロケーションカウンタは使用するべきではないのだろうと思われます。実際、インターネットを検索してみると、 MEMORY コマンドを使用しているリンカスクリプトで、セクションの外側でロケーションカウンタを参照や変更している例は見つけられませんでした。)

セクションの先頭アドレスを 8 バイトアラインしたい場合は、以下のように記述してください。

.data ALIGN(8) : {

注釈

弊社の SOLID 開発メンバーから、「LLD のマニュアルを見ると ALIGN() はコロン( : )の右側に定義するのが正しいのではないか?」という質問がありました。

https://lld.llvm.org/ELF/linker_script.html#output-section-description

section [address] [(type)] : [AT(lma)] [ALIGN(section_align)] [SUBALIGN](subsection_align)] {
  output-section-command
  ...
} [>region] [AT>lma_region] [:phdr ...] [=fillexp] [,]

これは非常に紛らわしいのですが、このコロンの左側の ALIGN(8) は、 構文ではなく GNU LD および LLD の組み込み関数です。

https://sourceware.org/binutils/docs/ld/Builtin-Functions.html

ALIGN(align) 関数は、現在のロケーションカウンタを align バイトアラインした値を返します。その値をセクションの開始アドレス [addresss] に指定しています。

コロンの右側に ALIGN() を記述した場合は ALIGN(section_align) 構文扱いとなり、アウトプットセクションがインプットセクション(関数や変数のシンボル)を扱う際のアライメントを指定したことになり、アウトプットセクションの開始アドレス(暗黙的に現在のロケーションカウンタが使われます)は変わりません。

例 2

これは逆に、期待通り動かないように思われるのに、動いてしまう例です。

SECTIONS {

    /* セクション定義の外側で . を変更し、外側で参照しているので問題なし */

    . = _smm_SOLID_PhysicalAddress;

    _ram_start = .;
    _solid_mon_start = .;

    .text ALIGN(8) : {
        (省略)
    } > solid_ram

    (省略)

    .stacks ALIGN(16) (NOLOAD) : {
        (省略)
    } > solid_ram

    /* 外側で . の変更は一切行われていないにもかかわらず */

    _solid_mon_end = .; /* 正しく .stacks セクションの終了アドレスを取得できる */

最初の _solid_mon_start などは、セクションの外側でグローバルな . を変更して、外側で参照しているので問題ありません。しかし最後の _solid_mon_end は、これまでの議論からすると、変更されていないはずのグローバルなロケーションカウンタを参照しているので、正しく動かないはずです。しかし実際には期待通り動いてしまいます。

ドキュメント化されていない挙動と思われますが、どうもセクション終了直後の . は、セクションの終了アドレスを指しているように思われます。

本来は

.stacks ALIGN(16) (NOLOAD) : {
    (省略)
    _solid_mon_end = .;
} > solid_ram

のように書くべきですが、もし .stack セクションの位置が変わったり、後ろにセクションを追加した場合に正しく動かなくなる可能性があります。また、意味的にも .stacks セクションに所属しているわけではない、何の関係も無いシンボルがセクション定義内に存在するのは、少々気持ち悪さがあるかもしれません。

様々な理由で SOLID-OS では実際このような書き方をしている所が多々ありますが、本来はあまり良くないと理解しておくことは重要です。

また、この lld の挙動は LLVM 19 時点のものですが、仕様としてドキュメント化されているわけではないので、今後の互換性は保証されません。

例 3

以下の例では、RAM の先頭から 64KB 離れたアドレスに .solid_cs セクションが配置されることを期待しています。

しかし、 MEMORY コマンドを使用していて、 .solid_cs セクションは solid_ram に配置するように指示されています。この時、これまでの例と同様に、セクションの外側でのロケーションカウンタの変更は無視されるので、期待通りに動作しません。

MEMORY {
    solid_ram (RWX) : ORIGIN = _smm_SOLID_PhysicalAddress, LENGTH = _smm_SOLID_Size
}

(省略)

SECTIONS {

    (省略)

    . = _smm_SOLID_PhysicalAddress + 64K;

    .solid_cs ALIGN(4K) : {
        (省略)
    } > solid_ram

正しい(期待通りに動く)書き方はいろいろ考えられますが、今回は RAM の先頭の 64KB はブートローダーが使用するという仕様になっているのて、 .solid_cs は必ず _smm_SOLID_PhysicalAddress + 64K から開始しなければいけませんでした。

そこで、以下のように直接セクションの開始アドレスを記述しました。(4KB アラインなのは自明なので省略されています。)

.solid_cs (_smm_SOLID_PhysicalAddress + 64K) : {
    (省略)
} > solid_ram

本来は、このようにアドレスを直書きしてしまうと、自動でセクションをリージョンに配置するという MEMORY コマンドの利点が失われるので、あまり良い書き方ではないと思われます。

まとめ

MEMORY コマンドを使う場合、ロケーションカウンタ( . )の参照や変更は、アウトプットセクションの定義の内側でのみ行うようにしましょう。