私が正しく理解していれば、ELFファイルの.bss
セクションを使用して、ゼロで初期化された変数にスペースを割り当てます。私たちのツールチェーンはELFファイルを生成するため、私の質問:.bss
セクションには実際にこれらのすべてのゼロを含める必要がありますか?スペースを無駄に浪費しているようです。たとえば、グローバルに10メガバイトの配列を割り当てると、ELFファイルで10メガバイトのゼロが発生します。ここで何が間違っていると思いますか?
ELFを使用してからしばらく経ちました。しかし、私はまだこのことを覚えていると思います。いいえ、これらのゼロは物理的に含まれていません。 ELFファイルプログラムヘッダーを調べると、各ヘッダーに2つの数値があることがわかります。1つはファイル内のサイズです。もう1つは、仮想メモリに割り当てられたときのセクションのサイズ(readelf -l ./a.out
)です。
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
INTERP 0x000114 0x08048114 0x08048114 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x00454 0x00454 R E 0x1000
LOAD 0x000454 0x08049454 0x08049454 0x00104 0x61bac RW 0x1000
DYNAMIC 0x000468 0x08049468 0x08049468 0x000d0 0x000d0 RW 0x4
NOTE 0x000128 0x08048128 0x08048128 0x00020 0x00020 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
タイプLOAD
のヘッダーは、ファイルが実行のためにロードされるときに仮想メモリにコピーされるヘッダーです。他のヘッダーには、必要な共有ライブラリなどの他の情報が含まれています。ご覧のとおり、FileSize
とMemSiz
は、bss
セクションを含むヘッダー(2番目のLOAD
1つ)で大きく異なります。
0x00104 (file-size) 0x61bac (mem-size)
このサンプルコードの場合:
int a[100000];
int main() { }
ELF仕様では、mem-sizeがfile-sizeよりも大きいセグメントの部分は、仮想メモリのゼロで埋められていると述べています。 2番目のLOAD
ヘッダーのセグメントからセクションへのマッピングは次のとおりです。
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
したがって、他にもいくつかのセクションがあります。 C++コンストラクタ/デストラクタ用。 Javaについても同様です。次に、.dynamic
セクションのコピーと、動的リンクに役立つその他のものが含まれます(これは、必要な共有ライブラリを含む場所だと思います)。その後、初期化されたグローバルとローカル静的変数を含む.data
セクション。最後に、.bss
セクションが表示されます。ファイルサイズではカバーされないため、ロード時にゼロで埋められます。
ところで、-M
リンカーオプションを使用すると、特定のシンボルがどの出力セクションに配置されるかを確認できます。 gccの場合、-Wl,-M
を使用して、オプションをリンカーに渡します。上記の例は、a
が.bss
内に割り当てられていることを示しています。これは、初期化されていないオブジェクトが本当に.bss
になり、他の場所にないことを確認するのに役立ちます。
.bss 0x08049560 0x61aa0
[many input .o files...]
*(COMMON)
*fill* 0x08049568 0x18 00
COMMON 0x08049580 0x61a80 /tmp/cc2GT6nS.o
0x08049580 a
0x080ab000 . = ALIGN ((. != 0x0)?0x4:0x1)
0x080ab000 . = ALIGN (0x4)
0x080ab000 . = ALIGN (0x4)
0x080ab000 _end = .
GCCは、古いコンパイラとの互換性のために、デフォルトで初期化されていないグローバルをCOMMONセクションに保持します。これにより、複数の定義エラーなしでプログラム内で2回グローバルを定義できます。 -fno-common
を使用して、GCCがオブジェクトファイルの.bssセクションを使用するようにします(最終的にリンクされた実行可能ファイルには違いがありません。 リンカースクリプトld -verbose
で表示します)。しかし、それはあなたを怖がらせるべきではありません。それは単なる内部の詳細です。 gccのマンページを参照してください。
ELFファイルの.bss
セクションは、プログラムで初期化されていないである静的データに使用されますが、実行時にゼロに設定されることが保証されています。これが違いを説明する小さな例です。
int main() {
static int bss_test1[100];
static int bss_test2[100] = {0};
return 0;
}
この場合、bss_test1
は初期化されていないため、.bss
に配置されます。ただし、bss_test2
は、一連のゼロとともに.data
セグメントに配置されます。ランタイムローダーは基本的に.bss
用に予約されたスペースの量を割り当て、ユーザーランドコードの実行が始まる前にそれをゼロにします。
objdump
、nm
、または同様のユーティリティを使用して、違いを確認できます。
moozletoots$ objdump -t a.out | grep bss_test
08049780 l O .bss 00000190 bss_test1.3
080494c0 l O .data 00000190 bss_test2.4
これは通常、組み込み開発者が最初に遭遇するサプライズの1つです... staticを明示的にゼロに初期化しないでください。ランタイムローダーは(通常)それを処理します。明示的に何かを初期化するとすぐに、実行可能イメージにデータを含めるようにコンパイラー/リンカーに指示します。
.bss
セクションは、実行可能ファイルに格納されていません。最も一般的なセクション(.text
、.data
、.bss
)のうち、.text
(実際のコード)と.data
(初期化されたデータ)のみがELFに存在しますファイル。
つまり、.bssは物理的にファイルに存在せず、ダイナミックローダーが.bssセクションをアプリケーションプログラムに割り当てるために、そのサイズに関する情報のみが存在します。経験則として、LOADのみです。TLSセグメントはアプリケーションプログラムのメモリを取得し、残りはダイナミックローダーに使用されます。
静的実行可能ファイルについては、bssセクションにも実行可能ファイルのスペースが与えられます
ローダーがない組み込みアプリケーションは、これが一般的です。
スマン