mem_cgroup_charge_common()はだいぶ前に消えていた (; ・`д・´) ナ、ナンダッテー !! (`・д´・ (`・д´・ ;)

昨日の続きです。

4.10にあるドキュメントには課金の処理ではmem_cgroup_charge_common()を使うよーなんて書いてあったんですが、この関数はだいぶ昔になくなってました。

3.15で消えたようです。さらに言うと、このパッチで使用している__mem_cgroup_commit_charge()は3.17で消えたようです。 github.com

3.17の開発時に課金周りの処理が書き換えられたみたいで、このパッチで__mem_cgroup_commit_charge()からcommit_charge()に変わってました。 github.com

commit_charge()は4.10にもありました。やったね!

IoTエンジニア養成読本

IoTエンジニア養成読本

Documentation/cgroup-v1/memory.txtのめも

コードではなくてドキュメントもたまには読みましょうということで、Documentation/cgroup-v1/memory.txtです。

accounting == 課金です。以下のめもは本文を自分が分かれば良い程度に大雑把に意訳した感じです。 機械翻訳はとかしてないので安心ですね?

2.2. Accounting

Figure 1の説明のところ。

  1. 課金は各cgroupの単位
  2. 各mm_struct構造体は自分がどのcgroupに属しているかわかる
  3. 各page(構造体だよな)はpage_cgroupへのポインタを持っていて、ここからどのcgroupに属しているかわかる

課金は以下の流れで行われる。

  1. mem_cgroup_charge_common()が呼ばれて、最低限のデータの設定、メモリの使用量が設定値を超えるかチェック
  2. 超えるようならこのcgroupを対象にページ回収の処理を行う。詳細はreclaimのセクションで。
  3. すべてが上手いこと行くようなら、page_cgroupの更新をする。page_cgroupはLRUを持ってる。page_cgroupはbootやメモリホットプラグでメモリが追加されたときにインスタンスが確保される

2.2.1 Accounting details

すべての無名ページ(RSS)とページキャッシュは課金される。reclaimableじゃないページとLRUで管理されないようなページは課金されない。ようは通常の仮想メモリ管理下にあるページが課金される。 RSSのページはページフォルト時に課金されるけど、すでに課金されてた場合は除く。ページキャッシュはinode(radix-tree)に入れられるときに課金する。プロセスのページテーブルにマップするときは重複して課金しないように気をつけないといけない。

RSSページは完全にunmmapするときに課金した分を減らす。ページキャッシュはradix-treeから削除するときに減らす。unaccountedって日本語でなんて言えばいいか思い浮かばなかった(´・ω・`) もしRSSページがkswapdによってunmapされた場合、SwapCacheとしてシステムによって完全に解放されるまで存在する可能性がある。こういったSwapCacheも課金の対象になる。swapされたページはmapされるまでは課金されない。

カーネルはswap inの先読みや、複数のswapされたページを一度に読みこむんだけど、このときにpage faultが起きる場合があり、この場合は課金しないようにする必要がある。

ページのマイグレーション時には課金の情報はkeepされる。

LRUで管理されるページを課金するのは利用中のページを管理するため。VM視点で行くとLRUで管理していないページはVMの管理外にある。

(´-`).。oO(2.3はまた次回

詳解 システム・パフォーマンス

詳解 システム・パフォーマンス

memory cgroupの初期化処理辺りを読む

誰得なめも。バージョンは4.1.15。

__initがついているのは以下の3関数。

  1. mem_cgroup_init()
  2. enable_swap_account()
  3. mem_cgroup_swap_init()

当たり前だけど、__initがあるのでカーネル起動時の初期化で呼ばれる。

mem_cgroup_init()はこのような関数。

5774 static int __init mem_cgroup_init(void)
5775 {
5776         int cpu, node;
5777 
5778         hotcpu_notifier(memcg_cpu_hotplug_callback, 0);
5779 
5780         for_each_possible_cpu(cpu)
5781                 INIT_WORK(&per_cpu_ptr(&memcg_stock, cpu)->work,
5782                           drain_local_stock);
5783 
5784         for_each_node(node) {
5785                 struct mem_cgroup_tree_per_node *rtpn;
5786                 int zone;
5787 
5788                 rtpn = kzalloc_node(sizeof(*rtpn), GFP_KERNEL,
5789                                     node_online(node) ? node : NUMA_NO_NODE);
5790 
5791                 for (zone = 0; zone < MAX_NR_ZONES; zone++) {
5792                         struct mem_cgroup_tree_per_zone *rtpz;
5793 
5794                         rtpz = &rtpn->rb_tree_per_zone[zone];
5795                         rtpz->rb_root = RB_ROOT;
5796                         spin_lock_init(&rtpz->lock);
5797                 }
5798                 soft_limit_tree.rb_tree_per_node[node] = rtpn;
5799         }
5800 
5801         return 0;
5802 }
5803 subsys_initcall(mem_cgroup_init);

hotcpu_notifier()はcpu hotplugの処理なので無視して、最初のfor_each_possible_cpuマクロはper cpuなデータであるstruct memcg_stock_pcpの初期化。memcg_stock_pcp構造体はこのような構造体。現段階では初期化処理しか追っていないので、この構造体の使用目的は未確認。とりあえず、ワークキューがあるのと(struct work_struct)、nr_pagesというどう考えでもページ数の数を保持するであろうメンバ変数があるのでページ関連の操作で使われるのでしょう。

2055 struct memcg_stock_pcp {
2056         struct mem_cgroup *cached; /* this never be root cgroup */
2057         unsigned int nr_pages;
2058         struct work_struct work;
2059         unsigned long flags;
2060 #define FLUSHING_CACHED_CHARGE  0
2061 };
2062 static DEFINE_PER_CPU(struct memcg_stock_pcp, memcg_stock);

次のfor_each_nodeマクロでメモリノード単位で処理が行われる。ここで出てくるmem_cgroup_tree_per_node構造体はこんな感じなんだけど、mm/memcontrol.cで定義されてるので他では使われない模様。赤黒木なデータ構造なんですね。

 172 struct mem_cgroup_tree_per_node {
 173         struct mem_cgroup_tree_per_zone rb_tree_per_zone[MAX_NR_ZONES];
 174 };

そして、各ゾーン(これはZONE_NORMALとかですね。)毎にrbツリーの初期化をします。

5791                 for (zone = 0; zone < MAX_NR_ZONES; zone++) {
5792                         struct mem_cgroup_tree_per_zone *rtpz;
5793 
5794                         rtpz = &rtpn->rb_tree_per_zone[zone];
5795                         rtpz->rb_root = RB_ROOT;
5796                         spin_lock_init(&rtpz->lock);
5797                 }

初期化したものはsoft_limit_treeという変数のrb_tree_per_node配列に突っ込みます。

5798                 soft_limit_tree.rb_tree_per_node[node] = rtpn;

soft_limit_treeはこう宣言されていて基本は参照だけみたいです。

 180 static struct mem_cgroup_tree soft_limit_tree __read_mostly;

ちなみに、MAX_NR_ZONESはinclude/generated配下のファイルにあるのでビルド時に定義されるようです。

./include/generated/bounds.h:10:#define MAX_NR_ZONES 4 /* __MAX_NR_ZONES        # */

2つ目の初期化処理はenable_swap_account()で、これはカーネルの起動時のコマンドラインで渡されたswapaccountの処理ですね。really_do_swap_account変数に値を設定するだけです。

5877 static int __init enable_swap_account(char *s)
5878 {
5879         if (!strcmp(s, "1"))
5880                 really_do_swap_account = 1;
5881         else if (!strcmp(s, "0"))
5882                 really_do_swap_account = 0;
5883         return 1;
5884 }
5885 __setup("swapaccount=", enable_swap_account);
5886 

このコマンドライン引数がなければ呼ばれないですけど。その場合はデフォルト値があるのでそちらが使われます。

5870 /* for remember boot option*/
5871 #ifdef CONFIG_MEMCG_SWAP_ENABLED
5872 static int really_do_swap_account __initdata = 1;
5873 #else
5874 static int really_do_swap_account __initdata;
5875 #endif

3つ目はmem_cgroup_swap_init()です。memory cgroupがenableで、really_do_swap_accountが1の場合(あえてswapaccount=0を設定しなければ大抵1になってるのでは)に処理があります。

5914 static int __init mem_cgroup_swap_init(void)
5915 {
5916         if (!mem_cgroup_disabled() && really_do_swap_account) {
5917                 do_swap_account = 1;
5918                 WARN_ON(cgroup_add_legacy_cftypes(&memory_cgrp_subsys,
5919                                                   memsw_cgroup_files));
5920         }
5921         return 0;
5922 }
5923 subsys_initcall(mem_cgroup_swap_init);

do_swap_accountを1に設定しているので、swapもアカウンティングするよというのと、cgroup_add_legacy_cftypes()を呼んでいるのでmemory cgroupのファイル作成処理があります。作成するのは以下のファイルです。/sys/kernel/fs/cgroup/memoryに以下のファイルが作られます。

5887 static struct cftype memsw_cgroup_files[] = {
5888         {
5889                 .name = "memsw.usage_in_bytes",
5890                 .private = MEMFILE_PRIVATE(_MEMSWAP, RES_USAGE),
5891                 .read_u64 = mem_cgroup_read_u64,
5892         },
5893         {
5894                 .name = "memsw.max_usage_in_bytes",
5895                 .private = MEMFILE_PRIVATE(_MEMSWAP, RES_MAX_USAGE),
5896                 .write = mem_cgroup_reset,
5897                 .read_u64 = mem_cgroup_read_u64,
5898         },
5899         {
5900                 .name = "memsw.limit_in_bytes",
5901                 .private = MEMFILE_PRIVATE(_MEMSWAP, RES_LIMIT),
5902                 .write = mem_cgroup_write,
5903                 .read_u64 = mem_cgroup_read_u64,
5904         },
5905         {
5906                 .name = "memsw.failcnt",
5907                 .private = MEMFILE_PRIVATE(_MEMSWAP, RES_FAILCNT),
5908                 .write = mem_cgroup_reset,
5909                 .read_u64 = mem_cgroup_read_u64,
5910         },
5911         { },    /* terminate */
5912 };

cgroup_add_legacy_cftypes()に渡しているmemory_cgrp_subsysは以下のようになってます。

  75 struct cgroup_subsys memory_cgrp_subsys __read_mostly;
  76 EXPORT_SYMBOL(memory_cgrp_subsys);

初期化処理はこんな感じですね。

cgroupのv1とv2の切り分け的なところ

cgroupはv1とv2という2つの実装(と言っていいのか?)があってそれらはkernel/cgroup.cで一つのコードベースにまとまっているのでコード読んでで混乱するなーというところで、誰得なめもを。

特に、4.1とかのカーネルだとv2のコードはまだ正式版となってないけどマージはされていて、DEVELsane_behaviorというオプションを使うことでv2の機能が使えるようになってます(参考:

Linux 3.16 から試せる cgroup の単一階層構造 (1) - TenForward)。今はv2がちゃんと入っているのでこのオプションは不要で、cgroupのマウント時にcgroup v2としてマウントするかどうかで切り分けできます。cgroup_mount()のis_v2がそれです。

2083 static struct dentry *cgroup_mount(struct file_system_type *fs_type,
2084                          int flags, const char *unused_dev_name,
2085                          void *data)
2086 {
2087         bool is_v2 = fs_type == &cgroup2_fs_type;
2088         struct super_block *pinned_sb = NULL;
2089         struct cgroup_namespace *ns = current->nsproxy->cgroup_ns;
2090         struct cgroup_subsys *ss;
2091         struct cgroup_root *root;

v1とv2は階層構造が違うんですが、どちらの構造を見せるかという判定には古めのカーネルならcgrp_dfl_root_visible変数、v2が正式に入ってからならcgrp_dfl_visible変数を使います。これはbool値を格納する変数で、trueならv2の構成を見せます。これらの変数の値を設定するのはcgroup_mount()です。

4.1の場合は以下のようにDEVELsane_behaviorオプションが設定されている場合にtrueをセットします。

1763         /* look for a matching existing root */
1764         if (opts.flags & CGRP_ROOT_SANE_BEHAVIOR) {
1765                 cgrp_dfl_root_visible = true;
1766                 root = &cgrp_dfl_root;
1767                 cgroup_get(&root->cgrp);
1768                 ret = 0;
1769                 goto out_unlock;
1770         }

4.10の場合はcgroup_mountの最初で設定したis_v2がtrueの場合(cgroup v2のファイルシステムをマウントする時)に設定します。

2113         if (is_v2) {
2114                 if (data) {
2115                         pr_err("cgroup2: unknown option \"%s\"\n", (char *)data);
2116                         put_cgroup_ns(ns);
2117                         return ERR_PTR(-EINVAL);
2118                 }
2119                 cgrp_dfl_visible = true;
2120                 root = &cgrp_dfl_root;
2121                 cgroup_get(&root->cgrp);
2122                 goto out_mount;
2123         }

この変数を使っているところは1箇所だけで、proc_cgroup_show()です。この関数は何をやっているかというと/proc//cgroupファイルの中身を表示します。ファイルの中身はこのような感じです。

masami@saga:~$ cat /proc/self/cgroup
11:pids:/user.slice/user-1000.slice/user@1000.service
10:cpuset:/
9:blkio:/
8:net_cls,net_prio:/
7:hugetlb:/
6:perf_event:/
5:cpu,cpuacct:/
4:devices:/user.slice
3:freezer:/
2:memory:/
1:name=systemd:/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service

4.1も4.10カーネルもproc_cgroup_show()の以下のところで使ってます。

5111                 if (root == &cgrp_dfl_root && !cgrp_dfl_root_visible)
5112                         continue;

ここで出てくるcgrp_dfl_rootはstaticな変数です。

144 struct cgroup_root cgrp_dfl_root;

4.1の場合、cgroup_init_early()init_cgroup_root()を呼び出して最初の初期化しています。

4952 int __init cgroup_init_early(void)
4953 {
4954         static struct cgroup_sb_opts __initdata opts;
4955         struct cgroup_subsys *ss;
4956         int i;
4957 
4958         init_cgroup_root(&cgrp_dfl_root, &opts);
4959         cgrp_dfl_root.cgrp.self.flags |= CSS_NO_REF;

init_cgroup_root()ではcgrp_dfl_rootのメンバ変数cgrpを対象に初期化してます。

1624 static void init_cgroup_root(struct cgroup_root *root,
1625                              struct cgroup_sb_opts *opts)
1626 {
1627         struct cgroup *cgrp = &root->cgrp;
1628 
1629         INIT_LIST_HEAD(&root->root_list);
1630         atomic_set(&root->nr_cgrps, 1);
1631         cgrp->root = root;
1632         init_cgroup_housekeeping(cgrp);
1633         idr_init(&root->cgroup_idr);
1634 
1635         root->flags = opts->flags;
1636         if (opts->release_agent)
1637                 strcpy(root->release_agent_path, opts->release_agent);
1638         if (opts->name)
1639                 strcpy(root->name, opts->name);
1640         if (opts->cpuset_clone_children)
1641                 set_bit(CGRP_CPUSET_CLONE_CHILDREN, &root->cgrp.flags);
1642 }

次に、cgroup_init()でさらなる設定が入ります。ここではcgroup_setup_root()でcgroup本体(という言い方で良いのか?)を行っていて、cgroup_init()ではcgroupのサブシステムに関する設定を行っています。

( ´ー`)フゥー...

Linuxシステム[実践]入門 (Software Design plus)

Linuxシステム[実践]入門 (Software Design plus)

Linuxカーネルのコードネームの扱いが不憫なので/proc/sys/kernel/codenameで読めるようにした

たぶん、この辺の会話から

この記事に繋がってたりするのかな?なんて思ったりということで、

さらに話を続けてみてですね、こんなpatchを書きました。4.10.0-rc6がベースのカーネルです。

diff --git a/Makefile b/Makefile
index 96b27a8..6c3f392 100644
--- a/Makefile
+++ b/Makefile
@@ -1024,6 +1024,7 @@ endif
 prepare2: prepare3 prepare-compiler-check outputmakefile asm-generic

 prepare1: prepare2 $(version_h) include/generated/utsrelease.h \
+                   include/generated/utscodename.h \
                    include/config/auto.conf
        $(cmd_crmodverdir)

@@ -1097,6 +1098,10 @@ define filechk_version.h
        echo '#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))';)
 endef

+define filechk_utscodename.h
+       (echo \#define UTS_CODENAME \"$(NAME)\";)
+endef
+
 $(version_h): $(srctree)/Makefile FORCE
        $(call filechk,version.h)
        $(Q)rm -f $(old_version_h)
@@ -1104,6 +1109,9 @@ $(version_h): $(srctree)/Makefile FORCE
 include/generated/utsrelease.h: include/config/kernel.release FORCE
        $(call filechk,utsrelease.h)

+include/generated/utscodename.h: $(srctree)/Makefile FORCE
+       $(call filechk,utscodename.h)
+
 PHONY += headerdep
 headerdep:
        $(Q)find $(srctree)/include/ -name '*.h' | xargs --max-args 1 \
diff --git a/init/version.c b/init/version.c
index fe41a63..82fde8f 100644
--- a/init/version.c
+++ b/init/version.c
@@ -11,6 +11,7 @@
 #include <linux/uts.h>
 #include <linux/utsname.h>
 #include <generated/utsrelease.h>
+#include <generated/utscodename.h>
 #include <linux/version.h>
 #include <linux/proc_ns.h>

@@ -45,7 +46,7 @@ EXPORT_SYMBOL_GPL(init_uts_ns);
 /* FIXED STRINGS! Don't touch! */
 const char linux_banner[] =
        "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
-       LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";
+       LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION UTS_CODENAME "\n";

 const char linux_proc_banner[] =
        "%s version %s"
diff --git a/kernel/utsname_sysctl.c b/kernel/utsname_sysctl.c
index c8eac43..8f3c12a 100644
--- a/kernel/utsname_sysctl.c
+++ b/kernel/utsname_sysctl.c
@@ -14,6 +14,7 @@
 #include <linux/utsname.h>
 #include <linux/sysctl.h>
 #include <linux/wait.h>
+#include <generated/utscodename.h>

 #ifdef CONFIG_PROC_SYSCTL

@@ -104,6 +105,13 @@ static struct ctl_table uts_kern_table[] = {
                .proc_handler   = proc_do_uts_string,
                .poll           = &domainname_poll,
        },
+       {
+               .procname       = "codename",
+               .data           = UTS_CODENAME,
+               .maxlen         = sizeof(UTS_CODENAME),
+               .mode           = 0444,
+               .proc_handler   = proc_do_uts_string,
+       },
        {}
 };

差分的にはこんな感じです。

masami@kerntest:~/linux-kernel (codename *)$ diffstat ../codename.patch
 Makefile                |    8 ++++++++
 init/version.c          |    3 ++-
 kernel/utsname_sysctl.c |    8 ++++++++
 3 files changed, 18 insertions(+), 1 deletion(-)

こうすると、dmesgの一番先頭、ビルド時刻の後ろにコードネーム(Fearless Coyote)がついてます。

masami@kerntest:~/linux-kernel (codename *)$ dmesg | head -n 1
[    0.000000] Linux version 4.10.0-rc6-ktest+ (masami@kerntest) (gcc version 6.3.1 20161221 (Red Hat 6.3.1-1) (GCC) ) #7 SMP Wed Feb 1 00:03:08 JST 2017Fearless Coyote

あと、/proc/sys/kernel/codenameファイルを作ってそれを読むこともできます。

masami@kerntest:~/linux-kernel (codename *)$ cat /proc/sys/kernel/codename
Fearless Coyote

これで起動中のカーネルのコードネームを確認できるようになりましたヽ(=´▽`=)ノ

だがしかし、new_utsname構造体に値を追加したらさすがにABIをぶち壊したようでブートできなかったので、unameで確認するのは諦めました/(^o^)\

systemd: 228のlocal exploit(CVE-2016-10156)めも

systemd 228にlocal exploitがあったようなのでどんなバグだったのかをメモします。脆弱性の説明は、Headsup: systemd v228 local root exploit (CVE-2016-10156)に書かれてました。CVEはCVE-2016-10156です。

ちなみに、fedora 25のsystemdは201701/24時点で231です。

修正patchはbasic: fix touch() creating files with 07777 mode · systemd/systemd@06eeacb · GitHubです。 修正は以下のようになってます。

-        fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode > 0 ? mode : 0644);
+        fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY,
+                        (mode == 0 || mode == MODE_INVALID) ? 0644 : mode);

修正された関数はbasic/fs-util.cのtouch_file()です。この関数はtouch()から呼ばれます。

int touch(const char *path) {
        return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID);

修正前のコードではmode > 0としてmodeの値を比較していますが、この変数は符号なしなのでこの条件は常にtrueになります。

mode_tは/usr/include/sys/types.hでこうなっていて、

#ifndef __mode_t_defined
typedef __mode_t mode_t;
# define __mode_t_defined
#endif

/usr/include/bits/types.hでは__MODE_T_TYPEとなってます。

__STD_TYPE __MODE_T_TYPE __mode_t;      /* Type of file attribute bitmasks.  */

/usr/include/bits/typesize.hでこのようになり、

#define __MODE_T_TYPE           __U32_TYPE

/usr/include/bits/types.hでこのように定義されてます。

#define __U32_TYPE              unsigned int

というわけでmode > 0は常に真になるからmodeがそのまま使われます。

つぎに、modeですが、これはtouch()がMODE_INVALIDを渡しています。MODE_INVALIDはsrc/basic/parse-util.hで以下のように定義されています。

#define MODE_INVALID ((mode_t) -1)

mode_tは符号なしなので、全bitが立ちますね。 なのでこんな感じのファイルが作れるからどのようなファイルを作るかによっては・・・(´・ω・`)

masami@saga:~$ cat mode_test.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
        mode_t mode = (mode_t) -1;
        int fd = open("mode_test", O_CREAT | O_RDWR, mode);

        if (fd < 0) {
                perror("open");
                return -1;
        }
        close(fd);
        return 0;
}
masami@saga:~$ gcc mode_test.c
masami@saga:~$ ./a.out
masami@saga:~$ ls -la mode_test
-rwsrwsr-t. 1 masami masami 0 Jan 25 00:12 mode_test*
masami@saga:~$

バグの内容としては変数の型を間違えたから条件文で正しく処理できてないかったというところですが、そこからこういった脆弱性になるわけですねφ(..)メモメモ

( ´ー`)フゥー...

gdbでアドレスが指す先のアドレスをdereferenceするコマンドを作った

gdbであるアドレスがアドレスを指している場合のdereferenceを簡単にやりたかったのでpythonでコマンド作ってみました。

これは例えばcのコードがこうで、

void func(char *p) 
{
        printf("[*] %s\n", p);
}

func()のディスアセンブル結果にこのような処理があるとします。

   0x000000000040066e <+8>:     mov    %rdi,-0x8(%rbp)

このとき、rbp - 8のアドレスは0x7fffffffdc68で、ここはアドレス0x0000000000602010を指しています。

(gdb) x/gx $rbp - 8
0x7fffffffdc68: 0x0000000000602010

このアドレスが指す内容は以下のようにしてdereferenceできますが、ちょっと面倒ですよね。

(gdb) x/s *(char **) ($rbp - 0x8)
0x602010:       "test"

ということで、作ったのがこちらです。

github.com

xコマンドのシンタックスを使う感じでdgp s $rbp - 8というように使えます。

f:id:masami256:20170108223631p:plain

その他、こんな感じです。

(gdb) dgp s $rbp - 8                                                                                                                                                                                               
[*]execute: x/gx $rbp - 8
[*]execute: x/s 0x0000000000602010
0x602010:       "test"

(gdb) dgp 4c $rbp - 8
[*]execute: x/gx $rbp - 8
[*]execute: x/4c 0x0000000000602010
0x602010:       116 't' 101 'e' 115 's' 116 't'

(gdb) dgp 4s $rbp - 8
[*]execute: x/gx $rbp - 8
[*]execute: x/4s 0x0000000000602010
0x602010:       "test"
0x602015:       ""
0x602016:       ""
0x602017:       ""

(gdb) dgp wx $rbp
[*]execute: x/gx $rbp + 0
[*]execute: x/wx 0x00007fffffffdca0
0x7fffffffdca0: 0x00400720

pythongdbの拡張できるの便利ですね〜

( ´ー`)フゥー...