Android 4.x 系统虚拟内存治理方案

业界治理虚拟内存的方案大多数是针对Android 5及以上机型,如:阿里巴巴开源Patrons方案。针对Android 4.x的治理方案少之又少,故此专门针对Android 4.x 系统虚拟内存治理提上了日程。

开释webview组件

仔细研究/proc/self/smap,因为APP应用没有使用到webview组件,占用18M的libwebviewchromium.so便映入眼帘。

71fc2000-7317a000 r-xp 00000000 b3:19 903        /system/lib/libwebviewchromium.so
Size:              18144 kB
Rss:                   0 kB
Pss:                   0 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:            0 kB
Anonymous:             0 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB

那如何开释这部分虚拟内存内?看到so,自然而言想到dl家族的dlclose函数。

#include <dlfcn.h>

int
dlclose(void* handle);

然而dl家族函数存在一个特性就是引用计数,dlclose调用一次引用计数减1,直到引用计数减到0时才会真正的卸载so,因此只有正确的读取到引用计数,才能完全卸载do。
那我们只能到Anroid源码中寻找答案了。

struct soinfo {
 public:
  char name[SOINFO_NAME_LEN];
  const Elf32_Phdr* phdr;
  size_t phnum;
  Elf32_Addr entry;
  Elf32_Addr base;
  unsigned size;

  uint32_t unused1;  // DO NOT USE, maintained for compatibility.

  Elf32_Dyn* dynamic;

  uint32_t unused2; // DO NOT USE, maintained for compatibility
  uint32_t unused3; // DO NOT USE, maintained for compatibility

  soinfo* next;
  unsigned flags;

  const char* strtab;
  Elf32_Sym* symtab;

  size_t nbucket;
  size_t nchain;
  unsigned* bucket;
  unsigned* chain;

  unsigned* plt_got;

  Elf32_Rel* plt_rel;
  size_t plt_rel_count;

  Elf32_Rel* rel;
  size_t rel_count;

  linker_function_t* preinit_array;
  size_t preinit_array_count;

  linker_function_t* init_array;
  size_t init_array_count;
  linker_function_t* fini_array;
  size_t fini_array_count;

  linker_function_t init_func;
  linker_function_t fini_func;

#if defined(ANDROID_ARM_LINKER)
  // ARM EABI section used for stack unwinding.
  unsigned* ARM_exidx;
  size_t ARM_exidx_count;
#elif defined(ANDROID_MIPS_LINKER)
  unsigned mips_symtabno;
  unsigned mips_local_gotno;
  unsigned mips_gotsym;
#endif

  size_t ref_count;
  link_map_t link_map;

  bool constructors_called;

  // When you read a virtual address from the ELF file, add this
  // value to get the corresponding address in the process' address space.
  Elf32_Addr load_bias;

  bool has_text_relocations;
  bool has_DT_SYMBOLIC;

  void CallConstructors();
  void CallDestructors();
  void CallPreInitConstructors();

 private:
  void CallArray(const char* array_name, linker_function_t* functions, size_t count, bool reverse);
  void CallFunction(const char* function_name, linker_function_t function);
};

对比4.1、4.2、4.3、4.4的 struct soinfo 定义可知,Google针对ref_count进行了位置对齐,即在4.x系列机型上,offsetof(struct soinfo, ref_count)为固定值(256),根据固定偏移,即可读到引用计数数值,即可知道调用dlclose的次数,废话不多说,直接上代码验证:

#ifdef __arm__
#define SO_INFO_REFCOUNT_OFFSET 256
#else
#define SO_INFO_REFCOUNT_OFFSET 248
#endif

void Android4_webview(bool k)
{
    std::vector<std::string> libs;
    libs.reserve(2);
    if (k)
    {
        libs.emplace_back("/system/lib/libwebviewchromium_plat_support.so");
        libs.emplace_back("/system/lib/libwebviewchromium.so");
    }
    else
    {
        libs.emplace_back("/system/lib/libwebcore.so");
        libs.emplace_back("/system/lib/libchromium_net.so");
    }

    for (auto lib : libs)
    {
        void* handle = dlopen(lib.data(), RTLD_LAZY);
        if (handle != nullptr) {
            while(1)
            {
                unsigned refcount = *((unsigned*) ((unsigned) handle + SO_INFO_REFCOUNT_OFFSET));
                if (refcount > 0)
                {
                    LOG("----- library : %s refcount : %d", lib.data(), refcount);
                    dlclose(handle);
                }
                else
                {
                    break;
                }
            }
        }
    }
}

再次读取/proc/self/smap可以看到zygote引入的libwebviewchromium.so被我们完全释放了,而且只要我们保证不做任何webview操作的,AUTO不会出现任何异常,perfect!!!

开释中英文外其他字体库

仔细研究/proc/self/smap,可以看到加载了好多其他语种字体库,汇总下来mmap了大概2M左右虚拟内存,像尼泊尔语、泰语这些语种对AUTO没什么意义,只是白白占用了系统虚拟内存,然后观看zygote smap可以推断出除Roboto-Regular.ttf外其他字体库都是在apk中加载的,这样就给了我们拦截其他字体库加载的机会。

cat /proc/30550/smaps | grep ttf
71cdd000-71cfa000 r--p 00000000 b3:19 537        /system/fonts/Roboto-Regular.ttf
758d3000-758f0000 r--p 00000000 b3:19 532        /system/fonts/Roboto-Bold.ttf
758f0000-7590c000 r--p 00000000 b3:19 476        /system/fonts/DroidNaskhUI-Regular.ttf
7590c000-75944000 r--p 00000000 b3:19 480        /system/fonts/DroidSansEthiopic-Regular.ttf
75944000-7594c000 r--p 00000000 b3:19 483        /system/fonts/DroidSansHebrew-Bold.ttf
7594c000-75951000 r--p 00000000 b3:19 528        /system/fonts/NotoSansThaiUI-Bold.ttf
75951000-75955000 r--p 00000000 b3:19 479        /system/fonts/DroidSansArmenian.ttf
75955000-7595b000 r--p 00000000 b3:19 482        /system/fonts/DroidSansGeorgian.ttf
7595b000-75979000 r--p 00000000 b3:19 499        /system/fonts/NotoSansDevanagariUI-Bold.ttf
75979000-75983000 r--p 00000000 b3:19 520        /system/fonts/NotoSansTamilUI-Bold.ttf
75983000-75991000 r--p 00000000 b3:19 515        /system/fonts/NotoSansMalayalamUI-Bold.ttf
75991000-759ab000 r--p 00000000 b3:19 495        /system/fonts/NotoSansBengaliUI-Bold.ttf
759ab000-759c7000 r--p 00000000 b3:19 524        /system/fonts/NotoSansTeluguUI-Bold.ttf
759c7000-759da000 r--p 00000000 b3:19 503        /system/fonts/NotoSansKannadaUI-Bold.ttf
759da000-759e5000 r--p 00000000 b3:19 507        /system/fonts/NotoSansKhmerUI-Bold.ttf
759e5000-759ee000 r--p 00000000 b3:19 511        /system/fonts/NotoSansLaoUI-Bold.ttf
759ee000-75b4b000 r--p 00000000 b3:19 491        /system/fonts/NanumGothic.ttf
75b4b000-75bb7000 r--p 00000000 b3:19 531        /system/fonts/Padauk-bookbold.ttf
75bb9000-75bba000 r--p 00000000 b3:19 517        /system/fonts/NotoSansSymbols-Regular.ttf
75bba000-75bbb000 r--p 00000000 b3:19 473        /system/fonts/AndroidEmoji.ttf
75bbc000-75ee9000 r--p 00000000 b3:19 492        /system/fonts/NotoColorEmoji.ttf
75eea000-7633c000 r--p 00000000 b3:19 481        /system/fonts/DroidSansFallback.ttf
76342000-7634a000 r--p 00000000 b3:19 484        /system/fonts/DroidSansHebrew-Regular.ttf
7634b000-76351000 r--p 00000000 b3:19 529        /system/fonts/NotoSansThaiUI-Regular.ttf
76352000-76371000 r--p 00000000 b3:19 500        /system/fonts/NotoSansDevanagariUI-Regular.ttf
76372000-7637b000 r--p 00000000 b3:19 521        /system/fonts/NotoSansTamilUI-Regular.ttf
7637b000-76389000 r--p 00000000 b3:19 516        /system/fonts/NotoSansMalayalamUI-Regular.ttf
7638a000-763a5000 r--p 00000000 b3:19 496        /system/fonts/NotoSansBengaliUI-Regular.ttf
763a6000-763c2000 r--p 00000000 b3:19 525        /system/fonts/NotoSansTeluguUI-Regular.ttf
763c3000-763d7000 r--p 00000000 b3:19 504        /system/fonts/NotoSansKannadaUI-Regular.ttf
763d8000-763e3000 r--p 00000000 b3:19 508        /system/fonts/NotoSansKhmerUI-Regular.ttf
763e3000-763ec000 r--p 00000000 b3:19 512        /system/fonts/NotoSansLaoUI-Regular.ttf
763ed000-76462000 r--p 00000000 b3:19 530        /system/fonts/Padauk-book.ttf

追踪Android源码,推断出libskia.so进行具体的字体库的加载和内存映射。

nm -D libskia.so | grep mmap
0024b6e8 T _Z8sk_fmmapP6SkFILEPj
0024b664 T _Z9sk_fdmmapiPj
         U mmap@LIBC
         
nm -D libskia.so | grep open
0024b970 T _Z8sk_fopenPKc12SkFILE_Flags
00186f94 T _ZNK10SkTypeface10openStreamEPi
0024df40 T _ZNK19SkTypeface_FreeType7Scanner8openFaceEP8SkStreamiP13FT_StreamRec_
         U fdopen@LIBC
         U fopen@LIBC
         U open@LIBC
         U opendir@LIBC
         U ucnv_open_55

到底是通过fopen还是open打开ttf文件:
(1)阅读skia源码
(2)hook mmap然后进行栈回溯可以精准推断
(3)直接hook fopen或者open,打印filename
最终结论:Android 4.4通过fopem方式打开ttf文件,Android 4.3通过open方式打开ttf文件,代理函数发现是中英文之外的其他语种字体直接返回(错误)即可,最终阻断了无用字体库的加载,节省了系统虚拟内存。

FILE* fopen_proxy(const char* filename, const char*  mode)
{
    if (filename == nullptr)
    {
        return nullptr;
    }

    LOG("fopen_proxy begin");

    LOG("fopen_proxy filename : %s", filename);

    FILE* ret = nullptr;

    if (strstr(filename, ".ttf") != nullptr)
    {
        if (strstr(filename, "Roboto-Regular.ttf") != nullptr
            || strstr(filename, "Roboto-Bold.ttf") != nullptr
            || strstr(filename, "DroidSansFallback.ttf") != nullptr)
        {
            PLTHOOK_CALL_ORIG_FUNC_SAFE(fopen_proxy, ret, filename, mode);
        }
    }
    else
    {
        PLTHOOK_CALL_ORIG_FUNC_SAFE(fopen_proxy, ret, filename, mode);
    }

    LOG("fopen_proxy end");
    return ret;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容