5

私の C コード スニペットは、引数のアドレスを取得し、揮発性メモリの場所 (前処理されたコード) に格納します。

void foo(unsigned int x) {
    *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)(&x);
}

int main() {
    foo(1);
    while(1);
}

このコードのコンパイルには GCC の SVN バージョンを使用しました。関数の最後に、値がスタックに格納され、その値を指すアドレスが に格納されるfooことを期待します。flag を使用して最適化なしでコンパイルすると、予想される ARM7TMDI アセンブリ出力が得られます (便宜上コメントされています)。10x40000d4-O0

        .align  2
        .global foo
        .type   foo, %function
foo:
        @ Function supports interworking.
        @ args = 0, pretend = 0, frame = 8
        @ frame_needed = 0, uses_anonymous_args = 0
        @ link register save eliminated.
        sub     sp, sp, #8
        str     r0, [sp, #4]     @ 3. Store the argument on the stack
        mov     r3, #67108864
        add     r3, r3, #212
        add     r2, sp, #4       @ 4. Address of the stack variable
        str     r2, [r3, #0]     @ 5. Store the address at 0x40000d4
        add     sp, sp, #8
        bx      lr
        .size   foo, .-foo
        .align  2
        .global main
        .type   main, %function
main:
        @ Function supports interworking.
        @ args = 0, pretend = 0, frame = 0
        @ frame_needed = 0, uses_anonymous_args = 0
        stmfd   sp!, {r4, lr}
        mov     r0, #1           @ 1. Pass the argument in register 0
        bl      foo              @ 2. Call function foo
.L4:
        b       .L4
        .size   main, .-main
        .ident  "GCC: (GNU) 4.4.0 20080820 (experimental)"

最初に引数をスタックに格納し、そこから に格納することは明らか0x40000d4です。を使用して最適化してコンパイルすると-O1、予期しない結果が得られます。

        .align  2
        .global foo
        .type   foo, %function
foo:
        @ Function supports interworking.
        @ args = 0, pretend = 0, frame = 8
        @ frame_needed = 0, uses_anonymous_args = 0
        @ link register save eliminated.
        sub     sp, sp, #8
        mov     r2, #67108864
        add     r3, sp, #4        @ 3. Address of *something* on the stack
        str     r3, [r2, #212]    @ 4. Store the address at 0x40000d4
        add     sp, sp, #8
        bx      lr
        .size   foo, .-foo
        .align  2
        .global main
        .type   main, %function
main:
        @ Function supports interworking.
        @ args = 0, pretend = 0, frame = 0
        @ frame_needed = 0, uses_anonymous_args = 0
        stmfd   sp!, {r4, lr}
        mov     r0, #1           @ 1. Pass the argument in register 0
        bl      foo              @ 2. Call function foo
.L4:
        b       .L4
        .size   main, .-main
        .ident  "GCC: (GNU) 4.4.0 20080820 (experimental)"

今回は、スタックの何かがまだ に格納されていても、引数がスタックに格納されることはありません0x40000d4

これは予想される/未定義の動作ですか? 何か間違ったことをしたのでしょうか、それとも実際に Compiler Bug™ を見つけたのでしょうか?

4

11 に答える 11

7

から戻るとfoo()xはなくなり、それへのポインタはすべて無効になります。その後、そのようなポインターを使用すると、C 標準が「未定義の動作」と呼ぶのが好きな結果が生じます。つまり、コンパイラーは、逆参照しないと仮定することが絶対に許可されています。あなたが期待するようなことをリモートで行います。リターンx後もポインターを有効のままにしたい場合は、 foo のスタック、ピリオドに割り当ててはなりません。原則として、それを上書きする理由がないことを知っていても、C では許可されていないためです。あなたが期待することをすることがどれほど頻繁に起こっても。foo()x

最も簡単な解決策はx、ローカル変数をmain()(または十分に有効なスコープを持つ他の関数に) 作成し、そのアドレスを foo に渡すことです。xグローバル変数を作成したり、 を使用してヒープに割り当てたり、malloc()よりエキゾチックな方法でメモリを確保したりすることもできます。スタックの一番上がどこにあるかを (できれば) より移植性の高い方法で把握し、スタックの一部にデータを明示的に格納することもできます。それがあなたが本当にしなければならないことだと確信しています。しかし、あなたが発見したように、あなたがそれを行うために使用してきた方法は十分に信頼できるものではありません.

于 2008-11-16T02:43:43.970 に答える
4

それで、ローカルスタック変数のアドレスを使用するDMAコントローラーに入れてから、スタック変数が使用可能な関数から戻ってきますか?

これはmain()の例では機能する可能性がありますが(スタックに再度書き込みを行わないため)、後で「実際の」プログラムでは機能しません。その値は、別の関数がアクセスする前またはDMAがアクセスしている間に上書きされます。が呼び出され、スタックが再度使用されます。

DMAが値にアクセスしている間、この値を格納するために使用できる構造体またはグローバル変数が必要です。そうしないと、値が壊れてしまいます。

-アダム

于 2008-09-10T13:34:59.160 に答える
4

これは奇妙なケースですが、実際にはコンパイラが間違っているとは思いません。

コード分​​析の観点からは、変数のアドレスを保存していることがわかりますが、そのアドレスは逆参照されず、保存したアドレスを使用できる外部コードに関数の外にジャンプしません。関数を終了すると、スタックのアドレスは存在しない変数のアドレスであるため、偽物と見なされます。

「volatile」キーワードは、特に複数のスレッドやハードウェアに関して、C ではあまり機能しません。アクセスを行う必要があることをコンパイラに伝えるだけです。ただし、データ フローによると x の値を使用するユーザーはいないため、「1」をスタックに格納する理由はありません。

あなたが書いた場合、それはおそらくうまくいくでしょう

void foo(unsigned int x) {
    volatile int y = x;
    *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)(&y);
}

foo が戻るとすぐに y のアドレスが無効と見なされるため、それでも不正なコードである可能性がありますが、DMA システムの性質は、プログラム フローとは無関係にその場所を参照することです。

于 2008-09-16T04:48:09.143 に答える
3

注意すべきことの 1 つは、標準によれば、キャストは r 値であるということです。以前は GCC で許可されていましたが、最近のバージョンでは少し標準に固執するようになりました。

それが違いを生むかどうかはわかりませんが、これを試してみてください:

void foo(unsigned int x) {
    volatile unsigned int* ptr = (unsigned int*)(0x4000000 + 0xd4);
    *ptr = (unsigned int)(&x);
}

int main() {
    foo(1);
    while(1);
}

また、意図したものとは思えませんが、関数 local x のアドレス (渡した int のコピー) を格納しています。foo に「unsigned int *」を取り、実際に保存したいもののアドレスを渡したいと思うでしょう。

したがって、より適切な解決策は次のようになると思います。

void foo(unsigned int *x) {
    volatile unsigned int* ptr = (unsigned int*)(0x4000000 + 0xd4);
    *ptr = (unsigned int)(x);
}

int main() {
    int x = 1;
    foo(&x);
    while(1);
}

編集: 最後に、コードが最適化で壊れている場合、通常はコードが何か間違っていることを示しています。

于 2008-11-16T02:45:04.217 に答える
1

現時点で参照を見つけることができるかどうかは気の毒ですが、常に引数のアドレスを取得できると想定されていることは 99% 確信しており、呼び出し規約の詳細を精巧にするのはコンパイラ次第です。 、レジスタの使用法など

実際、これは非常に一般的な要件であり、これに一般的な問題がある可能性があるとは考えにくいと思いました-最適化を混乱させた揮発性ポインターに関するものなのだろうか。

個人的には、これを試して、コンパイルが改善されたかどうかを確認するかもしれません。

void foo(unsigned int x) 
{
    volatile unsigned int* pArg = &x;
    *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)pArg;
}
于 2008-08-26T17:14:17.697 に答える
1

トミ・キョスティラが書いた

ゲームボーイアドバンスの開発。その DMA システムについて読んでいて、単色のタイル ビットマップを作成して実験しました。アイデアは、DMA を使用してその色でタイルを塗りつぶす関数に、インデックス付きの色を引数として渡すことでした。DMA 転送のソース アドレスは 0x40000d4 に格納されます。

それはあなたにとって完全に有効なことであり、-O1 最適化で取得した (予期しない) コードがどのように機能しないかがわかります。

-O0 最適化で得た (期待される) コードは、期待どおりに動作することがわかります。必要な色の値をスタックに置き、その色へのポインタを DMA 転送レジスタに置きます。

ただし、-O0 最適化で取得した (予想される) コードでさえも機能しません。DMA ハードウェアがそのポインターを取り、それを使用して目的の色を読み取るまでに、スタック上のその値は (おそらく) 他のサブルーチンまたは割り込みハンドラー、またはその両方によって上書きされています。したがって、予想されるコードと予想外のコードの結果は同じになります。DMA は (おそらく) 間違った色をフェッチします。

DMAが読み取りを完了するまで、色の値を安全な場所に保存することを本当に意図していたと思います。したがって、グローバル変数、または次のような関数ローカル静的変数

// 警告:仕事中の3 つ星プログラマー

// Warning: untested code.
void foo(unsigned int x) {
    static volatile unsigned int color = x; // "static" so it's not on the stack
    volatile unsigned int** dma_register =
        (volatile unsigned int**)(0x4000000 + 0xd4);
    *dma_register = &color;
}

int main() {
    foo(1);
    while(1);
}

それはあなたのために働きますか?

「volatile」を 2 回使用していることがわかります。これは、2 つの値を特定の順序で強制的に書き込むためです。

于 2011-03-15T19:33:27.247 に答える
0

T.にも答えがあると思います。変数を渡しましたが、関数内でその変数のアドレスを取得することはできません。ただし、その変数のコピーのアドレスを取得することはできますが、その変数は通常レジスタであるため、アドレスはありません。その関数をそのままにしておくと、呼び出し元の関数はそれを失います。値渡しではなく参照渡しが必要な関数でアドレスが必要な場合は、アドレスを送信してください。バグは gcc ではなく、コードにあるようです。

ところで、 *(volatile blah *)0xabcd またはその他の方法を使用してレジスタをプログラムしようとすると、最終的には噛まれます。gcc やその他のほとんどのコンパイラには、攻撃の最悪のタイミングを正確に知るためのこの驚くべき方法があります。

ここから変わる日を言って

*(volatile unsigned int *)0x12345 = someuintvariable;

*(volatile unsigned int *)0x12345 = 0x12;

優れたコンパイラは、指定したアーキテクチャ、またはその日のそのコンパイラのデフォルトのアーキテクチャに応じて、8 ビットのみを保存していることに気づき、そのために 32 ビット ストアを無駄にする理由がないため、それを str ではなく strb に最適化します。

この何十回もgccや他の人にやられた後、私は問題を強制することに頼りました:

.globl PUT32
PUT32:
   str r1,[r0]
   bx lr





   PUT32(0x12345,0x12);

数クロック サイクル余分にかかりますが、私のコードは昨日も今日も引き続き動作し、最適化フラグがあれば明日も動作します。古いコードに再度アクセスする必要がなく、一晩中安らかに眠ることは、あちこちで数クロック サイクルを追加する価値があります。

また、デバッグ用にコンパイルするのではなく、リリース用にコンパイルしたときにコードが壊れる場合も、コードのバグである可能性が高いことを意味します。

于 2009-04-29T06:43:15.060 に答える
0

一般に、これは有効な最適化であると言えます。さらに詳しく調べたい場合は、-da でコンパイルできます。これにより、.c.Number.Passname が生成され、rtl (gcc 内の中間表現) を見ることができます。そこでは、どのパスがどの最適化を行うかを確認できます (そして、1 つだけを無効にすることもできます)。

于 2008-11-11T14:47:44.640 に答える
0

答えではありませんが、あなたのための追加情報です。私の日常の仕事では、3.4.5 20051201 (Red Hat 3.4.5-2) を実行しています。

-O1 フラグを追加すると、一部のコード (ここには掲載できません) が機能しなくなることにも気付きました。私たちの解決策は、今のところフラグを削除することでした:(

于 2008-09-10T18:26:28.337 に答える
0

火花が書いた

GCC のバグを発見したと思われる場合、メーリング リストはあなたが立ち寄ってくれたことを嬉しく思いますが、一般的に、メーリング リストはあなたの知識に穴が開いていることを発見し、容赦なく責任を負い、嘲笑します :(

GCCメーリングリストに行って自分の無能さを示す前に、まずここで運を試してみようと思いました:)


アダム・デイビスが書いた

好奇心から、あなたは何を達成しようとしていますか?

ゲームボーイアドバンスの開発を試みていました。その DMA システムについて読んでいて、単色のタイル ビットマップを作成して実験しました。アイデアは、DMA を使用してその色でタイルを塗りつぶす関数に、インデックス付きの色を引数として渡すことでした。DMA 転送のソースアドレスは に格納され0x40000d4ます。


ウィル・ディーンが書いた

個人的には、これを試して、コンパイルが改善されたかどうかを確認するかもしれません。

void foo(unsigned int x) 
{
    volatile unsigned int* pArg = &x;
    *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)pArg;
}

-O0それも同様に機能し、質問に投稿し-O1たのとまったく同じ-O1アセンブリに最適化されています。

于 2008-08-26T17:54:48.083 に答える
-2

これは予想される/未定義の動作ですか? 何か間違ったことをしたのでしょうか、それとも実際に Compiler Bug™ を見つけたのでしょうか?

最適化オプションが動作しない可能性のある奇妙なコードを生成する可能性があるという定義された動作だけのバグではありません:)

編集:

GCC のバグを発見したと思われる場合、メーリング リストはあなたが立ち寄ってくれたことを嬉しく思いますが、一般的に、メーリング リストはあなたの知識に穴が開いていることを発見し、容赦なく責任を負い、嘲笑します :(

この場合、回避する必要があるのは、おそらくコードを壊すショートカットを試みる -O オプションだと思います。

于 2008-08-26T16:44:42.310 に答える