Sunday, June 7, 2020

AndroidのPerfMon解析 その1

AndroidのCPU使用率や動作クロックを表示してくれるアプリ「PerfMon」がAndroid8以降では/proc/以下や/sys/以下の権限がなく、動作しなくなっている。


rootを取得してる端末でもアプリがroot権限を要求してこないのでrootで実行させることができない。(過去にAndroid10のRedmiK20ProでPerMonにmagiskで権限を与えて動作させることができたのだが端末をすられてしまい、真相は闇の中)
なぜかPerfMonが動作しているAndroid10


ということでPerfMonが/sys/や/proc/にアクセスするときにsuを要求するように変更したい。



現在多忙のため何日か掛けながら目標を達成したいと思ってるので、毎日に進捗をメモしていこうと思う。



まずはapkをデコンパイルしてみた。
まず見るのはマニフェストファイルの以下の行
<activity android:excludeFromRecents="true" android:label="@string/app_name" android:launchMode="singleInstance" android:name=".LaunchActivity" android:theme="@android:style/Theme.NoDisplay">
LaunchActivityというクラスが最初に実行されるようだ。
LaunchActivityを開くとOnCreateがまず実行される。

    public void onCreate(Bundle bundle) {
        boolean z;
        boolean z2 = false;
        //~~長いので中略~~~
        if (z || booleanExtra || !z2) {
            a();
            return;
        }

重要なのは最後のa();の部分である。a()メソッドが定義された場所を見てみる。

    public void a() {
        boolean[] zArr = new boolean[4];
        for (int i = 0; i < zArr.length; i++) {
            zArr[i] = false;
        }
        if (PerfMonWindow.a(256)) {
            StandOutWindow.b((Context) this, PerfMonWindow.class, 256);
            zArr[0] = true;
        }
        if (PerfMonWindow.a(768)) {
            StandOutWindow.b((Context) this, PerfMonWindow.class, 768);
            zArr[1] = true;
        }
        if (PerfMonWindow.a(512)) {
            StandOutWindow.b((Context) this, PerfMonWindow.class, 512);
            zArr[2] = true;
        }
        if (PerfMonWindow.a(1024)) {
            StandOutWindow.b((Context) this, PerfMonWindow.class, 1024);
            zArr[3] = true;
        }
        Builder builder = VERSION.SDK_INT >= 11 ? new Builder(this, 2131099651) : new Builder(this);
        builder.setTitle("PerfMon" + this.a);
        builder.setItems(new CharSequence[]{"Foreground App", "CPU", "Disk I/O", "Network I/O", "Open all", "Close all", "My apps on Google Play", "Follow me on Twitter/G+"}, new d(this, zArr, this));
        builder.setCancelable(true);
        builder.setOnCancelListener(new e(this, zArr, this));
        builder.show();
    }
    
どうやらPerfMonWindowクラスのaメソッドを実行し、返り値次第でStandOutWindowのbメソッドも実行している。

そしてその下ではAlerDialogのBuilderが作られている。そのBuilderの中に"Foreground App", "CPU", "Disk I/O",...とPerfMonを起動したときに最初に現れるビューに書かれている文字のリストがある。

あれはAlertDialogだったのか。。。

PerfMonWindowクラスのaメソッドを見てみる

    public boolean a(int i, k kVar) {
        y yVar = (y) a.get(Integer.valueOf(i));
        if (yVar != null) {
            yVar.c = false;
        }
        a.remove(Integer.valueOf(i));
        return super.a(i, kVar);
    }

yクラスで定義されたオブジェクトの形にa.getの返り値を変換している。ちなみにaとはHashMapである
    public static HashMap a = new HashMap();

このHashMapに対してデータを挿入するメソッドが同クラス内にあった。
    public void a(int i, FrameLayout frameLayout) {
        if (i == 256) {
            a.put(Integer.valueOf(i), new n(this, this, i, frameLayout));
        }
        if (i == 512) {
            a.put(Integer.valueOf(i), new q(this, this, i, frameLayout));
        }
        if (i == 768) {
            a.put(Integer.valueOf(i), new j(this, this, i, frameLayout));
        }
        if (i == 1024) {
            a.put(Integer.valueOf(i), new u(this, this, i, frameLayout));
        }
    }

n,q,j,uというクラスが出てきた。それぞれ見てみる。
    public n(PerfMonWindow perfMonWindow, Context context, int i, FrameLayout frameLayout) {
        this.a = perfMonWindow;
        super(perfMonWindow, context, i, frameLayout);
        a(C0000R.layout.foreground_window, "Foreground App");
        this.l = (TextView) this.g.findViewById(C0000R.id.stat_pkg);
        this.m = (TextView) this.g.findViewById(C0000R.id.stat_pkg_name);
        this.n = (TextView) this.g.findViewById(C0000R.id.stat_cpu);
        this.o = (TextView) this.g.findViewById(C0000R.id.stat_cpu_detail);
        this.p = (TextView) this.g.findViewById(C0000R.id.stat_memory);
        this.q = (TextView) this.g.findViewById(C0000R.id.stat_memory_detail);
        a(new View[]{this.l, this.m, this.n, this.o, this.p, this.q});
        a();
        this.c = true;
        new Thread(new o(this)).start();
    }
nクラス

    public q(PerfMonWindow perfMonWindow, Context context, int i, FrameLayout frameLayout) {
        this.a = perfMonWindow;
        super(perfMonWindow, context, i, frameLayout);
        a(C0000R.layout.io_window, "Disk I/O");
        this.l = (ListView) this.g.findViewById(C0000R.id.stat_list_ios);
        this.c = true;
        new Thread(new r(this)).start();
    }
qクラス

    public j(PerfMonWindow perfMonWindow, Context context, int i, FrameLayout frameLayout) {
        this.a = perfMonWindow;
        super(perfMonWindow, context, i, frameLayout);
        a(C0000R.layout.cpu_window, "CPU");
        this.l = (ListView) this.g.findViewById(C0000R.id.stat_list_cpus);
        this.c = true;
        new Thread(new k(this)).start();
    }
jクラス

    public u(PerfMonWindow perfMonWindow, Context context, int i, FrameLayout frameLayout) {
        this.a = perfMonWindow;
        super(perfMonWindow, context, i, frameLayout);
        a(C0000R.layout.net_window, "Network I/O");
        this.l = (ListView) this.g.findViewById(C0000R.id.stat_list_net);
        this.c = true;
        new Thread(new v(this)).start();
    }
uクラス

なるほど、それぞれ"Foreground App", "Disk I/O", "CPU", "Network I/O"に対応しているらしい。
すると先ほどのPerfMonWindow.aメソッドの返り値次第でStandOutWindow.bメソッドを呼んでいたのは「どのモードを選択したか、どのモードを起動するか」というコードだろうと予想できる。

外部ライブラリとしてインポートされているこちらの wei.mark.standout.StandOutWindow 調べたら2012年に作られたFloatingWindow用のライブラリということが分かった
StandOut - Create Floating Apps

なるほど、chainefire氏はこのライブラリを使ってPerfMonを作ってたのか。。。
xdaにこのライブラリの使い方が詳しく説明されてるようなのでまずはこれを読んでみる。。。



続く



No comments:

Post a Comment