Android でアプリケーションのメモリを分析する方法 (14)
前回の連載記事では、Android アプリケーションがネイティブ メモリを分析する方法を紹介しました。
次のステップは、Android アプリケーションが Java メモリを分析する方法です。
ネイティブと同様に、 ART のヒープとスタック、ロック状況、メソッドのローカル変数、スタック フレームなども確認できるようにしたいと考えています。
したがって、ART のメモリ分析は 2 つの部分になります。
- 現在のスタック フレームのローカル変数などのスタックの内容を表示します。
- ヒープの内容、つまりオブジェクトの割り当てを表示します。
注: Android には、dex ファイルを保存するイメージ ヒープや共有リソースを保存する zygote ヒープなど、いくつかの特別なヒープ メモリがあります。これらはフレームワークが考慮すべき内容に属するため、このシリーズでは紹介しません。
jdbを使用して表示します
ARTのスタック状況を確認するにはどうすればよいですか?ネイティブと同じなので、デバッガーを使用して表示できます。
Java のデバッガは jdb と呼ばれます。
ネイティブ gdb および lldb リモート デバッグと同様、jdb も 2 つの部分に分かれています。
- 1 つは Android デバイス側に配置され、ART がこの部分を担当します。
- もう1つはPC側です。この部分は jdb によって処理されます
これら 2 つの部分は通信する必要があり、その通信プロトコルは JDWP (Java debug Wire Protocol) と呼ばれます。
ART は標準的な Java 仮想マシンの 1 つであるため、どの JDB でも Android Java アプリケーションをデバッグできます。
アプリの準備
コンパイル時に -g オプションを追加する必要があります。Android Studioを使用している場合は、コンパイラで次のように設定します。
ROMをカスタマイズしてコンパイル時に-gオプションを付ければFrmaworkのコード全体をデバッグできますが、ここではFrameworkは関係ないので詳しくは紹介しません。
デバッグ情報が含まれているかどうかを確認する方法
javap コマンドを使用して、対応する xxx.class に LineNumberTable および LocalVariableTable が含まれているかどうかを確認します。
- LineNumberTable には、ソース コードの行番号とバイトコード命令の間の対応関係が含まれており、デバッグ中にソース コードの行番号を見つけるために使用されます。
- LocalVariableTable には、変数名、データ型、スコープなど、メソッド内のローカル変数に関する情報が含まれており、デバッグ中に変数の値を表示するために使用されます。
スクリーンショットの例は次のとおりです。
javap -v ./MainActivity.class | grep -E "LineNumberTable|LocalVariableTable"
デバッグを開始する
-
アプリケーションをインストールし、adb jdwp を実行します。adb jdwp を使用して、どれが jdwp プロトコルに適合するかを確認します。リストされている数字はプロセス PID です
-
ローカル デバッグ ポートを Android アプリケーションに転送し、次のコマンドを実行します。
## 此处我们再次选择了5039端口用于jdwp的连接。(在gdb和lldb中也是使用了5039见:http://t.csdn.cn/QkkH3和http://t.csdn.cn/JWgcF)
adb forward tcp:5039 jdwp:6405
- 次のように jdb を使用して tcp:5039 に接続するだけです。
jdb -attach localhost:5039
出力は次のようになり、接続が成功したことを示します。
设置未捕获的java.lang.Throwable
设置延迟的未捕获的java.lang.Throwable
正在初始化jdb...
>
注: jdb を実行する前に、必ず Android Studio の実行を停止してください。停止しないと、接続が機能しません。
注: この記事では、ポンド記号で始まる行はコメントを示し、右山括弧は入力されたコマンドを示します。
スレッドを一時停止する
## suspend [thread id(s)] -- 挂起线程 (默认值: all)
> suspend
どのようなスレッドがあるかを確認してください
#threads [threadgroup] -- 列出线程
>threads
以下に示すように
印刷スタック
#where [<thread id> | all] -- 转储线程的堆栈
> where 0x4e81
出力は次のとおりです。
同時に、メインスレッドが現在のスレッドとして選択されます。
ローカル変数を出力する
#locals 打印本地变量
>locals
出力は次のとおりです
スタックフレームを移動する
# up [n frames] -- 上移线程的堆栈
# down [n frames] -- 下移线程的堆栈
印刷変数
# dump 变量名
> dump view.mTextClassifierHelper
出力は次のとおりです
ソースコードの場所を設定する
# 在jdb启动的时候,添加选项-sourcepath 路径1:路径2 即可添加源代码的路径
# 这样在调试AOSP代码的时候,会变得异常方便
# 还可以在jdb内部,使用use命令,修改源码路径
# 如下面使用jdb调试Android原生代码里面的MessageQueue
注: この後、AMS ソース コード分析があり、この章で紹介する jdb コンテンツは非常に頻繁に使用されます。
ソースコードをリストする
# list [line number|method] -- 输出源代码
# 见上图
ブレークポイントを設定する
# stop in <class id>.<method>[(argument_type,...)] 在方法处设置一个断点
# stop at <class id>:<line> 在某行设置一个断点
# clear <class id>.<method>[(argument_type,...)] 清除断点
# clear <class id>:<line> 清除断点
# clear 列出断点
# stop 列出断点
>stop in com.example.test_malloc.MainActivity.click
次の出力が表示されます
次のようにブレークポイントをトリガーします
ここで、bci はバイトコード インデックスを意味します。
単一段階
# step -- 执行当前行,可理解为单步进入
# step up -- 执行直到当前方法返回到它的调用者
# stepi -- 执行当前指令
# next -- 步进一行,可以理解为单步over
# cont -- 从断点处继续执行
値の変更
# set <lvalue> = <expr> 赋值字段,变量,数组元素新值
スレッドのメソッドの出入りを追跡する
# trace [go] methods [thread]
-- 跟踪方法进入和退出。
-- 除非指定 'go', 否则挂起所有线程
# trace [go] method exit | exits [thread]
-- 跟踪当前方法的退出, 或者所有方法的退出
-- 除非指定 'go', 否则挂起所有线程
untrace [methods] -- 停止跟踪方法进入和/或退出
トレース メソッド コマンドは、大規模なプログラムや複雑なメソッド呼び出し関係をデバッグする場合に特に便利です。これは、問題を特定し、コードの実行フローを理解し、潜在的なエラーや例外を見つけるのに役立ちます。
例外をキャッチする
# catch [uncaught|caught|all] <class id>|<class pattern> 当指定的异常发生时,暂停,如下图
# ignore [uncaught|caught|all] <class id>|<class pattern> 取消捕获某个异常
ロック情報を印刷する
# lock <expr> -- 打印某个对象上面的锁信息
# threadlocks [thread id] --打印线程上面的锁信息
計算式
# eval 表达式——计算表达式的值
# 计算变量x和y的值
>eval x+y
ジャストインタイムのデバッグ
ネイティブと同様に、ジャストインタイム デバッグは 2 つの部分に分かれています
- プログラムは開始するとすぐに、デバッガーが参加するのを待ちます。
- プログラムがクラッシュしたらすぐに、デバッガーが参加するのを待ちます
プログラムは開始するとすぐに、デバッガーが参加するのを待ちます。
- 適切な場所で停止するようにシステムに指示します
Android には、起動後にデバッガーが接続するまで待機するようにアプリに指示するコマンド ライン オプションが用意されています。コマンドは次のとおりです。
adb shell am set-debug-app -w com.example.test_malloc
说明:set-debug-app [-w] [--persistent] <PACKAGE>
-w: 应用开始,就等待debugger接入
--persistent: 这个值一直存在,不会因为应用消失之后被清除掉
設定が成功すると、アプリケーションを開くと以下のように表示されます。
- アプリに接続した後、すぐに実行せずにすべてのスレッドを一時停止するように jdb に指示します。
ユーザーディレクトリに設定ファイルを作成します。
- Linux と Macos では、.jdbrc と呼ばれます。
- Windows は jdb.ini を呼び出して
、次のコンテンツを保存します。
suspend
以下のスクリーンショットをご覧ください
- 対応する操作 (ブレークポイントなど) を設定した後、resume コマンドの使用を開始します。
以下に示すように
注: 上記の操作による jdb 接続の待機に加えて、code メソッドを使用することもできます。スレッド一時停止 API やループなど。比較的簡単なので、詳しくは説明しません。
トラブルシューティング
問題 1: jdb の接続後もアプリが実行中の場合
解決策: 上記のステップ 2 が欠落しています。
問題 2: アプリの起動後、「デバッガーを待つ」が表示されない
解決策: まず、システムがステップ 1 のコマンドをサポートしているかどうかを確認する必要があります。表示するには次のコマンドを使用します
adb shell am -h
上記の出力に従って、対応するコマンドに設定します。
プログラムがクラッシュしたらすぐに、デバッガーが参加するのを待ちます
Javaプログラムがクラッシュすると、詳細なコールスタックが出力されるためです。ネイティブプログラムほど複雑ではありません。したがって、プログラムのクラッシュを分析するのは簡単です。したがって、この部分には内容がありません。
注: Android アプリケーションでは、複雑なスレッドとロックの関係により ANR が発生し、クラッシュする可能性があります。現時点では、対応する理由は Android の ANR ファイルを通じて分析できます。これはこの記事の範囲を超えています。ANRの解析方法については、時間があるときにまた書きたいと思います。
ここまでで Java の jdb の機能のほとんどを紹介しましたが、AS が提供する機能よりも豊富で、追加の機能は次のとおりです。
- 特定のスレッドを単独で操作する
- ソースコードの場所を変更する
- メソッドの入口と出口の追跡
- 積極的に例外をキャッチする
- ロック情報を印刷する
上記の追加機能は、大規模な Android プロジェクトの分析、AOSP システム レベルのコードの分析、Android の脆弱性の分析、およびマルチスレッド プログラミングの分析に役立ちます。
jdb のコマンドラインの紹介を完了する前に、まず jdb が観察できるメモリ内容の概要を作成しましょう。
- ヒープフレームを表示する
- ローカル変数を表示する
- ビューロック
- オブジェクトの表示
この記事は終了しました。
Java ヒープの内容を表示する前。最初に対応する UI インターフェイスを紹介し、次のセクションでは Android アプリケーションをデバッグする VSCode を紹介します。