OpenOCD、ThreadX、およびプロセッサ

このメモは、ベアメタルコードを記述し、タスクでThreadXを使用する(自分で選択するか、SDKを課す)場合に役立ちます。 問題は、ThreadXまたは別のマルチスレッドオペレーティングシステムでコードを効果的にデバッグするために、これらのスレッドを確認し、スタックトレース、各スレッドのレジスタの状態を確認できる必要があることです。



OpenOCD( Open On Chip DebuggerはThreadXのサポートを主張していますが、その幅を明示的に指定していません。 通常、執筆時点では、バージョン0.8.0では、Cortex M3とCortex R4の2つのコアしかありません。 私は、運命の意志により、ARM926E-JSコアに基づくサイプレスFX3チップを使用する必要がありました。



猫の下で、CPUのThreadXのバージョンのサポートを追加するために何をする必要があるかを検討してください。 ARMに重点が置かれていますが、理論的には他のプロセッサに適している可能性があります。 さらに、ThreadXソースへのアクセスが想定されていない場合と想定されていない場合を検討します。



最初の行から、私はすぐに失望します。アセンブラなしで、どこにもいません。 いいえ、書く必要はありませんが、はい、コードを読みます。



OpenOCDにThreadXサポートを導入することから始めましょう。 これは、 src / rtos / ThreadX.cという 1つのファイルです。



サポートされるシステムは、 ThreadX_params構造体によって記述されます。これには、ターゲット名、バイト単位のポインターの「幅」、必要なサービスフィールドへのTX_THREAD構造体のオフセットのセットに関する情報、および切り替え中にストリームのコンテキストが保存される方法に関する情報(いわゆるスタッキング情報)が含まれます) サポートされるシステム自体は、 ThreadX_params_list配列を使用して登録されます。



最後のパラメーターを除くすべてのパラメーターに問題はありません。通常、ポインターの幅はプロセッサーの容量に等しく、オフセットはハンドルと見なされます(それでも、ほとんど常に同じです)。



興味深い質問:スタッキングに関する情報はどこで入手できますか? しかし、多くの情報があります。



この最後のものは、最も複雑でわかりにくいものです。 私はすぐにあなたを納得させることができます-ここには標準的なアプローチがありません。 これらの値を選択することは、不可能ではないにしても、非常に困難です。



さらに、Cortex M3 / R4コアで判明したように、1つのスタッキングスキームが使用され、ARM926E-JSでは2つが使用されています。 すべては経済のためです。



簡潔に(そして非常に失礼で不正確)、シェラダーがThreadXでどのように機能するか:同時に、マルチタスクへの協調的で混雑したアプローチを提供します。



協調的アプローチは、タイムスライス(0)を持たない同じ優先度のスレッドに対して機能します。 つまり ストリームAとBの優先順位が同じ場合、ストリームAが開始され、ストリームBはAまで制御を受け取りません。



タイムスライスが設定されている場合、完了時にフローは中断され、制御は準備完了状態の次のスライスに転送されます(フローがスリープ状態になったが、独自のスライスを開発していない場合、協調アプローチも機能します)。 ここでは、先制的なアプローチがすでに機能しています。 その動作には、タイマーが必要であり、一定の周期でタイマーから割り込みます。 また、上記の例のストリームAは、優先度が高い場合、ストリームBに置き換えることができます。



ストリームのコンテキストは、誰かに制御を移したときに保持され、制御を受け取ったときに復元されることは明らかです。 これがどのように発生するかを理解します。レジスタオフセットの配列で何を記述する必要があるかを理解します。



スケジューラの主要な部分がどこにどのように隠されているかをどのように発見したかについては詳しく説明しません;その多くはそれに精通しました:精通、幸運、Google、および逆アセンブラー。 しかし、私はその主なコンポーネントを提供します:

  1. _tx_timer_interrupt() -この関数は、タイマー割り込みのコンテキストから呼び出されます。実際、スケジューラーの一部をクラウディングアウトする責任があります。
  2. _tx_thread_context_save() (または_tx_thread_vectored_context_save() )および_tx_thread_context_restore()は、コンテキストを保存および復元するために割り込みから呼び出されるように設計された関数のペアです。 コンテキストを復元すると、再スケジュールが試行されます。
  3. _tx_thread_system_return()は、協調的アプローチの一部です。 解決につながる呼び出しのチェーンの最後に呼び出されます。
  4. そして最後に、 _tx_thread_schedule()は分析のための最も重要な関数であり、おそらく上記の中で最も単純な関数です。


これらすべての機能のリストを調べましたが、サポートされていないプロセッサーのサポートを再度ねじ込む必要がある場合は、最後の3つに焦点を当てます。 しかし、私は後者から始め、その後(十分な情報がなければ)他の人を勉強します。



そのリストを見てみましょう(いくつかの間接的なアドレス指定を実際の文字、文字自体に置き換えました

arm-none-eabi-nmを使用してelfファイルを調べます):

40004c7c <_tx_thread_schedule>: 40004c7c: e10f2000 mrs r2, CPSR 40004c80: e3c20080 bic r0, r2, #128 ; 0x80 40004c84: e12ff000 msr CPSR_fsxc, r0 40004c88: e59f104c ldr r1, [pc, #76] ; 40004cdc <_tx_thread_schedule+0x60> 40004c8c: e5910000 ldr r0, [r1] 40004c90: e3500000 cmp r0, #0 40004c94: 0afffffc beq 40004c8c <_tx_thread_schedule+0x10> 40004c98: e12ff002 msr CPSR_fsxc, r2 40004c9c: e59f103c ldr r1, [pc, #60] ; 40004ce0 <_tx_thread_schedule+0x64> 40004ca0: e5810000 str r0, [r1] 40004ca4: e5902004 ldr r2, [r0, #4] 40004ca8: e5903018 ldr r3, [r0, #24] 40004cac: e2822001 add r2, r2, #1 40004cb0: e5802004 str r2, [r0, #4] 40004cb4: e59f2028 ldr r2, [pc, #40] ; 40004ce4 <_tx_thread_schedule+0x68> 40004cb8: e590d008 ldr sp, [r0, #8] 40004cbc: e5823000 str r3, [r2] 40004cc0: e8bd0003 pop {r0, r1} 40004cc4: e3500000 cmp r0, #0 40004cc8: 116ff001 msrne SPSR_fsxc, r1 40004ccc: 18fddfff ldmne sp!, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}^ 40004cd0: e8bd4ff0 pop {r4, r5, r6, r7, r8, r9, sl, fp, lr} 40004cd4: e12ff001 msr CPSR_fsxc, r1 40004cd8: e12fff1e bx lr 40004cdc: 4004b754 .word 0x4004b754 ; _tx_thread_execute_ptr 40004ce0: 4004b750 .word 0x4004b750 ; _tx_thread_current_ptr 40004ce4: 4004b778 .word 0x4004b778 ; _tx_timer_time_slice
      
      





この関数はシンプルに夢中です:

  1. 割り込みを有効にする(行40004c7c-40004c84)
  2. 誰かが_tx_thread_execute_ptr (40004c88-40004c94)をコックするのを待ちます-実行する次のスレッド
  3. 中断を禁止するか、ステータスレジスタを復元します(40004c98)
  4. _tx_thread_current_ptrポインターをr0に保存(40004c9c-40004ca0)
  5. 現在のスレッドのtx_thread_run_count値を1増やします(40004ca4、40004cac-40004cb0)
  6. 現在のスレッドの値tx_thread_time_slic eを取得し、 _tx_timer_time_slice (40004ca8、40004cb4、40004cbc)を割り当てます
  7. スレッド構造に格納されているスタックに新しいポインターを設定します(tx_thread_stack_ptrを読み取ります )(40004cb8)


しかし、40004cb8からは、実際には新しいストリームのコンテキストを復元するコードがあります。



まず、2つの値がレジスタr0r1に読み込まれます。

 40004cc0: e8bd0003 pop {r0, r1}
      
      





次に、 r0とゼロの比較です。

 40004cc4: e3500000 cmp r0, #0
      
      





明らかに、少なくともr0のこれらの値はコンテキストの一部です(結局、スタックレジスタは復元されたスレッドのスタックで既に構成されています)が、これらはレジスタのようには見えません。 ゼロとの比較は、ある種の分岐を意味します。 分析を続けると、 r0!= 0の場合、コードが実行されることがわかります。

 40004cc8: 116ff001 msrne SPSR_fsxc, r1 40004ccc: 18fddfff ldmne sp!, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}^
      
      





実際、これはコンテキストの復元に似ています。 さらに、レジスタr1の値は、ステータスレジスタCPSRの保存値です。 行40004cccが実行されると、制御はそれ以上進みません。pc( r15 )レジスタが復元され、このポイント以降のプログラムは中断された場所に戻ります。



さて、今ではこのようなタブレットを書くことができます:

  オフセットレジスタ
   -------- -------
   0フラグ
   4 CPSR
   8 r0
   12 r1
   16 r2
   20 r3
   24 r4
   28 r5
   32 r6
   36 r7
   40 r8
   44 r9
   48 sl(r10)
   52 fp(r11)
   56 ip(r12)
   60 lr(r14)
   64個(r15)


各レジスタと各フラグはそれぞれ32ビットまたは4バイトであり、このコンテキストには17 * 4 = 68バイトが必要です。 中断時のように、スタックが継続することは論理的です。



しかし、見てのとおり、これは作業の一部です。 このフラグがあります。 値が0の場合、コードが実行されます:

 40004cd0: e8bd4ff0 pop {r4, r5, r6, r7, r8, r9, sl, fp, lr} 40004cd4: e12ff001 msr CPSR_fsxc, r1 40004cd8: e12fff1e bx lr
      
      





どうやら、これもコンテキストであり、わずかに減少しています。 さらに、そこから戻ることは、 pcレジスタを回復することではなく、通常の関数から発生します。 上記のプレートを書き換えると、次のようになります。

  オフセットレジスタ
   -------- -------
   0フラグ
   4 CPSR
   8 r4
   12 r5
   16 r6
   20 r7
   24 r8
   28 r9
   32 sl(r10)
   36 fp(r11)
   40 lr(r14)


このコンテキストでは、11 * 4 = 44バイトのみが必要です。



Googleを使用して、逆アセンブラーのリストを表示し、プロシージャを呼び出すための規則を調べると、協調型マルチタスクが機能するときにこのタイプのコンテキストが使用されることがわかります。 tx_thread_sleep()またはそれらのような他のものを呼び出したとき。 そして以来 実際、このようなスイッチは単なる関数呼び出しであり、呼び出し規則に従ってコンテキストを保存できます。これにより、呼び出し間でレジスタr0〜r3、r12の値を保存しない権利があります。 さらに、pcを保存する必要はありません-必要な情報はすべてrlに既に含まれています-tx_thread_sleep()からのアドレスを返します。 顔に恩恵を。 皮質は通常、ARM9Eよりも多くのメモリを搭載したシステムで使用されますが、そのようなトリックに頼らず、1種類のスタッキングを使用します。



インターネットからの情報によると、最初の種類のコンテキストは割り込みと呼ばれ、ストリームが割り込みによって中断されるときに使用される、つまりどこでも中断できるため、すべての可能なレジスタを保存する必要があることがわかりました。 2番目のタイプのコンテキストは要請型と呼ばれ、システムコールによってスレッドが中断されたときに使用され、再スケジュールにつながります。



これで、OpenOCDで必要な変更を理解する準備が整いました。



最初の段落のコードは提供しません。パッチを見てください。 ポイント2では、OpenOCDに適したディスプレイスメントラベルを作成する方法を少し説明します。



まず、「info register」コマンドの出力を見て、表示されるレジスタの数と順序を見て、そのような魚を作成します。

 static const struct stack_register_offset rtos_threadx_arm926ejs_stack_offsets_solicited[] = { { , 32 }, /* r0 */ { , 32 }, /* r1 */ { , 32 }, /* r2 */q { , 32 }, /* r3 */ { , 32 }, /* r4 */ { , 32 }, /* r5 */ { , 32 }, /* r6 */ { , 32 }, /* r7 */ { , 32 }, /* r8 */ { , 32 }, /* r9 */ { , 32 }, /* r10 */ { , 32 }, /* r11 */ { , 32 }, /* r12 */ { , 32 }, /* sp (r13) */ { , 32 }, /* lr (r14) */ { , 32 }, /* pc (r15) */ { , 32 }, /* xPSR */ };
      
      





ここで、32はレジスタのビットサイズです。 ARMの場合、常に32です。最初の列は、コンテキスト回復を分析するときに上で書いたプレートを使用して入力されます。 特別な値を考慮します:-1-このレジスタは保存されません、-2-スタックレジスタは、ストリーム構造から復元されます。



要請されたコンテキストで満たされた魚は次のとおりです。

 static const struct stack_register_offset rtos_threadx_arm926ejs_stack_offsets_solicited[] = { { -1, 32 }, /* r0 */ { -1, 32 }, /* r1 */ { -1, 32 }, /* r2 */ { -1, 32 }, /* r3 */ { 8, 32 }, /* r4 */ { 12, 32 }, /* r5 */ { 16, 32 }, /* r6 */ { 20, 32 }, /* r7 */ { 24, 32 }, /* r8 */ { 28, 32 }, /* r9 */ { 32, 32 }, /* r10 */ { 36, 32 }, /* r11 */ { -1, 32 }, /* r12 */ { -2, 32 }, /* sp (r13) */ { 40, 32 }, /* lr (r14) */ { -1, 32 }, /* pc (r15) */ { 4, 32 }, /* xPSR */ };
      
      





割り込みコンテキストについては、自分で記述するか、ソースを確認してください。



それは何を与えます:



gdb用のコマンド。



全体として、幸せなデバッグ!



リソース:





PS十分な「リバース開発」ハブがなく、異なるアセンブラー向けの強調表示がありません;-)



UPD / 2015-08-15 /:OpenOCDのメインブランチであるopenocd.zylin.com/#/c/2848の変更点



All Articles