ノートブック

古き良きスタイルのノートブックです。

Arduino "Hello World!"

Arduinoに使われているAVRチップの hello world プログラムを書いてその働きを理解したいと思います。
この記事ではarduinoのデジタル8番ピンから13番ピンまでの6ピンに接続したLEDを光らせるだけという単純なプログラムを 機械語の番地に直接対応したアセンブリ言語で書くところからはじめ、分割して記述したアセンブリ言語のコード、C言語、そしてC++言語で 同じようにLEDを光らせるだけの単純なプログラムをそれぞれ動かしてみます。 またavrのレジスタが計算を行う様子をシミュレーションツールを使っての観察もしてみます。

まずは開発環境の準備を以下で行いました。ちなみに私の実行した環境はubuntuです。
$ apt-get install arduino arduino IDEのインストール
$ apt-get install binutils-avr gcc-avr avr-libc avrdude
これでクロスコンパイルや逆アセンブルなどができるようになります。

以下に登場するコードはすべてarduinoのデジタル8番ピンから13番ピンまでの6ピンに接続したLEDを光らせるだけという単純なことをしているだけのプログラムです。

ベタ塗りアセンブリ言語

ではまずarduinoフラッシュメモリに展開される機械語をそのままアセンブリでベタ塗りに対応したものを書いて動かしてみたいと思います。
test.s

jmp main
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xc4
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
main:
    ldi r16, 0b00111111 ;レジスタに0b00111111を格納
    out 0x04, r16      ;DDRBでポートBをすべて出力に設定
    ldi r17, 0b00010101 ;レジスタに格納
    out 0x05, r17      ;PORTBにレジスタの値を設定
    rjmp main

そしてこれをビルドするmakefileが以下
Makefile

CC = avr-gcc
TARGET = test

$(TARGET):
  avr-gcc -c -mmcu=atmega328p -o test.o test.s
  avr-ld -o test.elf test.o
  avr-objcopy -I elf32-avr -O ihex test.elf test.hex

このmakefileではクロスコンパイラでアセンブリ言語で書かれたコードをオブジェクトファイルに変換し、それを実行形式であるelfファイルに変換し、それを実際のarduinoに書き込むhex形式に変換しています。
この2つのファイルを置いたディレクトリで、
$ make  このようにmakeをするとビルドされます。

以下が実際にmakeを実行し生成されたhex形式の中身。
test.hex

:100000000C9434000C9451000C9451000C94510049
:100010000C9451000C9451000C9451000C9451001C
:100020000C9451000C9451000C9451000C9451000C
:100030000C9451000C9451000C9451000C945100FC
:100040000C9462000C9451000C9451000C945100DB
:100050000C9451000C9451000C9451000C945100DC
:100060000C9451000C9451000FE304B915E115B93B
:02007000FBCFC4
:00000001FF

このhexフォーマットについてはwikipediaの記事
https://ja.wikipedia.org/wiki/Intel_HEX
あるいはこのサイトの解説
https://www2.yokogawa-digital.com/support/support_netimpress/top/index.php?m=FaqInfo&id=39
がとてもわかり易いです。 このhexファイルを以下のようにavrdudeを使ってarduinoへ書き込みます
私の環境では以下のようにしました。
$ avrdude -c arduino -P /dev/ttyACM0 -p m328p -b 115200 -U flash:w:test.hex:i
書き込むとarduinoのDigital8番から13番ピンまでの6ピンのon/offを設定できる。 この場合8番ピンから交互に点灯します。こんな感じに。
f:id:primitivem:20170928225400j:plain

ではこの書き込んだプログラムを逆アセンブルして処理の流れを見てみましょう。
$ avr-objdump -m avr -D -z test.hex このコマンドを実行すると逆アセンブルすることができ以下のように出力されます。

00000000 <.sec1>:
   0:  0c 94 34 00  jmp 0x68    ;  0x68
   4:  0c 94 51 00  jmp 0xa2    ;  0xa2
   8:  0c 94 51 00  jmp 0xa2    ;  0xa2
   c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  10:  0c 94 51 00  jmp 0xa2    ;  0xa2
  14:  0c 94 51 00  jmp 0xa2    ;  0xa2
  18:  0c 94 51 00  jmp 0xa2    ;  0xa2
  1c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  20:  0c 94 51 00  jmp 0xa2    ;  0xa2
  24:  0c 94 51 00  jmp 0xa2    ;  0xa2
  28:  0c 94 51 00  jmp 0xa2    ;  0xa2
  2c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  30:  0c 94 51 00  jmp 0xa2    ;  0xa2
  34:  0c 94 51 00  jmp 0xa2    ;  0xa2
  38:  0c 94 51 00  jmp 0xa2    ;  0xa2
  3c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  40:  0c 94 62 00  jmp 0xc4    ;  0xc4
  44:  0c 94 51 00  jmp 0xa2    ;  0xa2
  48:  0c 94 51 00  jmp 0xa2    ;  0xa2
  4c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  50:  0c 94 51 00  jmp 0xa2    ;  0xa2
  54:  0c 94 51 00  jmp 0xa2    ;  0xa2
  58:  0c 94 51 00  jmp 0xa2    ;  0xa2
  5c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  60:  0c 94 51 00  jmp 0xa2    ;  0xa2
  64:  0c 94 51 00  jmp 0xa2    ;  0xa2
  68:  0f e3           ldi r16, 0x3F   ; 63
  6a:   04 b9          out 0x04, r16   ; 4
  6c:   15 e1          ldi r17, 0x15   ; 21
  6e:   15 b9          out 0x05, r17   ; 5
  70:  fb cf           rjmp    .-10       ;  0x68
</.sec1>

この場合元のアセンブリコードと同じなので特に変わった様子はないが、処理が0番地からどのように行われてゆくのか明確に理解できます。
まず0番地で0x68に処理がジャンプしてここからmainルーティンが始まる。
0x6e番地でPORTBにr17に格納したレジスタの値を格納してLEDのon/offを設定している。

この0x00番地から0x64番地までの領域がarduinoの割り込みベクタになっている。 一番先頭でmainルーティンにジャンプする番地を書いたがそれ以外の番地には主に0xa2というアドレスが書かれているのがわかります。
これはarduino IDEに付属しているBlinkサンプルコードをコンパイルしてhexファイルを出力させ(arduino IDEGUIから操作した)、それを逆アセンブルしたらこのように書かれていたからそれをそのまま使っているためで、詳しいことはよくわかりません。

次にsimavrというavrシミュレータを用いてレジスタの状態を観察してみます。
simavrに必要な依存関係を以下でインストールして
$ sudo apt-get install libelf-dev build-essential freeglut3 freeglut3-dev gtkwave
https://github.com/buserror/simavr
このsimavrレポジトリを手元にダウンロードしてmakeすればsimavrがビルドできrun_avrという実行ファイルを実行すればsimavrが起動できました。

simavrの使い方

ここでsimavrの使い方を書いておきます
$ run_avr -g -t -ff -mcu atmega328p -freq 1 ファイル名.hex
でsimavrにhexを読み込ませて起動する。そして別のターミナルで
$ avr-gdb test.hex
としてgdbを起動して
$ target remote:1234
$ load
これでリモートアクセスします
そして何故かリモートアクセスではrunコマンドが使えずステップ実行できないためブレークポイントを設定して使いました。
hexファイルのようにソースがないコードのブレークポイントを設定するにはフラッシュメモリ上のアドレスを指定します。
普通は
(gdb) braak *0x00000074 このようににすれば設定できるのだがavr-gdbとsimavrではなぜか0x800000という数字が指定した数字の上に加算された状態になってしまう。要するにうまく行かない。
この問題の解決策を以下に見つけた。
https://sourceware.org/ml/gdb/2014-10/msg00142.html
ここを参考に以下のようにします
(gdb) break *(void (*)())0x00000074 このようにすると、意味はわからないがとにかくこれでうまくいきました。そして
(gdb) c 
で設定したブレークポイントまで実行され
(gdb) info registers    あるいは
(gdb) i r
レジスタの状態を確認できます。

simavrでレジスタの観察

では先ほど生成したhexファイルを
$ run_avr -g -t -ff -mcu atmega328p -freq 1 test.hex
でsimavrにtest.hexを読み込ませて起動し、別のターミナルで
$ avr-gdb test.hex
$ target remote:1234
$ load
とし、これでシミュレータとデバッガーを準備する

上の逆アセンブルの通りに計算機が処理するのかどうか観察します。
まず逆アセンブルの番地を参考にブレークポイントを以下のように設定する
$ (gdb) break *(void (*)())0x00000068 メインルーティンの最初(割り込みベクタからjmpする番地)
$ (gdb) break *(void (*)())0x0000006a r16に0x3Fが格納されたあとの場所
$ (gdb) break *(void (*)())0x0000006e r17に0x15が格納されたあとの場所

そしてまず最初の状態で
$ (gdb) i r を実行してレジスタを観察すると

r0             0x0 0
r1             0x0  0
r2             0x0  0
r3             0x0  0
~ 省略
r14            0x0  0
r15            0x0  0
r16            0x0  0
r17            0x0  0
r18            0x0  0
r19            0x0  0
~ 省略
r30            0x0  0
r31            0x0  0
SREG           0x0  0
SP             0x8008ff 0x8008ff
PC2            0x0  0
pc             0x0  0x0

gdbが返してきて、レジスタの数値はすべて0になっているのがわかります。次に
$ (gdb) c を実行する。そして
$ (gdb) where で今の場所を確認すると目論見通り0x00000068と出力されちゃんと次のブレークポイントに飛んでいるのがわかる。
$ (gdb) i r の結果は上のレジスタと同じですべて0。次に
$ (gdb) c  これで次のブレークポイントに移動し
$ (gdb) i r レジスタの状態を観察する

r0             0x0 0
r1             0x0  0
〜 略
r15            0x0  0
r16            0x3f 63
r17            0x0  0
r18            0x0  0
〜 略
r30            0x0  0
r31            0x0  0
SREG           0x0  0
SP             0x8008ff 0x8008ff
PC2            0x6a 106
pc             0x6a 0x6a

するとこのように r16 に 0x3f が格納されていおりちゃんとレジスタが仕事をしているのが確認できる。次のブレークポイントでは
$ (gdb) c
$ (gdb) i r

r0             0x0 0
r1             0x0  0
〜 略
r15            0x0  0
r16            0x3f 63
r17            0x15 21
r18            0x0  0
〜 略
r31            0x0  0
SREG           0x0  0
SP             0x8008ff 0x8008ff
PC2            0x6e 110
pc             0x6e 0x6e

となり r17 に 0x15 が格納されているのがわかる。

このように機械語の逆アセンブルの通りに計算機が命令を処理しているのが観察できました。

割り込みベクタとスタートアップとmainルーティンに分割する

上で行ったプログラムを、割り込みベクタを記述する部分、スタートアップを記述する部分、そしてmainルーティンを記述する部分の3つのアセンブリコードに分割して記述してみます。

vector.s

jmp start
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xc4
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2
jmp 0xa2

startup.s

 .section .text
    .global start
    .type   start,@function
start:
    rcall   main

main.s

 .global main

main:
    ldi r16, 0b00111111 ;レジスタに0b00111111を格納
    out 0x04, r16      ;DDRBでポートBをすべて出力に設定
    ldi r17, 0b00010101 ;レジスタに格納
    out 0x05, r17      ;PORTBにレジスタの値を設定
    rjmp main

この3つのファイルと以下のmakefileを用意します。

Makefile

all:
  avr-gcc -c -mmcu=atmega328p -o main.o main.s
  avr-gcc -c -mmcu=atmega328p -o vector.o vector.s
  avr-gcc -c -mmcu=atmega328p -o startup.o startup.s
  avr-ld -o test.elf vector.o main.o startup.o
  avr-objcopy -I elf32-avr -O ihex test.elf test.hex

Makefileを見てわかる通りvector.s,startup.s,main.sの3つをそれぞれオブジェクトファイルに変換しそれをリンクして一つの実行形式に変換しています。 この合計4つのファイル置いたディレクトリで先ほどと同じようにmakeしてビルドします。
これで割り込みベクタからまずはスタートアップルーティンにジャンプし、スタートアップルーティンからmainルーティンにジャンプするようになるはずです。

実際にビルドして生成されたhexファイルを逆アセンブルすると以下のようになります。

00000000 <.sec1>:
   0:  0c 94 34 00  jmp 0x68    ;  0x68
   4:  0c 94 51 00  jmp 0xa2    ;  0xa2
   8:  0c 94 51 00  jmp 0xa2    ;  0xa2
   c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  10:  0c 94 51 00  jmp 0xa2    ;  0xa2
  14:  0c 94 51 00  jmp 0xa2    ;  0xa2
  18:  0c 94 51 00  jmp 0xa2    ;  0xa2
  1c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  20:  0c 94 51 00  jmp 0xa2    ;  0xa2
  24:  0c 94 51 00  jmp 0xa2    ;  0xa2
  28:  0c 94 51 00  jmp 0xa2    ;  0xa2
  2c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  30:  0c 94 51 00  jmp 0xa2    ;  0xa2
  34:  0c 94 51 00  jmp 0xa2    ;  0xa2
  38:  0c 94 51 00  jmp 0xa2    ;  0xa2
  3c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  40:  0c 94 62 00  jmp 0xc4    ;  0xc4
  44:  0c 94 51 00  jmp 0xa2    ;  0xa2
  48:  0c 94 51 00  jmp 0xa2    ;  0xa2
  4c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  50:  0c 94 51 00  jmp 0xa2    ;  0xa2
  54:  0c 94 51 00  jmp 0xa2    ;  0xa2
  58:  0c 94 51 00  jmp 0xa2    ;  0xa2
  5c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  60:  0c 94 51 00  jmp 0xa2    ;  0xa2
  64:  0c 94 51 00  jmp 0xa2    ;  0xa2
  68:  00 d0          rcall   .+0        ;  0x6a
  6a:   0f e3           ldi r16, 0x3F   ; 63
  6c:   04 b9          out 0x04, r16   ; 4
  6e:   15 e1          ldi r17, 0x15   ; 21
  70:  15 b9          out 0x05, r17   ; 5
  72:  fb cf           rjmp    .-10       ;  0x6a
</.sec1>

すなわち割り込みベクタから 0x68 番地のスタートアップルーティンに飛びそこからrcallで 0x6a にあるmainルーティンに 飛んでいるのがわかります。 ここでもやっていることは上で書いたベタ塗りのアセンブリ言語のコードと同じなのですが、今回はアセンブリファイルを3つに分割してビルドするときにリンカでつなげて ひとつの実行ファイルにするという過程をちゃんと確認することができました。

C言語で書く

では次に割り込みベクタとスタートアップをアセンブリ言語で書いたのはそのままで、
今度はmainルーティンとLEDを光らす部部分をそれぞれ独立にC言語で書いてみたいと思います。

割り込みベクタとスタートアップは上と同じでそれに以下の3つを加えます。
まずはLEDを点灯させる処理を行う関数の部分

lib_blink.h

void blink();

lib_blink.c

#include "lib_blink.h"

void blink(){
    //char b = 0;
    asm volatile(
        "ldi r16, 0b00111111 \n"
        "out 0x04, r16 \n"
        "ldi r17, 0b00010101 \n"
        "out 0x05, r17 \n"
    );
}

関数の中の処理はインラインアセンブラで書いている。
そしてその関数を使うmain関数

main.c

#include "lib_blink.h"

int main(void)
{
    while(1){
        blink();
    }
    return 0;
}

この3つに先ほどのvector.sとstartup.sを足して合計5つのファイルから構成されます。
そしてMakefileが以下

all:
  avr-gcc -c -mmcu=atmega328p -o main.o main.c
  avr-gcc -S -mmcu=atmega328p lib_blink.c
  avr-gcc -c -mmcu=atmega328p -o lib_blink.o lib_blink.c
  avr-gcc -c -mmcu=atmega328p -o vector.o vector.s
  avr-gcc -c -mmcu=atmega328p -o startup.o startup.s
  avr-ld -o test.elf vector.o startup.o main.o lib_blink.o
  avr-objcopy -I elf32-avr -O ihex test.elf test.hex

これをビルドして逆アセンブリしたものが以下

00000000 <.sec1>:
   0:  0c 94 34 00  jmp 0x68    ;  0x68
   4:  0c 94 51 00  jmp 0xa2    ;  0xa2
   8:  0c 94 51 00  jmp 0xa2    ;  0xa2
   c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  10:  0c 94 51 00  jmp 0xa2    ;  0xa2
  14:  0c 94 51 00  jmp 0xa2    ;  0xa2
  18:  0c 94 51 00  jmp 0xa2    ;  0xa2
  1c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  20:  0c 94 51 00  jmp 0xa2    ;  0xa2
  24:  0c 94 51 00  jmp 0xa2    ;  0xa2
  28:  0c 94 51 00  jmp 0xa2    ;  0xa2
  2c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  30:  0c 94 51 00  jmp 0xa2    ;  0xa2
  34:  0c 94 51 00  jmp 0xa2    ;  0xa2
  38:  0c 94 51 00  jmp 0xa2    ;  0xa2
  3c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  40:  0c 94 62 00  jmp 0xc4    ;  0xc4
  44:  0c 94 51 00  jmp 0xa2    ;  0xa2
  48:  0c 94 51 00  jmp 0xa2    ;  0xa2
  4c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  50:  0c 94 51 00  jmp 0xa2    ;  0xa2
  54:  0c 94 51 00  jmp 0xa2    ;  0xa2
  58:  0c 94 51 00  jmp 0xa2    ;  0xa2
  5c:   0c 94 51 00  jmp 0xa2    ;  0xa2
  60:  0c 94 51 00  jmp 0xa2    ;  0xa2
  64:  0c 94 51 00  jmp 0xa2    ;  0xa2
  68:  00 d0          rcall   .+0        ;  0x6a
  6a:   cf 93          push    r28
  6c:   df 93          push    r29
  6e:   cd b7         in    r28, 0x3d   ; 61
  70:  de b7           in    r29, 0x3e   ; 62
  72:  0e 94 3c 00   call    0x78    ;  0x78
  76:  fd cf           rjmp    .-6        ;  0x72
  78:  cf 93          push    r28
  7a:   df 93          push    r29
  7c:   cd b7         in    r28, 0x3d   ; 61
  7e:   de b7           in    r29, 0x3e   ; 62
  80:  0f e3           ldi r16, 0x3F   ; 63
  82:  04 b9          out 0x04, r16   ; 4
  84:  15 e1          ldi r17, 0x15   ; 21
  86:  15 b9          out 0x05, r17   ; 5
  88:  df 91          pop r29
  8a:   cf 91          pop r28
  8c:   08 95         ret
</.sec1>

mainルーティン内で 0x78 へコールされblink()関数の番地へ飛ぶ。そしてその中の処理が行われ 処理がmainに戻ってくる。
今までのmainルーティンと違いC言語でmainや関数を記述するとスタックを用意するなどちゃんとした関数の仕組みになっているのがわかります。芸が細かい!

C++言語で書く

では最後に上で行ったC言語の関数をC++のクラスとして書いてみます。
ここではめんどくさいので関数とmainを含めた単一のC++ファイルにまとめて書くことにします。

割り込みベクタとスタートアップは変わらず、そこにmain.cppを足したの3つのファイルからなります。

main.cpp

class MyClass{
    private:
        char b;
    public:
        MyClass(){
            b = 3;
        }
        void func();
        void blink();
};

void MyClass::func(){
    b = 2;
}

void MyClass::blink(){
    asm volatile(
        "ldi r16, 0b00111111 \n"
        "out 0x04, r16 \n"
        "ldi r17, 0b00010101 \n"
        "out 0x05, r17 \n"
    );
}

int main() {
    MyClass myclass;
    myclass.func();
    while(1){
        myclass.blink();
    }
    return 0;
}

このコードでは、メソッドとしてfunc()とblink()という2つを定義している。
func()はなんの意味もないメソッドで試しに変数に値を格納しているだけである。
blink()は上で行ったC言語の関数のときと同じである。

Makefile

all:
  avr-g++ -c -mmcu=atmega328p -o main.o main.cpp
  avr-gcc -c -mmcu=atmega328p -o vector.o vector.s
  avr-gcc -c -mmcu=atmega328p -o startup.o startup.s
  avr-ld -o test.elf vector.o startup.o main.o
  avr-objcopy -I elf32-avr -O ihex test.elf test.hex

これをビルドして 生成されたelfファイル を逆アセンブルすると

00000000 <__ctors_end>:
   0:   0c 94 34 00     jmp 0x68    ; 0x68 <start>
   4:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
   8:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
   c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  10:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  14:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  18:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  1c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  20:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  24:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  28:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  2c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  30:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  34:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  38:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  3c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  40:   0c 94 62 00     jmp 0xc4    ; 0xc4 <main>
  44:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  48:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  4c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  50:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  54:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  58:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  5c:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  60:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>
  64:   0c 94 51 00     jmp 0xa2    ; 0xa2 <_zn7myclass5blinkev>

00000068 <start>:
  68:   21 d0           rcall   .+66        ; 0xac <main>

0000006a <_zn7myclass4funcev>:
  6a:   cf 93           push    r28
  6c:   df 93           push    r29
  6e:   00 d0           rcall   .+0         ; 0x70 <_zn7myclass4funcev>
  70:   cd b7           in  r28, 0x3d   ; 61
  72:   de b7           in  r29, 0x3e   ; 62
  74:   9a 83           std Y+2, r25    ; 0x02
  76:   89 83           std Y+1, r24    ; 0x01
  78:   89 81           ldd r24, Y+1    ; 0x01
  7a:   9a 81           ldd r25, Y+2    ; 0x02
  7c:   22 e0           ldi r18, 0x02   ; 2
  7e:   fc 01           movw    r30, r24
  80:   20 83           st  Z, r18
  82:   0f 90           pop r0
  84:   0f 90           pop r0
  86:   df 91           pop r29
  88:   cf 91           pop r28
  8a:   08 95           ret

0000008c <_zn7myclass5blinkev>:
  8c:   cf 93           push    r28
  8e:   df 93           push    r29
  90:   00 d0           rcall   .+0         ; 0x92 <_zn7myclass5blinkev>
  92:   cd b7           in  r28, 0x3d   ; 61
  94:   de b7           in  r29, 0x3e   ; 62
  96:   9a 83           std Y+2, r25    ; 0x02
  98:   89 83           std Y+1, r24    ; 0x01
  9a:   0f e3           ldi r16, 0x3F   ; 63
  9c:   04 b9           out 0x04, r16   ; 4
  9e:   15 e1           ldi r17, 0x15   ; 21
  a0:   15 b9           out 0x05, r17   ; 5
  a2:   0f 90           pop r0
  a4:   0f 90           pop r0
  a6:   df 91           pop r29
  a8:   cf 91           pop r28
  aa:   08 95           ret

000000ac <main>:
  ac:   cf 93           push    r28
  ae:   df 93           push    r29
  b0:   1f 92           push    r1
  b2:   cd b7           in  r28, 0x3d   ; 61
  b4:   de b7           in  r29, 0x3e   ; 62
  b6:   ce 01           movw    r24, r28
  b8:   01 96           adiw    r24, 0x01   ; 1
  ba:   0e 94 68 00     call    0xd0    ; 0xd0 <_zn7myclassc1ev>
  be:   ce 01           movw    r24, r28
  c0:   01 96           adiw    r24, 0x01   ; 1
  c2:   0e 94 35 00     call    0x6a    ; 0x6a <_zn7myclass4funcev>
  c6:   ce 01           movw    r24, r28
  c8:   01 96           adiw    r24, 0x01   ; 1
  ca:   0e 94 46 00     call    0x8c    ; 0x8c <_zn7myclass5blinkev>
  ce:   fb cf           rjmp    .-10        ; 0xc6 <main>

000000d0 <_zn7myclassc1ev>:
  d0:   cf 93           push    r28
  d2:   df 93           push    r29
  d4:   00 d0           rcall   .+0         ; 0xd6 <_zn7myclassc1ev>
  d6:   cd b7           in  r28, 0x3d   ; 61
  d8:   de b7           in  r29, 0x3e   ; 62
  da:   9a 83           std Y+2, r25    ; 0x02
  dc:   89 83           std Y+1, r24    ; 0x01
  de:   89 81           ldd r24, Y+1    ; 0x01
  e0:   9a 81           ldd r25, Y+2    ; 0x02
  e2:   23 e0           ldi r18, 0x03   ; 3
  e4:   fc 01           movw    r30, r24
  e6:   20 83           st  Z, r18
  e8:   0f 90           pop r0
  ea:   0f 90           pop r0
  ec:   df 91           pop r29
  ee:   cf 91           pop r28
  f0:   08 95           ret

セクション .comment の逆アセンブル:

00000000 <.comment>:
   0:   47 43           sbci    r20, 0x37   ; 55
   2:   43 3a           cpi r20, 0xA3   ; 163
   4:   20 28           or  r2, r0
   6:   47 4e           sbci    r20, 0xE7   ; 231
   8:   55 29           or  r21, r5
   a:   20 34           cpi r18, 0x40   ; 64
   c:   2e 38           cpi r18, 0x8E   ; 142
   e:   2e 31           cpi r18, 0x1E   ; 30
  10:   00 2d           Address 0x0000000000000010 is out of bounds.
.word   0xffff  ; ????

このようになる。
myclassのコンストラクタとfunc()とblink()の3つが別のセクションとしてアセンブルされているのがわかります。
最後にhexファイル(実際にarduinoに書き込まれるファームウェア)の逆アセンブル結果をelfファイルを逆アセンブルした結果を参考に、セクションがわかるように注釈をいれてみましょう。

0:    0c 94 34 00  jmp 0x68    ;  0x68    割り込みベクタの最初
4: 0c 94 51 00  jmp 0xa2    ;  0xa2
8: 0c 94 51 00  jmp 0xa2    ;  0xa2
c:  0c 94 51 00  jmp 0xa2    ;  0xa2
10:    0c 94 51 00  jmp 0xa2    ;  0xa2
14:    0c 94 51 00  jmp 0xa2    ;  0xa2
18:    0c 94 51 00  jmp 0xa2    ;  0xa2
1c: 0c 94 51 00  jmp 0xa2    ;  0xa2
20:    0c 94 51 00  jmp 0xa2    ;  0xa2
24:    0c 94 51 00  jmp 0xa2    ;  0xa2
28:    0c 94 51 00  jmp 0xa2    ;  0xa2
2c: 0c 94 51 00  jmp 0xa2    ;  0xa2
30:    0c 94 51 00  jmp 0xa2    ;  0xa2
34:    0c 94 51 00  jmp 0xa2    ;  0xa2
38:    0c 94 51 00  jmp 0xa2    ;  0xa2
3c: 0c 94 51 00  jmp 0xa2    ;  0xa2
40:    0c 94 62 00  jmp 0xc4    ;  0xc4
44:    0c 94 51 00  jmp 0xa2    ;  0xa2
48:    0c 94 51 00  jmp 0xa2    ;  0xa2
4c: 0c 94 51 00  jmp 0xa2    ;  0xa2
50:    0c 94 51 00  jmp 0xa2    ;  0xa2
54:    0c 94 51 00  jmp 0xa2    ;  0xa2
58:    0c 94 51 00  jmp 0xa2    ;  0xa2
5c: 0c 94 51 00  jmp 0xa2    ;  0xa2
60:    0c 94 51 00  jmp 0xa2    ;  0xa2
64:    0c 94 51 00  jmp 0xa2    ;  0xa2   割り込みベクタの終わり
68:    21 d0          rcall   .+66       ;  0xac  スタートアップの一行
6a: cf 93          push    r28           func()メソッドの最初
6c: df 93          push    r29
6e: 00 d0          rcall   .+0        ;  0x70
70:    cd b7         in    r28, 0x3d   ; 61
72:    de b7           in    r29, 0x3e   ; 62
74:    9a 83          std Y+2, r25   ; 0x02
76:    89 83         std Y+1, r24   ; 0x01
78:    89 81         ldd r24, Y+1   ; 0x01
7a: 9a 81          ldd r25, Y+2   ; 0x02
7c: 22 e0          ldi r18, 0x02   ; 2
7e: fc 01        movw    r30, r24
80:    20 83         st  Z, r18
82:    0f 90          pop r0
84:    0f 90          pop r0
86:    df 91          pop r29
88:    cf 91          pop r28
8a: 08 95         ret              func()メソッドの最後
8c: cf 93          push    r28           blink()メソッドの最初
8e: df 93          push    r29
90:    00 d0          rcall   .+0        ;  0x92
92:    cd b7         in    r28, 0x3d   ; 61
94:    de b7           in    r29, 0x3e   ; 62
96:    9a 83          std Y+2, r25   ; 0x02
98:    89 83         std Y+1, r24   ; 0x01
9a: 0f e3           ldi r16, 0x3F   ; 63
9c: 04 b9          out 0x04, r16   ; 4
9e: 15 e1          ldi r17, 0x15   ; 21
a0: 15 b9          out 0x05, r17   ; 5
a2: 0f 90          pop r0
a4: 0f 90          pop r0
a6: df 91          pop r29
a8: cf 91          pop r28
aa: 08 95         ret               blink()メソッドの最後
ac: cf 93          push    r28            mainルーティンの最初
ae: df 93          push    r29
b0: 1f 92          push    r1
b2: cd b7         in    r28, 0x3d   ; 61
b4: de b7           in    r29, 0x3e   ; 62
b6: ce 01          movw    r24, r28
b8: 01 96         adiw    r24, 0x01   ; 1
ba: 0e 94 68 00  call    0xd0    ;  0xd0     コンストラクタに飛ぶ
be: ce 01          movw    r24, r28
c0: 01 96         adiw    r24, 0x01   ; 1
c2: 0e 94 35 00  call    0x6a    ;  0x6a     func()関数に飛ぶ
c6: ce 01          movw    r24, r28
c8: 01 96         adiw    r24, 0x01   ; 1
ca: 0e 94 46 00  call    0x8c    ;  0x8c      blink()関数に飛ぶ
ce: fb cf           rjmp    .-10       ;  0xc6    mainルーティンの最後
d0: cf 93          push    r28             myclassのコンストラクタの最初
d2: df 93          push    r29
d4: 00 d0          rcall   .+0        ;  0xd6
d6: cd b7         in    r28, 0x3d   ; 61
d8: de b7           in    r29, 0x3e   ; 62
da: 9a 83          std Y+2, r25   ; 0x02
dc: 89 83         std Y+1, r24   ; 0x01
de: 89 81         ldd r24, Y+1   ; 0x01
e0: 9a 81          ldd r25, Y+2   ; 0x02
e2: 23 e0          ldi r18, 0x03   ; 3
e4: fc 01        movw    r30, r24
e6: 20 83         st  Z, r18
e8: 0f 90          pop r0
ea: 0f 90          pop r0
ec: df 91          pop r29
ee: cf 91          pop r28
f0: 08 95         ret                myclassのコンストラクタの最後

このように逆アセンブリコード自体は最初に示した単一のアセンブリコードのものに比べ随分長くはなったっが、処理の内実を追いかけるのはそれほど難しくはありません。
割り込みベクタからスタートアップの番地に処理が飛び、そこからmainルーティンに飛びそこでコンストラクタやメソッドの処理を行ってmainに戻るということを繰り返しているだけです。
このプログラムをarduinoの実機に書き込んでも当然6つのLEDを自由に点灯させるという動作を確認できました。

以上、arduinoのLEDを光らせるだけのプログラムの処理のあらましを低層から観察する記事でした。