Javaのバイトコードで遊ぶ (Hello World)
ASMを使ってJavaのバイトコードを生成して遊んでみます。 まずはその準備について。 Javaのバージョンは1.8.0_60、ASMのバージョンは5.0.3です。
生成したバイトコードを実行したり、クラスファイルに書き込みたいので そのためのユーティリティ(ExampleUtil)を準備します。 生成するクラスが持つmainメソッドを実行することにします。
private static class MyClassLoader extends ClassLoader { public Class<?> defineClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } } public static void execMain(String className, byte[] bytes) throws IllegalAccessException, IllegalArgumentException, NoSuchMethodException, SecurityException { MyClassLoader myClassLoader = new MyClassLoader(); Class<?> c = myClassLoader.defineClass(className, bytes); try { c.getMethod("main", String[].class).invoke(null, new Object[] { new String[0] }); } catch (InvocationTargetException e) { e.getCause().printStackTrace(); } } public static void write(String className, byte[] bytes) throws IOException { File dir = new File("target" + File.separator + "output"); if (!dir.exists()) dir.mkdirs(); DataOutputStream out = new DataOutputStream( new FileOutputStream(new File(dir, className + ".class"))); out.write(bytes); out.flush(); out.close(); }
次にバイトコードを生成する処理です。
private static byte[] generate(String className) { ClassWriter cw = new ClassWriter(0); MethodVisitor mv; cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, className, null, "java/lang/Object", null); // constructor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); mv.visitInsn(RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); // main mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); // println mv.visitLdcInsn("Hello world."); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false); mv.visitInsn(RETURN); mv.visitMaxs(2, 1); mv.visitEnd(); cw.visitEnd(); return cw.toByteArray(); }
generateメソッドが返すbyte[]を受け取って、実行したり、ファイルに出力したりする部分。
public static void main(String... args) throws Exception { String className = "HelloWorld"; byte[] bytes = generate(className); ExampleUtil.execMain(className, bytes); ExampleUtil.write(className, bytes); }
これを実行して、「Hello world.」という文字列が表示されればOKです。 出力したクラスファイルを実行するには以下のコマンド。
$ java HelloWorld Hello world.
出力したクラスファイルの中身を覗く場合は以下。オプションは適宜変更すればOKです。
$ javap -v -p -s HelloWorld Classfile /home/vagrant/sandbox/workspaces/asm_practice/AsmPlaying/target/output/HelloWorld.class Last modified 2015/10/13; size 341 bytes MD5 checksum 01828f9e45f7fe516438e7d3c51b04f4 public abstract class HelloWorld minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Utf8 HelloWorld #2 = Class #1 // HelloWorld #3 = Utf8 java/lang/Object #4 = Class #3 // java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = NameAndType #5:#6 // "<init>":()V #8 = Methodref #4.#7 // java/lang/Object."<init>":()V #9 = Utf8 main #10 = Utf8 ([Ljava/lang/String;)V #11 = Utf8 java/lang/System #12 = Class #11 // java/lang/System #13 = Utf8 out #14 = Utf8 Ljava/io/PrintStream; #15 = NameAndType #13:#14 // out:Ljava/io/PrintStream; #16 = Fieldref #12.#15 // java/lang/System.out:Ljava/io/PrintStream; #17 = Utf8 Hello world. #18 = String #17 // Hello world. #19 = Utf8 java/io/PrintStream #20 = Class #19 // java/io/PrintStream #21 = Utf8 println #22 = Utf8 (Ljava/lang/Object;)V #23 = NameAndType #21:#22 // println:(Ljava/lang/Object;)V #24 = Methodref #20.#23 // java/io/PrintStream.println:(Ljava/lang/Object;)V #25 = Utf8 Code { public HelloWorld(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #18 // String Hello world. 5: invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 8: return }
これをベースに次回からいろいろなバイトコードを生成して遊んでみたいと思います。 最後にコードの全体を載せておきます。
ExampleUtil
package example; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; public class ExampleUtil { private static class MyClassLoader extends ClassLoader { public Class<?> defineClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } } public static void execMain(String className, byte[] bytes) throws IllegalAccessException, IllegalArgumentException, NoSuchMethodException, SecurityException { MyClassLoader myClassLoader = new MyClassLoader(); Class<?> c = myClassLoader.defineClass(className, bytes); try { c.getMethod("main", String[].class).invoke(null, new Object[] { new String[0] }); } catch (InvocationTargetException e) { e.getCause().printStackTrace(); } } public static void write(String className, byte[] bytes) throws IOException { File dir = new File("target" + File.separator + "output"); if (!dir.exists()) dir.mkdirs(); DataOutputStream out = new DataOutputStream( new FileOutputStream(new File(dir, className + ".class"))); out.write(bytes); out.flush(); out.close(); } }
HelloWorld
package example; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class HelloWorld implements Opcodes { private static byte[] generate(String className) { ClassWriter cw = new ClassWriter(0); MethodVisitor mv; cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, className, null, "java/lang/Object", null); // constructor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); mv.visitInsn(RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); // main mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); // println mv.visitLdcInsn("Hello world."); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false); mv.visitInsn(RETURN); mv.visitMaxs(2, 1); mv.visitEnd(); cw.visitEnd(); return cw.toByteArray(); } public static void main(String... args) throws Exception { String className = "HelloWorld"; byte[] bytes = generate(className); ExampleUtil.execMain(className, bytes); ExampleUtil.write(className, bytes); } }
Fixity Resolution
https://www.haskell.org/onlinereport/haskell2010/haskellch10.html#x17-18100010.6
Haskellのリファレンスに掲載されている 結合性の解決方法を示したコードを動作確認のために動かした時のメモ。
以下は上記のページに掲載されているコード。
import Control.Monad type Prec = Int type Var = String data Op = Op String Prec Fixity deriving (Eq,Show) data Fixity = Leftfix | Rightfix | Nonfix deriving (Eq,Show) data Exp = Var Var | OpApp Exp Op Exp | Neg Exp deriving (Eq,Show) data Tok = TExp Exp | TOp Op | TNeg deriving (Eq,Show) resolve :: [Tok] -> Maybe Exp resolve tokens = fmap fst $ parseNeg (Op "" (-1) Nonfix) tokens where parseNeg :: Op -> [Tok] -> Maybe (Exp,[Tok]) parseNeg op1 (TExp e1 : rest) = parse op1 e1 rest parseNeg op1 (TNeg : rest) = do guard (prec1 < 6) (r, rest') <- parseNeg (Op "-" 6 Leftfix) rest parse op1 (Neg r) rest' where Op _ prec1 fix1 = op1 parse :: Op -> Exp -> [Tok] -> Maybe (Exp, [Tok]) parse _ e1 [] = Just (e1, []) parse op1 e1 (TOp op2 : rest) -- case (1): check for illegal expressions | prec1 == prec2 && (fix1 /= fix2 || fix1 == Nonfix) = Nothing -- case (2): op1 and op2 should associate to the left | prec1 > prec2 || (prec1 == prec2 && fix1 == Leftfix) = Just (e1, TOp op2 : rest) -- case (3): op1 and op2 should associate to the right | otherwise = do (r,rest') <- parseNeg op2 rest parse op1 (OpApp e1 op2 r) rest' where Op _ prec1 fix1 = op1 Op _ prec2 fix2 = op2
以下はテスト用のトークンを適当に定義したコード。
plus = Op "+" 6 Leftfix minus = Op "-" 6 Leftfix ast = Op "*" 7 Leftfix slash = Op "/" 7 Leftfix app = Op "->" 1 Rightfix rplus = Op "+." 6 Rightfix a = Var "a" b = Var "b" c = Var "c" d = Var "d" -- a + b +. c -- -> illegal expressions tokens1 = [TExp a, TOp plus, TExp b, TOp rplus, TExp c] -- a + b - c -- -> (a + b) - c tokens2 = [TExp a, TOp plus, TExp b, TOp minus, TExp c] -- a -> b -> c -- -> a -> (b -> c) tokens3 = [TExp a, TOp app, TExp b, TOp app, TExp c] -- -a + b -- -> (-a) + b tokens4 = [TNeg, TExp a, TOp plus, TExp b] -- a + -b -- -> illegal expressions tokens5 = [TExp a, TOp plus, TNeg, TExp b] -- a + b * c - d -- -> (a + (b * c)) - d tokens6 = [TExp a, TOp plus, TExp b, TOp ast, TExp c, TOp minus, TExp d] -- a + b -> c -- -> (a + b) -> c tokens7 = [TExp a, TOp plus, TExp b, TOp app, TExp c] -- a -> b + c -- -> a -> (b + c) tokens8 = [TExp a, TOp app, TExp b, TOp plus, TExp c]
このコードをghciで読み込み、以下のように入力すれば結果が返ってくる。
resolve tokens1 resolve tokens2 ...
xkbを使って修飾キー有のキーバインドを設定する
Windowsのキーバインド設定ツールとして以前から KeyExtension というフリーソフトを使わせてもらっています。 変換キー + U でHome、変換キー + H でLeft、といった感じの設定ができます。 Vimでコードを書く時は困らないのですが、Eclipseなど他のエディタで作業するような時は カーソル移動が楽になるので大変愛用しています。
これと同じことを Elementary OS で実現できないかと調べたところ、 xkbを使うことで可能になるようです。早速試してみました。
設定したいキーバインドは以下です。
キー | 動作 |
---|---|
変換キー + U | Home |
変換キー + O | End |
変換キー + H | Left |
変換キー + J | Down |
変換キー + K | Up |
変換キー + L | Right |
変換キー + ; | Delete |
変換キー + B | BackSpace |
変換キー + N | Return |
まずは、変換キーを押した時に修飾キー(Mod5)として認識させます。
$ cat ~/.xkb/compat/mycompat xkb_compatibility "henkan" { interpret Henkan_Mode { action = SetMods(modifiers=Mod5); }; };
キーバインドに使う型として、Shiftと変換キー(Mod5)を組み合わせた型として定義します。 Shift+の時はLevel2に設定した動作、変換キー+およびShift+変換キー+*の時はLevel3に設定した動作をします。
$ cat ~/.xkb/types/mytypes xkb_types "henkan" { type "HENKAN" { modifiers = Shift+Mod5; map[Shift] = Level2; map[Mod5] = Level3; map[Shift+Mod5] = Level3; level_name[Level1] = "Base"; level_name[Level2] = "Shift"; level_name[Level3] = "Henkan"; }; };
キーバインドを設定します。
左から順に、修飾キーなし(Level1)、Shiftキーあり(Level2)、変換キーあり(Level3)、の動作を設定します。
HomeやBackSpaceのところに何を書けば良いかわからないときは
$ xev
で目的のキーを打ち込んでみて表示されるkeysymを確認すれば良いようです。
$ cat ~/.xkb/symbols/mysymbols xkb_symbols "henkanmodekeys" { key <AD07> { type="HENKAN", [ u, U, Home ] }; key <AD09> { type="HENKAN", [ o, O, End ] }; key <AC06> { type="HENKAN", [ h, H, Left ] }; key <AC07> { type="HENKAN", [ j, J, Down ] }; key <AC08> { type="HENKAN", [ k, K, Up ] }; key <AC09> { type="HENKAN", [ l, L, Right ] }; key <AC10> { type="HENKAN", [ semicolon, plus, Delete ] }; key <AB05> { type="HENKAN", [ b, B, BackSpace ] }; key <AB06> { type="HENKAN", [ n, N, Return ] }; };
以上の設定を組み込むための定義を行います。 ベースとなる設定ファイルを生成して修正します。
$ setxkbmap -print > ~/.xkb/keymap/mykbd $ cat ~/.xkb/keymap/mykbd xkb_keymap { xkb_keycodes { include "evdev+aliases(qwerty)" }; xkb_types { include "complete+mytypes(henkan)" }; xkb_compat { include "complete+japan+mycompat(henkan)" }; xkb_symbols { include "pc+jp+inet(evdev)+mysymbols(henkanmodekeys)" }; xkb_geometry { include "pc(pc105)" }; };
テストします。
$ xkbcomp -I$HOME/.xkb ~/.xkb/keymap/mykbd $DISPLAY
ログイン時に反映させるために~/.bashrcに以下を追記します。
xkbcomp -I$HOME/.xkb ~/.xkb/keymap/mykbd $DISPLAY 2> /dev/null
以下のページが大変参考になりました。
Elementary OSにEclipseを導入する
インストール自体はファイルをダウンロードして適当な場所に配置すれば良いです。
ただEclipseのツールチップが、背景色も文字色も黒くなってしまい、全く内容が読めず 使い物になりませんでした。
ウェブを検索したところ、次のページを見つけました。
http://elementary.io/answers/tooltip_color
Themeの設定ファイルを書き換えるのと、起動時の環境変数を設定する必要がありました。
Themeの設定ファイルを変更
$ sudo vim /usr/share/themes/elementary/gtk-2.0/gtkrc
背景色と文字色を変更します。
gtk_color_scheme = "tooltip_bg_color:#000\ntooltip_fg_color:#FFF"
を
gtk_color_scheme = "tooltip_bg_color:#F5F5C5\ntooltip_fg_color:#000"
に修正します。
起動時の環境変数を設定する
SWT_GTK3=0
という環境変数を渡す必要があります。
$ env SWT_GTK3=0 /home/vagrant/Programs/Eclipse/eclipse-luna/eclipse
これで解消しました。
デスクトップアプリケーションとして登録する
ついでにデスクトップアプリケーションとして登録しておきます。
***.desktop
というファイルを作成すれば良いです。
$ sudo vim /usr/share/applications/eclipse.desktop
内容は次のとおりです。
[Desktop Entry] Name=Eclipse GenericName=IDE Comment=Development programs TryExec=/home/vagrant/Programs/Eclipse/eclipse-luna/eclipse Exec=env SWT_GTK3=0 /home/vagrant/Programs/Eclipse/eclipse-luna/eclipse Terminal=false Type=Application Icon=/home/vagrant/Programs/Eclipse/eclipse-luna/icon.xpm Categories=Utility;TextEditor; StartupNotify=true MimeType=text/plain;
これによりLauncherに表示されるようになります。Dockへの登録も可能になります。
Elementary OSをセットアップする
Elementary OSを自分用にセットアップした時のメモです。 英語版のまま使い、日本語が入力できれば良いという程度の要件です。
事前準備
VirtualBoxのゲストOSとして使う場合は、3Dアクセラレーションを
有効化しておきます。これをしておかないと、OS起動中ずっと
Gala
という Window Manager
がCPUを使いまくるためです。
全パッケージ更新
全パッケージでなくとも良いですが、最低限 slingshot-launcher
は
0.7.6 以上にアップグレードしておきます。ibus
などのインプットメソッド起動中に
検索文字を入力できないというバグを解消するためです。
$ sudo apt-get update $ sudo apt-get -s dist-upgrade $ sudo apt-get -u dist-upgrade
言語設定
System Settings
の Language Support
を起動すると、ダイアログが表示され
追加パッケージのインストールを聞かれるのでインストールします。
その後、言語に日本語を追加します。ibus-anthy
などのパッケージがさらに
インストールされます。
Mozcを使いたいので ibus-mozc
をインストールします。
$ sudo apt-get install ibus-mozc $ ibus-setup
フォント設定
Migu 1M
というフォントがお気に入りなのでインストールします。
http://mix-mplus-ipa.sourceforge.jp/migu/
システムフォントの設定
elementary-tweaks
をインストールします。
$ sudo apt-add-repository ppa:versable/elementary-update $ sudo apt-get update $ sudo apt-get install elementary-tweaks
System Settings
に Tweaks
というメニューが追加されるので
それを使ってフォントサイズなどを調整します。
その他の追加ソフトウェア
よく使うものを追加しておきます。
- システムモニター
$ sudo apt-get install gnome-system-monitor
Dock整理
Dockを整理します。
VagrantのBoxを作成する
VagrantのBoxを作成してみました。 Windows8.1なのでHyper-Vでやろうかと思いましたが、少し試してみたところ上手くいかなかったのでおとなしくVirtualBoxを使っています。
https://docs.vagrantup.com/v2/boxes/base.html
https://docs.vagrantup.com/v2/virtualbox/boxes.html
仮想マシンの作成
VirtualBoxでマシンを新規に作成します。
ディスク
ディスクの種類はVDIでもVMDKでも良いようです。VDIで作成しても、Boxにした際に VMDKに変換されたので最初からVMDKが良いかも。 サイズ可変で最大サイズで作る、とありますが最大サイズがよくわからないので可変サイズで128GBくらいで。
メモリ
デフォルトを大きくしすぎないように、ということなので512MBで。
周辺機器
オーディオやUSBのような不要なハードウェアは無効化しておくように、とのこと。
OSのインストール
インストールのISOイメージをドライブにセットして起動。 Elementary OS (Luna 64bit)でやりました。
ユーザー設定
vagrantというユーザーを作成します。パスワードもvagrantです。 rootのパスワードもvagrantにしておくと良いようです。
ダミーの公開鍵 を
~/.ssh/authorized_keys
に追記しておきます。パーミッションは ~/.ssh
が 0700
、
~/.ssh/authorized_keys
が 0600
です。
パスワードなしでsudoできるようにしておきます。
$ sudo update-alternatives --config editor $ sudo visudo
以下の行を末尾に追記します。
vagrant ALL=(ALL) NOPASSWD: ALL
SSHサーバー導入
SSHサーバーをインストールします。
$ sudo apt-get install ssh sysv-rc-conf $ sudo sysv-rc-conf ssh on $ sudo sysv-rc-conf --list | grep ssh
VirtualBox Guest Additionsのインストール
追加プログラムをインストールします。
$ sudo apt-get install linux-headers-generic build-essential dkms $ sudo mkdir /media/cdrom $ sudo mount /dev/cdrom /media/cdrom $ sudo sh /media/cdrom/VBoxLinuxAdditions.run $ sudo reboot
後述しますが、これではインストールが正しく完了しておらず VirtualBox Guest Additionsを再セットアップする必要がありました。 念を入れてこのタイミングでやっておくと良いかなと思いました。
$ sudo /etc/init.d/vboxadd setup
Vagrant Boxの作成
ここからはゲストOSをシャットダウンして、ホストOSで作業します。
Boxの作成
{vm name}
はVirtualBoxでつけたマシンの名前です。
{/path/to/box}
は作成するBoxの出力先です。
-- vagrant package --base {vm name} --output {/path/to/box} $ vagrant package --base elementary-64 --output elementary-64.box
Boxの登録
{box name}
はBoxの登録名です。
-- vagrant box add {box name} {/path/to/box} $ vagrant box add elementary-64-minimal elementary-64.box $ vagrant box list
Boxからマシンを作成
適当なディレクトリを作成しBoxからマシンを作成します。
$ mkdir elem $ cd elem -- vagrant init {box name} $ vagrant init elementary-64-minimal
起動・停止
$ vagrant up $ vagrant halt
起動時にエラーが出る場合は、VirtualBox Guest Addtionsを再セットアップ
起動時に共有フォルダのマウントでエラーが出ました。
Failed to mount folders in Linux guest. This is usually because
the "vboxsf" file system is not available. Please verify that
the guest additions are properly installed in the guest and
can work properly. The command attempted was:mount -t vboxsf -o uid=
id -u vagrant
,gid=getent group vagrant | cut -d: -f3
vagrant /vagrant
mount -t vboxsf -o uid=id -u vagrant
,gid=id -g vagrant
vagrant /vagrantThe error output from the last command was:
stdin: is not a tty
/sbin/mount.vboxsf: mounting failed with the error: No such device
ゲストOSに戻り、VirtualBox Guest Addtionsを再セットアップ、Boxの再作成を行うことで解決しました。
$ sudo /etc/init.d/vboxadd setup
びわいち
京都に住む友人と琵琶湖サイクリングに行きました。
まずは京都の橋から。ここから1号線を通って大津へ向かいます。
近江大橋のそばで朝食。雨ばかりの8月でしたがこの日は快晴。楽しいライドとなる予感がします。 大津でも路面電車が走っているんですね。親近感を覚えました。
湖岸通りを快調にとばします。交通量が多く、多少肩身の狭い思いをしながら走りました。
ここでサイコンでエラーが発生。初期化されデータが全て消失。もう何回目だ... おかげで自分の総走行距離はさっぱり分かりません。
ピエリ守山。リニューアル中?
脇に見える湖の景色が気持ちいいです。200kmの予定なのでのんびりしてもいられないのに、ついつい写真を撮ってしまいます。
昼食は長浜の翼果楼で焼鯖そーめんと焼鯖寿司。行列になる前に入れました。
昼食後は湖の北側。少し湖から離れた場所を走ります。
こんな感じで軽快に走っていましたが、どうも友人の様子がおかしい。 足の限界が近いなどと言う。まだ半分近く残っているよ…ペースを抑えてゆっくり走ります。
白髭神社。
と、とうとう比良で友人がギブアップ。 びわいち(琵琶湖一周)は諦めて輪行で京都へ戻りました。
走行距離は163km。 交通量が多くヒヤヒヤすることもありましたが、ほぼ平坦な道が続き走りやすかったです。 一周できなかったのは残念。また機会があれば。
夕飯は馬野郎で打ち上げ。 馬肉と友人の練習不足ぶりを肴に楽しく過ごしました。