FreeRTOSの動作をLLDBで追っかける
プログラムの動作を追うにはデバッガを使うのが手っ取り早い。ということでgdbを使おうと思ったら、コード署名しろというエラーが。おそらくこのgdbはbrewでインストールしたもののはず。ウェブを探すとコード署名する方法は見つかるが、いい機会なのでLLDBを使ってFreeRTOSの動作を追っかけてみる。正確にはPOSIXシミュレータなので、リアルなものとの違いはある。
(gdb) run
Starting program: /Users/ryousei/Devel/FreeRTOS/FreeRTOS/Demo/Posix_GCC/build/posix_demo
Unable to find Mach task port for process-id 77007: (os/kern) failure (0x5).
(please check gdb is codesigned - see taskgated(8))
(当然ながら)コマンドに互換性はないので、ウェブやhelpコマンドで調べつつ使ってみる。GDBを知っているのであれば、特に戸惑うことはないはず。スタックフレームやスレッドがなんぞやというのは知っている前提で進める。コマンドの対照表はここ。
% lldb build/posix_demo
(lldb) target create "build/posix_demo"
Current executable set to '<snip>/FreeRTOS/FreeRTOS/Demo/Posix_GCC/build/posix_demo' (x86_64).
(lldb)
まずはブレイクポイントを設定する。試しにxTaskCreate関数にセットして、実行開始。補完機能が効くので、ターゲットの関数名を正確に覚えてなくても、なんとかなる。
(lldb) breakpoint set --name xTaskCreate
Breakpoint 1: where = posix_demo`xTaskCreate + 31 at tasks.c:765:50, address = 0x000000010000ceef
(lldb) r
Process 79205 launched: '<snip>/FreeRTOS/FreeRTOS/Demo/Posix_GCC/build/posix_demo' (x86_64)
Trace started.
The trace will be dumped to disk if a call to configASSERT() fails.
The trace will be dumped to disk if Enter is hit.
Starting echo blinky demo
Process 79205 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x000000010000ceef posix_demo`xTaskCreate(pxTaskCode=(posix_demo`prvQueueReceiveTask at main_blinky.c:230), pcName="Rx", usStackDepth=70, pvParameters=0x0000000000000000, uxPriority=2, pxCreatedTask=0x0000000000000000) at tasks.c:765:50
762 StackType_t * pxStack;
763
764 /* Allocate space for the stack used by the task being created. */
-> 765 pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */
766
767 if( pxStack != NULL )
768 {
Target 0: (posix_demo) stopped.
LLDBをr(un)すると、ブレイクポイントで引っかかって、実行が止まる。フレーム情報も表示されているので、Rxタスクが生成されるところだとわかる。しばらくc(ontinue)で先に進める。
(lldb) c
Process 79205 resuming
Message received from task
Message received from task
Message received from task
Message received from task
Process 79205 stopped
thread #4, stop reason = signal SIGALRM
frame #0: 0x00007fff20367c22 libsystem_kernel.dylib`__semwait_signal + 10
libsystem_kernel.dylib`__semwait_signal:
-> 0x7fff20367c22 <+10>: jae 0x7fff20367c2c ; <+20>
0x7fff20367c24 <+12>: movq %rax, %rdi
0x7fff20367c27 <+15>: jmp 0x7fff2036672d ; cerror
0x7fff20367c2c <+20>: retq
Target 0: (posix_demo) stopped.
(lldb)
タスクの実行が始まるので、Ctrl+cでSIGALRMを送って、実行を止める。次はpxCurrentTCB変数にウォッチポイントを設定して、コンテキストスイッチのタイミングを調べてみよう。
(lldb) watchpoint set variable pxCurrentTCB
Watchpoint created: Watchpoint 1: addr = 0x100031e60 size = 8 state = enabled type = w
watchpoint spec = 'pxCurrentTCB'
new value: 0x00000001000302d8
(lldb) c
Process 79205 resuming
Watchpoint 1 hit:
old value: 0x0000000108504360
new value: 0x00000001085046c0
Process 79205 stopped
* thread #2, stop reason = watchpoint 1
frame #0: 0x000000010000e4b4 posix_demo`vTaskSwitchContext at tasks.c:3062:9
3059
3060 /* Select a new task to run using either the generic C or port
3061 * optimised asm code. */
-> 3062 taskSELECT_HIGHEST_PRIORITY_TASK(); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
3063 traceTASK_SWITCHED_IN();
3064
3065 /* After the new task is switched in, update the global errno. */
Target 0: (posix_demo) stopped.
pxCurrentTCBが0x0000000108504360から0x00000001085046c0に変わった。これの実体はTCB_t *型だと知っているので、p(rint)コマンドで構造体の中身を表示する。それぞれRxタスクとTXタスクだということがわかる。
(lldb) p *(TCB_t *)0x0000000108504360
(TCB_t) $0 = {
pxTopOfStack = 0x0000000108504330
xStateListItem = {
xItemValue = 0
pxNext = 0x0000000100030730
pxPrevious = 0x0000000100030730
pvOwner = 0x0000000108504360
pvContainer = 0x0000000100030720
}
xEventListItem = {
xItemValue = 5
pxNext = 0x00000001085040d8
pxPrevious = 0x00000001085040d8
pvOwner = 0x0000000108504360
pvContainer = 0x00000001085040c8
}
uxPriority = 2
pxStack = 0x0000000108504130
pcTaskName = "Rx"
uxTCBNumber = 1
uxTaskNumber = 65538
uxBasePriority = 2
uxMutexesHeld = 0
pxTaskTag = 0x0000000000000000
ulRunTimeCounter = 1
ulNotifiedValue = ([0] = 0)
ucNotifyState = ""
ucStaticallyAllocated = '\0'
ucDelayAborted = '\0'
}
(lldb) p *(TCB_t *)0x00000001085046c0
(TCB_t) $1 = {
pxTopOfStack = 0x0000000108504690
xStateListItem = {
xItemValue = 800
pxNext = 0x0000000100030788
pxPrevious = 0x0000000100030788
pvOwner = 0x00000001085046c0
pvContainer = 0x0000000100030778
}
xEventListItem = {
xItemValue = 6
pxNext = NULL
pxPrevious = NULL
pvOwner = 0x00000001085046c0
pvContainer = NULL
}
uxPriority = 1
pxStack = 0x0000000108504490
pcTaskName = "TX"
uxTCBNumber = 2
uxTaskNumber = 65539
uxBasePriority = 1
uxMutexesHeld = 0
pxTaskTag = 0x0000000000000000
ulRunTimeCounter = 0
ulNotifiedValue = ([0] = 0)
ucNotifyState = ""
ucStaticallyAllocated = '\0'
ucDelayAborted = '\0'
}
バックトレース(bt)を見ると、xQueueReceiveからxTaskResumeAllが呼ばれているので、Rxタスクがキューからの受信待ち(Suspended状態)になって、実行権を放棄し、TXタスクがResumeされたことになる。
(lldb) bt
* thread #2, stop reason = watchpoint 1
* frame #0: 0x000000010000e4b4 posix_demo`vTaskSwitchContext at tasks.c:3062:9
frame #1: 0x0000000100013c6e posix_demo`vPortYieldFromISR at port.c:275:5
frame #2: 0x0000000100013d1e posix_demo`vPortYield at port.c:287:5
frame #3: 0x000000010000da20 posix_demo`xTaskResumeAll at tasks.c:2301:21
frame #4: 0x0000000100008d11 posix_demo`xQueueReceive(xQueue=0x0000000108504080, pvBuffer=0x0000000304218f84, xTicksToWait=18446744073709551615) at queue.c:1427:21
frame #5: 0x0000000100003786 posix_demo`prvQueueReceiveTask(pvParameters=0x0000000000000000) at main_blinky.c:242:3
frame #6: 0x000000010001386f posix_demo`prvWaitForStart(pvParams=0x0000000108504338) at port.c:435:5
frame #7: 0x00007fff2039a954 libsystem_pthread.dylib`_pthread_start + 224
frame #8: 0x00007fff203964a7 libsystem_pthread.dylib`thread_start + 15
なおPOSIXシミュレータではタスクはPスレッドを使って実装されている。#2がRx、#3がTX、#4がIDLE、#5がタイマーになっているようだ。
(lldb) thread list
Process 79205 stopped
thread #1: tid = 0x15c463, 0x00007fff2036de3a libsystem_kernel.dylib`__sigwait + 10, queue = 'com.apple.main-thread'
* thread #2: tid = 0x15c486, 0x000000010000e4b4 posix_demo`vTaskSwitchContext at tasks.c:3062:9, stop reason = watchpoint 1
thread #3: tid = 0x15c487, 0x00007fff20367d4e libsystem_kernel.dylib`__psynch_cvwait + 10
thread #4: tid = 0x15c488, 0x00007fff20367d4e libsystem_kernel.dylib`__psynch_cvwait + 10
thread #5: tid = 0x15c489, 0x00007fff20367d4e libsystem_kernel.dylib`__psynch_cvwait + 10
t(hread)コマンドでスレッドを行き来して、スタックフレームをup/downして眺めているうちに大まかな動作の流れがイメージできる(はず)。例えば、上記の例だと、RxタスクはprvSuspendSelf関数で自発的にSuspended状態に遷移するが、port実装としてはpthread_cond_waitが呼ばれている。さらにその先のpsynch_cvwaitはmacOS(XNUカーネル)のシステムコールかな。
ちなみにここではRosetta 2を使ってx86_64版を動かしているが、arm64ネイティブでも同様に動く。