gdb for kernel

Table of Contents

/usr/bin/fortune

FreeBSDにログインする度に、Tipsが表示される。いいかげんにウザイので、 止めようとしたんだ。 .profile

# Display a random cookie on each login.
if [ -x /usr/bin/fortune ] ; then /usr/bin/fortune freebsd-tips ; fi

正体は、これである。中身は、/usr/share/games/fortune/freebsd-tips で、 145個が収録されてた。一気読みしてから停止するか。いや、(中華式な)おみくじだから、楽しめ。OpenBSDで共有できるTipsかな?なんて想像するにはい いかも。

逆アセを眺めて

前回の最後で、逆アセンブルした。なんか悪い物を見たようで、眩暈がする。 大体、糞石は、レジスターに意味ありげな名前が付けられているんで、困惑し てしまうんだな。その点、昔やってた、MC68000みたいな、ミニコンの流れを 汲むやつは、すっきりしたレジスタ名で良い。

とある本を読んだんだけど、知らない事は、知るチャンス。嫌ッていないで、 触れてみろ。世の中はステキな情報で満ちている。たとえば、 OpenBSD kernel hack memo のregisterの部に要領良くまとめられているぞ。

x86 (IA-32) Assembly Language

元ソース

#define _FSHIFT 11              /* bits to right of fixed binary point */
#define FSCALE  (1<<_FSHIFT)
static const fixpt_t cexp[3] = {
        0.9200444146293232 * FSCALE,    /* exp(-1/12) */
        0.9834714538216174 * FSCALE,    /* exp(-1/60) */
        0.9944598480048967 * FSCALE,    /* exp(-1/180) */
};

update_loadavg(void *unused) {
        static struct timeout to = TIMEOUT_INITIALIZER(update_loadavg, NULL);
        CPU_INFO_ITERATOR cii;
        struct cpu_info *ci;
        u_int i, nrun = 0;

        CPU_INFO_FOREACH(cii, ci) {
                if (!cpuset_isset(&sched_idle_cpus, ci))
                        nrun++;
                nrun += ci->ci_schedstate.spc_nrun;
        }
        for (i = 0; i < 3; i++) {
                averunnable.ldavg[i] = (cexp[i] * averunnable.ldavg[i] +
                    nrun * FSCALE * (FSCALE - cexp[i])) >> FSHIFT;
        }
        timeout_add_sec(&to, 5);
}

逆アセ

update_loadavg:         pushl   %ebp                ; save base reg
update_loadavg+0x1:     movl    %esp,%ebp           ; sp -> base reg
update_loadavg+0x3:     pushl   %edi                ; save edi
update_loadavg+0x4:     pushl   %esi                ; save esi
update_loadavg+0x5:     movl    cpu_info_list,%edi
update_loadavg+0xb:     xorl    %esi,%esi           ; 0 -> esi
update_loadavg+0xd:     testl   %edi,%edi           ; edi == 0 ?
update_loadavg+0xf:     jz      update_loadavg+0x47 ; yes
update_loadavg+0x11:    jmp     update_loadavg+0x20 ; no
update_loadavg+0x14:    int     $3
 :
update_loadavg+0x20:    pushl   %edi
update_loadavg+0x21:    pushl   $0xd0fd70d0
update_loadavg+0x26:    call    cpuset_isset
update_loadavg+0x2b:    addl    $0x8,%esp                  ; 
update_loadavg+0x2e:    addl    0x170(%edi),%esi
update_loadavg+0x34:    cmpl    $0x1,%eax                  ; 1 == eax ?
update_loadavg+0x37:    adcl    $0,%esi
update_loadavg+0x3a:    movl    0x190(%edi),%edi
update_loadavg+0x40:    testl   %edi,%edi                   ; edi == 0 ?
update_loadavg+0x42:    jnz     update_loadavg+0x20
update_loadavg+0x44:    shll    $0xb,%esi                   ; 11 << esi
update_loadavg+0x47:    imull   $0x75c,averunnable,%eax
update_loadavg+0x51:    imull   $0xa4,%esi,%ecx
update_loadavg+0x57:    addl    %eax,%ecx
update_loadavg+0x59:    shrl    $0xb,%ecx                   ; 11 >> ecx
update_loadavg+0x5c:    movl    %ecx,averunnable            ; save 1min's
update_loadavg+0x62:    imull   $0x7de,averunnable+0x4,%eax
update_loadavg+0x6c:    movl    %esi,%ecx
update_loadavg+0x6e:    shll    $0x5,%ecx                    ; 5 << ecx
update_loadavg+0x71:    leal    0(%ecx,%esi,2),%ecx
update_loadavg+0x74:    addl    %eax,%ecx
update_loadavg+0x76:    shrl    $0xb,%ecx                    ; 11 >> ecx
update_loadavg+0x79:    movl    %ecx,averunnable+0x4         ; save 5min's
update_loadavg+0x7f:    imull   $0x7f4,averunnable+0x8,%eax  
update_loadavg+0x89:    leal    0(%esi,%esi,2),%ecx
update_loadavg+0x8c:    leal    0(%eax,%ecx,4),%eax
update_loadavg+0x8f:    shrl    $0xb,%eax                    : 11 >> eax
update_loadavg+0x92:    movl    %eax,averunnable+0x8         ; save 15min's
update_loadavg+0x97:    pushl   $0x5                         ; below next update
update_loadavg+0x99:    pushl   $0xd0ea178c
update_loadavg+0x9e:    call    timeout_add_sec
update_loadavg+0xa3:    addl    $0x8,%esp
update_loadavg+0xa6:    popl    %esi
update_loadavg+0xa7:    popl    %edi
update_loadavg+0xa8:    popl    %ebp
update_loadavg+0xa9:    ret

for文は、展開されて、ベタになってた。この方が速いと判断したんだな。そ の他、乗除算は左右へのシフトで代用したり。

それより驚いたのは、インテルの想定したレジスターの用途を遵守してるのが、 esp(SP)とebp(FP)ぐらいしか無い事。後は普通なレジスター扱いになってる事。 用途を想定して、それらしい名前をレジスターに付けているけど、それは空回 りってもんだ。

無駄な機能と言えば筆頭に挙げたいのは、セグメントとかの機構。良かれと思っ て搭載したのに、話をこんがらせている。まあ、そんなんが出現するのは、カー ネルが起動できる舞台を整えるまでだから、関係無いちゃ無いんだけどね。

watch loadavg

起動直後からのロード・アベレージを取得して、それをグラフにしてみる。 下記はその取得スクリプト。

#! /bin/sh
CNT=30
while test $CNT -gt 0
do
    uptime
    sleep 5
    CNT=`expr $CNT - 1`
done

そして、その実行結果。

6:53AM  up 2 mins, 1 user, load averages: 1.27, 0.49, 0.19
6:53AM  up 2 mins, 1 user, load averages: 1.17, 0.48, 0.19
 :
6:56AM  up 4 mins, 1 user, load averages: 0.22, 0.34, 0.17
6:56AM  up 4 mins, 1 user, load averages: 0.21, 0.34, 0.17

このデータから、1分のロードアベレージ・データのみを抽出。

ob$ cat RAW | cut -d',' -f3 | cut -d' ' -f4 >LOG

それをgnuplotでグラフ表示。

set grid
plot 'LOG' w l
set term pdf size 6in, 4in
set output 'loadavg.pdf'
replot

いつも使い方を忘れるので、その度にググる。 きれいなグラフを作ろう! gnuplot入門 とか、 gnuplotで作成したグラフの部分だけをpdfにする

loadavg.pdf 今回のデータを掲載。X軸は測定開始からの経過秒。所々グラフ が上昇してるのは、スクリプトのsleep 5 と、カーネル側の5秒毎が干渉して いるのだろう(多分)。予想が本当かどうか検証してみる。

まず、1回のループにどれぐらいの時間がかかっているか測定。ループが2回の 場合と、1回の場合の時間を測定。その差を取れば、正味の1回の消費時間がわ かる。

qemu$ time ./wa.sh > /dev/null
    0m10.82s real     0m00.10s user     0m00.64s system
qemu$ time ./wa.sh > /dev/null
    0m05.47s real     0m00.00s user     0m00.42s system

10.82 - 5.47 = 5.35s となった。この時間と、(多分)正確であろう、 update_loadavg の時間間隔5sとの一致する間隔を求めれば良い。 そう、5.35と5の最小公倍数ね。小数点を含み数じゃまずいので、100倍して、 LCMを求め、後で100で割って、元の単位に戻す。LCMの計算は、calc/emacsに お任せあれ。

    500
    535
lcm 53500  ;; k l (‘calc-lcm’)
    100
  />535

この例だと、両者が同一タイミングになるのは、535秒後という事だ。但し、 これはあくまで数学上の事。5.35sと言うのには、当然ゆらぎが有る。5.34sだ として計算すると、1335sと大幅に、かけ離れた数値になってしまった。

make

qemu用にgdbイネーブルなカーネルを作成してみる。

ob# time COPTS="-O0" make

/usr/src/sys/crypto/curve25519.c:782:5: error: stack frame size (2964) exceeds limit (2047) in function 'curve25519' [-Werror,-Wframe-larger-than]
int curve25519(uint8_t out[CURVE25519_KEY_SIZE],
    ^
1 error generated.
 *** Error 1 in /sys/arch/i386/compile/SEE (Makefile:951 'curve25519.o')
    2m47.26s real     2m06.02s user     0m20.58s system

なんてこったい。些細な事で足を引っぱられる。

./arch/i386/conf/Makefile.i386:         -Wframe-larger-than=2047

元ネタはこのファイルにあったので、倍に設定。って、もう遅いんだけどね。 下記のように、直接編集。忘れてしまう、-O0も忘れずに編集ね。

ob# cd obj
ob# vi Makefile
ob# time make
 :
text    data    bss     dec     hex
12806195        213448  1130496 14150139        d7e9fb
mv bsd bsd.gdb
ctfstrip -S -o bsd bsd.gdb
    6m53.29s real     5m16.25s user     1m02.29s system
ob# ls -l bsd*
-rwxrwx---  1 root  wobj  14627100 Feb 16 08:15 bsd
-rw-r--r--  1 root  wobj      4636 Feb 16 08:10 bsd-comp.d
-rw-r--r--  1 root  wobj     25388 Feb 16 08:10 bsd-comp.o
-rwxrwx---  1 root  wobj  36827296 Feb 16 08:15 bsd.gdb

drmとnfsは使わないので削除したんだけど、その効果は微々たるものだな。 もっと大胆にUSB一式削除とか、RAIDはいらないよとかすればダイエットにな るだろうけど、まあ、程々に。

use new kernel

wobjなグループに所属していないと現場の、 usr/obj/sys/arch/i386/compile/SEE に、到達できない。 現場には、

ob$ cat .gdbinit
target remote :1234

を用意。これを有効にする為、~/.gdbinitにも設定が必要。

ob$ cat /home/sakae/.gdbinit
add-auto-load-safe-path /usr/obj/sys/arch/i386/compile/SEE/.gdbinit

ああ、qemuからOpenBSDを起動するスクリプトには、 -s を追加して、gdbと通 信できる用に設定しとく。母艦側で、qemuを動作させ、これにPuTTY側のgdbが 通信しながら、観察と言うか観光って言う使い方ができる。

high load

ob$ cat heavy
#!/bin/sh
CNT=90
while test $CNT -gt 0
do
    seq 100 | grep 7 > /dev/null
    ls -al > /dev/null
#    CNT=`expr $CNT - 1`
done

先程作ったスクリプトを暴走族にして、負荷をかけてみる。

qemu$ uptime
 6:37AM  up 14 mins, 1 user, load averages: 2.33, 1.06, 0.48
qemu$ ps u
USER       PID %CPU %MEM   VSZ   RSS TT  STAT   STARTED       TIME COMMAND
sakae    23073  9.6  0.8  1076   828 00  Sp      6:35AM    0:15.39 /bin/sh ./he
 :

watch

ob$ gdb -q bsd.gdb
Reading symbols from bsd.gdb...
pmap_map_ptes_86 (pmap=0xd14ee000) at /usr/src/sys/arch/i386/i386/pmap.c:525
525             if (pmap_is_curpmap(pmap)) {
(gdb) p averunnable
$1 = {ldavg = {5028, 3111, 1509}, fscale = 2048}
(gdb) p cexp
$3 = {1884, 2014, 2036}
(gdb) b update_loadavg
Breakpoint 1 at 0xd048a22a: file /usr/src/sys/kern/sched_bsd.c, line 125.
(gdb) c
Continuing.

Breakpoint 1, update_loadavg (unused=0x0) at /usr/src/sys/kern/sched_bsd.c:125
125             u_int i, nrun = 0;
(gdb) bt
#0  update_loadavg (unused=0x0) at /usr/src/sys/kern/sched_bsd.c:125
#1  0xd0893248 in timeout_run (to=0xd0e3fee8 <update_loadavg.to>)
    at /usr/src/sys/kern/kern_timeout.c:638
#2  0xd089386f in softclock_process_tick_timeout (
    to=0xd0e3fee8 <update_loadavg.to>, new=0)
    at /usr/src/sys/kern/kern_timeout.c:687
#3  0xd089198b in softclock (arg=0x0) at /usr/src/sys/kern/kern_timeout.c:718
#4  0xd076f9fa in softintr_dispatch (which=0)
    at /usr/src/sys/arch/i386/i386/softintr.c:98
#5  0xd071c6ee in Xsoftclock ()
#6  0xd0f7b3a0 in uvm ()
127             CPU_INFO_FOREACH(cii, ci) {
(gdb)
133             for (i = 0; i < 3; i++) {
(gdb) p nrun
$2 = 2
(gdb) info registers
eax            0x0                 0
ecx            0x1                 1
edx            0x1                 1
ebx            0x0                 0
esp            0xf1b34cb0          0xf1b34cb0
ebp            0xf1b34ccc          0xf1b34ccc
esi            0xd071c5fe          -797850114
edi            0x5                 5
eip            0xd048a28f          0xd048a28f <update_loadavg+111>
eflags         0x246               [ IOPL=0 IF ZF PF ]
 :
(gdb) disassemble
    :
   0xd048a281 <+97>:    mov    0x190(%eax),%eax
   0xd048a287 <+103>:   mov    %eax,-0xc(%ebp)
   0xd048a28a <+106>:   jmp    0xd048a240 <update_loadavg+32>
=> 0xd048a28f <+111>:   movl   $0x0,-0x10(%ebp)
   0xd048a296 <+118>:   cmpl   $0x3,-0x10(%ebp)
   0xd048a29a <+122>:   jae    0xd048a2ee <update_loadavg+206>
   0xd048a2a0 <+128>:   mov    -0x10(%ebp),%eax
   0xd048a2a3 <+131>:   mov    -0x2f2a730c(,%eax,4),%ecx
   0xd048a2aa <+138>:   mov    -0x10(%ebp),%eax
   0xd048a2ad <+141>:   imul   -0x2f13a368(,%eax,4),%ecx
   0xd048a2b5 <+149>:   mov    -0x14(%ebp),%eax
   0xd048a2b8 <+152>:   shl    $0xb,%eax
   0xd048a2bb <+155>:   mov    -0x10(%ebp),%esi
   0xd048a2be <+158>:   mov    $0x800,%edx
   0xd048a2c3 <+163>:   sub    -0x2f2a730c(,%esi,4),%edx
   0xd048a2ca <+170>:   xchg   %edx,%eax
   0xd048a2cc <+172>:   imul   %eax,%edx
   0xd048a2cf <+175>:   xchg   %edx,%eax
   0xd048a2d1 <+177>:   add    %eax,%ecx
   0xd048a2d3 <+179>:   shr    $0xb,%ecx
   0xd048a2d6 <+182>:   mov    -0x10(%ebp),%eax
   0xd048a2d9 <+185>:   mov    %ecx,-0x2f13a368(,%eax,4)
   0xd048a2e0 <+192>:   mov    -0x10(%ebp),%eax
   0xd048a2e3 <+195>:   add    $0x1,%eax
   0xd048a2e6 <+198>:   mov    %eax,-0x10(%ebp)
   0xd048a2e9 <+201>:   jmp    0xd048a296 <update_loadavg+118>
   0xd048a2ee <+206>:   mov    %esp,%eax
   0xd048a2f0 <+208>:   movl   $0x5,0x4(%eax)
   0xd048a2f7 <+215>:   movl   $0xd0e3fee8,(%eax)
   0xd048a2fd <+221>:   call   0xd0891e10 <timeout_add_sec>
    :

コンパイルした時、-O0 を指定しているので効率化は実施されていない。ゆえ に、長いコードになっている(逆に、分かりづらい面も有る)。

再びloadavgの検証

先の暴走族を100ループ(約1分)回して負荷をかけ、29,30,31秒間隔で、21ポイ ント分の locaavgを計測してみた。

gnuplot> set grid
gnuplot> plot 's29' w l
gnuplot> replot 's30' w l
gnuplot> replot 's31' w l
gnuplot> set term pdf size 6in, 4in
gnuplot> set output 'loadavg3.pdf'
gnuplot> replot

一枚のグラフに重ね描きするには、replotなんだね。

loadavg3.pdf

etc