Takym > QA
質疑応答
このページでは低レイヤに関する質問とその回答をまとめています。
下記に該当する質問が無い場合は Slack の #初心者質問相談 チャンネルに訪れてみてください。
目次
質問: 機械語命令に数字が使われる事はありますか?
回答: あります。
例えば x86 では下記の様な命令があります。
|命令 |説明 (概略) |参考 |
|:----------|:---------------------------------------------------|:-------------------------------------------------------------------|
|`CMPXCHG8B`|レジスタとメモリ上の値を比較し等しければ交換します。|<https://www.felixcloutier.com/x86/cmpxchg8b:cmpxchg16b> |
|`CRC32` |[巡回冗長検査]します。 |<https://www.felixcloutier.com/x86/crc32> |
|`INT3` |デバッグ時に使用されます。 |<https://x86.puri.sm/html/file_module_x86_id_142.html> |
|`UD2` |無効命令例外を発生させます。 |<https://mudongliang.github.io/x86/html/file_module_x86_id_318.html>|
質問: GCC のインラインアセンブラの "a"(arg0)
や "Nd"(arg1)
は何を意味しますか?
回答: オペランド制約を意味します。
* `a` は **EAX** レジスタを表します。
* `d` は **EDX** レジスタを表します。
* `N` は8ビット即値の命令エンコーディングを優先する事を表します。
* `IN` 命令と `OUT` 命令で使用されます。
* `Nd` は `N` と `d` の両方を指定しています。
#### 参考
* [Constraints (Using the GNU Compiler Collection (GCC))](https://gcc.gnu.org/onlinedocs/gcc/Constraints.html>)
* [Machine Constraints (Using the GNU Compiler Collection (GCC))](https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html)
* [GCCのインラインアセンブラの書き方 for x86](https://wocota.hatenadiary.org/entry/20090628/1246188338)
* [GCCのインラインアセンブラの構文について調査](https://msyksphinz.hatenablog.com/entry/2016/03/28/020000)
* [オペランド制約](https://wiki.onakasuita.org/pukiwiki/?%E3%82%AA%E3%83%9A%E3%83%A9%E3%83%B3%E3%83%89%E5%88%B6%E7%B4%84)
* <https://github.com/torvalds/linux/blob/3dbdb38e286903ec220aaf1fb29a8d94297da246/arch/x86/include/asm/io.h#L275-L287>
#### 補足
これは GCC における仕様です。
[Microsoft Visual C] の[インラインアセンブラの仕様](https://docs.microsoft.com/cpp/assembler/inline/inline-assembler?view=msvc-160)とは異なります。
質問: CPU動作モードはどの様にして決定・判定しますか?
回答: 区画記述子(セグメント・ディスクリプタ、Segment Descriptor)の属性から決定・判定します。
x86 での区画記述子は下記の書式になります。
|31...24 |23 |22 |21 |20 |19...16 |15 |14...13|12 |11...8|7...0 |
|:--------:|:-:|:-:|:-:|:-:|:--------------:|:-:|:-----:|:-:|:----:|:--------:|
|Base 31:24| G |D/B|L |AVL|Seg. Limit 19:16|P |DPL |S |Type |Base 23:16|
|31...16 |15...0 |
|:----------------:|:-----------------:|
|Base Address 15:00|Segment Limit 15:00|
* **`G`** (Granularity) - 粒度。`0` の場合は 1 B 単位で設定し、`1` の場合は 4 KB 単位で設定する。
* **`D/B`** (Default operation size) - 既定の制御ビット数。`0` の場合は16ビット、`1` の場合は32ビット。
* **`L`** (64-bit code segment) - `1` の場合は64ビット。(IA-32e のみ)
* **`AVL`** (Available for use by system software)
* **`P`** (Segment present) - セグメントが存在する場合は `1`、存在しない場合は `0`。
* **`DPL`** (Descriptor privilege level) - 特権レベルを0~3で指定する。
* **`S`** (Descriptor type) - `0` の場合はシステム、`1` の場合はコードまたはデータ。
出典:<https://software.intel.com/content/www/us/en/develop/articles/intel-sdm.html>、ページ:`Vol. 3A 3-9` 下部から `Vol. 3A 3-12` 上部、参照日:2021/07/11
質問: C言語で構造体のアライメントを無効化する方法を教えてください。
回答: 属性 __attribute__((packed))
を構造体に付与します。
* 下記の様に属性を指定します。
```c
typedef struct _STRUCT_A_ {
unsigned int id;
unsigned char *name;
} __attribute__((packed)) StructA;
```
* [Microsoft Visual C] では下記の様に [`#pragma pack`] を使って記述できます。
```c
#pragma pack(1)
typedef struct _STRUCT_A_ {
unsigned int id;
unsigned char *name;
} StructA;
#pragma pack()
```
#### 補足
[データ構造アライメント]はメモリ上のデータを効率的に配置する為に利用されています。
質問: INT 0xXX
命令で呼び出されるコードを読む方法を教えてください。
回答: 環境に依存しますが、デバッガで覗く事ができます。
`INT 0xXX` 命令にブレークポイントを貼り、デバッガを使ってコードを覗く事ができます。
実機でデバッグを行う場合は専用の機器が必要になる可能性があります。
仮想環境でデバッグを行う場合は [QEMU] に [GDB] を接続すれば良いでしょう。
もしくはオープンソースの実装を読む事もできます。
#### Legacy BIOS のコードを読む場合
* [Compatibility Support Module](https://github.com/tianocore/edk2/tree/master/OvmfPkg/Csm)
* [SeaBIOS](https://review.coreboot.org/plugins/gitiles/seabios/)
#### OS のコードを読む場合
* [Linux](https://github.com/torvalds/linux)
質問: リアルモードで BIOS (INT 0x10
命令) を使わずに描画処理を行う方法はありますか?
回答: ありますが、難易度は高いです。
BIOS (`INT 0x10` 命令) を使わずに描画処理を行うには下記の方法が考えられます。
* グラフィックコントローラ、ビデオチップ、GPU 等の仕様書を入手する。
* 仕様書は有料である場合や公開されていない場合もあります。
* [X Window System] 等既存のオープンソースシステムを利用または解析する。
#### 補足
BIOS ROM に保存されているコードの解析は利用規約や著作権に抵触する可能性があります。
質問: 「あ」という文字のコードポイントは Unicode では U+3042
ですが、「あ」と書かれたファイルには 30 42
という値は見つかりません。
回答: UTF-16BE または UTF-32BE 以外の文字コードで保存されている可能性があります。
[Unicode] では必ずコードポイントと同じ値がファイルに保存される訳では無く、通常は [UTF-8]、[UTF-16]、[UTF-32] のいずれかに符号化してから保存されます。
「あ」(U+3042)という文字の場合は文字コード毎に下記の様に変換されます。
* **UTF-8**: `E3 81 82`
* **UTF-16LE**: `42 30`
* **UTF-16BE**: `30 42`
* **UTF-32LE**: `42 30 00 00`
* **UTF-32BE**: `00 00 30 42`
#### 補足
C言語の型 `wchar_t` はワイド文字型であり、どの文字コードを使うかは規定されておらず、環境依存になります。コンパイラやOSなどにより異なります。
上記で挙げた文字コードではない可能性もあります。
どの文字コードが使われるかは適宜確認する必要があります。
質問: scanf
や cin
を使って整数値を読み取る時に、a
などの文字列を入力するとどうなりますか?
回答: 不定値が返却されます。異常な値が入力された場合の動作は未定義です。
#### `scanf` の場合
異常な値が入力された場合、標準入力ストリームにその値を残したままにしてしまいます。
例えば、下記のコードを実行し「`123a`」と入力した場合、`(2)` で入力は行われず、`(3)` は「`123`」を出力します。勿論、`(1)` は「`123`」を出力します。
```c
#include
int main(int argc, char *argv[])
{
int a;
scanf("%d", &a);
printf("%d\r\n", a); // (1)
scanf("%d", &a); // (2)
printf("%d\r\n", a); // (3)
return 0;
}
```
Wikipedia の「[Scanf#異常な入力が行われた時の処理]」が参考になります。
`scanf` の内部実装は下記のページが参考になります。
* <https://github.com/bminor/glibc/blob/b92a49359f33a461db080a33940d73f47c756126/stdio-common/vfscanf-internal.c#L266>
#### `cin` の場合
異常な値が入力された場合、正しい値は返却されません。
下記の様に正しい整数値が入力されたかどうか検証する事ができます。
```cpp
#include
using namespace std;
int main(int argc, char *argv[])
{
int a;
cin >> a;
if (cin.fail()) {
cout << "Not a number" << endl;
} else {
cout << a << endl;
}
return 0;
}
```
下記のページが参考になります。
* [[短文メモ][初級記事]std::cinから整数受け取るのは、エラーチェックしないと怖くて使えないね](https://qiita.com/gyu-don/items/28d32117cf9463fda2be)
* [C++ で cin.fail メソッドを使用する](https://www.delftstack.com/ja/howto/cpp/cpp-cin-fail/)
* `cin` の内部実装
* <https://github.com/gcc-mirror/gcc/blob/84beef30a51988e77e9df3104ed6203756172d65/libstdc%2B%2B-v3/include/std/istream#L167>
* <https://github.com/gcc-mirror/gcc/blob/84beef30a51988e77e9df3104ed6203756172d65/libstdc%2B%2B-v3/src/c%2B%2B98/istream.cc#L207>
#### 補足
* `scanf` 及び `cin` の動作実験は、[こちらのページ](https://gist.github.com/Takym/a7e1f9236f2de11aa0c48d733422a565)をご参照ください。
* 上記のコードは GCC で検証しました。VC など他のコンパイラでは異なる動作をする場合があります。
</details>
--------
## その他
### **質問**: 立方体を回転させるプログラムの作り方を教えてください。
回答: こちらのページを参考にしてください。
(書きかけ...)
--------
### **質問**: 自作 OS を強制終了させても大丈夫でしょうか?
回答: OS の仕組みに依りますが恐らく問題ありません。
「[はりぼてOS]」や「[MikanOS]」等を参考に開発した OS であれば強制終了しても問題は無いでしょう。
UI 上から電源を切る機能(シャットダウン)がない場合(UI が破損している場合を除く)は殆どの場合は問題無いと考えても良いと思います。
強制終了して PC が壊れる原因としては下記の様な原因が挙げられます:
* **常にハードディスクを読み書きしている場合**
* 強制終了すると、ハードディスクの読み書きが中断され、保存されているデータが壊れる可能性があります。
* **一時ファイルやキャッシュが正しく破棄されない場合**
* 終了時に一時ファイルやキャッシュを破棄する必要がある場合、次回起動時にエラーが発生する可能性があります。
* **動作中に設定を変更した場合**
* 終了時に変更された設定を整理し保存する様な仕組みになっている場合、次回起動時に変更した設定が失われる可能性があります。
* **プログラムの更新中に終了した場合**
* 正しくないプログラムが保存され、誤動作を引き起こす可能性があります。
* または、OS そのものが起動しなくなる可能性もあります。
因みに、筆者([@Takym])は [Windows 10] で強制終了を行った事がありますが、壊れた事はありません。
強制終了しても壊れない OS の開発に挑戦してみるのも良いと思います。
--------
[@Takym]: https://github.com/Takym
[`#pragma pack`]: https://docs.microsoft.com/cpp/preprocessor/pack?view=msvc-160
[GDB]: https://www.gnu.org/software/gdb/
[Microsoft Visual C]: https://docs.microsoft.com/cpp/?view=msvc-160
[MikanOS]: http://zero.osdev.jp/
[QEMU]: https://www.qemu.org/
[Scanf#異常な入力が行われた時の処理]: https://ja.wikipedia.org/wiki/Scanf#%E7%95%B0%E5%B8%B8%E3%81%AA%E5%85%A5%E5%8A%9B%E3%81%8C%E8%A1%8C%E3%82%8F%E3%82%8C%E3%81%9F%E6%99%82%E3%81%AE%E5%87%A6%E7%90%86
[Unicode]: https://ja.wikipedia.org/wiki/Unicode
[UTF-8]: https://ja.wikipedia.org/wiki/UTF-8
[UTF-16]: https://ja.wikipedia.org/wiki/UTF-16
[UTF-32]: https://ja.wikipedia.org/wiki/UTF-32
[Windows 10]: https://ja.wikipedia.org/wiki/Microsoft_Windows_10
[X Window System]: https://www.x.org/wiki/
[巡回冗長検査]: https://ja.wikipedia.org/wiki/%E5%B7%A1%E5%9B%9E%E5%86%97%E9%95%B7%E6%A4%9C%E6%9F%BB
[データ構造アライメント]: https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0%E3%82%A2%E3%83%A9%E3%82%A4%E3%83%A1%E3%83%B3%E3%83%88
[はりぼてOS]: http://hrb.osask.jp/