この記事はいくつかの言語のランタイムで 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 パッケージを使っているとデフォルトで動的リンクになるらしい。
golangで書いたアプリケーションのstatic link化 - okzkメモokzk.hatenablog.com
ということで 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 さんありがとう。