Python/CRASH API aka pykdump
カーネルのクラッシュダンプの中のslabをなめるのに、gdbスクリプトを回すと何日もかかる。pykdumpなら数分とか。
https://sourceforge.net/projects/pykdump/ ← SFなのでよく落ちている。クソ。
ドキュメントはソースアーカイブの中にlyx形式で置いてある。整形めんどい。
コンパイル
Pythonのバージョンがあわないとかでロクにコンパイルできない。コンパイル済みのバイナリを使うのが吉。今どきなら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ではディレクトリを指定する必要がある。
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()は、指定されたアドレスにあるポインタを整数として拾ってくる。