この記事はいくつかの言語のランタイムで glibc が利用されていることを確認し、glibc に感謝するものです。
Python3
Python3 の処理系は CPython。
# python3 -VV Python 3.6.8 (default, Nov 21 2019, 19:31:34) [GCC 8.3.1 20190507 (Red Hat 8.3.1-4)]
これはインタプリタ型。このインタプリタの実装に glibc が利用されていることを確認する。
python3 にリンクされているライブラリを確認すると、libc.so.6
が存在する。
# ldd $(which python3) linux-vdso.so.1 (0x00007ffe3c9aa000) libpython3.6m.so.1.0 => /lib64/libpython3.6m.so.1.0 (0x00007fa4cd52e000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fa4cd30e000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fa4cd10a000) libutil.so.1 => /lib64/libutil.so.1 (0x00007fa4ccf06000) libm.so.6 => /lib64/libm.so.6 (0x00007fa4ccb84000) libc.so.6 => /lib64/libc.so.6 (0x00007fa4cc7c2000) /lib64/ld-linux-x86-64.so.2 (0x00007fa4cdc8a000)
以下のような HTTP アクセスするコードを用意し、
#!/usr/bin/env python3 import urllib.request resp = urllib.request.urlopen("http://example.com") print(resp.getcode())
bpftrace で glibc の関数が実行されていることを確認する。
terminal A]# python3 main.py 200 terminal B]# bpftrace -e 'uprobe:/lib64/libc.so.6:connect /comm == "python3" / { printf("%s called %s\n", comm, probe); } ' Attaching 1 probe... python3 called uprobe:/lib64/libc.so.6:connect python3 called uprobe:/lib64/libc.so.6:connect python3 called uprobe:/lib64/libc.so.6:connect python3 called uprobe:/lib64/libc.so.6:connect
ということで、Python3 は glibc の関数を利用している(こともある)。
Java
Java の処理系は OpneJDK 1.8。
# java -version openjdk version "1.8.0_242" OpenJDK Runtime Environment (build 1.8.0_242-b08) OpenJDK 64-Bit Server VM (build 25.242-b08, mixed mode)
これはソースコードを Java 仮想マシンの機械語にコンパイルしておき、実行時にインタプリタ型のように機械語に変換する(ここでは、JIT は考えない)。このインタプリタの実装に glibc が利用されていることを確認する。
Java にリンクされているライブラリを確認すると、libc.so.6
が存在する。
# ldd $(which java) linux-vdso.so.1 (0x00007ffc351f1000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f6653fbb000) libz.so.1 => /lib64/libz.so.1 (0x00007f6653da4000) libjli.so => not found libdl.so.2 => /lib64/libdl.so.2 (0x00007f6653ba0000) libc.so.6 => /lib64/libc.so.6 (0x00007f66537de000) /lib64/ld-linux-x86-64.so.2 (0x00007f66543dd000)
以下のような HTTP アクセスするコードを用意し、
import java.net.HttpURLConnection; import java.net.URL; public class Main { public static void main(String[] args) throws Exception { URL url = new URL("http://example.com"); HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); urlConn.setRequestMethod("GET"); urlConn.connect(); System.out.println(urlConn.getResponseCode()); } }
bpftrace で glibc の関数が実行されていることを確認する。
terminal A]# javac Main.java terminal A]# java Main 200 terminal B]# bpftrace -e 'uprobe:/lib64/libc.so.6:connect /comm == "java" / { printf("%s called %s\n", comm, p robe); } ' Attaching 1 probe... java called uprobe:/lib64/libc.so.6:connect java called uprobe:/lib64/libc.so.6:connect java called uprobe:/lib64/libc.so.6:connect java called uprobe:/lib64/libc.so.6:connect java called uprobe:/lib64/libc.so.6:connect java called uprobe:/lib64/libc.so.6:connect ^C
ということで、java は glibc の関数を利用している(こともある)。
Go
たしか Go はシングルバイナリ、かつ、glibc などに依存しない(マルチプラットフォーム対応) と聞いたことがある。確認してみよう。
Go の処理系が複数あるのかは知らないが、goenv でインストールしたやつ。バージョンは 1.14。
# go version go version go1.14.3 linux/amd64
これはコンパイル型言語なので、Go 自体にリンクされているライブラリは確認しない。
以下のような HTTP アクセスするコードを用意し、
package main import ( "fmt" "net/http" ) func main() { resp, _ := http.Get("http://example.com") defer resp.Body.Close() fmt.Println(resp.StatusCode) }
ビルドすると、動的リンク(dynamically linked
)されたバイナリが生成された。アレ?
# go build main.go # file main main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, not stripped
しかも glibc が依存しているぞ。
# ldd main linux-vdso.so.1 (0x00007ffef6165000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f613055f000) libc.so.6 => /lib64/libc.so.6 (0x00007f613019d000) /lib64/ld-linux-x86-64.so.2 (0x00007f613077f000)
ググると、 net パッケージを使っているとデフォルトで動的リンクになるらしい。
ということで CGO_ENABLED=0
(CGO は C ライブラリを Go から呼び出す仕組みらしい) を無効にしてもういちどコンパイルすると、静的リンクされたバイナリが生成された。
# CGO_ENABLED=0 go build main.go # file main main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped # ldd main 動的実行ファイルではありません
bpftrace で glibc の関数をトレースしても、表示されない。
terminal A]# ./main 200 terminal B]# bpftrace -e 'uprobe:/lib64/libc.so.6:connect { printf("%s called %s\n", comm, probe);}' Attaching 1 probe... ^C
念のため nm コマンドでシンボルを表示しても、glibc の connect がリンクされてなさそうな事がわかる。
# nm /lib64/libc.so.6 | grep "connect$" 00000000000fcec0 t __GI___connect 00000000000fcec0 W __connect 00000000000fcec0 t __libc_connect 00000000000fcec0 W connect # nm main | grep "connect$" 0000000000545900 T net.(*netFD).connect 0000000000618190 T net/http.(*socksDialer).connect 00000000004996f0 T syscall.connect
ということでオプション設定すればバイナリは glibc に依存してなさそう(すごい)。
まあでも go コンパイラは glibc に依存してますから。
# ldd /root/.goenv/versions/1.14.3/bin/go linux-vdso.so.1 (0x00007fffb2bd6000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f9a7ddd0000) libc.so.6 => /lib64/libc.so.6 (0x00007f9a7da0e000) /lib64/ld-linux-x86-64.so.2 (0x00007f9a7dff0000)
まとめ
我々は glibc という巨人の肩の上に立っていたのだ。glibc さんありがとう。