kgdbによるkernelデバッグ(2)

前回に引き続いてkgdbによるLinux kerenlのデバッグです。
実際にアタッチするのと、kernel moduleのシンボルの解決について記します。

アタッチ

remoteからのアタッチになるので、gdb実行側で、kernelのシンボル情報を読み込ませる必要があります。
vmlinuxファイルに入っているので、前回展開したものをgdbに読み込ませます。
その後、シリアル接続の情報を設定します。

# gdb vmlinux
(gdb) set remotebaud 115200

targetとなる端末では、アタッチするためにkerenlを一旦止める必要があります。目当ての動作中のデバッグをするには、breakpointを貼って、breakpointで止まったところを見る必要があります。
止めるには以下のコマンドを実行するか、ブートオプションのkgdbwaitを使います。

# echo g > /proc/sysrq-trigger

この状態で、gdb

(gdb) target remote /dev/ttyS0

というようにシリアルからアタッチ出来ると思います。あとはいつものgdbです。continueコマンドでkernel実行を継続すれば、止めてたshellも復帰します。

kernel moduleのデバッグ

この状態だと、kernel coreのシンボル解決は出来ますが、moduleについては情報がないため、デバッグ出来ない状態です。
moduleのアドレス情報は起動ごとに異なるので、まずはターゲット上で目当てのmodule情報を収集します。
moduleの各セクションのアドレス情報は、/sys/modue//sections/以下にあります。ここから各セクションの情報を取得します。
取得したアドレス情報はgdbに登録します。gdbのヘルプを見ると、

(gdb) help add-symbol-file Load symbols from FILE, assuming FILE has been dynamically loaded. Usage: add-symbol-file FILE [-readnow | -readnever] [-o OFF] [ADDR] [-s SECT-NAME SECT-ADDR]... ADDR is the starting address of the file's text. Each '-s' argument provides a section name and address, and should be specified if the data and bss segments are not contiguous with the text. SECT-NAME is a section name to be loaded at SECT-ADDR. OFF is an optional offset which is added to the default load addresses of all sections for which no other address was specified.

とあるので、textのアドレスとそれ以外のアドレスを入れる感じですね。 これを手で入れるのは面倒なので、スクリプトを作成します。

(print-section.sh ターゲットに配置)

#!/bin/sh

cd /sys/module/$1/sections/
if [ $? -ne 0 ]; then
    echo "cannot found module"
    exit 1
fi

addr=`cat .text`
echo -n "$addr"

for A in .*
do
    if [ "$A" = "." ]; then continue; fi
    if [ "$A" = ".." ]; then continue; fi
    if [ "$A" = ".text" ]; then continue; fi
    addr=`cat $A`
    echo -n " -s $A $addr"
done
echo

(kgdb-attach.sh リモートに配置)

#/bin/bash

gdbinit=".gdbinit"

dirdriver="lib/modules/4.16.7_D1+/kernel/drivers/"
vmlinux="boot/vmlinux-4.16.75_D1+"

declare -A modules
modules=(
    ["mod1"]="${dirdriver}/.../mod1.ko"
    ["mod2"]="${dirdriver}/.../mod2.ko"
)



echo "" > ${gdbinit}
echo "file $vmlinux" >> ${gdbinit}
echo "set remotebaud 115200" >> ${gdbinit}

for m in ${!modules[@]}; do
    section="`ssh root@192.168.100.1 bash ./print-section.sh ${m}`"
    echo "add-symbol-file ${modules[$m]} $section" >> ${gdbinit}
done

gdb
echo "" > ${gdbinit}

kgdb-attach.shの配列にデバッグしたい、もしくは関連するモジュールを入れて実行すると、アドレス情報を取ってきてgdbを実行します。
gdbの自動設定はgdbinitを使用しています。生成される.gdbinitの場所次第で警告が出ると思いますが、これを解消するには、homeディレクトリに、

# vi ~/.gdbinit
add-auto-load-safe-path /.../.gdbinit

というようにすれば大丈夫だと思います。
これでkerenl moduleに対してもデバッグできるようになりました。
kernelの開発だけでなく、kerenlの動きの理解や、ユーザランドのアプリの検証などにも使えると思うので気軽にkernelにアタッチしてみましょう!
最近はシリアルポートがない端末も増えてきてるので、ethernet経由でのデバッグもやってみようと思います。

kgdbによるkernelデバッグ

久しぶりにkerenlデバッグをする機会があったのでまとめておきます。 環境は以下

  • 物理PC(x86)
  • kernel 4.16.7(OSはRedhat系)
  • 接続はシリアル接続(デバッグ対象を端末A, 接続端末をB)

kernelをデバッグするときは、kernelを一旦止める必要があるため、基本的には外部からアタッチする形になります。
使うツールはgdbです。
qemuなどの仮想環境上で動いている場合は、qemu上のgdbserverが使えるのか、その限りではないです(?)
仮想環境上で試せる場合は先にそちらで試したほうがいいですね。 やり方はkernel Documentに書いてあります。(Documentation/dev-tools/gdb-kernel-debugging.rst)

kernel config

まずは、デバッグ対象のkerenlのconfigで、kgdbによるデバッグができるよう設定を行います。 Documentation/dev-tools/kgdb.rstやネットで書いてある通りです。

# make menuconfig
(kgdbの有効化 & シリアルコンソールからのデバッグ有効化)
Kernel hacking  --->
  [*] Kernel debugging
  [*] KGDB: kernel debugger  --->
    <*>   KGDB: use kgdb over the serial console
(kernelのデバッグシンボルを残す)
  Compile-time checks and compiler options  --->
    [*] Compile the kernel with debug info
    [ ]   Reduce debugging information
    [*]   Provide GDB scripts for kernel debugging   <= 必須ではないが、仮想環境上でのデバッグやkernelのビルド環境上でデバッグする場合は

その他のconfig

上記以外に以下のconfigを設定しますが、正直やり方があっているかわかりません。今の所問題はないようです。

CONFIG_FRAME_POINTER

ファイルにフレーム情報を保存するコードを埋め込むなどするので、スタックトレースなどが正確になる(らしい)ので、これを有効化します。
いろんなところで、

-> Kernel hacking
  -> Compile-time checks and compiler options

の中に設定項目があると書いてありますが見えません。menuconfigで"/"を押し、探してみると、UNWINDER_FRAME_POINTERに依存されているので、

-> Kernel hacking
  (X) Frame pointer unwinder

とします。これにより

CONFIG_FRAME_POINTER=y
CONFIG_UNWINDER_FRAME_POINTER=y

となります。

CONFIG_STRICT_KERNEL_RWX

これはkernelのメモリ空間をroとし、セキュリティを高める設定ですが、これが有効だと、breakpointの設定ができなくなります。 breakpoint自体は貼れるけど、動かすと多分こんな感じになる。

(gdb) c
Cannot inser breakpont 1.
Error accessing memory address 0xffff...: 不明なエラーです-1.

ただ、x86の場合、強制的に有効化されています。私は無理やり有効化しています。

# vim arch/x86/Kconfig
- select ARCH_HAS_STRICT_KERNEL_RWX
- select ARCH_HAS_STRICT_MODULE_RWX
+ #select ARCH_HAS_STRICT_KERNEL_RWX
+ #select ARCH_HAS_STRICT_MODULE_RWX

# make prepare
# make menuconfig

こうすると、.configから設定が消えると思います。

nokaslr

configとは異なりますが、この設定も無効化する必要があります。KASLRとは仮想メモリアドレスをランダム化し、セキュリティを高める仕組みですが、これが有効だとデバッグシンボルを入れてもgdbが関数などのアドレスを見つけられません。
そのためこれを無効化するんですが、これはkernelのブートオプションで無効化できます。(configでも無効化できると思いますがやったことはありません)
kernelのブートオプションにnokaslrを入れるだけです。設定方法は、kgdbの設定と一緒に紹介します。

Build

私はRPMでのパッケージ管理を崩したくない(デバッグ,開発時もリリースと手順を変えないことで、影響を減らしたい)のと、対象PCが貧弱なので、ビルドマシンでRPM化しています。

# make rpm-pkg

このとき、Makefile

VERSION = 4
PATCHLEVEL = 16
SUBLEVEL = 7
EXTRAVERSION =

RPMの名前が決まるので、EXTRAVERSIONにデバッグ番号などをつけています。 出来たRPMは端末Aにインストールします。develパッケージはこの用途では入れる必要はないと思いますが、別途systemtapなど端末A自体でデバッグを行う場合は必要になると思います。
別途端末Bでもvmlinuxが必要なため、RPMを展開します。vmlinuxもbzip2で圧縮されているので展開しておきます。

端末B
# rpm2cpio kernel.rpm | cpio -id
# bunzip2 boot/vmlinuz-xxx

kgdb設定

端末Aでkgdbの接続設定を行います。その前にちゃんとシリアルコンソールで接続できるか確認しておいたほうがいいでしょう。


シリアルコンソール接続方法

端末Aでシリアル接続を有効かします。systemdを利用している場合です。

# systemctl start serial-getty@ttyS0.service
# vi /etc/securetty
+ ttyS0

これで、端末Bからminicomなどで接続すると、端末Aにログインできるはずです。私はこのやり方が簡単なので使っていますが、systemdが起動しないとログインできないので、OS起動時から入りたい場合はgrubに設定することになります。


接続できることがわかったら、kgdbの設定を行います。kernelのブートオプションに設定します。基本的には2つです。

kgdboc=[kms][[,]kbd][[,]serial_device][,baud]
kgdbwait

kgdbocは接続情報、kgdbwaitはOSブート時に、kgdb接続を待ち受けるかです。kgdbwaitが有効だとkernelはできるだけ早くkgdbの待受に入ります。boot時のデバッグや、breakpointを貼ってから起動する場合などに使います。
以下は設定例です。KASLRの無効化設定も入れています。

# vi /etc/default/grub
GRUB_CMDLINE_LINUX="rhgb nokaslr kgdboc=ttyS0,115200 kgdbwait"
# grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg

これでOSを再起動したら、remoteのgdbでkernelにアタッチできるようになりました。
接続方法は次の記事に回します。

flatpak(1)

Flatpakを少し触ってみました。

Flatpak—the future of application distribution

どのディストリビューションでも同じ動きをし、flatpak環境下で動作させることで、sandbox的な制御も 可能となるようです。 アプリケーションはgithubのような、flathubというリポジトリがあり、主要なアプリケーションは簡単に インストールすることができます。

libreofficeや、VLCといった大きいアプリケーションほど、どの環境でも動作するというのは 助かるので、GUIアプリケーションの導入元としてはありだと思いました。 ただ、アプリケーションのバージョンアップがちゃんと追従するのかは確認ですかね。

sandboxとして、アプリケーションの制限をどのように行っているかは、次の記事にします。

ニューラルネットワーク自作入門メモ

ニューラルネットワーク自作入門

最近AIや機械学習が自分の周りでも聞かれるようになってきたので、基本だけ抑えておこうと思い読んでみました。 読んでみてなんとなくニューラルネットワークがわかった気になれるいい本だったと思います。

文字認識もいいんですが、画像認識はどうなのかと思い、 CIFAR-10 and CIFAR-100 datasets で方式はそのままで試してみました。 データの読み取り方法は、サンプルにもありますが、

def unpickle(self, file):
    import _pickle
    fo = open(file, 'rb')
    dict = _pickle.load(fo, encoding='latin1')
    fo.close()
    return dict

こんな感じで、dict型で取得できます。この中に、dataとlabelsがあり、dataは32323(RGB)の値が入っているようなので、まずはこの1次元配列をそのまま入力層として利用してみました。 本のサンプルから変更しているところはこんな感じ

    epochs = 5

    for e in range(1, epochs):
        data_dict = n.unpickle('cifar-10-batches-py/data_batch_' + str(e))
        # go through all records in the training data set
        for record in range(len(data_dict['labels'])):
            # split the record by the ',' commas
            #all_values = record.split(',')
            # scale and shift the inputs
            inputs = (numpy.asfarray(data_dict['data'][record]) / 255.0 * 0.99) + 0.01
            # create the target output values (all 0.01, except the desired label which is 0.99)
            targets = numpy.zeros(output_nodes) + 0.01
            # all_values[0] is the target label for this record
            targets[int(data_dict['labels'][record])] = 0.99
            n.train(inputs, targets)
            pass
        pass
    for record in range(len(test_dict['labels'])):
    # split the record by the ',' commas
    #all_values = record.split(',')
    # correct answer is first value
    correct_label = int(test_dict['labels'][record])
    # scale and shift the inputs
    inputs = (numpy.asfarray(test_dict['data'][record]) / 255.0 * 0.99) + 0.01
    # query the network
    outputs = n.query(inputs)
    #print(outputs)
    # the index of the highest value corresponds to the label
    label = numpy.argmax(outputs)
    # append correct or incorrect to list
    if (label == correct_label):
    # network's answer matches correct answer, add 1 to scorecard
    scorecard.append(1)
    print (label, " == ", correct_label)
    else:
    # network's answer doesn't match correct answer, add 0 to scorecard
    scorecard.append(0)
    print (label, " != ", correct_label)
    pass

    pass

これでやってみると、正解率はだいたい20%ほど。 こんなもんかという感じですが、色をまとめて入れるのはどうかという気もするのでもう少し工夫したり、CNNをやってみてもいいかも。 なかなか楽しいですが、これを自分のサービスに入れ込もうとするにはいろいろやってみて応用を効かせられるようにならないといけないですね。