85

私はしばらくの間Cと協力してきましたが、ごく最近、ASMを使い始めました。プログラムをコンパイルするとき:

int main(void)
  {
  int a = 0;
  a += 1;
  return 0;
  }

objdump逆アセンブリにはコードがありますが、retの後にnopsします。

...
08048394 <main>:
 8048394:       55                      push   %ebp
 8048395:       89 e5                   mov    %esp,%ebp
 8048397:       83 ec 10                sub    $0x10,%esp
 804839a:       c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%ebp)
 80483a1:       83 45 fc 01             addl   $0x1,-0x4(%ebp)
 80483a5:       b8 00 00 00 00          mov    $0x0,%eax
 80483aa:       c9                      leave  
 80483ab:       c3                      ret    
 80483ac:       90                      nop
 80483ad:       90                      nop
 80483ae:       90                      nop
 80483af:       90                      nop
...

私が学んだことから、nopsは何もしません、そしてretの後は実行さえされないでしょう。

私の質問は:なぜわざわざ?ELF(linux-x86)は、任意のサイズの.textセクション(+ main)で動作しませんでしたか?

ただ学ぼうとして、助けていただければ幸いです。

4

3 に答える 3

94

まず第一に、gccこれを常に行うとは限りません。パディングは、によって制御されます-falign-functions。これは、およびによって自動的にオンに-O2なり-O3ます。

-falign-functions
-falign-functions=n

関数の開始を、バイトnまでスキップして、次の2の累乗に揃えます。nたとえば、 -falign-functions=32関数を次の32バイト境界に-falign-functions=24整列しますが、23バイト以下をスキップしてこれを実行できる場合にのみ、次の32バイト境界に整列します。

-fno-align-functions-falign-functions=1は同等であり、関数が整列されないことを意味します。

一部のアセンブラは、nが2の累乗である場合にのみこのフラグをサポートします。その場合は切り上げます。

nが指定されていないかゼロの場合は、マシンに依存するデフォルトを使用します。

レベル-O2、-O3で有効になります。

これを行う理由は複数ある可能性がありますが、x86の主な理由はおそらく次のとおりです。

ほとんどのプロセッサは、整列された16バイトまたは32バイトのブロックで命令をフェッチします。コード内の16バイト境界の数を最小限に抑えるために、クリティカルループエントリとサブルーチンエントリを16だけ整列させると有利な場合があります。または、クリティカルループエントリまたはサブルーチンエントリの後の最初のいくつかの命令に16バイトの境界がないことを確認してください。

(Agner Fogによる「アセンブリ言語でのサブルーチンの最適化」から引用。)

編集:パディングを示す例を次に示します。

// align.c
int f(void) { return 0; }
int g(void) { return 0; }

デフォルト設定でgcc4.4.5を使用してコンパイルすると、次のようになります。

align.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <f>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   b8 00 00 00 00          mov    $0x0,%eax
   9:   c9                      leaveq 
   a:   c3                      retq   

000000000000000b <g>:
   b:   55                      push   %rbp
   c:   48 89 e5                mov    %rsp,%rbp
   f:   b8 00 00 00 00          mov    $0x0,%eax
  14:   c9                      leaveq 
  15:   c3                      retq   

指定すると、次のようになり-falign-functionsます。

align.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <f>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   b8 00 00 00 00          mov    $0x0,%eax
   9:   c9                      leaveq 
   a:   c3                      retq   
   b:   eb 03                   jmp    10 <g>
   d:   90                      nop
   e:   90                      nop
   f:   90                      nop

0000000000000010 <g>:
  10:   55                      push   %rbp
  11:   48 89 e5                mov    %rsp,%rbp
  14:   b8 00 00 00 00          mov    $0x0,%eax
  19:   c9                      leaveq 
  1a:   c3                      retq   
于 2011-10-27T07:00:54.643 に答える
15

これは、次の関数を8、16、または32バイトの境界で整列させるために行われます。

A.Fogによる「アセンブリ言語でのサブルーチンの最適化」から:

11.5コードの調整

ほとんどのマイクロプロセッサは、整列された16バイトまたは32バイトのブロックでコードをフェッチします。重要なサブルーチンエントリまたはジャンプラベルが16バイトのブロックの終わり近くにある場合、マイクロプロセッサは、そのコードのブロックをフェッチするときに、数バイトの有用なコードのみを取得します。ラベルの後の最初の命令をデコードする前に、次の16バイトもフェッチする必要がある場合があります。これは、重要なサブルーチンエントリとループエントリを16だけ揃えることで回避できます。

[...]

サブルーチンエントリの整列は、必要に応じてアドレスを8、16、32、または64で除算できるように、サブルーチンエントリの前に必要な数のNOPを配置するのと同じくらい簡単です。

于 2011-10-27T06:53:51.787 に答える
5

私が覚えている限り、命令はcpuでパイプライン化され、さまざまなcpuブロック(ローダー、デコーダーなど)が後続の命令を処理します。RET命令が実行されているとき、次の命令はすでにCPUパイプラインにロードされています。推測ですが、ここで掘り下げてみることができます。見つけたら(NOP安全な特定の数の場合は、調査結果を共有してください)。

于 2011-10-27T06:44:52.920 に答える