web-dev-qa-db-ja.com

gccの-ffunction-sectionおよび-fdata-sectionsオプションに対するクエリ

関数セクションとデータセクションのオプションについては、以下のGCCページで説明されています。

-ffunction-sections
-fdata-sections

ターゲットが任意のセクションをサポートしている場合、各関数またはデータ項目を出力ファイルの独自のセクションに配置します。関数の名前またはデータ項目の名前によって、出力ファイル内のセクションの名前が決まります。これらのオプションは、リンカが最適化を実行して命令空間での参照の局所性を向上できるシステムで使用します。 ELFオブジェクト形式とSPARC Solaris 2を実行するプロセッサーを使用するほとんどのシステムには、そのような最適化を備えたリンカーがあります。AIXは将来これらの最適化を行う可能性があります。

これらのオプションは、そうすることによる大きなメリットがある場合にのみ使用してください。 これらのオプションを指定すると、アセンブラーとリンカーはより大きなオブジェクトと実行可能ファイルを作成し、速度も低下します。このオプションを指定すると、すべてのシステムでgprofを使用できなくなり、このオプションと-gの両方を指定すると、デバッグに問題が発生する可能性があります。

これらのオプションは実行可能ファイルのサイズを小さくするのに役立つという印象を受けました。なぜこのページはより大きな実行可能ファイルを作成すると言っているのですか?何か不足していますか?

28
Jay

これらのコンパイラオプションを使用する場合、リンカーオプション-Wl,--gc-sectionsを追加して、未使用のコードをすべて削除できます。

24
leppie

興味深いことに、-fdata-sectionsを使用すると、関数のリテラルプールを作成できるため、関数自体が大きくなります。私はこれにARM=特に)気づきましたが、他の場所では本当である可能性があります。テストしていたバイナリは4分の1パーセントしか伸びませんでしたが、大きくなりました。変更された機能の理由は明らかでした。

オブジェクトファイル内のすべてのBSS(またはDATA)エントリが1つのセクションに割り当てられている場合、コンパイラはそのセクションのアドレスを関数リテラルプールに格納し、関数内のそのアドレスからの既知のオフセットでロードを生成して、データ。ただし、-fdata-sectionsを有効にすると、BSS(またはDATA)データの各部分が独自のセクションに配置されます。また、これらのセクションのどれが後でガベージコレクションされる可能性があるか、またはリンカがすべての順序を配置するかどうかがわからないためこれらのセクションを最終的な実行可能イメージに含めると、単一のアドレスからのオフセットを使用してデータをロードできなくなります。そのため、代わりに、使用済みデータごとにリテラルプールにエントリを割り当てる必要があります。リンカが最終的なイメージの内容と場所を特定したら、これらのリテラルプールエントリを実際のアドレスで修正できます。データ。

つまり、-Wl,--gc-sectionsを使用しても、実際の関数テキストが大きくなるため、結果の画像が大きくなる可能性があります。

以下に最小限の例を追加しました

以下のコードは、私が話している動作を確認するのに十分です。実際のコードで疑わしいvolatile宣言とグローバル変数の使用に惑わされないでください。ここでは、-fdata-sectionsが使用されたときに2つのデータセクションの作成を保証します。

static volatile int head;
static volatile int tail;

int queue_empty(void)
{
    return head == tail;
}

このテストに使用されたGCCのバージョンは次のとおりです。

gcc version 6.1.1 20160526 (Arch Repository)

まず、-fdata-sectionsを指定しないと、次のようになります。

> arm-none-eabi-gcc -march=armv6-m \
                    -mcpu=cortex-m0 \
                    -mthumb \
                    -Os \
                    -c \
                    -o test.o \
                    test.c

> arm-none-eabi-objdump -dr test.o

00000000 <queue_empty>:
 0: 4b03     ldr   r3, [pc, #12]   ; (10 <queue_empty+0x10>)
 2: 6818     ldr   r0, [r3, #0]
 4: 685b     ldr   r3, [r3, #4]
 6: 1ac0     subs  r0, r0, r3
 8: 4243     negs  r3, r0
 a: 4158     adcs  r0, r3
 c: 4770     bx    lr
 e: 46c0     nop                   ; (mov r8, r8)
10: 00000000 .Word 0x00000000
             10: R_ARM_ABS32 .bss

> arm-none-eabi-nm -S test.o

00000000 00000004 b head
00000000 00000014 T queue_empty
00000004 00000004 b tail

arm-none-eabi-nmから、queue_emptyが20バイト長(14 hex)であることがわかり、arm-none-eabi-objdump出力は、関数の最後に単一の再配置ワードがあることを示しています。これは、BSSセクションのアドレスです(初期化されていないデータのセクション)。関数の最初の命令は、その値(BSSのアドレス)をr3にロードします。次の2つの命令は、r3に関連してロードされ、それぞれ0バイトと4バイトずつオフセットされます。これらの2つの荷重は、ヘッドとテールの値の荷重です。これらのオフセットは、arm-none-eabi-nmからの出力の最初の列で確認できます。関数の最後のnopは、リテラルプールのアドレスをWordが揃えるためのものです。

次に、-fdata-sectionsを追加するとどうなるかを確認します。

arm-none-eabi-gcc -march=armv6-m \
                  -mcpu=cortex-m0 \
                  -mthumb \
                  -Os \
                  -fdata-sections \
                  -c \
                  -o test.o \
                  test.c

arm-none-eabi-objdump -dr test.o

00000000 <queue_empty>:
 0: 4b03     ldr   r3, [pc, #12]    ; (10 <queue_empty+0x10>)
 2: 6818     ldr   r0, [r3, #0]
 4: 4b03     ldr   r3, [pc, #12]    ; (14 <queue_empty+0x14>)
 6: 681b     ldr   r3, [r3, #0]
 8: 1ac0     subs  r0, r0, r3
 a: 4243     negs  r3, r0
 c: 4158     adcs  r0, r3
 e: 4770     bx    lr
    ...
             10: R_ARM_ABS32 .bss.head
             14: R_ARM_ABS32 .bss.tail

arm-none-eabi-nm -S test.o

00000000 00000004 b head
00000000 00000018 T queue_empty
00000000 00000004 b tail

すぐに、queue_emptyの長さが4バイト増えて24バイト(18 hex)になり、queue_emptyのリテラルプールで2つの再配置が行われるようになりました。これらの再配置は、グローバル変数ごとに1つずつ、作成された2つのBSSセクションのアドレスに対応しています。コンパイラはリンカが2つのセクションを配置することになる相対位置を知ることができないため、ここには2つのアドレスが必要です。queue_emptyの先頭にある命令を見ると、追加の負荷があることがわかります。コンパイラセクションのアドレスを取得してから、そのセクションの変数の値を取得するには、個別のロードペアを生成する必要があります。このバージョンのqueue_emptyの追加の命令は、関数の本体を長くするものではなく、以前はnopであったスポットを取得するだけですが、一般的にはそうではありません。

30
Anton Staaf

静的ライブラリで-ffunction-sectionsおよび-fdata-sectionsを使用できます。これにより、各関数とグローバルデータ変数が別々のセクションに配置されるため、静的ライブラリのサイズが大きくなります。

次に、この静的ライブラリとリンクしているプログラムで-Wl,--gc-sectionsを使用します。これにより、未使用のセクションが削除されます。

したがって、最終的なバイナリは、これらのフラグがない場合よりも小さくなります。

ただし、-Wl,--gc-sectionsは問題を引き起こす可能性があるので注意してください。

13
fwhacking

追加のステップを追加して.aアーカイブを構築すると、より良い結果が得られます。

  1. まず、gccとg ++が-ffunction-sections-fdata-sectionsフラグと共に使用されます
  2. 次に、すべての.oオブジェクトが.aとともにar rcs file.a *.oアーカイブに入れられます
  3. 最後に、リンカは-Wl,-gc-sections,-u,mainオプションで呼び出されます
  4. 全体として、最適化は-Osに設定されています。
4
Rei Vilo

しばらく前に試してみましたが、結果を見ると、サイズの増加は、配置が異なるオブジェクトの順序に起因しているようです。通常、リンカはオブジェクト間のパディングを小さくするためにオブジェクトを並べ替えますが、それはセクション内ではなく、セクション内でのみ機能するように見えます。そのため、各関数のデータセクション間に余分なパディングが発生し、全体的なスペースが増えることがよくあります。

-Wl、-gc-sectionsを指定した静的ライブラリの場合、未使用のセクションを削除すると、わずかな増加だけで十分ではなくなります。