CUBE SUGAR CONTAINER

技術系のこと書きます。

Linux: util-linux を gdb でデバッグする

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 のやり方で調べていけば良い。

いじょう。