ITコンサルの日常

ITコンサル会社に勤務する普通のITエンジニアの日常です。

AOPっぽいC言語(関数へのenter/exitをフックする)

BINARY HACKS p300

まずは写経。

taka@ubuntu:~$ more aop.c
#include 

__attribute__((no_instrument_function))
void __cyg_profile_func_enter(void *func_address, void *call_site) {
        printf("function start %p %p\n", func_address, call_site);
}

__attribute__((no_instrument_function))
void __cyg_profile_func_exit(void *func_address, void *call_site) {
        printf("function end %p %p\n", func_address, call_site);
}

int add(int a, int b)
{
        return a + b;
}

int main()
{
        int result = add(3, 4);
        printf("result = %d\n", result);
}
taka@ubuntu:~$ cc -o aop aop.c -finstrument-functions
taka@ubuntu:~$ ./aop
function start 0x8048441 0xb7ec8ea2
function start 0x8048404 0x8048484
function end 0x8048404 0x8048484
result = 7
function end 0x8048441 0xb7ec8ea2
taka@ubuntu:~$

わりとさくっと動きます。


ただ、コールされた関数のアドレスが分かってもあまり面白くはない(というか役に立たない)ので、どうにかしてアドレスから関数名が取り出せないものかと、BINARY HACKSをひっくり返してみる。
と、p254に「libbfdでシンボルの一覧を取得する」ってのがあり、これが使えそうということで試してみる。


またもや写経。
その前に、apt-get install binutils-devしてます。

taka@ubuntu:~$ more bfdtest.c
#include 
#include 
#include 
#include 

void dump_symbols(const char *filename) {
        bfd *abfd;
        asymbol *store;
        char *p;
        void *minisyms;
        int symnum, i;
        size_t size;
        int dyn = 0;
        int ret;

        abfd = bfd_openr(filename, NULL);
        assert(abfd);
        ret = bfd_check_format(abfd, bfd_object);
        assert(ret);

        if(!(bfd_get_file_flags(abfd) & HAS_SYMS)) {
                assert(bfd_get_error() == bfd_error_no_error);
                bfd_close(abfd);
                return;
        }

        store = bfd_make_empty_symbol(abfd);

        symnum = bfd_read_minisymbols(abfd, dyn, &minisyms, &size);
        assert(symnum >= 0);

        p = (char *)minisyms;
        for(i = 0; i < symnum; i++)
        {
                asymbol *sym = bfd_minisymbol_to_symbol(abfd, dyn, p, store);
                const char *name = bfd_asymbol_name(sym);
                int value = bfd_asymbol_value(sym);
                printf("%08X %s\n", value, name);
                p += size;
        }

        free(minisyms);
        bfd_close(abfd);
}

int main(int argc, char *argv[]) {
        dump_symbols("./aop");

        return 0;
}
taka@ubuntu:~$ cc -o bfdtest bfdtest.c -lbfd
taka@ubuntu:~$ ./bfdtest
... 省略 ...
080482D8 _init
08048404 add
08048320 _start
080484B0 __libc_csu_init
080496DC __bss_start
080483C0 __cyg_profile_func_enter
08048441 main
00000000 __libc_start_main@@GLIBC_2.0
080496D0 data_start
00000000 printf@@GLIBC_2.0
08048578 _fini
080496DC _edata
08048368 __i686.get_pc_thunk.bx
080496E0 _end
0804859C _IO_stdin_used
080496D0 __data_start
00000000 _Jv_RegisterClasses
00000000 __gmon_start__
taka@ubuntu:~$

というわけで、なんとなく関数のアドレスと関数名の関連が取れている模様。


これをさっきのaopっぽいやつと合わせワザしてみる。

taka@ubuntu:~$ more aop3.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <bfd.h>

__attribute__((no_instrument_function))
void __cyg_profile_func_enter(void *func_address, void *call_site) {
        char out_symname[256];
        dump_symbols("./aop3", func_address, out_symname);

        printf("function start %s\n", out_symname);
}

__attribute__((no_instrument_function))
void __cyg_profile_func_exit(void *func_address, void *call_site) {
        char out_symname[256];
        dump_symbols("./aop3", func_address, out_symname);

        printf("function end %s\n", out_symname);
}

int add(int a, int b)
{
        return a + b;
}

int main()
{
        int result = add(3, 4);
        printf("result = %d\n", result);
}
taka@ubuntu:~$ more getsymbol.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <bfd.h>

void dump_symbols(const char *filename, int addr, char *out_symname) {
        bfd *abfd;
        asymbol *store;
        char *p;
        void *minisyms;
        int symnum, i;
        size_t size;
        int dyn = 0;
        int ret;

        abfd = bfd_openr(filename, NULL);
        assert(abfd);
        ret = bfd_check_format(abfd, bfd_object);
        assert(ret);

        if(!(bfd_get_file_flags(abfd) & HAS_SYMS)) {
                assert(bfd_get_error() == bfd_error_no_error);
                bfd_close(abfd);
                return;
        }

        store = bfd_make_empty_symbol(abfd);

        symnum = bfd_read_minisymbols(abfd, dyn, &minisyms, &size);
        assert(symnum >= 0);

        p = (char *)minisyms;
        for(i = 0; i < symnum; i++)
        {
                asymbol *sym = bfd_minisymbol_to_symbol(abfd, dyn, p, store);
                const char *name = bfd_asymbol_name(sym);
                int value = bfd_asymbol_value(sym);

                if(addr == value)
                {
                        strcpy(out_symname, name);
                }
                p += size;
        }

        free(minisyms);
        bfd_close(abfd);
}
taka@ubuntu:~$ more comp.sh
cc -c -o getsymbol.o getsymbol.c
cc -Wl,-soname,libgetsymbol.so -o libgetsymbol.so -shared getsymbol.o
cc aop3.c -o aop3 -L. -lgetsymbol -lbfd -finstrument-functions

taka@ubuntu:~$ ./comp.sh
taka@ubuntu:~$ ./aop3
function start main
function start add
function end add
result = 7
function end main
taka@ubuntu:~$

なんとなく出来たっぽい。
ファイル名が固定打ち(./aop3)なので、argv[0]から取り回せばもう少しマシかも。
しかしまあ、これだけのことやるのに大変ですね。。