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の拡張できるの便利ですね〜

( ´ー`)フゥー...

c++で書かれたコードのアセンブリの読み方めも

http://pwnable.kr/play.phpのuafがc++で書かれていたので読む必要が有ったので今後のためにもメモ程度に覚書を。今回はgdbのdisasとかobjdumpでアセンブリを読む時にnewはどのように呼んでいるるのかとかのめもです。

読んでいるバイナリのタイプはLinuxのELF64です。 基本的にはcHumanというクラスが有って、このクラスを継承したManとWomanというクラスがあります。main関数では最初にManとWomanのクラスのインスタンスをnewして作成します。そして、virtual関数のintroduce()を呼びます。

やっている処理のc++のコードを単純に書くとこんな感じになります。

Human *m = new Man("hoge", 1);
m->introduce();

Humanクラスはこのようなクラスです。

class Human{
private:
    virtual void give_shell(){
        system("/bin/sh");
    }
protected:
    int age;
    string name;
public:
    virtual void introduce(){
        cout << "My name is " << name << endl;
        cout << "I am " << age << " years old" << endl;
    }
};

main関数のディスアセンブル結果はこちらです。

0000000000400ec4 <main>:
  400ec4:   55                     push   %rbp
  400ec5:   48 89 e5               mov    %rsp,%rbp
  400ec8:   41 54                 push   %r12
  400eca:   53                     push   %rbx
  400ecb:   48 83 ec 50           sub    $0x50,%rsp
  400ecf:   89 7d ac                mov    %edi,-0x54(%rbp)
  400ed2:   48 89 75 a0           mov    %rsi,-0x60(%rbp)
  400ed6:   48 8d 45 ee            lea    -0x12(%rbp),%rax
  400eda:   48 89 c7               mov    %rax,%rdi
  400edd:   e8 8e fe ff ff          callq  400d70 <_ZNSaIcEC1Ev@plt>
  400ee2:   48 8d 55 ee            lea    -0x12(%rbp),%rdx
  400ee6:   48 8d 45 b0            lea    -0x50(%rbp),%rax
  400eea:   be f0 14 40 00         mov    $0x4014f0,%esi
  400eef:   48 89 c7               mov    %rax,%rdi
  400ef2:   e8 19 fe ff ff         callq  400d10 <_ZNSsC1EPKcRKSaIcE@plt>
  400ef7:   4c 8d 65 b0             lea    -0x50(%rbp),%r12
  400efb:   bf 18 00 00 00         mov    $0x18,%edi
  400f00:   e8 8b fe ff ff          callq  400d90 <_Znwm@plt>
  400f05:   48 89 c3               mov    %rax,%rbx
  400f08:   ba 19 00 00 00         mov    $0x19,%edx
  400f0d:   4c 89 e6                mov    %r12,%rsi
  400f10:   48 89 df               mov    %rbx,%rdi
  400f13:   e8 4c 03 00 00          callq  401264 <_ZN3ManC1ESsi>
  400f18:   48 89 5d c8            mov    %rbx,-0x38(%rbp)
  400f1c:   48 8d 45 b0            lea    -0x50(%rbp),%rax
  400f20:   48 89 c7               mov    %rax,%rdi
  400f23:   e8 d8 fd ff ff           callq  400d00 <_ZNSsD1Ev@plt>
  400f28:   48 8d 45 ee            lea    -0x12(%rbp),%rax
  400f2c:   48 89 c7               mov    %rax,%rdi
  400f2f:   e8 0c fe ff ff           callq  400d40 <_ZNSaIcED1Ev@plt>
  400f34:   48 8d 45 ef            lea    -0x11(%rbp),%rax
  400f38:   48 89 c7               mov    %rax,%rdi
  400f3b:   e8 30 fe ff ff         callq  400d70 <_ZNSaIcEC1Ev@plt>
  400f40:   48 8d 55 ef            lea    -0x11(%rbp),%rdx
  400f44:   48 8d 45 c0            lea    -0x40(%rbp),%rax
  400f48:   be f5 14 40 00         mov    $0x4014f5,%esi
  400f4d:   48 89 c7               mov    %rax,%rdi
  400f50:   e8 bb fd ff ff           callq  400d10 <_ZNSsC1EPKcRKSaIcE@plt>
  400f55:   4c 8d 65 c0             lea    -0x40(%rbp),%r12
  400f59:   bf 18 00 00 00         mov    $0x18,%edi
  400f5e:   e8 2d fe ff ff          callq  400d90 <_Znwm@plt>
  400f63:   48 89 c3               mov    %rax,%rbx
  400f66:   ba 15 00 00 00         mov    $0x15,%edx
  400f6b:   4c 89 e6                mov    %r12,%rsi
  400f6e:   48 89 df               mov    %rbx,%rdi
  400f71:   e8 92 03 00 00         callq  401308 <_ZN5WomanC1ESsi>
  400f76:   48 89 5d d0            mov    %rbx,-0x30(%rbp)
  400f7a:   48 8d 45 c0            lea    -0x40(%rbp),%rax
  400f7e:   48 89 c7               mov    %rax,%rdi
  400f81:   e8 7a fd ff ff          callq  400d00 <_ZNSsD1Ev@plt>
  400f86:   48 8d 45 ef            lea    -0x11(%rbp),%rax
  400f8a:   48 89 c7               mov    %rax,%rdi
  400f8d:   e8 ae fd ff ff           callq  400d40 <_ZNSaIcED1Ev@plt>
  400f92:   be fa 14 40 00         mov    $0x4014fa,%esi
  400f97:   bf 60 22 60 00           mov    $0x602260,%edi
  400f9c:   e8 4f fd ff ff          callq  400cf0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
  400fa1:   48 8d 45 e8            lea    -0x18(%rbp),%rax
  400fa5:   48 89 c6               mov    %rax,%rsi
  400fa8:   bf e0 20 60 00         mov    $0x6020e0,%edi
  400fad:   e8 1e fe ff ff          callq  400dd0 <_ZNSirsERj@plt>
  400fb2:   8b 45 e8                mov    -0x18(%rbp),%eax
  400fb5:   83 f8 02               cmp    $0x2,%eax
  400fb8:   74 46                 je     401000 <main+0x13c>
  400fba:   83 f8 03               cmp    $0x3,%eax
  400fbd:   0f 84 b3 00 00 00       je     401076 <main+0x1b2>
  400fc3:   83 f8 01               cmp    $0x1,%eax
  400fc6:   74 05                 je     400fcd <main+0x109>
  400fc8:   e9 dc 00 00 00           jmpq   4010a9 <main+0x1e5>
  400fcd:   48 8b 45 c8            mov    -0x38(%rbp),%rax
  400fd1:   48 8b 00              mov    (%rax),%rax
  400fd4:   48 83 c0 08           add    $0x8,%rax
  400fd8:   48 8b 10              mov    (%rax),%rdx
  400fdb:   48 8b 45 c8            mov    -0x38(%rbp),%rax
  400fdf:   48 89 c7               mov    %rax,%rdi
  400fe2:   ff d2                 callq  *%rdx
  400fe4:   48 8b 45 d0            mov    -0x30(%rbp),%rax
  400fe8:   48 8b 00              mov    (%rax),%rax
  400feb:   48 83 c0 08           add    $0x8,%rax
  400fef:   48 8b 10              mov    (%rax),%rdx
  400ff2:   48 8b 45 d0            mov    -0x30(%rbp),%rax
  400ff6:   48 89 c7               mov    %rax,%rdi
  400ff9:   ff d2                 callq  *%rdx
  400ffb:   e9 a9 00 00 00           jmpq   4010a9 <main+0x1e5>
  401000:  48 8b 45 a0            mov    -0x60(%rbp),%rax
  401004:  48 83 c0 08           add    $0x8,%rax
  401008:  48 8b 00              mov    (%rax),%rax
  40100b:   48 89 c7               mov    %rax,%rdi
  40100e:   e8 0d fd ff ff           callq  400d20 <atoi@plt>
  401013:  48 98                 cltq   
  401015:  48 89 45 d8           mov    %rax,-0x28(%rbp)
  401019:  48 8b 45 d8            mov    -0x28(%rbp),%rax
  40101d:   48 89 c7               mov    %rax,%rdi
  401020:  e8 4b fc ff ff          callq  400c70 <_Znam@plt>
  401025:  48 89 45 e0           mov    %rax,-0x20(%rbp)
  401029:  48 8b 45 a0            mov    -0x60(%rbp),%rax
  40102d:   48 83 c0 10           add    $0x10,%rax
  401031:  48 8b 00              mov    (%rax),%rax
  401034:  be 00 00 00 00          mov    $0x0,%esi
  401039:  48 89 c7               mov    %rax,%rdi
  40103c:   b8 00 00 00 00          mov    $0x0,%eax
  401041:  e8 7a fd ff ff          callq  400dc0 <open@plt>
  401046:  48 8b 55 d8            mov    -0x28(%rbp),%rdx
  40104a:   48 8b 4d e0             mov    -0x20(%rbp),%rcx
  40104e:   48 89 ce               mov    %rcx,%rsi
  401051:  89 c7                   mov    %eax,%edi
  401053:  e8 48 fc ff ff         callq  400ca0 <read@plt>
  401058:  be 13 15 40 00           mov    $0x401513,%esi
  40105d:   bf 60 22 60 00           mov    $0x602260,%edi
  401062:  e8 89 fc ff ff         callq  400cf0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
  401067:  be 60 0d 40 00         mov    $0x400d60,%esi
  40106c:   48 89 c7               mov    %rax,%rdi
  40106f:   e8 dc fc ff ff           callq  400d50 <_ZNSolsEPFRSoS_E@plt>
  401074:  eb 33                   jmp    4010a9 <main+0x1e5>
  401076:  48 8b 5d c8             mov    -0x38(%rbp),%rbx
  40107a:   48 85 db               test   %rbx,%rbx
  40107d:   74 10                 je     40108f <main+0x1cb>
  40107f:   48 89 df               mov    %rbx,%rdi
  401082:  e8 b3 01 00 00           callq  40123a <_ZN5HumanD1Ev>
  401087:  48 89 df               mov    %rbx,%rdi
  40108a:   e8 f1 fb ff ff           callq  400c80 <_ZdlPv@plt>
  40108f:   48 8b 5d d0             mov    -0x30(%rbp),%rbx
  401093:  48 85 db               test   %rbx,%rbx
  401096:  74 10                 je     4010a8 <main+0x1e4>
  401098:  48 89 df               mov    %rbx,%rdi
  40109b:   e8 9a 01 00 00          callq  40123a <_ZN5HumanD1Ev>
  4010a0:   48 89 df               mov    %rbx,%rdi
  4010a3:   e8 d8 fb ff ff           callq  400c80 <_ZdlPv@plt>
  4010a8:   90                     nop
  4010a9:   e9 e4 fe ff ff           jmpq   400f92 <main+0xce>
  4010ae:   49 89 c4               mov    %rax,%r12
  4010b1:   48 89 df               mov    %rbx,%rdi
  4010b4:   e8 c7 fb ff ff           callq  400c80 <_ZdlPv@plt>
  4010b9:   4c 89 e3                mov    %r12,%rbx
  4010bc:   eb 03                   jmp    4010c1 <main+0x1fd>
  4010be:   48 89 c3               mov    %rax,%rbx
  4010c1:   48 8d 45 b0            lea    -0x50(%rbp),%rax
  4010c5:   48 89 c7               mov    %rax,%rdi
  4010c8:   e8 33 fc ff ff         callq  400d00 <_ZNSsD1Ev@plt>
  4010cd:   eb 03                   jmp    4010d2 <main+0x20e>
  4010cf:   48 89 c3               mov    %rax,%rbx
  4010d2:   48 8d 45 ee            lea    -0x12(%rbp),%rax
  4010d6:   48 89 c7               mov    %rax,%rdi
  4010d9:   e8 62 fc ff ff         callq  400d40 <_ZNSaIcED1Ev@plt>
  4010de:   48 89 d8               mov    %rbx,%rax
  4010e1:   48 89 c7               mov    %rax,%rdi
  4010e4:   e8 b7 fc ff ff           callq  400da0 <_Unwind_Resume@plt>
  4010e9:   49 89 c4               mov    %rax,%r12
  4010ec:   48 89 df               mov    %rbx,%rdi
  4010ef:   e8 8c fb ff ff          callq  400c80 <_ZdlPv@plt>
  4010f4:   4c 89 e3                mov    %r12,%rbx
  4010f7:   eb 03                   jmp    4010fc <main+0x238>
  4010f9:   48 89 c3               mov    %rax,%rbx
  4010fc:   48 8d 45 c0            lea    -0x40(%rbp),%rax
  401100:  48 89 c7               mov    %rax,%rdi
  401103:  e8 f8 fb ff ff           callq  400d00 <_ZNSsD1Ev@plt>
  401108:  eb 03                   jmp    40110d <main+0x249>
  40110a:   48 89 c3               mov    %rax,%rbx
  40110d:   48 8d 45 ef            lea    -0x11(%rbp),%rax
  401111:  48 89 c7               mov    %rax,%rdi
  401114:  e8 27 fc ff ff         callq  400d40 <_ZNSaIcED1Ev@plt>
  401119:  48 89 d8               mov    %rbx,%rax
  40111c:   48 89 c7               mov    %rax,%rdi
  40111f:   e8 7c fc ff ff          callq  400da0 <_Unwind_Resume@plt>

c++のクラスのインスタン生成時の細かい挙動はわからないんですが(´・ω・`)、newしているのは↓の部分です。

  400f59:    bf 18 00 00 00         mov    $0x18,%edi
  400f5e:   e8 2d fe ff ff          callq  400d90 <_Znwm@plt>

stackoverflowによると、_Znwmがnewで、

operator new(unsigned long)

_Znamがnew[]のようです。

operator new[](unsigned long)

今回のコード以下のような処理なので、前者の方を使ってます。サイズとして0x18を渡しています。

  400f59:    bf 18 00 00 00         mov    $0x18,%edi
  400f5e:   e8 2d fe ff ff          callq  400d90 <_Znwm@plt>

newの呼び出しはこんな感じで、次にいって、virtual関数の呼び出し部分を見てみます。

m->introduce()をやっているのがこの部分です。

  400fcd:    48 8b 45 c8            mov    -0x38(%rbp),%rax
  400fd1:   48 8b 00              mov    (%rax),%rax
  400fd4:   48 83 c0 08           add    $0x8,%rax
  400fd8:   48 8b 10              mov    (%rax),%rdx
  400fdb:   48 8b 45 c8            mov    -0x38(%rbp),%rax
  400fdf:   48 89 c7               mov    %rax,%rdi
  400fe2:   ff d2                 callq  *%rdx

アドレス0x400fcdのところは変数mのアドレスををraxレジスタに入れます。 そして、0x400fd1でraxが指す先のアドレスがraxの値が入ります。0x400fd1を実行するとraxはgive_shell()のアドレスになります。そして、0x400fd4ではraxに8を足すのでintroduce()のアドレスを指すようになります。呼び出す関数の設定の最後にraxの指す先のアドレスをrdxにコピーします。 つぎに、再度mのアドレスをraxにコピーします。そして、それをそのままrdiに入れます。最後にrdxが指すアドレスにある関数を呼び出します。これでm->introduce()の呼び出しになります。

ちなみに、問題のほうは無事にクリアしてます(∩´∀`)∩ワーイ

f:id:masami256:20161225214740p:plain

レガシーコード改善ガイド

レガシーコード改善ガイド