コアダンプの数だけ強くなれるよ

見習いエンジニアの備忘log

gdbで絶対秒を日付で表示する

gdbデバッグ時に、対象のプログラムが内部で持っている絶対秒(Unix時間)を
日付に変換して表示する方法。

バグ等でcoreファイルから原因を調査する際に欲しくなったので作ってみた。
具体的には~/.gdbinitにユーザ定義のコマンドを作って実現する。

#普通にgdbでプログラムを動かしている場合は、glibcの関数等を呼び出して変換できるはず。

coreファイル出力用のソースコード

解析用のソースコードとcoreファイルが必要なので、
グローバル変数に時刻と項番を書き込んだらabort()で自爆する単純なプログラムを作る。

printime.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

#define ENTRY_MAX 10

typedef struct {
    struct timeval tv;
    int entno;
} entry_t;

static entry_t ent[ENTRY_MAX];

int main(int argc, char* argv[])
{
    int i = 0;
    for(i = 0; i < ENTRY_MAX; i++) {
        gettimeofday(&ent[i].tv, NULL);
        ent[i].entno = i;
    }

    abort();

    return 0;
}

~/.gdbinitの作成

gdbinitにユーザコマンドを定義する際のフォーマットは次の通り。

define <コマンド名>
    #実行したい処理
    <処理1>
    <処理2>
    ...
end

↓みたいな秒とマイクロ秒の引数を渡すと日付で表示してくれるコマンドを作る。

(gdb) ShowClock <sec> <usec>
2017/04/14 00:45:12.246025(JST)




実際に作成したgdbinitは下記。
Unix時間から表示する各要素を求めるのがとっても面倒だった。

~/.gdbinit

#
# Convert Unix-Time (tv_sec, tv_usec) to date(yy/mm/dd hh:mm:ss)
#
define ConvertUnixTime2Date
    set $__acdays1  = 31
    set $__acdays2  = 59
    set $__acdays3  = 90
    set $__acdays4  = 120
    set $__acdays5  = 151
    set $__acdays6  = 181
    set $__acdays7  = 212
    set $__acdays8  = 243
    set $__acdays9  = 273
    set $__acdays10 = 304
    set $__acdays11 = 334
    set $__acdays12 = 365

    set $__leapdays = ((($__year-1)/4) - (($__year-1)/100) + (($__year-1)/400)) - 477
    set $__year = 1970 + (($__daycount - $__leapdays)/365)
    set $__restdays = $__daycount - ((($__year - 1970) * 365) + $__leapdays)

    if (($__year % 400 == 0) || (($__year % 4 == 0) && ($__year % 100 !=0 )))
        set $__acdays2++
        set $__acdays3++
        set $__acdays4++
        set $__acdays5++
        set $__acdays6++
        set $__acdays7++
        set $__acdays8++
        set $__acdays9++
        set $__acdays10++
        set $__acdays11++
        set $__acdays12++
    end

    if ($__restdays <= $__acdays1)
        set $__mon = 1
        set $__day = $__restdays
    end

    if ($__acdays1 < $__restdays && $__restdays <= $__acdays2)
        set $__mon = 2
        set $__day = $__restdays - $__acdays1
    end

    if ($__acdays2 < $__restdays && $__restdays <= $__acdays3)
        set $__mon = 3
        set $__day = $__restdays - $__acdays2
    end

    if ($__acdays3 < $__restdays && $__restdays <= $__acdays4)
        set $__mon = 4
        set $__day = $__restdays - $__acdays3
    end

    if ($__acdays4 < $__restdays && $__restdays <= $__acdays5)
        set $__mon = 5
        set $__day = $__restdays - $__acdays4
    end

    if ($__acdays5 < $__restdays && $__restdays <= $__acdays6)
        set $__mon = 6
        set $__day = $__restdays - $__acdays5
    end

    if ($__acdays6 < $__restdays && $__restdays <= $__acdays7)
        set $__mon = 7
        set $__day = $__restdays - $__acdays6
    end

    if ($__acdays7 < $__restdays && $__restdays <= $__acdays8)
        set $__mon = 8
        set $__day = $__restdays - $__acdays7
    end

    if ($__acdays8 < $__restdays && $__restdays <= $__acdays9)
        set $__mon = 9
        set $__day = $__restdays - $__acdays8
    end

    if ($__acdays9 < $__restdays && $__restdays <= $__acdays10)
        set $__mon = 10
        set $__day = $__restdays - $__acdays9
    end

    if ($__acdays10 < $__restdays && $__restdays <= $__acdays11)
        set $__mon = 11
        set $__day = $__restdays - $__acdays10
    end

    if ($__acdays11 < $__restdays && $__restdays <= $__acdays12)
        set $__mon = 12
        set $__day = $__restdays - $__acdays11
    end

end

#
# Show clock form Unix-Time
# This command requires 2 arguments (tv_sec, tv_usec)
#
define ShowClock

    set $__tv_sec = $arg0
    set $__tv_usec = $arg1

    # Set Time-Zone(JST)
    set $__timezone = 9
    set $__tv_sec = $__tv_sec + ($__timezone * 60 * 60)

    set $__mon        = 0
    set $__day      = 0
    set $__daycount = ($__tv_sec / 86400) + 1
    set $__year     = (1970 + ($__daycount / 365))
    set $__hour     = (($__tv_sec % 86400) / 3600)
    set $__min        = ((($__tv_sec % 86400) / 60) % 60)
    set $__sec        = (($__tv_sec % 86400) % 60)

    ConvertUnixTime2Date

    if ($__timezone == 0)
        printf "%04d/%02d/%02d %02d:%02d:%02d.%06d(UTC)  ", \
               $__year, $__mon, $__day, $__hour, $__min, $__sec, $__tv_usec
    end

    if ($__timezone == 9)
        printf "%04d/%02d/%02d %02d:%02d:%02d.%06d(JST)  ", \
               $__year, $__mon, $__day, $__hour, $__min, $__sec, $__tv_usec
    end
end


define ShowEntryDump

    set $__index = 0

    while ($__index < 10)
        set $__ent = &ent[$__index]
        ShowClock $__ent->tv.tv_sec $__ent->tv.tv_usec
        printf "entno=%d\n", $__ent->entno
        set $__index++
    end
end

実行結果

$ ulimit -c unlimited
$ ./printime
$ gdb printime corefile
$ (gdb) showentrydump



f:id:segmentation-fault:20170414005018p:plain




すごく処理が重いけどいい感じ。