elfファイルのdebugセクション分割とgdbの分割されたデバッグ情報のサポート機能めも

rpmパッケージとかは通常のパッケージとデバッグ用のパッケージを分けて、debugしたいときはデバッグ情報付きのパッケージをインストールしますよね。あれがどんな感じで動いているのか確認したのでメモです。

使っている機能としては、gdbデバッグ情報の分割機能とdebuglink機能だと思います。debuglinkはデバッグ情報のないバイナリファイルにデバッグ情報のあるバイナリファイルの情報を書き込むことでgdbがそのファイルを参照してくれるようになります。

やることとしては、以下の2点というところです。

では、実際に試してみます。まずは適当なc言語で書いたコードを-g付きでビルドします。そして、デバッグ情報付きのバイナリを以下の2ファイルに分けます。

ここでは、以下のようにtestという名称でバイナリを作ります。

masami@saga:~/tmp$ gcc -g test.c -o test

そして、このtestバイナリをobjcopyを使ってデバッグ情報を抜き出したファイルを作ります。

masami@saga:~/tmp$ objcopy --only-keep-debug test test.debug

これで、testに含まれるデバッグ情報をtest.debugファイルに書き出しました。この時点ではtestのほうは変更はありません。 次にtestからデバッグ情報を削除します。

masami@saga:~/tmp$ strip -g test

これでtestからデバッグ情報が消えたのでgdbでちょっと確認してみると、当然デバッグシンボルは無いと言われます。

masami@saga:~/tmp$ gdb ./test
Reading symbols from ./test...(no debugging symbols found)...done.
(gdb) quit

この時点でのtestバイナリには31個のセクションヘッダーがあります。

masami@saga:~/tmp$ readelf -S test
There are 31 section headers, starting at offset 0x1290:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400200  00000200
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             000000000040021c  0000021c
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             000000000040023c  0000023c
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400260  00000260
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           0000000000400280  00000280
       0000000000000060  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           00000000004002e0  000002e0
       000000000000003d  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           000000000040031e  0000031e
       0000000000000008  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400328  00000328
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400348  00000348
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400360  00000360
       0000000000000030  0000000000000018  AI       5    24     8
  [11] .init             PROGBITS         0000000000400390  00000390
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004003b0  000003b0
       0000000000000030  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         00000000004003e0  000003e0
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         00000000004003f0  000003f0
       00000000000001c2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         00000000004005b4  000005b4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000004005c0  000005c0
       0000000000000004  0000000000000004  AM       0     0     4
  [17] .eh_frame_hdr     PROGBITS         00000000004005c4  000005c4
       000000000000003c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000400600  00000600
       0000000000000114  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600718  00000718
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600720  00000720
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600728  00000728
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600730  00000730
       00000000000001d0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600900  00000900
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000600908  00000908
       0000000000000028  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000600930  00000930
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000600940  00000940
       0000000000000008  0000000000000000  WA       0     0     1
  [27] .comment          PROGBITS         0000000000000000  00000940
       0000000000000034  0000000000000001  MS       0     0     1
  [28] .shstrtab         STRTAB           0000000000000000  00001184
       000000000000010c  0000000000000000           0     0     1
  [29] .symtab           SYMTAB           0000000000000000  00000978
       0000000000000600  0000000000000018          30    44     8
  [30] .strtab           STRTAB           0000000000000000  00000f78
       000000000000020c  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

次に、objcopyを使って、debuglinkを埋め込みます。

masami@saga:~/tmp$ objcopy --add-gnu-debuglink=test.debug test

この結果、index 28のセクションヘッダに.gnu_debuglinkというのが追加されていて、合計32個のセクションヘッダになります。

asami@saga:~/tmp$ readelf -S test
There are 32 section headers, starting at offset 0x12c8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400200  00000200
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             000000000040021c  0000021c
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             000000000040023c  0000023c
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400260  00000260
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           0000000000400280  00000280
       0000000000000060  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           00000000004002e0  000002e0
       000000000000003d  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           000000000040031e  0000031e
       0000000000000008  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400328  00000328
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400348  00000348
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400360  00000360
       0000000000000030  0000000000000018  AI       5    24     8
  [11] .init             PROGBITS         0000000000400390  00000390
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004003b0  000003b0
       0000000000000030  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         00000000004003e0  000003e0
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         00000000004003f0  000003f0
       00000000000001c2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         00000000004005b4  000005b4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000004005c0  000005c0
       0000000000000004  0000000000000004  AM       0     0     4
  [17] .eh_frame_hdr     PROGBITS         00000000004005c4  000005c4
       000000000000003c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000400600  00000600
       0000000000000114  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600718  00000718
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600720  00000720
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600728  00000728
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600730  00000730
       00000000000001d0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600900  00000900
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000600908  00000908
       0000000000000028  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000600930  00000930
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000600940  00000940
       0000000000000008  0000000000000000  WA       0     0     1
  [27] .comment          PROGBITS         0000000000000000  00000940
       0000000000000034  0000000000000001  MS       0     0     1
  [28] .gnu_debuglink    PROGBITS         0000000000000000  00000974
       0000000000000010  0000000000000000           0     0     1
  [29] .shstrtab         STRTAB           0000000000000000  000011ac
       000000000000011b  0000000000000000           0     0     1
  [30] .symtab           SYMTAB           0000000000000000  00000988
       0000000000000618  0000000000000018          31    45     8
  [31] .strtab           STRTAB           0000000000000000  00000fa0
       000000000000020c  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

readelfコマンドでどんな内容か確認すると、test.debugという文字列が入っています。

masami@saga:~/tmp$ readelf -p .gnu_debuglink test

String dump of section '.gnu_debuglink':
  [     0]  test.debug
  [     c]  c

で、ここでgdbを再度実行すると、自動的にtest.debugを読み込んで、デバッグ情報が使えるようになります。

masami@saga:~/tmp$ gdb ./test
Reading symbols from ./test...Reading symbols from /home/masami/tmp/test.debug...done.
done.
(gdb) list
1       #include <stdio.h>
2
3       static void print_args(char **argv)
4       {
5               while (*argv) {
6                       printf("%s\n", *argv++);
7               }
8       }
9
10      int main(int argc, char **argv)
(gdb)

今はtestとtest.debugが同じ場所にあるので問題なかったですが、例えば、test.debugをdebug/においた場合、gdbデバッグシンボルを見つけられません。このような場合は、set debug-file-directoryでデバッグ情報のあるディレクトリを指定します。setでディレクトリを指定して、fileコマンドで読み込んだりすれば大丈夫です。

masami@saga:~/tmp$ gdb ./test
Reading symbols from ./test...(no debugging symbols found)...done.
(gdb) set debug-file-directory ./debug
(gdb) list
No symbol table is loaded.  Use the "file" command.
(gdb) show debug-file-directory
The directory where separate debug symbols are searched for is "./debug".
(gdb) file ./debug/test.debug
Reading symbols from ./debug/test.debug...done.
(gdb) list
1       #include <stdio.h>
2
3       static void print_args(char **argv)
4       {
5               while (*argv) {
6                       printf("%s\n", *argv++);
7               }
8       }
9
10      int main(int argc, char **argv)
(gdb)

あと、Build IDも重要なはずですね。これもreadelfコマンドで確認できます。どちらのファイルもBuild IDは同じなことを確認できます。

masami@saga:~/tmp$ readelf -n test

Displaying notes found at file offset 0x0000021c with length 0x00000020:
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 2.6.32

Displaying notes found at file offset 0x0000023c with length 0x00000024:
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: d97bf9a494dbde8a03785c102de536b2d8f82ac7
masami@saga:~/tmp$ readelf -n test.debug

Displaying notes found at file offset 0x0000021c with length 0x00000020:
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 2.6.32

Displaying notes found at file offset 0x0000023c with length 0x00000024:
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: d97bf9a494dbde8a03785c102de536b2d8f82ac7

( ´ー`)フゥー...

参考

Separate Debug Files - Debugging with GDB

Learning Linux Binary Analysis

Learning Linux Binary Analysis

linux kernelのlivepatchにライブパッチを適用する・しないの判断機能を入れて遊ぶ

2016年7月13日〜15日で行われたLinuxCon Japan 2016のセッションでカーネルハックの一環でカーネルのlivepatchの機能を使って、FreeBSDバイナリをLinuxで動かすってセッションに参加して、そういう使い方もありだなと思ったのでちょっと試してみました。アリだなと思ったところは条件に応じてライブパッチするしないの判断をできるようにすれば便利だなってとこだったので、そのへんを試してみた感じです。

まず、linux kernelのlivepatch機能を条件判断できるように変更します。

github.com

livepatch機能の変更点はpatch対象の関数が呼ばれた時のハンドラ関数のklp_ftrace_handler()です。ここで、条件判断の関数を呼んで、その関数がtrueを返したら新しい関数を呼び、falseの場合はオリジナルの関数を引き続き実行できるようにしています。

-    klp_arch_set_pc(regs, (unsigned long)func->new_func);
+   if (likely(klp_need_patch_apply_func())) 
+       klp_arch_set_pc(regs, (unsigned long)func->new_func);
+   else
+       klp_arch_enter_orig_func(regs, ip);
+

条件判断の関数はカーネル本体にはデフォルトの関数だけ作っていて、ライブパッチの登録時に変更するようにしました。Linux Kernelのlivepatchはカーネルモジュールとして作るので、条件判断の関数もlivepatch用のカーネルモジュール内で作ります。

livepatch test module

上のコードは、以下の3パターンの条件判断のうちどれかを選択できるようにしてます。

  1. 指定したPID
  2. 指定したプロセス名(comm)
  3. アクティブなPID名前空間が指定したPID名前空間に所属している

例えば、特定のPID名前空間のプロセスを対象にする場合、条件判断式は↓を使います。

static bool need_patch_apply_by_pidns(void)
{
        struct pid_namespace *pidns = task_active_pid_ns(current);
        return target_inum == pidns->ns.inum;
}

この場合、PID名前空間の指定はinode番号を使います。

実際の例でいくと、dockerコンテナ内のプロセスを対象にするとして、動かしているpid:1071のPID名前空間のinodeを調べます。

masami@kerntest:~$ sudo pstree -p
systemd(1)─┬─agetty(339)
           ├─agetty(340)
           ├─dbus-daemon(251)
           ├─dhcpcd(335)
           ├─docker(761)─┬─docker-containe(779)─┬─docker-containe(1027)─┬─bash(1043)───cmdline(1071)
           │             │                      │                       ├─{docker-containe}(1028)
           │             │                      │                       ├─{docker-containe}(1029)
           │             │                      │                       ├─{docker-containe}(1030)
           │             │                      │                       ├─{docker-containe}(1031)
           │             │                      │                       ├─{docker-containe}(1032)

pidがわかればそこで使っている名前空間のinode番号は/procを見ることでわかります。

masami@kerntest:~/livetest$ sudo ls -la /proc/1071/ns/pid
lrwxrwxrwx 1 root root 0 Jul 16 11:33 /proc/1071/ns/pid -> 'pid:[4026532216]'

コンテナで動かしているプロセスはこんな感じのやつです。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void read_cmdline(void)
{
        int fd;
        char buf[256] = {0};

        fd = open("/proc/cmdline", O_RDONLY);
        if (fd == -1) {
                perror("open");
                exit(-1);
        }

        read(fd, buf, sizeof(buf) - 1);
        close(fd);
        printf("%s", buf);
}

int main(int argc, char **argv)
{

        printf("pid is %d\n", getpid());
        while (1) {
                read_cmdline();
                sleep(1);
        }
        return 0;

}

後はカーネルモジュールをinsmodするだけです。

masami@kerntest:~/livetest$ sudo insmod livetest.ko target_inum=4026532216

そうすると、dockerコンテナ内で動いているプロセスが最初は普通に/proc/cmdlineの初期値を読んでいたのに、livepatchモジュールの読み込みにより出力内容が変わります。

masami@kerntest:~/livetest$ cd && sudo docker run --rm -v `pwd`:/tmp/test -it base/archlinux /bin/bash
[root@e1d3894ff868 /]# cd /tmp/test
[root@e1d3894ff868 test]# ./cmdline
pid is 7
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
~~~略~~~
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
hello, world
hello, world
hello, world

dockerコンテナを抜けるとpid名前空間が変わるので、/proc/cmdlineを見ると、またカーネルコマンドラインが見れます。

^C
[root@e1d3894ff868 test]# exit
exit
masami@kerntest:~$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M

(´-`).oO(元々のlivepatch機能の意図からは外れるんだけど、ちょっとした実験とかには良さげな気もします

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ

docker-machine createする時に、provisioning先のLinuxディストーションにはnetstatが必要

2時間位ハマったのでメモしときましょう φ(..)メモメモ

KVMでCentOS7の仮想環境を作って、そこにdocker-machine create -d generic ~とやっていたんですが、sshでエラーコード127が返ってきていて、何がおきてるのかさっぱりわからずでハマりました。

こんな感じでエラーになってました。

(my-keystore) Calling .GetSSHHostname
(my-keystore) Calling .GetSSHPort
(my-keystore) Calling .GetSSHKeyPath
(my-keystore) Calling .GetSSHKeyPath
(my-keystore) Calling .GetSSHUsername
Using SSH client type: external
Using SSH private key: /home/masami/.docker/machine/machines/my-keystore/id_rsa (-rw-------)
&{[-F /dev/null -o BatchMode=yes -o PasswordAuthentication=no -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet -o ConnectionAttempts=3 -o ConnectTimeout=10 -o ControlMaster=no -o ControlPath=none masami@192.168.122.92 -o IdentitiesOnly=yes -i /home/masami/.docker/machine/machines/my-keystore/id_rsa -p 22] /usr/bin/ssh <nil>}
Error running SSH command: exit status 127
(my-keystore) Calling .GetSSHHostname
(my-keystore) Calling .GetSSHPort
(my-keystore) Calling .GetSSHKeyPath
(my-keystore) Calling .GetSSHKeyPath
(my-keystore) Calling .GetSSHUsername
Using SSH client type: external
Using SSH private key: /home/masami/.docker/machine/machines/my-keystore/id_rsa (-rw-------)
&{[-F /dev/null -o BatchMode=yes -o PasswordAuthentication=no -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet -o ConnectionAttempts=3 -o ConnectTimeout=10 -o ControlMaster=no -o ControlPath=none masami@192.168.122.92 -o IdentitiesOnly=yes -i /home/masami/.docker/machine/machines/my-keystore/id_rsa -p 22] /usr/bin/ssh <nil>}
Error running SSH command: exit status 127
Error creating machine: Error running provisioning: Unable to verify the Docker daemon is listening: Maximum number of retries (10) exceeded
open : no such file or directory
notifying bugsnag: [Error creating machine: Error running provisioning: Unable to verify the Docker daemon is listening: Maximum number of retries (10) exceeded]

まず、docker-machineでsshするときには↓のようなコマンドラインを組み立てます。鍵はdocker-machine create実行時に--generic-ssh-key ~/.ssh/id_ras_nopassで渡した秘密鍵が~/.docker/machine/machines//id_rsaとしてコピーされます。

ssh -F /dev/null -o BatchMode=yes -o PasswordAuthentication=no -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet -o ConnectionAttempts=3 -o ConnectTimeout=10 -o ControlMaster=no -o ControlPath=none masami@192.168.122.92 -o IdentitiesOnly=yes -i /home/masami/.docker/machine/machines/my-keystore/id_rsa -p 22

使用している鍵はパスフレーズなしで、ユーザーはmasamiでsudoする時にパスワード不要で設定済みです。

上記のdocker-machineが作るコマンドラインsshログインできるんだけど、sshコマンドで失敗するわけですね。CentOSがわでtailf /var/log/secureしていても怪しいログは出てきませんでした。そりゃnetstatの実行だけななので通常のユーザ権限でやってるし・・・

それでググって見つけたのがこちらです。

Unable to provision with generic driver (exit status 127) · Issue #2480 · docker/machine · GitHub

netstat入ってなくね?という質問に対して、issueを書いた人が「入れたらできた!」って言ってたので、net-toolsパッケージを入れたら難なく成功しました( ´Д`)=3 フゥ

Running pre-create checks...
Creating machine...
(my-keystore) Importing SSH key...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with centos...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env my-keystore

ちなみにdocker-machineのコードだとlibmachine/provision/utils.goのcheckDaemonUp()netstat -tlnを実行しています。

( ´ー`)フゥー...

クラウド開発徹底攻略 (WEB+DB PRESS plus)

クラウド開発徹底攻略 (WEB+DB PRESS plus)

CentOS7でdocker daemonが「devmapper: Unknown option dm.no_warn_on_loop_devices」で起動しなかった

CentOS7でyumアップデートした後にdocker daemonが「devmapper: Unknown option dm.no_warn_on_loop_devices」というエラーで起動してなかったのでめも。

level=error msg="[graphdriver] prior storage driver \"devicemapper\" failed: devmapper: Unknown option dm.no_warn_on_loop_devices\n"

CentOSのバージョンは。

CentOS Linux release 7.2.1511 (Core)

カーネル

3.10.0-327.22.2.el7.x86_64

docker

docker-forward-journald-1.10.3-44.el7.centos.x86_64
docker-selinux-1.10.3-44.el7.centos.x86_64
docker-common-1.10.3-44.el7.centos.x86_64
docker-1.10.3-44.el7.centos.x86_64

まあ、「dm.no_warn_on_loop_devices」オプションを付けているのを外せば良いか〜という程度の適当さで、オプションを付けているところ探すと、/etc/sysconfig/docker-storageの最終行にてオプションを設定しているところを発見。

DOCKER_STORAGE_OPTIONS="--storage-opt dm.no_warn_on_loop_devices=true"

これを単純に下のようにして完了。

DOCKER_STORAGE_OPTIONS=""

根本原因は調べてないですけどね。 ( ´ー`)フゥー...

クラウド開発徹底攻略 (WEB+DB PRESS plus)

クラウド開発徹底攻略 (WEB+DB PRESS plus)

Linuxカーネルのコードを読んで勉強になったこと

Linuxカーネルのコードを読んでて、なるほど〜と思うことはよくあるけど、その中でも特に今までの考え方をぶち壊してくれたのはなんだっけと思ったところ、やっぱりリスト構造かなと言うところ。

c言語でリスト構造を作る場合、一般的な教科書方式だと↓のようにデータとnextポインタは密結合になってると思います。これの場合、struct foobarのポインタをnext要素に使っているので、他の構造体(例えば、struct hogehoge)で同じことをしようとすると、その構造体ではstruct hogehoge *nextというメンバ変数を持つ必要があります。 ヘッド要素はstruct foobarです。

struct foobar {
  int n;
  char s[64];
  struct foobar *next;
};

struct foobar head;

Linuxカーネルの場合、データとリスト構造の管理が別になっていて、リストはstruct list_headとして独立しているんですよね。

185 struct list_head {
186         struct list_head *next, *prev;
187 };

これのおかげで、リストの管理をデータに非依存で行えるようになってます。

さきのstruct foobarにstruct list_headを適用するとこのようになって、ヘッド要素はstruct list_headになります。

struct foobar {
 int n;
 char s[64];
 struct list_head *next;
};

struct list_head head;

struct list_headを使うと、データのリンクはstruct list_headを使って行うので、このようになります。

f:id:masami256:20160605225918p:plain

データはstruct list_headのnext変数でつながってる感じです。なので、リストを辿る場合はnext変数(双方向リストならprevも)を辿っていきます。struct list_headはprevとnextを持っていて、list_addマクロで登録するときはprev、nextともに設定されます。 では、データにはどうやってアクセスするのか?なると、container_ofマクロを使用します。

823 #define container_of(ptr, type, member) ({                      \
824         const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
825         (type *)( (char *)__mptr - offsetof(type,member) );})

それと、offsetofマクロも必要です。こちらはコンパイラがoffsetofの機能をサポートしていればそれを使うようになってます。機能的には構造体のメンバ変数「MEMBER」が構造体の先頭から何バイト目にあるかを取得するものです。

 14 #undef offsetof
 15 #ifdef __compiler_offsetof
 16 #define offsetof(TYPE, MEMBER)  __compiler_offsetof(TYPE, MEMBER)
 17 #else
 18 #define offsetof(TYPE, MEMBER)  ((size_t)&((TYPE *)0)->MEMBER)
 19 #endif

この辺は、Linux: inodeからtask_struct構造体を取得 - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモで説明したりしてます。ようは、メモリ上にある構造体のメンバ変数のアドレスから、そのメンバ変数の構造体先頭からのオフセットを引けば、その構造体の先頭アドレスが取得できるという感じです。これにより、struct list_head構造体とデータを粗結合にすることができています。

一般的なリスト構造だとデータとリストが密に結合していたのに、Linuxカーネルの場合はそのへんが上手く抽象化されてて、勉強になったなと思ったわけです。 といっても、これはoffsetofとかcontainer_ofといったテクニックがあった上での実装方法なので、初学者向けではないと思いますが。でも、データとリストを別に管理するという方法は良いですよね。

page cacheの検索と作成・登録

詳解Linuxカーネル読書会 - 詳解Linuxカーネル読書会 | Doorkeeperのもくもく結果。今日はページキャッシュの処理を調べました。 参考資料はUnderstanding Linux Kernelですが、長いので赤本と呼びます。

kernelは4.6を参照

buffer_head構造体の初期化。

buffer_init()で実施。

  • buffer_headという名前のスラブキャッシュ作成
  • buffer_head用に確保できる最大のページ数を設定
  • cpu hotplugでcpuが外れた時のbuffer_headの後処理関数を設定

ページキャッシュの検索とキャッシュがなかった時に追加

赤本ではページキャッシュの追加はadd_to_page_cache()って書いているんだけど、検索した限りあまり使われていない感じです。

f:id:masami256:20160528133650p:plain

それよりも、add_to_page_cache_lru()や、find_or_create_page()が使われている。find_or_create_page()はページキャッシュを検索して、見つからなければページを確保してページキャッシュに登録してくれる関数。内部的にはadd_to_page_cache_lru()を使ってます。

赤本で説明されているfind_get_page()は内部の実装としてはpagecache_get_page()を呼びます。find_or_create_page()pagecache_get_page()を呼びます。違いはpagecache_get_page()の引数fgp_flagsにFGP_CREATを設定しているかどうかです。FGP_CREATのビットが立っている場合は、ページキャッシュがなかった時にキャッシュを登録します。ページキャッシュを検索する系の関数は基本的にpagecache_get_page()を使う感じですね。

ページキャッシュの検索は、最初に find_get_entry()でページを検索します。このfind_get_entry()は赤本でradix treeを探索してるって説明のところを行っている関数っぽいです。find_get_page()の場合は、find_get_entry()でページが見つかれば処理完了です。pageが見つかった場合は参照数を増やします。find_lock_page()の場合はpage->mappingが引数で渡されたmappingと同じかをチェックします。この時はpage構造体のロックを取る必要があって、ロックが取れなければキャッシュ見つからずという感じでNULLを返します。page->mapping != mappingの場合は確保したロックの解除と、pageの参照数を減らして、再度ページキャッシュの検索を行います。 つぎにfgp_flagsにFGP_ACCESSEDが設定されていた場合はmark_page_accessed()を呼びます。これはLRUキューの操作をしたりといったところです。

ページキャッシュが見つかった場合の処理はここまでです。次にページキャッシュが見つからなかった場合です。pageがNULLでfgp_flagsにFGP_CREATが設定されている場合にキャッシュの登録があります。最初にpageをallocします(これは通常はalloc_page()で)。pageを確保できなければそこで終了で、NULLを返します。次にfgp_flags関連の処理で、FGP_LOCKが設定されていなければ、FGP_LOCKを追加します。それと、FGP_ACCESSEDが設定されていれば、__SetPageReferenced()を実行します。

最後にadd_to_page_cache_lru()でLRUにページキャッシュを登録します。 LRUに登録するのはpage構造体だけど、まだpageとaddress_space構造体は関連付いていません。なので、その関連付けが最初に行うことになります。この処理を行うのは__add_to_page_cache_locked()です。ここでpage->mappingにpagecache_get_page()に渡されたmappingを設定したり、radix treeへの登録を行ってます。__add_to_page_cache_locked()の処理が終わったら、page構造体をLRUに登録します。

パーフェクトPython

パーフェクトPython

Dockerコンテナにalpine linuxを使って、headlessなXサーバでSeleniumを動かせるようにする

Dockerコンテナ内で使うディストリビューションはAlpine Linuxがマイブームですm( )m

Dockerfileはこんな感じ。

FROM alpine:3.3

RUN apk update && \
apk add xvfb dbus firefox imagemagick ruby libffi && \
apk add --virtual=build-deps gcc make libc-dev ruby-dev libffi-dev && \
gem install --no-ri --no-rdoc selenium-webdriver && \
gem install --no-ri --no-rdoc json && \
dbus-uuidgen && \
mkdir /tests && \
apk del build-deps && \
rm -rf /var/cache/apk/*

ENV DISPLAY :1

CMD Xvfb :1 -screen 0 1024x768x24

とりあえず、xvfbを動かすのに必要なのがこれら。

dbusdbusパッケージにあるdbus-genuuidを実行しておかないと、xvfb実行時にエラーになります。

--virtualを使ってインストールしているところは、gemのインストール時にビルドが必要になるので、そこで使うものをグループ化してインストール。gemのインストールが終わったら消してます。

後はこのようなdocker-compose.ymlを作成。

version: "2"

services:
  selenium:
    hostname: selenium
    build: .
    volumes:
      - ./tests:/tests:rw

Dockerfileがあるディレクトリにtestsを作って、そこに下のファイルを置いて、

#!/usr/bin/env ruby

require 'selenium-webdriver'

driver = Selenium::WebDriver.for :firefox 

driver.navigate.to "http://www.kernel.org"
driver.save_screenshot("./screenshot.png")

driver.quit

docker-compose upでコンテナを起動して、別のターミナルからdocker-compose execで起動中にコンテナに入って、/testsに移動してrubyのコードを動かすと、こんなスクリーンショットが撮れます。

f:id:masami256:20160528011844p:plain

Docker実践ガイド

Docker実践ガイド