3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

plantumlで実行結果から簡単にシーケンス図を作る

3
Last updated at Posted at 2017-08-13

何がしたいか

実行時の関数呼び出しをログにとって、自動でシーケンス図を作りたい。

できたもの

とりあえず、githubにあげる。(8/14更新)

(gistにしようとしたら、フォルダは作るなと怒られた)

pluntumlを入れる。mac osでは

$ brew install pluntuml

でいけるはず

上記、コードをcloneして

$ git clone https://github.com/tksmiura/simple_trace.git

ビルドして実行までmakeで

$ cd simple_trace
$ make trace

で画像ファイル(*.svg)ができるので、

open main_trace.svg

あたりでsafariで見ることができます。(mac only)

main_trace.png

PNGが欲しい場合、

make PIC=PNG trace

でできます。(上記画像はこちら)

技術的な話

  • gccのオプション(-finstrument-functions)で関数呼び出しと戻りのタイミングでフック関数を呼ばれるようにする。フック関数でIN/OUTの時に関数のアドレスを出力。
  • perlスクリプトでmapファイルで上記アドレスを関数名に変換。(dlopenを使う例がネットでは多いですが、組込み用途では難しいのでこちらで)注意点としては、mapファイルのアドレスと実行時のアドレスは異なるため、どこかわかっている関数のアドレスからオフセットを計算する必要がある。(__cyg_profile_func_enterで初回に自身のアドレスをログに残しています)
  • 後はperlでplantumlの形式に変換してplantumlで画像化する

 補足

  • マクロTRACE_EVENTとTRACE_NOTEを追加しました。これで、シーケンス図のイベント(非同期の意味で矢印の形が違う)と四角形のノートが記載できます。ただし、元コードに埋め込みが必要です。
  • mac osがclangですが、homebrewで入れたgcc(ver 7.1.0)でも動作しました。なのでmapファイルの形式がどっちも同じ。組込みのgccはもっと古いバージョンを使っていそうですが、、、手元に環境がないので、諦め。
simple_trace.c
# include <stdio.h>
# include <string.h>

__attribute__((no_instrument_function))
void __cyg_profile_func_enter(void* func_address, void* call_site);
__attribute__((no_instrument_function))
void __cyg_profile_func_exit(void* func_address, void* call_site);


__attribute__((no_instrument_function))
void __cyg_profile_func_enter(void* func_address, void* call_site) {
    static int f = 0;
    if (! f) {
        printf("offset __cyg_profile_func_enter = %lx\n",
               (unsigned long)__cyg_profile_func_enter);
        f = 1;
    }
    printf("in %lx -> %lx\n",
           (unsigned long)call_site,
           (unsigned long)func_address);
}

__attribute__((no_instrument_function))
void __cyg_profile_func_exit(void* func_address, void* call_site) {
    printf("out %lx <- %lx\n",
           (unsigned long)call_site,
           (unsigned long)func_address);
}
seq_trace.pl
# !/usr/bin/perl

%file_map = {};
@start = ();
@end = ();
@fid = ();
@func = ();
# read map.txt for MAC OS X(CLang) or gcc-7 (Homebrew GCC 7.1.0) 
open(MAP, "<", "map.txt") || die "Can't open map file";
while ($line = <MAP>) {
    #[  0] linker synthesized
    if ($line =~ /^\[([^\]])+\](.*)/) {
        my ($fid) = $1;
        my ($fname) = $2;
        $file_map{$fid} = $fname;
    #0x100000D90	0x00000060	[  1] _func1
    } elsif ($line =~ /^0x([0-9A-Fa-f]+)\s+0x([0-9A-Fa-f]+)\s+\[([^\]])+\](.*)/) {
        my($fn) = $4;
        push(@start, hex($1));
        push(@end, hex($1) + hex($2));
        push(@fid, $3);
        $fn =~ s/^\s*\_//;
        push(@func, $fn);
    }
}
close(MAP);

if ($ARGV[0]) {
    $dest_file = $ARGV[0];
} else {
    $dest_file = "tarce.png";
}

print STDOUT "\@startuml $dest_file\n";
print STDOUT "hide footbox\n\n";
while ($line = <STDIN>) {
    if ($line =~ /offset\s+([_a-zA-Z0-9]+)\s*\=\s*([0-9A-Fa-f]+)/) {
        my ($base_func) = $1;
        my ($base_func_addr) = hex($2);
        my ($i);
        for($i = 0; $i < $#func; $i++) {
            if ($func[$i] eq $base_func) {
                $offset = $base_func_addr - $start[$i];
                last;
            }
        }
    } elsif ($line =~ /in\s+([0-9A-Fa-f]+)\s+\-\>\s+([0-9A-Fa-f]+)/) { 
        my ($cf, $cm) = &func(hex($1) - $offset);
        my ($f, $m) = &func(hex($2) - $offset);
        if ($delay_out) {
            print STDOUT $delay_out;
            $delay_out = 0;
        }
        if ($cm ne "unknown" && $m ne "unknown") {
            print STDOUT "$cm -> $m : $f\n";
            $delay_out = "activate $m\n";
        } elsif ($cm ne "unknown" && $m eq "unknown") {
            print STDOUT "$cm ->] : $f\n";
        } elsif ($cm eq "unknown" && $m ne "unknown") {
            print STDOUT "[-> $m : $f\n";
            $delay_out = "activate $m\n";
        } else {
            # no output
        }
    } elsif ($line =~ /out\s+([0-9A-Fa-f]+)\s+\<\-\s+([0-9A-Fa-f]+)/) { 
        my ($cf, $cm) = &func(hex($1) - $offset);
        my ($f, $m) = &func(hex($2) - $offset);
        if ($cm ne $m) {
            if ($cm ne "unknown" && $m ne "unknown") {
                if (! $delay_out) {                    
                    print STDOUT "$cm <-- $m\n";
                }
            } elsif ($cm ne "unknown" && $m eq "unknown") {
                print STDOUT "$cm <--]\n";
            } elsif ($cm eq "unknown" && $m ne "unknown") {
                print STDOUT "[<-- $m\n";
            } else {
                # no output
            }
        }
        if (! $delay_out) {
            print STDOUT "deactivate $m\n";
        }
        $delay_out = 0;
    } elsif ($line =~ /EVENT\s*([^\s]+)\s+\-\>\>\s*([^\:]*)\s*\:(.*)$/) {
        my $from, $to, $c;
        $from = $1;
        $to = $2;
        $c = $3;
        $from =~ s/.c$//;
        $to =~ s/.c$//;
        
        if ($delay_out) {
            print STDOUT $delay_out;
            $delay_out = 0;
        }
        print STDOUT "$from ->> $to : $c\n";
    } elsif ($line =~ /NOTE\s*([^\:]+)\:(.*)$/) {
        my $m, $c;
        $m = $1;
        $c = $2;
        $m =~ s/.c$//;
        if ($delay_out) {
            print STDOUT $delay_out;
            $delay_out = 0;
        }
        print STDOUT "rnote over $m\n$c\nendrnote\n";
    }
}

print STDOUT "\n\@enduml\n";
close(STDOUT);
close(STDIN);

exit 0;

sub func {
    my ($addr) = @_;
    my ($i);
    my ($m) = "unknown";
    my ($f) = sprintf("(%x)", $addr);
    for($i = 0; $i < $#func; $i++) {
        if ($addr >= $start[$i] && $addr < $end[$i]) {
            $f = $func[$i];
            $m = $file_map{$fid[$i]};
            $m =~ s/\.trace_o$//;
            last;
        }
    }
    return ($f, $m);
}
3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?