リアルモードでも32bitのレジスタを使うことができる

この記事は自作OS Advent Calendar 2018の21日目の記事です。

自作OSのアドカレなので、まずは作っているCPUエミュの話をしましょう。

CPUエミュを作っている話

そもそもなぜCPUエミュを作っているのかというと、 Golangで書かれたファミコンのエミュのコードを読んでめっちゃおもしろいなって思ったのが、 CPUエミュを作ってみようと思い始めたきっかけでした。

github.com

それで一番馴染みがあるアーキテクチャであるx86を対象にGolangx86のCPUエミュ「cibo」を作りはじめました。 x86/x64を対象にしたソフトウェアエミュレータは、Type1ハイパーバイザーがあるのであまり需要がない気はしますが、 はじめて作るCPUエミュは親しみのあるアーキテクチャの方がいいでしょう。 ciboという名はBLAMEの生電社の主任科学者シボさんから取っています。 「久しぶりだな頭取!」って言って霧亥を助けに来るシーン、かっこいいですよね。 github.com

ひとまずQEMUBochsで使われているBIOSである、SeaBIOSを動作させることを目標に開発しています。 今のところ、NASMでアセンブルされた簡単なバイナリを走らせることができるくらいの進捗です。早くVGAあたりをエミュレートしてカラーの画面を出したいですね

$ cibo --debug ./samples/asm/simple_call
EIP = 0x7C00, Opcode = 0xB8
EIP = 0x7C05, Opcode = 0xBB
EIP = 0x7C0A, Opcode = 0xE8
EIP = 0x7C14, Opcode = 0x89
EIP = 0x7C16, Opcode = 0xB8
EIP = 0x7C1B, Opcode = 0xC3
EIP = 0x7C0F, Opcode = 0xE9
No mapping area: 0x0
==================== registers ====================
01: EAX = 0x1011
02: ECX = 0x0
03: EDX = 0x0
04: EBX = 0xF1
05: ESP = 0x7C04
06: EBP = 0x0
07: ESI = 0x0
08: EDI = 0x0
09: EIP = 0x0
10: CS = 0x0
11: DS = 0x0
12: SS = 0x0
13: ES = 0x0
14: FS = 0x0
15: GS = 0x0
16: EFLAGS = 0x2 (00000000000000000000000000000010)
17: MM0 = 0x0
18: MM1 = 0x0
19: MM2 = 0x0
20: MM3 = 0x0
21: MM4 = 0x0
22: MM5 = 0x0
23: MM6 = 0x0
24: MM7 = 0x0

また、Unicornのようにコードからも操作できるようにしています。

package main

import (
    "github.com/tkmru/cibo/core"
)

func main() {
    beginAddress := 0x7c00
    emu := cibo.NewEmulator(32, beginAddress, 29, false, true)
    cpu := emu.CPU
    reg := &cpu.X86registers

    emu.RAM = []byte{0xb8, 0x01, 0x00, 0x00, 0x00, 0x3d, 0x02, 0x00, 0x00, 0x00, 0x75, 0x05, 0xe9, 0xef, 0x83, 0xff, 0xff, 0xb8, 0x02, 0x00, 0x00, 0x00, 0x3d, 0x02, 0x00, 0x00, 0x00, 0x74, 0xef}
    /*
       mov eax, 0x1
       cmp eax, 0x2
       jnz not_equal

     equal:
       jmp 0

     not_equal:
       mov eax, 0x2
       cmp eax, 0x2
       jz equal
   */

    reg.Init()
    emu.Run()
}

テストはmulti-architecture assembler frameworkであるKeystoneGolangバインディングと標準パッケージのtestingを使って書いています。 Golangを使ってCPUエミュのテストを書くにはこれがいいと思います。実際のテストコードはこれです。

うっかり自作CPUエミュの話が長くなってしまいましたが、自作OSのアドカレの記事なのでまあよしとしましょう。 そろそろ本題に入ります。

BIOSとは

特に説明をせずBIOSについて言及していましたが、ここで説明していきます。 BIOSというのは、Basic Input/Output Systemの略でPCの電源を入れた直後の初期化や HDDから最初のプログラムローダをメモリに読み出す役割を持つプログラムです。

BIOSはリアルモードという16bitで動作するモードで動き、 周辺機器に関する処理を行ったあと、MBR(Master Boot Record)やPBR(PartiPartition Boot Record)といったブートセクタを読み出し、 32bit/64bitで動くOSに動作を移します。

このようなBIOSですが、16bitでしか動作しないので、32bit/64bitで動作するUEFIに取って代わられようとしています。 Intel CPUでは 2020年までに CSM (BIOS 互換機能) を撤廃して UEFI に完全移行する方針のようで、 近いうちにBIOSが搭載されたデバイスは見かけなくなってしまうようです。

neriring.hatenablog.jp

なぜ、そのうちなくなるであろうBIOSを自作CPUエミュで動かそうとしているのかというと、いきなりx64のエミュを実装するのは大変かなと思ったからです。ところで、seabiosを自作CPUエミュで動かそうと思うと、seabios内で使われている命令すべてを自作エミュ上で動作するようにしておく必要があります。そのため、どんな命令が使われているのか知るためにIDAでseabiosのバイナリをディスアセンブルしてみたところ、リアルモードでも32bitのレジスタは読み書きされていることに気づきました。

リアルモードで32bitのレジスタ???

下の画像を見ると32bitのレジスタを使っている命令は先頭に0x66というopcodeがついているのが分かると思います。 0x66というopcodeはOperand-size override prefixという役割を持っていて、オペランドのサイズを上書きできる接頭辞です。 つまり、リアルモードではopcode 0x66を使うことで32bitのレジスタを読み書きできるのです!!

f:id:TAKEmaru:20181220015606p:plain
seabiosのバイナリをIDAでディスアセンブルした結果の一部

これを知って、私の好きなopcodeは0x66になりました。 16bitで動作している環境で32bitのレジスタが使えるというのはとてもおもしろいですね。 わたしはこれを自分で発見してとても驚いたのですが、 Wikipediaに書かれていたので自明なことだったようです....

PinなどのDBIツールでトレースした結果をIDAにマッピングするプラグイン「Lighthouse」を使ってみた

Lighthouseとは

Lighthouse とは、DBIツールでトレースすることで得たコードカバレッジデータを見やすくIDA内の独自のViewに表示し、IDAのDisassembly View、Graph ViewにマッピングしてくれるIDAプラグインである。

github.com

DBI(Dynamic Binary Instrumentation)は実行時にバイナリの内容を書き換えることによって、CPU命令の単位でトレースなどを行う手法である。 Lighthouseは、PinDynamoRIOFridaの3種類のDBIツールに対応している。この記事ではPinを用いた。

インストールする

git cloneして/plugin以下にあるlighthouse_plugin.pyとlighthouseフォルダをIDAの/pluginsフォルダに入れるとインストールできる。 以下はmacOSでのコマンド例だが、OSが違ってもパスが変わるくらいでそんなに変わらないはず。

$ git clone git@github.com:gaasedelen/lighthouse.git
$ cp -R lighthouse/plugin/ /Applications/IDA\ Pro\ 7.1/ida64.app/Contents/MacOS/plugins/

また、PinのログフォーマットをLighthouseで扱える形式にするためのpintoolをビルドしておく必要がある。64bitのMacOSだと、/lighthouse/coverage/pin/obj-intel64/CodeCoverage.dylibが生成される。

$ export PIN_ROOT=~/pin # Pinのインストールパスを指定
$ export PATH=$PATH:$PIN_ROOT
$ cd ~/lighthouse/coverage/pin
$ make
mkdir -p obj-intel64
/Applications/Xcode.app/Contents/Developer/usr/bin/make objects
make[1]: Nothing to be done for `objects'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make libs
make[1]: Nothing to be done for `libs'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make dlls
make[1]: Nothing to be done for `dlls'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make apps
make[1]: Nothing to be done for `apps'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make tools
...

使ってみる

Pinを使って解析したいプログラムをトレースする。 ここでは、最近、Golangで書いているCPUエミュ cibo をPinにかけた。 実行結果はtrace.logとしてファイルに出力される。ちなみにgolangのバイナリの構造はこれがくわしい。

$ pin -t obj-intel64/CodeCoverage.dylib -- cibo hoge/samples/asm/simple_call
CodeCoverage tool by Agustin Gianni (agustingianni@gmail.com)
Logging code coverage information to: trace.log
Loaded image: 0x1000000:0x12fe707 -> cibo
Loaded image: 0x59e1000:0x5a7efff -> dyld
Loaded image: 0x7fff6a7ed000:0x7fff6a820fff -> libclosured.dylib
Loaded image: 0x7fff6acff000:0x7fff6ad00fff -> libSystem.B.dylib
Loaded image: 0x7fff6af33000:0x7fff6af89fff -> libc++.1.dylib
Loaded image: 0x7fff6af8a000:0x7fff6afaefff -> libc++abi.dylib
Loaded image: 0x7fff6c300000:0x7fff6c6eefff -> libobjc.A.dylib
Loaded image: 0x7fff6cd9b000:0x7fff6cd9ffff -> libcache.dylib
Loaded image: 0x7fff6cda0000:0x7fff6cdaafff -> libcommonCrypto.dylib
Loaded image: 0x7fff6cdab000:0x7fff6cdb2fff -> libcompiler_rt.dylib
Loaded image: 0x7fff6cdb3000:0x7fff6cdbbfff -> libcopyfile.dylib
Loaded image: 0x7fff6cdbc000:0x7fff6ce41fff -> libcorecrypto.dylib
Loaded image: 0x7fff6cec9000:0x7fff6cf02fff -> libdispatch.dylib
Loaded image: 0x7fff6cf03000:0x7fff6cf20fff -> libdyld.dylib
Loaded image: 0x7fff6cf21000:0x7fff6cf21fff -> libkeymgr.dylib
Loaded image: 0x7fff6cf2f000:0x7fff6cf2ffff -> liblaunch.dylib
Loaded image: 0x7fff6cf30000:0x7fff6cf34fff -> libmacho.dylib
Loaded image: 0x7fff6cf35000:0x7fff6cf37fff -> libquarantine.dylib
Loaded image: 0x7fff6cf38000:0x7fff6cf39fff -> libremovefile.dylib
Loaded image: 0x7fff6cf3a000:0x7fff6cf51fff -> libsystem_asl.dylib
Loaded image: 0x7fff6cf52000:0x7fff6cf52fff -> libsystem_blocks.dylib
Loaded image: 0x7fff6cf53000:0x7fff6cfdcfff -> libsystem_c.dylib
Loaded image: 0x7fff6cfdd000:0x7fff6cfe0fff -> libsystem_configuration.dylib
Loaded image: 0x7fff6cfe1000:0x7fff6cfe4fff -> libsystem_coreservices.dylib
Loaded image: 0x7fff6cfe5000:0x7fff6cfe6fff -> libsystem_darwin.dylib
Loaded image: 0x7fff6cfe7000:0x7fff6cfedfff -> libsystem_dnssd.dylib
Loaded image: 0x7fff6cfee000:0x7fff6d037fff -> libsystem_info.dylib
Loaded image: 0x7fff6d038000:0x7fff6d05dfff -> libsystem_kernel.dylib
Loaded image: 0x7fff6d05e000:0x7fff6d0a9fff -> libsystem_m.dylib
Loaded image: 0x7fff6d0aa000:0x7fff6d0c9fff -> libsystem_malloc.dylib
Loaded image: 0x7fff6d0ca000:0x7fff6d16efff -> libsystem_network.dylib
Loaded image: 0x7fff6d16f000:0x7fff6d179fff -> libsystem_networkextension.dylib
Loaded image: 0x7fff6d17a000:0x7fff6d183fff -> libsystem_notify.dylib
Loaded image: 0x7fff6d184000:0x7fff6d18bfff -> libsystem_platform.dylib
Loaded image: 0x7fff6d18c000:0x7fff6d197fff -> libsystem_pthread.dylib
Loaded image: 0x7fff6d198000:0x7fff6d19bfff -> libsystem_sandbox.dylib
Loaded image: 0x7fff6d19c000:0x7fff6d19dfff -> libsystem_secinit.dylib
Loaded image: 0x7fff6d19e000:0x7fff6d1a5fff -> libsystem_symptoms.dylib
Loaded image: 0x7fff6d1a6000:0x7fff6d1b9fff -> libsystem_trace.dylib
Loaded image: 0x7fff6d1bb000:0x7fff6d1c0fff -> libunwind.dylib
Loaded image: 0x7fff6d1c1000:0x7fff6d1edfff -> libxpc.dylib

生成されたtrace.logをIDAのメニューバーFile -> Load file -> Code coverage fileより読み込む。 f:id:TAKEmaru:20181011233401p:plain

Coverage OverviewというカスタムViewが表示される。Golang製のバイナリで試したため、Coverage率の上位はruntime系の関数が大部分を占めている。シンボルがないバイナリでは、Coverage率が上位の関数をザッと見るのは処理系によっては効率悪いかも。

f:id:TAKEmaru:20181011183102p:plain

フィルタすることもできる。パッケージ名になっているコマンド名でフィルタすると本質っぽい関数だけが表示されていいかんじだった。

f:id:TAKEmaru:20181011183127p:plain

Disassembly View、Graph Viewの中で実際に実行された部分に色をつけてくれる。これは本質ではないアセンブリを読み飛ばすことができるので、すごく便利そう。 f:id:TAKEmaru:20181011183140p:plain

f:id:TAKEmaru:20181011194339p:plain

感想

DBIツールによる動的解析結果を、IDAでの静的解析時に効果的に利用できる点がすばらしいと感じた。 Disassembly View、Graph Viewの中で実際に実行された部分に色をつけてくれるので、読まなくていいアセンブリを無駄に読むことがなくなり解析の効率をあげられそうであった。

また、今回は試してないが複数のトレースログの差分を表示することもできるようで、これによってシンボルがないバイナリでも、Coverage Overviewに本質のルーチンだけをフィルタして表示できそうである。そのうち試してみたい。

Rails appのRuboCopのconfigにはGitHub社で使われているものを使うとよさそう

RuboCopとは

RuboCopRuby向けのコードフォーマッターで、書いたコードがスタイルガイドに準拠しているかシュッとチェックできる。

github.com

-aでauto fixすることができてとても便利であるが、  結構うるさいのでconfigである.rubocop.ymlを調整しないと使いづらい。 

rubocop-github

自分でフォーマッターの出力結果を見ながら、どのコーディングルールをパスするか、ゆるくするかをconfigに書くのはとてもめんどくさい。 なにかいい.rubocop.ymlがないか探してみると、 GitHubが社内で使用しているRuboCopの設定ファイルを github/rubocop-github: Code style checking for GitHub Ruby repositoriesとして公開していた。

github.com

普通のRuboCopで使われているチェック項目をカスタムしているのに加え、 Rails向けのルール(rubocop-github/lib/rubocop/cop/github)が追加されていてとてもよさそう。

導入方法

以下の行をGemfileに追加し、bundle installするとインストールされる。

gem "rubocop-github"

インストールすると設定ファイルが追加される。 これらを.rubocop.ymlに追加すると使えるようになる。

inherit_gem:
  rubocop-github:
    - config/default.yml
    - config/rails.yml

使い方

普通のRuboCopと同じくrubocopコマンドで使える。

$ rubocop

快適なRuboCopライフを送っていきましょう!!

Android/iOS端末、アプリを扱うのに便利なコマンド、aliasたち

Android/iOS端末、アプリについて取得したい情報があるとき、ついついAndroidiPhoneの端末のUIからがんばってしまいがちだが、 コマンドでやると楽に取得できる。 そんなときに使える、Android/iOSアプリを開発するときや、解析するときに便利なコマンド、aliasたちのまとめ。 普段つかっているのがmacOSなので、macOSでしか動作確認してないです。

Android端末向け

端末のipアドレスを取得する

.bashrcに以下のようにaliasを貼っている。

alias androidip='adb -d shell ip addr show wlan0 | egrep -o "10\.[^\. ]*.[^\. ]*\.[^\. ]*| 172\.[^\. ]*.[^\. ]*\.[^\. ]*| 192\.168\.[^\. ]*\.[^\. ]*" | head -n 1'

以下のように使える。

$ androidip
10.4.56.8/21

スクリーンショットを取る

.bashrcに以下のように関数を定義している。

function androidscreenshot () {
  timestamp=$(date +"%Y-%m-%d-%H-%M-%S")
  adb shell screencap -p /data/local/tmp/screenshot-${timestamp}.png
  adb pull /data/local/tmp/screenshot-${timestamp}.png
  adb shell rm /data/local/tmp/screenshot-${timestamp}.png
}

以下のように使える。

$ androidscreenshot 
/data/local/tmp/screenshot-2018-09-24-18-34-52.png: 1 file pulled. 15.0 MB/s (1999962 bytes in 0.127s)

.apkのパッケージ名を取得する

.bashrcに以下のように関数を定義している。aaptコマンドの結果をgrepしてるだけ。aaptコマンドの使い方を覚えられないので関数にしている。

function packagename () {
  aapt l -a $1 | grep "A: package"
}

以下のように使える。

$ packagename hoge.apk
    A: package="com.xxx.hoge" (Raw: "com.xxx.hoge")

.apkのパーミッションを取得する

.bashrcに以下のように関数を定義している。これもaaptコマンドの結果をgrepしてるだけ。

function apkpermission () {
  aapt l -a $1 | grep -i permission
}

以下のように使える。

$ apkpermission hoge.apk
    E: uses-permission (line=14)
      A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")

iOS端末向け

スクリーンショットを取る

libimobiledevice に付属するコマンドを使う。

www.libimobiledevice.org

macOSではhomebrewからインストールできる。

$ brew install libimobiledevice

以下のように使える。

$ idevice_id -l # 接続できてるか確認
7a54172f5b3c2ab46372fb71127fa7b4de8d9d9e
$ idevicescreenshot
Screenshot saved to screenshot-2018-09-24-09-23-58.png

.ipaのインストールパスを取得する

IDAに付属しているios_deployコマンドを使う。IDAを持っている人はIDA Support: Download Centerからダウンロードできる。持っていない人は他のツールからがんばりましょう。

www.hex-rays.com

ios_deployコマンド、微妙に便利だが、公式マニュアルとは違う挙動をするので注意。

$ ios_deploy install -b Payload/hoge.app # 少し時間かかります
$ ios_deploy path -b com.xxxx.xxxxx.hoge
/private/var/containers/Bundle/Application/625D8A60-C8N3-272A-XXXX-XXXXXXXX/hoge.app/hoge

MagSafe2のケーブルを熱収縮チューブで補修した

MagSafe 2のケーブルの皮膜が劣化して、中の電線が見える状態になったので熱収縮チューブで補修した。使い始めて5年なので寿命かな。

f:id:TAKEmaru:20180909031051j:plain

チューブをケーブルで巻いてはんだごてで温めるだけ。

手持ちの熱収縮チューブが見当たらなくて新しく買うことにしたけれど、通販で買うと送料かかるし、秋葉原は遠い。そんな中、東急ハンズで売っているのを知って、会社近くで調達できてよかった。

hands.net

東急ハンズ初めて行ったけど、イヤホンジャックとか抵抗も売っててすごかった。

Homebrewで入るNASMは古すぎて、aptで入るNASMとは挙動が違う

自作CPUエミュの動作確認をしているときに、Homebrewで入るNASMとaptで入るNASMの挙動が違うことに気がついた。

挙動の違いを見る

簡単なコードをアセンブルして違いを見る。

BITS 32
    org 0x7c00
    sub esp, 16

aptで入るNASM

Ubuntuにaptで入るNASMを使って、アセンブルした結果をhexdumpで確認する。

$ nasm sub-test.asm -o sub-test-linux
$ hexdump -C sub-test-linux
00000000  83 ec 10                                          |...|
00000003

0x83のopcodeは、メモリとレジスタの間で8bitの即値を操作する。 ModR/Mによってどんな操作をするのかが分かる。この場合はレジスタの値から8bitの即値を引いている。

Homebrewで入るNASM

macOSにHomebrewで入るNASMを使って、アセンブルした結果をhexdumpで確認する。

$ nasm sub-test.asm -o sub-test-mac
$ hexdump -C sub-test-mac 
00000000  81 ec 10 00 00 00                                 |......|
00000006

0x81のopcodeは、メモリとレジスタの間で32bitの即値を操作する。 この場合もModR/Mによってどんな操作をするのかが分かる。レジスタの値から8bitの即値を引いている。 オペランドに指定している0x16は8bitで収まるのに32bitの無駄に大きい値を扱っていて、うまくアセンブルできていないことがわかる。

バージョンの違いが原因だった

macOSでHomebrewを使ってインストールできる最新のNASMのバージョンは以下の通り。

$ nasm -v
NASM version 0.98.40 (Apple Computer, Inc. build 11) compiled on Aug 24 2016

Ubuntuでaptを使ってインストールできる最新のNASMのバージョンは以下の通り。

$ nasm -v
NASM version 2.10.09 compiled on Jun 28 2018

この通り、バージョンが大幅に違う。 appleが独自にメンテしているからバージョンの進み方が違うのかなと思ったけど、 単に古いだけっぽくて、最新のmacOS向けのビルドは以下のリンクで配布されている。 Index of /pub/nasm/releasebuilds/2.13/macosx

まとめ

このようにアセンブラのversionが違うと、出力されるopcodeが変わってくるので気をつけたい。

LLVM IR、LLVM bitcodeを扱うコマンドたちのメモ

LLVM IR、LLVM bitcodeを扱うコマンドをよく忘れるのでメモしておく。 CのコードをLLVM IR、LLVM bitcodeに変換するコマンド、LLVM bitcodeをインタプリタから実行するコマンド、コンパイルするコマンドなどを書いておく。 以下のCのコードを変換していく。

# include <stdio.h>

int main() { 
  int a = 1;
  int b = 2;
  int sum = a + b;
  printf("%d", sum);
  return 0;
}

LLVM IRを出力する

LLVM内ではLLVM IRという中間言語表現が用いられる。LLVM内で使えるアセンブリのようなものである。 LLVMを用いたコンパイラでは、ソースコードLLVM IRコードに変換したあと、そのLLVM IRコードをターゲットのアーキテクチャのバイナリに変換することでコンパイルが行われる。

Cのコードを変換する

clangに-emit-llvm-Sの2つのオプションを指定して実行するとLLVM IRが出力される。 以下のようにコマンドを実行するとtest.llが出力される。

$ clang test.c -emit-llvm -S

出力されたLLVM IR

以下のようなLLVM IRのコードが出力される。

$ cat test.ll
; ModuleID = 'test.c'
source_filename = "test.c"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.12.0"

@.str = private unnamed_addr constant [3 x i8] c"%d\00", align 1

; Function Attrs: nounwind ssp uwtable
define i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store i32 1, i32* %2, align 4
  store i32 2, i32* %3, align 4
  %5 = load i32, i32* %2, align 4
  %6 = load i32, i32* %3, align 4
  %7 = add nsw i32 %5, %6
  store i32 %7, i32* %4, align 4
  %8 = load i32, i32* %4, align 4
  %9 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i32 0, i32 0), i32 %8)
  ret i32 0
}

declare i32 @printf(i8*, ...) #1

attributes #0 = { nounwind ssp uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"PIC Level", i32 2}
!1 = !{!"Apple LLVM version 8.0.0 (clang-800.0.38)"}

LLVM bitcodeを出力する

LLVM bitcodeはLLVM IRのバイナリフォーマットである。 LLVM IRと LLVM bitocodeは相互変換でき、LLVM bitcodeはバイナリ(.out, .exeなど)に変換せずとも、LLVMインタプリタから直接実行することができる。

Cのコードを変換する

clangに-emit-llvm-cの2つのオプションを指定して実行するとLLVM bitcodeが出力される。 以下のようにコマンドを実行するとtest.bcが出力される。

$ clang test.c -emit-llvm -c

LLVM IRを変換する

llvm-asコマンドを使うことでLLVM IRをLLVM bitcodeに変換できる。 以下のようにコマンドを実行するとtest.bcが出力される。

$ llvm-as test.ll 

出力されたLLVM bitcode

人の目で直接読むのはきびしい。

$ file test.bc
test.bc: LLVM bitcode, wrapper x86_64
$ hexdump -C test.bc
00000000  de c0 17 0b 00 00 00 00  14 00 00 00 a8 09 00 00  |................|
00000010  07 00 00 01 42 43 c0 de  35 14 00 00 05 00 00 00  |....BC..5.......|
00000020  62 0c 30 24 49 59 be a6  ee d3 3e 2d 44 01 32 05  |b.0$IY....>-D.2.|
00000030  00 00 00 00 21 0c 00 00  1e 02 00 00 0b 02 21 00  |....!.........!.|
00000040  02 00 00 00 13 00 00 00  07 81 23 91 41 c8 04 49  |..........#.A..I|
00000050  06 10 32 39 92 01 84 0c  25 05 08 19 1e 04 8b 62  |..29....%......b|
00000060  80 10 45 02 42 92 0b 42  84 10 32 14 38 08 18 4b  |..E.B..B..2.8..K|
00000070  0a 32 42 88 48 90 14 20  43 46 88 a5 00 19 32 42  |.2B.H.. CF....2B|
00000080  04 49 0e 90 11 22 c4 50  41 51 81 8c e1 83 e5 8a  |.I...".PAQ......|
00000090  04 21 46 06 51 18 00 00  e9 00 00 00 1b 4c 25 f8  |.!F.Q........L%.|
000000a0  ff ff ff ff 01 90 00 0d  08 03 82 1c d2 61 1e c2  |.............a..|
000000b0  41 1c d8 a1 1c da 80 1e  c2 21 1d d8 a1 0d c6 21  |A........!.....!|
....

LLVM bitcodeをLLVM IRにデコンパイル

相互変換できるのでLLVM IRに戻すことができる。すごい。

$ llvm-dis test.bc

LLVM bitcodeをインタプリタで実行する

lliコマンドを使うことでLLVM bitcodeをインタプリタから直接実行できる。

$ lli test.bc 
3

LLVMの bitcodeをバイナリにする

llcコマンドを使うことでLLVM bitcodeをターゲットアーキテクチャアセンブリに変換し、そのアセンブリをclangでコンパイルすることでバイナリが出力される。

$ llc test.bc 
$ clang test.s
$ ./a.out
3