pid cgroupのコードを読む

この記事はLinuxカーネルもくもく会 #24 - connpassで書いてます。今日は前に追加されたcgroupsのpid管理のコードを読んでみます。

ファイルはcgroup_pids.cです。Linux 4.7のコードを対象にします。

最初にデータ構造です。pidの場合はpids_cgroup構造体が使われます。この構造体はcgroup_pids.cで定義されています。

 43 struct pids_cgroup {
 44         struct cgroup_subsys_state      css;
 45 
 46         /*
 47          * Use 64-bit types so that we can safely represent "max" as
 48          * %PIDS_MAX = (%PID_MAX_LIMIT + 1).
 49          */
 50         atomic64_t                      counter;
 51         int64_t                         limit;
 52 };

構造も結構スッキリしていて、今のPID数と上限、それにcgroup_subsys_state構造体です。cgroup_subsys_state構造体から親階層のpids_cgroupを取得したりします。

pid cgroupsで定義されている操作はcgroup_subsys構造体で定義します。css_alloc/css_free, can_attach/cancel_attach, can_fork/cancel_forkはペアですね。

306 struct cgroup_subsys pids_cgrp_subsys = {
307         .css_alloc      = pids_css_alloc,
308         .css_free       = pids_css_free,
309         .can_attach     = pids_can_attach,
310         .cancel_attach  = pids_cancel_attach,
311         .can_fork       = pids_can_fork,
312         .cancel_fork    = pids_cancel_fork,
313         .free           = pids_free,
314         .legacy_cftypes = pids_files,
315         .dfl_cftypes    = pids_files,
316 };
317 

pid cgroupsのファイルとしてはこれらのファイルがあります。

291 static struct cftype pids_files[] = {
292         {
293                 .name = "max",
294                 .write = pids_max_write,
295                 .seq_show = pids_max_show,
296                 .flags = CFTYPE_NOT_ON_ROOT,
297         },
298         {
299                 .name = "current",
300                 .read_s64 = pids_current_read,
301                 .flags = CFTYPE_NOT_ON_ROOT,
302         },
303         { }     /* terminate */
304 };

maxのほうは最大値を設定するファイルで読み(pids_max_show)書き(pids_max_write)可能です。currentの方は現在の値を表示するだけなので読み込み(pids_current_read)しか設定されていません。

これらのファイルはflagsにCFTYPE_NOT_ON_ROOTが設定されているので、pid cgroupのトップディレクトリには表示されません。

[root@miko user.slice]# ls -la /sys/fs/cgroup/pids
total 0
dr-xr-xr-x  5 root root   0 Sep 16 02:36 ./
drwxr-xr-x 11 root root 260 Sep 16 02:35 ../
-rw-r--r--  1 root root   0 Sep 16 02:36 cgroup.clone_children
-rw-r--r--  1 root root   0 Sep 16 02:36 cgroup.procs
-r--r--r--  1 root root   0 Sep 16 02:36 cgroup.sane_behavior
drwxr-xr-x  2 root root   0 Sep 16 02:35 init.scope/
-rw-r--r--  1 root root   0 Sep 16 02:36 notify_on_release
-rw-r--r--  1 root root   0 Sep 16 02:36 release_agent
drwxr-xr-x 31 root root   0 Sep 16 02:35 system.slice/
-rw-r--r--  1 root root   0 Sep 16 02:36 tasks
drwxr-xr-x  3 root root   0 Sep 16 02:36 user.slice/

pid cgroupsの下の階層には存在します。pids.maxとpids.currentがそのファイルです。

[root@miko user.slice]# ls -la /sys/fs/cgroup/pids/user.slice
total 0
drwxr-xr-x 3 root root 0 Sep 16 02:36 ./
dr-xr-xr-x 5 root root 0 Sep 16 02:36 ../
-rw-r--r-- 1 root root 0 Sep 16 02:36 cgroup.clone_children
-rw-r--r-- 1 root root 0 Sep 16 02:36 cgroup.procs
-rw-r--r-- 1 root root 0 Sep 16 02:36 notify_on_release
-r--r--r-- 1 root root 0 Sep 16 02:36 pids.current
-rw-r--r-- 1 root root 0 Sep 16 02:36 pids.max
-rw-r--r-- 1 root root 0 Sep 16 02:36 tasks
drwxr-xr-x 4 root root 0 Sep 16 02:36 user-1000.slice/

ファイルの内容はこんな感じです。

[root@miko user.slice]# cat pids.max
max
[root@miko user.slice]# cat pids.current
9

では、コードを適当に読んでいきます。まずはpids_css_alloc()。新しくサブシステムが作られる時に呼ばれます。個々で作成するのはcgroup_subsys_state構造体です。実際にはpids_cgroup構造体を作って、その中のcss変数を返しています。この辺はカーネル定番のcontainer_ofマクロの出番ですね。

 64 static struct cgroup_subsys_state *
 65 pids_css_alloc(struct cgroup_subsys_state *parent)
 66 {
 67         struct pids_cgroup *pids;
 68 
 69         pids = kzalloc(sizeof(struct pids_cgroup), GFP_KERNEL);
 70         if (!pids)
 71                 return ERR_PTR(-ENOMEM);
 72 
 73         pids->limit = PIDS_MAX;
 74         atomic64_set(&pids->counter, 0);
 75         return &pids->css;
 76 }

PIDS_MAXは↓のように定義されています。

40 #define PIDS_MAX (PID_MAX_LIMIT + 1ULL)

PID_MAX_LIMITはinclude/linux/threads.hにて↓のように決まります。

 33 #define PID_MAX_LIMIT (CONFIG_BASE_SMALL ? PAGE_SIZE * 8 : \
 34         (sizeof(long) > 4 ? 4 * 1024 * 1024 : PID_MAX_DEFAULT))

limit以外は初期値0って感じになります。

pids_css_free()はpids_css_allocで作ったpids_cgroup構造体のメモリをkfree()で解放するだけです。

次はpids_can_attach()です。この関数はプロセスがあるcgroupから別のcgroupに移動する時に呼ばれます。処理内容としては移動元のpid cgroupからPIDの使用数(counter)を減らして、新しいほうのPIDの使用数を増やします。

165 static int pids_can_attach(struct cgroup_taskset *tset)
166 {
167         struct task_struct *task;
168         struct cgroup_subsys_state *dst_css;
169 
170         cgroup_taskset_for_each(task, dst_css, tset) {
171                 struct pids_cgroup *pids = css_pids(dst_css);
172                 struct cgroup_subsys_state *old_css;
173                 struct pids_cgroup *old_pids;
174 
175                 /*
176                  * No need to pin @old_css between here and cancel_attach()
177                  * because cgroup core protects it from being freed before
178                  * the migration completes or fails.
179                  */
180                 old_css = task_css(task, pids_cgrp_id);
181                 old_pids = css_pids(old_css);
182 
183                 pids_charge(pids, 1);
184                 pids_uncharge(old_pids, 1);
185         }
186 
187         return 0;
188 }

countを増やしたり減らしたりするのがpids_charge()とpids_uncharge()です。

pids_charge()はこのような処理で、階層を上位に移動しながら、pidのcounterを増やします。

122 static void pids_charge(struct pids_cgroup *pids, int num)
123 {
124         struct pids_cgroup *p;
125 
126         for (p = pids; parent_pids(p); p = parent_pids(p))
127                 atomic64_add(num, &p->counter);
128 }

pids_uncharge()のほうは単純にいうとpids_charge()と逆の処理をします。実際にcounterの値を更新するのはpids_cancel()ですが。

pids_cancel_attach()はアタッチをキャンセルする処理で、やることは移動先のcounterを減らして移動元のcounterの値を増やします。

pids_can_fork()はfork/clone/vfork時にcopy_process()の途中で呼ばれます。cgroup_can_fork()がサブシステムのcan_fork()を呼び出す形です。

1567         retval = cgroup_can_fork(p);
1568         if (retval)
1569                 goto bad_fork_free_pid;

そして、pids_can_fork()の処理ですが、これはpids_try_charge()を呼んでpid数の上限に達していないか確認し、問題なければ0を返します。上限に達している場合はEAGAINが返ります。なので、fork爆弾とかはここで上限値チェックに引っかかるんじゃないでしょうか。試してませんが。。。

pids_cancel_fork()はキャンセル処理なので増やしたcounterの値を減らします。

次に、pids.maxファイルへの書き込み時のpids_max_write()ですが、これは大方の予想通りなことしかしてません。 読み込みのときにmaxって文字列を出してましたが、これは条件があります。pids_max_show()で、pid cgroupに設定されているlimitがPIDS_MAX以上の場合はmaxという文字列が表示されます。

269 static int pids_max_show(struct seq_file *sf, void *v)
270 {
271         struct cgroup_subsys_state *css = seq_css(sf);
272         struct pids_cgroup *pids = css_pids(css);
273         int64_t limit = pids->limit;
274 
275         if (limit >= PIDS_MAX)
276                 seq_printf(sf, "%s\n", PIDS_MAX_STR);
277         else
278                 seq_printf(sf, "%lld\n", limit);
279 
280         return 0;
281 }

limitの初期値はPIDS_MAXなので、pids.maxを弄ってなければデフォルトはmaxになります。

pid cgroupはだいたいこれくらいですね。結構短くて読みやすいですね。

改訂3版 Linuxエンジニア養成読本 (Software Design plus)

改訂3版 Linuxエンジニア養成読本 (Software Design plus)

bccめも:bpf_probe_read()

bpf_probe_read()は3番目の引数を安全に読み出すためのものという認識で間違ってないとは思うけど。

linuxカーネルsamples/bpf/bpf_helpers.hを見ると引数名はunsafe_ptrとかなってるし。strpcyとかだとinvalid opcode的なエラーになったので、この辺を使っておくのが良いのだろう。たぶん。

 18 static int (*bpf_probe_read)(void *dst, int size, void *unsafe_ptr) =
 19         (void *) BPF_FUNC_probe_read;

memo.

#!/usr/bin/env python

from bcc import BPF
import ctypes as ct

prog = """
#include <uapi/linux/ptrace.h>
#include <linux/kernfs.h>

BPF_PERF_OUTPUT(events);

struct cgroup_rmdir_dname {
    char name[256];
};

void kprobe__cgroup_rmdir(struct pt_regs *ctx, struct kernfs_node *kn)
{
        struct cgroup_rmdir_dname crd = {};
        bpf_probe_read(&crd.name, sizeof(crd.name) - 1, (void *) kn->name);
        events.perf_submit(ctx, &crd, sizeof(crd));
}
"""

class CgroupRmdirData(ct.Structure):
    _fields_ = [
        ("name", ct.c_char * 256)
    ]

def print_event(cpu, data, size):
    event = ct.cast(data, ct.POINTER(CgroupRmdirData)).contents
    print("name: %s" % (event.name))

print("Ctrl-c to stop")
b = BPF(text=prog)
b["events"].open_perf_buffer(print_event)
while 1:
    b.kprobe_poll()

こんな感じ。

# mkdir test && rmdir test
# ./cgroup_rmdir.py
Ctrl-c to stop
name: b'test'

改訂3版 Linuxエンジニア養成読本 (Software Design plus)

改訂3版 Linuxエンジニア養成読本 (Software Design plus)

Linux 2.0.40の頃のkmalloc()

たまには古いカーネルでも読んでみましょう的なところで、Linux 2.0.40の頃はkmalloc()はどんな実装だったのかというところでも見てみましょう。最近のkmalloc()の実装は使用しているディストーションが採用しているスラブアローケータの__kmalloc()の実装を見てみましょう。今のkmalloc()はスラブアローケータを使用しています。SLUBだとこんな感じです。

で、Linux 2.0.40はkernel.orgのファイル作成日時で見ると08-Feb-2004 07:13となっているので、12年前にリリースされたんですね。このバージョンが2.0系の最終バージョンです。

この当時のkmallocはスラブアローケータは使用していないけど、スラブアローケータ的に要求されたサイズを一番近い2の累乗サイズに切り上げて使っています。サイズはblocksizeという変数で管理してます。

 97 static const unsigned int blocksize[] = {
 98         32,
 99         64,
100         128,
101         252,
102         508,
103         1020,
104         2040,
105         4096 - 16,
106         8192 - 16,
107         16384 - 16,
108         32768 - 16,
109         65536 - 16,
110         131072 - 16,
111         0
112 };

切り上げかたは「要求されたサイズ+管理用のヘッダーサイズ」に近い次のサイズです。管理用のヘッダーサイズと言うのは、割り当てたメモリ領域を管理するためのヘッダーです。データとしてはstruct block_header型で、x86_32だと8byteになります。

 46 struct block_header {
 47         unsigned long bh_flags;
 48         union {
 49                 unsigned long ubh_length;
 50                 struct block_header *fbh_next;
 51         } vp;
 52 };

例えば、要求サイズが20byteだったとして、8byteがヘッダーに必要なので一番近い32byteのメモリを確保して返すことになります。呼び出し元にはヘッダーの領域を返す必要な無いので、ヘッダーの終了アドレスの次の位置を返すことになります。図にすると↓のような感じですね。

f:id:masami256:20160819232743p:plain

struct block_headerのfbh_nextが次の空き領域を指してるわけですね。

ちなみにSLUBだとヘッダーで管理って方法はとっていません。雑に図を描くとこうなります。

f:id:masami256:20160820000040p:plain

SLUBのポイントは32byteのスラブオブジェクトはヘッダーとかなくて、そのまま32byteの領域です。未使用の領域は先頭に次の空きスラブオブジェクトを指すポインタとして使用します。使用するときはこの領域毎返します。使用中のオブジェクトをkfreeで解放する場合は、また先頭の領域を次の空きスラブオブジェクト指すようにします。次の空きスラブオブジェクトをどう知るかってのはstruct kmem_cache_cpu構造体を使いますが、今日はSLUBの話ではないので、興味のある人はmm/slub.cを読みましょう。

では、kmalloc()を読んできます。

まず最初のほう。

229 void *kmalloc(size_t size, int priority)
230 {
231         unsigned long flags;
232         unsigned long type;
233         int order, dma;
234         struct block_header *p;
235         struct page_descriptor *page, **pg;
236         struct size_descriptor *bucket = sizes;
237 
238         /* Get order */
239         order = 0;
240         {
241                 unsigned int realsize = size + sizeof(struct block_header);
242                 for (;;) {
243                         int ordersize = BLOCKSIZE(order);
244                         if (realsize <= ordersize)
245                                 break;
246                         order++;
247                         bucket++;
248                         if (ordersize)
249                                 continue;
250                         printk("kmalloc of too large a block (%d bytes).\n", (int) size);
251                         return NULL;
252                 }
253         }
254 

最初に必要なサイズを設定し、適切なサイズの領域があるか調べます。 次に、gfpのチェックですね。DMA領域からメモリ確保するかとか。

255         dma = 0;
256         type = MF_USED;
257         pg = &bucket->firstfree;
258         if (priority & GFP_DMA) {
259                 dma = 1;
260                 type = MF_DMA;
261                 pg = &bucket->dmafree;
262         }
263 
264         priority &= GFP_LEVEL_MASK;
265 

ここは割り込み中なのにGFP_ATOMICを付けずにkmalloc()を呼んだ場合の処理ですね。

266 /* Sanity check... */
267         if (intr_count && priority != GFP_ATOMIC) {
268                 static int count = 0;
269                 if (++count < 5) {
270                         printk("kmalloc called nonatomically from interrupt %p\n",
271                                __builtin_return_address(0));
272                         priority = GFP_ATOMIC;
273                 }
274         }

ここから割り込みを禁止して処理が進みます。page変数は最近のSLUBとかを読んでるとstruct pageかな?って思うんですがstruct page_descriptorです。 これはkmalloc用に確保したpageを管理するのに使ってます。そして、この構造体がNULLの場合は、空き領域が無い場合なのでno_bucket_pageラベルにジャンプします。 そうでない場合はpage->firstfreeが次に返すメモリとなります。MF_FREEというのはこの領域を使用していない場合に設定されるようなので、使えると思った領域が実は空いて無かった場合のチェックですね。この場合はnot_free_on_freelistラベルに飛びます。pはstruct block_headerです。

276         save_flags(flags);
277         cli();
278         page = *pg;
279         if (!page)
280                 goto no_bucket_page;
281 
282         p = page->firstfree;
283         if (p->bh_flags != MF_FREE)
284                 goto not_free_on_freelist;

無事に空き領域が見つかった場合はそのままfound_itラベルのところの処理になります。firstfree変数を更新や、空き領域数(nfree)を減らしたりなどなどです。 returnするときはsizeof(struct block_header)の分を飛ばして、ヘッダーより後のアドレスを返しています。

286 found_it:
287         page->firstfree = p->bh_next;
288         page->nfree--;
289         if (!page->nfree)
290                 *pg = page->next;
291         restore_flags(flags);
292         bucket->nmallocs++;
293         bucket->nbytesmalloced += size;
294         p->bh_flags = type;     /* As of now this block is officially in use */
295         p->bh_length = size;
296 #if CONFIG_SADISTIC_KMALLOC
297         memset(p+1, 0xf0, size);
298 #endif
299         return p + 1;           /* Pointer arithmetic: increments past header */

次はno_bucket_pageラベルのところです。ここはpageを確保する程度です。pageはstruct pageではなくてstruct page_descriptorです。get_kmalloc_pages()はバディアローケータからページを確保して返しますが、受け取る方はstruct page_descriptor *で受け取ってるだけです。ページが確保できなければno_free_pageラベルにジャンプします。

302 no_bucket_page:
303         /*
304          * If we didn't find a page already allocated for this
305          * bucket size, we need to get one..
306          *
307          * This can be done with ints on: it is private to this invocation
308          */
309         restore_flags(flags);
310 
311         {
312                 int i, sz;
313                 
314                 /* sz is the size of the blocks we're dealing with */
315                 sz = BLOCKSIZE(order);
316 
317                 page = get_kmalloc_pages(priority, bucket->gfporder, dma);
318                 if (!page)
319                         goto no_free_page;

ページを確保できた場合はfound_cached_pageラベルのところの処理に入ります。ここでは確保したページに対してkmallocで返す領域のヘッダーを設定していく感じですね。

320 found_cached_page:
321 
322                 bucket->npages++;
323 
324                 page->order = order;
325                 /* Loop for all but last block: */
326                 i = (page->nfree = bucket->nblocks) - 1;
327                 p = BH(page + 1);
328                 while (i > 0) {
329                         i--;
330                         p->bh_flags = MF_FREE;
331                         p->bh_next = BH(((long) p) + sz);
332                         p = p->bh_next;
333                 }
334                 /* Last block: */
335                 p->bh_flags = MF_FREE;
336                 p->bh_next = NULL;
337 
338                 p = BH(page+1);
339         }

ヘッダーの設定が終わったら、found_itラベルにジャンプします。

341         /*
342          * Now we're going to muck with the "global" freelist
343          * for this size: this should be uninterruptible
344          */
345         cli();
346         page->next = *pg;
347         *pg = page;
348         goto found_it;

ページを確保できなかった場合は、kmalloc_cacheというキャッシュにページがあるか調べて、あればそのページを使います。このキャッシュはkfree時に作ってるようです。条件としてはorder < 3で、DMA領域以外の場合みたいです。キャッシュも無ければ諦めます/(^o^)\

351 no_free_page:
352         /*
353          * No free pages, check the kmalloc cache of
354          * pages to see if maybe we have something available
355          */
356         if (!dma && order < MAX_CACHE_ORDER) {
357                 page = xchg(kmalloc_cache+order, page);
358                 if (page)
359                         goto found_cached_page;
360         }
361         {
362                 static unsigned long last = 0;
363                 if (priority != GFP_BUFFER && priority != GFP_IO &&
364                     (last + 10 * HZ < jiffies)) {
365                         last = jiffies;
366                         printk("Couldn't get a free page.....\n");
367                 }
368                 return NULL;
369         }
370 

最後はnot_free_on_freelistラベルです。空いていると思った領域が空いてないのはおかしいのでエラーメッセージを出してからNULLを返します。

371 not_free_on_freelist:
372         restore_flags(flags);
373         printk("Problem: block on freelist at %08lx isn't free.\n", (long) p);
374         return NULL;
375 }

というわけで、2.0.40のkmalloc()を見てみました。この記事だと新しいほうの解説はほとんどしてないですけども、昔のカーネルと今のカーネルを比較しながら読んでみるのも面白いと思います。

改訂3版 Linuxエンジニア養成読本 (Software Design plus)

改訂3版 Linuxエンジニア養成読本 (Software Design plus)

Linux 4.7の新機能「perf trace calls stack」めも

kernelnewbies.orgのLinux Changesを見てて良さげだなと思った「perf trace calls stack」のめもを。

基本的な使い方は説明が書かれているので、

perfでread、open、closeシステムコールを指定する場合。

masami@saga:~/tmp$ sudo perf trace -e read,open,close cat test.c
#include <stdio.h>

static void print_args(char **argv)
{
        while (*argv) {
                printf("%s\n", *argv++);
        }
}

int main(int argc, char **argv)
{
        print_args(argv);
        return 0;
}
     0.019 ( 0.019 ms): cat/8165 open(filename: 0xf9f9fc78, flags: CLOEXEC                             ) = 3
     0.038 ( 0.002 ms): cat/8165 close(fd: 3                                                           ) = 0
     0.065 ( 0.011 ms): cat/8165 open(filename: 0xfa1a7ed0, flags: CLOEXEC                             ) = 3
     0.070 ( 0.003 ms): cat/8165 read(fd: 3, buf: 0x7fff4703af68, count: 832                           ) = 832
     0.164 ( 0.002 ms): cat/8165 close(fd: 3                                                           ) = 0
     0.447 ( 0.015 ms): cat/8165 open(filename: 0xf9d4b910, flags: CLOEXEC                             ) = 3
     0.474 ( 0.002 ms): cat/8165 close(fd: 3                                                           ) = 0
     0.531 ( 0.012 ms): cat/8165 open(filename: 0x4703cd0b, mode: ISGID|0xfffe0000                     ) = 3
     0.563 ( 0.005 ms): cat/8165 read(fd: 3, buf: 0x7f29fa184000, count: 131072                        ) = 174
     0.581 ( 0.002 ms): cat/8165 read(fd: 3, buf: 0x7f29fa184000, count: 131072                        ) = 0
     0.594 ( 0.002 ms): cat/8165 close(fd: 3                                                           ) = 0
     0.608 ( 0.002 ms): cat/8165 close(fd: 1                                                           ) = 0
     0.612 ( 0.002 ms): cat/8165 close(fd: 2                                                           ) = 0

traceコマンドの場合。

masami@saga:~/tmp$ sudo trace --call dwarf cat ./test.c
#include <stdio.h>

static void print_args(char **argv)
{
        while (*argv) {
                printf("%s\n", *argv++);
        }
}

int main(int argc, char **argv)
{
        print_args(argv);
        return 0;
}
     0.066 ( 0.003 ms): cat/10174 brk(                                                                  ) = 0x13f0000
                                       brk+0x9 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_start+0x2c5 (/usr/lib/ld-2.23.so)
                                       _dl_start+0x288 (/usr/lib/ld-2.23.so)
                                       _dl_start_user+0x0 (/usr/lib/ld-2.23.so)
     0.106 ( 0.013 ms): cat/10174 access(filename: 0x82769f80, mode: R                                  ) = -1 ENOENT No such file or directory
                                       access+0x7 (/usr/lib/ld-2.23.so)
                                       dl_main+0x1650 (/usr/lib/ld-2.23.so)
     0.130 ( 0.014 ms): cat/10174 open(filename: 0x82767c78, flags: CLOEXEC                             ) = 3
                                       open64+0x7 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_read_whole_file+0x26 (/usr/lib/ld-2.23.so)
                                       _dl_load_cache_lookup+0x2d6 (/usr/lib/ld-2.23.so)
                                       _dl_map_object+0x550 (/usr/lib/ld-2.23.so)
                                       openaux+0x30 (/usr/lib/ld-2.23.so)
                                       _dl_catch_error+0x74 (/usr/lib/ld-2.23.so)
                                       _dl_map_object_deps+0x275 (/usr/lib/ld-2.23.so)
                                       dl_main+0x1699 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_start+0x40c (/usr/lib/ld-2.23.so)
                                       _dl_start+0x288 (/usr/lib/ld-2.23.so)
                                       _dl_start_user+0x0 (/usr/lib/ld-2.23.so)
     0.138 ( 0.003 ms): cat/10174 fstat(fd: 3, statbuf: 0x7ffc46e20da0                                  ) = 0
                                       _fxstat+0x14 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_read_whole_file+0x3b (/usr/lib/ld-2.23.so)
                                       _dl_load_cache_lookup+0x2d6 (/usr/lib/ld-2.23.so)
                                       _dl_map_object+0x550 (/usr/lib/ld-2.23.so)
                                       openaux+0x30 (/usr/lib/ld-2.23.so)
                                       _dl_catch_error+0x74 (/usr/lib/ld-2.23.so)
                                       _dl_map_object_deps+0x275 (/usr/lib/ld-2.23.so)
                                       dl_main+0x1699 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_start+0x40c (/usr/lib/ld-2.23.so)
                                       _dl_start+0x288 (/usr/lib/ld-2.23.so)
                                       _dl_start_user+0x0 (/usr/lib/ld-2.23.so)
     0.157 ( 0.015 ms): cat/10174 mmap(len: 158913, prot: READ, flags: PRIVATE, fd: 3                   ) = 0x7f6882946000
                                       mmap64+0x3a (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_read_whole_file+0x85 (/usr/lib/ld-2.23.so)
                                       _dl_load_cache_lookup+0x2d6 (/usr/lib/ld-2.23.so)
                                       _dl_map_object+0x550 (/usr/lib/ld-2.23.so)
                                       openaux+0x30 (/usr/lib/ld-2.23.so)
                                       _dl_catch_error+0x74 (/usr/lib/ld-2.23.so)
                                       _dl_map_object_deps+0x275 (/usr/lib/ld-2.23.so)
                                       dl_main+0x1699 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_start+0x40c (/usr/lib/ld-2.23.so)
                                       _dl_start+0x288 (/usr/lib/ld-2.23.so)
                                       _dl_start_user+0x0 (/usr/lib/ld-2.23.so)
     0.163 ( 0.002 ms): cat/10174 close(fd: 3                                                           ) = 0
                                       close+0x7 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_read_whole_file+0x5b (/usr/lib/ld-2.23.so)
                                       _dl_load_cache_lookup+0x2d6 (/usr/lib/ld-2.23.so)
                                       _dl_map_object+0x550 (/usr/lib/ld-2.23.so)
                                       openaux+0x30 (/usr/lib/ld-2.23.so)
                                       _dl_catch_error+0x74 (/usr/lib/ld-2.23.so)
                                       _dl_map_object_deps+0x275 (/usr/lib/ld-2.23.so)
                                       dl_main+0x1699 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_start+0x40c (/usr/lib/ld-2.23.so)
                                       _dl_start+0x288 (/usr/lib/ld-2.23.so)
                                       _dl_start_user+0x0 (/usr/lib/ld-2.23.so)
     0.192 ( 0.011 ms): cat/10174 open(filename: 0x8296fed0, flags: CLOEXEC                             ) = 3
                                       open64+0x7 (/usr/lib/ld-2.23.so)
                                       open_verify.constprop.7+0x58 (/usr/lib/ld-2.23.so)
     0.199 ( 0.003 ms): cat/10174 read(fd: 3, buf: 0x7ffc46e20f48, count: 832                           ) = 832
                                       read+0x7 (/usr/lib/ld-2.23.so)
                                       open_verify.constprop.7+0x9c (/usr/lib/ld-2.23.so)
     0.206 ( 0.002 ms): cat/10174 fstat(fd: 3, statbuf: 0x7ffc46e20de0                                  ) = 0
                                       _fxstat+0x14 (/usr/lib/ld-2.23.so)
                                       _dl_map_object_from_fd+0x5c (/usr/lib/ld-2.23.so)
     0.219 ( 0.009 ms): cat/10174 mmap(len: 4096, prot: READ|WRITE, flags: PRIVATE|ANONYMOUS, fd: -1    ) = 0x7f6882945000
                                       mmap64+0x3a (/usr/lib/ld-2.23.so)
                                       __libc_memalign+0x7e (/usr/lib/ld-2.23.so)
                                       _dl_new_object+0x86 (/usr/lib/ld-2.23.so)
                                       _dl_map_object_from_fd+0x16d (/usr/lib/ld-2.23.so)
                                       _dl_map_object+0x211 (/usr/lib/ld-2.23.so)
                                       openaux+0x30 (/usr/lib/ld-2.23.so)
                                       _dl_catch_error+0x74 (/usr/lib/ld-2.23.so)
                                       _dl_map_object_deps+0x275 (/usr/lib/ld-2.23.so)
                                       dl_main+0x1699 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_start+0x40c (/usr/lib/ld-2.23.so)
                                       _dl_start+0x288 (/usr/lib/ld-2.23.so)
                                       _dl_start_user+0x0 (/usr/lib/ld-2.23.so)
     0.249 ( 0.015 ms): cat/10174 mmap(len: 3803440, prot: EXEC|READ, flags: PRIVATE|DENYWRITE, fd: 3   ) = 0x7f68823a9000
                                       mmap64+0x3a (/usr/lib/ld-2.23.so)
                                       _dl_map_object_from_fd+0x533 (/usr/lib/ld-2.23.so)
                                       _dl_map_object+0x211 (/usr/lib/ld-2.23.so)
                                       openaux+0x30 (/usr/lib/ld-2.23.so)
                                       _dl_catch_error+0x74 (/usr/lib/ld-2.23.so)
                                       _dl_map_object_deps+0x275 (/usr/lib/ld-2.23.so)
                                       dl_main+0x1699 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_start+0x40c (/usr/lib/ld-2.23.so)
                                       _dl_start+0x288 (/usr/lib/ld-2.23.so)
                                       _dl_start_user+0x0 (/usr/lib/ld-2.23.so)
     0.276 ( 0.023 ms): cat/10174 mprotect(start: 0x7f6882540000, len: 2097152                          ) = 0
                                       mprotect+0x7 (/usr/lib/ld-2.23.so)
                                       _dl_map_object_from_fd+0xdf1 (/usr/lib/ld-2.23.so)
     0.302 ( 0.021 ms): cat/10174 mmap(addr: 0x7f6882740000, len: 24576, prot: READ|WRITE, flags: PRIVATE|DENYWRITE|FIXED, fd: 3, off: 1667072) = 0x7f6882740000
                                       mmap64+0x3a (/usr/lib/ld-2.23.so)
                                       _dl_map_object_from_fd+0xe5a (/usr/lib/ld-2.23.so)
                                       _dl_map_object+0x211 (/usr/lib/ld-2.23.so)
                                       openaux+0x30 (/usr/lib/ld-2.23.so)
                                       _dl_catch_error+0x74 (/usr/lib/ld-2.23.so)
                                       _dl_map_object_deps+0x275 (/usr/lib/ld-2.23.so)
                                       dl_main+0x1699 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_start+0x40c (/usr/lib/ld-2.23.so)
                                       _dl_start+0x288 (/usr/lib/ld-2.23.so)
                                       _dl_start_user+0x0 (/usr/lib/ld-2.23.so)
     0.326 ( 0.009 ms): cat/10174 mmap(addr: 0x7f6882746000, len: 14640, prot: READ|WRITE, flags: PRIVATE|ANONYMOUS|FIXED, fd: -1) = 0x7f6882746000
                                       mmap64+0x3a (/usr/lib/ld-2.23.so)
                                       _dl_map_object_from_fd+0xf37 (/usr/lib/ld-2.23.so)
                                       _dl_map_object+0x211 (/usr/lib/ld-2.23.so)
                                       openaux+0x30 (/usr/lib/ld-2.23.so)
                                       _dl_catch_error+0x74 (/usr/lib/ld-2.23.so)
                                       _dl_map_object_deps+0x275 (/usr/lib/ld-2.23.so)
                                       dl_main+0x1699 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_start+0x40c (/usr/lib/ld-2.23.so)
                                       _dl_start+0x288 (/usr/lib/ld-2.23.so)
                                       _dl_start_user+0x0 (/usr/lib/ld-2.23.so)
     0.338 ( 0.002 ms): cat/10174 close(fd: 3                                                           ) = 0
                                       close+0x7 (/usr/lib/ld-2.23.so)
                                       _dl_map_object_from_fd+0x9d2 (/usr/lib/ld-2.23.so)
     0.364 ( 0.005 ms): cat/10174 mmap(len: 4096, prot: READ|WRITE, flags: PRIVATE|ANONYMOUS, fd: -1    ) = 0x7f6882944000
                                       mmap64+0x3a (/usr/lib/ld-2.23.so)
                                       __libc_memalign+0x7e (/usr/lib/ld-2.23.so)
                                       _dl_allocate_tls_storage+0x1c (/usr/lib/ld-2.23.so)
                                       init_tls+0xd0 (/usr/lib/ld-2.23.so)
                                       dl_main+0x1cb3 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_start+0x40c (/usr/lib/ld-2.23.so)
                                       _dl_start+0x288 (/usr/lib/ld-2.23.so)
                                       _dl_start_user+0x0 (/usr/lib/ld-2.23.so)
     0.376 ( 0.004 ms): cat/10174 mmap(len: 4096, prot: READ|WRITE, flags: PRIVATE|ANONYMOUS, fd: -1    ) = 0x7f6882943000
                                       mmap64+0x3a (/usr/lib/ld-2.23.so)
                                       __libc_memalign+0x7e (/usr/lib/ld-2.23.so)
                                       allocate_dtv+0x22 (/usr/lib/ld-2.23.so)
                                       _dl_allocate_tls_storage+0x62 (/usr/lib/ld-2.23.so)
                                       init_tls+0xd0 (/usr/lib/ld-2.23.so)
                                       dl_main+0x1cb3 (/usr/lib/ld-2.23.so)
                                       _dl_sysdep_start+0x40c (/usr/lib/ld-2.23.so)
                                       _dl_start+0x288 (/usr/lib/ld-2.23.so)
                                       _dl_start_user+0x0 (/usr/lib/ld-2.23.so)
     0.385 ( 0.002 ms): cat/10174 arch_prctl(option: 4098, arg2: 140086844081920, arg3: 140086844076048, arg4: 34, arg5: -1) = 0
                                       init_tls+0x118 (/usr/lib/ld-2.23.so)
                                       dl_main+0x1cb3 (/usr/lib/ld-2.23.so)
     0.474 ( 0.021 ms): cat/10174 mprotect(start: 0x7f6882740000, len: 16384, prot: READ                ) = 0
                                       mprotect+0x7 (/usr/lib/ld-2.23.so)
                                       _dl_protect_relro+0x3d (/usr/lib/ld-2.23.so)
                                       _dl_relocate_object+0x32a (/usr/lib/ld-2.23.so)
     0.498 ( 0.015 ms): cat/10174 mprotect(start: 0x60b000, len: 4096, prot: READ                       ) = 0
                                       mprotect+0x7 (/usr/lib/ld-2.23.so)
                                       _dl_protect_relro+0x3d (/usr/lib/ld-2.23.so)
                                       _dl_relocate_object+0x32a (/usr/lib/ld-2.23.so)
     0.525 ( 0.019 ms): cat/10174 mprotect(start: 0x7f688296d000, len: 4096, prot: READ                 ) = 0
                                       mprotect+0x7 (/usr/lib/ld-2.23.so)
                                       _dl_protect_relro+0x3d (/usr/lib/ld-2.23.so)
                                       _dl_relocate_object+0x32a (/usr/lib/ld-2.23.so)
     0.542 ( 0.011 ms): cat/10174 munmap(addr: 0x7f6882946000, len: 158913                              ) = 0
                                       munmap+0x7 (/usr/lib/ld-2.23.so)
                                       _dl_unload_cache+0x28 (/usr/lib/ld-2.23.so)
                                       dl_main+0x21db (/usr/lib/ld-2.23.so)
no symbols found in /usr/bin/cat, maybe install a debug package?
     0.653 ( 0.002 ms): cat/10174 brk(                                                                  ) = 0x13f0000
                                       __brk+0x9 (/usr/lib/libc-2.23.so)
                                       __sbrk+0x5f (/usr/lib/libc-2.23.so)
                                       __default_morecore+0x9 (/usr/lib/libc-2.23.so)
                                       sysmalloc+0x3ef (/usr/lib/libc-2.23.so)
                                       _int_malloc+0x80d (/usr/lib/libc-2.23.so)
                                       malloc+0x54 (/usr/lib/libc-2.23.so)
                                       _nl_normalize_codeset+0x6a (/usr/lib/libc-2.23.so)
                                       _nl_load_locale_from_archive+0x352 (/usr/lib/libc-2.23.so)
                                       _nl_find_locale+0x43a (/usr/lib/libc-2.23.so)
                                       setlocale+0x48f (/usr/lib/libc-2.23.so)
                                       [0x1a51] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.668 ( 0.009 ms): cat/10174 brk(brk: 0x1411000                                                    ) = 0x1411000
                                       __brk+0x9 (/usr/lib/libc-2.23.so)
                                       __sbrk+0x79 (/usr/lib/libc-2.23.so)
                                       __default_morecore+0x9 (/usr/lib/libc-2.23.so)
                                       sysmalloc+0x3ef (/usr/lib/libc-2.23.so)
                                       _int_malloc+0x80d (/usr/lib/libc-2.23.so)
                                       malloc+0x54 (/usr/lib/libc-2.23.so)
                                       _nl_normalize_codeset+0x6a (/usr/lib/libc-2.23.so)
                                       _nl_load_locale_from_archive+0x352 (/usr/lib/libc-2.23.so)
                                       _nl_find_locale+0x43a (/usr/lib/libc-2.23.so)
                                       setlocale+0x48f (/usr/lib/libc-2.23.so)
                                       [0x1a51] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.706 ( 0.020 ms): cat/10174 open(filename: 0x82513910, flags: CLOEXEC                             ) = 3
                                       _nl_load_locale_from_archive+0x419 (/usr/lib/libc-2.23.so)
     0.713 ( 0.002 ms): cat/10174 fstat(fd: 3, statbuf: 0x7f6882745960                                  ) = 0
                                       __fxstat64+0x14 (/usr/lib/libc-2.23.so)
                                       _nl_load_locale_from_archive+0x443 (/usr/lib/libc-2.23.so)
     0.735 ( 0.016 ms): cat/10174 mmap(len: 2704400, prot: READ, flags: PRIVATE, fd: 3                  ) = 0x7f6882114000
                                       __mmap64+0x3a (/usr/lib/libc-2.23.so)
                                       _nl_load_locale_from_archive+0x46d (/usr/lib/libc-2.23.so)
                                       _nl_find_locale+0x43a (/usr/lib/libc-2.23.so)
                                       setlocale+0x48f (/usr/lib/libc-2.23.so)
                                       [0x1a51] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.748 ( 0.002 ms): cat/10174 close(fd: 3                                                           ) = 0
                                       _nl_load_locale_from_archive+0x4c6 (/usr/lib/libc-2.23.so)
     0.803 ( 0.003 ms): cat/10174 fstat(fd: 1, statbuf: 0x7ffc46e215d0                                  ) = 0
                                       __fxstat64+0x14 (/usr/lib/libc-2.23.so)
                                       [0x1bc7] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.823 ( 0.013 ms): cat/10174 open(filename: 0x46e22d12, mode: ISGID|0xfffe0000                     ) = 3
                                       __GI___libc_open+0x10 (/usr/lib/libc-2.23.so)
                                       [0x1c88] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.830 ( 0.002 ms): cat/10174 fstat(fd: 3, statbuf: 0x7ffc46e215d0                                  ) = 0
                                       __fxstat64+0x14 (/usr/lib/libc-2.23.so)
                                       [0x1cae] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.839 ( 0.002 ms): cat/10174 fadvise64(fd: 3, advice: 2                                            ) = 0
                                       posix_fadvise+0xd (/usr/lib/libc-2.23.so)
                                       [0x1cf1] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.855 ( 0.010 ms): cat/10174 mmap(len: 139264, prot: READ|WRITE, flags: PRIVATE|ANONYMOUS, fd: -1  ) = 0x7f688294b000
                                       __mmap64+0x3a (/usr/lib/libc-2.23.so)
                                       sysmalloc+0x87e (/usr/lib/libc-2.23.so)
                                       _int_malloc+0x80d (/usr/lib/libc-2.23.so)
                                       malloc+0x54 (/usr/lib/libc-2.23.so)
                                       [0x5ed9] (/usr/bin/cat)
                                       [0x2412] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.877 ( 0.005 ms): cat/10174 read(fd: 3, buf: 0x7f688294c000, count: 131072                        ) = 174
                                       __GI___libc_read+0x10 (/usr/lib/libc-2.23.so)
                                       [0x5286] (/usr/bin/cat)
                                       [0x2463] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.900 ( 0.017 ms): cat/10174 write(fd: 1, buf: 0x7f688294c000, count: 174                          ) = 174
                                       __GI___libc_write+0x10 (/usr/lib/libc-2.23.so)
                                       [0x52e6] (/usr/bin/cat)
                                       [0x2c1e] (/usr/bin/cat)
                                       [0x2449] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.908 ( 0.002 ms): cat/10174 read(fd: 3, buf: 0x7f688294c000, count: 131072                        ) = 0
                                       __GI___libc_read+0x10 (/usr/lib/libc-2.23.so)
                                       [0x5286] (/usr/bin/cat)
                                       [0x2463] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.924 ( 0.010 ms): cat/10174 munmap(addr: 0x7f688294b000, len: 139264                              ) = 0
                                       __GI___munmap+0x7 (/usr/lib/libc-2.23.so)
                                       [0x21cd] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.932 ( 0.002 ms): cat/10174 close(fd: 3                                                           ) = 0
                                       __GI___libc_close+0x10 (/usr/lib/libc-2.23.so)
                                       [0x21e4] (/usr/bin/cat)
                                       __libc_start_main+0xf1 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.950 ( 0.002 ms): cat/10174 close(fd: 1                                                           ) = 0
                                       _IO_file_close+0xb (/usr/lib/libc-2.23.so)
                                       _IO_file_close_it@@GLIBC_2.2.5+0x120 (/usr/lib/libc-2.23.so)
                                       fclose@@GLIBC_2.2.5+0x19f (/usr/lib/libc-2.23.so)
                                       [0x61fa] (/usr/bin/cat)
                                       [0x2b02] (/usr/bin/cat)
                                       __run_exit_handlers+0xe8 (/usr/lib/libc-2.23.so)
                                       [0x35c35] (/usr/lib/libc-2.23.so)
                                       __libc_start_main+0xf8 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.959 ( 0.002 ms): cat/10174 close(fd: 2                                                           ) = 0
                                       _IO_file_close+0xb (/usr/lib/libc-2.23.so)
                                       _IO_file_close_it@@GLIBC_2.2.5+0x120 (/usr/lib/libc-2.23.so)
                                       fclose@@GLIBC_2.2.5+0x19f (/usr/lib/libc-2.23.so)
                                       [0x61fa] (/usr/bin/cat)
                                       [0x2b25] (/usr/bin/cat)
                                       __run_exit_handlers+0xe8 (/usr/lib/libc-2.23.so)
                                       [0x35c35] (/usr/lib/libc-2.23.so)
                                       __libc_start_main+0xf8 (/usr/lib/libc-2.23.so)
                                       [0x25d9] (/usr/bin/cat)
     0.966 ( 0.000 ms): cat/10174 exit_group(                                                           )

Working With Unix Processes (English Edition)

Working With Unix Processes (English Edition)

linux 4.7でライブパッチ用のカーネルモジュールにelfのセクションチェックが入ったのでめも

Linux 4.7からライブパッチ用のカーネルモジュールをロードする時にELFのセクションをチェックするようになりました。

このチェックが入ったこととで、今までのモジュールを再コンパイルするだけだと以下のようなエラーになってロードに失敗します。

[  153.824961] livepatch: module livetest is not marked as a livepatch module
[  153.826718] livetest: failed to register patch

これはkernel/livepatch/core.cのklp_resolve_symbols()という関数でチェックしています。

233         for (i = 0; i < relasec->sh_size / sizeof(Elf_Rela); i++) {
234                 sym = pmod->core_kallsyms.symtab + ELF_R_SYM(relas[i].r_info);
235                 if (sym->st_shndx != SHN_LIVEPATCH) {
236                         pr_err("symbol %s is not marked as a livepatch symbol",
237                                strtab + sym->st_name);
238                         return -EINVAL;
239                 }

SHN_LIVEPATCHはinclude/uapi/linux/elf.hで定義されています。 これをどうやって設定するかですが、livepatch: reuse module loader code to write relocations · torvalds/linux@425595a · GitHubを見ると、

MODULE_INFO(livepatch, "Y");

こんな行が追加されているので、自分のモジュールにも追加します。そして再度コンパイルすればロードできるようになります。

動くメカニズムを図解&実験! Linux超入門 (My Linuxシリーズ)

動くメカニズムを図解&実験! Linux超入門 (My Linuxシリーズ)

elfファイルのdebugセクション分割とgdbの分割されたデバッグ情報のサポート機能めも

rpmパッケージとかは通常のパッケージとデバッグ用のパッケージを分けて、debugしたいときはデバッグ情報付きのパッケージをインストールしますよね。あれがどんな感じで動いているのか確認したのでメモです。

使っている機能としては、gdbデバッグ情報の分割機能とdebuglink機能だと思います。debuglinkはデバッグ情報のないバイナリファイルにデバッグ情報のあるバイナリファイルの情報を書き込むことでgdbがそのファイルを参照してくれるようになります。

やることとしては、以下の2点というところです。

では、実際に試してみます。まずは適当なc言語で書いたコードを-g付きでビルドします。そして、デバッグ情報付きのバイナリを以下の2ファイルに分けます。

ここでは、以下のようにtestという名称でバイナリを作ります。

masami@saga:~/tmp$ gcc -g test.c -o test

そして、このtestバイナリをobjcopyを使ってデバッグ情報を抜き出したファイルを作ります。

masami@saga:~/tmp$ objcopy --only-keep-debug test test.debug

これで、testに含まれるデバッグ情報をtest.debugファイルに書き出しました。この時点ではtestのほうは変更はありません。 次にtestからデバッグ情報を削除します。

masami@saga:~/tmp$ strip -g test

これでtestからデバッグ情報が消えたのでgdbでちょっと確認してみると、当然デバッグシンボルは無いと言われます。

masami@saga:~/tmp$ gdb ./test
Reading symbols from ./test...(no debugging symbols found)...done.
(gdb) quit

この時点でのtestバイナリには31個のセクションヘッダーがあります。

masami@saga:~/tmp$ readelf -S test
There are 31 section headers, starting at offset 0x1290:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400200  00000200
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             000000000040021c  0000021c
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             000000000040023c  0000023c
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400260  00000260
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           0000000000400280  00000280
       0000000000000060  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           00000000004002e0  000002e0
       000000000000003d  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           000000000040031e  0000031e
       0000000000000008  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400328  00000328
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400348  00000348
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400360  00000360
       0000000000000030  0000000000000018  AI       5    24     8
  [11] .init             PROGBITS         0000000000400390  00000390
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004003b0  000003b0
       0000000000000030  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         00000000004003e0  000003e0
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         00000000004003f0  000003f0
       00000000000001c2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         00000000004005b4  000005b4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000004005c0  000005c0
       0000000000000004  0000000000000004  AM       0     0     4
  [17] .eh_frame_hdr     PROGBITS         00000000004005c4  000005c4
       000000000000003c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000400600  00000600
       0000000000000114  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600718  00000718
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600720  00000720
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600728  00000728
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600730  00000730
       00000000000001d0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600900  00000900
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000600908  00000908
       0000000000000028  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000600930  00000930
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000600940  00000940
       0000000000000008  0000000000000000  WA       0     0     1
  [27] .comment          PROGBITS         0000000000000000  00000940
       0000000000000034  0000000000000001  MS       0     0     1
  [28] .shstrtab         STRTAB           0000000000000000  00001184
       000000000000010c  0000000000000000           0     0     1
  [29] .symtab           SYMTAB           0000000000000000  00000978
       0000000000000600  0000000000000018          30    44     8
  [30] .strtab           STRTAB           0000000000000000  00000f78
       000000000000020c  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

次に、objcopyを使って、debuglinkを埋め込みます。

masami@saga:~/tmp$ objcopy --add-gnu-debuglink=test.debug test

この結果、index 28のセクションヘッダに.gnu_debuglinkというのが追加されていて、合計32個のセクションヘッダになります。

asami@saga:~/tmp$ readelf -S test
There are 32 section headers, starting at offset 0x12c8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400200  00000200
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             000000000040021c  0000021c
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             000000000040023c  0000023c
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400260  00000260
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           0000000000400280  00000280
       0000000000000060  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           00000000004002e0  000002e0
       000000000000003d  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           000000000040031e  0000031e
       0000000000000008  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400328  00000328
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400348  00000348
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400360  00000360
       0000000000000030  0000000000000018  AI       5    24     8
  [11] .init             PROGBITS         0000000000400390  00000390
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004003b0  000003b0
       0000000000000030  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         00000000004003e0  000003e0
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         00000000004003f0  000003f0
       00000000000001c2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         00000000004005b4  000005b4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000004005c0  000005c0
       0000000000000004  0000000000000004  AM       0     0     4
  [17] .eh_frame_hdr     PROGBITS         00000000004005c4  000005c4
       000000000000003c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000400600  00000600
       0000000000000114  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600718  00000718
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600720  00000720
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600728  00000728
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600730  00000730
       00000000000001d0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600900  00000900
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000600908  00000908
       0000000000000028  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000600930  00000930
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000600940  00000940
       0000000000000008  0000000000000000  WA       0     0     1
  [27] .comment          PROGBITS         0000000000000000  00000940
       0000000000000034  0000000000000001  MS       0     0     1
  [28] .gnu_debuglink    PROGBITS         0000000000000000  00000974
       0000000000000010  0000000000000000           0     0     1
  [29] .shstrtab         STRTAB           0000000000000000  000011ac
       000000000000011b  0000000000000000           0     0     1
  [30] .symtab           SYMTAB           0000000000000000  00000988
       0000000000000618  0000000000000018          31    45     8
  [31] .strtab           STRTAB           0000000000000000  00000fa0
       000000000000020c  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

readelfコマンドでどんな内容か確認すると、test.debugという文字列が入っています。

masami@saga:~/tmp$ readelf -p .gnu_debuglink test

String dump of section '.gnu_debuglink':
  [     0]  test.debug
  [     c]  c

で、ここでgdbを再度実行すると、自動的にtest.debugを読み込んで、デバッグ情報が使えるようになります。

masami@saga:~/tmp$ gdb ./test
Reading symbols from ./test...Reading symbols from /home/masami/tmp/test.debug...done.
done.
(gdb) list
1       #include <stdio.h>
2
3       static void print_args(char **argv)
4       {
5               while (*argv) {
6                       printf("%s\n", *argv++);
7               }
8       }
9
10      int main(int argc, char **argv)
(gdb)

今はtestとtest.debugが同じ場所にあるので問題なかったですが、例えば、test.debugをdebug/においた場合、gdbデバッグシンボルを見つけられません。このような場合は、set debug-file-directoryでデバッグ情報のあるディレクトリを指定します。setでディレクトリを指定して、fileコマンドで読み込んだりすれば大丈夫です。

masami@saga:~/tmp$ gdb ./test
Reading symbols from ./test...(no debugging symbols found)...done.
(gdb) set debug-file-directory ./debug
(gdb) list
No symbol table is loaded.  Use the "file" command.
(gdb) show debug-file-directory
The directory where separate debug symbols are searched for is "./debug".
(gdb) file ./debug/test.debug
Reading symbols from ./debug/test.debug...done.
(gdb) list
1       #include <stdio.h>
2
3       static void print_args(char **argv)
4       {
5               while (*argv) {
6                       printf("%s\n", *argv++);
7               }
8       }
9
10      int main(int argc, char **argv)
(gdb)

あと、Build IDも重要なはずですね。これもreadelfコマンドで確認できます。どちらのファイルもBuild IDは同じなことを確認できます。

masami@saga:~/tmp$ readelf -n test

Displaying notes found at file offset 0x0000021c with length 0x00000020:
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 2.6.32

Displaying notes found at file offset 0x0000023c with length 0x00000024:
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: d97bf9a494dbde8a03785c102de536b2d8f82ac7
masami@saga:~/tmp$ readelf -n test.debug

Displaying notes found at file offset 0x0000021c with length 0x00000020:
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 2.6.32

Displaying notes found at file offset 0x0000023c with length 0x00000024:
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: d97bf9a494dbde8a03785c102de536b2d8f82ac7

( ´ー`)フゥー...

参考

Separate Debug Files - Debugging with GDB

Learning Linux Binary Analysis

Learning Linux Binary Analysis

linux kernelのlivepatchにライブパッチを適用する・しないの判断機能を入れて遊ぶ

2016年7月13日〜15日で行われたLinuxCon Japan 2016のセッションでカーネルハックの一環でカーネルのlivepatchの機能を使って、FreeBSDバイナリをLinuxで動かすってセッションに参加して、そういう使い方もありだなと思ったのでちょっと試してみました。アリだなと思ったところは条件に応じてライブパッチするしないの判断をできるようにすれば便利だなってとこだったので、そのへんを試してみた感じです。

まず、linux kernelのlivepatch機能を条件判断できるように変更します。

github.com

livepatch機能の変更点はpatch対象の関数が呼ばれた時のハンドラ関数のklp_ftrace_handler()です。ここで、条件判断の関数を呼んで、その関数がtrueを返したら新しい関数を呼び、falseの場合はオリジナルの関数を引き続き実行できるようにしています。

-    klp_arch_set_pc(regs, (unsigned long)func->new_func);
+   if (likely(klp_need_patch_apply_func())) 
+       klp_arch_set_pc(regs, (unsigned long)func->new_func);
+   else
+       klp_arch_enter_orig_func(regs, ip);
+

条件判断の関数はカーネル本体にはデフォルトの関数だけ作っていて、ライブパッチの登録時に変更するようにしました。Linux Kernelのlivepatchはカーネルモジュールとして作るので、条件判断の関数もlivepatch用のカーネルモジュール内で作ります。

livepatch test module

上のコードは、以下の3パターンの条件判断のうちどれかを選択できるようにしてます。

  1. 指定したPID
  2. 指定したプロセス名(comm)
  3. アクティブなPID名前空間が指定したPID名前空間に所属している

例えば、特定のPID名前空間のプロセスを対象にする場合、条件判断式は↓を使います。

static bool need_patch_apply_by_pidns(void)
{
        struct pid_namespace *pidns = task_active_pid_ns(current);
        return target_inum == pidns->ns.inum;
}

この場合、PID名前空間の指定はinode番号を使います。

実際の例でいくと、dockerコンテナ内のプロセスを対象にするとして、動かしているpid:1071のPID名前空間のinodeを調べます。

masami@kerntest:~$ sudo pstree -p
systemd(1)─┬─agetty(339)
           ├─agetty(340)
           ├─dbus-daemon(251)
           ├─dhcpcd(335)
           ├─docker(761)─┬─docker-containe(779)─┬─docker-containe(1027)─┬─bash(1043)───cmdline(1071)
           │             │                      │                       ├─{docker-containe}(1028)
           │             │                      │                       ├─{docker-containe}(1029)
           │             │                      │                       ├─{docker-containe}(1030)
           │             │                      │                       ├─{docker-containe}(1031)
           │             │                      │                       ├─{docker-containe}(1032)

pidがわかればそこで使っている名前空間のinode番号は/procを見ることでわかります。

masami@kerntest:~/livetest$ sudo ls -la /proc/1071/ns/pid
lrwxrwxrwx 1 root root 0 Jul 16 11:33 /proc/1071/ns/pid -> 'pid:[4026532216]'

コンテナで動かしているプロセスはこんな感じのやつです。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void read_cmdline(void)
{
        int fd;
        char buf[256] = {0};

        fd = open("/proc/cmdline", O_RDONLY);
        if (fd == -1) {
                perror("open");
                exit(-1);
        }

        read(fd, buf, sizeof(buf) - 1);
        close(fd);
        printf("%s", buf);
}

int main(int argc, char **argv)
{

        printf("pid is %d\n", getpid());
        while (1) {
                read_cmdline();
                sleep(1);
        }
        return 0;

}

後はカーネルモジュールをinsmodするだけです。

masami@kerntest:~/livetest$ sudo insmod livetest.ko target_inum=4026532216

そうすると、dockerコンテナ内で動いているプロセスが最初は普通に/proc/cmdlineの初期値を読んでいたのに、livepatchモジュールの読み込みにより出力内容が変わります。

masami@kerntest:~/livetest$ cd && sudo docker run --rm -v `pwd`:/tmp/test -it base/archlinux /bin/bash
[root@e1d3894ff868 /]# cd /tmp/test
[root@e1d3894ff868 test]# ./cmdline
pid is 7
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
~~~略~~~
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M
hello, world
hello, world
hello, world

dockerコンテナを抜けるとpid名前空間が変わるので、/proc/cmdlineを見ると、またカーネルコマンドラインが見れます。

^C
[root@e1d3894ff868 test]# exit
exit
masami@kerntest:~$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.6.0-ktest+ root=UUID=538fe610-066c-4939-9d33-18a80f7a28a0 rw quiet console=tty0 console=ttyS0,115200n8 crashkernel=128M

(´-`).oO(元々のlivepatch機能の意図からは外れるんだけど、ちょっとした実験とかには良さげな気もします

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ