Linuxのメモリダンプ解析の話 (その2)


[Linuxのメモリダンプ解析の話 (その1)](http://qiita.com/hikanda/items/7206a8b35e8618e84f12) の続きです。

メモリダンプ解析の話に結構食いつきがあったので、引き続き書いてみたいと思います。カーネルの中身を理解するのは何故か敷居が高いと思われがちなのですが、全然そんなこと無いってことを分かっていただけると嬉しく思いますし、「ここが分かりにくい」みたいな話があれば教えていただけると有難いです。僕もまだまだカーネルの中身を知ってる段階ではなく、その都度調べてる状況なので。
個人的には Web 界隈の様々なミドルウェア・ツールが乱立してて理解が追いつきません。

それでは早速やりましょう。今回も Ubuntu 上での操作となります。

#crashの操作
##crash> log
`log` コマンドはカーネル内のバッファに蓄積されたカーネルログを出力します。ログの中身は、一般的なシェル上から使うことのできる `dmesg` コマンドで出力されるものと同じはずです。

いつの間にかカーネルログにもタイムスタンプが記録されるようになったんですね (左側の`[ ]`で囲まれた数値が起動時からの時刻 UPTIME に相当しており、[秒.マイクロ秒] という形式になっている)。こりゃ便利。
あと、cgroup サブシステムの初期化がログの最初に表示されているのを見て、今どきだなぁと思ったりする。

```
crash> log
[    0.000000] Initializing cgroup subsys cpuset
[    0.000000] Initializing cgroup subsys cpu
[    0.000000] Initializing cgroup subsys cpuacct
[    0.000000] Linux version 3.13.0-83-generic (buildd@lgw01-53) (gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1) ) #127-Ubuntu SMP Fri Mar 11 00:26:47 UTC 2016 (Ubuntu 3.13.0-83.127-generic 3.13.11-ckt35)
[    0.000000] KERNEL supported cpus:
[    0.000000]   Intel GenuineIntel
[    0.000000]   AMD AuthenticAMD
[    0.000000]   NSC Geode by NSC
[    0.000000]   Cyrix CyrixInstead
[    0.000000]   Centaur CentaurHauls
[    0.000000]   Transmeta GenuineTMx86
[    0.000000]   Transmeta TransmetaCPU
[    0.000000]   UMC UMC UMC UMC
[    0.000000] e820: BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009cfff] usable
[    0.000000] BIOS-e820: [mem 0x000000000009d000-0x000000000009ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000000e0000-0x00000000000fffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000ce228fff] usable
[    0.000000] BIOS-e820: [mem 0x00000000ce229000-0x00000000dcd3efff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000dcd3f000-0x00000000dce7efff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x00000000dce7f000-0x00000000dcefefff] ACPI data
[    0.000000] BIOS-e820: [mem 0x00000000dceff000-0x00000000df9fffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000f8000000-0x00000000fbffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fec00000-0x00000000fec00fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fed08000-0x00000000fed08fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fed10000-0x00000000fed19fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fed1c000-0x00000000fed1ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000ffc00000-0x00000000ffffffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000011e5fffff] usable
[    0.000000] NX (Execute Disable) protection: active
[    0.000000] SMBIOS 2.7 present.
[    0.000000] DMI: LENOVO 20AN008NJP/20AN008NJP, BIOS GLET68WW (2.22 ) 03/24/2014

・・・（省略）・・・

[39815.466957] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready
[39815.469656] iwlwifi 0000:03:00.0: L1 Enabled - LTR Enabled
[39815.470456] iwlwifi 0000:03:00.0: L1 Enabled - LTR Enabled
[39815.483240] IPv6: ADDRCONF(NETDEV_UP): wlan0: link is not ready
[39819.097847] wlan0: authenticate with 00:1c:7b:fa:5c:d3
[39819.099718] wlan0: send auth to 00:1c:7b:fa:5c:d3 (try 1/3)
[39819.100590] wlan0: authenticated
[39819.100752] wlan0: associate with 00:1c:7b:fa:5c:d3 (try 1/3)
[39819.101502] wlan0: RX AssocResp from 00:1c:7b:fa:5c:d3 (capab=0x11 status=0 aid=3)
[39819.103550] wlan0: associated
[39819.103569] IPv6: ADDRCONF(NETDEV_CHANGE): wlan0: link becomes ready
```

最後の `wlan0` のメッセージがパラパラ出てるのは、PCがスタンバイ状態から復帰して無線LAN設定が走ったからですね。ちゃんと not ready から ready 状態になってるし、検出してから4秒もかかるんだなとか分かるのも面白い。わざわざ `crash` 経由で見る必要は全くないけど。

`log` のヘルプを見ると chronological order で表示と書いてるので、基本的に先にロギングされたものから順に上から表示されるはずですが、ちょっと表示ズレがあるところを発見。カーネル起動時のCPU初期化付近のログです。以下に示します。

`Initializing CPU#0` が出力されて、`tsc: Detected 2594.272 MHz processor` でTSC (x86の Time Stamp Counter のことだと思う) の初期化時にプロセッサ情報を出力したタイムスタンプが `[    0.004000]` であり、その直後の `Calibrating delay loop (skipped), value calculated using timer frequency.. 5188.54 BogoMIPS (lpj=10377088)` ではタイムスタンプが `[    0.000001]` に戻るわけです。
これは恐らくキャリブレーションと書いてるぐらいなので、CPU周波数に応じてなにかタイマの初期化が必要なんでしょう。ちゃんとカーネルソースを読めば分かることですが、ここでは深入りしません。

```
[    0.000000] Initializing CPU#0
[    0.000000] xsave: enabled xstate_bv 0x7, cntxt size 0x340
[    0.000000] allocated 9383928 bytes of page_cgroup
[    0.000000] please try 'cgroup_disable=memory' option if you don't want memory cgroups
[    0.000000] Initializing HighMem for node 0 (00037bfe:0011e600)
[    0.000000] Memory: 3797084K/3874580K available (6564K kernel code, 645K rwdata, 2776K rodata, 880K init, 924K bss, 77496K reserved, 2961580
K highmem)
[    0.000000] virtual kernel memory layout:
                   fixmap  : 0xfff14000 - 0xfffff000   ( 940 kB)
                   pkmap   : 0xffc00000 - 0xffe00000   (2048 kB)
                   vmalloc : 0xf83fe000 - 0xffbfe000   ( 120 MB)
                   lowmem  : 0xc0000000 - 0xf7bfe000   ( 891 MB)
                     .init : 0xc19c3000 - 0xc1a9f000   ( 880 kB)
                     .data : 0xc1669740 - 0xc19c2740   (3428 kB)
                     .text : 0xc1000000 - 0xc1669740   (6565 kB)
[    0.000000] Checking if this processor honours the WP bit even in supervisor mode...Ok.
[    0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=8, Nodes=1
[    0.000000] Hierarchical RCU implementation.
[    0.000000]  RCU dyntick-idle grace-period acceleration is enabled.
[    0.000000] NR_IRQS:2304 nr_irqs:744 16
[    0.000000] CPU 0 irqstacks, hard=f340a000 soft=f340c000
[    0.000000] Console: colour dummy device 80x25
[    0.000000] console [tty0] enabled
[    0.000000] hpet clockevent registered
[    0.000000] tsc: Fast TSC calibration using PIT
[    0.004000] tsc: Detected 2594.272 MHz processor
[    0.000001] Calibrating delay loop (skipped), value calculated using timer frequency.. 5188.54 BogoMIPS (lpj=10377088)
[    0.000003] pid_max: default: 32768 minimum: 301
[    0.000024] Security Framework initialized
```

他にも、CPU#0 (CPUの0番目のCPUコアのこと。物理CPU、論理CPU、CPUコアの違いはまた今度・・・) の初期化が終わって CPU#1, CPU#2, ... とCPUが持っているCPUコア数分の初期化が行われるところでタイムスタンプが表示ズレしてました (基本的にカーネルブート時はCPU#0をまず初期化して、次に残りのCPUを初期化します)。
これはきっと、どのCPUから出力されたメッセージなのかがポイントになってて、各々のCPUが初期化しつつそれぞれメッセージを出力する場合には、CPUコア間のタイマの同期が関連しそうだな、と思っています。完全な憶測なんですが、CPUアーキテクチャ毎に初期化・タイマ同期方法に違いがあると思うので、今は深入りせずほっときます。

```
[    0.064041] CPU 1 irqstacks, hard=f3550000 soft=f3552000
[    0.064043] x86: Booting SMP configuration:
[    0.074332] Initializing CPU#1
[    0.078950] NMI watchdog: enabled on all CPUs, permanently consumes one hw-PMU counter.
[    0.064044] .... node  #0, CPUs:      #1
[    0.079015] CPU 2 irqstacks, hard=f3620000 soft=f3622000
[    0.089282] Initializing CPU#2
[    0.079017]  #2
[    0.093914] CPU 3 irqstacks, hard=f3666000 soft=f3668000
[    0.104181] Initializing CPU#3
[    0.093917]  #3
[    0.108764] x86: Booted up 1 node, 4 CPUs
```

なんか無駄な話が長くなりましたが、カーネルログを見る意味としては、やはりパニック前にそのノード上でどんな処理が実施されていたかおおまかに知ることができるという点です。WARNINGやERRORの文字列が出ていると、調査をするポイントが絞れるわけです。OOMメッセージがでてたら嫌だなぁとか(笑)。

ただし、あることが原因でやたらと色んなメッセージが出力されることがあるので、騙されないように注意が必要。自分で書いたカーネルコードやドライバのテストで山のようにメッセージ出しすぎて結局何が問題なのかわからないってことが多々ありました (反省)。
最近のCPUだと、カーネルは複数CPUコア並列でナノセカンド以下の速さで1命令コードを処理しているので、メッセージ出力のような遅い処理を間違ってよく通るルート入れてしまうと、そこがボトルネックとなり全然処理が進まない、または、タイミングがズレて追いかけたかった現象が発生しなくなる可能性があります。身を持って体感しました。

ちなみに、カーネルログを格納している配列は `__log_buf` という名前の char 型配列です。[配列サイズは `#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)` で決まる](http://lxr.free-electrons.com/source/kernel/printk/printk.c?v=3.13#L248)ので、ちゃんとサイズを知りたいときはカーネルコンフィグ設定を確認しましょう。[Linux-3.13のx86の場合は `CONFIG_LOG_BUF_SHIFT=18` なので、256KiBですね](http://lxr.free-electrons.com/source/arch/x86/configs/i386_defconfig?v=3.13#L14)。カーネルソースも本当は Ubuntu のソースを追いかける必要がありますが、差分は無視して [LXRに上がっているLinux-3.13のソースを見てます](http://lxr.free-electrons.com/source/?v=3.13)。

```:kernel/printk/printk.c
248 #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
249 static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
```

```:arch/x86/configs/i386_defconfig
 14 CONFIG_LOG_BUF_SHIFT=18
```

シンボルがあることを確認して先頭アドレスからメモリ内容をリードすれば `log` コマンドで出力される情報と同じように、"タイムスタンプ + メッセージ" の文字列の並びが格納されていることがわかります (`sym`, `rd` コマンドについては後で説明します)。
見て分かるとおり、ASCII文字コードが連続すると、非常に特徴的な16進数表記になることが分かります。0x20〜0x7a の16進数が並ぶと「あ、これ文字列じゃね！？」ってすぐに気づくと思いますが (気づきますよね？)、この能力を鍛えておくとメモリダンプ解析で非常に役に立ちます。皆さん[目grepを鍛えましょう](http://www.slideshare.net/murachue/grep-8132856)。

```
crash> sym __log_buf
c1ac16cc (b) __log_buf
crash> rd __log_buf 28
c1ac16cc:  00000000 00000000 00210034 c6000000   ........4.!.....
c1ac16dc:  74696e49 696c6169 676e697a 72676320   Initializing cgr
c1ac16ec:  2070756f 73627573 63207379 65737570   oup subsys cpuse
c1ac16fc:  00000074 00000000 00000000 001e0030   t...........0...
c1ac170c:  c6000000 74696e49 696c6169 676e697a   ....Initializing
c1ac171c:  72676320 2070756f 73627573 63207379    cgroup subsys c
c1ac172c:  00007570 00000000 00000000 00220034   pu..........4.".
crash> log | head -n2
[    0.000000] Initializing cgroup subsys cpuset
[    0.000000] Initializing cgroup subsys cpu
crash> 
```

##crash> sym
`sym` コマンドは、カーネル内のシンボルと仮想アドレス空間の対応関係を知るためのコマンドです。

例えば、`jiffies` を見てみましょう。タイマ割り込み毎にカウントアップされる変数で、[ググると簡単に説明が見つかるほどメジャーな変数](https://www.google.co.jp/search?q=jiffies)なんですが、カーネルソース上の関連箇所 (定義など) をピックアップすると以下の通りです。

```:kernel/timer.c
 55 u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES;
```

```:include/asm-generic/bitsperlong.h
  7 #ifdef CONFIG_64BIT
  8 #define BITS_PER_LONG 64
  9 #else
 10 #define BITS_PER_LONG 32
 11 #endif /* CONFIG_64BIT */
```

```:include/linux/jiffies.h
 65 /* some arch's have a small-data section that can be accessed register-relative
 66  * but that can only take up to, say, 4-byte variables. jiffies being part of
 67  * an 8-byte variable may not be correctly accessed unless we force the issue
 68  */
 69 #define __jiffy_data  __attribute__((section(".data")))
 70 
 71 /*
 72  * The 64-bit value is not atomic - you MUST NOT read it
 73  * without sampling the sequence number in jiffies_lock.
 74  * get_jiffies_64() will do this for you as appropriate.
 75  */
 76 extern u64 __jiffy_data jiffies_64;
 77 extern unsigned long volatile __jiffy_data jiffies;
 78 
 79 #if (BITS_PER_LONG < 64)
 80 u64 get_jiffies_64(void);
 81 #else
 82 static inline u64 get_jiffies_64(void)
 83 {
 84         return (u64)jiffies;
 85 }
 86 #endif
```

```:kernel/time/jiffies.c
 72 #if (BITS_PER_LONG < 64)
 73 u64 get_jiffies_64(void)
 74 {
 75         unsigned long seq;
 76         u64 ret;
 77 
 78         do {
 79                 seq = read_seqbegin(&jiffies_lock);
 80                 ret = jiffies_64;
 81         } while (read_seqretry(&jiffies_lock, seq));
 82         return ret;
 83 }
 84 EXPORT_SYMBOL(get_jiffies_64);
 85 #endif
 86 
 87 EXPORT_SYMBOL(jiffies);
```

・・・結局よく分からないですよね。`jiffies` と `jiffies_64` のどっちを使うんだろうとか、32bitマシンなら `jiffies` で64bitマシンなら `jiffies_64` なのか、と。ソースだけ見てても埒が明かないので、まず `crash` でシンボル検索してみましょう。

```
crash> sym jiffies
c1928240 (D) jiffies
crash> sym jiffies_64
c1928240 (D) jiffies_64
```

ちゃんと検索にヒットしてます。一行に仮想アドレス、領域、シンボル名と並んでいますが、なんと `jiffies` と `jiffies_64` の開始アドレスは同じ仮想アドレスになっていますね。領域 `(D)` の定義はヘルプになかったのですが、たぶんデータ領域にのことでしょう。ちなみに小文字の `(d)` になってる時もありますが、これはグローバルか `static` かの違いかと思われます。

さらに、前後のアドレスのシンボルも一緒に検索してみましょう。`-p`, `-n` オプションをつけると一つ前、一つ後ろのシンボルも見れます。

```
crash> sym -np jiffies
c1928200 (d) softirq_vec
c1928240 (D) jiffies
c1928280 (d) pidmap_lock
crash> sym -np jiffies_64
c1928240 (D) jiffies
c1928240 (D) jiffies_64
c1928280 (d) pidmap_lock
```

`jiffies` と次のシンボル `pidmap_lock` との差分が 0x40 なので、ちゃんと64ビット分データを格納する領域が確保されているようです。さらに `jiffies_64` の前後を調べると、一つ前のシンボルが `jiffies` と表示されていますが、やはりアドレスは同じです。どうやら基本的に同じものを指しているようなのですが、わざわざ変数名を分けて扱っているところが怪しいです。

なぜこんなことになってるかですが、元々Linuxでは32ビットの `jiffies` しかありませんでした。オーバーフローしても気にしません (というか、不具合起きてたんじゃなかろうか)。しかし、それじゃマズイだろうってことで Linux-2.6 系になって64ビット化されたとのこと。`jiffies` は色んな所で直接参照されてるので、`jiffies_64` の下位32ビット分を `jiffies` に割り当てて残したようです。
それでどの32ビットを割り当てるのかですが、ここにエンディアン対応が必要になってきます。

リトルエンディアンの x86 からソースを見てみましょう。
`vmlinux.lds.S` は、一般的なバイナリ `a.out` を作るときのリンカスクリプトみたいなもんで、カーネルイメージ `vmlinux` を作るときの `.text` や `.data` の配置を決めるためのものらしいです。
それはさておき、32ビットだろうが64ビットだろうがアドレス配置は `jiffies` = `jiffies_64` っぽいです。リトルエンディアンは下位アドレスから順番にデータを埋めるので、`jiffies_64` の先頭アドレスから32ビット分が `jiffies` になるということですね。

```:arch/x86/kernel/vmlinux.lds.S
 34 #ifdef CONFIG_X86_32
 35 OUTPUT_ARCH(i386)
 36 ENTRY(phys_startup_32)
 37 jiffies = jiffies_64;
 38 #else
 39 OUTPUT_ARCH(i386:x86-64)
 40 ENTRY(phys_startup_64)
 41 jiffies_64 = jiffies;
 42 #endif
```

次にビッグエンディアンの SPARC を見てみますと以下の通りになってます。
何やらさっきと違って、32ビットアーキの時は `jiffies` のアドレスが `jiffies_64 + 4` になってますね。4バイト分、つまり32ビット後ろにずらしてる。これは、ビッグエンディアンが大きなアドレスから先に埋めるため、こういうアドレスずらしが必要になります。

```:arch/sparc/kernel/vmlinux.lds.S
 23 #ifdef CONFIG_SPARC32
 24 OUTPUT_FORMAT("elf32-sparc", "elf32-sparc", "elf32-sparc")
 25 OUTPUT_ARCH(sparc)
 26 ENTRY(_start)
 27 jiffies = jiffies_64 + 4;
 28 #else
 29 /* sparc64 */
 30 OUTPUT_FORMAT("elf64-sparc", "elf64-sparc", "elf64-sparc")
 31 OUTPUT_ARCH(sparc:v9a)
 32 ENTRY(_start)
 33 jiffies = jiffies_64;
 34 #endif
```

先ほど、各変数の宣言で、`u64 jiffies_64`, `unsigned long jiffies` となっていましたが、64ビットアーキの場合は `unsigned long` も64ビット幅となるため、`u64` と同じサイズになります。なので、x86 だろうが SPARC だろうが `jiffies` と `jiffies_64` のアドレス開始位置が同じなんですね。色々と分かりましたが、共用体の `union` じゃ対応できなさそうだなと思いました。 

深入りしないと言いながら深入りしてますが、`sym` を使えばカーネルがどの変数を実際に使っているのか判別する手助けになります。カーネルソースを見てると、やたらとカーネルコンフィグで変数・関数が `#ifdef` もしくは `#ifndef` で囲まれてるところがあって、ソースを注意して読まないと使われる変数・関数が分からない時がありますが、`sym` コマンドで検索して見つかれば、実際に使われていることがすぐ分かります。

変なシンボル名を引数に入れても、見つからない場合は以下のように表示してくれます。

```
crash> sym pizza
symbol not found: pizza
possible alternatives:
  (none found)
```

`possible alternatives` と表示されてますが、何か引っかかりそうなものだと、ちゃんと調べて表示してくれます。例えば `test` とやると、完全一致はしないけど、他にもたくさんシンボルが見つかって、数えてみたら100個超えてました。「あ〜、あの変数なんだったっけ？」というときに重宝する機能です。

```
crash> sym test | cat
symbol not found: test
possible alternatives:
  c1000326 (t) verify_cpu_sse_test
  c104ea30 (T) ptep_test_and_clear_young
  c104ea60 (T) pmdp_test_and_clear_young
  c10568d0 (T) test_taint
・・・（省略）・・・
  f977e150 (t) ieee80211_if_fmt_tkip_mic_test [mac80211]
  f977f5c0 (t) ieee80211_if_read_tkip_mic_test [mac80211]
  f977fae0 (t) ieee80211_if_write_tkip_mic_test [mac80211]
  f977fbb0 (t) ieee80211_if_parse_tkip_mic_test [mac80211]
  f97945e0 (r) tkip_mic_test_ops [mac80211]
crash> sym test | wc -l
153
crash> 
```

部分マッチするものすべて検索するときは `-q` オプションを使います。`jiffies` を引っ掛けてみると、やはりたくさん出てきました。領域の `(T)` はテキスト領域のことですね。つまり、`jiffies_to_msecs` は関数名で、その関数アドレスが `0xc105a220` となります。

```
crash> sym -q jiffies
c105a220 (T) jiffies_to_msecs
c105a230 (T) jiffies_to_usecs
c105a360 (T) msecs_to_jiffies
c105a380 (T) usecs_to_jiffies
c105a3b0 (T) timespec_to_jiffies
c105a430 (T) jiffies_to_timespec
c105a480 (T) timeval_to_jiffies
c105a500 (T) jiffies_to_timeval
c105a560 (T) jiffies_to_clock_t
c105a5b0 (T) jiffies_64_to_clock_t
・・・（省略）・・・
```

あとは特に便利ではありませんが、全てのシンボルをとりあえず表示させるには `-l` オプションですね。

```
crash> sym -l
c1000000 (T) _text
c1000000 (T) startup_32
c10000dc (t) bad_subarch
c10000dc (W) lguest_entry
c10000dc (W) xen_entry
c10000e0 (T) start_cpu0
c10000f0 (T) startup_32_smp
c1000111 (t) default_entry
c1000189 (t) enable_paging
c100020b (t) is486
・・・（省略)・・・
```

Linux-2.6以降であれば、`/proc/kallsyms` で表示できる情報と同じですが、やってみるとアドレスがでないですね。と思ったら単純にセキュリティがまずかっただけで `sudo` すると見えました。[`/proc/kallsyms` に関してはmanマニュアルにも少し説明がありますので確認してみてください](https://linuxjm.osdn.jp/html/LDP_man-pages/man5/proc.5.html)。

```
$ head /proc/kallsyms 
00000000 T startup_32
00000000 T _text
00000000 t bad_subarch
00000000 W lguest_entry
00000000 W xen_entry
00000000 T start_cpu0
00000000 T startup_32_smp
00000000 t default_entry
00000000 t enable_paging
00000000 t is486
$ sudo head /proc/kallsyms 
c1000000 T startup_32
c1000000 T _text
c10000dc t bad_subarch
c10000dc W lguest_entry
c10000dc W xen_entry
c10000e0 T start_cpu0
c10000f0 T startup_32_smp
c1000111 t default_entry
c1000189 t enable_paging
c100020b t is486
```

最後になりましたが、`sym` コマンドの本当の役割は、シンボルを探して変数なら中身を確認する、関数ならディスアセンブルするなどが考えられます。`jiffies` 中身をみると以下の通りで、コマンド実行のたびにカウントがちゃんと増えていっていることが分かります。右側に出てる変な文字は、リードした中身をASCIIコードとして変換した値なので、この場合は気にしなくて構いません。

```
crash> sym jiffies
c1928240 (D) jiffies
crash> rd jiffies
c1928240:  01527988                              .yR.
crash> rd jiffies
c1928240:  01527af4                              .zR.
crash> rd jiffies
c1928240:  01527b9c                              .{R.
```

`sym` はまぁこんなもんですかね。思いの外長くなってしまった。

##crash> rd
あるアドレスの中身をリードするには、`rd` コマンドを使用します。C言語で言うと、ポインタの中身を参照するみたいな感じです。

グローバル変数は引数にアドレスを指定しなくても、変数名でリードできます。上記でも `jiffies` の中身みてましたね。ロードモジュールで定義されている変数の場合は、正しいアドレスを指定する必要があります。ただ、`crash` 内でモジュールをロードしてあげると変数名でアクセスできるので、そのやり方はまた別途説明します。

```
crash> sym jiffies
c1928240 (D) jiffies
crash> rd 0xc1928240
c1928240:  015341a6                              .AS.
crash> rd jiffies
c1928240:  015344e9                              .DS.
```

また、リードするビットサイズを指定できます。64ビットで見たいときは `-64` オプションを指定してあげます。他にも `-8`, `-16`, `-32` があります。出力結果をパイプで別コマンドに渡す場合など、ASCIIコードのテキスト情報が邪魔な場合は `-x` で消してあげましょう。

```
crash> rd -32 jiffies
c1928240:  015592f9                              ..U.
crash> rd -64 jiffies
c1928240:  00000001015594e9                    ..U.....
crash> rd -x64 jiffies
c1928240:  00000001015685a9 
```

あとは、引数の最後に読み出す回数を数字で指定すると、その分リードしてくれます。あるアドレスから何バイト分とりあえず表示して中身を見たいというときに役立ちます。

```
crash> rd -x64 jiffies 1
c1928240:  00000001018e158d 
crash> rd -x64 jiffies 2
c1928240:  00000001018e1701 0000000000000000 
crash> rd -x64 jiffies 3
c1928240:  00000001018e184b 0000000000000000 
c1928250:  0000000000000000 
crash> rd -x32 jiffies 1
c1928240:  018e1f80 
crash> rd -x32 jiffies 2
c1928240:  018e20b9 00000001 
crash> rd -x32 jiffies 3
c1928240:  018e21e4 00000001 00000000 
```

`rd` のヘルプを見てみると、結構オプションが豊富なんですが、他に使ったことが無いので説明は割愛します。便利な使い方があれば教えてください。


今日のところは以上にしたいと思います。まだ、メモリ周り系の調査方法書かなきゃ（泣）。