LoginSignup
15
12

More than 5 years have passed since last update.

Linuxのデバッグ機能(kmemleak)のコードを読む(その1)

Posted at

Linuxのデバッグ機能、面白そうですね

Linux Advent Calendar 2015からかなり間が開きました。他の方が書かれた記事群はどれも大変素晴らしく堪能いたしました。
その中でも、Linux Advent Calendar 2015の最終日の記事は大変優れた記事の一つでした。この記事のトピックは、Linuxのデバッグ機能です。

実は恥ずかしながら、私はLinuxのデバッグ機能をあまり知りません。せっかくの機会ですので、ちょびちょびとソース読むなり動かすなりして楽しみたいと思います。

色々とやることがあり、読んだ結果をまとめきれていなかったり、ソース読みたいところが読めなかったりですが、それでも、少しずつ読んだ結果を書いていこうと思います。

kmemleak

最終日の記事にこんな記述がありました。

最終日の記事より抜粋
Linuxのデバッグをするための方法としても下記のようなものがあります(kmemleakとかlockdep等は省略m( )m)

kmemleakとは何でしょうか。その名前からメモリリークを調べるためのツールらしきことがわかります。ちょっとわくわくします。

魅惑のmm

kmemleakのコードはmm/kmemleak.cにあります。そして、処理のメインはkmemleak_scan_threadを実体とするカーネルスレッドが行っています。
kmemleak_scan_thread()は以下のとおりです。

mm/kmemleak.c
static int kmemleak_scan_thread(void *arg)
{
    static int first_run = 1;

    pr_info("Automatic memory scanning thread started\n");
    set_user_nice(current, 10);

    /*
     * Wait before the first scan to allow the system to fully initialize.
     */
    if (first_run) {
        first_run = 0;
        ssleep(SECS_FIRST_SCAN);
    }

    while (!kthread_should_stop()) {
        signed long timeout = jiffies_scan_wait;

        mutex_lock(&scan_mutex);
        kmemleak_scan();
        mutex_unlock(&scan_mutex);

        /* wait before the next scan */
        while (timeout && !kthread_should_stop())
            timeout = schedule_timeout_interruptible(timeout);
    }

    pr_info("Automatic memory scanning thread ended\n");

    return 0;
}

一読して感じ取れるように、ご本尊はkmemleak_scan()です。早速読んでみましょう。

mm/kmemleak.c
/*
 * Scan data sections and all the referenced memory blocks allocated via the
 * kernel's standard allocators. This function must be called with the
 * scan_mutex held.
 */
static void kmemleak_scan(void)
{
    unsigned long flags;
    struct kmemleak_object *object;
    int i;
    int new_leaks = 0;

    jiffies_last_scan = jiffies;

    /* prepare the kmemleak_object's */
    rcu_read_lock();
    list_for_each_entry_rcu(object, &object_list, object_list) {
        spin_lock_irqsave(&object->lock, flags);
#ifdef DEBUG
    /* 略 */
#endif
        /* reset the reference count (whiten the object) */
        object->count = 0;
        if (color_gray(object) && get_object(object))
            list_add_tail(&object->gray_list, &gray_list);

        spin_unlock_irqrestore(&object->lock, flags);
    }
    rcu_read_unlock();

ここで、color_gray()とかobjectとか何やらローカルルールらしきものが出てきました。
こういう場合は、ソースコードの中を調べてみましょう。

mm/kmemleak.c
/*
 * Object colors, encoded with count and min_count:
 * - white - orphan object, not enough references to it (count < min_count)
 * - gray  - not orphan, not marked as false positive (min_count == 0) or
 *      sufficient references to it (count >= min_count)
 * - black - ignore, it doesn't contain references (e.g. text section)
 *      (min_count == -1). No function defined for this color.
 * Newly created objects don't have any color assigned (object->count == -1)
 * before the next memory scan when they become white.
 */

このコメントを見る限り、min_countが鍵になるようです。更にコメントを読んでみましょう。

mm/kmemleak.c
/**
 * kmemleak_alloc - register a newly allocated object
 * @ptr:    pointer to beginning of the object
 * @size:   size of the object
 * @min_count:  minimum number of references to this object. If during memory
 *      scanning a number of references less than @min_count is found,
 *      the object is reported as a memory leak. If @min_count is 0,
 *      the object is never reported as a leak. If @min_count is -1,
 *      the object is ignored (not scanned and not reported as a leak)
 * @gfp:    kmalloc() flags used for kmemleak internal memory allocations
 *
 * This function is called from the kernel allocators when a new object
 * (memory block) is allocated (kmem_cache_alloc, kmalloc, vmalloc etc.).
 */

コメントから、min_countは以下のように、「ある領域がメモリリークしている領域かどうか」を判断する材料として使われていると推察できます。

「オブジェクトに対する参照の最小値である。メモリースキャンをしている間にmin_countよりも小さな参照数のobjectを見つけた場合、以下のように対処する。

 - min_count == 0のobjectをメモリリークとみなす。
 - min_count == -1の場合は、該当objectを絶対にメモリリークとみなさない。
 - それ以外はとりあえず無視する。

実は、min_countは「メモリが割り当てられたある領域に対する、最低限存在するはずの参照数」です。
また、objectというのは、「あるメモリ領域に対して、メモリリークの有無を調査するためのメタデータ」です。先のコメントで「when a new object(memory block)」と書かれていることからも、メモリ領域を指すデータ構造であると推察できます。

また、先のコメントから以下のことがわかります。

color 概要
white 孤立したメモリ領域で、count < min_count、つまりメモリリークの疑いありの領域
gray 疑わしい領域とみなさないか、もしくは十分な参照数がある領域
black 一般的なテキスト領域(命令コード)のように参照を含まない(注:動的に割り当てるメモリでない)領域。動的割り当てでないので、当然メモリリークの疑いなし

black == メモリリークという先入観がありましたが、実際にはwhiteが「怪しい領域」です。
思い込みでソースを読むのでなく、コメントなどをしっかり見て判断しましょう。特に職業でLinuxのコードを読む場合、思い込みでソースを読むと毎晩のように「進捗どうですか?」と聞かれかねません。

このコメントと先に読んだコードから「メモリ領域をobjectという単位に区切ってscanする。参照数(count)とmin_countの値を手がかりにメモリリークの疑いのある領域をリストにし、まとめて表示」という処理の流れが予想できそうです。コメントとコードの断片から、ある程度全体の実装が推察できましたね。大雑把に全体を予想してから細部を読むのは非常に重要です。
Linuxのコメントは、残念SIerのソースコードコメントなど比較にならない良いコメントが多いと思います。素晴らしい。

それでもイメージしにくいかな

文字での説明が意外と面倒なので、概要を図示してみます。

chart1.png

実は、カーネル内で諸々のメモリ割り当て関数が呼ばれると、その都度、kmemleak_object()という関数経由でobjectを作ります。
「kmemleak_alloc」でカーネルソースを検索するとこのことがわかります。

逆にobjectの解放は__delete() -> put_object()のルートで行われます。kmemleak外部に公開しているI/F、kmemleak_free()経由で呼ばれることが多いです。

create_object()では以下の処理を行っています。重要でないと判断したところは省略しています。

mm/kmemleak.c
static struct kmemleak_object *create_object(unsigned long ptr, size_t size,
                         int min_count, gfp_t gfp)
{
/* 略 */
    object = kmem_cache_alloc(object_cache, gfp_kmemleak_mask(gfp));
/* 略 */
    atomic_set(&object->use_count, 1);
    object->flags = OBJECT_ALLOCATED;
    object->pointer = ptr;
    object->size = size;
    object->min_count = min_count;
    object->count = 0;          /* white color initially */
    object->jiffies = jiffies;
    object->checksum = 0;

/* 略 */
    min_addr = min(min_addr, ptr);
    max_addr = max(max_addr, ptr + size);
    link = &object_tree_root.rb_node;
    rb_parent = NULL;
    while (*link) {
/* 
 * 割り当てようとした領域がすでに他処理によって割り当てられているか確認する。
 * 割り当て済みならエラー扱いとし、goto outする。
 */
    }
/* objectを赤黒木に挿入し、objectに対応するメモリアドレスで検索できるようにする */
    rb_link_node(&object->rb_node, rb_parent, link);
    rb_insert_color(&object->rb_node, &object_tree_root);

    list_add_tail_rcu(&object->object_list, &object_list);
out:
    write_unlock_irqrestore(&kmemleak_lock, flags);
    return object;
}

実はobject割り当て時には、min_count(この領域は最低限この値だけの参照数があるはずだ!)を初期設定します。

一方で、kmemleak_scan_threadがメモリリークスキャンをするとき、実際に使われているメモリ領域を探り、その領域を指すアドレスから対応するobjectを探します。
見つけたら、該当object内のcount(現在の参照数)を増やします。

普通は、使われているメモリ領域、すなわち実際に使われている領域のアドレス値を取得することができるのなら、そのアドレス値を使ってobjectを引くことができるので、min_count <= count の関係が成り立ちます。

しかし、メモリリークの場合、たいてい解放を忘れたままポインタを上書きするなどしてしまいます。これにより、「実際に使われているアドレス」経由でobjectを引くことができなくなります。その場合、スキャン処理でobjectのカウンタを増やすことができません。
しかも、メモリの解放がされていないので、objectは残ったままです。
この場合、countはmin_countに到達しないことが解ると思います。基本的には、count < min_countなobjectに対応した領域を「メモリリークしている領域」であると疑いをかけるのです。

次回は

ここで得られた知見をもとにスレッドの残り処理を読んでみましょう。

15
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
12