PyOpenCLで手軽にOpenCLを組む

OpenCLのコードを書こうとすると、ホストコードとカーネルコードの両方を書いてコンパイルして、となかなか大変だったりする。もともとカーネルコードは実行時にコンパイルできる仕様なので、スクリプト言語をとても相性が良いのではないかと思う。それを実現したのがPyOpenCL。

MacLinuxに限っては、以下のように簡単にセットアップできる。

Mac OS X 10.6

  1. MacPortsをインストールし、「sudo port -v selfupdate」しておく
  2. 「port search opencl」してみると、「py26-pyopencl」が見つかるのでインストール
  3. python2.6からpyopenclが使える
  4. これだけ

MacではGPUとCPUがOpenCLの対象デバイスになるので、ほとんどのiMacMacBookではデバイスがふたつ扱えるようになる。手元のiMacでは以下のようなデバイス情報が出た。

NAME: Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz
MAX_COMPUTE_UNITS: 8

NAME: Radeon HD 4850
MAX_COMPUTE_UNITS: 10

Ubuntu 10.10

  1. 「システム管理」「追加のドライバ」からnVidiaの純正ドライバをインストール(たぶんAMDでも)
  2. 「システム管理」「Synapticパッケージ・マネージャ」から「python-pyopencl」をインストール
  3. 標準のpythonからpyopenclが使える
  4. これだけ

Ubuntuのほうには、以下のパスにサンプルが入っている。これはMacのほうに持って行っても、ちゃんと動いた。

/usr/share/doc/python-pyopencl/examples

ひょっとすると次のような例外が発生するかも知れない。

Traceback (most recent call last):
  File "demo.py", line 8, in 
    ctx = cl.create_some_context()
  File "/usr/lib/pymodules/python2.6/pyopencl/__init__.py", line 346, in create_some_context
    return Context(devices)
pyopencl.RuntimeError: Context failed: out of host memory

以下の設定を変えると直るかも知れない。

  • 「設定」「外観の設定」「視覚効果」を「効果なし」に

benchmark-all.pyをちょっと最適化

examplesの中に入っているスクリプトの一つを、ちょっとだけ速くしてみた。カーネルのソースを見てみると、毎回globalメモリへアクセスしているため効率が悪い。以下のパッチを当てて計測してみる。

$ diff benchmark-all.py benchmark-all-private.py 
56,58c56,62
<                                 c[gid] = a[gid] + b[gid];
<                                 c[gid] = c[gid] * (a[gid] + b[gid]);
<                                 c[gid] = c[gid] * (a[gid] / 2.0);
    • -
> float fa, fb, fc; > fa = a[gid]; > fb = b[gid]; > fc = fa + fb; > fc = fc * (fa + fb); > fc = fc * (fa / 2.0); > c[gid] = fc;

[before]

Execution time of test without OpenCL: 11.3199682236 s
===============================================================
Platform name: Apple
Platform profile: FULL_PROFILE
Platform vendor: Apple
Platform version: OpenCL 1.0 (Dec 23 2010 17:30:26)

                                                                                                                            • -

Device name: Radeon HD 4850
Device type: GPU
Device memory: 512 MB
Device max clock speed: 503 MHz
Device compute units: 10
Execution time of test: 0.00564408 s
Results OK
===============================================================
Platform name: Apple
Platform profile: FULL_PROFILE
Platform vendor: Apple
Platform version: OpenCL 1.0 (Dec 23 2010 17:30:26)

                                                                                                                            • -

Device name: Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz
Device type: CPU
Device memory: 6144 MB
Device max clock speed: 2800 MHz
Device compute units: 8
Execution time of test: 0.0021682 s
Results OK

[after]

Execution time of test without OpenCL: 11.3338668346 s
===============================================================
Platform name: Apple
Platform profile: FULL_PROFILE
Platform vendor: Apple
Platform version: OpenCL 1.0 (Dec 23 2010 17:30:26)

                                                                                                                            • -

Device name: Radeon HD 4850
Device type: GPU
Device memory: 512 MB
Device max clock speed: 503 MHz
Device compute units: 10
Execution time of test: 0.00388932 s
Results OK
===============================================================
Platform name: Apple
Platform profile: FULL_PROFILE
Platform vendor: Apple
Platform version: OpenCL 1.0 (Dec 23 2010 17:30:26)

                                                                                                                            • -

Device name: Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz
Device type: CPU
Device memory: 6144 MB
Device max clock speed: 2800 MHz
Device compute units: 8
Execution time of test: 0.00147917 s
Results OK

たったこれだけだけど、30%ぐらい速くなってる。しかし、困ったことにCPUのほうが速い……恐らくMAX_COMPUTE_UNITSがCPU 8に対して、GPU 10しかないため、個々の演算ユニットのガチ勝負になると、CPUのほうが速いためと思われる。それとも、もっと最適化する方法があるんだろうか。

新年度

「0と1しか興味ない人のためのマルウェア分析会」のレポートを書きますと言ってから、もう二ヶ月あまりが経ってしまって、だいぶ時期を逃した感があるのでなかったことにして、またOpenCLのポストをいくつか書きたいと思う。


そこで、OpenCLの環境を整えるにあたって、nVidia Quadro NVS 295を載せたマシンにUbuntu 10.10をインストールしたら、再起動後に画面に何も映らない状態になってしまった。どうやらオープンソース版のドライバではDisplayPortの出力に失敗するらしい。
このままではnVidia製のドライバをインストールすることもできないので、起動時にSHIFTキーを押しっぱなしにして、Grubのメニューを表示させ、起動するカーネルを選んでeキーを押す。
そして、カーネルのパラメータの最後に次オプションを追加する。

gfxpayload=text nomodeset

これでとりあえず起動することはできるので、あとはGnoneのメニューからnVidia製のドライバをインストールして再起動すればよい。

あけましておめでとうございました

MacBook Air 11インチ欲しい!

↑主にこれのために更新したようなものというw


さて、一年以上もほったらかしのこのブログを再開せねば。
最近はネトゲと仕事しかやってない。それは嘘、主にネトゲ


とりあえず、昨年末に参加した「0と1しか興味ない人のためのマルウェア分析会」のレポートを書きます。近いうちに。

Windows Vistaにオフラインでパッチをあてる

どうしてもオンラインにできないVistaマシンに、セキュリティパッチを当てなければならなくなったので方法を調べてみた。もちろん毎回手でパッチをダウンロードしてきてもできるが、本当に必要なパッチがどれなのかハッキリしないし、手間を省きたい。
まず、Microsoft Baseline Security Analyzer(MBSA)を利用して、どのパッチが必要なのかをレポートさせる。

MBSAをオフラインで実行する
http://www.atmarkit.co.jp/fwin2k/win2ktips/1053mbsascan/mbsascan.html

基本的にMBSAGUIのアプリケーションだが、CUIで作業したいので以下のファイルをインストールフォルダから取り出しおく。これ意外は必要ない。

  • mbsacli.exe
  • wusscan.dll

更にwsusscn2.cabをダウンロードして、この上の二つと一緒にパッチを当てたいマシンに置く。wsusscn2.cabはパッチが出るたびに更新されるので、その都度以下からダウンロードする必要がある。

上級ユーザー向けの新しいバージョンの Windows Update オフライン スキャン ファイル Wsusscn2.cab について
http://support.microsoft.com/kb/926464/ja

あとで使い易いようにXMLでレポートを出力させる。

mbsacli.exe /catalog wsusscn2.cab /unicode /xmlout > report.xml

このXMLには既にインストールされているパッチもあわせて出力されているので、スクリプトを書いてインストールされていないパッチを探す。例としてPythonで書いてみた。parseReport()の引数reportStringは文字列として読み込んだレポートのXMLファイルだ。この例ではSeverityもチェックしている。Severityが0のものとは、普通のWindows UpdateMicrosoft Update自動的にインストールされないもの、例えば新バージョンのIEとか、ルート証明書などが該当する。

def parseReport(reportString):
	dom = minidom.parseString(reportString)
	root = dom.documentElement

	for e in root.getElementsByTagName('UpdateData'):
		isInstalled = e.getAttribute('IsInstalled')
		severity = int(e.getAttribute('Severity'))

		if 'false' == isInstalled and 0 < severity:
			for url in e.getElementsByTagName('DownloadURL'):
				yield url.childNodes[0].data

for url in parseReport(reportString):
	print url

これでパッチをダウンロードするためのURLが出力された。さてVista以前(XPなど)でこの方法を使うと、パッチの実行体(EXE)が落ちてくるのでそのまま実行させれば良い。
ところがVistaの場合は、パッチが入ったCABファイルが落ちてくる。このCABファイルをパッチとするには、展開してpkgmgrを使う必要がある。ググってみたら便利そうなBATファイルを公開している人がいた。ロシア語なので内容はぜんぜんわからないが、カレントフォルダにあるCABファイルを全て適応してくれるようだ。

Abrosovさんの書き込みから引用
http://forum.oszone.net/archive/index.php/t-84254-p-4.html

@echo off
for %%a in (*.cab) do (
md %%~na
start /wait expand %%a -f:* %%~na
start /wait pkgmgr /ip /m:%%~na /quiet /norestart
)

これでパッチは適用されたので、あとは忘れずに再起動しておく。(/norestartがついているため)
また事前にアクティーベーションをやっておかないと、海賊版の被害にあっているのでは?というエラーが出るので注意。

実際に試したのはVistaだけだが、恐らくWindows Server 2008Windows Server 2008 R2Windows 7でも同様だと思われる。

VMware FusionでChrome OS試してみた

gdgtというサイトでChrome OSのディスクイメージを配っていたので、VMware Fusionで試してみた。ディスクイメージしかないので、仮想マシンを作ってディスクだけダウンロードしてきたものを使うことになる。

以下、VMware Fusionで実行するまでの作業手順。

  • 上記のサイトにアカウントを作ってchrome-os-0.4.22.8-gdgt.vmdk.zipをダウンロードする(328.3MB)
  • zipを解凍するとvmdkイメージが出てくる
  • VMware Fusionを起動して、新規仮想マシンを作成
  • インストールディスクを使用せずに続行
  • 「既存の仮想ディスクを使用」で解凍してできたvmdkを指定する
  • 仮想ディスクの個別のコピーを使用する(古いディスクイメージなので変換する)
  • オペレーティングシステムLinux、バージョンは「その他のLinux 2.6.xカーネル
  • 設定のカスタマイズおすすめ
    • VM仮想マシンのフォルダを好きな場所に保存
    • メモリを適当に指定(512MBで動作確認)
    • ネットワークをブリッジに
    • USB自動接続を解除
    • プリンタとかシリアルポートはいらない
  • VMware起動時に開くはおすすめしない

これで起動すると次のような画面になるはず。

usernameとpasswordは、Google Accountのものを入力する。(gmailなどで使っているアカウント)
タイムゾーンが間違っていたので、時計をクリックして設定画面を開く。Touchpadなんていう項目がある。興味深い。

ちゃんと日本語も表示される。

Youtubeも見ることが出来るが、音が出ないのはなぜだろう?

まだリリース版ではないから、しょっちゅうフリーズするのはしょうがない。


Googleの出してるものが全部合わさると面白いことになっていきそうだ。OfficeソフトはWEB化が進んでいるし、音楽やムービーは既に多くのリソースが移行している。Native ClientやWebGLを使えば、重いゲームだってブラウザ上に持ってこれる。重たいOSに無駄なお金を使わなくてすむ日が早く来ないかなぁ。

Core i7iMac買ってしまった。届くのは12月入ってからだけど、wktkが止まらない。

デフォルト引数で失敗した

2009-10-19 追記
コメントで指摘してもらっているようにデフォルト引数ではなくて、キーワード引数の問題だった。
よく分からないものを無理して使った上に、ブログに間違いを載せるとか、恥の上塗りでしかなかった。



最近は特に勉強もせず、ネトゲやったりしてダラダラ過ごしてしまっている。
そんな中、ちょっとしたミスからなかなかバグがとれなかったので、ここに晒して戒めとしよう。
それは似たようなコードをC++Pythonで使い回していて、次のような関数のデフォルト引数を使ったコードで発生した。

# Python

def func(a = 10):
	print(a)

def main():
	a = 20

	func(a = 30)
	print(a)

if '__main__' == __name__:
	main()
// C++

#include <iostream>

using namespace std;

void func(int a = 10) {
	cout << a << endl;
}

int main() {
	int a = 20;

	func(a = 30);
	cout << a << endl;

	return 0;
}

出力は以下の通り。

Python
    30
    20

C++
    30
    30

このコードなら大した話ではないので間違いにはすぐ気がつくが、他のロジックに混ざっていると突然変数の中身が変化したような錯覚に。
根本的には、変数名の付け方が悪かったことに起因するんだろうなぁ。

リバースエンジニアリングチャレンジ2009の結果

今年の成績は、こんな感じ。
(一問目は5秒ではなく5分。二問目も同様)

設問 経過時間 順位
一問目 00:05 三位
二問目 00:53 八位
三問目 未クリア -
四問目 未クリア -

情けない話ですが、三問目の途中で時間切れ。
いや、心が折れたというべきか。

以下、解けた部分だけ解説。

一問目

去年と比べるととても簡単。
変換すらないので、逆アセンブルして出てきたマジックナンバーを引数に渡すだけ。
「00401157」でコマンドライン引数の数が4つであることを確認。
以下は、順番にatol()関数を使って、引数に渡された数字の文字列を数値に変換、マジックマンバーを一致するかどうかをチェックしていく。

二問目

まさかのブロック崩し

ゲームをクリアすれば答えが出るような気がするが、とても不可能なゲーム内容になっている。(バーの左右はマウス操作できるが、自動的に上方向に移動してしまう!)
しかし、コードをみるとその必要はない。
「004043A0」からWord値が11個あり、「00401423」のMessageBoxWで表示とわかる。
この11個のWord値は、コードのいろいろな部分から、それぞれバラバラに設定される。
さらに「00401080」の関数内で、各Word値から「00404754」にある値を引いていることがわかる。
この「00404754」を追っかけていくと、「00401143」で5がセットされる。
なので、11個のWord値を全て取り出して、5を引けば答えとなる。

三問目

去年も次から次にいろんなフォーマットが出てきた三問目。
今年は6種類のエンコーダを全て適応して変換されたデータ元に戻す。
つまり、6つのデコーダを作らないといけない。

encode6

エンコードされたデータをみてみると、「Salted__」というヘッダーがヒントになっている。
またエンコーダのコードをみると内部にPerlスクリプトを持っていて、実行してみるとPerlスクリプトを一度ファイルに落としてからそれを実行しているのが分かる。
そのPerlスクリプトが以下のような感じ。

package main; shift @INC;
#line 1 "script/encode6.pl"
#!/usr/local/bin/perl

use Crypt::CBC;

$key1 = 'I LOVE PAR!';
$key2 = 'ACTIVE PERL';
$key3 = 'CRYPT::CBC';

open(R, "$ARGV[0]") or die "err";
open(W, ">$ARGV[1]") or die "err";

binmode R;
binmode W;

$cipher1 = Crypt::CBC->new($key1, 'DES');
$cipher2 = Crypt::CBC->new($key2, 'DES');
$cipher3 = Crypt::CBC->new($key3, 'DES');

$data = ""; $new = "";
while(read(R, $new, 1)){
	$data .= $new;
}

$data = $cipher1->encrypt($data);
$data = $cipher2->encrypt($data);
$data = $cipher3->encrypt($data);

print W ($data);

close W;
close R;

これでTriple-DESのキーが三つ判明した。
あとはopensslコマンド等で、デコードできる。

encode5

encode5のデータをfileコマンドにかけてみると、waveファイルだと分かる。
さらにコードをみると、「00401150」の関数でwaveファイルのヘッダーを作り、「00401207」の関数で入力データを変換して出力している。
では、どう変換しているかというと、入力データ1バイトを上位4ビット、下位4ビットにわけ、ランダムに生成した値の中に埋め込んでいる。
シフトして埋め込んであるので、デコーダを書くとこんな感じ。

f = open('password.e0.e1.e2.e3.e4.e5', 'rb')
f.read(0x2c)

dataList = []
while True:
	try:
		i1 = ord(f.read(1))
		i2 = ord(f.read(1))
		i3 = ord(f.read(1))
		i4 = ord(f.read(1))
	except:
		break
	i = i2 << 8 | i1
	i = (i >> 2) & 0x0f

	j = i4 << 8 | i3
	j = (j >> 6) & 0xf0

	i = i | j

	dataList.append(i)
f.close()

f = open('password.e0.e1.e2.e3.e4', 'wb')
for i in dataList:
	f.write(chr(i))
f.close()

最初に0x2C読み飛ばしている分が、waveのヘッダーの部分。

encode4

データをみると256個の同じデータが連続して出てくるので、encode5のデコーダを何か間違えたんじゃないかと不安になるが、それであっている。
このエンコーダはamd64Windows用の実行体なので、逆アセンブルするのがちょっと大変だけど、中身は割と簡単。
「00000000004011A0」から始まるループで(VAが長い!)、入力値を0x55でxorして256回出力している。
それだけ、たぶん。(ここからちょっと自信がなくなってきた)

encode3

これが解けなかった。
エンコーダはARMのLinux環境用。Linux Zaurusでなんとか動かないかと思ったが、ライブラリの配置が違うのか?バージョンが違うのか?うまくいかなかった。
コードを読むしかないが、慣れないARMのコードでよくわからず。
他の参加者の方々をみるとqemuでエミュレートさせて実行なんという人もいた。
なるほど、勉強になるなぁ。

encode2

encode3が解けてないので、ざっくりみただけ。
たぶん、JNIでJavaを呼び出している。
classファイルを取り出して、ごにょごにょするんじゃないかな?

encode1

実はこっちのほうが簡単という罠。
16bitのx86命令で、入力データからキーを引き算。
キーは0x00から0x41の範囲をぐるぐるする。

四問目

公式で全問公開されたら、確認したい。


去年は一問目しか解けなかったので、進歩はしているようだが、もうちょっと頑張りたかった。
やはりx86以外のCPU命令がまったく分からないのが、難点か。
勉強、勉強。