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タグの魔法はテーブルコンテンツの表示を高速化します

推薦する

Linux trコマンドの使用

1. はじめにtr はテキストの一部を変換または削除するために使用されます。 tr は transl...

この記事では、MySQLのマスタースレーブ同期の原理を説明します。

目次MySQL マスタースレーブ同期原理の簡単な分析1. マスタースレーブとは何ですか? 2. 主従...

OEL7.6 ソースコードから MYSQL5.7 をインストールするチュートリアル

まず、公式サイト https://dev.mysql.com/downloads/mysql/5.7...

Vue3のいくつかの利点についての簡単な説明

目次1. ソースコード1.1 モノレポ1.2 タイプスクリプト2. パフォーマンス2.1 ソースコー...

CSSセレクタを使用してラベルスタイルを設定するサンプルコード

CSS セレクターHTML タグにスタイルを設定すると、タグの属性を設定できます。 <div ...

VueのRender関数

目次1. ノード、ツリー、仮想DOM 2. 仮想DOM 2.1 データオブジェクトの詳細2.2 制約...

フレックスレイアウトにおける画像変形の解決策の詳細な説明

フレックス レイアウトは現在よく使用されるレイアウト方法ですが、場合によっては小さな問題が発生するこ...

vue-cli の紹介とインストール

目次1. はじめに2. vue-cli の紹介2.1 コマンドライン2.2 CLI サービス2.3 ...

Angularが予期しない例外エラーを処理する方法の詳細な説明

前面に書かれたコードがどれだけ適切に記述されていても、すべての可能性のある例外を完全に処理することは...

IE8対応のボーダー半径処理方法

canisue (http://caniuse.com/#search=border-radius)...

Mysqlは隣接リスト(隣接リスト)を通じてツリー構造を保存します。

以下の内容では、隣接リストを使用してツリー構造を保存する MYSQL のプロセスとソリューションを紹...

Tomcat サーバーが tomcat7w.exe を開けない場合の解決策

今日、Tomcat サーバーの設定時にちょっとした問題が発生したので、参考までにいくつかご説明したい...

忘れられたMySQLパスワードとログインエラーの問題について簡単に説明します

MySQL ログイン パスワードを忘れた場合、解決方法は実はとても簡単です。MySQL メイン構成フ...

Vueは小さな検索機能を実装する

この記事の例では、検索機能を実装するためのVueの具体的なコードを参考までに共有しています。具体的な...

VMwareでCentOSがインターネットにアクセスできない問題を素早く解決

昨日、VMware に CentOS7 をインストールしました。Tomcat パッケージを転送するた...