4

64ビットマシンで簡単なコードを書きました

int main() {
    printf("%d", 2.443);
}

したがって、これがコンパイラの動作です。2番目の引数がdoubleであると識別されるため、スタックに8バイトをプッシュするか、呼び出し全体でレジスタを使用して変数にアクセスします。%d4バイトの整数値を想定しているため、ガベージ値を出力します。

興味深いのは、このプログラムを実行するたびに出力される値が変わることです。では、何が起こっているのでしょうか。毎回同じガベージ値を出力し、毎回異なる値を出力しないことを期待していました。

4

2 に答える 2

7

もちろん、フォーマットに対応しない引数を渡すことは未定義の動作であるため、言語は出力が変更される理由を教えてくれません。実装、それが生成するコード、そして場合によってはオペレーティングシステムも確認する必要があります。

私の設定はあなたの設定とは異なります、

Linux3.1.10-1.16-デスクトップx86_64GNU/ Linux(openSuSE 12.1)

gcc-4.6.2を使用。しかし、それは十分に類似しているので、同じメカニズムを疑うのは合理的です。

生成されたアセンブリ(-O3、習慣から)を見ると、関連する部分(main)は次のようになります。

.cfi_startproc
subq    $8, %rsp             # adjust stack pointer
.cfi_def_cfa_offset 16
movl    $.LC1, %edi          # move format string to edi
movl    $1, %eax             # move 1 to eax, seems to be the number of double arguments
movsd   .LC0(%rip), %xmm0    # move the double to the floating point register
call    printf
xorl    %eax, %eax           # clear eax (return 0)
addq    $8, %rsp             # adjust stack pointer
.cfi_def_cfa_offset 8
ret                          # return

の代わりにdouble、私が渡す場合int、あまり変更はありませんが、それは大幅に

movl    $47, %esi            # move int to esi
movl    $.LC0, %edi          # format string
xorl    %eax, %eax           # clear eax
call    printf

に渡される引数のタイプと数の多くのバリエーションについて生成されたコードを調べました。printf一貫して、最初のdouble(またはプロモートさfloatれた)引数は、で渡されxmmNN = 0, 1, 2整数(int、、、、符号charlong関係なく)は、、で渡されesiますedxecx、、r8dそしてr9dスタック。

だから私printfはで発表さintれたものを探し、esiそこに起こったことは何でも印刷するという推測に挑戦します。

そこに何も動かされていないときに、の内容がesi何らかの形で予測可能であるかどうかmain、そしてそれらが何を意味するのか、私にはわかりません。

于 2012-10-12T02:36:04.613 に答える
0

この回答は、変動の原因のいくつかに対処しようとします。これは、ダニエル・フィッシャーの回答とそれに対するいくつかのコメントのフォローアップです。

私はLinuxを使っていないので、明確な答えを出すことはできません。後のprintf大規模なアプリケーションでは、潜在的な変動の原因が無数にあります。これは小さなアプリケーションの早い段階で、ほんの少ししか存在しないはずです。

アドレス空間配置のランダム化(ASLR)はその1つです。オペレーティングシステムは、使用するアドレスをマルウェアが認識しないように、メモリの一部を意図的にランダムに再配置します。Linux3.4.4-2にこれがあるかどうかはわかりません。

もう1つは環境変数です。シェル環境変数は、それが生成するプロセスにコピーされます(そしてgetenvルーチンからアクセスできます)。それらのいくつかは自動的に変更される可能性があるため、値がわずかに異なります。printfこれは、欠落している整数引数を使用しようとしたときに表示される内容に直接影響を与える可能性は低いですが、カスケード効果が発生する可能性があります。

mainが呼び出される前または呼び出される前に実行される共有ライブラリローダーが存在する場合がありますprintf。たとえば、printfが実行可能ファイルに組み込まれているのではなく、共有ライブラリにある場合、を呼び出すと、printf実際にはローダーを呼び出すスタブルーチンが呼び出される可能性があります。ローダーは共有ライブラリを検索し、を含むモジュールを見つけ、そのモジュールをプロセスのアドレススペースにロードし、スタブを変更して、将来(ローダーを呼び出す代わりに)printf新しくロードされたものを直接呼び出すようにします。printfprintf。ご想像のとおり、これはかなり大規模なプロセスであり、特に、ディスク上のファイル(共有ライブラリと共有ライブラリにアクセスするためのすべてのディレクトリ)を検索して読み取る必要があります。システムでのキャッシュまたはファイル操作によっては、ローダーでの動作がわずかに異なることが考えられます。

これまでのところ、私は上記のものの中で最も可能性の高い候補としてASLRを支持しています。後者の2つは、かなり安定している可能性があります。関連する値は通常、頻繁ではなく、時々変更されます。ASLRは毎回変更されるため、アドレスをレジスタに残すだけでprintf動作を説明できます。

これが実験です:最初の後に、このコードでprintf別のものを挿入します:printf

printf("%d\n", 2.443);
int a;
printf("%p\n", (void *) &a);

2番目のprintfはa、スタック上にある可能性が高いのアドレスを出力します。プログラムを2〜3回実行し、最初printfの値と2番目の値の差を計算しますprintf。(2番目printfは16進数で出力される可能性が高いため、最初の値を "%x"に変更して16進数にするのが便利な場合があります。)2番目の値printfが実行ごとに異なる場合は、プログラムでASLRが発生しています。 。値が実行ごとに変化しても、それらの差が一定のままである場合printf、最初に発生した値printfは、プログラムの初期化後に放置されたプロセス内のアドレスです。

アドレスがa変更されても違いが一定でない場合は、最初の値をアドレススペースの別の部分と比較すると、より良い結果が得られるかどうかを確認するために変更int a;してみてください。static int a;

当然、これは信頼できるプログラムを書くのに役立ちません。プログラムのロードと初期化がどのように機能するかについては、単なる教育です。

于 2012-10-12T23:41:29.717 に答える