クラッシュダンプからNICの制御情報を確認する

# 概要

Linuxのクラッシュダンプからネットワークデバイスの管理情報であるnet_device構造体の内容を確認してみます。

# 事前準備

以下の記事を参考にして事前準備を行います。

- [カーネルクラッシュダンプの解析方法](https://qiita.com/Kernel_OGSun/items/ae85b8d804cd60495ed2)


カーネルのデバッグ情報をインストールします。

```
 # yum install --enablerepo=base-debuginfo kernel-debuginfo-`uname -r`
```

クラッシュコマンドをインストールします。

```
 # yum install crash
```

OSを強制的にパニックさせてテスト用のクラッシュダンプを採取します。

```
 # echo c > /proc/sysrq-trigger
```

OS再起動後に/var/crash配下を確認し、クラッシュダンプが採取されていることを確認します。

# ipコマンドによる構成の確認

あらかじめ、ipコマンドにより対象サーバのNIC名やIPアドレス等を確認しておきます。

```rb
# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 52:54:00:20:1d:56 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.62/24 brd 192.168.0.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet6 240f:a1:2044:1:cb15:e4dc:e642:6dbc/64 scope global noprefixroute dynamic
       valid_lft 278sec preferred_lft 278sec
    inet6 fe80::b52b:b451:8c26:4b40/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
3: br-201008366ec8: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:81:87:19:69 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 scope global br-201008366ec8
       valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:fc:76:39:b9 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
```

以降ではeth0の制御情報についてcrashコマンドで確認していきます。

|  項目         |   値                        |
|:-------------|:----------------------------|
|NIC名         |eth0                         |
|Index         |2                            |
|IPアドレス    |192.168.0.62                  |
|ネットマスク   |255.255.255.0                 |
|MTU          |1500                           |
|FLAGS        |BROADCAST,MULTICAST,UP,LOWER_UP|

# crashコマンドの起動

採取したクラッシュダンプを指定してcrashコマンドを起動します。

```rb
[root@cent7-vm 127.0.0.1-2022-02-03-15:11:50]# cd /var/crash/127.0.0.1-2022-02-03-15\:11\:50/
[root@cent7-vm 127.0.0.1-2022-02-03-15:11:50]# crash /usr/lib/debug/usr/lib/modules/`uname -r`/vmlinux vmcore

crash 7.2.3-11.el7_9.1
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.
Copyright (C) 2005, 2011  NEC Corporation
Copyright (C) 1999, 2002, 2007  Silicon Graphics, Inc.
Copyright (C) 1999, 2000, 2001, 2002  Mission Critical Linux, Inc.
This program is free software, covered by the GNU General Public License,
and you are welcome to change it and/or distribute copies of it under
certain conditions.  Enter "help copying" to see the conditions.
This program has absolutely no warranty.  Enter "help warranty" for details.

GNU gdb (GDB) 7.6
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu"...

WARNING: kernel relocated [172MB]: patching 87388 gdb minimal_symbol values

      KERNEL: /usr/lib/debug/usr/lib/modules/3.10.0-1160.53.1.el7.x86_64/vmlinux
    DUMPFILE: vmcore  [PARTIAL DUMP]
        CPUS: 2
        DATE: Thu Feb  3 15:11:48 2022
      UPTIME: 00:23:00
LOAD AVERAGE: 0.01, 0.14, 0.14
       TASKS: 161
    NODENAME: cent7-vm
     RELEASE: 3.10.0-1160.53.1.el7.x86_64
     VERSION: #1 SMP Fri Jan 14 13:59:45 UTC 2022
     MACHINE: x86_64  (2095 Mhz)
      MEMORY: 2 GB
       PANIC: "SysRq : Trigger a crash"
         PID: 1994
     COMMAND: "bash"
        TASK: ffff8d8db8ede300  [THREAD_INFO: ffff8d8db9c7c000]
         CPU: 1
       STATE: TASK_RUNNING (SYSRQ)

crash>

```

# ネットワーク情報の表示

netコマンドでネットワークデバイスの一覧を表示します。

```rb
crash> net
   NET_DEVICE     NAME   IP ADDRESS(ES)
ffff8d8dbbbb2000  lo     127.0.0.1
ffff8d8d75ccd000  eth0   192.168.0.62
ffff8d8dba7fa000  br-201008366ec8 172.18.0.1
ffff8d8dba7f8000  docker0 172.17.0.1
```

出力される項目は以下のとおりです。

|  　項目       |   説明                 |
|:-------------|:-----------------------|
|NET_DEVICE    |net_device構造体のポインタ|
|NAME          |NIC名                   |
|IP_ADDRESS(ES)|IPアドレス            |



# net_device構造体のダンプ

structコマンドで**eth0**のnet_device構造体 **ffff8d8d75ccd000** の内容をダンプします。


```rb
crash> struct net_device ffff8d8d75ccd000
struct net_device {
  name = "eth0\000\000\000\000\000\000\000\000\000\000\000",
  name_hlist = {
    next = 0x0,
    pprev = 0xffff8d8dbbbc8440
  },
  ifalias = 0x0,
  mem_end = 0,
  mem_start = 0,
  base_addr = 0,
  irq = 0,
  state = 3,
  dev_list = {
    next = 0xffff8d8dba7fa050,
    prev = 0xffff8d8dbbbb2050
  },
  napi_list = {
    next = 0xffff8d8d75c8c450,
    prev = 0xffff8d8d75c8c450
  },
  unreg_list = {
    next = 0xffff8d8d75ccd070,
    prev = 0xffff8d8d75ccd070
  },
  upper_dev_list = {
    next = 0xffff8d8d75ccd080,
    prev = 0xffff8d8d75ccd080
  },
  features = 4296755753,
  hw_features = 35192963811337,
  wanted_features = 1787913,
  vlan_features = 35188668825641,
  hw_enc_features = 8796093022209,
  mpls_features = 1,
  ifindex = 2,
  iflink = 2,
  ...

  以降省略
```

# net_device構造体のメンバーを個別にダンプ

structコマンドで**eth0**のnet_device構造体 **ffff8d8d75ccd000** のメンバーを個別にダンプします。

デバイス名を表示する場合、**net_device.name**を指定します。

```rb
crash> struct net_device.name ffff8d8d75ccd000
  name = "eth0\000\000\000\000\000\000\000\000\000\000\000"
crash>
```

デバイスのインデックス番号を表示する場合、**net_device.ifindex**を指定します。

```rb
crash> struct net_device.ifindex ffff8d8d75ccd000
  ifindex = 2
crash>
```

ifindexの値はip addrコマンドで表示されたものと同一の値です。

デバイスのインタフェースフラグを表示する、**net_device.flags**を指定します。

```rb
crash> struct net_device.flags ffff8d8d75ccd000
  flags = 4099
crash>
```

flagsの値**4096**を16進数に変換すると0x1003となります。これは以下のフラグのORをとった値です。

|フラグ|値|
|-|-|
|IFF_UP        |0x01|
|IFF_BROADCAST |0x02|
|IFF_MULTICAST |0x1000|

デバイスのMTUを表示する、**net_device.mtu**を指定します。

```rb
crash> struct net_device.mtu ffff8d8d75ccd000
  mtu = 1500
crash>
```

MTUの値はip addrコマンドで表示されたものと同一の値です。

# IPv4情報のダンプ

**eth0**のnet_device構造体からIPv4の制御情報**ip_ptr**のポインタ**ffff8d8d7614ec00**をin_device構造体にマッピングして内容をダンプします。

```rb
crash> struct net_device.ip_ptr ffff8d8d75ccd000
  ip_ptr = 0xffff8d8d7614ec00
crash> struct in_device ffff8d8d7614ec00
struct in_device {
  dev = 0xffff8d8d75ccd000,
  refcnt = {
    counter = 3
  },
  dead = 0,
  ifa_list = 0xffff8d8db8423100,
  mc_list = 0xffff8d8dba818f00,
  mc_count = 1,
  mc_tomb_lock = {
    {
      rlock = {
        raw_lock = {
          val = {
            counter = 0
          }
        }
      }
    }
  },
  mc_tomb = 0x0,
  mr_v1_seen = 0,
  mr_v2_seen = 0,
  mr_maxdelay = 0,
  mr_qrv = 2 '\002',
  mr_gq_running = 0 '\000',
  mr_ifc_count = 0 '\000',
  mr_gq_timer = {
    entry = {
      next = 0x0,
      prev = 0x0
    },
    expires = 0,
    base = 0xffffffff8cd03bc0,
    function = 0xffffffff8c2e30f0,
    data = 18446618237443828736,
    slack = -1,
    start_pid = -1,
    start_site = 0x0,
    start_comm = "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
  },
  mr_ifc_timer = {
    entry = {
      next = 0x0,
      prev = 0x0
    },
    expires = 0,
    base = 0xffffffff8cd03bc0,
    function = 0xffffffff8c2e3120,
    data = 18446618237443828736,
    slack = -1,
    start_pid = -1,
    start_site = 0x0,
    start_comm = "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
  },
  arp_parms = 0xffff8d8d766c2840,
  cnf = {
    sysctl = 0xffff8d8d75ef1800,
    data = {1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
    state = {67108863},
    extra_data = {10000, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    extra_state = {65535}
  },
  callback_head = {
    next = 0x0,
    func = 0x0
  }
}
```

**eth0**のin_device構造体の**ifa_list**の内容**ffff8d8db8423100**を**in_ifaddr**構造体にマッピングしてIPアドレスの情報を読み取ります。

```rb
crash> struct in_device.ifa_list ffff8d8d7614ec00
  ifa_list = 0xffff8d8db8423100
crash> list ffff8d8db8423100
ffff8d8db8423100
crash> struct in_addr ffff8d8db8423100
struct in_addr {
  s_addr = 0
}
crash> struct in_ifaddr ffff8d8db8423100
struct in_ifaddr {
  hash = {
    next = 0x0,
    pprev = 0xffffffff8ce6a390
  },
  ifa_next = 0x0,
  ifa_dev = 0xffff8d8d7614ec00,
  callback_head = {
    next = 0x0,
    func = 0x0
  },
  ifa_local = 1040230592,
  ifa_address = 1040230592,
  ifa_mask = 16777215,
  ifa_broadcast = 4278233280,
  ifa_scope = 0 '\000',
  ifa_prefixlen = 24 '\030',
  ifa_flags = 640,
  ifa_label = "eth0\000\000\000\000\000\000\000\000\000\000\000",
  ifa_valid_lft = 0,
  ifa_preferred_lft = 0,
  ifa_cstamp = 4294674072,
  ifa_tstamp = 4294674128
}
```

in_ifaddr構造体のifa_address/ifa_mask の値**1040230592/16777215**をそれぞれ16進数に変換すると**0x3E00A8C0/0xffffff**となります。
これらのバイトオーダーをひっくり返すと **0xc0A8003E/0xffffff00**となり、10進数ドット表記に変換すると、IPアドレス/ネットマスクは192.168.0.62/255.255.255.0であることが判ります。これらはip addrコマンドの出力結果と一致しています。

# ドライバの情報のダンプ

**eth0**のnet_device構造体から**netdev_ops**のポインタ**ffff8d8d7614ec00**をnet_device_ops構造体にマッピングして内容をダンプします。

```rb
crash> struct net_device.netdev_ops ffff8d8d75ccd000
  netdev_ops = 0xffffffffc02d4520
crash> struct  net_device_ops ffffffffc02d4520
struct net_device_ops {
  ndo_init = 0x0,
  ndo_uninit = 0x0,
  ndo_open = 0xffffffffc02d29e0,
  ndo_stop = 0xffffffffc02d0550,
  ndo_start_xmit = 0xffffffffc02d1260,
  {
    ndo_select_queue = 0x0,
    __UNIQUE_ID_rh_kabi_hide51 = {
      ndo_select_queue = 0x0
    },
    {<No data fields>}
  },
  ndo_change_rx_flags = 0x0,
  ndo_set_rx_mode = 0xffffffffc02d2aa0,
  ndo_set_mac_address = 0xffffffffc02d0cf0,
  ndo_validate_addr = 0xffffffff8c282b50,
  ndo_do_ioctl = 0x0,
  ndo_set_config = 0x0,
  ndo_change_mtu_rh74 = 0x0,
  ndo_neigh_setup = 0x0,
  ndo_tx_timeout = 0x0,
  {
    ndo_get_stats64 = 0xffffffffc02d03d0,
    __UNIQUE_ID_rh_kabi_hide52 = {
      ndo_get_stats64 = 0xffffffffc02d03d0
    },
    {<No data fields>}
  },
  ndo_get_stats = 0x0,
  ndo_vlan_rx_add_vid = 0xffffffffc02d0e60,
  ndo_vlan_rx_kill_vid = 0xffffffffc02d0ef0,
  ndo_poll_controller = 0xffffffffc02d0140,
  ndo_netpoll_setup = 0x0,
  ndo_netpoll_cleanup = 0x0,
  ndo_busy_poll = 0x0,
  ndo_set_vf_mac = 0x0,
  ndo_set_vf_vlan_rh73 = 0x0,
  ndo_set_vf_tx_rate = 0x0,
  ndo_set_vf_spoofchk = 0x0,
  ndo_get_vf_config = 0x0,
  ndo_set_vf_link_state = 0x0,
  ndo_set_vf_port = 0x0,
  ndo_get_vf_port = 0x0,
  ndo_setup_tc_rh72 = 0x0,
  ndo_fcoe_enable = 0x0,
  ndo_fcoe_disable = 0x0,
  ndo_fcoe_ddp_setup = 0x0,
  ndo_fcoe_ddp_done = 0x0,
  ndo_fcoe_ddp_target = 0x0,
  ndo_fcoe_get_hbainfo = 0x0,
  ndo_fcoe_get_wwn = 0x0,
  ndo_rx_flow_steer = 0x0,
  ...

  以降省略
```

試しにイーサネットドライバのopenハンドラ **ndo_open**を逆アセンブルしてみます。

```rb
crash> dis ffffffffc02d29e0 10
0xffffffffc02d29e0 <virtnet_open>:      nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffc02d29e5 <virtnet_open+5>:    push   %rbp
0xffffffffc02d29e6 <virtnet_open+6>:    mov    %rsp,%rbp
0xffffffffc02d29e9 <virtnet_open+9>:    push   %r15
0xffffffffc02d29eb <virtnet_open+11>:   lea    0x900(%rdi),%r15
0xffffffffc02d29f2 <virtnet_open+18>:   push   %r14
0xffffffffc02d29f4 <virtnet_open+20>:   lea    0x8c0(%rdi),%r14
0xffffffffc02d29fb <virtnet_open+27>:   push   %r13
0xffffffffc02d29fd <virtnet_open+29>:   push   %r12
0xffffffffc02d29ff <virtnet_open+31>:   mov    %rdi,%r12
crash>
```

シンボルが**virtnet_open**となっていることから、イーサネットドライバは**virtnet**であることが判ります。

# その他の情報のダンプ

まず、カーネルソースを調査して出力すべき制御情報としてどのようなものがあるかを確認します。次にそれらをリンクしているポインタやテーブル管理リストが格納されているグローバル変数を特定し、順番にたどっていくことで、最終的に確認したい情報が表示できるはずです。