私は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;
}
_
パックされた構造体が私が思うように機能することは今や明らかですが、それでも何が問題の原因であるかはわかりません。確定的でなければならない関数に同じ値を渡していますが、呼び出しごとに異なる結果が得られます。
私のカーネルは32ビットプロテクトモードのセグメント化を利用しています。データセグメントを0x0000.0000-0x000f.ffffとして、スタックセグメントを0x0003.8000-0x0003.ffffとして、スタックがオーバーフローした場合に他のカーネルデータやコードにオーバーフローするのではなく、一般保護違反をトリガーしました。 。
ただし、GCCはCコードをコンパイルするときに、スタックとデータセグメントが同じベースを持っていると想定します。これは、ローカル変数のアドレスを取得したときにスタックセグメントに関連していたため(ローカル変数がスタック上にあるため)、問題が発生していましたが、呼び出された関数でポインターを逆参照すると、データセグメント。
スタックが独自のセグメントではなくデータセグメントにあるように、セグメント化モデルを変更しました。これにより問題が解決しました。