「C」カテゴリーアーカイブ

Ubuntuでコアダンプが出力できないことがある

Ubuntu(14.04 LTS)で、コアダンプが出力できない場合があるという現象が発生した。
できる場合もある。プログラムによる。

以下のように、ulimitは問題ない。

$ ulimit -c unlimited
$ ulimit -a
core file size          (blocks, -c) unlimited
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 15739
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 15739
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

コアダンプの出力先を確認。

$ sudo sysctl -a | grep core_pattern
kernel.core_pattern = |/usr/share/apport/apport %p %s %c %P

Ubuntu(14.04 LTS)では、デフォルトでcore_patternがapportを使用するように設定されていた。

コアダンプが出力されるケースでは、カレントディレクトリにcoreというファイルが出力される。
コアダンプが出力されないケースでは、apportでエラーが発生していた。

/var/log/apport.log

ERROR: apport (pid 3480) Tue May 12 18:48:31 2015: called for pid 3479, signal 6, core limit 18446744073709551615
ERROR: apport (pid 3480) Tue May 12 18:48:31 2015: ignoring implausibly big core limit, treating as unlimited
ERROR: ERROR: apport (pid 3480) Tue May 12 18:48:31 2015: Unhandled exception:
Traceback (most recent call last):
  File "/usr/share/apport/apport", line 357, in <module>
    (info['ExecutablePath'], info['ProcCmdline']))
  File "/usr/share/apport/apport", line 99, in error_log
    apport.error('apport (pid %s) %s: %s', os.getpid(), time.asctime(), msg)
  File "/usr/lib/python3/dist-packages/apport/__init__.py", line 44, in error
    sys.stderr.write(msg % args)
UnicodeEncodeError: 'ascii' codec can't encode character '\ufffd' in position 143: ordinal not in range(128)
ERROR: apport (pid 3480) Tue May 12 18:48:31 2015: pid: 3480, uid: 0, gid: 0, euid: 0, egid: 0
ERROR: apport (pid 3480) Tue May 12 18:48:31 2015: environment: environ({})

apportでエラーになっているから、コアダンプが出力されないのだ。
pythonのエラーのようだがよく分からないので、core_patternにapportを使用しないよう設定することにした。

# echo 'core.%e.%p' > /proc/sys/kernel/core_pattern
$ ulimit -c unlimited
$ cat segfault.c
#include <stdio.h>

int main(void)
{
  char *s = "hello, world!";
  *s = 'H';

  return 0;
}
$ gcc -Wall -g -o segfault segfault.c
$ ./segfault 
Segmentation fault (コアダンプ)
$ ls
core.segfault.3423  segfault  segfault.c

echoで変更した設定は、システム再起動後には無効になってしまう。
変更を永続化するためには、/etc/sysctl.confに設定する。

情報源: E.4. sysctl コマンドの使用

/etc/sysctl.conf

kernel.core_pattern = core.%e.%p

しかし、なぜかシステムを再起動すると、apportを使用するように設定が戻ってしまっている。
システム起動時のapport起動で、設定を上書きしているらしい。
情報源: 12.04 - How to permanently edit the core_pattern file? - Ask Ubuntu
これを止めさせるには、apportを無効にして再起動する。

/etc/default/apport

# set this to 0 to disable apport, or to 1 to enable it
# you can temporarily override this with
# sudo service apport start force_start=1
#enabled=1
enabled=0

コマンドの終了コードが128を超える場合はシグナルで終了した場合

コマンドの終了コードが128を超える場合はシグナルで終了した場合。

$ man bash
The return value of a simple command is its exit status, or 128+n if the command is terminated by signal n.
$ some_command
$ echo $?
139

例えば上記のように終了コードが139なら、139=128+11なので、シグナル11で終了したということになる。
man signalとかで見ると、シグナル11はSIGSEGVで、セグメンテーション違反だということが分かる。

GDBでポインタを配列として表示する

配列はprintコマンド(p)で参照できるが、配列を引数で受け取った場合などポインタになっている場合は、@演算子を用いるとうまく表示できる。
@演算子は、左項をアドレスとみなし、左項の型のデータを右項の数だけ配列のように表示する。

(gdb) list
34
35	void func(int v[], int len)
36	{
(gdb) p v[0] @ len
$1 = {4470, 5197, 2482, 1016, 4154, 9986, 8313, 6873, 8517, 5857, 9019, 8002, 349, 9817, 365, 1018, 2269, 9759, 7092, 8674, 4902, 3890, 9746, 7668, 792, 3842, 4053, 6422, 630, 4880, 9996, 7164, 8392, 8469, 2959, 8380, 6533, 1795, 4296, 9964, 8259, 7474, 72, 2948, 3681, 501, 3994, 508, 9544, 941, 8054, 2186, 7418, 8684, 3224, 7322, 3822, 5022, 3085, 8341, 2296, 4073, 1034, 9839, 7067, 1197, 739, 7028, 2809, 6610, 8633, 483, 3017, 9634, 4758, 8101, 7604, 4018, 2174, 5014, 6600, 3754, 2854, 4798, 3921, 2124, 9889, 1147, 2409, 6105, 224, 6973, 5937, 2953, 8614, 9101, 3399, 8431, 2226, 3548}

Source: Arrays - Debugging with GDB

Cの宣言の読み方

Cの型宣言は読みにくい。
「例解UNIXプログラミング教室」(冨永和人、権藤 克彦 ピアソンエデュケーション)の説明が分かりやすい。

例解UNIXプログラミング教室 より引用。

  1. 最左の識別子(関数名や変数名など)に注目する。それ以外の識別子は引数の名前なので無視する。
  2. 下記の優先度に従い、その識別子から外側に向かいカッコをつける。左側の*よりも先に、右側の()や[]にカッコをつけるのがポイント。
  3. 日本語なら外側から、英語なら内側から読む。

優先度(上ほど強く結合)

記号 説明
() 宣言のグループ化
[], () 配列、関数引数
* ポインタ
その他 intなど

例: signalの型

void (*signal (int sig, void (*handler)(int)))(int);

  1. 最左の識別子はsignal。(sigとhandlerは引数)。
  2. signalからカッコをつける。

    1. *より()の方が強いので、signal (int sig, void (*handler)(int)) をカッコで囲む。
    2. 一番左のvoidより一番右の (int) の方が強いので、一番左の void 以外をカッコで囲む。
    3. 結果はこうなる。
      void ((*(signal (int sig, void (*handler)(int))))(int));
  3. 外側からカッコを外しながら読む。

    1. void ⚫; ⚫はvoid
    2. void (⚫)(int); ⚫はvoidを返す関数(引数はint
    3. void (* ⚫)(int); ⚫はvoidを返す関数(引数はint)へのポインタ
    4. void (*signal(⚫))(int); signalはvoidを返す関数(引数はint)へのポインタを返す関数(引数は⚫)
    5. void (*signal(int sig, void (*handler)(int)))(int); signalはvoidを返す関数(引数はint)へのポインタを返す関数(引数はintとvoidを返す関数(引数はint)

システムデータ型などtypedefされた型の実際の型を調べる

gdbのptypeを使うのが簡単。
例えば、以下のfoo.c。

#include <sys/types.h>
struct st { int i; char c; };
typedef int Array[10];
typedef struct st St;
int main(void)
{ 
  size_t s1;
  ssize_t s2;
  pid_t pid;
  Array arr;
  St st1;
  return 0;
}
$ gcc -Wall -g -o foo foo.c
$ gdb foo
(gdb) b main
(gdb) run
(gdb) ptype s1
type = long unsigned int
(gdb) ptype size_t
type = long unsigned int
(gdb) ptype s2
type = long int
(gdb) ptype ssize_t 
type = long int
(gdb) ptype pid
type = int
(gdb) ptype pid_t
type = int
(gdb) ptype arr
type = int [10]
(gdb) ptype Array 
type = int [10]
(gdb) ptype st1
type = struct st {
    int i;
    char c;
}
(gdb) ptype St
type = struct st {
    int i;
    char c;
}

のように、
ptype 式や型名
で実際の型が表示される。
配列の場合はサイズが表示されるし、構造体の場合はメンバの型が表示されるので便利。
以下のように、gccの-Eオプションでプリプロセッサの出力調べても分かりそうだが、面倒くさいし、追い切れないかもしれない。

$ gcc -E foo.c | grep ssize_t
typedef long int __ssize_t;
typedef __ssize_t ssize_t;
(以下略)

このシステム(x86_64 GNU/Linux)では、ssize_tは
__ssize_t => long int

$ gcc -E foo.c | grep ssize_t
typedef long __darwin_ssize_t;
(中略)
typedef __darwin_ssize_t ssize_t;
(以下略)

このシステム(Mac OS X)では、ssize_tは
ssize_t => __darwin_ssize_t => long
であることが分かる。