Linuxでmysqldがcoreをお吐きになりませぬ

以下、Linux kernel 2.6 でのお話です。

  • vanilla な Linux kernel 2.6.29.1
  • MySQL 5.1.35とかそのへん


ぼくが改めて言うまでもなく、プログラム(mysqld)が異常終了したときなどに吐かれるcoreファイルは、その原因を探る上で非常に有益です。

が、なぜか、

  • setrlimitのRLIMIT_COREはunlimitedになっている
  • my.cnfの[mysqld]セクションに「core-file」を指定している

にも関わらず、自分の環境のmysqldはcoreを吐いてくれませんでした。

結論から言うと、

# MYSQL_DIR=/usr/local/app/mysql

# ( sleep 10 && pkill -ABRT mysqld ) &
# $MYSQL_DIR/bin/mysqld \
    --basedir=$MYSQL_DIR \
    --datadir=/db/mysql \
    --pid-file=/db/var/mysql.pid \
    --user mysql \
    ;
Aborted
# ls ~mysql/core*
ls: cannot access /db/mysql/core*: No such file or directory

# ( sleep 10 && pkill -ABRT mysqld ) &
# sudo -u mysql $MYSQL_DIR/bin/mysqld \
    --basedir=$MYSQL_DIR \
    --datadir=/db/mysql \
    --pid-file=/db/var/mysql.pid \
    ;
Aborted (core dumped)
# ls ~mysql/core*
/db/mysql/core.1254295158_mysqld_3367

の様に、実効ユーザの指定にmysqldの--userオプションを使った場合はcoreができませんが、sudo (やdaemontoolsに含まれるsetuidgidでもOK) を使った場合は期待通りcoreができます。


さて、--userの場合にcoreができない原因はなんでしょうか。

MySQLのドキュメントに依れば、

On some systems, such as Solaris, you do not get a core file if you are also using the --user option.

MySQL :: MySQL 8.0 Reference Manual :: 5.1.7 Server Command Options

だそうなのですが、「Linux」は明言されていないのでmysqlのソースを覗いてみます。

ながーいmain関数の中にこのようなコードがあり、check_user()の後でset_user()を呼んでいます。

  if ((user_info= check_user(mysqld_user)))
  {
#if defined(HAVE_MLOCKALL) && defined(MCL_CURRENT)
    if (locked_in_memory) // getuid() == 0 here
      set_effective_user(user_info);
    else
#endif
      set_user(mysqld_user, user_info);
  }

続いて、check_user()とset_user()を引用します。

static struct passwd *check_user(const char *user)
{
(snip)
    if (*pos)                                   // Not numeric id
      goto err;
    if (!(tmp_user_info= getpwuid(atoi(user))))
      goto err;
  }
  return tmp_user_info;
  /* purecov: end */

err:
  sql_print_error("Fatal error: Can't change to run as user '%s' ;  Please check that the user exists!\n",user);
  unireg_abort(1);

#ifdef PR_SET_DUMPABLE
  if (test_flags & TEST_CORE_ON_SIGNAL)
  {
    /* inform kernel that process is dumpable */
    (void) prctl(PR_SET_DUMPABLE, 1);
  }
#endif

#endif
  return NULL;
}
static void set_user(const char *user, struct passwd *user_info_arg)
{
(snip)
  if (setgid(user_info_arg->pw_gid) == -1)
(snip)
  if (setuid(user_info_arg->pw_uid) == -1)
(snip)
}


Linux の場合、setuidやsetgidなどで実効uidやgidが操作された場合、prctl(2)でPR_SET_DUMPABLEしないとcoreは吐かれません。

ここで前掲のコードを見ると、prctlを呼んでいるcheck_user()を実行した後、setgid,setuidを呼んでいるset_user()を実行する流れになっています。

prctlは、setuidしたで呼ばないと意味がないので、これではcoreは吐かれませんね。

もうひとつ興味深いのは、check_userでprctlが呼ばれるのは、何かしらの処理が失敗してerrラベルへ飛ばされたときのみ、という点です。つまり、正常処理ではそもそもprctlが呼ばれないように読めます。うひ。


この症状がバグレポートされてないかと探してみるとありました。

Bug#21723の報告者のパッチではきちんとsetuidの後にprctlを呼ぶようになっているのですが、なぜか(5.1.20で)なされた修正では、前述のように変なところでprctlが呼ばれるようになってしまっていました。。。


というわけで、いざというときにcoreを吐かせるために、mysqldを起動するときは--userオプションではなくて、sudoやsetuidgidコマンドで実効ユーザを指定するといいんじゃないかと思います。


以上、さも自分が調べたかのように書いてますが、(7月頃に)実際に調べたのは id:kazuhooku さんです>< ありがとうございました!


あと、

echo 2 > /proc/sys/fs/suid_dumpable

したらsetuid後prctlしないでもcoreが生まれるかなーと思ったんですが生まれませんでした。なんか勘違いしてるのかしら。。。

http://linuxjm.osdn.jp/html/LDP_man-pages/man5/proc.5.html

↑生まれました。

Linux 3.6 以降では、/proc/sys/fs/suid_dumpable が 2 ("suidsafe") に設定されている場合、テンプレートは、絶対パス名 (先頭に '/' 文字があるパス名) かパイプ (以下で説明) のどちらかでなければならない。

http://linuxjm.osdn.jp/html/LDP_man-pages/man5/core.5.html

CentOSのkernel 2.6.32-431.el6ですが、この制限が効いてるらしく、

echo '/var/tmp/core.%t_%e_%p' > /proc/sys/kernel/core_pattern

とかしたらちゃんと生まれました!

あと、suid_dumpableの変更はその後で起動したプロセスにだけ適用されるので、変更後にmysqldの再起動が必要な点に注意す。



追記

松信さん のおかげで、reopenされました! @matsunobu++