はじめに
この記事は低レイヤーについてほとんど知識がない人間がアセンブリ言語を学んでいく際のメモ書きです。学習の際には「低レベルプログラミング」という本を参考書にしています。今回は実際にアセンブリ言語を書く前に必要なIntel x64アーキテクチャにおけるレジスタとその周辺の知識についてまとめました。次回以降、実際にアセンブリ言語を書いていきたいと思います。
目次
- レジスタ
- 汎用レジスタ
- その他レジスタ
- システムレジスタ
- プロテクションリング
- ハードウェアスタック
レジスタ
レジスタ(resister)はCPU自身が持っているメモリセルのこと。
メモリよりも高速にアクセスでき、CPUが頻繁に利用する計算結果をレジスタに保存しておくことで、CPUがメモリチップとデータのやりとりをする際に生じる応答のタイムロスを防ぐことができる。
多くの場合、プログラムのある処理で使用されるデータは、レジスタ群に格納しておき、レジスタを利用して処理・作業をしていく。その後、結果をメインメモリにフラッシュする。
汎用レジスタ
プログラマが使用できるレジスタとして汎用レジスタ(general purpose register)と呼ばれるレジスタがある。
intel x64での汎用レジスタは、それぞれ64bitで、r0からr15までの計16個のレジスタが存在する。うち、r0からr7には別名がついていて、特別な命令(役割)をもっている。
-
r0:別名rax。算術命令で使用される。(aはaccumulatorから) -
r1:別名rcx。ループの回数に使用される。(cはcycleから) -
r2:別名rdx。入出力処理の際にデータを格納する。(dはdataから) -
r3:別名rbx。ベースレジスタ。初期のプロセッサモデルでベースアドレスに使用されていた。(bはbaseから) -
r4:別名:rsp。スタックポインタ。ハードウェアスタックの最も上にある要素のアドレスを格納する。(spはstack pointerから?) -
r5:別名:rbp。スタックフレームのベースポインタ。 -
r6:別名:rsi:ストリング操作コマンドのソース側インデックス。 -
r7:別名:rdi:ストリング操作コマンドのデスティネーション側インデックス。 -
r8...r15:後に追加されたもので、ほとんどが一時的な値の格納に使用される。
これらの汎用レジスタは、全体を使用しなくても一部分だけを使用するということもできる。具体的には下位32bit、下位16bit、下位8bitをアドレッシングすることができる。
アドレッシングする方法は、r6d(r6の下位4バイト)やr4w(r4の下位2バイト)、r1b(r1の下位1バイト)のようにレジスタ名に接尾語を追加すれば良い。
接尾語については
-
d:下位32bit。(dはDouble Wordから) -
w:下位16bit。(wはWordから) -
b:下位8bit。(bはbyteから)
となっている。
またこれとは別に、レジスタの一部分を別名で指定できるものも存在する。
それぞれのレジスタの分解は以下のようになる。
-
raxの分解
-
rbxの分解
-
rcxの分解
-
rdxの分解
-
rsiの分解
-
rdiの分解
-
rbpの分解
-
rspの分解
-
r8...r15の分解
その他のレジスタ
汎用レジスタ以外は基本的にはOSだけに書き換える権限がある。
例外としてripレジスタとrflagsレジスタなどがあり、これらのレジスタはプログラマにもアクセスする権限がある。
-
rip:次に実行するべき命令のアドレスが格納されているレジスタ。 -
rflags:最後に行った演算結果が負であったかどうかやオーバーフローしたかどうかなどのプログラムの現在の状態を反映するフラグ。
この他に並列演算命令のために使用されるレジスタやモデル固有レジスタ(MSR:model-specific register)などがある。
システムレジスタ
一部のレジスタはOSが使用するためだけに特別に設計されていて、アプリケーションからアクセスできないようになっている。このアプリケーション側からは変更できないようにしているという点が特権モードを実現している。
プロテクションリング
アプリケーションの能力を制限するために特権レベル別のリング、プロテクションリング(protection ring)というものが実装されている。各種の命令は1つ以上のリングにリンクされていて、他のレベルでは実行できないようになっていてセキュリティと堅牢性を実現している。例えば、Longモードではcsレジスタとssレジスタそれぞれの下位2ビットにプロテクションリング番号が格納され、これを変更できるのは割り込みまたはシステムコールを発行した際だけである。
Intel x64には4つの特権レベルが設けられているが、実際には最大の特権(リング0)と最小の特権(リング3)の2つのみが使用されている。
ハードウェアスタック
レジスタではスタックを実装するにあたって実際にスタック専用のメモリをハードウェアとして実装しているわけではなく、1個のレジスタ(rsp)を用いてデータ構造をエミュレーションしている。rspにはスタックの一番上の要素のアドレスが格納されていて、pushとpopによって値が書き換わる。
具体的には
- push 引数
- 引数のサイズ(2, 4, 8byte)によって
rspの値から2, 4, 8が減算される。 - 引数がメモリに格納される。
- 引数のサイズ(2, 4, 8byte)によって
- pop 引数
- スタックの一番上の要素がレジスタやメモリにコピーされる。
-
rspに引数のサイズが加算される。
となっている。ここでハードウェアスタックの特徴として以下のような点がある。
- スタックが空の状態は存在し得ない(空の状態でpopが実行されると
rspに存在するアドレスにあるゴミが返される)。したがってハードウェアスタックの要素数は知ることができない。 - スタックはメモリアドレスを負の方向に成長する(アドレス0に向かって成長する)。
次回
こちらになります。








