1
1

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 3 years have passed since last update.

GDBで動作確認

Last updated at Posted at 2019-07-19

概要

Arduinoのブートローダから起動するプログラムを作成し,シミュレータで動作を確認する.前提として,AVRの開発環境とgdbをインストールしておく必要がある.

以下やったこと.

  1. startup.sだけ作成してミュレータで実行する
  2. リンカスクリプトを作成し,startup.sだけ実行
  3. main.cを加え,startup.sからmainを呼び出す
  4. main.cからstartup.sにアセンブラで書かれている関数を呼び出す

startup.sだけ作成してシミュレータで実行

以下のプログラムをアセンブリ言語で作成

startup.s
        .section .text
        .global start
        .type   start, @function

start:
        mov r16, r16
        ldi r20, 0x2   // 即値をr20に代入
        ldi r17, 0x1
        lds r18, 0x40  //0x40番地の値をr18に代入
        add r18, r17   // r17をr18に加えてr18に保存
        sts 0x40, r18  // r18の値を0x40番地に格納
        lds r19, 0x40
        mov r16, r16

このプログラム自身に特に意味はなく,どうやってアセンブリ言語でプログラムを書いてシミュレータで実行するのかを確認する

以下のようにMakefileを作成する.PREFIXなどはAVRの開発環境と合わせる必要がある.

TARGET = startup
SRC = $(TARGET).s
BINARY = a.out
PREFIX = avr-elf
BASEDIR = /usr/core/$(PREFIX)
BINDIR = $(BASEDIR)/bin
AS = $(BINDIR)/$(PREFIX)-as
NM = $(BINDIR)/$(PREFIX)-nm
OBJDMP = $(BINDIR)/$(PREFIX)-objdump
GDB = $(BINDIR)/avr-gdb

all: $(SRC)
        $(AS) -mmcu=avr5 $(SRC)

dump: a.out
        $(OBJDMP) -d $^

sym: a.out
        $(NM) $^

run: a.out
        $(GDB) $^

clean:
        rm -rf a.out
  • makeでコンパイル
  • make dumpでコンパイルしたプログラムをobjdumpでディスアセンブル
  • make symでシンボルテーブルの出力
  • make runでgdbを使って実行

以下,それぞれの実行結果

>make dump

a.out:     file format elf32-avr


Disassembly of section .text:

00000000 <start>:
   0:   00 2f           mov     r16, r16
   2:   42 e0           ldi     r20, 0x02       ; 2
   4:   11 e0           ldi     r17, 0x01       ; 1
   6:   20 91 40 00     lds     r18, 0x0040
   a:   21 0f           add     r18, r17
   c:   20 93 40 00     sts     0x0040, r18
  10:   30 91 40 00     lds     r19, 0x0040
  14:   00 2f           mov     r16, r16

ディスアセンブルの結果です.

>make sym

00000000 T start

nmの結果です.

>make run

/usr/core/avr-elf/bin/avr-gdb a.out
GNU gdb (GDB) 7.11
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-apple-darwin14.5.0 --target=avr".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...(no debugging symbols found)...done.
(gdb) target sim
Connected to the simulator.
(gdb) load
Loading section .text, size 0x16 lma 0x0
Start address 0x0
Transfer rate: 176 bits in <1 sec.
(gdb) break *0x0
Breakpoint 1 at 0x0
(gdb) 
(gdb) run
Starting program: /Users/hiroaki/home/Samples/Arduino/src/assem/a.out 

Program received signal SIGTRAP, Trace/breakpoint trap.
0x00000002 in start ()

(gdb) disas
Dump of assembler code for function start:
   0x00000000 <+0>:     mov     r16, r16
=> 0x00000002 <+2>:     ldi     r20, 0x02       ; 2
   0x00000004 <+4>:     ldi     r17, 0x01       ; 1
   0x00000006 <+6>:     lds     r18, 0x0040
   0x0000000a <+10>:    add     r18, r17
   0x0000000c <+12>:    sts     0x0040, r18
   0x00000010 <+16>:    lds     r19, 0x0040
   0x00000014 <+20>:    mov     r16, r16
End of assembler dump.

(gdb) info register
r0             0x0      0
r1             0x0      0
r2             0x0      0
r3             0x0      0
r4             0x0      0
r5             0x0      0
r6             0x0      0
r7             0x0      0
r8             0x0      0
r9             0x0      0
r10            0x0      0
r11            0x0      0
r12            0x0      0
r13            0x0      0
r14            0x0      0
r15            0x0      0
r16            0x0      0
r17            0x0      0
r18            0x0      0
r19            0x0      0
r20            0x0      0
r21            0x0      0
r22            0x0      0
r23            0x0      0
r24            0x0      0
r25            0x0      0
r26            0x0      0
r27            0x0      0
r28            0x0      0
r29            0x0      0
r30            0x0      0
r31            0x0      0
SREG           0x0      0
SP             0x0      0x0 <start>
PC2            0x2      2
pc             0x2      0x2 <start+2>

(gdb) x/16bx 0x0
0x0 <start>:    0x00    0x2f    0x42    0xe0    0x11    0xe0    0x20    0x91
0x8 <start+8>:  0x40    0x00    0x21    0x0f    0x20    0x93    0x40    0x00

(gdb) x/16bx 0x1
0x800001:       0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x800009:       0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00

gdb) si
0x00000004 in start ()
(gdb) disas
Dump of assembler code for function start:
   0x00000000 <+0>:     mov     r16, r16
   0x00000002 <+2>:     ldi     r20, 0x02       ; 2
=> 0x00000004 <+4>:     ldi     r17, 0x01       ; 1
   0x00000006 <+6>:     lds     r18, 0x0040
   0x0000000a <+10>:    add     r18, r17
   0x0000000c <+12>:    sts     0x0040, r18
   0x00000010 <+16>:    lds     r19, 0x0040
   0x00000014 <+20>:    mov     r16, r16

ここでは,以下の処理をしています.

  1. gdbで作成したプログラム(a.out)を実行(読み込み?)
  2. (gdb)target simでシミュレータで実行を指定
  3. (gdb)loadでプログラムを読み込み
  4. (gdb)break *0x0でプログラムメモリの0x0番地にブレイクポイントを設定
  5. (gdb)runで実行.0x0番地で止まる.
  6. (gdb)disasでgdbに読み込んだプログラムのディスアセンブル.現在実行する位置に矢印がある.
  7. (gdb)info registerでレジスタの値を表示
  8. (gdb)x/16bx 0x0で,プログラムメモリの0x0からバイト単位(b)で値を16進(x)で16回繰り返して表示する.なお,10進で表示する場合はxの代わりにd
  9. (gdb) x/16bx 0x1で,データメモリの0x0からバイト単位(b)で値を16進(x)で16回繰り返して表示する.なお,アドレスが0x800001となっているのは,gdbではデータメモリを表す場合0x800000がアドレスに加えられて表示される.実際はデータメモリの0x1番地となる.恐らく,プログラムメモリかデータメモリかを指定して表示させることはできない.プログラムメモリの0x1以降を見たい場合は,必ず0x0を指定してその代わり長さを256とかにして見るしかない(2016/8/24現在).
  10. (gdb)siでステップ実行.その後disasで本当に1ステップ実行したことを確認

P.S データメモリに0x80000が加えられるのは,gdbではなくてリンカの問題らしい(https://www.microchip.com/webdoc/AVRLibcReferenceManual/mem_sections_1sec_dot_data.html)

ここまでのプログラムは,以下の環境で試すことができる

>git clone https://github.com/hiro4669/iosv.git
>cd iosv
>git branch st_ver1 origin/st_ver1
>git checkout st_ver1
>cd startup
>make

リンカスクリプトを作成し,startup.sだけ実行

これまではstartup.sをavr-elf-asでアセンブリしたバイナリを実行していたが,これをavr-elf-gccを使ってコンパイルするようにMakefileを修正する.また,メモリマップを調整するためのリンカスクリプトを導入し,startup.sをコンパイルして実行する.

ld.scr
OUTPUT_FORMAT("elf32-avr")
OUTPUT_ARCH(avr)
ENTRY("start")

SECTIONS
{
        . = 0x0;
        .text : {
                *(.text)
        }
        .rodata : {
                *(.strings)
                *(.rodata)
                *(.rodata.*)
        }

        .data : {
                *(.data)
        }

        .bss : {
                *(.bss)
                *(COMMON)
        }
}
PREFIX = avr-elf
BASEDIR = /usr/core/$(PREFIX)
BINDIR = $(BASEDIR)/bin

AS = $(BINDIR)/$(PREFIX)-as
NM = $(BINDIR)/$(PREFIX)-nm
CC = $(BINDIR)/$(PREFIX)-gcc
LD = $(BINDIR)/$(PREFIX)-ld
OBJDMP = $(BINDIR)/$(PREFIX)-objdump
GDB = $(BINDIR)/avr-gdb

OBJS = startup.o
TARGET = tos

CFLAGS = -Wall -nostdinc -nostdlib -fno-builtin
CFLAGS += -mmcu=avr5
CFLAGS += -g

LFLAGS = -static -T ld.scr -L.

.SUFFIXES: .c .o
.SUFFIXES: .s .o

all:	$(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(OBJS) -o $(TARGET) $(CFLAGS) $(LFLAGS)
	cp $(TARGET) $(TARGET).elf

.c.o :	$<
	$(CC) -c $(CFLAGS) $<

.s.o :	$<
	$(CC) -c $(CFLAGS) $<




dump: $(TARGET).elf
	$(OBJDMP) -d $^

sym: $(TARGET).elf
	$(NM) $^

run: $(TARGET).elf
	$(GDB) $^

clean:
	rm -rf $(OBJS) $(TARGET) $(TARGET).elf

startup.sは変更せず,先の例と同じようにmake runなどを実行して正しく実行できていることを確認する.

ここまでのプログラムは,以下の環境で試すことができる

>git clone https://github.com/hiro4669/iosv.git
>cd iosv
>git branch st_ver2 origin/st_ver2
>git checkout st_ver2
>cd startup
>make

main.cを加え,startup.sからmainを呼び出す

以下に示すmain.cを作成し,startup.sから呼び出してみる.Arduino UnoはAtmega328pというMCUで動作する.Arduino UnoはこのAtmega328pに独自のブートローダをインストールした状態でArduinoのブートローダがROMに書き込まれた状態で出荷され,ユーザプログラムを呼び出すようになっている.また,このブートローダはアプリケーションプログラムをROM貼り付けたあと,リセットベクタと割り込みベクタの位置を変更し,0x0番地にリセットベクタ,0x2番地から割り込みベクタが設定されるようにMCUの設定を書き換える(詳しくはデータシート参照).
そして,これらの処理が終わると,PCを0に設定し,割り込みベクタにジャンプします.そのため,通常はその後に続く割り込みベクタを避け,スタートアップルーチンにジャンプすることでアプリケーションプログラムをスタートします.具体的には以下の様なコードを記述します.

reset:  jmp start
        jmp reset
        jmp reset
        jmp reset              

start:
        mov r0, r0
        ldi r28, 0x2

このソースコードををコンパイルし,make runして0x0でブレイクポイントを設定すると,以下のようにうまく動きません.

(gdb) break *0
Breakpoint 1 at 0x0: file startup.s, line 6.
(gdb) run
Starting program: /Users/hiroaki/home/Samples/Arduino/src/boot/tos.elf 

Program received signal SIGTRAP, Trace/breakpoint trap.
0x00000002 in reset () at startup.s:6
6       reset:  jmp start
(gdb) disas
Dump of assembler code for function reset:
   0x00000000 <+0>:     jmp     0x4     ;  0x4 <start>
End of assembler dump.
(gdb) si

Program terminated with signal SIGILL, Illegal instruction.
The program no longer exists.

ここでは,(gdb)siとステップ実行を試みても,プログラムが終了したと言われてしまいます.どうやら,gdbのシミュレータはjmp命令を使うとこうなるようです.

また,例えばstartのラベルのアドレスでブレイクしようとしても,0x0以外のアドレスはデータメモリだと認識されてしまい,ブレイクポイントを設定できません.そこで,C言語で書くプログラムの行番号を指定してブレイクポイントを仕込むとします.そして,そのブレイクポイントからステップ実行することで,そこまで書いたプログラムが正しく実行できていることを確認します.

C言語で書いたプログラムを呼び出す前に,最低限やらなければならないのはスタックポインタの設定です.Atmega328pのデータシートにも,アプリケーションプログラムの起動時にスタックポインタを設定するよう書かれています.Atmega328pのデータシートによると,データメモリは2KBでなので,0x0000から0x800までで,先頭256バイト(0x100)はレジスタやIO,拡張IOのアドレスとされているので,実際に使えるのは0x100から0x800までです.そこで,スタックポインタの初期値をほぼデータメモリの最後尾,0x7fcとして設定します.ただし,gnuではデータメモリは0x800000が加えられて表現されるので,0x8007fcとなります.

この設定を行うため,リンカスクリプト,およびstartup.sを変更します.

ld.scr
OUTPUT_FORMAT("elf32-avr")
OUTPUT_ARCH(avr)
ENTRY("start")

SECTIONS
{
        . = 0x0;
        .text : {
                *(.text)
        }
        .rodata : {
                *(.strings)
                *(.rodata)
                *(.rodata.*)
        }

        .data : {
                *(.data)
        }

        .bss : {
                *(.bss)
                *(COMMON)
        }

        . = 0x8007fc;

        .bootstack : {
                _bootstack = .;
        }

}

最後の数行が書き加えた部分で,オフセットを変更し,その後にシンボル'_bootstack'を追加しています.

次はstartup.sです.

startup.s
.equ TEXT, 0x1122
        .section .text
        .global start
        .type   start, @function
reset:  jmp start

start:
        mov r0, r0
        ldi r28, lo8(_bootstack)
        ldi r29, hi8(_bootstack)
        out 0x3d, r28
        out 0x3e, r29
        jmp call_main

call_main:
        rcall main

このプログラムはリンカスクリプトで0x0に配置されるので,jmp startがリセットベクタの位置に配置されることになり,まずこのコードが実行されます.これはstartねのジャンプ命令なので,そのままstartにジャンプします.次に,ldilo8hi8,_bootstackを使って_bootstackのアドレスの上位1バイトをr29に代入,下位1バイトをr28に代入し(メモリ空間は2バイト空間なので),それをout命令で0x3dと0x3eに代入します.このアドレスにはスタックポインタ(SP)が割り当てられているので,これでスタックポインタを設定したということになります.

その後,call_mainにジャンプして,そこからrcall命令でmainにジャンプします.このmainはC言語で書かれた関数です.以下に示します.

main.c
int main(void) {
   char a = 1;
   return (int)a;
}

これをコンパイルし,リンクするためにMakefileを書き換えます.

-OBJS = startup.o
+OBJS = startup.o main.o

OBJSにmain.oを書き足すだけです.
コンパイルし,make dumpしてみます


Disassembly of section .text:

00000000 <reset>:
   0:   0c 94 02 00     jmp     0x4     ; 0x4 <start>

00000004 <start>:
   4:   00 2c           mov     r0, r0
   6:   c2 e0           ldi     r28, 0x02       ; 2
   8:   d1 e0           ldi     r29, 0x01       ; 1
   a:   cc ef           ldi     r28, 0xFC       ; 252
   c:   d7 e0           ldi     r29, 0x07       ; 7
   e:   cd bf           out     0x3d, r28       ; 61
  10:   de bf           out     0x3e, r29       ; 62
  12:   0c 94 0b 00     jmp     0x16    ; 0x16 <call_main>

00000016 <call_main>:
  16:   00 d0           rcall   .+0             ; 0x18 <main>

00000018 <main>:
  18:   cf 93           push    r28
  1a:   df 93           push    r29
  1c:   0f 92           push    r0
  1e:   cd b7           in      r28, 0x3d       ; 61
  20:   de b7           in      r29, 0x3e       ; 62
  22:   81 e0           ldi     r24, 0x01       ; 1
  24:   89 83           std     Y+1, r24        ; 0x01
  26:   89 81           ldd     r24, Y+1        ; 0x01
  28:   99 27           eor     r25, r25
  2a:   87 fd           sbrc    r24, 7
  2c:   90 95           com     r25
  2e:   0f 90           pop     r0
  30:   df 91           pop     r29
  32:   cf 91           pop     r28
  34:   08 95           ret

0x0にresetが配置され,0x4にジャンプ,さらにcall_mainにジャンプしてrcallでmainを呼び出していることが分かります.

main runしてmainの1行目にブレイクポイントを設定します.

GNU gdb (GDB) 7.11
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-apple-darwin14.5.0 --target=avr".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from tos.elf...done.
(gdb) target sim
Connected to the simulator.
(gdb) load
Loading section .text, size 0x3c lma 0x0
Start address 0x4
Transfer rate: 480 bits in <1 sec.
(gdb) break 1
Breakpoint 1 at 0x28: file main.c, line 1.
(gdb) 

break 1で設定しています.(gdb)runします

(gdb) run
Starting program: /Users/hiroaki/home/Samples/Arduino/src/boot/tos.elf 

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000002a in main () at main.c:3
3          char a = 1;
(gdb) disas
Dump of assembler code for function main:
   0x0000001e <+0>:     push    r28
   0x00000020 <+2>:     push    r29
   0x00000022 <+4>:     push    r0
   0x00000024 <+6>:     in      r28, 0x3d       ; 61
   0x00000026 <+8>:     in      r29, 0x3e       ; 62
   0x00000028 <+10>:    ldi     r24, 0x01       ; 1
=> 0x0000002a <+12>:    std     Y+1, r24        ; 0x01
   0x0000002c <+14>:    ldd     r24, Y+1        ; 0x01
   0x0000002e <+16>:    eor     r25, r25
   0x00000030 <+18>:    sbrc    r24, 7
   0x00000032 <+20>:    com     r25
   0x00000034 <+22>:    pop     r0
   0x00000036 <+24>:    pop     r29
   0x00000038 <+26>:    pop     r28
   0x0000003a <+28>:    ret
End of assembler dump.

すると,0x28のldi r24, 0x01で止まり,PCが1つずれて0x2aで止まっていることが確認できます.
さらに,(gdb)info registerでレジスタの値を見てみます.

(gdb) info register
r0             0x0      0
r1             0x0      0
r2             0x0      0
r3             0x0      0
r4             0x0      0
r5             0x0      0
r6             0x0      0
r7             0x0      0
r8             0x0      0
r9             0x0      0
r10            0x0      0
r11            0x0      0
r12            0x0      0
r13            0x0      0
r14            0x0      0
r15            0x0      0
r16            0x0      0
r17            0x0      0
r18            0x0      0
r19            0x0      0
r20            0x0      0
r21            0x0      0
r22            0x0      0
r23            0x0      0
r24            0x0      0
r25            0x0      0
r26            0x0      0
r27            0x0      0
r28            0xf7     247
r29            0x7      7
r30            0x0      0
r31            0x0      0
SREG           0x0      0
SP             0x8007f7 0x8007f7
PC2            0x2a     42
pc             0x2a     0x2a <main+12>

これをみると,スタックポインタが0x8007f7であり.先ほど設定したスタックポインタは0x7fcだったので5バイトずれています.これは,PCの保存で2バイト,mainの先頭でpushを3回実行しているので,合計5バイトスタックに積んでいることが予想でき,辻褄が合いそうです.
また,詳細は省略しますが,この後(gdb)siでステップ実行していくと,C言語の関数を抜けたところでstartup.sに戻ることが確認できます.

ここまでのプログラムは,以下の環境で試すことができる

>git clone https://github.com/hiro4669/iosv.git
>cd iosv
>git branch st_ver3 origin/st_ver3
>git checkout st_ver3
>cd startup
>make

P.S gcc-4.8.5で試したところ,mainでpushはしないバイナリが出力されました.

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?