web-dev-qa-db-ja.com

レジスタ値をC変数に読み込む

拡張されたgccインラインアセンブリを使用してレジスタ値を読み取り、C変数に格納する方法を見たことを覚えています。

私の人生では、asmステートメントの作成方法を思い出せません。

40
Brian

編集者注:ローカルregister-asm変数を使用するこの方法は、 GCCによって「サポートされていません」として文書化されていますです。通常はGCCで動作しますが、clangで中断します。 (ドキュメントのこの文言は、この回答が投稿された後に追加されたと思います。)

グローバル固定レジスタ変数バージョンは、32ビットx86のパフォーマンスコストが高く、GP整数レジスタは7つしかありません(スタックポインターはカウントしません)。これはそれを6に減らします。すべてのコードが頻繁に使用するグローバル変数がある場合にのみ、これを考慮してください。


あなたが何を望んでいるかわからないので、これまでのところ他の答えとは異なる方向に進んでいます。

GCCマニュアル§5.40指定されたレジスタの変数

register int *foo asm ("a5");

ここでa5は使用するレジスタの名前です…

当然、レジスタ名はCPUに依存しますが、特定のレジスタは明示的なアセンブラ命令で最も役立つことが多いため、これは問題ではありません( Extended Asm を参照)。これらの両方の場合、一般に、CPUタイプに従ってプログラムを条件付けする必要があります。

そのようなレジスタ変数を定義しても、レジスタは予約されません。変数の値が有効でないとフロー制御が判断する場所では、他の用途で引き続き使用できます。

GCCマニュアル§3.18コード生成規則のオプション

-ffixed-reg

regという名前のレジスタを固定レジスタとして扱います。生成されたコードはそれを参照してはなりません(おそらく、スタックポインター、フレームポインター、または他の固定された役割として)。

これにより、リチャードの答えをより簡単な方法で再現できます。

int main() {
    register int i asm("ebx");
    return i + 1;
}

ebxレジスタの内容が分からないため、これはかなり無意味です。

これら2つを組み合わせて、gcc -ffixed-ebxでコンパイルすると、

#include <stdio.h>
register int counter asm("ebx");
void check(int n) {
    if (!(n % 2 && n % 3 && n % 5)) counter++;
}
int main() {
    int i;
    counter = 0;
    for (i = 1; i <= 100; i++) check(i);
    printf("%d Hamming numbers between 1 and 100\n", counter);
    return 0;
}

c変数は常に高速アクセスのためにレジスタに常駐し、他の生成されたコードによって破壊されないことを保証できます。 (便利なことに、通常のx86呼び出し規約ではebxは呼び出し先に保存されるので、-ffixed-*なしでコンパイルされた他の関数の呼び出しによって破壊されたとしても、復元されるはずです。)

一方、これは間違いなく移植性がなく、コンパイラの自由を制限しているため、通常はパフォーマンス上のメリットもありません。

31
ephemient

Ebxを取得する方法は次のとおりです。

int main()
{
    int i;
    asm("\t movl %%ebx,%0" : "=r"(i));
    return i + 1;
}

結果:

main:
    subl    $4, %esp
    #APP
             movl %ebx,%eax
    #NO_APP
    incl    %eax
    addl    $4, %esp
    ret

"= r"(i)は出力制約であり、最初の出力(%0)は変数 "i"に配置する必要があるレジスターであることをコンパイラーに伝えます。この最適化レベル(-O5)では、変数iは決してメモリに保存されませんが、eaxレジスタに保持されます。これはたまたま戻り値レジスタでもあります。

20

私はgccについては知りませんが、VSでは次のようになります。

int data = 0;   
__asm
{
    mov ebx, 30
    mov data, ebx
}
cout<<data;

基本的に、ebxのデータを変数dataに移動しました。

5
Jacob

これにより、スタックポインターレジスタがsp変数に移動します。

intptr_t sp;
asm ("movl %%esp, %0" : "=r" (sp) );

「esp」を実際のレジスタに置き換えて(ただし、%%を失わないようにしてください)、「sp」を変数に置き換えてください。

3

インラインasmステートメントの実行時にコンパイラー生成コードがレジスターに保存する値を知ることができないので、値は通常は無意味であり、デバッガーを使用して見る方がはるかに良いでしょうブレークポイントで停止したときに値を登録します。

とはいえ、この奇妙なタスクを実行する場合は、効率的に実行することもできます。

一部のターゲット(x86など)では、特定レジスタ出力制約を使用して、コンパイラにwhichレジスタが出力されることを通知できます。特定のレジスター出力制約は空のasm template(ゼロ命令)を使用して、asmステートメントは入力のレジスター値を気にしないが、その後は指定されたC変数はコンパイラーに通知しますそのレジスタで。

_#include <stdint.h>

int foo() {
    uint64_t rax_value;           // type width determines register size
    asm("" : "=a"(rax_value));  // =letter determines which register (or partial reg)

    uint32_t ebx_value;
    asm("" : "=b"(ebx_value));

    uint16_t si_value;
    asm("" : "=S"(si_value) );

    uint8_t sil_value;  // x86-64 required to use the low 8 of a reg other than a-d
       // With -m32:  error: unsupported size for integer register
    asm("# Hi mom, my output constraint picked %0" : "=S"(sil_value) );

    return sil_value + ebx_value;
}
_

x86-64のGodboltのclang5. でコンパイルされています。 2つの未使用の出力値は最適化されて離れていることに注意してください。_#APP_/_#NO_APP_コンパイラー生成のasm-commentペアはありませんもはやない)。これは、_asm volatile_を使用しておらず、出力オペランドがあるため、暗黙的にvolatileではないためです。

_foo():                                # @foo()
# BB#0:
    Push    rbx
    #APP
    #NO_APP
    #DEBUG_VALUE: foo:ebx_value <- %EBX
    #APP
    # Hi mom, my output constraint picked %sil
    #NO_APP
    #DEBUG_VALUE: foo:sil_value <- %SIL
    movzx   eax, sil
    add     eax, ebx
    pop     rbx
    ret
                                    # -- End function
                                    # DW_AT_GNU_pubnames
                                    # DW_AT_external
_

指定されたレジスタから直接、2つの出力を一緒に追加するコンパイラー生成コードに注意してください。 RBXはx86-64 System V呼び出し規約の呼び出し保存レジスタであるため、RBXのプッシュ/ポップにも注意してください。 (基本的にすべての32ビットおよび64ビットのx86呼び出し規約)。しかし、コンパイラーにasmステートメントが値を書き込むことを伝えました。 (空のasmステートメントを使用するのは一種のハックです。コンパイラーがレジスターを読み取りたいだけであると直接指示する構文はありません。これは、asmステートメントの実行時にコンパイラーがレジスターで何をしていたかわからないためです挿入されます。)

コンパイラーは、asmステートメントを実際にそのレジスターにwroteするかのように扱います。したがって、後で値が必要な場合は、別のレジスターにコピー(またはメモリーに流出)します。 asmステートメントが「実行」されるとき。


他の x86レジスタの制約b(bl/bx/ebx/rbx)、c(.../rcx )、d(.../rdx)、S(sil/si/esi/rsi)、D(.../rdi)。 bpl/bp/ebp/rbpには特定の制約はありませんが、フレームポインターのない関数では特別ではありません。 (おそらく、それを使用すると、コードが_-fno-omit-frame-pointer_でコンパイラーではなくなるためです。)

register uint64_t rbp_var asm ("rbp")を使用できます。その場合、asm("" : "=r" (rbp_var));は_"=r"_制約がrbpを選択することを保証します。同様に、明示的な制約もないr8-r15についても同様です。 ARMなどの一部のアーキテクチャでは、asmレジスタ変数が、asmの入力/出力制約に使用するレジスタを指定する唯一の方法です。 (そして、asm制約はonlyサポートされている_register asm_変数の使用;変数の値がいつでもそのレジスターにあるという保証はありません。


関数(またはインライン化後の親関数)内の任意の場所にコンパイラがこれらのasmステートメントを配置することを止めるものは何もありません。したがって、レジスタの値をサンプリングしているwhereを制御することはできません。 _asm volatile_はいくつかの並べ替えを回避できますが、他のvolatileアクセスに関してのみ可能性があります。コンパイラが生成したasmをチェックして、必要なものが手に入れられたかどうかを確認できますが、それは偶然で、後で壊れる可能性があることに注意してください。

他の何かの依存関係チェーンにasmステートメントを配置して、コンパイラーが配置する場所を制御できます。 _"+rm"_制約を使用して、最適化されていないものに実際に使用される他の変数を変更するようコンパイラーに指示します。

_uint32_t ebx_value;
asm("" : "=b"(ebx_value), "+rm"(some_used_variable) );
_

ここで、_some_used_variable_は、ある関数からの戻り値であり、(処理後に)argとして別の関数に渡されます。または、ループで計算され、関数の戻り値として返されます。その場合、asmステートメントは、ループの終了後、その変数の後の値に依存するコードの前のいずれかのポイントに来ることが保証されます。

ただし、これにより、その変数の定数伝播などの最適化が無効になります。 https://gcc.gnu.org/wiki/DontUseInlineAsm 。コンパイラは、出力値についてanythingを想定できません。 asmステートメントに命令がないことを確認しません。


これは、gccが出力オペランドまたはclobberとして使用できないレジスタによっては機能しません。スタックポインター

ただし、プログラムがスタックで特別な処理を行う場合、値をC変数に読み込むことはスタックポインターにとって意味があるかもしれません。

Inline-asmの代替として、スタックアドレスを取得するための __builtin_frame_address(0) があります。 (ただし、IIRCでは、_-fomit-frame-pointer_が有効になっている場合でも、x86のデフォルトのように、その関数が完全なスタックフレームを作成します。)

それでも、ほとんど無料の多くの関数では(また、ローカルフレームへのRSP相対アクセスよりもRBP相対のアドレッシングモードが小さいため、スタックフレームを作成することはコードサイズに適しています)。

movステートメントでasm命令を使用することももちろん機能します。

2
Peter Cordes

GCCドキュメント自体から: http://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html

2
user108127
#include <stdio.h>

void gav(){
        //rgv_t argv = get();
        register unsigned long long i asm("rax");
        register unsigned long long ii asm("rbx");
        printf("I`m gav - first arguman is: %s - 2th arguman is: %s\n", (char *)i, (char *)ii);
}

int main(void)
{
    char *test = "I`m main";
    char *test1 = "I`m main2";
    printf("0x%llx\n", (unsigned long long)&gav);
    asm("call %P0" : :"i"((unsigned long long)&gav), "a"(test), "b"(test1));
    return 0;
}
1
Mahdi Mohammadi