Fortranを書こう (書くとは言ってない)

Posted on

とあるイベントでのLT。

[全画面/表示されない場合]

なんでFortranせなあかんねんと。その時間を使ってね、バイナリおじさんをする。そういう考え方もできると思うんですよ。だってスパコン向けでもないのにFortran書いてもこころぴょんぴょんしないでしょ。


Fortranが書きたくないなら機械語を埋め込めばいいじゃないっ!

Posted on

Fortranが書きたくない!!

わたしは大学で天気とか地震とか海流といった「地球物理」と呼ばれるジャンルを学んでいるのですが、このジャンルではコンピュータをガンガン回して計算しまくります。気象庁が毎日やってる天気予報もそうですし、「地球シミュレータ」でエルニーニョや地球温暖化の予測とかをやっているのもそうです。

で、そういう学科なので、コンピュータの実習があるのですが、なぜか今でもFortranを教えています。

たしかにFortranには、LAPACKのような優れた数値計算ライブラリがあったり、スパコンで書く時はコンパイラのサポートが充実してたりします。なので、そういう特定の用途には悪くない言語だと思うのですが、デスクトップPCで十分扱えるようなデータサイズで、そもそも計算速度はさして重要ではなくてデータ解析手法を学ぶ実習で使うのには辛すぎます。陽には書きたくありません。1でも書かないと単位くれないんだってさ!

なので、Fortranを書かずにFortranを書く方法を検討して、実際に実装しました。今回のソースコードも、すべてgithub上に上げています。

Fortranを書かずにFortranを書くには?

最初、Fortran上で小さなVMのインタプリタを実装して、そのVMの仮想機械語にコンパイルされる言語のコンパイラを実装しようと思いました。

が、VMを書くのが普通にしんどいのと、言語とコンパイラをこのためだけに実装するのもかなりつらいので、もっとラクな方法は無いか考えてみました。

冷静に考えると、別にVMのインタプリタを実装しなくとも、コンパイルされたFortranのコードは既にCPUという名前のx86機械語のインタプリタ機械(?)で実行されています。このインタプリタ(?)を利用できないでしょうか。利用できれば自力でVMのインタプリタを実装しなくていいので手間を省略できますし、コンパイラもgccとかclang/llvmとかすごいやつがいっぱい使えます!

具体的にはそのためにどうすれば良いのかというと、Fortranのinteger配列にC言語のソースをコンパイルしたx86の機械語の数字を沢山並べて入れて、そのinteger配列のポインタをC言語の関数ポインタに無理やりキャストして、そして実行すればいいのですっ!!

module a;interface;subroutine f (z) bind(c);use iso_c_binding;type(c_ptr), value :: z;
end subroutine;end interface;type G;procedure(f), pointer, nopass :: x;end type;type J;integer, pointer :: x;
end type;end module;program b;use iso_c_binding;use a;implicit none;type (J), pointer :: x(:);
real(8),pointer :: dbl(:);type(G), pointer :: p;integer, pointer :: d(:);integer :: i;integer :: n;allocate( x(1) );
allocate( dbl(10000000) );allocate( d(10000000) );dbl = 0;x(1)%x => d(645);d(1)=-443987883;d(2)=1213580125;d(3)=267576713;
d(4)=1223181585;d(5)=1223181709;d(6)=48087689;d(7)=450755289;d(8)=-128612024;d(9)=-398095544;d(10)=-532313784;
d(11)=1158680562;d(12)=1438866912;d(13)=1223002440;d(14)=1223705997;d(15)=-338050423;d(16)=-1991763235;
d(17)=-1958152107;d(18)=-1991708603;d(19)=267577413;d(20)=1575503120;d(21)=-1991748157;d(22)=286257893;
d(23)=-1924601787;d(24)=-1991710651;d(25)=-654123582;d(26)=1209720318;d(27)=1224234377;d(28)=1223181707;
d(29)=-220183159;d(30)=-532344817;d(31)=1213580125;d(32)=267576713;d(33)=1223181585;d(34)=1223181709;d(35)=48087689;
d(36)=450756569;d(37)=-128612024;d(38)=-398095544;d(39)=-532313784;d(40)=1158680562;d(41)=1438866912;
d(42)=-219838136;d(43)=-398126833;d(44)=-398095032;d(45)=-574453432;d(46)=-572401406;d(47)=1435060250;
d(48)=1166756088;d(49)=1166625000;d(50)=269480672;d(51)=-1017257915;d(52)=-443987883;d(53)=-394426040;
d(54)=-1192987255;d(55)=0;d(56)=-129660600;d(57)=16008647;d(58)=-352321536;d(59)=-196768982;d(60)=-1924622264;
d(61)=50452;d(62)=-1958215680;d(63)=21555269;d(64)=269480656;d(65)=269480448;d(66)=267581517;d(67)=267567448;
d(68)=-2080881391;d(69)=-1962806203;d(70)=1161557061;d(71)=1221491940;d(72)=1224230283;d(73)=-220707447;
d(74)=-666562545;d(75)=1213580125;d(76)=-1991711351;d(77)=-1991710595;d(78)=1435099253;d(79)=47324;d(80)=-1991770112;
d(81)=1170733125;d(82)=244;d(83)=-1958286592;d(84)=-1740049339;d(85)=-988508856;d(86)=0;d(87)=-398095544;
d(88)=-221249208;d(89)=-1962405873;d(90)=-1740049339;d(91)=-988508856;d(92)=0;d(93)=-532313272;d(94)=-221249208;
d(95)=-234876913;d(96)=-222209777;d(97)=-129167345;d(98)=-1051193358;d(99)=1158746098;d(100)=-196770824;
d(101)=-196769023;d(102)=2094810427;d(103)=1166756018;d(104)=1166625016;d(105)=269480656;d(106)=-1017262011;

(略)

d(761)=-800748728;d(762)=-389576376;d(763)=-1168;d(764)=1223710091;d(765)=1220572555;d(766)=-1178057333;d(767)=20;
d(768)=-389576376;d(769)=-683;d(770)=-1980742261;d(771)=535478722;d(772)=-120467455;d(773)=-223591031;
d(774)=-1404753393;d(775)=-2008708280;d(776)=1118194;d(777)=16008647;d(778)=-352321536;d(779)=-196768970;
d(780)=-2092394424;d(781)=-1924660800;d(782)=50452;d(783)=-1958215680;d(784)=21530693;d(785)=-196768830;
d(786)=-1924622264;d(787)=50444;d(788)=-1958215680;d(789)=21545029;d(790)=9128136;d(791)=-2096985784;
d(792)=-1962806203;d(793)=1161557061;d(794)=-1195213652;d(795)=0;d(796)=1213580233;d(797)=-1017256567;
call c_f_pointer(C_LOC( x(1) ), p);i=0;read (*,*), n;dbl(1) = real(n);do i=2,n+1;read (*,*), dbl(i);end do;call p%x( c_loc(dbl(1)) );
n = int(dbl(1));do i=2, n+1;write (*,*), i, dbl(i);end do;deallocate ( d );deallocate ( x );deallocate ( dbl );
contains;end program;

Fortran Side

Fortranでのキャストテクニック

今回はintegerの配列のポインタをCの関数へのポインタだと思わせたいため、(Cで言うと)int*をvoid (*ptr)(double*)のポインタにキャストする必要があります。しかし、Fortranは割りと型に厳しく、Cのように簡単にキャストしたり出来ません。なので、ちょっと撚(ひね)る必要があります。

!! 関数の型定義。
!! typedef void(*f)(void*);
interface
	subroutine f (z) bind(c)
		use iso_c_binding
		type(c_ptr), value :: z
	end subroutine
end interface
!! struct FPointer { f fptr; };
type FPointer
	procedure(f), pointer, nopass :: fptr
end type
!! struct IPointer { int* iptr; };
type IPointer
	integer, pointer :: iptr
end type

このように2つ構造体(IPointer,FPointer)を作っておき、この2つの構造体のポインタをC言語のポインタを経由することで無理やりキャストします。

type (IPointer), pointer :: ip(:);       !! IPointer* ip;
real(8),pointer :: dbl(:);               !! double* dbl;
type(FPointer), pointer :: fp            !! FPointer* fp;
type(C_PTR) :: cptr                      !! void * cptr;
integer, pointer :: d(:) !命令列用の配列  !! int* d;
allocate( ip(1) )                        !! ip = malloc(sizeof(IPointer));
allocate( dbl(10000000) )                !! dbl = malloc(sizeof(double)*10000000);
allocate( d(10000000) )                  !! d = malloc(sizeof(int)*10000000);
!! 命令列をセット
d(1)=-443987883;
d(2)=1213580125;
!! 略
d(790)=-443987883;
d(791)=50013;
!! 645番目の要素がC言語側エントリポイント関数の頭なので、そこへのアドレスをセット。
ip(1)%iptr => d(645);
!! IPointerへのポインタを、C言語のポインタ、いわばvoid*に落とす
cptr = C_LOC( ip(1) );
!! そこからさらにFPointerへのポインタにキャストする(専用の関数を使います)
call c_f_pointer(cptr, fp)
!! 無理やり変換した関数ポインタ経由でバイナリコードにジャンプ
call fp%fptr( c_loc(dbl(1)) )

IPointer 構造体へのポインタを一度Cのポインタ(いわばvoid*)にキャストし、それを再度void*からFPointerのポインタにキャストしています。構造体の中身の型が「intへのポインタ」と「関数へのポインタ」で違うので、構造体を通して、その中身についてキャストすることができました。

なんでこんな七面倒臭いことをしているかというと、intのポインタと関数のポインタを直接キャストすることができないからです。intのポインタは type(C_PTR)、関数のポインタはtype(C_FUNPTR)なので互換性がなく、さらにC言語みたいにintに無理やり落としてもう一度ポイ ンタにするような操作もできません。しかし、一回構造体でくるんでしまえばどちらも値のポインタとして扱い、キャストすることができます!

さらに、現在のLinuxでは、通常データ領域をプログラムとして実行することはできない(DEP)ので、コンパイルする時にその制限をはずす必要もあります。これには、 -z execstackというオプションスイッチが使えます。

これで、任意のx86機械語の列をFortranから実行することがきました。あとは、その機械語の列をどうやって作るかという話になりますね。原理的には自分で書いてもよいのですが、一応課題をやっているコードなので、結構複雑です。なので、C言語で書いたコードをコンパイルして、その結果を利用したいところです。

Binary Side

コードのコンパイルにはGCCを使うことにしましょう。

この時問題になるのは、Fortranの動的に配置した配列の上に機械語を置くので、機械語の置かれるアドレスが実行時になるまでわからないことです。なので、機械語がどこに置かれるかがわかっていることが前提の通常のコードはうまく動いてくれません。

そのようなケースは実は珍しくなく、共有ライブラリもアドレス上のどこにロードされるかは分からないので、ちゃんとそのためのコードをコンパイラが出力します2。そのようなコードのことを、PIC(Position Indepentent Code)と呼びます!gccでは、-fPICというコンパイルオプションをつけると、PICとしてコンパイルしてくれます。

じゃ、-fPICを付けてコンパイルしたバイナリから機械語をもってくれば、目的は達成できるのでしょうか…?

Position Independent Code

ところがどっこい、これではうまく行きません。なんでかというと、一個前に翻訳した記事で解説されている、PLTとGOTがあるからです。

こんな非常に簡単なC言語コードを考えてみましょう。

void calledFunction(){
}

void function(){
  calledFunction();
}

これをコンパイルするためのMakefileはこんな感じで、

.PHONY: all

all:
  gcc -c -o test.o test.c -fPIC
  ld -shared -fPIC -o test.so test.o

コンパイルした結果が、こんな感じでーす。

test.so:     file format elf64-x86-64


Disassembly of section .plt:

0000000000000280 <calledFunction@plt-0x10>:
280:    ff 35 82 0d 20 00        push   QWORD PTR [rip+0x200d82]        # 201008 <_GLOBAL_OFFSET_TABLE_+0x8>
286:    ff 25 84 0d 20 00        jmp    QWORD PTR [rip+0x200d84]        # 201010 <_GLOBAL_OFFSET_TABLE_+0x10>
28c:    0f 1f 40 00              nop    DWORD PTR [rax+0x0]

0000000000000290 <calledFunction@plt>:
290:    ff 25 82 0d 20 00        jmp    QWORD PTR [rip+0x200d82]        # 201018 <_GLOBAL_OFFSET_TABLE_+0x18>
296:    68 00 00 00 00           push   0x0
29b:    e9 e0 ff ff ff           jmp    280 <calledFunction@plt-0x10>

Disassembly of section .text:

00000000000002a0 <calledFunction>:
2a0:    55                       push   rbp
2a1:    48 89 e5                 mov    rbp,rsp
2a4:    5d                       pop    rbp
2a5:    c3                       ret

00000000000002a6 <function>:
2a6:    55                       push   rbp
2a7:    48 89 e5                 mov    rbp,rsp
2aa:    b8 00 00 00 00           mov    eax,0x0
2af:    e8 dc ff ff ff           call   290 <calledFunction@plt>
2b4:    5d                       pop    rbp
2b5:    c3                       ret

こんな感じで、ばっちりpltを経由して実行してしまいます。この機械語をFortran上に載せて強制的に呼び出したとしても、PLTがちゃんと解決されないとうまく動いてくれません。デフォルトのPLT解決処理は共有ライブラリとして実行されていることが前提になっているので、今回は利用することはできません。

integer配列のアドレスから関数のアドレスを解決することは原理的には可能なので、Fortran上でPLTのアドレスの所を埋めてちゃんと実行できるようにすること自体は可能(なはず)です。

しかし、かなり意味もなく複雑になってしまうので、できればPLTにジャンプするのではなくて、呼びたい関数に直接ジャンプするようなコードを出力して欲しいです。PLTを経由して関数を呼び出す時に使ってる0xe8命令は相対アドレスによるcall命令なので、可能なはずです。

リンカースクリプトを自分で書けばPLTとGOTを使わない…!?

リンク時にPLTを経由するようにリンクしているので、じゃあすごく単純なリンカースクリプト自分で書けばPLTみたいな便利機能使わなくなるんじゃない?

ということで、実際に書いてみました。こうなりました。

OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64","elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)

ENTRY(ep);
MEMORY
{
ROM(rxai) : ORIGIN = 0, LENGTH = 64k
}
SECTIONS
{
.text : {} > ROM
.rodata : {} > ROM
.data : {} > ROM
. = ALIGN(4);
__bss_start = .
; .bss : {} > ROM
__bss_end = . ;
}

Makefileは、

linkerscript:
    gcc -c -o test.o test.c -fPIC 
    ld test.so test.o --script ./linker.script

こんな感じにすると指定できます。結果は、

test-with-linkerscript:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <calledFunction>:
   0:    55                       push   rbp
   1:    48 89 e5                 mov    rbp,rsp
   4:    5d                       pop    rbp
   5:    c3                       ret    

0000000000000006 <function>:
   6:    55                       push   rbp
   7:    48 89 e5                 mov    rbp,rsp
   a:    b8 00 00 00 00           mov    eax,0x0
   f:    e8 ec ff ff ff           call   0 <calledFunction>
  14:    5d                       pop    rbp
  15:    c3                       ret

となって、見事、PLTを使わずに直接相対ジャンプするようになりました!

実は-pieだけで大丈夫

実をいうと、リンカースクリプトなんて書かなくても、-pieというオプションを付けて、通常の実行ファイルとしてコンパイルすればplt/gotを使わないコードを生成してくれます。

pie:
    gcc -c -o test.o test.c -pie
    ld -pie test.so test.o

-pieというオプションは、gcc –helpすると、

  -pie                     Create a position independent executable

ということで、実行時にどのアドレスに配置されても実行できる実行ファイルを生成してくれます。

実行ファイルは共有オブジェクトとは違って外部の他のプログラムからその中の関数が呼ばれるようなことはないので、内部のそれぞれの関数のアドレスを動的に解決する必要はありません。なので、PLTを経由せずに直接callするようなコードにしてくれる…のでしょう。

…でも冷静に考えると、共有ライブラリの中でならPLT経由せずに直接ジャンプでよくない?

全てが終わったあとに気づいたので時既に遅しという感じでしたが、一応書いておきます。

プログラムセクションを抽出する

残るところは、出来上がったファイルから機械語を抽出して、数字の列にするだけです。そのために、機械語が書いてある部分はファイルのなかでどこなのかを調べましょう。

セクションの一覧を出力するには、objdump -hが使えます。

% objdump -h test.so

test.so:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
0 .interp       0000000f  00000000000001c8  00000000000001c8  000001c8  2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .hash         00000028  00000000000001d8  00000000000001d8  000001d8  2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .dynsym       00000078  0000000000000200  0000000000000200  00000200  2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .dynstr       00000019  0000000000000278  0000000000000278  00000278  2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .text         00000016  0000000000000294  0000000000000294  00000294  2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
5 .eh_frame     00000058  00000000000002b0  00000000000002b0  000002b0  2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .dynamic      000000c0  0000000000200f40  0000000000200f40  00000f40  2**3
CONTENTS, ALLOC, LOAD, DATA
7 .comment      0000002c  0000000000000000  0000000000000000  00001000  2**0
CONTENTS, READONLY

機械語本体が置かれているのは、.textというセクションです。この中のデータをFortranに書きましょう。File offの項目が、ファイルの先頭からの位置で、Sizeという項目がセクションのサイズで、今回の場合、要はプログラムコード全体のサイズです。

さらに、関数呼び出しするには、呼び出したい関数の、プログラムコード内での相対位置も必要です。これには、nmコマンドが使えます。

% nm test.so
0000000000201000 D __bss_start
0000000000000294 T calledFunction
0000000000200f40 d _DYNAMIC
0000000000201000 D _edata
0000000000201000 D _end
000000000000029a T function
0000000000000000 d _GLOBAL_OFFSET_TABLE_
                 U _start

セクションの所のVMAもしくはLMAというのがメモリ上での位置なので、機械語列の中での相対位置を調べるにはここでのアドレスから.textセクションのVMA/LMAの値を引けば求めることができます。

シェルスクリプトを使うともちろんこの作業は自動化できます。呼び出す関数名は”ep”としました。EntryPointの略ですね!

_start=$(objdump -h test.so | grep \\s.text\\s | awk '{ print $6 }')
_size=$(objdump -h test.so | grep \\s.text\\s | awk '{ print $3 }')
_ep=$(nm test.so | grep \\sep$ | awk '{ print $1 }')

Cのソースをコンパイルする所から、命令列を抽出してFortranのソースに変換する作業まで、これらの作業はすべて自動化しているので、よかったらgithubのソースを見てね!

今回書き込んだinteger型は4バイトなので、エントリポイントのオフセットが4の倍数でないとき(よくある)には、その整列条件を満たすために数バイトズラしたりしないといけません。そのコードとかも入ってます。

Programming Side

これで終わり…ではありません!もう一個、プログラムを書くという問題があります!!今回の環境では、

  • 外部にあるライブラリ関数などは呼び出すことができない

という、非常に大きな制限があります。実行時に決定されるライブラリ関数の実際のアドレスを知らないし、知るためにはライブラリ関数を呼び出さないといけないので、「服を買う服がない」状態なんです。もちろん、Fotranでなら調べられるので、調べた結果をC言語の関数に投げればいいんですけど、Fortran書きたくないし(ぉ)。

科学計算すればいいだけなので、sin/cosとsqrtくらいが使えればなんとかなります。sin/cos/sqrt/absは、FPU命令を使って計算できるので、ライブラリは必要ありません。

そのためには、インラインアセンブリで陽にFPU命令を実行する必要があります。

double fsin(double v)
{
	intptr_t d0;
	__asm__ (
		".intel_syntax noprefix;"
		"fld qword ptr [%0];"
		"fsin;"
		"fstp qword ptr [%0];"
		".att_syntax;"
		: "=&r"(d0)
		: "0"((intptr_t) &v)
		: "eax", "ecx"
	);
	return v;
}

こんな感じのを、FPUの命令ごとに実装していきます。

関数だけでなく、piもFPUを使って取得しています。なんでかというと、今回は機械語だけを埋め込んで、それとは別にある定数の領域をFortranのソースに含めていないからです!

Cのソースに円周率の値を直接書いていてもすこし複雑な処理になると定数領域に飛ばされてしまって、.textセクションに含まれなくなってしまい、結果としてプログラムがおかしくなってしまいました。定数領域もFortranに埋め込むだけでその問題は回避できるのですが、その改修がちょっとめんどくさかったのでやめて、FPU命令を毎回呼ぶことで回避しました。

入力と出力ですが、実際のデータの読み込みと結果の出力のみ、Fortranが担当することにしました。C言語は関数のシグネチャをvoid (*fun)(double *)とし、doubleの配列を処理してその結果をもとの配列の領域に書き込むことだけに専念していただきます。

他の言語でやってみても面白いかもね!

と思った。Fortranはなんだかんだ任意の機械語を実行する難易度は低い言語だったと思います。

ここまでやるならFortranで書いたほうが早くね?

ちなみに

書かなくて良くなった(╹◡╹)

ひさびさに色々バイナリが触れてたのしかったです。まぁ結果としては良かったのではないでしょうか!

  1. っていうか、いままでプログラム書いたことがない人にいきなりこれ教えるのってどうなんでしょう…。 []
  2. Windowsでは、64bit版からそうなりました。32bitでは固定だそうです。 []


GENETOSのBGMを差し替えられるパッチ作ってみた

Posted on

 以前、フリーのSTGゲーム“GENETOS”の音楽を復号化する“Oreshiki Decrypter”を公開する時に書いた、GENETOSのBGMを差し替えるための改造パッチを公開する…と言いながら忘れていましたが、最近ソースコードのフォルダを眺めていたら発見されたので配布します。

ダウンロード

 け、結構前…。

使い方

 ダウンロードして出てくる「_patch_genetos.EXE」を実行するとパッチが適用されます。

 次に、BGMのファイルを次のように書き換えてください。

  • 最終面の「origin.mp3.ore」と「answer.mp3.ore」、エンディングの「rebirth.mp3.ore」はそのまま置き換えられます。“Oreshiki Decrypter”で差し替えたいmp3を暗号化し、そのまま置き換えてください。
  • それ以外は拡張子が変わった上で、拡張子以外のファイル名が4文字減ります。こんな感じ:
    • 「little_invader.mid.ore」を差し替えたい場合は「little_inv.mp3.ore」という名前のファイルを用意してください。
    • Oreshiki Decrypterを利用して差し替えたいmp3を暗号化した後、上記のようなファイル名に変更してBGMフォルダにコピーしてください。
  • 自機が進化した後のBGMに関して、最初の数秒間が再生されません†1。予め自分で数秒間分のブランクを挿入しておく必要があります、

パッチについて簡単な解説。

 内部では音楽を再生する関数はmidiを再生するものとMP3を再生する関数に別れています。どちらもファイル名と追加のいくつかの引数を取るもので、これを利用して、midiを再生する関数を書き換えてmp3を再生する関数にバイパスしています。

 ただしひとつ問題があって、midiを再生する関数は、ステージ開始時に一気にmidiをロードしてしかるタイミングで再生するBGMを変更する、という事ができるのですが、mp3の関数ではそれはできませんでした。この問題を解決すべくいろいろ試行錯誤した結果、自機が進化する瞬間の処理をフックしてMP3再生関数を呼んでBGMを切り替えています。

パッチ適用時の注意。

 メニューから普通にゲームを開始する時以外の動作に関してはうまく動かないと思われます。フリープレイでちゃんと動く事とかは考えてません。

  • †1: 原因はよくわかりません


gccでのstatic constなクラス変数の謎挙動

Posted on

 今日はひたすらアセンブラを追いかけるだけのお話です。ごめんね。

 C++では定数の定義を、defineでなく、static constな変数で行うことが推奨されてるらしいです

// これはC++では(・A・)イクナイ!!
#define VAL (1)
//こっち推奨
const int VAL = 1;

 基本的には、これで問題ないのですが、リンク先によると、この定数がクラス変数の場合、定義と実体の二つに分けないといけないそうです。

//ヘッダファイル側(宣言)
class Test
{
private:
	
protected:
	
public:
	static const int VAL=01234;
};
//ソースファイル側(実体)
//gccでは宣言(ヘッダ)に値を書いても良いけど、VC++等の古いコンパイラだと実体に書かないといけない。
//そのためにenumハックが存在する(上記のリンク先参照)
const int Test:VAL;

 ほえー、なるほど。

 が、しかし。実際には、gccのときは、実体を書かなくても割と大丈夫みたいです。Ubuntu11.04のx86-64のgccで実験してみました(Win32のMingwでも、機械語は違いますが同じ結果でした)。

 以下のソースはgithubからもDLできるよ☆彡 あ、ZIPももちろんあるよ!!

共通のヘッダファイルはこちら。

(Test.h)

#ifndef TEST_H
#define TEST_H

class Test
{
private:
	
protected:
	
public:
	static const int VAL1=1234;
	static const int VAL2=4321;
};

#endif

 特に意味はないです。このTestというクラスの定数を使って、いろいろテストしてみましょう。

 以下test*-*とありますが、これをmakeのビルドターゲットにすると自動でコンパイルとかしてくれます。

make test*-*

(test1-1)実体を定義しないまま、std::coutに入れてみる。

(Test1-1.cc)

#include "./Test.h"
#include <iostream>

int main(int argc, char** argv)
{
	std::cout << "VAL: " << std::hex << Test::VAL1 << std::endl;
	
	return 0;
}

 ヘッダ上のTest::VAL1やTest::VAL2の実体を一切定義していないことに注意していください。

 これをコンパイルして実行すると…

% g++ -o Test1-1.out Test1-1.cc
% ./Test1-1.out 
VAL: 1234

 どうなってるの!?と思って、アセンブラを出力させてみると…

% g++ -S -masm=intel Test1.cc ←こうすると見慣れたINTEL形式で出力してくれます
% cat Test1.s
main:
.LFB963:
	.cfi_startproc
	push	rbp
	.cfi_def_cfa_offset 16
	mov	rbp, rsp
	.cfi_offset 6, -16
	.cfi_def_cfa_register 6
	sub	rsp, 16
	mov	DWORD PTR [rbp-4], edi
	mov	QWORD PTR [rbp-16], rsi
	mov	esi, OFFSET FLAT:.LC0
	mov	edi, OFFSET FLAT:_ZSt4cout
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
;---------------------------
	mov	esi, 1234
;---------------------------
	mov	rdi, rax
	call	_ZNSolsEi
	mov	esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
	mov	rdi, rax
	call	_ZNSolsEPFRSoS_E
	mov	eax, 0
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc

 線で囲ったところを見てもらえばわかるとおり、インライン展開されています。ちなみに、1234という数値はこの場所以外一切定義されません。

(test1-2)じゃ、実体を定義したらどうなるの?

 定義を書き加えて

(Test1-2.cc)

//ヘッダファイルとメイン関数の間に追加。
const int Test::VAL1;

 としてアセンブラを出力してみると…。

;実体も定義される
_ZN4Test4VAL1E:
	.long	1234

;(略)

main:
.LFB963:
	.cfi_startproc
	push	rbp
	.cfi_def_cfa_offset 16
	mov	rbp, rsp
	.cfi_offset 6, -16
	.cfi_def_cfa_register 6
	sub	rsp, 16
	mov	DWORD PTR [rbp-4], edi
	mov	QWORD PTR [rbp-16], rsi
	mov	esi, OFFSET FLAT:.LC0
	mov	edi, OFFSET FLAT:_ZSt4cout
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
;---------------------------
	mov	esi, 1234
;---------------------------
	mov	rdi, rax
	call	_ZNSolsEi
	mov	esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
	mov	rdi, rax
	call	_ZNSolsEPFRSoS_E
	mov	eax, 0
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc

 Test::VALの値が埋め込みになるのは同じでしたが、それとは独立した実体も定義されました。

 一切最適化オプションをつけなくても、それなりに最適化してくれるみたいです。

(test1-3)ポインタを通す等して、実体が必要な状況をつくってみる

 ポインタを通す場合、アセンブラに直接引数を書くことはできません。メモリ上に一旦置かなければなりません。

(Test1-3.cc)

#include "./Test.h"
#include <iostream>

void outVal(const int* val)
{
	std::cout << "VAL: " << *val << std::endl;
}

int main(int argc, char** argv)
{
	outVal(&Test::VAL1);
	
	return 0;
}

 今度からは面倒なのでビルドターゲットでコンパイルします。†1

% make test1-3
g++ -S -masm=intel Test1-3.cc -o Test1-3.s
g++ -o Test1-3.out Test1-3.cc
/tmp/ccIrdzjI.o: In function `main':
Test1-3.cc:(.text+0x50): undefined reference to `Test::VAL1'
collect2: ld returned 1 exit status
make: *** [test1-3] エラー 1

 案の定実行は失敗ですね。アセンブラコードを見てみると?

;実体は定義されないけど...

;(略)

main:
.LFB964:
	.cfi_startproc
	push	rbp
	.cfi_def_cfa_offset 16
	mov	rbp, rsp
	.cfi_offset 6, -16
	.cfi_def_cfa_register 6
	sub	rsp, 16
	mov	DWORD PTR [rbp-4], edi
	mov	QWORD PTR [rbp-16], rsi
;---------------------------
	mov	edi, OFFSET FLAT:_ZN4Test4VAL1E ;使おうとする
;---------------------------
	call	_Z6outValPKi
	mov	eax, 0
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc

 予想通りですね。

(test1-4)でも、最適化を掛けると…。

 でもしかし。同じソースのまま、-O3とかを付けて最適化を掛けると、実行可能です。

make test1-4
g++ -S -masm=intel Test1-3.cc -o Test1-4.s -O3 ;ソースは上と同じTest1-3.cc
g++ -o Test1-4.out Test1-3.cc -O3
./Test1-4.out
VAL: 1234
main:
.LFB1004:
	.cfi_startproc
	push	rbp
	.cfi_def_cfa_offset 16
	mov	edx, 5
	mov	esi, OFFSET FLAT:.LC0
;std::coutへの出力を行う別の関数(outVal)があったのに、インライン展開されてる。
;(展開されてない、outValの実体も別箇所で存在します。)
	mov	edi, OFFSET FLAT:_ZSt4cout
	push	rbx
	.cfi_def_cfa_offset 24
	sub	rsp, 8
	.cfi_def_cfa_offset 32
	.cfi_offset 3, -24
	.cfi_offset 6, -16
	call	_ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
;----------------------------------
	mov	esi, 1234   ;ハードコーディング。
;----------------------------------
	mov	edi, OFFSET FLAT:_ZSt4cout
	call	_ZNSolsEi
	mov	rbx, rax
	mov	rax, QWORD PTR [rax]
	mov	rax, QWORD PTR [rax-24]
	mov	rbp, QWORD PTR [rbx+240+rax]
	test	rbp, rbp
	je	.L11
	cmp	BYTE PTR [rbp+56], 0
	je	.L9
	movzx	eax, BYTE PTR [rbp+67]

じゃポインタにしないなら良いんだね良かった良かった

 ポインタとして使えないのはdefineでも一緒ですし、だったら同じ条件ですねやったー。

 …と言いたいところだが、(大佐風)微妙にこったことをすると必ず実体の定義が要らなくなるとは限りません。

(test2-1)三項演算子の結果に使う

 三項演算子を使って、フラグによって定数を振り分けたい!ありそうなシチュエーションです。

(Test2.cc)

#include "./Test.h"
#include <iostream>

int main(int argc, char** argv)
{
	int val = argc > 1 ? Test::VAL1 : Test::VAL2;

	std::cout << "VAL: " << val << std::endl;
	
	return 0;
}

 これを最適化なしでコンパイルすると…

% make test2-1
g++ -S -masm=intel Test2.cc -o Test2-1.s
g++ -o Test2-1.out Test2.cc
/tmp/cc87c0Ta.o: In function `main':
Test2.cc:(.text+0x17): undefined reference to `Test::VAL1'
Test2.cc:(.text+0x1f): undefined reference to `Test::VAL2'
collect2: ld returned 1 exit status
make: *** [test2-1] エラー 1

 なんと。駄目でした。アセンブラを読んでみると、

;もちろん実体は定義されてない。

main:
.LFB963:
	.cfi_startproc
	push	rbp
	.cfi_def_cfa_offset 16
	mov	rbp, rsp
	.cfi_offset 6, -16
	.cfi_def_cfa_register 6
	sub	rsp, 32
	mov	DWORD PTR [rbp-20], edi
	mov	QWORD PTR [rbp-32], rsi
;---------------------------------- ここから三項演算子
	cmp	DWORD PTR [rbp-20], 1
	jle	.L2
	mov	eax, DWORD PTR _ZN4Test4VAL1E[rip] ;実体は無いのに
	jmp	.L3
.L2:
	mov	eax, DWORD PTR _ZN4Test4VAL2E[rip] ;使おうとする。
;----------------------------------
.L3:
	mov	DWORD PTR [rbp-4], eax
	mov	esi, OFFSET FLAT:.LC0
	mov	edi, OFFSET FLAT:_ZSt4cout
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	mov	edx, DWORD PTR [rbp-4]
	mov	esi, edx
	mov	rdi, rax
	call	_ZNSolsEi
	mov	esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
	mov	rdi, rax
	call	_ZNSolsEPFRSoS_E
	mov	eax, 0
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc

(test2-2)最適化してみる。

 でも最適化をつけると…

make test2-2
g++ -S -masm=intel Test2.cc -O3 -o Test2-2.s
g++ -o Test2-2.out Test2.cc -O3
./Test2-2.out
VAL: 4321
main:
.LFB1003:
	.cfi_startproc
	push	rbp
	.cfi_def_cfa_offset 16
	mov	eax, 4321
	mov	edx, 5
	mov	esi, OFFSET FLAT:.LC0
	push	rbx
	.cfi_def_cfa_offset 24
	mov	ebx, 1234
	.cfi_offset 3, -24
	.cfi_offset 6, -16
	sub	rsp, 8
	.cfi_def_cfa_offset 32
	cmp	edi, 2
	mov	edi, OFFSET FLAT:_ZSt4cout
;----------------------
	cmovl	ebx, eax ;フラグを見て、条件にあうならeaxをebxへ。ここが三項演算子。
;----------------------
	call	_ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
	mov	esi, ebx
	mov	edi, OFFSET FLAT:_ZSt4cout
	call	_ZNSolsEi
	mov	rbx, rax
	mov	rax, QWORD PTR [rax]
	mov	rax, QWORD PTR [rax-24]
	mov	rbp, QWORD PTR [rbx+240+rax]
	test	rbp, rbp
	je	.L8
	cmp	BYTE PTR [rbp+56], 0
	je	.L4
	movzx	eax, BYTE PTR [rbp+67]

 まさかの分岐命令…なし…!?cmovlはこちら参考

 また、何度もstd::coutの関数を呼ぶのでなく、出力関数は一度だけのようです。

(test2-3)じゃ素直にif文を使う。

 じゃあ、if文で同じことをしましょう。

(Test2-3.cc)

#include "./Test.h"
#include <iostream>

int main(int argc, char** argv)
{
	int val;
	if(argc > 1){
		val = Test::VAL1;
	}else{
		val = Test::VAL2;
	}

	std::cout << "VAL: " << val << std::endl;
	
	return 0;
}
% make test2-3
g++ -S -masm=intel Test2-3.cc -o Test2-3.s
g++ -o Test2-3.out Test2-3.cc
./Test2-3.out
VAL: 4321

 むむ。成功してしまいました。アセンブラは?

main:
.LFB963:
	.cfi_startproc
	push	rbp
	.cfi_def_cfa_offset 16
	mov	rbp, rsp
	.cfi_offset 6, -16
	.cfi_def_cfa_register 6
	sub	rsp, 32
	mov	DWORD PTR [rbp-20], edi
	mov	QWORD PTR [rbp-32], rsi
	cmp	DWORD PTR [rbp-20], 1
	jle	.L2
	mov	DWORD PTR [rbp-4], 1234
	jmp	.L3
.L2:
	mov	DWORD PTR [rbp-4], 4321
.L3:
	mov	esi, OFFSET FLAT:.LC0
	mov	edi, OFFSET FLAT:_ZSt4cout
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	mov	edx, DWORD PTR [rbp-4]
	mov	esi, edx
	mov	rdi, rax
	call	_ZNSolsEi
	mov	esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
	mov	rdi, rax
	call	_ZNSolsEPFRSoS_E
	mov	eax, 0
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc

 分岐命令で割り振るまでは三項演算子と同じ。でもif文の場合はインラインで定数を書いてくれるみたいですね。

 ちなみに、最適化をかけたときの結果は、三項演算子の時と殆ど同じになりました(test2-4)。

残りのテストの概要

 残りのテストの結果は上記の結果から推測できる面白くない結果だったので概要だけ。

  • test3:三項演算子でも実体が書き加えてあればちゃんと動作します。そりゃそうさね。
  • test4-1:テンポラリな変数に三項演算子を代入するのでなく、そのままstd::coutに突っ込むところに直接書きました。でも駄目でした。
  • test4-2:4-1をコンパイルする際に最適化を掛けると、やっぱり大丈夫でした。

 どうやら、三項演算子はif文に展開されたりするわけではなく、別扱いになっていて、最適化フラグが掛からないときは扱いが少し違う…そんな感じでしょうか。gccのソースは読んでないのでわかりませんが…。

 エミュレータで実体を定義し忘れてたのになぜか動いていたのですが、三項演算子を使ったところでコンパイルエラーでした。なお、実体を定義する前にVC++でビルドしてたので、VC++も同じようにやってくれるみたいですが、最適化の結果どうなるのか、とかは確かめてないです。

けつろん!

  • ちゃんと実体書こう!
  • define替わりに使うならenumハックを検討しよう!

 ほんとC++は黒魔術やでぇ…いえ、仕様通り書かない私が悪いんです。でも動いちゃうのもちょっと困るよ…。

  • †1: といっても、そのmakefileも私が書いたんですが^^;

Twitterで住所がバレる!?「Twitter住所特定実験」

Posted on

なにやら物騒なタイトルで。たまにはタイトル一本釣り。

リア充の皆様。AndroidにiPhoneといったスマートフォンを活用してリアルを充実させていらっしゃることと存じます。今回、私どもは、皆様のスマートフォンからTwitterへ送信することができる位置情報から、住所を特定できるのでは無いか、というありがちだけど実際検証した人は居ない†1仮説に基づき、実際にそれを行えるのかどうかやってみました。

今回開発したアプリでは、過去のツイートのうち、位置情報を持っている物を抜き出してGoogleMaps上に配置します。このアプリでは自分の位置情報のみを取得するようにしていますが、誰でも取得できる情報です。

実例と結果

こちらからアクセスしてください。

使い方も解説します。

20110626_01.jpg

この画面で「Twitter経由でログインする!」をクリックすると、このようにOAuthの認証画面が現れます。ログインを押して続行できます。

20110626_02.jpg

ログインに成功すると、このような画面になりますので、「ロード」を押してツイートの読み込みを開始してください。

20110626_03.jpg

うまく読み込むと、こんな感じで地図上にマッピングされます。このψ(プサイ)という人は関東から…出ていないようです…w

20110626_04.jpg

ズームしていくと…つくばエクスプレスユーザーだと一目で分かってしまいますーー;;

20110626_05.jpg

自宅周辺では、たくさんのピンが立っているのが特徴なのはもちろん、こういった自宅でしてるっぽいツイートがあるのも特徴です。

住所を特定するコツ

単に位置情報だけでは通勤・通学で使う駅等で呟いてることが案外多かたりで、それだけで住所まで絞り込むのは難しいです。

ですので、発言内容も手がかりにしてみてください。住所だけじゃなくてよく使うレストランとかも特定できます←父親のアカウントで検証済み、こがねちゃん弁当乙

色々と生活パターンが分かって怖い…^^;こち亀でGPSの大きな装置を背負って生活してもらう!なんて話が100巻~120巻くらいにありましたけど、それが現実になってるんだな~、って感じです。もちろん、背負わなきゃいけないほど大きな装置ではありあませんが。

住所が第三者からでも特定できちゃいそう!どうすれば?

Twitterの設定画面から、あなたの位置情報をすべて消すことができます。

20110626_twitter-acount-settings.jpg

書かれている通り、大体30分くらい時間が必要なようです。何か怖いと感じたら消してしまって良いと思います。

そもそも

位置情報って活用されてるんでしょうか?私はなんとな~くiPhoneの時は毎回入れていましたけど、冷静に考えるとあんまりいらない機能である気がして仕方がありません…w

政情不安とか地震とかの超リアルタイム緊急時だったら案外活用されてるのかな。

その他

sessionでOAuth用のtokenの受け渡しとかが出来るのが便利~。やっぱりフレームワーク使うとサクサクできていいですね~。

  • †1: Google先生調べ

気をつけないと、Ruby on Railsで勝手にDBが書き換えられちゃう?

Posted on

 ふと思いついたネタを。単純なネタですがこの点に関する注意をしている記事を見た記憶がないので。

概要

 scaffoldは気をつけて使わないと、ユーザから上書きされてはいけないフィールドが書き換えられる恐れあり。scaffoldは便利だけど、そのままではすこし危険

scaffoldは便利だけど…

 とりあえずRuby on Railsが有名だと思うのでRuby on Rails(バージョン2.2.3)で書きますが、grailsでも同様の問題がありました。

 さて、まずはscaffoldしないと話になりません:

% rails test
% cd test
% script/generate scaffold User name:string password:string is_admin:boolean

 としてモデル・コントローラ・ビューを自動生成できますよね。このうち、おそらく管理者かどうかをチェックするためにつけたと思われる「is_admin」が、そのままだと一般ユーザから書き換え放題になります。

 どうやるかって?簡単です。newアクションのviewはこうなりますよね。

20100918_01.png

 この時の、生成されるhtmlのうち:

<p>
<label for="user_is_admin">Is admin</label><br />
<input id="user_is_admin" name="user[is_admin]" type="checkbox" value="1" /><input name="user[is_admin]" type="hidden" value="0" />
</p>

 のチェックボックスの<input>とその後のhiddenな<input>が並んだところをコピーして控えておきます。この部分は、このアプリだけでなく他のアプリを攻撃する際にも使い回せます。(name属性だけ書き換えて再利用します。)

 さて。実際のアプリにおいて、is_adminは管理者が設定するフラグであって、ユーザ側から選択できてはマズいですから、削除すると思います。こんな感じで:

<h1>New user</h1>
<% form_for(@user) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :password %><br />
<%= f.text_field :password %>
</p>
//-------------ここから削除------------------
<p>
<%= f.label :is_admin %><br />
<%= f.check_box :is_admin %>
</p>
//-------------ここまで削除------------------
<p>
<%= f.submit "Create" %>
</p>
<% end %>
<%= link_to 'Back', users_path %> 

 そうするとこんな感じの画面になって、一見ユーザからは管理人であるかどうかは選択できなくなりますよね:

20100918_02.png

 実際に、このまま普通に作っても、「Is admin: 」となって、falseになっていることが分かります:

20100918_08.png

 ところがどっこい、動的にHTMLを書き換えてさっきメモした<input>のコードを動的にフォームの中にコピーすると管理者になれちゃいます!w

 FirefoxのFirebugや、Chromeのデベロッパーツールを使えば簡単にHTMLを動的に書き換えることできます。プラグインをインストールする必要が無くて楽なChromeで試してみます。

 デベロッパーメニューはこちらにあります†1: 

20100918_03.png

 次に、動的に書き換えるためにformのところをクリックして、「Edit as HTML」を選びます:

20100918_04.png

 すると編集できるようになるので、先程の<input>のコードを<form>のすぐ次くらいにでも張り付けます:

20100918_05.png

 これで終わりです。適当にエディット領域外をクリックして編集を終了すると…

20100918_06.png

 チェックボックスが出てきます。あとはご想像のとおり。これを黒くチェックして、適当にIDとパスワードを入力すると…

20100918_07.png

 はい、管理者ユーザになれました。

 まあたしかに、is_adminという名前が分からないと書き換えはできないので、ソースコードを公開してないアプリなら即危ないというわけではありませんが、セキュリティ的にはやっぱりマズイと思います。特にオープンソースアプリ…大丈夫でしょうか?

対策

Userコントローラのcreateアクションのうち、

@user = User.new(params[:user])

 この部分を、

@user = User.new(params[:user])
@user.is_admin = false

 にするとか(今確認したらredmineだとこうしてる)、

@user = User.new(:name=> params[:user][:name], :password=> params[:user][:password])

 として、そのアクションで何を書き換えるのかをはっきりさせる、とかでしょうか。

なんだかなあ

 すごく単純なミスですが、実を言うと今まで全く気づかなかったので今回掲載しました^^;UFO大好き霊子さんにもこのミスがあります。証明写真作成工房は修正しておきました。

 出力されるformの中身を自動的にチェックして、formの中身以外のがリクエストに混入してたらアウトって出来ないのかなあ?その方がrailsっぽいよね。Ajaxで動的にフォームを生成するときは、そのための設定をするとか。

 ソースコード読んだら修正できるだろうか?

  • †1: Dev channelなので、多少画面はお手持ちのChromeと異なる可能性があります

[痛デバドラ] /dev/louise_love作ってみた [Linux]

Posted on

全自動無限ルイズコピペ生成器搭載!

これは何?

Linux2.6(32bit/64bit)用デバイスドライバです(2.6.32で動作確認しています)。インストールすると/dev/louise_loveが現れ、読み出すとルイズコピペをマルコフ連鎖アルゴリズムに基づいて無限に自動生成し続けます。

ちなみに/dev/louise_loveへ書き込むとすべて書き込みを読み捨てますので、自分の書いたポエムも安心して読んでもらえます。

デモ

ソースコード

使い方

まずはダウンロードして解凍し、フォルダを移動しましょう。

% git clone https://github.com/ledyba/louise_love_driver.git
% cd louise_love_driver/driver

コンパイルするには、普通のプログラムのようにmakeしてください。

% make

デバイスドライバドライバと同時に、ユーザランドで動くアプリケーションとしてもコンパイルされているので、いきなりデバイスドライバとして組み込むのが不安な方は、安全なユーザランドで試すことができます。それには、

% make test
もしくは
% ./userland_louise_love

として実行してください。

では、実際にデバイスドライバとしてインストールしてみましょう。root権限が必ず必要です。

% sudo make install

おめてとうございます、インストールが完了しました。ルイズたんへの愛を得るにはこのようにしてください。

% cat /dev/louise_love

ゆっくりルイズたんへの愛を噛み締めたい場合は、moreコマンドなどを使うと良いでしょう。

ハードディスクやSSD、USBメモリをルイズたんへの愛で埋める場合には、次のようにしてください(注意:これをやるとHDDのデータが壊れます)。

% dd if=/dev/louise_love of=/dev/sda

/dev/louiseのパーミッションは666なので、書き込みも行うことができます。書き込んだデータはすべてスルーしますので、恥ずかしいポエムも安心して書き込むことができます。

% cat my_poem.txt > /dev/louise_love

昨今はアニメが多いですから、ルイズたんへの100年の恋も冷めてしまうかもしれません(一期やってたのもう4年くらい前だし)。でも大丈夫、アンインストールは次のように簡単にできます。

(インストール時に解凍したフォルダで)
% sudo make uninstall

今後の展開

簡単なソースではあるんで、デバドラやマルコフ連鎖の超簡単なサンプルとしてご活用ください。適当に書いたのでソースコードはむちゃくちゃ汚いですがw なお、/dev/yoshinoyaなども(つくろうと思えば)簡単にできます。

任意の文章に置き換えるには、ユーティリティをダウンロードして、

% ruby make_c_source.rb <文章ファイル> > c_source.txt

として出力結果のc_source.txtの中身を、louise_love.cの該当部分に適当に貼りつけてください。

ちなみに、

% ruby markov.rb <文章ファイル>

とするとrubyを使っていくつか文章を作ってくれます。

反省点

あまりカーネルランドをいじった感が無いのがちょっと…。8割のコードはAPIの名前さえ変えればユーザランドでも動きますし、そもそもデバイスドライバなのに、ハードウェアを触っていません。

解説記事

ちなみに:ユーラランドとカーネルランド時のパフォーマンスの違い

ユーザランド時

% ./userland_louise_love | dd of=/dev/null
1130544+0 記録始め
1130544+0 記録終わり
578838528 バイト (579 MB) コピー終了, 25.5481 s, 22.7 MB/s

カーネルランド時†1

% cat /dev/louise_love | dd of=/dev/null
3619712+0 記録始め
3619712+0 記録終わり
1853292544 バイト (1.9 GB) コピー終了, 25.9583 s, 71.4 MB/s

単純比較していいのかわからないけどさすがカーネルランド3倍はえーw

  • †1: 条件を近づける?ために、一回パイプ通してます。でもddで直接やっても変わらなかった。

プログラミング&セキュリティキャンプ2010参加のおしらせ

Posted on

独立行政法人情報処理推進機構(IPA)の主催する「プログラミング&セキュリティキャンプ2010」の「セキュリティコース/ソフトウェアセキュリティ組」への参加が決定したのでお知らせします。

 てめーらの税金使って遊んでくる†1からwwwwwwwうへえwwwwwwww

 応募用紙に「社会の役に立ちたいといった目的はありません、純然たる知識欲、あるいはハッカーになりたいという思いから応募しました」とはっきり書いた上で選考されたのだから多分問題ないと思います(キリッ

 高校の時から参加したかったんですが受験で参加できず、大学入ってからやっとこさの参加でございます。浮かないかなあ?

 税金で思い出しましたが、大学のMacでアニメ見るとなかなか画面も大きくて迫力があって良いですよ!!なぜ誰も見ていない!!最近は忙しくてなかなか出来ないのですが…orz

 9月にも試験があると書いたばかり…なんか自分で自分の首を絞めている気がする(

 小学校の同窓会と思いっきり被ってるんですが気にしない事にしました(キリッ

 結局ネットがあるし、同窓会っても本当に8年ぶりに会う人って稀なんだよなあ。

  • †1: プログラミング&セキュリティキャンプなら、送料(交通費)・手数料(宿泊費その他)もIPAが負担!!