ARMアーキテクチャにおける関数呼び出しプロセスの簡単な分析

ARMアーキテクチャにおける関数呼び出しプロセスの簡単な分析

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 をどのように起動するかを確認できます)

要約:

スタックをプッシュするための鍵:

  • 現在のシーン
  • CPUアーキテクチャのスタックオープン方法に精通している

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 の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Linuxのアラーム機能の例の説明
  • PHP は 6 つの Linux コマンド関数コード例を実行します
  • Linux での stat 関数と stat コマンドの使用法の詳細な説明
  • Linux で time(NULL) 関数と localtime() を使用して現在の時刻を取得する方法
  • Linux/Mac で Python 関数にタイムアウトを追加する方法
  • Linux のリンク解除機能とファイルの削除方法
  • Linux lseek関数の使い方の詳しい説明

<<:  Vue3でelement-plusを使用する方法の詳細な説明

>>:  tbodyタグの魔法はテーブルコンテンツの表示を高速化します

推薦する

データベースの冗長フィールドを合理的に使用する方法

privot は、多対多の関係の中間テーブルです。 PT5 フレームワークは自動的に privot ...

Nginx におけるサーバーとロケーションのマッチングロジックの詳細な理解

サーバーマッチングロジックNginx は、リクエストを実行するサーバー ブロックを決定するときに、サ...

MySQL 5.7 のインストールと設定方法のグラフィックチュートリアル

このチュートリアルでは、MySQL 5.7のインストールと設定方法を参考までに紹介します。具体的な内...

Nginxサービス500:内部サーバーエラーの原因の1つ

500 (内部サーバー エラー) サーバーでエラーが発生したため、要求を完了できませんでした。 50...

アニメーションとトランジションの違い

CSS3アニメーションとJSアニメーションの違いJSはフレームアニメーションを実装しますCSS3はト...

JDカルーセル効果を実現するための純粋なHTMLとCSS

JD カルーセルは、動的な効果を追加せず、主に位置決めの知識を使用して、純粋な HTML と CS...

JS ES6における構造化分解についてお話しましょう

概要es6 では、配列またはオブジェクトから指定された要素を取得する新しい方法が追加されました。これ...

Nginx の負荷分散と動的および静的分離の原理と構成

目次1. Nginxは負荷分散の原則を実装する2. Nginxの動的および静的分離の原則Nginx ...

クールな点滅アラームボタンをおすすめします

効果は以下のとおりです。 コードは次のとおりです (クリックすると展開してソース コードが表示されま...

Vue でインデックスをキー属性値として使用することが推奨されないのはなぜですか?

目次序文キーの役割差分アルゴリズムにおけるキーの役割ヘッドノードを同期するテールノードを同期する新し...

要素テーブルテーブルコンポーネントの複数フィールド(複数列)ソート方法

目次必要:発生した問題:解決する:必要:要素テーブル内の複数の列を並べ替えるには、日付の並べ替えをク...

フラッシュコンテンツの表示に使用される OBJECT タグと EMBED タグの違いの紹介

1. はじめに:ウェブページにフラッシュ コンテンツを正常に表示したい場合は、ページ上のフラッシュ ...

マークアップ言語 - 印刷スタイルシート

123WORDPRESS.COM HTML チュートリアル セクションに戻るには、ここをクリックして...

Apache ab同時負荷ストレステストの実装方法

腹筋コマンドの原則Apache の ab コマンドは、マルチスレッドの同時リクエストをシミュレートし...

MySQL に大量のデータを挿入する 4 つの方法の例

序文この記事では主に、MySQLに大量のデータを挿入する4つの方法を紹介し、参考と学習のために共有し...