pykdump

## Python/CRASH API aka pykdump

カーネルのクラッシュダンプの中のslabをなめるのに、gdbスクリプトを回すと何日もかかる。pykdumpなら数分とか。

https://sourceforge.net/projects/pykdump/ ← SFなのでよく落ちている。クソ。

ドキュメントはソースアーカイブの中にlyx形式で[置いてある](https://github.com/juergh/pykdump/tree/master/do)。整形めんどい。

## コンパイル

Pythonのバージョンがあわないとかでロクにコンパイルできない。[コンパイル済みのバイナリ](https://sourceforge.net/projects/pykdump/files/mpykdump-x86_64/)を使うのが吉。今どきならDockerかなにかでCentOS 6環境を作って、そこにコンパイル済みのバイナリを展開する。ターゲットのカーネルデバッグシンボルも持ってきて、展開。Debianやubuntuのdebファイルは、実体はarアーカイブなので、

```
$ ar xf linux-image-VERSION-dbgsym_VERSION_amd64.ddeb
```

とすると、ファイルが3つくらいできて、そのうちdata.tar.xz (現行のバージョンの場合) に欲しいファイルが入っている。

```
$ crash64 /usr/lib/debug/boot/vmlinux-VERSION dump.20YYMMDDHHMM

crash64 X.X.X
Copyright (C) 2002-2017  Red Hat, Inc.
Copyright (C) 2004, 2005, 2006, 2010  IBM Corporation
Copyright (C) 1999-2006  Hewlett-Packard Co
Copyright (C) 2005, 2006, 2011, 2012  Fujitsu Limited
Copyright (C) 2006, 2007  VA Linux Systems Japan K.K.
...

crash64> 
```

これで起動する。crash64となっているので、.crashrcではなく.crash64rcが読まれる。また、ターゲットのカーネルパッケージがインストールされていない場合、mod -Sではディレクトリを指定する必要がある。

``` ~/.crash64rc
set emacs
mod -S /usr/lib/debug/lib/modules/VERSION
extend /usr/local/lib/mpykdump64.so
```

## スクリプトの書き方

以下を最初に入れる。

```
from pykdump.API import *
```

カーネルのグローバル変数を読むには、readSymbol()

```
nr_pfn_mapped = readSymbol('nr_pfn_mapped')
pfn_mapped = readSymbol('pfn_mapped')

for i in range(0, nr_pfn_mapped):
    for pfn in range(pfn_mapped[i].start, pfn_mapped[i].end-pfn_mapped[i].start):
        # pfnにRAMの物理ページフレーム番号がはいるよ
```

あるアドレスにある構造体を読むには、readSU()

```
        pg = readSU('struct page', 0xffffea0000000000+64*pfn)
```

さっきのと組み合わせると、struct pageをなめられる。イエイ

```
       if ((pg.flags & 16384) and (pg.flags & 128)): # head && slab
            if (pg.slab_cache == なんとか):
                pgs_なんとか.append(pfn)
```

なんとかはあらかじめ目的のstruct kmem_cacheのポインタを入れておく。pgs_なんとか は、空で初期化しておく。

```
from pykdump.API import *

kmalloc_caches = readSymbol('kmalloc_caches')
km192 = kmalloc_caches[2]
pgs_km192 =[]
pfn_mapped = readSymbol('pfn_mapped')
for i in range(0, readSymbol('nr_pfn_mapped')):
    for pfn in range(pfn_mapped[i].start, pfn_mapped[i].end-pfn_mapped[i].start):
        pg = readSU('struct page', 0xffffea0000000000+64*pfn)
        if ((pg.flags & 16384) and (pg.flags & 128)):	# head && slab
            if (pg.slab_cache == km192_a):
                pgs_km192.append(pfn)
```

slabがslubになってから、crashのkmem -Sが使えなくなった (空きオブジェクトのないスラブがkmem_cacheからリンクされなくなった) ので、上みたいなコードで全部をスキャンしてやらなきゃならん。メンドい。

そうそう、どういうわけかbit fieldsがうまく扱えない。

```
for pfn in pg_km192:
    pg = readSU('struct page', 0xffffea0000000000+64*pfn)
    pgstart = 0xffff880000000000+pfn*4096
    # pykdump cannot access bitfields
    inuse = pg.counters & 0xffff
    objects = trunc((pg.counters & 0x7fff0000) / 65536)
    print ("SLAB 0x%x MEMORY 0x%x TOTAL %d ALLOCATED %d" % (pg_a, pgstart, objects, inuse))
```

あとは各ページの中のオブジェクトを表示すればいいのかな。

```
    usage = [1]*objects
    p = pg.freelist
    while p:
        usage[int((p-pgstart)/km192.size)] = 0
        p = readPtr(p)
    usedcount = 0
    for i in range(0, objects):
        if usage[i]:
            print("[0x%08x]" % (pgstart+i*km192.size))
            usedcount = usedcount + 1
        else:
            print(" 0x%08x " % (pgstart+i*km192.size))

    if usedcount != inuse:
        print("!!! inuse counter disagrees with the freelist")
```

readPtr()は、指定されたアドレスにあるポインタを整数として拾ってくる。