qemuでバックトレースを出力する

# サマリ

qemuを改造してバックトレースを出力するようにします
自分でビルドする人向けです

gdbでやれという感じですがちょこちょこ止めるのが面倒だったので
怪しそうなところに仕込んで実行してから後でじっくり見ようという

あと、最適化しないと`qemu-system-aarch64`がすごく重く
最適化するとデバッグ時の位置がずれるという問題もありました

# 環境

- fedora40
- binutils-devel 

# 事前準備

dladdrだと取れない関数が多かったので
デバッグ情報から出力させます

```shell
sudo yum install binutils-devel
```

## `pkg-config`
`pkg-config`に対応しているとqemu側の設定が楽です
`binutils-devel`をインストールしただけだと
以下のように`.pc`ファイルがインストールされないようなので

```shell
$ rpm -ql binutils binutils-devel | grep -E "pc$"
$
```
自分で作ります。

`sudo vim /usr/lib64/pkgconfig/bfd.pc`
などで以下のように作成します。

```
$ cat /usr/lib64/pkgconfig/bfd.pc
prefix=/usr
exec_prefix=${prefix}
symbol_prefix=
libdir=/usr/lib64
sharedlibdir=${libdir}
includedir=${prefix}/include

Name: bfd
Description: BFD and opcodes static and dynamic libraries and header files
Version: 2.41

Requires:
Libs: -L${libdir} -L${sharedlibdir} -lbfd
Cflags: -I${includedir}
```

# qemuコードの修正

## meson.build

bfdを含められるようにします。

```diff
diff --git a/meson.build b/meson.build
index a1e51277b0..a6bccbbeb1 100644
--- a/meson.build
+++ b/meson.build
@@ -1008,6 +1008,7 @@ if not get_option('pixman').auto() or have_system or have_tools
 endif
 
 zlib = dependency('zlib', required: true)
+bfd = dependency('bfd', required: true)
 
 libaio = not_found
 if not get_option('linux_aio').auto() or have_block
```

## xx/meson.build
どこでも良いのですがデバッグコードをコンパイルする様に変更します

自分はui周りを見たかったので`ui/meson.build`にしました
こんな感じでデバッグコードを追加し、bfdライブラリをリンクするようにします
```diff
diff --git a/ui/meson.build b/ui/meson.build
index 28c7381dd1..bbe28925a3 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -18,7 +18,8 @@ system_ss.add(files(
   'ui-hmp-cmds.c',
   'ui-qmp-cmds.c',
   'util.c',
-))
+  'find_out.c',
+), bfd)
 system_ss.add(when: pixman, if_true: files('console-vc.c'), if_false: files('console-vc-stubs.c'))
 if dbus_display
   system_ss.add(files('dbus-module.c'))
```

## デバッグコード

ファイル名などは適当に決めてください

### cファイル

1. `backtrace_symbols`でファイル名とアドレスを取得します
2. `function_name`で文字列を整形し、ファイル名とアドレスを分離、取得します
3. `find_function_name`は辞書登録して同じファイルの再検索をしないようにします
4. `_find_function_name`で該当ファイルを開き、アドレスに該当する関数を取得します

`debug_printf`でファイル出力します

<details><summary>コード</summary>


```c
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <bfd.h>
#include <search.h>

#include "find_out.h"

#define QEMU_LOG "/tmp/qemu.log"

static FILE *debug_fp;
static void find_function_name(const char *path, bfd_vma address, char *buf);
static FILE *debug_file(void)
{
    if (debug_fp)
        return debug_fp;

    debug_fp = fopen(QEMU_LOG, "w");
    return debug_fp;
}

__attribute__((format(gnu_printf, 1, 2))) int debug_printf(const char *s, ...)
{
    FILE *fp = debug_file();
    if (!fp)
        return -1;
    va_list ap;
    va_start(ap, s);
    vfprintf(fp, s, ap);
    va_end(ap);
    fflush(fp);
    return 0;
}

static char _func_name[256];
static char *function_name(char *_s)
{
    char s[256];
    char *fname = s;
    char *saddr = NULL;
    uint64_t addr;
    bool done = false;
    int i;
    _func_name[0] = '\0';

    sprintf(s, "%s", _s);
    for (i = 0; s[i]; i++)
    {
        switch (s[i])
        {
        case '(':
            saddr = &s[i + 2];
            s[i] = '\0';
            break;
        case ')':
            s[i] = '\0';
            done = true;
            break;
        }
        if (done)
            break;
    }

    addr = strtoul(saddr, NULL, 16);

    find_function_name(fname, addr, _func_name);
    return _func_name;
}

void print_stack_trace(void)
{
    void *buffer[100];
    int nptrs;
    Dl_info info;
    char *p;
    size_t diff;

    nptrs = backtrace(buffer, sizeof(buffer) / sizeof(void *));
    char **symbols = backtrace_symbols(buffer, nptrs);
    for (int i = 1; i < nptrs; i++)
    {
        p = NULL;
        diff = -1;
        if (dladdr(buffer[i], &info) && info.dli_sname)
        {
            p = (char *)info.dli_sname;
            diff = (char *)buffer[i] - (char *)info.dli_saddr;
            debug_printf("%-3d %p %s + %zd\n", i, buffer[i], p, diff);
        }
        else if (symbols[i] && strlen(symbols[i]))
        {
            p = function_name(symbols[i]);
            debug_printf("%-3d %p %s", i, buffer[i], p);
        }
        else
        {
            debug_printf("%-3d %p\n", i, buffer[i]);
        }
    }
}

typedef struct
{
    char *name;
    void *addr;
} symbol_t;

typedef struct
{
    long symcount;
    symbol_t *s;
} symbols_t;

#define max(a, b) ((a) < (b) ? (b) : (a))
static size_t padding8(size_t l) { return (l + 7) & ~7; }
static int compare_symbol(const void *pa, const void *pb)
{
    symbol_t *a = (symbol_t *)pa;
    symbol_t *b = (symbol_t *)pb;
    return (intptr_t)a->addr - (intptr_t)b->addr;
}

static bool initialized = false;
static symbols_t *_find_function_name(const char *binary_path, bfd_vma address, char *buf, symbols_t *symbols);
static void find_function_name(const char *path, bfd_vma address, char *buf)
{
    ENTRY item;
    ENTRY *found;
    symbols_t *symbols;

    if (!path) return;

    if (!initialized)
    {
        initialized = true;
        hcreate(20);
    }

    item.key = strdup(path);
    item.data = NULL;

    found = hsearch(item, FIND);
    if (!found)
    {
        symbols = _find_function_name(path, address, buf, NULL);
        item.data = (void *)symbols;
        found = hsearch(item, ENTER);
    }
    else
    {
        symbols = _find_function_name(path, address, buf, (symbols_t *)found->data);
        free(item.key);
    }
}

static symbols_t *_find_function_name(const char *binary_path, bfd_vma address, char *buf, symbols_t *symbols)
{
    bfd *abfd;
    char **matching;
    asymbol **syms;
    long size;
    unsigned int symindex;
    asection *section;

    if (!symbols)
    {
        long symcount;
        bfd_init();

        abfd = bfd_openr(binary_path, NULL);
        if (!abfd)
        {
            bfd_perror("bfd_openr");
            return NULL;
        }

        if (!bfd_check_format_matches(abfd, bfd_object, &matching))
        {
            bfd_perror("bfd_check_format_matches");
            bfd_close(abfd);
            return NULL;
        }

        size = bfd_get_symtab_upper_bound(abfd);
        if (size < 0)
        {
            bfd_perror("bfd_get_symtab_upper_bound");
            bfd_close(abfd);
            return NULL;
        }

        syms = malloc(size);

        symcount = bfd_canonicalize_symtab(abfd, syms);
        if (symcount < 0)
        {
            bfd_perror("bfd_canonicalize_symtab");
            free(syms);
            bfd_close(abfd);
            return NULL;
        }

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

        size_t maxlen = 0;
        for (symindex = 0; symindex < symcount; symindex++)
        {
            asymbol *sym = syms[symindex];
            size_t len = strlen(bfd_asymbol_name(sym));
            maxlen = max(maxlen, len);
            // printf("%08x %s\n", sym->section->vma + sym->value, bfd_asymbol_name(sym));
        }
        maxlen = padding8(maxlen);
        symbols = malloc(sizeof(symbols_t));
        symbols->s = malloc((sizeof(symbol_t) + maxlen) * symcount);
        symbols->symcount = symcount;
        for (int i = 0; i < symbols->symcount; i++)
        {
            asymbol *sym = syms[i];
            if (sym->section != section)
                continue;
            symbols->s[i].addr = (void *)(sym->section->vma + sym->value);
            symbols->s[i].name = ((void *)((char *)&symbols->s[symcount]) + maxlen * i);
            sprintf(symbols->s[i].name, "%s", bfd_asymbol_name(sym));
        }
        free(syms);
        bfd_close(abfd);

        qsort(symbols->s, symcount, sizeof(symbol_t), compare_symbol);
    }
    for (int i = 1; symbols->symcount; i++)
    {
        if ((intptr_t)symbols->s[i].addr > (intptr_t)address)
        {
            // sprintf(buf, "%08x %s(+%d)\n", (intptr_t)symbols[i-1].addr, symbols[i-1].name, (intptr_t)address - (intptr_t)symbols[i-1].addr);
            sprintf(buf, "%s(+%lx)\n", symbols->s[i - 1].name, (intptr_t)address - (intptr_t)symbols->s[i - 1].addr);
            break;
        }
    }
    return symbols;
}

```

</details>

### hファイル

特になし

<details><summary>コード</summary>

```c
#ifndef __FIND_OUT_H__
#define __FIND_OUT_H__

#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)

#define ERR_RETn(c)            \
    do                         \
    {                          \
        if (c)                 \
            goto error_return; \
    } while (0)

#define ERR_RETp(c, p)         \
    do                         \
    {                          \
        if (c)                 \
        {                      \
            p;                 \
            goto error_return; \
        }                      \
    } while (0)

#define d(s, ...)                                                                           \
    do                                                                                      \
    {                                                                                       \
        debug_printf("%s(%d) %s " s "\n", __FILENAME__, __LINE__, __func__, ##__VA_ARGS__); \
    } while (0)

#define ERR_RET(c, s, ...)                                                                      \
    do                                                                                          \
    {                                                                                           \
        if (c)                                                                                  \
        {                                                                                       \
            debug_printf("%s(%d) %s " s "\n", __FILENAME__, __LINE__, __func__, ##__VA_ARGS__); \
            goto error_return;                                                                  \
        }                                                                                       \
    } while (0)

int debug_printf(const char *s, ...) __attribute__((format(gnu_printf, 1, 2)));
void print_stack_trace(void);

#endif

```

</details>

## 呼び出し
ヘッダをincludeしてコールするだけです。

例えば以下のようにします。

```diff
diff --git a/ui/sdl2.c b/ui/sdl2.c
index 98ed974371..174497efa9 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -35,6 +35,8 @@
 #include "ui/win32-kbd-hook.h"
 #include "qemu/log.h"
 
+#include "find_out.h"
+
 static int sdl2_num_outputs;
 static struct sdl2_console *sdl2_console;
 
@@ -99,6 +101,8 @@ void sdl2_window_create(struct sdl2_console *scon)
     }
 #endif
 
+d("");
+print_stack_trace();
     scon->real_window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED,
                                          SDL_WINDOWPOS_UNDEFINED,
                                          surface_width(scon->surface),
```

# 出力例

こんな感じで出力されます
```
sdl2.c(104) sdl2_window_create 
1   0x555555cbdd73 sdl2_window_create(+73)
2   0x555555cbed98 sdl2_gl_switch(+d8)
3   0x555555924442 displaychangelistener_gfx_switch(+22)
4   0x555555925800 register_displaychangelistener(+120)
5   0x555555cbd94b sdl2_display_init(+19b)
6   0x555555c5089f qemu_init(+356f)
7   0x55555591eb49 main(+9)
8   0x7ffff641e088 __libc_start_call_main(+78)
9   0x7ffff641e14b __libc_start_main + 139
10  0x55555591fb25 _start(+25)
sdl2.c(104) sdl2_window_create 
1   0x555555cbdd73 sdl2_window_create(+73)
2   0x555555cbed98 sdl2_gl_switch(+d8)
3   0x555555924442 displaychangelistener_gfx_switch(+22)
4   0x555555925f4d dpy_gfx_replace_surface(+8d)
5   0x555555cbfcd0 virtio_gpu_do_set_scanout(+160)
6   0x555555cc2a17 virtio_gpu_simple_process_cmd(+897)
7   0x555555cc3e79 virtio_gpu_process_cmdq(+e9)
8   0x555556223411 aio_bh_call(+41)
9   0x5555562235cb aio_bh_poll(+ab)
10  0x55555620cd4e aio_dispatch(+1e)
11  0x55555622326e aio_ctx_dispatch(+e)
12  0x7ffff75c1e8c 13  0x7ffff75c2155 g_main_context_dispatch + 37
14  0x555556224998 main_loop_wait(+208)
15  0x555555c49dd0 qemu_main_loop(+1a0)
16  0x55555616b476 qemu_default_main(+6)
17  0x7ffff641e088 __libc_start_call_main(+78)
18  0x7ffff641e14b __libc_start_main + 139
19  0x55555591fb25 _start(+25)
...
```