util-linux に含まれるコマンドの振る舞いを動的に解析したい場面があったので、手順を書き残しておく。
使った環境は次のとおり。
$ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=20.04 DISTRIB_CODENAME=focal DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS" $ uname -rm 5.4.0-91-generic aarch64
下準備
まずは、システムにインストールされている util-linux のバージョンを調べておく。
今回使った環境では 2.34
らしい。
$ dpkg -l | grep util-linux ii util-linux 2.34-0.1ubuntu9.1 arm64 miscellaneous system utilities
ただし、システムに入っているものはリリース版なので、シンボルなどが削除されてしまっている。
$ file $(which unshare) /usr/bin/unshare: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=3c2afa819eb8040c947103fe980e2d88acc92c2b, for GNU/Linux 3.7.0, stripped
そこで、自分でソースコードを落としてきてビルドする。 まずはビルドに必要なパッケージ一式を入れる。 また、後ほど使うデバッガとして gdb も入れておく。
$ sudo apt-get -y install build-essential gdb
続いては肝心の util-linux の tarball をダウンロードする。
$ wget -O - https://mirrors.edge.kernel.org/pub/linux/utils/util-linux/v2.34/util-linux-2.34.tar.gz | tar zxvf -
ビルドする。
$ cd util-linux-2.34/ $ ./configure && make
これで util-linux に含まれている諸々がビルドできた。
$ ls ABOUT-NLS addpart chrt ctrlaltdel fsck ipcs libmount.la lscpu mkfs.minix readprofile sfdisk tools AUTHORS agetty col delpart fsck.minix isosize libsmartcols lsipc mkswap rename stamp-h1 umount COPYING autogen.sh colcrt disk-utils fsfreeze kill libsmartcols.la lslocks mount renice sulogin unshare ChangeLog bash-completion colrm dmesg fstrim last libtcolors.la lslogins mountpoint resizepart swaplabel utmpdump Documentation blkdiscard column eject getopt ldattach libtool lsmem namei rev swapoff uuidd Makefile blkid config fallocate hardlink lib libuuid lsns nologin rfkill swapon uuidgen Makefile.am blkzone config.h fdformat hexdump libblkid libuuid.la m4 nsenter rtcwake switch_root uuidparse Makefile.in blockdev config.h.in fdisk hwclock libblkid.la logger mcookie partx schedutils sys-utils wall NEWS cal config.log fincore include libcommon.la login-utils mesg pivot_root script taskset wdctl README chcpu config.status findfs ionice libfdisk look misc-utils po scriptreplay term-utils whereis README.licensing chmem configure findmnt ipcmk libfdisk.la losetup mkfs prlimit setarch tests wipefs aclocal.m4 choom configure.ac flock ipcrm libmount lsblk mkfs.bfs raw setsid text-utils zramctl
中身を見ると、ちゃんとシンボルなどが残っている。
$ file unshare unshare: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=a70bad2506d9972f359c141b679d33fdfe03e58d, for GNU/Linux 3.7.0, with debug_info, not stripped
動作することも確認しておこう。 試しに unshare(1) を実行する。 以下では、PID Namespace を新たに作っている。
$ sudo ./unshare --fork --pid --mount-proc bash
新たに起動したシェルで確認すると、ちゃんと PID が 1 からリセットされている。
# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.3 8592 3528 pts/0 S 16:54 0:00 bash root 8 0.0 0.2 10188 2816 pts/0 R+ 16:54 0:00 ps aux
動作確認が終わったら終了しておこう。
# exit
gdb でデバッグする
動作確認ができたので、続いては gdb を使って動的解析する。 まずは gdb 経由で unshare(1) を起動する。
$ sudo gdb ./unshare
ブレークポイントとして、とりあえず適当にメイン関数を設定する。 ここはまあご自由に。
(gdb) break main Breakpoint 1 at 0x2940: file sys-utils/unshare.c, line 288.
ちなみに、デバッグ途中で fork(2) する場合には、親プロセスと子プロセスのどちらを追跡するか設定しておく必要がある。 ここでは子プロセスを追跡する場合。
(gdb) set follow-fork-mode child
設定が終わったらコマンドライン引数を渡して実行する。 設定したブレークポイントまで処理が進むはず。
gdb) run --fork --pid --mount-proc bash Starting program: /home/ubuntu/util-linux-2.34/unshare --fork --pid --mount-proc bash Breakpoint 1, main (argc=5, argv=0xfffffffff638) at sys-utils/unshare.c:288 288 {
次のように、ちゃんとメイン関数でブレークしている。
(gdb) l 283 284 exit(EXIT_SUCCESS); 285 } 286 287 int main(int argc, char *argv[]) 288 { 289 enum { 290 OPT_MOUNTPROC = CHAR_MAX + 1, 291 OPT_PROPAGATION, 292 OPT_SETGROUPS,
あとは通常の gdb のやり方で調べていけば良い。
いじょう。