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ネイティブでも同様に動く。

この記事が気に入ったらサポートをしてみませんか?