0

gdtrレジスタの内容を、具体的には gcc インライン アセンブリを使用して C 変数に読み込もうとしています。そのために、ここで見つけたいくつかのコードを適応させていますが、コードは 32 ビット プロセッサ用に書かれています。そのため、命令を 64 ビットに適合させる際に、誰かが説明してくれることを期待していた奇妙な動作に遭遇しました。

まず、gdtrgdtr レジスターの構造をモデル化する構造体です。

struct gdtr64 {
    uint16_t limit;
    uint64_t addr;
};

十分に単純です。実行してレジスタの内容をそのような構造に出力しようとすると:

struct gdtr64 gdtr64 = {0xcccc,0xa2a2a2a2a2a2a2a2};
printf("gdtr64 limit: %x\ngdtr64 addr: %llx\n", gdtr64.limit, gdtr64.addr);
printf("<--asm call-->\n");
__asm__ volatile("sgdt %0\n" : :"m"(gdtr64));
printf("gdtr64 limit: %x\ngdtr64 addr: %llx\n", gdtr64.limit, gdtr64.addr);

私は得る:

gdtr64 limit: cccc
gdtr64 addr: a2a2a2a2a2a2a2a2
<--asm call-->
gdtr64 limit: a0
gdtr64 addr: a2a2a2a2a2a2ffff

呼び出し前の値はただのジャンク値なので、何が変更されたかがわかります。制限が から に更新され、 の最後の 2 バイトが変更されていることがccccわかり00a0ますgdtr64.addr。これは私にはあまり意味がありません。

実験として、gdtr64.addrアセンブリ セクションに渡すことを除いて、同じコードを実行しました。

struct gdtr64 gdtr64 = {0xcccc,0xa2a2a2a2a2a2a2a2};
printf("gdtr64 limit: %x\ngdtr64 addr: %llx\n", gdtr64.limit, gdtr64.addr);
printf("<--asm call-->\n");
__asm__ volatile("sgdt %0\n" : :"m"(gdtr64.addr));
printf("gdtr64 limit: %x\ngdtr64 addr: %llx\n", gdtr64.limit, gdtr64.addr);

出力は私を驚かせました:

gdtr64 limit: cccc
gdtr64 addr: a2a2a2a2a2a2a2a2
<--asm call-->
gdtr64 limit: cccc
gdtr64 addr: ff8076db40000097

この場合、 が占有するメモリ アドレスの後に書き込みを開始しますがgdtr64.limit、書き込まれる内容が大幅に異なることがわかります。前の00a0例で限界だったのが addr の最後に移行したのがこれです。それ以外の場合は、適切なアドレスの構成のように見えます。

それで、それが私が使っていた に固有のものではないかどうか疑問に思ったstructので、一連のchars を試すことにしました。レジスタの長さは 10 バイトである必要があるため、次のようになります。

char gdtr_char[10] = "0000000000";
printf("GDTR_CHAR: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x \n",
    (unsigned char) gdtr_char[0],
    (unsigned char) gdtr_char[1],
    (unsigned char) gdtr_char[2],
    (unsigned char) gdtr_char[3],
    (unsigned char) gdtr_char[4],
    (unsigned char) gdtr_char[5],
    (unsigned char) gdtr_char[6],
    (unsigned char) gdtr_char[7],
    (unsigned char) gdtr_char[8],
    (unsigned char) gdtr_char[9]
);
printf("<--asm call-->\n");
__asm__ volatile("sgdt %0\n" : :"m"(gdtr_char[0]));
printf("GDTR_CHAR: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x \n",
    (unsigned char) gdtr_char[0],
    (unsigned char) gdtr_char[1],
    (unsigned char) gdtr_char[2],
    (unsigned char) gdtr_char[3],
    (unsigned char) gdtr_char[4],
    (unsigned char) gdtr_char[5],
    (unsigned char) gdtr_char[6],
    (unsigned char) gdtr_char[7],
    (unsigned char) gdtr_char[8],
    (unsigned char) gdtr_char[9]
);

私のCスキルは...発展途上です。この結果は次のとおりです。

GDTR_CHAR: 30 30 30 30 30 30 30 30 30 30 
<--asm call-->
GDTR_CHAR: 97 00 00 50 dd 76 80 ff ff ff 

繰り返しますが、初期値はジャンクですが、レジスターを読み取った後、10 バイトすべてが考慮されていることがわかりますが、2 回目の試行で得られたものとは逆の順序になっています。要約する:

trial 1 limit: 00a0
trial 1 addr:  ************ffff
-------------------------------
trial 2 limit: ****
trial 2 addr:  ff8076db40000097
-------------------------------
trial 3 array: 97 00 00 40 db 76 80 ff ff ff
reversed:      ff ff ff 80 76 db 40 00 00 97 //byte-wise

ちなみに、これは別々の「トライアル」に分割されていましたが、これらは一度に実行されました. レジスタの内容は、実行ごとに変化するようです (これも奇妙だと思います)。そうは言っても、次の問題に頭を悩ませることはできません。

  • GDTR の内容が実行ごとに変わるのはなぜですか?
  • struct と char 配列の使用に違いがあるのはなぜですか?
  • GDT のベース メモリ アドレスは何ですか (つまり、どの結果が正しいか [ある場合])?

どんな助けでも大歓迎です。ここまで読んでくれてありがとう。

4

1 に答える 1

2

おそらく少なくとも 2 つの問題に直面しています。

最初の問題は、コンパイラがアラインメントのためにパディングを追加することです。そのため、「16 ビット制限と 64 ビット アドレス」を含む構造体であると考えられるのは、おそらく「16 ビット制限、CPU が予期しない 48 ビットのパディング」です。および64ビットアドレス」。ほとんどのコンパイラには、構造体をパックするための (非標準の) 拡張機能があります (例: " #pragma pack" または " __attribute__((packed)))" )。

2 つ目の問題はエンディアン性です。80x86 はリトルエンディアンです。つまり、バイト 0x12、0x34、0x45、0x67 は 32 ビット整数 0x67452312 を表します。

2 回目と 3 回目の試行では、制限は 0x0097 で、アドレス部分は 0xFFFFFF8076DB4000 だったと思います。ただし、最初の試行についてはわかりません (GDTR が 1 回目と 2 回目の試行で変更されたようです)。

編集:また、最初の試行からの制限の結果がとにかく間違っているように見えることに注意してください。制限は「GDT のサイズ - 1」であり、GDT エントリはそれぞれ 8 (または 16) バイトであるため、制限には常に下位 3 ビットを設定する必要があります (たとえば、「0x???7」または「0x???F」 "。

于 2012-04-25T02:32:36.180 に答える