[熱修復(fù)]動手實(shí)現(xiàn)sopfix熱修復(fù),整個替換ArtMethod

今天我們在最新的android版本(12,13)上實(shí)操Sophix的核心原理: 基于ArtMethod的整體替換方案. 首先回顧學(xué)習(xí)阿里的sopfix原理介紹
從文中可知, 最關(guān)鍵的2個技術(shù)點(diǎn):

  • 找到要熱修復(fù)的java方法對應(yīng)的ArtMethod
  • 對ArtMethod進(jìn)行替換
    是不是很簡單, 看起來是這樣. 但是目前由于android的art虛擬機(jī)安全性的加強(qiáng), 并沒有這么容易,

原文中有代碼示例:

art::mirror::ArtMethod* smeth =
            (art::mirror::ArtMethod*) env->FromReflectedMethod(src);
art::mirror::ArtMethod* dmeth =
            (art::mirror::ArtMethod*) env->FromReflectedMethod(dest);

然而很遺憾, 當(dāng)你拿來用時, 并不生效, 倒不是說運(yùn)行報錯, 而是返回的值, 明顯不是內(nèi)存地址. 在我的小米11手機(jī)上分別返回57, 59. 這明顯不是內(nèi)存地址.
看起來像是index序號之類的值. 回過頭來看一眼我們這次要熱修的方法定義:

public class SomeClass {
    public final static int f1() {
        return 100;
    }
    public final static int f2() {
        return 200;
    }
}

我們在SomeClass類中只定義了兩個方法, 一個要被替換的方法f1(), 另一個則是要用來替換f1()的熱修下發(fā)新方法f2() 這里為了演示方便, 寫在一起了,現(xiàn)實(shí)中肯定要在補(bǔ)丁patch dex中動態(tài)下發(fā)f2(), 而這不是熱修的關(guān)鍵技術(shù), 先忽略.
回到上面的地址不對的問題, 怎么解決呢?
參考了epic的實(shí)現(xiàn), 發(fā)現(xiàn)目前得到的返回值果然是序號.
/art/runtime/jni/jni_id_manager.cc

image.png

看起來仍然是在jni中正常邏輯處理不了的, 那就直接拿來用吧. 函數(shù)在libart.so中, 函數(shù)簽名:_ZN3art3jni12JniIdManager14DecodeMethodIdEP10_jmethodID
如何使用, 請參考前面的文章[APM學(xué)習(xí)]如何在android N之后dlopen使用系統(tǒng)私有庫
定義個本地函數(shù)指針JniIdManager_DecodeMethodId_指向libart.so中的_ZN3art3jni12JniIdManager14DecodeMethodIdEP10_jmethodID

JniIdManager_DecodeMethodId_ 
      = reinterpret_cast<void *(*)(void *, jlong)>
      (mydlsym(handle, "_ZN3art3jni12JniIdManager14DecodeMethodIdEP10_jmethodID"));

然后就是解析java方法的ArtMethod地址具體步驟:

// 通過反射先得到methodId, 就是上面說的57
size_t src_art_method = reinterpret_cast<size_t> (env->GetStaticMethodID(someClass, "f1",
                                                                             "()I"));
//然后轉(zhuǎn)換為地址
src_art_method = reinterpret_cast<jlong>(JniIdManager_DecodeMethodId_(
                ArtHelper::getJniIdManager(), src_art_method));

同樣的邏輯, 得到f1(),f2()的ArtMethod地址, 分別存放在smeth, dmeth指針中, 最后進(jìn)行整體賦值替換.

    void *smeth =(void *) src_art_method;
    void *dmeth =(void *) dest_art_method;

    // art_method_length 就是ArtMehod的結(jié)構(gòu)體占用的內(nèi)存空間, 
    //也就是緊挨著的兩個java方法的對應(yīng)的地址的差
    size_t art_method_length = dest_art_method - src_art_method;
     // 整體替換
    memcpy(smeth, dmeth, art_method_length);

UI 代碼

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        replace = new NativeArtMethodReplace();
        // 模擬顯示一個錯誤結(jié)果
        updateTextView();

        binding.doReplace.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 模擬熱修
                replace.hotfix();
                // 熱修完成后,再次刷新頁面顯示
                updateTextView();
            }
        });
    }

    // 更新顯示頁面顯示
    private void updateTextView() {
        TextView tv = binding.sampleText;
        tv.setText("100+100=" + SomeClass.f1());
    }

效果

image.png

點(diǎn)了"do Hotfix"模擬熱修, 界面變成:
image.png

代碼已經(jīng)上傳到github: ArtMethodReplaceDemo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容