web-dev-qa-db-ja.com

同じはずの2つのポインタが異なるデータを指すのはなぜですか?

私はFAT16ドライバをGNU Cで趣味のオペレーティングシステム用に書いています。構造は次のように定義されています。

_struct directory_entry {
  uint8_t name[11];
  uint8_t attrib;
  uint8_t name_case;
  uint8_t created_decimal;
  uint16_t created_time;
  uint16_t created_date;
  uint16_t accessed_date;
  uint16_t ignore;
  uint16_t modified_time;
  uint16_t modified_date;
  uint16_t first_cluster;
  uint32_t length;
} __attribute__ ((packed));
_

nameは構造体全体と同じアドレスにあり、attribはその後11バイトになるという印象を受けました。実際、_(void *)e.name - (void *)&e_は0で、_(void *)&e.attrib - (void *)&e_は11です。eは_struct directory_entry_型です。

私のカーネルでは、eへのvoidポインターが、その内容をディスクから読み取る関数に渡されます。この関数の後、*(uint8_t *)&eは80で、*((uint8_t *)&e + 11は8です。ただし、_e.name[0]_および_e.attrib_はどちらも0です。

ここで何が得られますか? __attribute__ ((packed))の仕組みを誤解していますか?同じ属性を持つ他の構造体は、カーネルの他の部分で期待どおりに機能します。必要に応じて、完全なソースへのリンクを投稿できます。

編集:完全なソースは、_stack-overflow_ブランチの this gitlab repository にあります。関連する部分は、src/kernel/main.cの34行目から52行目です。 *(uint8_t *)&e*((uint8_t *)&e + 11)を確認すると、データが正しく入力されていると確信しています。実行すると、その部分によって以下が出力されます。

_(void *)e.name - *(void *)&e
  => 0
*(uint8_t *)&e
  => 80
e.name[0]
  => 0
(void *)&e.attrib - (void *)&e
  => 11
*((uint8_t *)&e + 11)
  => 8
e.attrib
  => 0
_

_e.name[0]_が*(uint8_t *)&eと異なる理由について、私は非常に混乱しています。

編集2:コンパイルされたコードの違いを確認するために、objdumpを使用してこの部分を逆アセンブルしましたが、今ではさらに混乱しています。 u8_dec(*(uint8_t *)&e, nbuf);u8_dec(e.name[0], nbuf);はどちらも次のようにコンパイルされます:(私のコメント)

_lea   eax, [ebp - 0x30] ;loads address of e from stack into eax
movzx eax, byte [eax]   ;loads byte pointed to by eax into eax, zero-extending
movzx eax, al           ;not sure why this is here, as it's already zero-extended
sub esp, 0x8
Push  0x31ce0 ;nbuf
Push  eax     ;the byte we loaded
call  0x3162f ;u8_dec
add esp, 0x10
_

予想通り、これは構造体の最初のバイトで渡されます。最初の引数は参照ではなく値で渡されるため、_u8_dec_はeを変更しないと確信しています。 nbufはファイルスコープで宣言された配列ですが、eは関数スコープで宣言されているため、それらが重複したり、何かが重複したりすることはありません。たぶん、_u8_dec_は正しく機能していませんか?そのソースは次のとおりです。

_void u8_dec(uint8_t n, uint8_t *b) {
  if (!n) {
    *(uint16_t *)b = '0';
    return;
  }
  bool zero = false;
  for (uint32_t m = 100; m; m /= 10) {
    uint8_t d = (n / m) % 10;
    if (zero)
      *(b++) = d + '0';
    else if (d) {
      zero = true;
      *(b++) = d + '0';
    }
  }
  *b = 0;
}
_

パックされた構造体が私が思うように機能することは今や明らかですが、それでも何が問題の原因であるかはわかりません。確定的でなければならない関数に同じ値を渡していますが、呼び出しごとに異なる結果が得られます。

4
Benji Dial

私のカーネルは32ビットプロテクトモードのセグメント化を利用しています。データセグメントを0x0000.0000-0x000f.ffffとして、スタックセグメントを0x0003.8000-0x0003.ffffとして、スタックがオーバーフローした場合に他のカーネルデータやコードにオーバーフローするのではなく、一般保護違反をトリガーしました。 。

ただし、GCCはCコードをコンパイルするときに、スタックとデータセグメントが同じベースを持っていると想定します。これは、ローカル変数のアドレスを取得したときにスタックセグメントに関連していたため(ローカル変数がスタック上にあるため)、問題が発生していましたが、呼び出された関数でポインターを逆参照すると、データセグメント。

スタックが独自のセグメントではなくデータセグメントにあるように、セグメント化モデルを変更しました。これにより問題が解決しました。

4
Benji Dial