ダンプの構造
ダンプの解析を行なうためには、ダンプの構造を理解しておく必要があります。
前回、ダンプはメモリの内容を吐き出したものだと説明しましたが、実際の中身は、どのような形式になっているのでしょうか。ダンプの中身の形式は、ダンプ採取機能によって異なることがあります。kdumpでは、ELF形式で吐き出しています。
ELF形式なので、readelfコマンドで中の形式を覗くことができます。早速、覗いてみましょう。
ダンプの形式
# readelf -a vmcore
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: CORE (Core file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x0
Start of program headers: 64 (bytes into file)
Start of section headers: 0 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 5
Size of section headers: 0 (bytes)
Number of section headers: 0
Section header string table index: 0
There are no sections in this file.
There are no sections in this file.
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
NOTE 0x0000000000000158 0x0000000000000000 0x0000000000000000
0x0000000000000148 0x0000000000000148 0
LOAD 0x00000000000002a0 0x00000000c0000000 0x0000000000000000
0x00000000000a0000 0x00000000000a0000 RWE 0
LOAD 0x00000000000a02a0 0x00000000c0100000 0x0000000000100000
0x0000000000f00000 0x0000000000f00000 RWE 0
LOAD 0x0000000000fa02a0 0x00000000c9000000 0x0000000009000000
0x000000002f000000 0x000000002f000000 RWE 0
LOAD 0x000000002ffa02a0 0xffffffffffffffff 0x0000000038000000
0x0000000007e70000 0x0000000007e70000 RWE 0
There is no dynamic section in this file.
There are no relocations in this file.
There are no unwind sections in this file.
No version information found in this file.
Notes at offset 0x00000158 with length 0x00000148:
Owner Data size Description
CORE 0x00000090 NT_PRSTATUS (prstatus structure)
CORE 0x00000090 NT_PRSTATUS (prstatus structure)
#
タイプが「CORE」と言うことで、プロセスのコアファイルと同じ形式になっています。ただし、プロセスのコアファイルは、プロセスの仮想アドレス空間を吐き出したものですが、ダンプの場合は物理アドレス空間を吐き出したもので、意味が異なっているので注意してください。ダンプは、カーネルの仮想アドレス空間を吐き出したものではありません。ダンプのフォーマットにコアファイルのフォーマットを借用しているというのが正しい表現です。
ダンプに吐き出されているメモリの範囲は、プログラムヘッダ(Program Headers:)の中のロード(LOAD)セグメントから分かります。上記の出力例では、以下のアドレス範囲が吐き出されているのが分かります。
0x00000000 – 0x000a0000
0x00100000 – 0x01f00000
0x09000000 – 0x38000000
0x38000000 – 0x3fe70000
先ほど、物理アドレス空間を吐き出していると記述しましたが、ストレートマッピングしている部分に関しては、一応、仮想アドレス(VirtAddr)も付けてくれています。3番目のセグメントと4番目のセグメントは物理アドレスが連続していますが、ふたつに分かれているのは、3番目のセグメントはストレートマッピング内で仮想アドレスあり、4番目のセグメントはストレートマッピング内ではないので仮想アドレスなし、という違いによります。ただし、ダンプの解析の観点からは、仮想アドレスを入れておいてくれる必要はないので、余分なことではあります。
メモリの範囲
それでは次に、吐き出されるメモリの範囲に関して、どのように決まっているのかを確認してみましょう。そのヒントは、/proc/iomemにあります。
# cat /proc/iomem
00000000-0009ffff : System RAM
000a0000-000bffff : Video RAM area
000c0000-000ca7ff : Video ROM
000f0000-000fffff : System ROM
00100000-3fe6ffff : System RAM
00400000-005fed40 : Kernel code
005fed41-006d292f : Kernel data
01000000-08ffffff : Crash kernel
3fe70000-3fe71fff : ACPI Non-volatile Storage
3fe72000-3fe92fff : ACPI Tables
3fe93000-3fefffff : reserved
e8000000-efffffff : 0000:00:02.0
f0000000-f7ffffff : 0000:00:00.0
fea00000-feafffff : PCI Bus #01
feae0000-feafffff : 0000:01:0c.0
feae0000-feafffff : e1000
feb7f900-feb7f9ff : 0000:00:1f.5
feb7f900-feb7f9ff : Intel ICH5
feb7fa00-feb7fbff : 0000:00:1f.5
feb7fa00-feb7fbff : Intel ICH5
feb7fc00-feb7ffff : 0000:00:1f.1
feb80000-febfffff : 0000:00:02.0
fec00000-fec0ffff : reserved
fecf0000-fecf0fff : reserved
fed20000-fed8ffff : reserved
fee00000-fee0ffff : reserved
ffa80800-ffa80bff : 0000:00:1d.7
ffa80800-ffa80bff : ehci_hcd
ffb00000-ffffffff : reserved
#
この出力結果とダンプで吐き出されているメモリ範囲を比べてみると、「System RAM」すなわち本物のメモリの部分のみ吐き出していることに思い至るでしょう。ちなみに「Crash kernel」というのは、セカンドカーネル用のリザーブ領域です。ダンプでは、ご丁寧にこの領域が省かれています。その点を考慮すると、確かに「System RAM」の部分だけが吐き出されているのが分かります。
セカンドカーネルは、どのようにして、このファーストカーネルの/proc/iomemの情報を得ているのでしょうか。種を明かすと、これは、kexecコマンドの働きによります。kexecコマンドでは、/proc/iomemを見て、vmcoreファイルのヘッダ部分をあらかじめ作っています。そして、セカンドカーネルをロードするときに、そのELFヘッダ部分もリザーブ領域にロードしておきます。セカンドカーネルはそれを見て、ファーストカーネルのメモリ領域を把握します。
コンソールバッファ
さて、ダンプの構造が分かったところで、早速解析を始めてみましょう。最低限、カーネルのソースコードとSystem.mapファイルがあれば、解析ができるはずです。
試しにコンソールバッファを覗いてみましょう。カーネルがダウンしたとき、最後に出しているメッセージは、解析の重要な手がかりになりますからね。
カーネルのソースコードより、コンソールバッファは、__log_bufで示されるchar配列に格納されていることが分かります。
static char __log_buf[__LOG_BUF_LEN];
アドレスをSystem.mapファイルから確かめます。
System.map-2.6.18-8.el5
c0764c20 b __log_buf
物理アドレスは、0x764c20です。ダンプ中の2番目のロードセグメントにあります。ファイルオフセットを計算すると、
0xa02a0 + (0x764c20 – 0x100000) = 0x704ec0 (== 7360192)
というわけで、ダンプのファイルオフセット7360192から__log_bufの内容が格納されていることが分かります。
この内容は文字列なので、直接覗いて見ましょう。コンソールバッファは、循環バッファなので、他の変数の値も確かめて、さらに位置決めする必要がありますが、ここでは単に先頭から見てみます。
かなり、べたなコマンドですが、以下に出力例を示します。
コンソールバッファの内容
# dd bs=1 if=vmcore skip=7360192 count=4096
<5>Linux version 2.6.18-8.el5 (brewbuilder@ls20-bc2-14.build.redhat.com)
(gcc version 4.1.1 20070105 (Red Hat 4.1.1-52)) #1 SMP Fri Jan 26 14:15:21 EST 2007
<6>BIOS-provided physical RAM map:
<4> BIOS-e820: 0000000000000000 - 00000000000a0000 (usable)
<4> BIOS-e820: 00000000000f0000 - 0000000000100000 (reserved)
<4> BIOS-e820: 0000000000100000 - 000000003fe70000 (usable)
<4> BIOS-e820: 000000003fe70000 - 000000003fe72000 (ACPI NVS)
<4> BIOS-e820: 000000003fe72000 - 000000003fe93000 (ACPI data)
<4> BIOS-e820: 000000003fe93000 - 000000003ff00000 (reserved)
<4> BIOS-e820: 00000000fec00000 - 00000000fec10000 (reserved)
<4> BIOS-e820: 00000000fecf0000 - 00000000fecf1000 (reserved)
<4> BIOS-e820: 00000000fed20000 - 00000000fed90000 (reserved)
<4> BIOS-e820: 00000000fee00000 - 00000000fee10000 (reserved)
<4> BIOS-e820: 00000000ffb00000 - 0000000100000000 (reserved)
<5>126MB HIGHMEM available.
<5>896MB LOWMEM available.
<6>found SMP MP-table at 000fe710
<4>Using x86 segment limits to approximate NX protection
<7>On node 0 totalpages: 261744
<7> DMA zone: 4096 pages, LIFO batch:0
<7> Normal zone: 225280 pages, LIFO batch:31
<7> HighMem zone: 32368 pages, LIFO batch:7
<6>DMI 2.3 present.
<6>Using APIC driver default
<7>ACPI: RSDP (v000 DELL ) @ 0x000feba0
<7>ACPI: RSDT (v001 DELL GX270 0x00000008 ASL 0x00000061) @ 0x000fd196
<7>ACPI: FADT (v001 DELL GX270 0x00000008 ASL 0x00000061) @ 0x000fd1ce
<7>ACPI: SSDT (v001 DELL st_ex 0x00001000 MSFT 0x0100000d) @ 0xfffd5132
<7>ACPI: MADT (v001 DELL GX270 0x00000008 ASL 0x00000061) @ 0x000fd242
<7>ACPI: BOOT (v001 DELL GX270 0x00000008 ASL 0x00000061) @ 0x000fd2ae
<7>ACPI: ASF! (v016 DELL GX270 0x00000008 ASL 0x00000061) @ 0x000fd2d6
<7>ACPI: DSDT (v001 DELL dt_ex 0x00001000 MSFT 0x0100000d) @ 0x00000000
<6>ACPI: PM-Timer IO Port: 0x808
<7>ACPI: Local APIC address 0xfee00000
<6>ACPI: LAPIC (acpi_id[0x01] lapic_id[0x00] enabled)
<4>Processor #0 15:2 APIC version 20
<6>ACPI: LAPIC (acpi_id[0x02] lapic_id[0x01] enabled)
<4>Processor #1 15:2 APIC version 20
<6>ACPI: LAPIC (acpi_id[0x03] lapic_id[0x01] disabled)
<6>ACPI: LAPIC (acpi_id[0x04] lapic_id[0x03] disabled)
<6>ACPI: IOAPIC (id[0x02] address[0xfec00000] gsi_base[0])
...
確かにコンソールメッセージが見れました。
このように、
- 1. System.mapで変数や関数のアドレスを確認
- 2. プログラムヘッダからファイルのオフセットを計算
- 3. ファイルの中身を見る
という手順で解析を進めていくことができます。
しかし、さすがにこれは面倒すぎますね。こういったことを自動にやってくれるツールのひとつでも作りたくなるところですが、実はもう既に大変便利なツールが存在しています。
(それを早く言えよ、とおっしゃるかもしれませんが、何事も原理が大切ですぞ。)