1. 背景知識1. ARM64レジスタの紹介2. STP命令の詳しい説明(ARMV8マニュアル)まずは、命令フォーマット(64bit)とレジスタマシンの実行結果に対する命令の影響について見てみましょう。 タイプ 1、STP <Xt1>、<Xt2>、[<Xn|SP>]、#<imm> Xt1とXt2をXn|SPに対応するアドレスメモリに格納し、Xn|SPのアドレスをXn|SPの新しいアドレス+immオフセットに変更します。 タイプ 2、STP <Xt1>、<Xt2>、[<Xn|SP>、#<imm>]! Xt1とXt2をXn|SPプラスimmのアドレスに対応するアドレスメモリに格納し、Xn|SPのアドレスをXn|SP + immのオフセット後の新しいアドレスに変更します。 タイプ3、STP <Xt1>、<Xt2>、[<Xn|SP>{、#<imm>}] Xt1とXt2をXn|SPプラスimmのアドレスに対応するアドレスメモリに格納する マニュアルには 3 種類のオペコードがありますが、ここではプログラムに関係する最後の 2 種類についてのみ説明します。 疑似コードは次のとおりです。 すべてのエンコードの共有デコード 整数 n = UInt(Rn); 整数 t = UInt(Rt); 整数 t2 = UInt(Rt2); L:opc<0> == '01' || opc == '11' の場合、未定義です。 整数スケール = 2 + UInt(opc<1>); 整数データサイズ = 8 << スケール; ビット(64)オフセット = LSL(SignExtend(imm7, 64), スケール); ブール型 tag_checked = wback || n != 31; すべてのエンコーディングに対する操作 ビット(64) アドレス; ビット(データサイズ) data1; ビット(データサイズ) data2; 定数整数 dbytes = データサイズ DIV 8; ブール値 rt_unknown = FALSE; HaveMTEExt() の場合 タグチェックされていない命令を設定します(!tag_checked); wback && (t == n || t2 == n) && n != 31の場合 制約 c = ConstrainUnpredictable(); c が {Constraint_NONE、Constraint_UNKNOWN、Constraint_UNDEF、Constraint_NOP} であることをアサートします。 ケースcの Constraint_NONE rt_unknown = FALSE; // 保存された値は書き戻し前です Constraint_UNKNOWN rt_unknown = TRUE; // 格納される値は UNKNOWN です Constraint_UNDEF が UNDEFINED の場合; Constraint_NOP EndOfInstruction() の場合; n == 31の場合 SPAlignment をチェックします。 アドレス = SP[]; それ以外 アドレス = X[n]; !postindexの場合 アドレス = アドレス + オフセット; rt_unknown && t == n の場合 data1 = ビット(データサイズ) 不明; それ以外 データ1 = X[t]; rt_unknown && t2 == n の場合 data2 = ビット(データサイズ) 不明; それ以外 データ2 = X[t2]; Mem[アドレス、dbytes、AccType_NORMAL] = data1; Mem[アドレス+dbytes、dbytes、AccType_NORMAL] = data2; もしも当時 postindexの場合 アドレス = アドレス + オフセット; n == 31の場合 SP[] = アドレス; それ以外 X[n] = アドレス; 赤い部分はスタックプッシュのキーロジックに対応します。その他のアセンブリ命令の意味については、armv8マニュアルまたはBaiduを参照してください。 2. 例上記の部分について理解できたので、例を見てみましょう。 C コードは次のとおりです。 いくつかの関連する関数の逆アセンブリは次のとおりです (通常、スタック プッシュに関連する命令は 2 つだけです)。 メイン\f3\f4\strlen gdbを実行すると、strlenがSEGFAULTを引き起こし、プロセスがハングすることがわかります。 上記のコードをコンパイルするとストリップがなくなるので、elfファイルにはシンボルが 実行ステータス(情報レジスタ)を確認します。4つのレジスタ$29、$30、SP、PCに注意してください。 核となる考え方: CPU は C コードではなく命令を実行し、関数の呼び出しと戻りは実際にはスレッド スタックをプッシュおよびポップするプロセスです。 次に、上記の呼び出し関係が現在のタスク スタックでどのように機能するかを見てみましょう。 スタック内の関数呼び出し間の関係 (関数呼び出しはスタックをプッシュし、アドレスが減少します。関数を返すとスタックがポップされ、アドレスが増加します): 以下はスタックをプッシュするプロセスです(強調) 前回のまとめを振り返ってみましょう。 メイン\f3\f4\strlen 現在のspから開始して、フレーム0はstrlenであり、スタックは開かれていないため、前のレベルの呼び出し関数はx30のままであり、フレーム1がf3を呼び出すと推測できます。 関数 f3 の開始エントリ アセンブリ: (gdb) x/2i f3 0x400600 <f3>: stp x29、x30、[sp、#-48]! 0x400604 <f3+4>: 移動 x29、sp f3関数によって開かれたスタック空間は48バイトであることがわかります。したがって、フレーム2のスタックの最上部は、現在のsp + 48バイトです:0xffffffffff2c0 (gdb) x/gx 0xfffffffff2c0+8 0xfffffffff2c8: 0x000000000040065c (gdb)x/i 0x000000000040065c 0x40065c <f4+36>: 移動 w0, #0x0 // #0 フレーム2の機能はsp+8: 0x000000000040065c -> <f4+36> フレーム1の関数をsp = 0xfffffffff2c0からプッシュバックし続けます 関数 f4 の開始エントリ アセンブリは次のとおりです。 (gdb) x/2i f4 0x400638 <f4>: stp x29、x30、[sp、#-48]! 0x40063c <f4+4>: 移動 x29、sp f4関数によって開かれたスタックスペースも48バイトであることがわかります。したがって、フレーム3のスタックの最上部は、現在の0xffffffffff2c0 + 48バイト:0xffffffffff2f0です。 フレーム2の機能は0xffffffff2c0 + 8: 0x000000000040065c -> <f4+36> (gdb) x/gx 0xfffffffff2f0+8 0xfffffffff2f8: 0x0000000000400684 (gdb)x/i 0x0000000000400684 0x400684 <メイン+28>: mov w0, #0x0 // #0 したがって、フレーム3の関数はメイン関数であり、メイン関数に対応するスタックの最上部は0xffffffffff320です。 これで導出は終了です (興味のある方は導出を続行して、libc が main をどのように起動するかを確認できます) 要約: スタックをプッシュするための鍵:
3. 実践的な説明現場では、次のコアが利用可能です: ご覧のとおり、すべてのシンボルが見つかりません。シンボル テーブルをロードした後でも、まだ動作せず、実際の呼び出しスタックを解析できません。 (gdb) うーん #0 0x0000ffffaeb067bc ?? () 内 /lib64/libc.so.6 から #1 0x0000aaaad15cf000 は??() 内 バックトレース停止: このフレームの内側の前のフレーム (スタックが破損している?) まず情報レジスタを見て、4つのレジスタx29、x30、sp、pcの値に注目してください。 派生タスク スタック: まず、sp コンテンツをエクスポートします。 下の図は実際に結果を示したものです。どのように導出するかを詳しく説明しましょう。 pcは現在実行されている関数命令を表します。現在の命令が開かれていない場合、通常x30は現在の関数を呼び出す前のフレームの次の命令を表します。アセンブリを見ると、次の関数に逆順にすることができます。 (gdb) x/i 0xaaaacd3de4fc 0xaaaacd3de4fc <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)+108>: mov x27, x0 スタックの最上位関数を見つけたら、関数のスタック操作を確認します。 (gdb) x/6i PGXCNodeConnStr 0xaaaacd3de490 <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)>: sub sp, sp, #0xd0 0xaaaacd3de494 <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)+4>: stp x29, x30, [sp,#80] 0xaaaacd3de498 <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)+8>: x29、sp、#0x50 を追加 前のフレームは現在の sp + 0xd0 - 0x80 に存在し、0xfffec4cebd40 + 0xd0 - 0x80 = 0xfffec4cebd90 であり、スタックの最下部は 0xfffec4cebd40 + 0xd0 = 0xfffec4cebe10 にあることがわかります。 したがって、次のレベルのフレームに対応するスタックのトップと、前のレベルのLRリターン命令を見つけます。逆にすると、関数build_node_conn_strが得られます。 (gdb)x/i 0x0000aaaacd414e08 0xaaaacd414e08 <build_node_conn_str(Oid, DatabasePool*)+224>: 移動 x21, x0 上記の導出を繰り返すと、関数 build_node_conn_str が 176 バイトのスタックを開くことがわかります。 (gdb) x/4i ビルド_ノード_conn_str 0xaaaacd414d28 <build_node_conn_str(Oid, DatabasePool*)>: stp x29、x30、[sp、#-176]! 0xaaaacd414d2c <build_node_conn_str(Oid, DatabasePool*)+4>: 移動 x29, sp 0xfffec4cebe10 + 176 = 0xfffec4cebec0 と続けます reload_database_poolsの呼び出し元0xfffec4cebe10+8を確認します 引き続きreload_database_poolsを参照してください (gdb) x/8i データベースプールの再ロード 0xaaaacd4225e8 <reload_database_pools(PoolAgent*)>: サブ sp、sp、#0x1c0 0xaaaacd4225ec <reload_database_pools(PoolAgent*)+4>: adrp x5、0xaaaad15cf000 0xaaaacd4225f0 <reload_database_pools(PoolAgent*)+8>: adrp x3、0xaaaacf0ed000 0xaaaacd4225f4 <reload_database_pools(PoolAgent*)+12>: adrp x4、0xaaaaceeed000 <_ZN4llvm18ConvertUTF8toUTF16EPPKhS1_PPtS3_NS_15ConversionFlagsE> 0xaaaacd4225f8 <reload_database_pools(PoolAgent*)+16>: x3、x3、#0x9e0 を追加 0xaaaacd4225fc <reload_database_pools(PoolAgent*)+20>: adrp x1、0xaaaacf0ee000 <_ZZ25PoolManagerGetConnectionsP4ListS0_E8__func__+24> 0xaaaacd422600 <reload_database_pools(PoolAgent*)+24>: stp x29、x30、[sp、#-96]! 実際のスタックは0x220バイトで開かれるので、このフレームのスタックの一番下は0xfffec4cebec0 + 0x220 = 0xfffec4cec0e0です。 したがって、基本的な呼び出し関係の構造は次のようになります。 上記は基本的に問題を分析するのに十分であるため、さらに導出する必要はない。 ヒント: この命令は通常、ARM アーキテクチャでの呼び出しで使用されます。 stp x29, x30, [sp,#immediate]! 感嘆符付きまたは感嘆符なし したがって、各フレーム層には、前のフレーム層のスタックトップアドレスとLR命令が格納されます。最下位フレーム0のスタックトップを正確に見つけることで、すべての呼び出し関係をすばやく推測できます(赤い破線で囲まれた部分)。関数の逆解決は、シンボルテーブルに依存します。元のelfファイルのシンボルセグメントが削除されていない限り、対応する関数シンボルを見つけることができます(readelf -Sで確認してください)。 フレームを見つけた後、フレームの各レイヤーのコンテンツとアセンブリを組み合わせて、基本的にプロセス変数を推測することができます。 上記は、ARM アーキテクチャでの関数呼び出しプロセスの詳細な内容の簡単な分析です。ARM アーキテクチャでの関数呼び出しプロセスの詳細については、123WORDPRESS.COM の他の関連記事に注目してください。 以下もご興味があるかもしれません:
|
<<: Vue3でelement-plusを使用する方法の詳細な説明
>>: tbodyタグの魔法はテーブルコンテンツの表示を高速化します
privot は、多対多の関係の中間テーブルです。 PT5 フレームワークは自動的に privot ...
サーバーマッチングロジックNginx は、リクエストを実行するサーバー ブロックを決定するときに、サ...
このチュートリアルでは、MySQL 5.7のインストールと設定方法を参考までに紹介します。具体的な内...
500 (内部サーバー エラー) サーバーでエラーが発生したため、要求を完了できませんでした。 50...
CSS3アニメーションとJSアニメーションの違いJSはフレームアニメーションを実装しますCSS3はト...
JD カルーセルは、動的な効果を追加せず、主に位置決めの知識を使用して、純粋な HTML と CS...
概要es6 では、配列またはオブジェクトから指定された要素を取得する新しい方法が追加されました。これ...
目次1. Nginxは負荷分散の原則を実装する2. Nginxの動的および静的分離の原則Nginx ...
効果は以下のとおりです。 コードは次のとおりです (クリックすると展開してソース コードが表示されま...
目次序文キーの役割差分アルゴリズムにおけるキーの役割ヘッドノードを同期するテールノードを同期する新し...
目次必要:発生した問題:解決する:必要:要素テーブル内の複数の列を並べ替えるには、日付の並べ替えをク...
1. はじめに:ウェブページにフラッシュ コンテンツを正常に表示したい場合は、ページ上のフラッシュ ...
123WORDPRESS.COM HTML チュートリアル セクションに戻るには、ここをクリックして...
腹筋コマンドの原則Apache の ab コマンドは、マルチスレッドの同時リクエストをシミュレートし...
序文この記事では主に、MySQLに大量のデータを挿入する4つの方法を紹介し、参考と学習のために共有し...