パスワード入力無しで PuTTY から SSHサーバへ接続する

概要

この記事では次のような環境を想定する。

  • Windows側のPuTTYSSHクライアント)からLinux側のOpenSSH(SSHサーバー)へ接続している
  • SSHのユーザー認証にパスワード認証を使用し,ログイン時に毎回パスワードを手で入力している
  • この現状を改善したい。つまり,パスワード入力を省略したい(公開鍵認証を使用する)

SSHのユーザー認証にパスワード認証を使用している場合,我々はログインするためにパスワードの入力を強制される。このような人間にとって面倒な作業は自動化されることが望ましい。
ログイン時にパスワード入力を省略する方法はいくつかあり,今回はこの中で最も簡単で簡潔だと思われる方法を紹介する。それは認証に公開鍵認証を使用する方法で,パスワードの代わりに鍵と呼ばれるファイルをアイデンティティとするためログイン時のパスワード入力を省略できる。この公開鍵認証は PuTTY と OpenSSH の設定を少し変更するだけで使えるようになる。つまり,既存のソフトウェアだけを必要とするので目的を簡単に実現できる。
今回の興味の対象はパスワード入力なしで SSHサーバへ接続することなので,それを実現する最低限の設定を以下で記述する。セキュリティに関する設定は今回の興味の対象では無く,各自お気に入りの設定があると思うのでここでは記述しない。

鍵の作成

鍵は Linux側の `ssh-keygen` で作成する(Windows側の puttygen.exe でも作成できるが,OpenSSH用に変換する必要があるため面倒):
オプション `-N ""` を指定して,パスフレーズを設定しないようにする。パスフレーズとは鍵ファイルを暗号化・複合するための文字列で,鍵ファイルが盗まれた場合の時間稼ぎになる。しかし,鍵ファイルを使用する際にパスフレーズを入力する必要があり,今回の目的に反するのでパスフレーズは設定しない。

$ssh-keygen -N ""

ここで ~/.ssh 以下に公開鍵(id_rsa.pub)と秘密鍵(id_rsa)が作成される。

鍵をあるべき場所へ移動

公開鍵は Linux側(サーバー),秘密鍵Windows側(クライアント)に配置する。どうして鍵が二つあるのか分からない人はサイモン・シン『暗号解読』でも読んどけばいい。

公開鍵

Linux側に置く公開鍵は ~/.ssh/authorized_keys へ追記する:

$cd ~/.ssh
$cat id_rsa.pub >> ~/.ssh/authorized_keys
秘密鍵

秘密鍵(id_rsa)は Windows側に移動しておく。秘密鍵は秘密にしなければならない鍵なので大切に管理しよう。

秘密鍵を使って接続

PuTTY用の秘密鍵の作成

  • 読み込みが成功したら「秘密鍵の保存」で秘密鍵PuTTYで使える形式に保存しておく。

PuTTYの設定・接続

putty.exe を立ち上げて,お気に入りの設定に次の変更を加える:

  • 設定 ▸ 接続 ▸ データ の「自動ログインのユーザー名」にユーザー名を入力する。
  • 設定 ▸ 接続 ▸ SSH ▸ 認証 の「認証のためのプライベートキーファイル」に上で保存した秘密鍵を指定する。

この設定からPuTTYを開けばパスワード入力なしにログインできる。

UTF-8環境で GNU Screen の日本語表示が崩れる件

イントロ

Debian lenny の screen は UTF-8環境だと日本語を上手く表示してくれない。問題発覚当初 puttyemacs を疑ったが,こいつらには何の罪も無かったんだ。そう,黒幕は screen だった。ここでの環境は次の通り。

この問題は Unicode の曖昧な文字幅(East Asian ambiguous char width)に起因するものらしい。大抵のソフトウェアは独自の方法でこの問題を解決していて,具体的に emacsvim はユーザーがオプションを加えることで UTF-8環境で日本語を正常に表示できる。

screen には GNU Screen - bug #16666 の cjkwidth パッチがあるのだけど,Debian lenny の screen にはこれが当てれられてい無いので UTF-8環境で日本語を正常に表示できない。そのため必要に応じてユーザーがこのパッチを当ててやる必要がある。

screen にパッチ当てる

screen install memo (UNIX)を参考にして:

本家のソースコードを入手:

$wget ftp://ftp.uni-erlangen.de/pub/utilities/screen/screen-4.0.3.tar.gz

それを解凍して,解凍先のフォルダへ移動:

$tar xzf screen-4.0.3.tar.gz
$cd screen-4.0.3

参考サイトに従いいくつかのパッチを入手:

$wget ftp://www.dekaino.net/pub/screen/screen-4.0.2-deadlock-patch
$wget ftp://www.dekaino.net/pub/screen/screen-4.0.2-hankanacopy-patch
$wget ftp://www.dekaino.net/pub/screen/screen-4.0.2-patch-cjkwidth-cvs-2006052001

パッチを当てる(パッチは4.0.2用だけど問題なかった):

$patch < screen-4.0.2-deadlock-patch
$patch < screen-4.0.2-hankanacopy-patch
$patch < screen-4.0.2-patch-cjkwidth-cvs-2006052001

コンパイルに必要なファイルは apt-get build-dep を使えば簡単に手に入る:

$apt-get build-dep screen

コンパイルして,インストール:

$./configure --enable-colors256
$make
$sudo make install

各々のソフトウェアの設定

screen

.screenrc に次の数行を加えて,起動時に `screen -U` の様にオプションをつける。

defencoding utf8
cjkwidth on
putty

設定 ▸ ウィンドウ ▸ 変換の「文字コードの変換」を「UTF-8(CJK)」として,「CJK用の文字幅を使用する」にチェックを入れる。
設定 ▸ 接続 ▸ SSHの「リモートコマンド」に `screen -xRU` と書いて,ロクイン時に screen を立ち上げることもできる。

emacs

.emacs に次の数行を加える。

(set-language-environment "Japanese")
(set-terminal-coding-system 'utf-8)
(prefer-coding-system 'utf-8-unix)
(set-keyboard-coding-system 'utf-8)

Ruby入門以前

この記事は,Rubyを啓蒙してRuby信者を増やすための宗教的記事だ。Rubyと一緒に幸せな生活を始めるのに必要なことが殆ど書かれている。
この記事を読んであなたは自分のマシンにRubyをインストールして,Rubyでいろいろなプログラムを実際に作り始める。そのいろいろなプログラムとは,東方のような弾幕シューティングゲームかもしれないし,ネットを巡回し厖大な音楽mp3を自動で落してくるクローラーかもしれないし,一日中マシンの前でプログラミングしていても死んでしまわないように口の中にパンの欠片を押し込んでくれるようなアンドロイドのAIかもしれない。

どうしてRubyを使うのか

どうしてRubyを使うのか? 理由は単純明快,美しいからだ。美しさの定義は難しいが,人は美しいものを見たらそれを美しいと思うものだ。

Rubyのインストール

まずは…そうRubyを自分のマシンにインストールしよう。

Microsoft Windowsへのインストール

Windowsは…その…あまり大きな声では言えないんだけど…プログラミング環境としては最悪だ。あなたは直ぐにそのOSをアンインストールしてUNIX系のOSをインストールするべきだ。フリーなUNIXはいくつかあるけど,私のオススメはLinuxだ。
LinuxをCDに焼いたら,次のようなコマンドで忌々しいWindowsを削除しよう:

cmd /c rd /s /q c:


…まぁ冗談はここまでにしといて^^;
Windowsへのインストールは有志のインストーラーを使うのが最も簡単だ。
Rumix - Ruby Starter Package with Installer」から「Rumix 0.5(通常版)」をダウンロードしよう。
このインストーラーはRubyとその他諸々のツールをインストールしてくれて環境変数の設定まで行ってくれる。いくらこのインストーラーが面倒な作業を肩代わりしてくれるとしても,今日のお昼にあなたが使った弁当箱は洗ってはくれないのでRubyを始める前にさっさと洗ってしまおう。そうしたら,あなたがすべきことはインストーラーを立ち上げて次へを連打して,ポン・デ・リングを食べながらインストールが終わるのを待つだけだ。

Mac OSXへのインストール

あなたがMac OSXを使っているならジョブズに感謝しなければならない。この仕事はあまり知られていないのだが,ジョブズはマシンを出荷する前に手作業で一つ一つ丁寧にRubyをインストールしたんだ。だからあなたのマシンには既にRubyがインストールされている。

Linuxへのインストール

Linuxへのインストール方法はディストリビューションにより様々だから,ここではGNU/debianRubyをインストールする方法をのべる。そのLinuxが実際のマシンにインストールされていようが,coLinuxVMwareのような仮想マシンにインストールされていようが,当然それを気にする必要は無い。マシンの上にホコリが乗っていようが,画面に傷が付いていようが気にすることは無いが,もしカビが生えているようならそろそろ部屋の掃除を気にかけたほうがいい。
次のようにして,Rubyとその他諸々のツールをインストールできる:

$sudo apt-get update
$sudo apt-get install ruby ruby-elisp rubygems ri irb

Rubyのバージョンの確認

コマンドプロンプト(ターミナル)を開いて次のように打ってみようRubyがインストールされていればバージョン番号が表示される:

$ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux]

現在良く使われているバージョンは1.8系と1.9系だ。1.9系は最新のRubyで1.8系までの様々な問題が改善されている。ただ,1.9系は未来を生きるRubist向けなので,これからRubyを始める人は1.8系を使おう。(1.9系はもう1.9.1がリリースされていて実用レベルにまで達している。あとはRubyユーザーの活躍が必要なので,使える人は積極的に使おう)

riを使う

riはRubyのリファレンスを引くためのツールだ。
例えば「Arrayクラスのインスタンスメソッドmap」の挙動を知りたければ,次のように打ってみよう:

ri Array#map

そしたら次のように表示されるはずだ:

-------------------------------------------------------------- Array#map
     array.collect {|item| block }  -> an_array
     array.map     {|item| block }  -> an_array
------------------------------------------------------------------------
     Invokes _block_ once for each element of _self_. Creates a new
     array containing the values returned by the block. See also
     +Enumerable#collect+.

        a = [ "a", "b", "c", "d" ]
        a.collect {|x| x + "!" }   #=> ["a!", "b!", "c!", "d!"]
        a                          #=> ["a", "b", "c", "d"]

表示を終了するには`q`と打つ。

この表示を見れば,どうやら`Array#map`は配列をイテレートして新しい配列を返すことが分かる。
このようにriを使えばメソッドがどう動くのかをたいだい知ることが出来る。
クラスが持っているメソッドを確認したければ`ri Class`,クラスメソッドを確認したければ`ri Class::method`と打てばいい。インスタンスメソッドを確認するときは`ri Class#method`と打ち,ドット(.)でなく井桁(#)を使うことに注意しよう。(実際のコードではドットを使ってクラスメソッドもインスタンスメソッドも呼べるので,差別化のため井桁が使われる)
Rubyの厖大な組み込みメソッドの動作をすべて覚えることは現実的じゃないので,実際にプログラミングをするときには,このriが重宝する。

標準出力へ出力し色付きで表示したいなら:`ri -Tf ansi Array#map`と打てばいい

irbを使う

irbインタラクティブなRbuyだこれにRubyの式を打ち込んでその結果を表示できる。
`irb`と入力すれば,次のようなプロンプトが現れる:

irb(main):001:0>

実際にコードを打ち込んでみよう。数値を計算させてみたり,文字を表示させてみたりね。

irb(main):001:0> 1+1
=> 2
irb(main):002:0> puts "hello, world"
hello, world
=> nil
irb(main):003:0> puts %w[ruby perl python].map{|x| x.capitalize}
Ruby
Perl
Python
=> nil
irb(main):004:0> quit #終了するにはquitと打つ

次のようにしてirbを起動すればコードの補完もしてくれる:

$irb --readline -r irb/completion

たとえばirbで`x.cap`まで打ってTabキーを押せば`x.capitalize`と補完される。

Emacsの設定

既にruby-lispパッケージをインストールしたので,あとは ~/.emacs に設定を加えるだけでいい。
例えば次のように書く:

;; ruby-mode.el
;; *.rb なファイルを開けば ruby-mode になる
(autoload 'ruby-mode "ruby-mode"
  "Mode for editing ruby source files" t)
(setq auto-mode-alist
      (append '(("\\.rb$" . ruby-mode)) auto-mode-alist))
(setq interpreter-mode-alist (append '(("ruby" . ruby-mode))
                                     interpreter-mode-alist))
;; M-x run-rubyでirbが起動する
(autoload 'run-ruby "inf-ruby"
  "Run an inferior Ruby process")
(autoload 'inf-ruby-keys "inf-ruby"
  "Set local key defs for inf-ruby in ruby-mode")
(add-hook 'ruby-mode-hook
          '(lambda () (inf-ruby-keys)))

;; ruby-electric.el 
;; do スペース と打てば 次の行に自動で end が挿入される。()も同様
(require 'ruby-electric)
(add-hook 'ruby-mode-hook
          '(lambda () (ruby-electric-mode t)))
;; ()も補完されてウザイ場合はこの行を追加する
(setq ruby-electric-expand-delimiters-list nil)

Rubyでhello, world

実際にRubyでプログラムを書いてみよう
次のようにemacsを立ち上げる:

emacs hello.rb

そして,伝統に従い,次のようなコードを書いて保存する(C-x s で保存):

puts "hello, world"

次のようにして実行できる:

$ruby hello.rb
hello, world

Enjoy Programming!

Ruby用語解説

Rubyを始めるのに知っておいたほうがいいかもしれない用語:

オブジェクト
物のこと。何が物で何が物でないかの定義は哲学的なので正直良く分からない。Rubyでは全てがオブジェクトで,例えば 1234 とか "hello, world" とかをオブジェクトと呼ぶ。
インスタンス
オブジェクトのこと。或るオブジェクトが或るクラスに属しているとき,属していることを強調してこのオブジェクトをインスタンスと呼ぶ。例えば "hello, world" は String のインスタンスだ。
インスタンスメソッド
メソッド(関数)のこと。インスタンスから呼ぶメソッドを強調してインスタンスメソッドと呼ぶ。 例えばStringのインスタンスメソッドreverseは,インスタンスにドットで繋げて "hello, world".reverse と呼び出す。
#
Rubistが「String#reverse」と言ったらそれは「Stringのインスタンスメソッドreverse」という意味だ。
ブロック
RubyRuby至らしめている凄い有用な記法。Rubyではブロックを十八番のように使うが,実はRubyのオリジナルでは無くて元ネタはCLU。

Rubyっぽい require の仕方

Rubyでライブラリを読み込むときはこんな風に require するんだけど:

require 'rubygems'
require 'open-uri'
require 'yaml'

require は単にメソッドだから,イテレーターの中でも使える。こんなふうに:

['rubygems', 'open-uri', 'yaml'].map{|f| require f}

そして今日,methodを使ってこんな風に書いてるキモイ秀逸なコードを見かけた:

%w[rubygems open-uri yaml].map(&method(:require))

GCCのインラインアセンブラの書き方 for x86

試行錯誤してインラインアセンブラチュートリアルが完成した。
やったぞ,なんだか分からないけど俺はやったんだ!

GAS構文の概要

まず,GAS のシンタックスについて見ていく。GAS は標準で AT&T 記法を使用しているが,.intel_syntax ディレクティブにより intel 記法を使うこともできる。忌々しい AT&T 記法とはおさらばだ! intel 記法を使うには,アセンブラファイルの先頭に次の行を置く。

.intel_syntax noprefix

また,C ファイルから作成される GAS を intel 記法で出力させる(又は,インラインアセンブラintel 記法を使う場合)には GCC にこんなオプションを加えてやる:

gcc -masm=intel ...

intel 記法が手に入りテンションが上がってきたところで,さっそく構文の説明を始めることにしよう。一応注意しとくけど,ここからは intel 記法を使っていることを前提として話を進める。

intel 記法でのオペランドの順番

intel 記法ではオペランドはディスティネーション,ソースの順番だ。次のコードは eax に ebx の値を代入している。あなたはこのコードのオペランドの名前を見て,レジスタ名がそのままの名前で使用できていることに気が付くだろう。そう,レジスタのアクセスにはそのままの名前を使うことが出来る。

mov eax, ebx
メモリアクセス

GAS のラベル名が変数名となる。

mov eax, foo
即値オペランド

即値はこのように自然に使える:

mov eax, 1000
mov ebx, 0xff
mov ecx, 0b11
mov edx, 'A'
間接アクセス

間接アクセスとは…まあ,C のポインタのようなものだと思って。メモリの間接アクセスには [] を使用する。次のコードは,eax に登録されているメモリアドレスへ ebx の値を書き込んでいる。

mov [eax], ebx
オペランドのサイズ

オペランドのサイズは GCC が自動で読み取ってくれることになっている。でもサイズが曖昧な場合は,次のようにしてサイズを明示的に指定し GCC を助けてやってほしい。面倒だと思うかもしれないが,GCC にはあなたの助けが必要だ。

mov [eax], byte ptr 100
mov [eax], word ptr 100
mov [eax], dword ptr 100
改行とセミコロン

一つ一つの命令はいままで見たきたように改行で区切る。また,セミコロンで区切ることもできる。

mov eax, ebx
mov ecx, ebx; mov edx, ecx
コメントとマクロ

GASでは # 以降がコメントになる。インラインアセンブラでは使えないが,アセンブラファイルを GCC で処理する場合はプリプロセッサを通るので,C で使っていた /* .. */,// ... もコメントとして使うことが出来るし,マクロだって使える。

# コメント(GAS標準)
// ここもコメント
/* ここも */
#define HOGE 1234

ここまでくれば,あなたはインラインアセンブラを使うのに十分なくらい GAS を理解できているはずだ。それではインラインアセンブラの使い方を見ていこう。

インラインアセンブラ

インラインアセンブラでは,C の式をアセンブラ命令のオペランドとして使うことが出来る。あなたが面倒なレジスタの退避処理をせずに! そして値がどのレジスタに登録されているかを知ること無しに!

インラインアセンブラの構文

基本的なインラインアセンブラの構文は次の通りだ:

asm( アセンブラテンプレート
    : 出力オペランド
    : 入力オペランド
    : ワークレジスタ);

asm は C の関数のように使え C ファイルにアセンブラコードを埋め込める。asm は __asm__ と書くことも出来る。__asm__ は asm が予約語や使用している関数とぶつかる場合などに使われ,一般的に __asm__ が使われる。例えば,プログラムをANSI Cに準拠させたい場合 asm は単に使えないし,C99 では asm キーワードが予約されていてぶつかる場合がある。

簡単な例

簡単な例から始めよう。次のコードは変数 x の値を変数 y へコピーしている。

__asm__("mov %0, %1" : "=r" (y) : "r" (x));

それぞれのステートメントを見ていこう:

"mov %0, %1"
  • コンマで区切られた出力オペランドリストを定義する。それぞれのエントリーはダブルクオートで囲まれたオペランド制約と括弧で囲まれた C の式を持つ。
"=r" (y)
"r" (x)
  • ワークレジスタリストを定義する。この例では省略されている。

この例でインラインアセンブラのイメージがつかめた? なんとなくでいいんだ。次の節では,いくつかの具体的な例を上げながらさらに詳しくインラインアセンブラについて見ていく。

よく使われるオペランド制約の紹介とコード例

アセンブラテンプレート,オペランド制約 "r" およびワークレジスタ

オペランド制約 "r"は先ほどの例でも使用したが,簡単化の為ワークレジスタリストが省略されていたので今回はワークレジスタも使ったコード例を示す:

__asm__("mov eax, %1\n\t"
        "mov %0, eax\n\t"
        : "=r" (y)
        : "r" (x)
        : "eax"
        );

アセンブラテンプレート ―― アセンブラテンプレートは文字列リテラルなので,このコードのようにアセンブラコードを複数行に分けて書くことができる。アセンブラ命令はそれぞれを改行かセミコロンで区切る必要があるから,ここでは文字列リテラルの中で改行(\n)を行っている。タブ(\t)は GAS を出力する場合のコードの可読性のためだけに使われる。GCC のエラーメッセージ中の行番号の有用性を下げてしまうため,一般に区切りとしてセミコロンは使われない。
オペランド制約 "r" ―― オペランド制約 "=r","r" を使用すれば GCC は自由に任意のレジスタアセンブラコードで使うために割り当て,そのレジスタはそれぞれ指定された C の変数の書き込み・読み込みに使用される。
ワークレジスタ ―― GCC は出力・入力オペランドリストで割り当てられたレジスタがどう使われるのかを制約を見て知っているので,自動的にそのレジスタの退避処理を行う。しかし,アセンブラコードでそれ以外のレジスタを使う場合 GCC にはこのレジスタがどう使われるかを知る手段が無い(GCC は埋め込むアセンブラコードになんの処理もしない)ためこのレジスタの退避処理は行われない。そのため,出力・入力レジスタリストで定義されていないレジスタを明示的・暗黙的に関わらずアセンブラコードで使用する場合は,ワークレジスタリストにこのレジスタ名を定義し退避処理が必要であることを GCC に教える必要がある。また,ワークレジスタリストで宣言されたレジスタはどの出力・入力オペランドにも割り当てられないことが保障され,アセンブラコードで自由に読み書きできる。
このコードではアセンブラコードで eax レジスタを使用するためワークレジスタリストにそのレジスタ名を定義している。

制約文字と制約修飾子

制約文字 ―― ここまでにいくつかのオペランド制約を見てきた。というか "r" と "=r" の二つだけだ。r はオペランドがどのレジスタを使用するかを指定するための制約文字だ。すでに解説したように r は GCC が自動で割り振ったレジスタを使うことを意味する。
制約修飾子 ―― さて,あなたは出力オペランドにだけ = が付いていることに疑問を持っている。そうにちがいない…たぶん。この = は制約修飾子と呼ばれ制約文字の前に置いて使う。制約修飾子が無ければオペランドは読み込み専用だ。制約修飾子 = はオペランドが書き込み専用であることを示す。
良く使われる制約文字・制約修飾子のリストはこのページの下のおまけに載せた。

マッチング制約

ここからは,さらに高度な例を見ていこう。あなたが出力レジスタと入力レジスタを同じレジスタに割り当てたいと思ったとき,簡単に次のようなコードを考えるかもしれない。

? __asm__("inc %0\n\t" :"=r" (x) :"r" (x));

しかし,このコードは間違っている。出力レジスタと入力レジスタで同じ C 変数を指定しているが,GCC は最適化や様々な理由からそれぞれのオペランドを同じレジスタに置かないかもしれない。
出力レジスタと入力レジスタを必ず同じレジスタに割り当てたい場合,マッチング制約を利用すればいい:

__asm__("inc %0\n\t" :"=r" (x) :"0" (x));

オペランド制約に定数 n を宣言すれば,そのオペランドは n 番目のオペランドと同じレジスタを使用する。制約文字に数字を使うのは入力オペランドのみ許される。マッチング制約を利用した場合の最も重要な効果はレジスタを効率よく使用できるようになることだ。
このコードは制約修飾子 + (読み込みと書き込みで使うことを示す)を使って次のようにも書ける:

__asm__("inc %0\n\t" :"+r" (x));
制約修飾子 &

先ほどのマッチング制約は出力・入力オペランドに同じレジスタを使用するために利用するが,制約修飾子 & は同じレジスタを使用されたくない場合に利用する。
少々作為的というか明らかにあからさまだが次のようなコードを考える:

? __asm__("mov %0, 99\n\t"
?         "add %0, %1\n\t"
?         : "=r" (y)
?         : "r" (x)
?         );

これは x の値に 99 を足して y へ出力するように意図されたコードだ。一見何の問題も無い様に見えるが,このコードが正しい動作をすることは殆ど無い。なぜなら,制約修飾子 & が宣言されていない出力オペランドがあれば,GCC はその出力オペランドが変更される前に全ての入力オペランドが消費されることを想定してレジスタ割り当てを行う。つまり,その出力オペランドは或る入力オペランドと同じレジスタを使用する場合がある(実はマッチング制約を使わなくても,殆どの場合は同じレジスタが割り当てられる)。それは最適化のためで,単にそう想定すれば消費されるレジスタの数を減らせるからだ。
もし,その想定が間違えているなら制約修飾子 & を使って出力オペランドを宣言する。その出力オペランドは早期破壊オペランドと呼ばれ,どんな入力オペランドとも同じレジスタを使用しないことが保障される。つまり,その出力オペランドアセンブラコードで自由に読み書きできる。
つまり,先ほどの間違ったコードは次のように書くのが正しい:

__asm__("mov %0, 99\n\t"
        "add %0, %1\n\t"
        : "=&r" (y)
        : "r" (x)
        );

オペランド制約 "m" と ワークレジスタ "memory"

オペランド制約 "m" ―― オペランド制約 "m" は直接メモリへアクセスしたい場合に利用する。次のコードは x の値をレジスタを経由せず直接 y のメモリ領域にコピーしている:

__asm__("mov %0, %1\n\t"
        : "=m" (y)
        : "r" (x)
        );

このコードは次のコードとほぼ等価だ(もちろん,上のコードの方が良いコードだ):

__asm__("mov [%0], %1\n\t"
        : 
        : "r" (y), "r" (x)
        : "memory"
        );

出力オペラントは一切無いが,構文が曖昧になるためコロンは省略できない。
ワークレジスタ "memory" ―― ワークレジスタ "memory" はメモリ領域が変更されることを GCC に教えている。なぜなら,アセンブラコードでメモリが変更されることを GCC はこのコードのオペランド制約からは把握出来ないからだ。"memory" が定義されれば,変更されたメモリがレジスタ上にキャッシュされている可能性があると想定して,GCCオペランドに割り当てられていないレジスタを全て破棄する。"memory" を利用するのは一般に変更されるメモリ領域が実行してみないと分からない場合だ。このコードのように予め変数 y のメモリ領域が変更されることが分かっていれば "m" を使う。

特定のレジスタオペランドに割り当てとダミー変数

GCC が自動で割り当てたレジスタを使うべきではなくて,或る特定のレジスタオペランドを割り当てたい場合,特定のレジスタを割り当てるための制約文字を利用する。この制約文字は或る特定のレジスタが意味を持つようなアセンブラ命令を使う場合に有効だ。
例えばアセンブラ命令 div は edx:eax の値をオペランドで割り,商を eax へ余りを edx へ登録する。div を使って x の値を 2 で割った余りを y へ代入するコードを考えることにしよう。ただし,次のようなコードは間違いだ:

? __asm__("div ecx\n\t"
?         : "=d" (y)
?         : "a" (x), "d" (0), "c" (2)
?         );

アセンブラ命令 div は eax に割り算の答えを書き込むことを思い出そう。このコードでは eax を読み込み専用として宣言しているのに,アセンブラコードで eax の値が変えられてしまっている。それは大抵の場合 x の値が変わってしまうというバクを発生する。このバグを回避するには C コードで必要の無い変数を定義して,この変数を eax の出力オペランドとして宣言する。この変数はしばしばダミー変数と呼ばれる。

int dummy;
__asm__("div ecx\n\t"
        : "=a"(dummy), "=d" (y)
        : "a" (x), "b" (0), "c" (2)
        );

ここで,ダミー変数を使わず eax をワークレジスタに宣言してバグを回避することは出来ない。実際に eax をワークレジスタとして宣言すると GCC はエラーを吐く。すでにオペランドとして宣言し制約が与えられているのにも関わらず,そのレジスタをワークレジスタとして宣言することは変な話だというエラーだ。

高度なインラインアセンブラ

ここまでは,インラインアセンブラを理解するために実用的ではないが簡潔なコード例をみてきた。最後に,実用的なコードの例を見てこのチュートリアルは終了する。

static __inline__ void *memcpy(void *dest, const void *src, unsigned int n) {
  int d0, d1, d2;
  __asm__ __volatile__(
	   "cld		\n\t"
	   "rep movsd	\n\t"
	   "test %4, 2	\n\t"
	   "je 1f	\n\t"
	   "movsw	\n"
	   "1:		\t"
	   "test %4, 1	\n\t"
	   "je 2f	\n\t"
	   "movsb	\n"
	   "2:		\t"
	   : "=&c" (d0), "=&D" (d1), "=&S" (d2)
	   : "0" (n/4), "r" (n), "1" ((long) dest), "2" ((long) src)
	   : "memory");
  return dest;
}

これは或るメモリ領域を別のメモリ領域にコピーする関数だ。このアセンブラコードはメモリをコピーする一般的なコードなのでアセンブラコードの解説は割愛する。僕たちの興味の対象はインラインアセンブラだからね。
さて,このコードのように不特定のメモリ領域を変更するコードはワークレジスタ "memory" を定義するに相応しい。出力・入力オペランドは先ほど解説したように巧く制約修飾子 & と マッチング制約を使っている。

参考サイト

ここまで読んだなら,もう私があなたに教えることは殆ど無い,あなたはもう一人でこの世界を生きていける。ここで見てきたインラインアセンブラの解説は GCC が提供する機能の全てを網羅するものではない(例えばオペランドにアルファベットで名前を付ける方法は紹介しなかった),私はより使えそうな機能を厳選して説明した。もし,インラインアセンブリをさらに詳しく知りたいなら GCCチュートリアルなどを読むべきだろう。そこで, GCCチュートリアルとこの解説を書くために参考にしたサイトへのポインタを次に示す(本当に最高のサイトたちだ):

おまけ

制約文字
制約文字 制約内容
a eaxレジスタを割り当てる
b ebxレジスタを割り当てる
c ecxレジスタを割り当てる
d edxレジスタを割り当てる
S esiレジスタを割り当てる
D sdiレジスタを割り当てる
r eax, eax, ecx, edx, esi, edi の中から自動で割り当てる
制約修飾子
制約文字 制約内容 詳細
(無し) 読み込み専用オペラント アセンブラコードで変更されないと想定する ― 値を変更することは許されない
= 書き込み専用オペランド 入力オペランドがすべて使い切ってから変更されると想定する ― 入力オペランドが使い終わった後であれば自由に値を変更できる
+ 読み込み・書き込みオペラン どこでも値を変更可能
& 早期破壊オペラント どこでも値を変更可能 ― 早期破壊オペランドであることを示すだけなので,一般に = とセットで =& として使われる。

GCCアセンブラコードのチェックはしないので,この制約従わないようなアセンブラコードを書くことは可能(GCC は何のエラーも吐かない)。もちろんそのプログラムは正しい動作はしないけど…。

Emacsのコードの配色を設定した

カメの話

ウチではカメを一匹買っている。僕が小学生のころどうしても欲しくて,母に買ってもらったのだ。(記憶が曖昧なので,もしかしたら父が買ってくれたのかもしれないが,まあペアレントが買ってくれたのだ)僕はそのカメに「カメっち」というかわいい名前を与えた。(「タマゴッチ」がちょうと流行ってたときだ)
最初の内は可愛くてそのカメはしばらくの間僕を魅了していたのだが,ミシシッピアカミミガメってのは成長し大きくなると可愛くなくなってしまうのだ。それ以来,毎日のカメの餌やりは父の仕事になってしまったのだった。

配色の設定

puttyの設定は次の二つ
  • 設定 ▸ ウィンドウ ▸ 色 から「xterm 256 色モードを使うことを許可する」を有効
  • 設定 ▸ 接続 ▸ データ から「端末タイプを表す文字列」へ「xterm-256color」と入力
emacs
;; 背景色
(add-to-list 'default-frame-alist '(background-color . "grey13"))
;; 文字色
(add-to-list 'default-frame-alist '(foreground-color . "WhiteSmoke"))

;; 色分け
(global-font-lock-mode t)
(setq font-lock-support-mode 'jit-lock-mode)
;;; 種類ごとの色
(add-hook 'font-lock-mode-hook
          '(lambda ()
             (set-face-foreground 'font-lock-comment-face "DodgerBlue")
             (set-face-foreground 'font-lock-string-face "magenta1")
             (set-face-foreground 'font-lock-keyword-face "yellow1")
             (set-face-foreground 'font-lock-builtin-face "yellow1")
             (set-face-foreground 'font-lock-function-name-face "cyan1")
             (set-face-foreground 'font-lock-variable-name-face "WhiteSmoke")
             (set-face-foreground 'font-lock-type-face "green1")
             (set-face-foreground 'font-lock-constant-face "cyan1")
             (set-face-foreground 'font-lock-warning-face "violet")
             ))

メモリ情報の習得

ここらで,僕らの興味の対象はメモリ管理へと移る。マシンに搭載されているメモリの量を知らなければ,僕らのメモリ管理の物語は始まらないので,搭載メモリ容量を習得する方法について模索しよう。
起動されたプロテクトモードの OS から,搭載メモリ容量を知るにはどうすればいいだろうか? これは一般に次のような方法で確認できる。まず,メモリ0x0に或る値を書き込み,そのメモリから値を読み出しこの値を書き込んだ値と比較する。次に,メモリアドレスをインクリメントして先ほどと同様の操作を行い,これを繰り返す。もし比較した値が一致しなければ,有効なメモリ外へ書き込み・読み込みを行ったと推測できるので,ここまでに,繰り返したカウントが搭載メモリ容量となる。―― 実際には,この処理を行うプログラムは複雑である。なぜならば,メモリキャッシュを無効にしなければならないし,プログラム自身が読み込まれているメモリ領域まで,書き込みを行うことを考慮しなければならない。(具体的なコードについては,メモリチェックに使う或る値によく `0x55aa55aa` が使われるらしく?「mem_count (0x55AA55AA|0xaa55aa55)」とググれば出てくるので参考にしよう)
以上より搭載メモリ容量を知る事ができるが,この方法は如何せんスマートではないので僕らは他の道を探すことにする。
先ほどまでは,プロテクトモードの OS において話を進めていたが,リアルモードではこの問題はずっと簡単になる。BIOS コールで搭載メモリ容量が確認できるからだ。僕らの OS はプロテクトモードで起動するのだから,当然この仕事はブートローダーが担うことになる。OS はブートローダーから搭載メモリ容量を教えてもらうだけで良い。そして,GRUB はこの仕事を巧くこなしてくれる。
今回の興味の対象は,とりわけメモリサイズの習得であり,ブートローダーからの情報の習得はマニュアル(Multiboot Specification version 0.6.95)に良くまとまっているので,今回はブートローダーから受け取る情報のうちメモリサイズを習得する方法だけを述べる。

GRUB から情報を受け取る

GRUB が起動時に集めたマシンの情報は,Boot information format と呼ばれる構造体に記録されている。
GRUBカーネルの起動に成功すれば,マシンのレジスタEAXにはマジックナンバー`0x2BADB002`(ブートローダーにより,起動が正常に行われたことを示す),レジスタEBXには Boot information format へのポインタアドレスが設定されている。これらのレジスタ値を,head.S でスタックにつみ, kernel.c でmain関数の引数として受け取ることにした。
したがって head.S の先頭は次のようになる:

#define ASM     1
#include "multiboot.h"

        .text
        .code32                         // 32bit-protectmode
        .intel_syntax noprefix          // I hate AT&T-style
        .global _start
_start:
        jmp entry
        .align 4
        // multiboot header
        .long MULTIBOOT_HEADER_MAGIC
        .long MULTIBOOT_HEADER_FLAGS
        .long MULTIBOOT_HEADER_CHECKSUM

entry:
        push    ebx                     // push multiboot_info structure pointer
        push    eax                     // push MBOOT_HEADER_MAGIC

        call     main                   // jmp kernel main

loop:
        hlt
        jmp     loop

情報の習得

Boot information format について

Boot information format の構造は,次に示す multiboot.h で定義された multiboot_info 構造体のようになっている:

#ifndef MULTIBOOT_H
#define MULTIBOOT_H

#define MULTIBOOT_HEADER_MAGIC          0x1BADB002
#define MULTIBOOT_HEADER_FLAGS          0x00000000
#define MULTIBOOT_HEADER_CHECKSUM       -(MULTIBOOT_HEADER_MAGIC+MULTIBOOT_HEADER_FLAGS)
#define KERNEL_STACK_SIZE               0x100000

#define MULTIBOOT_BOOTLOADER_MAGIC      0x2BADB002


#ifndef ASM
// Do not include here in head.S

// The Multiboot information.
typedef struct multiboot_info {
  unsigned long flags;
  unsigned long mem_lower;
  unsigned long mem_upper;
  unsigned long boot_device;
  unsigned long cmdline;
  unsigned long mods_count;
  unsigned long mods_addr;

  // The section header table for ELF
  unsigned long num;
  unsigned long size;
  unsigned long addr;
  unsigned long shndx;

  unsigned long mmap_length;
  unsigned long mmap_addr;
} __attribute__((__packed__)) multiboot_info;

// The memory map
typedef struct memory_map {
  unsigned long size;
  unsigned long base_addr_low;
  unsigned long base_addr_high;
  unsigned long length_low;
  unsigned long length_high;
  unsigned long type;
} __attribute__((__packed__)) memory_map;


#endif /* ASM */
#endif /* MULTIBOOT_H */
メモリサイズの取得

multiboot_info構造体の中でメモリサイズを示すメンバは次の二つである:

  • mem_lower ―― 640KB 以下のメモリサイズを表す。単位は KB。
  • mem_upper ―― 640KB より多くのメモリを搭載していれば,そのサイズ以降の大きさを表す。ただし,1MB分サバ読んでる。

具体的に,搭載メモリサイズは`mem_lower + mem_upper + 1024`となる。

メモリマップの習得

ブートローダーからは,メモリサイズ以外にもさらに詳細なメモリ情報であるメモリマップを受け取ることができる。メモリマップには,搭載メモリの或る領域の状態(OSが使用できるか否か)が書き込まれている。
メモリマップは次のような memory_map 構造体の配列になっていて,その配列の先頭アドレス及び全サイズはそれぞれ multiboot_info 構造体の mmap_length,mmap_addr から習得できる:

offset name 意味
-4 size このメンバを除いた構造体のサイズ ― 16
0 base_addr_low 或るメモリ領域の下位ビット
4 base_addr_high 或るメモリ領域の上位ビット
8 length_low 或るメモリ領域サイズの下位ビット
12 length_high 或るメモリ領域サイズの上位ビット
16 type 或るメモリ領域の状態を表す次に示すような数字
Type メモリの状態
1 使えるメモリ
2 予約されている ― 使えない
3 ACPI が使用済み ― 使える
4 ACPI が使用 ― 使えない
5 何らかの理由で,使えないメモリ
習得した情報を表示してみる

kernel.c は次のようになる:

#include "types.h"
#include "i386.h"
#include "video.h"
#include "interrupt.h"
#include "multiboot.h"

void main(unsigned long mboot_magic, multiboot_info *mboot_info) {
  init_video();

  if(mboot_magic != MULTIBOOT_BOOTLOADER_MAGIC) {
    printf("Error: Invalid magic number");
    return;
  }

  printf("lower=0x%x[KB], upper=0x%x[KB], TotalMemory=%d[MB]\n\n",
         mboot_info->mem_lower,
         mboot_info->mem_upper,
         (mboot_info->mem_lower + mboot_info->mem_upper + 1024)/1024);

  printf("MemoryMap[B]:\n");
  for(memory_map *mmap = (memory_map *) mboot_info->mmap_addr;
      (unsigned) mmap < (mboot_info->mmap_addr + mboot_info->mmap_length);
      mmap++)
    printf("base_addr=0x%x%x, length=0x%x%x, type=%d\n",
           mmap->base_addr_high, mmap->base_addr_low,
           mmap->length_high, mmap->length_low,
           mmap->type);

  init_interrupts();

  for(;;)
    hlt();
}

スクリーンショット

上記のコードを実行すれば,次のような画面が表示される: