libbfdのめも

libbfdの使い方のめもです。利用してるバージョンはbinutils-devel-2.31.1-13.fc29.x86_64です。

nmもどき

アドレスとセクション名、それにdebug情報があればファイル名と行数を表示。連想配列が使いたかったのでヘッダファイルだけで実装されてるuthashというライブラリを使ってます。

simple_nm.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bfd.h>

#include "uthash.h"

struct section_info {
    char name[64];
    unsigned long vma;
    UT_hash_handle hh;
};

static struct section_info *sections_info = NULL;

static void delete_all_hash_values(void)
{
    struct section_info *pos, *tmp;

    HASH_ITER(hh, sections_info, pos, tmp) {
        HASH_DEL(sections_info, pos);
        free(pos);
    }

    HASH_CLEAR(hh, sections_info);
}

static struct section_info *add_section(bfd *abfd, const char *sname)
{
    struct section_info *info;

    asection *section = bfd_get_section_by_name(abfd, sname);
    if (!section)
        return NULL;

    info = malloc(sizeof(*info));
    if (!info) {
        fprintf(stderr, "malloc failed\n");
        exit(-1);
    }
    memset(info, 0x0, sizeof(*info));

    strcpy(info->name, sname);
    info->vma = section->vma;

    HASH_ADD_STR(sections_info, name, info);

    return info;
}

static struct section_info *get_section_info(bfd *abfd, const char *sname)
{
    struct section_info *info = NULL;

    HASH_FIND_STR(sections_info, sname, info);
    if (info)
        return info;

    return add_section(abfd, sname);
}

static void show_function_info(bfd *abfd, asection *debug_info, asymbol **symbols, int idx)
{
    const char *file, *func;
    unsigned int line;
    const char *sname = symbols[idx]->section->name;
    const char *name = symbols[idx]->name;
    struct section_info *info = get_section_info(abfd, sname);

    if (!info)
        return ;

    unsigned long addr = symbols[idx]->value + info->vma;
    if (bfd_find_nearest_line(abfd, debug_info, symbols, addr, &file, &func, &line))
        printf("0x%lx %s %s %s:%u\n", addr, sname, name, file, line);
    else
        printf("0x%lx %s %s\n", addr, sname, name);
}

static void show(char *prog)
{
    bfd *abfd;
    asection *debug_info;
    size_t nsyms;
    asymbol **symbols;
    int ret;

    bfd_init();

    abfd = bfd_openr(prog, NULL);
    if (!abfd) {
        fprintf(stderr, "failed to open %s\n", prog);
        exit(-1);
    }

    ret = bfd_check_format(abfd, bfd_object);
    if (!ret) {
        fprintf(stderr, "invalid object format\n");
        exit(-1);
    }

    debug_info = bfd_get_section_by_name(abfd, ".debug_info");

    symbols = malloc(bfd_get_symtab_upper_bound(abfd));
    if (!symbols) {
        fprintf(stderr, "malloc failed\n");
        exit(-1);
    }

    nsyms = bfd_canonicalize_symtab(abfd, symbols);

    for (int i = 0; i < nsyms; i++)
        show_function_info(abfd, debug_info, symbols, i);

    delete_all_hash_values();
    free(symbols);
    bfd_close(abfd);
}

int main(int argc, char **argv)
{
    if (argc != 2) {
        fprintf(stderr, "usage: %s <file>\n", argv[0]);
        exit(-1);
    }

    show(argv[1]);
    return 0;
}

Makefile

objs=simple_nm.o

prog=simple_nm

all: $(objs)
  $(CC) -O0 -g $(objs) -o $(prog) -lbfd

.c.o:
  $(CC) -O0 -I. -g -c -Wall $<

test:
  ./$(prog) $(prog)

clean:
  rm -f core* $(objs) $(prog)

実行例

0xffffffff829e0018 .bss panic_param
0xffffffff827347e3 .init.text unknown_bootoption /home/masami/linux-kernel/init/main.c:294
0xffffffff81002010 .text trace_initcall_finish_cb /home/masami/linux-kernel/init/main.c:841
0xffffffff810028e3 .text trace_initcall_start_cb /home/masami/linux-kernel/init/main.c:832
0xffffffff81002911 .text run_init_process /home/masami/linux-kernel/init/main.c:1011
0xffffffff8100294f .text try_to_run_init_process /home/masami/linux-kernel/init/main.c:1020
0xffffffff82734977 .init.text trace_event_define_fields_initcall_level /home/masami/linux-kernel/./include/trace/events/initcall.h:10
0xffffffff8273499f .init.text trace_event_define_fields_initcall_start /home/masami/linux-kernel/./include/trace/events/initcall.h:27
0xffffffff827349c4 .init.text trace_event_define_fields_initcall_finish /home/masami/linux-kernel/./include/trace/events/initcall.h:48
0xffffffff81002050 .text perf_trace_initcall_start /home/masami/linux-kernel/./include/trace/events/initcall.h:27
0xffffffff81002120 .text perf_trace_initcall_finish /home/masami/linux-kernel/./include/trace/events/initcall.h:48
0xffffffff81002200 .text trace_raw_output_initcall_level /home/masami/linux-kernel/./include/trace/events/initcall.h:10
0xffffffff81002250 .text trace_raw_output_initcall_start /home/masami/linux-kernel/./include/trace/events/initcall.h:27
0xffffffff81002290 .text trace_raw_output_initcall_finish /home/masami/linux-kernel/./include/trace/events/initcall.h:48
0xffffffff810022e0 .text __bpf_trace_initcall_level /home/masami/linux-kernel/./include/trace/events/initcall.h:10
0xffffffff810022f0 .text __bpf_trace_initcall_start /home/masami/linux-kernel/./include/trace/events/initcall.h:27
0xffffffff81002300 .text __bpf_trace_initcall_finish /home/masami/linux-kernel/./include/trace/events/initcall.h:48
0xffffffff82734a1c .init.text loglevel /home/masami/linux-kernel/init/main.c:230
0xffffffff81002310 .text initcall_blacklisted /home/masami/linux-kernel/init/main.c:790
0xffffffff82213020 .data blacklisted_initcalls
0xffffffff82329a50 .data descriptor.60757
0xffffffff82734a77 .init.text set_debug_rodata /home/masami/linux-kernel/ini

ptrace(2)で呼び出す関数を動的に変更

bfdで関数のアドレスを調べてptrace(2)でブレークポイントを設定して、ブレークポイントにきたらripを呼び出したい関数に変えて本来の関数は実行しない形。inline化されてるものには対応してませんけども。。

simple_break_point.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <assert.h>
#include <bfd.h>

static void stub_function(void)
{
    printf("stub function is called\n");
}

static void say_hello(void)
{
    printf("hello, world\n");
}

void *get_function_address(char *prog_name, char *func_name)
{
    bfd *abfd;
    asection *text;
    size_t nsyms;
    asymbol **symbols;
    void *addr = NULL;
    int ret;

    bfd_init();

    abfd = bfd_openr(prog_name, NULL);
    assert(abfd != NULL);

    ret = bfd_check_format(abfd, bfd_object);
    assert(ret != 0);

    text = bfd_get_section_by_name(abfd, ".text");

    symbols = malloc(bfd_get_symtab_upper_bound(abfd));
    assert(symbols != NULL);

    nsyms = bfd_canonicalize_symtab(abfd, symbols);

    for (int i = 0; i < nsyms; i++) {
        if (!strcmp(symbols[i]->name, func_name)) {
            addr = (void *) (symbols[i]->value + text->vma);
            break;
        }
    }

    free(symbols);
    bfd_close(abfd);

    return addr;
}

int main(int argc, char **argv)
{
    pid_t pid;
    long ret;
    void *addr;

    if (argc != 2) {
        printf("usage: %s <function name>\n", argv[0]);
        exit(-1);
    }

    addr = get_function_address(argv[0], argv[1]);
    if (!addr) {
        printf("function %s is not found\n", argv[1]);
        exit(-1);
    }

    pid = fork();
    assert(pid >=  0);

    if (!pid) {
        say_hello();
    } else {
        int status;
        void *break_point;
        long orig_text;

        ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
        assert(ret != -1);
        waitpid(pid, &status, 0);

        orig_text = ptrace(PTRACE_PEEKTEXT, pid, addr, NULL);
        break_point = (void *) (unsigned long) (orig_text | 0xcc);
        ret = ptrace(PTRACE_POKETEXT, pid, addr, break_point);
        assert(pid != -1);

        printf("[*] set break point at %p\n", addr);

        ret = ptrace(PTRACE_CONT, pid, NULL, NULL);
        assert(pid != -1);

        waitpid(pid, &status, 0);

        if (WIFEXITED(status)) {
            printf("program normally finishd\n");
            exit(0);
        } else if (WIFSTOPPED(status)) {
            struct user_regs_struct regs = { 0 };
            ret = ptrace(PTRACE_GETREGS, pid, 0, &regs);
            assert(ret != -1);
            regs.rip = (unsigned long long) stub_function;

            ret = ptrace(PTRACE_SETREGS, pid, 0, &regs);
            assert(ret != -1);

            ret = ptrace(PTRACE_CONT, pid, NULL, NULL);
            assert(pid != -1);
        } else {
            printf("unkown error\n");
            exit(-1);
        }
    }

    return 0;
}

Makefile

objs=simple_break_point.o

prog=simple_break_point

all: $(objs)
  $(CC) -O0 -g $(objs) -o $(prog) -lbfd

.c.o:
  $(CC) -O0 -g -c -Wall $<

test:
  ./$(prog) say_hello

clean:
  rm -f core* $(objs) $(prog)

実行例

ここでブレークポイントを設定する関数のアドレスはnmで見るとこうなってます。

masami@saga:~/codes/simple_break_point$ nm ./simple_break_point | grep "say_hello\|main"
                 U __libc_start_main@@GLIBC_2.2.5
0000000000402a69 T main
00000000004028c7 t say_hello
ブレークポイントにヒットする場合
masami@saga:~/codes/simple_break_point$ ./simple_break_point say_hello
[*] set break point at 0x4028c7
stub function is called
ブレークポイントにヒットしない場合

すでに実行されたmain()に設定することでブレークポイントにヒットしない感じで。

mi@saga:~/codes/simple_break_point$ ./simple_break_point main
[*] set break point at 0x402a69
hello, world
program normally finishd

virt-managerでarmのゲストを動かすメモ

忘れないようにメモっとこうという程度なので大したことはしてないです。

aarch64やarmhfpのイメージはIndex of /pub/Linux/Fedora/releasesから取得したとします。

qemuのバージョン

ホストはx86_64なFedora 29で、qemuは3.0.0になります。

masami@saga:~/arm/aarch64$ rpm -qa  | grep qemu-system
qemu-system-arm-core-3.0.0-1.fc29.x86_64
qemu-system-x86-3.0.0-1.fc29.x86_64
qemu-system-x86-core-3.0.0-1.fc29.x86_64
qemu-system-aarch64-core-3.0.0-1.fc29.x86_64
qemu-system-arm-3.0.0-1.fc29.x86_64
qemu-system-aarch64-3.0.0-1.fc29.x86_64

あとこの辺も必要かと思います。

masami@saga:~/arm/aarch64$ rpm -qa | grep edk2
edk2-aarch64-20180815gitcb5f4f45ce-1.fc29.noarch
edk2-ovmf-20180815gitcb5f4f45ce-1.fc29.noarch

aarch64

イメージはxz形式で圧縮されているのでまずは展開します。

masami@saga:~/arm/aarch64$ xz -dc Fedora-Server-28-1.1.aarch64.raw.xz  > Fedora-Server-28-1.1.aarch64.raw

そして、カーネルやコマンド諸々込みのファイルができるのですが、これをそのまま使ってもログイン可能なユーザーもいないし、rootログインできないのでまずはrootのパスワードを設定します。ここでは virt-customizeというコマンドを使います。これはlibguestfs-toolsってパッケージをインストールすると使えます。

masami@saga:~/arm/aarch64$ virt-customize --root-password file:./rootpw.txt -a Fedora-Server-28-1.1.aarch64.raw
[   0.0] Examining the guest ...
[  16.3] Setting a random seed
[  16.4] Setting the machine ID in /etc/machine-id
[  16.4] Setting passwords
[  17.7] Finishing off

このコマンドのオプションが見たままですがrootのパスワードを設定しています。パスワードはrootpw.txtってファイルに書いてます。このファイルの1行目に平文でパスワードを記述します。このコマンドでユーザー作ったりも出来ます。 それはともかく、これでrootログインできるようになったので後はvirt-installでインストールします。

ディスクイメージを/var/lib/libvirt/imagesに置き、以下のような感じでインストールします。これが最低限なコマンドラインオプションかと思います。

masami@saga:~/arm/aarch64$ sudo virt-install --name fedora28-aarch64 --ram 4096 --arch aarch64 --import --os-variant fedora22 --disk /var/lib/libvirt/images/Fedora-Server-28-1.1.aarch64.raw --boot uefi

armhfp版との違いとして--bootでufeiを指定してるというのがあります。armhfpの場合だとvmlinuzとかinitrdの指定があります。

あとはしばらく待っていればログインプロンプトが表示されるのでidをroot、パスワードは先程設定したものを入力すればログインできます(∩´∀`)∩ワーイ

[  OK  ] Started Update UTMP about System Runlevel Changes.

Fedora 28 (Server Edition)
Kernel 4.16.3-301.fc28.aarch64 on an aarch64 (ttyAMA0)

Admin Console: https://192.168.122.223:9090/ or https://[fe80::27d2:fdd6:dc02:4ab]:9090/

localhost login: root
Password:
[root@localhost ~]# uname -a
Linux localhost.localdomain 4.16.3-301.fc28.aarch64 #1 SMP Mon Apr 23 21:45:59 UTC 2018 aarch64 aarch64 aarch64 GNU/Linux  

また、サーバ版はCockpitが動いているのでAdmin Console: ~と書かれているURLにブラウザでアクセスすればブラウザからでも端末エミュレータが使えます。

f:id:masami256:20181106220907p:plain

armhfp

aarch64とも多少違うのでこちらもメモしておきます。基本的にはArchitectures/ARM/F28/Installation - Fedora Project Wikiの手順に沿えば良いのですが、qemuのバージョンが3.0.0の場合(今後はわかりません)はwikiの手順だけだとBug 1633328 – Armv7 guest fails to boot with qemu-3.0.0-1に書かれているように以下のようなワーニングが出てインストール完了しません。

[   51.049803] audit: type=1130 audit(1541508483.457:11): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=kernel msg='unit=systemd-tmpfiles-setup comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[  OK  ] Reached target System Initialization.
[  OK  ] Reached target Basic System.
[  371.409670] dracut-initqueue[338]: Warning: dracut-initqueue timeout - starting timeout scripts
[  373.553982] dracut-initqueue[338]: Warning: dracut-initqueue timeout - starting timeout scripts
[  374.886815] dracut-initqueue[338]: Warning: dracut-initqueue timeout - starting timeout scripts
[  376.308904] dracut-initqueue[338]: Warning: dracut-initqueue timeout - starting timeout scripts
[  377.638877] dracut-initqueue[338]: Warning: dracut-initqueue timeout - starting timeout scripts

そのため、commentの14に書かれているようにvirt-install--qemu-commandline='-machine highmem=off というオプションを追加します。よって、virt-installコマンドラインはこんなふうになります。

sudo virt-install --name fedora28-armhfp --ram 1024 --arch armv7l \
  --import --os-variant fedora22 \
  --disk /var/lib/libvirt/images/Fedora-Minimal-armhfp-28-1.1-sda.raw \
  --boot kernel=/var/lib/libvirt/images/vmlinuz-4.16.3-301.fc28.armv7hl,initrd=/var/lib/libvirt/images/initramfs-4.16.3-301.fc28.armv7hl.img,kernel_args="console=ttyAMA0 rw root=LABEL=_/ rootwait" --qemu-commandline='-machine highmem=off'

こちらもvirt-installの前にvirt-customizeでrootのパスワードを設定しておく必要があります。

ゲストへの接続

普通にvirsh console ゲスト名で接続できます。

その他

armhfpのほうはhighmem=offなどの設定が必要なのが注意事項ですね ( ´ー`)フゥー...

ARMで学ぶ アセンブリ言語入門

ARMで学ぶ アセンブリ言語入門

When kallsyms doesn't show addresses even though kptr_restrict is 0

When I did grep kernel symbol address, addresses were all zero.

masami@saga:~$ grep slab_alloc /proc/kallsyms 
0000000000000000 t ___slab_alloc
0000000000000000 t __slab_alloc

So, I checked kptr_restrict if it's not zero.

masami@saga:~$ sudo sysctl kernel.kptr_restrict
kernel.kptr_restrict = 0

Um, kptr_restrict was already zero. By the way, how about kernel config that enables KALLSYMS ? Current kernel version is here.

masami@saga:~$ uname -a
Linux saga 4.18.13-300.fc29.x86_64 #1 SMP Wed Oct 10 17:22:50 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

So, in /boot there is kernel configs that I could check it.

masami@saga:~$ grep -i kallsyms /boot/config-4.18.13-300.fc29.x86_64
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
CONFIG_KALLSYMS_ABSOLUTE_PERCPU=y
CONFIG_KALLSYMS_BASE_RELATIVE=y

Ok, KALLSYMS is enabled. But how come I couldn't get addresses? Let's check kernel functions.

When you open /proc/kallsyms, kallsyms_show_value() is called. In my case, kptr_restrict is 0, so kallsyms_for_perf() is called.

int kallsyms_show_value(void)
{
    switch (kptr_restrict) {
    case 0:
        if (kallsyms_for_perf())
            return 1;
    /* fallthrough */
    case 1:
        if (has_capability_noaudit(current, CAP_SYSLOG))
            return 1;
    /* fallthrough */
    default:
        return 0;
    }
}

kallsyms_for_perf() is following simple function.

static inline int kallsyms_for_perf(void)
{
#ifdef CONFIG_PERF_EVENTS
    extern int sysctl_perf_event_paranoid;
    if (sysctl_perf_event_paranoid <= 1)
        return 1;
#endif
    return 0;
}

As you can see, thee is #ifdef macro. so, check CONFIG_PERF_EVENTS in kernel config.

masami@saga:~$ grep CONFIG_PERF_EVENTS /boot/config-4.18.13-300.fc29.x86_64
CONFIG_PERF_EVENTS=y
CONFIG_PERF_EVENTS_INTEL_UNCORE=m
CONFIG_PERF_EVENTS_INTEL_RAPL=m
CONFIG_PERF_EVENTS_INTEL_CSTATE=m
CONFIG_PERF_EVENTS_AMD_POWER=m

Ok, if perf_event_paranoid is less than or equal to 1, it returns 1, otherwise return 0. Let's check perf_event_paranoid.

masami@saga:~$ sudo sysctl kernel.perf_event_paranoid
kernel.perf_event_paranoid = 2

Ah, I got it. However, what value can I use for? According to Documentation/sysctl/kernel.txt, there is a paragraph that describes perf_event_paranoid values. Maybe 1 is okay for me now.

masami@saga:~$ sudo sysctl -w kernel.perf_event_paranoid=1
kernel.perf_event_paranoid = 1

Then check symbols again.

masami@saga:~$ grep slab_alloc /proc/kallsyms 
ffffffff96272150 t ___slab_alloc
ffffffff96272790 t __slab_alloc

got it :)

Understanding the Linux Kernel: From I/O Ports to Process Management

Understanding the Linux Kernel: From I/O Ports to Process Management

fedoraでdockerを使ってaarch64のクロスビルド環境を作る

qemuのstaticなバイナリとbinfmt_miscを使ってdockerでクロスビルド環境を作ってみます。

今回作ったものはこちらに置きました。

github.com

環境

HostはFedora 29で、arm64v8/fedoraにあるaarch 64のFedora 28(2018/10/11時点でのlatestはfedora 28です)。このイメージはオフィシャルなものです。

qemu

qemuのパッケージで必要なのは qemu-user-static です。これをサクッとインストールしましょう。

binfmt_misc

つぎにbinfmt_miscの設定が必要なんですが、fedoraの場合systemdのproc-sys-fs-binfmt_misc.mountが動いてればOKです。自分の場合はインストールした後に所要で再起動したのですが、restartとかすれば良い気がします。

masami@saga:~$ systemctl status proc-sys-fs-binfmt_misc.mount
● proc-sys-fs-binfmt_misc.mount - Arbitrary Executable File Formats File System
   Loaded: loaded (/usr/lib/systemd/system/proc-sys-fs-binfmt_misc.mount; static; vendor preset: disabled)
   Active: active (mounted) since Thu 2018-10-11 20:07:16 JST; 2h 2min ago
    Where: /proc/sys/fs/binfmt_misc
     What: binfmt_misc
     Docs: https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html
           https://www.freedesktop.org/wiki/Software/systemd/APIFileSystems
    Tasks: 0 (limit: 4915)
   Memory: 60.0K
   CGroup: /system.slice/proc-sys-fs-binfmt_misc.mount

Oct 11 20:07:16 saga systemd[1]: Mounting Arbitrary Executable File Formats File System...
Oct 11 20:07:16 saga systemd[1]: Mounted Arbitrary Executable File Formats File System.

/proc/sys/fs/binfmt_miscを手動で設定するときはELFバイナリのシグネチャとかを自分で設定しないといけないんですが、proc-sys-fs-binfmt_misc.mountが上手いことやってくれてるようです。/proc/sys/fs/binfmt_misc/が↓のようになっていれば設定完了です。

masami@saga:~$ ls /proc/sys/fs/binfmt_misc/
./            qemu-aarch64_be  qemu-armeb  qemu-microblaze    qemu-mips64    qemu-mipsn32    qemu-ppc      qemu-riscv32  qemu-sh4          qemu-xtensa    status
../           qemu-alpha       qemu-hppa   qemu-microblazeel  qemu-mips64el  qemu-mipsn32el  qemu-ppc64    qemu-riscv64  qemu-sh4eb        qemu-xtensaeb
qemu-aarch64  qemu-arm         qemu-m68k   qemu-mips          qemu-mipsel    qemu-or1k       qemu-ppc64le  qemu-s390x    qemu-sparc32plus  register

docker image

arm64v8/fedoraのイメージを使います。

試しに動作確認をしてたのが以下になります。unameコマンドで表示されるアーキテクチャがaarch64になってますね。

masami@saga:~$ sudo docker pull arm64v8/fedora
Using default tag: latest
Trying to pull repository docker.io/arm64v8/fedora ... 
sha256:a2adc1621935869bcd51a84b007b3ee6aa8aa9aef0bf30a947f7312fd43e5b0b: Pulling from docker.io/arm64v8/fedora
f9afc51fb922: Pull complete 
Digest: sha256:a2adc1621935869bcd51a84b007b3ee6aa8aa9aef0bf30a947f7312fd43e5b0b
Status: Downloaded newer image for docker.io/arm64v8/fedora:latest
masami@saga:~$ sudo docker image ls
REPOSITORY                 TAG                 IMAGE ID            CREATED             SIZE
docker.io/arm64v8/fedora   latest              95cddd448075        4 weeks ago         290 MB
runtimejstest_web          latest              5cf57ccadd2f        4 weeks ago         675 MB
docker.io/node             8                   8198006b2b57        5 weeks ago         673 MB
docker.io/amazonlinux      latest              585cc50169e6        3 months ago        163 MB
alpine                     latest              3fd9065eaf02        9 months ago        4.15 MB
masami@saga:~$ sudo docker run -it docker.io/arm64v8/fedora /bin/bash
[root@54034b092275 /]# uname -a
Linux 54034b092275 4.19.0-rc7-test+ #102 SMP Mon Oct 8 11:06:09 JST 2018 aarch64 aarch64 aarch64 GNU/Linux

ホストでunameコマンド使ったらx86_64となります。

masami@saga:~$ uname -a
Linux saga 4.19.0-rc7-test+ #102 SMP Mon Oct 8 11:06:09 JST 2018 x86_64 x86_64 x86_64 GNU/Linux

クロスビルド環境の作成

では、本題のクロスビルド環境を作ってみます。今回はソースはホストで編集し、docker-compose upでmakeとmake testを動かすようにします。ソースはDockerfileが有るディレクトリにsrc/というディレクトリを作ってそこに置きましょう。

Dockerfileはこうなります。とりあえずCMDでmakeしないでbashを起動させるだけにします。

FROM docker.io/arm64v8/fedora

RUN mkdir /src && \
dnf install -y gcc make file

WORKDIR /src

CMD /bin/bash

docker-compose.ymlはこうなります。selinuxが有効なのでvolumesの時にzオプションを付けています。commandのところでCMDを上書きしてmakeコマンドを実行するようにしました。

version: '3'

services:
  builder:
    build: .
    image: aarch64-cross-builder
    hostname: fedora-aarch64
    volumes:
      - ./src:/src:z
    command: make all

実行

masami@saga:~/codes/arm-cross-build$ sudo docker-compose up
Creating arm-cross-build_builder_1 ... done
Attaching to arm-cross-build_builder_1
builder_1  | rm -f hello
builder_1  | cc hello.c -o hello
builder_1  | file ./hello
builder_1  | ./hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=39cbef1ba26dcae61413b891b9bd4e9d202a5083, not stripped
builder_1  | ./hello
builder_1  | Hello, World
arm-cross-build_builder_1 exited with code 0

まとめ

Fedora 29ならかなり簡単にクロスビルド環境を作ることが出来ますね( ´∀`)bグッ! qemuで完全なエミュレーションをしてももちろんOKですが、ユーザーランドのアプリならdockerでも良さげですね。

( ´ー`)フゥー...

ARMで学ぶ アセンブリ言語入門

ARMで学ぶ アセンブリ言語入門

linux: seq_fileの使い方めも

sysfs、debugfs、procfs等でファイルを作ってユーザーランドからreadするときにseq_file構造体を使う方法が有るけど使ったことなかったのでめもです。

コード

こんな感じです。

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>

MODULE_DESCRIPTION("seq file test");
MODULE_AUTHOR("masami256");
MODULE_LICENSE("GPL");

#define SEQ_FILE_TEST_DATA_ARRAY_SIZE 8

struct seq_file_test_data {
    char s[16];
};

static struct seq_file_test_data data_array[SEQ_FILE_TEST_DATA_ARRAY_SIZE];

static struct dentry *seq_file_test_file;

static void *seq_file_get_next_pos(loff_t *pos)
{
    if (*pos > SEQ_FILE_TEST_DATA_ARRAY_SIZE)
        return NULL;

    return data_array + *pos;
}

static void *seq_file_test_start(struct seq_file *m, loff_t *pos)
{
    return seq_file_get_next_pos(pos);
}

static void seq_file_test_stop(struct seq_file *m, void *v)
{
}

static void *seq_file_test_next(struct seq_file *m, void *v, loff_t *pos)
{
    (*pos)++;

    return seq_file_get_next_pos(pos);
}

static int seq_file_test_show(struct seq_file *m, void *v)
{
    struct seq_file_test_data *p = v;

    seq_printf(m, "%s", p->s);
    return 0;
}

static struct seq_operations seq_file_test_seq_ops = {
    .start = seq_file_test_start,
    .stop = seq_file_test_stop,
    .next = seq_file_test_next,
    .show = seq_file_test_show,
};

static int seq_file_test_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &seq_file_test_seq_ops);
}

static struct file_operations seq_file_test_fops = {
    .owner = THIS_MODULE,
    .open = seq_file_test_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = seq_release,
};

static int seq_file_test_init(void)
{
    int i;
    pr_info("%s start\n", __func__);

    for (i = 0; i < SEQ_FILE_TEST_DATA_ARRAY_SIZE; i++) {
        struct seq_file_test_data *p = &data_array[i];
        sprintf(p->s, "test data %d\n", i);
    }

    seq_file_test_file = debugfs_create_file("seq_file_test", 0444,
                        NULL, NULL,
                        &seq_file_test_fops);

    if (IS_ERR(seq_file_test_file))
        return PTR_ERR(seq_file_test_file);

    return 0;
}

static void seq_file_test_exit(void)
{
    pr_info("%s bye\n", __func__);
    debugfs_remove(seq_file_test_file);
}

module_init(seq_file_test_init);
module_exit(seq_file_test_exit);

実行例

masami@kerntest:~/seq_file_test$ sudo cat /sys/kernel/debug/seq_file_test
test data 0
test data 1
test data 2
test data 3
test data 4
test data 5
test data 6
test data 7
masami@kerntest:~/seq_file_test$

動作の概要

seq_file構造体はinclude/linux/seq_file.hでこんな感じで定義されてます。

struct seq_operations {
    void * (*start) (struct seq_file *m, loff_t *pos);
    void (*stop) (struct seq_file *m, void *v);
    void * (*next) (struct seq_file *m, void *v, loff_t *pos);
    int (*show) (struct seq_file *m, void *v);
};

show()がデータを表示するための関数です。start()がファイル読み込み時に呼ばれて、データがあればデータのポインタを返します。続いてnext()が呼ばれていってNULLが返るまでは処理が続く感じです。 show()のほうは引数のvにstart()とかnext()で返したポインタが渡ってきます。なのでseq_file_test_show()ではseq_printf()で一つのデータの内容を表示させてるだけなのにcatはちゃんとすべてのデータが読めるってことですね。 ファイルを作成したときはfile_operations構造体でファイルのopen、readなどの関数を設定しますがseq_file構造体を使ってreadの処理を行う場合はopen()を行う関数の方でseq_open()を呼びます。seq_open()に渡すのはfile_operations構造体のopen()に渡されるfile構造体と、seq_file用のseq_operations構造体です。

分かってみればそんなに難しいことはないのでこれからは利用していきたいですね( ´∀`)bグッ!

systemtapでlivepatchする

CVE-2018-14634(Integer overflow in Linux's create_elf_tables function)でRed Hatのbugzillaを見ていて軽減策としてsystemtapでlivepatch的なことをしていて面白いな〜って思ったのでめもです。

環境はFedora 28で、カーネルのバージョンは4.18.9-200です。systemtapを使っているのでkernel-debuginfoパッケージも入れてます。

utsname構造体のnodenameを書き換えてホスト名を変えてみたいと思います。まずは初期状態はこうです。kerntestがnodenameになります。

masami@kerntest:~$ uname -a                                                                                                                                                                                        
Linux kerntest 4.18.9-200.fc28.x86_64 #1 SMP Thu Sep 20 02:43:23 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

cのコードでuname(2)を実行するものを作ります。

#include <stdio.h>
#include <sys/utsname.h>

int main(int argc, char **argv)
{
        struct utsname utsname = { 0 };

        printf("call uname(2) to execute livepatch via systemtap\n");

        if (uname(&utsname)) {
                perror("uname");
                return -1;
        }

        printf("patched nodename: %s\n", utsname.nodename);
        return 0;
}

まずはこれを-oオプション無しにコンパイルしてa.outで実行した場合。

masami@kerntest:~$ ./a.out
call uname(2) to execute livepatch via systemtap
patched nodename: kerntest

なにも起きませんね。

次に以下のsystemtapスクリプトを実行します。 uname(2)で呼ばれる__do_sys_newuname()にprobeを設定しています。 このスクリプトの内容としてはuname(2)を実行したプロセス名がdo-livepatchならそのプロセスが所属するuts名前空間に設定されてるnodenameの値を書き換えてます。

#!/usr/bin/env stap


%{
#include <linux/sched.h>
#include <linux/string.h>
%}

function set_dummy_uname:long()
%{
        struct task_struct *p = current;
        if (strcmp(p->nsproxy->uts_ns->name.nodename, "livepatched")) {
                if (unlikely(!strcmp(p->comm, "do-livepatch"))) {
                        memset(p->nsproxy->uts_ns->name.nodename, 0, sizeof(p->nsproxy->uts_ns->name.nodename));
                        strcpy(p->nsproxy->uts_ns->name.nodename, "livepatched");
                }
        }

        STAP_RETURN(0);

%}

probe begin {
        printf("start\n")
}


probe kernel.function("__do_sys_newuname") {
        set_dummy_uname()
}

probe end {
        printf("Done\n")
}

上記のコードを実行します。

masami@kerntest:~$ sudo stap -v -g test.stp                                                                                                                                                                       
Pass 1: parsed user script and 474 library scripts using 157272virt/50300res/8740shr/41432data kb, in 90usr/20sys/106real ms.                                                                                     
Pass 2: analyzed script: 3 probes, 1 function, 1 embed, 0 globals using 211816virt/105600res/9628shr/95976data kb, in 750usr/30sys/777real ms.                                                                    
Pass 3: translated to C into "/tmp/stapmurOiN/stap_134d2448e08da1a0409648297476fac2_1549_src.c" using 211816virt/105912res/9940shr/95976data kb, in 10usr/0sys/6real ms.                                          
Pass 4: compiled C into "stap_134d2448e08da1a0409648297476fac2_1549.ko" in 1990usr/740sys/2506real ms.
Pass 5: starting run.
start

そして、cコードの方をdo-livepatchという名前のバイナリでコンパイルして実行するとnodenameが書き換えられます。これ以降はuname(2)は変更した名前が返るようになってます。

masami@kerntest:~$ ./do-livepatch 
call uname(2) to execute livepatch via systemtap
patched nodename: livepatched
masami@kerntest:~$ uname -a
Linux livepatched 4.18.9-200.fc28.x86_64 #1 SMP Thu Sep 20 02:43:23 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
masami@kerntest:~$ uname -n
livepatched

今まではすでにログインしてたのでbashプロンプトのほうはホスト名変わってませんが、新規に接続するとホスト名が新しい名前になってます。

masami@saga:~$ ssh kerntest 
Last login: Thu Sep 27 02:05:41 2018 from 192.168.122.1
masami@livepatched:~$ 

というわけで、systemtapでlivepatch的なことをする実験でした( ´ー`)フゥー...

unikernelのruntimejsを試してみる

runtime.jsを試してみたのでメモ書きです。

f:id:masami256:20180909150234p:plain

runtme.jsはunikernelの一つでカーネルにv8のエンジンが組み込まれてて、JavaScriptの実行がサポートされています。runtime.jsのサンプルで、eshttpを使ったwebアプリのサンプルが有ったのでこれを使って動かしてみます。 今回使ったjsのコードやDockerfileなどは↓にあります。

github.com

実行するコードは以下の内容でnodeコマンドでそのまま動かすことができるようになってます。

const eshttp = require('eshttp');
const server = new eshttp.HttpServer();
const response = new eshttp.HttpResponse(200, { 'server': 'runtimejs' }, 'Hello World!');

server.onrequest = request => {
  request.respondWith(response);
};

server.listen(9000);
console.log('listening to port 9000');

今回の計測はやり方として良くはないんだけど、手頃な環境がなかったのですべて1つのマシン上で行ってます。ベンチマークの実行にはabコマンドを使ってます。 hostマシンはFedora28です。

実行方法は以下の3パターンを試しました。

  1. ホストローカルで実行
  2. runteime.jsで実行
  3. Fedora 28の仮想マシン上のdockerで実行

まずはdockerもruntime.jsも使わずにローカルでindex.jsを実行した場合。

Test 1: Requests per second:    3031.68 [#/sec] (mean)
Test 2: Requests per second:    4135.48 [#/sec] (mean)
Test 3: Requests per second:    4542.56 [#/sec] (mean)
Test 4: Requests per second:    3834.94 [#/sec] (mean)
Test 5: Requests per second:    4983.80 [#/sec] (mean)
Test 6: Requests per second:    4654.19 [#/sec] (mean)
Test 7: Requests per second:    4711.87 [#/sec] (mean)
Test 8: Requests per second:    5130.57 [#/sec] (mean)
Test 9: Requests per second:    5187.80 [#/sec] (mean)
Test 10: Requests per second:    5351.89 [#/sec] (mean)

runtime.jsで実行した場合。

Test 1: Requests per second:    2577.45 [#/sec] (mean)
Test 2: Requests per second:    2562.92 [#/sec] (mean)
Test 3: Requests per second:    2494.01 [#/sec] (mean)
Test 4: Requests per second:    2558.20 [#/sec] (mean)
Test 5: Requests per second:    2802.85 [#/sec] (mean)
Test 6: Requests per second:    2705.70 [#/sec] (mean)
Test 7: Requests per second:    2850.22 [#/sec] (mean)
Test 8: Requests per second:    2901.33 [#/sec] (mean)
Test 9: Requests per second:    2505.95 [#/sec] (mean)
Test 10: Requests per second:    2331.00 [#/sec] (mean)

fedora 28の仮想環境の上でdockerで実行した場合。

Test 1: Requests per second:    2086.77 [#/sec] (mean)
Test 2: Requests per second:    3054.37 [#/sec] (mean)
Test 3: Requests per second:    3071.91 [#/sec] (mean)
Test 4: Requests per second:    2478.68 [#/sec] (mean)
Test 5: Requests per second:    2888.59 [#/sec] (mean)
Test 6: Requests per second:    2938.15 [#/sec] (mean)
Test 7: Requests per second:    3135.09 [#/sec] (mean)
Test 8: Requests per second:    3611.41 [#/sec] (mean)
Test 9: Requests per second:    3088.80 [#/sec] (mean)
Test 10: Requests per second:    3299.46 [#/sec] (mean)

この結果だとdockerのほうが良い感じの結果になってます。qemuの実行方法やvm環境で動くカーネルも違うので一概にはどっちが良いとか言えない面もありすね。

runtime.jsのqemuコマンドライン

masami   19666 28.3  0.2 1110156 139224 pts/1  Sl+  14:20   0:01 qemu-system-x86_64 -m 512 -smp 1 -s -kernel /home/masami/.runtime/runtime.2064 -initrd .initrd -net nic,model=virtio,macaddr=1a:46:0b:ca:bc:7c -net user,net=192.168.76.0/24,dhcpstart=192.168.76.9,hostfwd=udp::9000-:9000,hostfwd=tcp::9000-:9000 -nographic -monitor none -enable-kvm -no-kvm-irqchip -serial stdio

virt-managerのほうのqemuコマンドライン

qemu     11627  0.9  1.4 10844960 974920 ?     Sl   12:43   0:52 /usr/bin/qemu-system-x86_64 -machine accel=kvm -name guest=fedora-labo,debug-threads=on -S -object secret,id=masterKey0,format=raw,file=/var/lib/libvirt/qemu/domain-1-fedora-labo/master-key.aes -machine pc-i440fx-2.7,accel=kvm,usb=off,vmport=off,dump-guest-core=off -cpu Haswell-noTSX-IBRS,vme=on,ss=on,vmx=on,f16c=on,rdrand=on,hypervisor=on,arat=on,tsc_adjust=on,ssbd=on,xsaveopt=on,pdpe1gb=on,abm=on -m 8192 -realtime mlock=off -smp 1,sockets=1,cores=1,threads=1 -uuid 239af4a6-ef28-4fa8-aacb-13b2588b0c21 -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/domain-1-fedora-labo/monitor.sock,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc,driftfix=slew -global kvm-pit.lost_tick_policy=delay -no-hpet -no-shutdown -global PIIX4_PM.disable_s3=1 -global PIIX4_PM.disable_s4=1 -boot strict=on -device ich9-usb-ehci1,id=usb,bus=pci.0,addr=0x6.0x7 -device ich9-usb-uhci1,masterbus=usb.0,firstport=0,bus=pci.0,multifunction=on,addr=0x6 -device ich9-usb-uhci2,masterbus=usb.0,firstport=2,bus=pci.0,addr=0x6.0x1 -device ich9-usb-uhci3,masterbus=usb.0,firstport=4,bus=pci.0,addr=0x6.0x2 -device virtio-serial-pci,id=virtio-serial0,bus=pci.0,addr=0x5 -drive file=/var/lib/libvirt/images/fedora-labo.qcow2,format=qcow2,if=none,id=drive-virtio-disk0 -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x7,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1 -drive if=none,id=drive-ide0-0-0,readonly=on -device ide-cd,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0 -netdev tap,fd=25,id=hostnet0,vhost=on,vhostfd=27 -device virtio-net-pci,netdev=hostnet0,id=net0,mac=52:54:00:95:01:e7,bus=pci.0,addr=0x3 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -chardev socket,id=charchannel0,path=/var/lib/libvirt/qemu/channel/target/domain-1-fedora-labo/org.qemu.guest_agent.0,server,nowait -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=org.qemu.guest_agent.0 -chardev spicevmc,id=charchannel1,name=vdagent -device virtserialport,bus=virtio-serial0.0,nr=2,chardev=charchannel1,id=channel1,name=com.redhat.spice.0 -device usb-tablet,id=input0,bus=usb.0,port=1 -spice port=5900,addr=127.0.0.1,disable-ticketing,image-compression=off,seamless-migration=on -device qxl-vga,id=video0,ram_size=67108864,vram_size=67108864,vram64_size_mb=0,vgamem_mb=16,max_outputs=1,bus=pci.0,addr=0x2 -device intel-hda,id=sound0,bus=pci.0,addr=0x4 -device hda-duplex,id=sound0-codec0,bus=sound0.0,cad=0 -chardev spicevmc,id=charredir0,name=usbredir -device usb-redir,chardev=charredir0,id=redir0,bus=usb.0,port=2 -chardev spicevmc,id=charredir1,name=usbredir -device usb-redir,chardev=charredir1,id=redir1,bus=usb.0,port=3 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x8 -msg timestamp=on

runtime.jsのほうはqemuのオプションを調整することで結果がよくなるかも?

以下はめも runtime.jsは--kvmオプションを使わないとkvmが有効になりません。--nographicオプションを付けるとqemuGUIは表示しません。

--kernelでqemuに渡すカーネルを渡せるようだけど、initrdも作る必要ありそう。

( ´ー`)フゥー...

Docker/Kubernetes 実践コンテナ開発入門

Docker/Kubernetes 実践コンテナ開発入門