手動Arduino(Adafruit nRF52編)
SWDが無ければ即死だった。。SWDデバッグは↓に書いた。
"手動Arduino" とは、Arduino IDEを使わずにIDEをビルドすることでArduinoでないと使えないマイコンボードを普通の開発環境で活用しましょうという取り組み。だってESP32もRaspberry Pi PicoもビルドシステムがCMakeなのに他がビルドシステム違うとか超不便じゃん。。
とりあえずリポジトリは:
そのうち、ESP-IDFとかSpresenseのような他の奴と纏めたリポジトリを用意したい。
前回
前回はWio TerminalのSAMD51の奴をやった。これはAdafruit nRF52の元になった https://github.com/sandeepmistry/arduino-nRF5 の元になっているので、これらは親戚同士と言える。
そもそも起動しない
普通にcrosstool-NGでビルドしたnewlibでビルドすると、全く動作しなかった。
SWDで止まったところを観察してみると、 Newlibのスタートアップで bkpt AngelSWI
でfaultしていた。...これデフォルトで有効なの。。?
とりあえず、gdbを繋いで c
を連射したらちゃんと実行されたのでとりあえずコレで。。 ARM_RDI_MONITOR
がdefineされた経緯を調べる必要があるな。。
# If newlib is supplying syscalls, select which debug protocol is being used.
# ARM_RDP_MONITOR selects the Demon monitor.
# ARM_RDI_MONITOR selects the Angel monitor.
# If neither are defined, then hard coded defaults will be used
# to create the program's environment.
# If --disable-newlib-supplied-syscalls is specified, then the end-user
# may specify the protocol via gcc spec files supplied by libgloss.
if [ "x${newlib_may_supply_syscalls}" = "xyes" ] ; then
# newlib_cflags="${newlib_cflags} -DARM_RDP_MONITOR"
newlib_cflags="${newlib_cflags} -DARM_RDI_MONITOR"
fi
;;
うーん。。やっぱりcrosstool-NGでsyscallを外し忘れたからかな。。
USB-CDCのUARTが初期化されない
これも gdb で追っていくと
(gdb) s
Adafruit_USBD_CDC::isValid (this=0x20007620 <Serial>)
at ../Adafruit_nRF52_Arduino/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_CDC.h:85
85 bool isValid(void) { return _instance != INVALID_INSTANCE; }
この isValid
が false で、そもそも初期化されていないことがわかった。
...どうもTinyUSB用のAPIがweakシンボルになっているようで、リンカが正常に参照を拾わず、 実行自体が省略 されてしまっているようだ。
// Called by core/sketch to initialize usb device hardware and stack
// This also initialize Serial as CDC device
void TinyUSB_Device_Init(uint8_t rhport) __attribute__((weak));
// Called by core/sketch to handle device event
void TinyUSB_Device_Task(void) __attribute__((weak));
// Called by core/sketch to flush write on CDC
void TinyUSB_Device_FlushCDC(void) __attribute__((weak));
というわけで --whole-archive
を指定して全部の .o
をリンク対象に含めて未使用関数の削除は --gc-sections
に任せることにした。
そもそもリンカがコードを変更できるの!?
C言語としてはnull dereferenceは未定義挙動なので何が起きても文句は言えない。けどもうちょっと安全に失敗しても良い気がする。。call命令がcall以外になってるわけで。。
例えば:
void NEVERLAND(void) __attribute__((weak));
int
main(int ac,char** av){
NEVERLAND();
return 0;
}
のようなコードがあったとして、普通に .o
を出力させると、
$ arm-none-eabihf-objdump -S check.o
check.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <main>:
0: b580 push {r7, lr}
2: b082 sub sp, #8
4: af00 add r7, sp, #0
6: 6078 str r0, [r7, #4]
8: 6039 str r1, [r7, #0]
a: f7ff fffe bl 0 <NEVERLAND> ;; ★ 未解決のシンボルに対する bl
e: 2300 movs r3, #0
10: 4618 mov r0, r3
12: 3708 adds r7, #8
14: 46bd mov sp, r7
16: bd80 pop {r7, pc}
のように、ブランチ命令 bl
が生成され、関数を呼出したい気持ちは逆アセンブリに現われている。しかし、これをリンクすると:
00008184 <main>:
8184: b580 push {r7, lr}
8186: b082 sub sp, #8
8188: af00 add r7, sp, #0
818a: 6078 str r0, [r7, #4]
818c: 6039 str r1, [r7, #0]
818e: f3af 8000 nop.w ;; ★ nop になっている
8192: 2300 movs r3, #0
8194: 4618 mov r0, r3
8196: 3708 adds r7, #8
8198: 46bd mov sp, r7
819a: bd80 pop {r7, pc}
のように、 nop
で上書きされてしまう。
普通のOSでは、ダイナミックリンクが行われる都合上このように未解決のweakシンボルを nop
で埋めることは行われない。そもそも weak シンボルの意味合い自体が環境によって割と異なると言える。
ツールチェーンをリビルドしたら動かなくなった
[1/1] Linking CXX executable target
/opt/yuniboard/arm-m4f/lib/gcc/arm-none-eabihf/11.2.0/../../../../arm-none-eabihf/bin/ld: warning: start of section .text changed by 12
"warning: start of section .text changed by 12" ... なぜズレるんだ。。
oku@stripe ~/repos/nrftest/build
$ /opt/yuniboard/arm-m4f/bin/arm-none-eabi-objdump.exe -h target
target: file format elf32-littlearm
Sections:
Idx Name Size VMA LMA File off Algn
0 .note.gnu.build-id 00000024 00026000 00026000 00006000 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .text 0000b664 00026030 00026030 00006030 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .ARM.exidx 00000008 00031694 00031694 00011694 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
.text
の前に何か入ってるじゃねぇか。。 ldscriptで落とさないとダメだな。。
とりあえず -Wl,--build-id=none
でお茶を濁す。とりあえずこれで正常に起動するようになった。