読者です 読者をやめる 読者になる 読者になる

明日にはでっかい太陽が昇るかもしれません。

「覚悟」とは!! 暗闇の荒野に!!進むべき道を切り開く事だッ!

バックトレース in ARM(Qemu)

以前、挫折していた Linux 環境での C のバックトレース出力について、改めてトライしてみました。

ちなみに、ホスト環境(x86)ではそのものの backtrace()backtrace_symbols() といったAPIが利用できるけど、 buildroot で構築したクロスビルド環境には execinfo.h がなかったので、自力でバックトレースを取ることに。

業務で使用するログ機能の強化を考えていたのですが、スタックトレースは必須だと思っていたので、 Qemu 環境ではありますが動作して良かったです。

同じプログラムが業務で使用している開発ボードでも動作するか確認してみよう。

/**     @file           backtrace.c
 *      @brief          porting backtrace.
 *      @sa                     http://stackoverflow.com/questions/2536021/any-porting-available-of-stacktrace-for-uclibc/2536136#2536136
 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* for Dl_info */
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <inttypes.h>
#include <limits.h>
#include <dlfcn.h>
#include <errno.h>

#include "log.h"


extern void *__libc_stack_end;


struct backtrace_frame {
        void *fp;
        void *sp;
        void *lr;
        void *pc;
};


#define lengthof(array) (sizeof(array)/sizeof((array)[0]))


size_t Backtrace(void **frames, size_t size)
{
        void *top_frame_p;
        void *current_frame_p;
        struct backtrace_frame *frame_p;
        size_t frame_count;

        top_frame_p     = __builtin_frame_address(0);
        current_frame_p = top_frame_p;
        frame_p         = (struct backtrace_frame *) ((void **) current_frame_p - 3);
        frame_count     = 0;

        if ((current_frame_p != NULL)
            && (current_frame_p > (void *) &frame_count)
            && (current_frame_p < __libc_stack_end)) {

                while ((frame_count < size)
                       && (current_frame_p != NULL)
                           && (current_frame_p > (void *) &frame_count)
                           && (current_frame_p < __libc_stack_end)) {

                        frame_p = (struct backtrace_frame *) ((void **) current_frame_p - 3);
                        frames[frame_count] = frame_p->lr;
                        ++frame_count;
                        current_frame_p = frame_p->fp;
                }
        }

        return frame_count;
}

void PrintBacktrace(void)
{
        void *stack[128] = { NULL };
        Dl_info info;
        int i;

        if (Backtrace(stack, lengthof(stack)) == 0) {
                fprintf(stderr, "not found backtrace...\n");
                return;
        }
        for (i = 0; stack[i] != NULL; ++i) {
                if (dladdr(stack[i], &info) == 0) {
                        fprintf(stderr, "  unknown(???+???)[%p]\n", stack[i]);
                } else {
                        fprintf(stderr, "  %s(%s+%p)[%p]\n", info.dli_fname, info.dli_sname, stack[i] - info.dli_saddr, stack[i]);
                }
        }

        return;
}

int bar(void)
{
        printf("%s(%d) %s: called\n", __FILE__, __LINE__, __func__);
        PrintBacktrace();
        return 0;
}

int foo(void)
{
        printf("%s(%d) %s: called\n", __FILE__, __LINE__, __func__);
        bar();
        return 0;
}

int main(int argc, char **argv)
{
        foo();
        return 0;
}
# makefile for backtrace.

TARGET := backtrace

ARCH := arm
ifeq ($(ARCH),arm)
CROSS_COMPILE := arm-buildroot-linux-uclibcgnueabi-
else
CROSS_COMPILE :=
endif
CC := $(CROSS_COMPILE)gcc
CFLAGS := -g -O0 -I.
LDFLAGS := -rdynamic -ldl
ifeq ($(ARCH),arm)
LDFLAGS += -mapcs-frame
else
LDFLAGS += -fno-omit-frame-pointer
endif

all:
        $(CC) -std=c99 $(CFLAGS) -o $(TARGET) $(TARGET).c log.c $(LDFLAGS)

clean:
        rm -rf $(TARGET)

出力結果は以下の通り。

# ./backtrace
backtrace.c(101) foo: called
backtrace.c(94) bar: called
  /root/backtrace(PrintBacktrace+0x2c)[0x88b4]
  /root/backtrace(bar+0x24)[0x898c]
  /root/backtrace(foo+0x24)[0x89c4]
  /root/backtrace(main+0x10)[0x8688]
  /lib/libc.so.1(__uClibc_main+0x1fc)[0xb6f0172c]