Linker学习笔记必威:,不涉及太底层的代码均是

复制代码 代码如下:

1.2什么是源代码,指标文件,可实行文件。

源代码 ——源文件正是贮存程序代码的文件。平日大家编辑代码的文书正是源文件。

  • 源代码相对目的代码和可进行代码来说的。
  • 源代码正是用汇编语言和高等语言写出来的地代码。

对象文件——指源代码通过编写翻译程序发生的能被cpu直接识别二进制代码。

  • 对象代码指Computer科学中编写翻译器或汇编器管理源代码后所生成的代码,它常常由机器代码或左近于机器语言的代码组成。
  • 目的文件席卷着机器代码(可平素被Computer大旨微型机试行卡塔 尔(英语:State of Qatar)和代码在运维时采纳的数目,如重从来消息,如用于链接或调节和测量检验的主次符号,别的还包含另向外调拨运输试新闻。
gcc -c main.c 编译main.c ,生成目标文件main.o,但不进行link. gcc -o main.o链接成可执行文件main。

可推行文件——可实践代码就是将指标代码连接后产生的可实施文件,当然也是二进制的。 连接程序系统库文件三番两次就变化可施行文件。

例如:*.obj是程序编译之后生成的目标文件,连接程序再将这个文件与系统库文件连接就生成可执行文件

假定感觉笔者的小说对你有救助,想要打赏笔者,请扫下边包车型地铁Wechat(嗤笑下简书的打赏提现略坑卡塔 尔(英语:State of Qatar)

必威 1

Paste_Image.png

正文主要分析在debug情形下Android是怎么加载到bundle文件的严重性加载流程,不关乎太底层的代码均是Java代码深入分析。

总计一下什么样显示加载格局加载DLL,

原理其实便是通过在固化地方后边,加上二个莫衷一是值的日子数值,以到达地点不重复的目标,让浏览器每回都实时加载,不从缓存中读取文件。 很用于 动态验证码等需求每每加载文件的时候。本站实际选择实例 //www.jb51.net/tools/files.shtml

1.1C语言创造
  • 编辑
  • 编译
  • 链接
  • 执行

编辑:正是创立和改革C程序的源代码-大家编辑的前后相继名叫源代码。编译:正是将源代码转变为机器语言。编写翻译器的出口结果产生指标代码,存放它们的文本称为指标文件。增加名称为.o也许.obj。(该部分编写翻译是指汇编器编写翻译汇编语言照旧编译器编写翻译高档语言卡塔尔链接:链接器将源代码由编写翻译器发生的各个模块组合起来,再从C语言提供的程序库中加多供给的代码模块,将它们构成三个可举办的文书。在windows下扩大名为.exe,Unix下无扩充名。执行:运路程序。

必威 2C.png

1.1 SO文件的读取与加载职业

Linker使用ElfRead类的load函数达成so文件的深入分析专门的学问。该类的源代码在linker_phdr.cpp
中。Load函数代码如下:

bool ElfReader::Load() {
  return ReadElfHeader() &&
         VerifyElfHeader() &&
         ReadProgramHeader() &&
         ReserveAddressSpace() &&
         LoadSegments() &&
         FindPhdr();
}

领悟此函数依次调用ReadElfHeader、ReadProgramHeader等函数。
先是,大家必要掌握Android系统加载segments的体制:
两个ELF文件的次第头表蕴含一个或多少个PT_LOAD segments,那么些segments标识ELF文件中供给被映射到过程空间的区域。每多少个能够加载的segment都包括如下主要性质:
p_offset: 段在文件的撼动地址
p_filesz:段的深浅
p_memsz:段在内部存款和储蓄器中据有的大小(日常大于p_filesz)。
p_vaddr: 段的虚构地址
p_flags:段的标识(可读,可写,可举行)

这两天,大家忽视p_paddrp_align成员。
能够加载的segments 能在虚构地址范围 [p_vaddr…p_vaddr+p_memsz)以列表的花样表现。此中宛如下几个法规:

  1. 各个segments的虚构地址范围不得重叠;
  2. 假定四个segmentp_filesz小于p_memsz,那么两个之间的附加数据将被开始化为0;
  3. segment的虚构地址范围的起、始地址不是必需在某后生可畏页的边界。多少个不等的segments的起、始地址能够在相仿页,在此种境况,该页世襲后后生可畏segment的炫彩标识(mapping flags)
  4. 每一个segment事实上加载的地址并非p_vaddr,而是由加载器决定将率先个segment加载到内部存款和储蓄器中的哪些岗位,然后剩下的segments就以第二个segment为参照物,进行加载。

上面是多个loadable segments的消息:

[ offset:0,      filesz:0x4000, memsz:0x4000, vaddr:0x30000 ],
[ offset:0x4000, filesz:0x2000, memsz:0x8000, vaddr:0x40000 ],

相当于那五个segments的虚构地址范围分别为:

0x30000...0x34000
0x40000...0x48000

若是加载器决定将率先个segment加载到0xa0000000的话(通过前边的解析会知道,那几个加载地址是在加载程序底部表的时候由系统显然的),那么它们的骨子里设想地址范围正是:

0xa0030000...0xa0034000
0xa0040000...0xa0048000

换句话说,全数的segments的实际上加载伊始地址与其vaddr的偏差值是永世的(0xa0030000 – 0x30000 = 0xa0040000 – 0x40000)。

不过,在实质上意况下,segments之处而不是在每黄金年代页的疆界出开端的。思量到我们不能不在页面边界进行内部存款和储蓄器映射,由此,那就代表加载地址的偏差bias应当据守如下方法开展总计:

load_bias = phdr0_load_address - PAGE_START(phdr0->p_vaddr)
(#define PAGE_START(x) ((x) & PAGE_MASK)
PAGE_MASK的值一般为0xfffff000。)

所以率先个segment的load_bias = 0xa0030000 – 0x30000&0xfffff000 = 0xa00000000。
这里 phdr0_load_address 必需以某大器晚成页的边际为发轫地址,所以该segments的的确内容的发端地址为:

phdr0_load_address + PAGE_OFFSET(phdr0->p_vaddr)(#define PAGE_OFFSET(x) ((x) & ~PAGE_MASK) 就是x & 0xfff)

潜心:ELF供给如下条件,以满意mmap符合规律干活:

PAGE_OFFSET(phdr0->p_vaddr) == PAGE_OFFSET(phdr0->p_offset)

每一个loadable segments的 p_vaddr 都必需抬高 load_bias,其和就是该segments在内存中的实际开端地址。

1.1.1 ReadProgramHeader
理清了Android加载segments的体制,大家就来看linker中的实际代码,先看ReadProgramHeader:

bool ElfReader::ReadProgramHeader() {
phdr_num_ = header_.e_phnum;
  ……..
  ElfW(Addr) page_min = PAGE_START(header_.e_phoff);
  ElfW(Addr) page_max = PAGE_END(header_.e_phoff + (phdr_num_ * sizeof(ElfW(Phdr))));
  ElfW(Addr) page_offset = PAGE_OFFSET(header_.e_phoff);

  phdr_size_ = page_max - page_min;

  void* mmap_result = mmap(NULL, phdr_size_, PROT_READ, MAP_PRIVATE, fd_, page_min);
  ……..
  phdr_mmap_ = mmap_result;
  phdr_table_ = reinterpret_cast<ElfW(Phdr)*>(reinterpret_cast<char*>(mmap_result) + page_offset);
  return true;
}
  1. 首先读取elf文件的程序尾部表项数目phdr_num;
  2. 然后分别获得程序尾部表在页边界对齐后的胚胎地址page_min、甘休地址page_max和偏移地址page_offset。并根据page_max与page_start总括出程序尾部表私吞的页面大小phdr_size;
  3. 再以只读形式创建一个民用映射,该映射将elf文件中偏移值为page_min,大小为phdr_size的区域映射到内部存储器中。将映射后的内部存款和储蓄器地址赋给phdr_mmap_,轻巧一句话:将次第尾部表映射到内部存款和储蓄器中,并将内部存款和储蓄器地址赋值;
  4. reinterpret_cast<new_type>(expression),那是c++中的强制类型调换符,相通于(new_type*)(expression)。这里大家对地点黄铜色部分代码加以表明:

(注:浅蓝代码为倒数第三句)
首先reinterpret_cast<char>(mmap_result):经void型指针mmap_result强制调换来char型;
然后reinterpret_cast<char
>(mmap_result) + page_offset:char型指针+page_offset,表示针对程序尾部表真正伊始之处;
最终再将其改动到ElfW(Phdr)
型指针,显然phdr_table_针对程序底部表在此此前地址。

1.1.2 ReserveAddressSpace
再来看ReserveAddressSpace:

/*预备一块足够大的虚拟地址范围,用来加载所有可加载的segments.我们可以通过mmap创建一个带有PROT_NONE属性的私有匿名内存映射。PROT_NONE表示页不可访问,匿名映射表示映射区不与任何文件关联(要求fd为-1),私有映射表示对该映射区域的写入操作会产生一个映射文件的复制,对此区域做的任何修改够不会写会原来的文件*/
bool ElfReader::ReserveAddressSpace() {
  ElfW(Addr) min_vaddr;
  load_size_ = phdr_table_get_load_size(phdr_table_, phdr_num_, &min_vaddr);
  ……..
  uint8_t* addr = reinterpret_cast<uint8_t*>(min_vaddr);
  int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS;
  void* start = mmap(addr, load_size_, PROT_NONE, mmap_flags, -1, 0);
  ……..
  load_start_ = start;
  load_bias_ = reinterpret_cast<uint8_t*>(start) - addr;
  return true;
}

这里有四个首要函数phdr_table_get_load_siz:

/*返回ELF文件程序头部表中所指定的所有可加载segments(这些segments可能是非连续的)的区间大小,如果没有可加载的segments,就返回0
如果out_min_vaddr 或 out_max_vadd是非空的,它们就会被设置成将被存储的页的最小/大地址(如果没有可加载segments的话,就设为0) */
size_t phdr_table_get_load_size(const ElfW(Phdr)* phdr_table, size_t phdr_count,
                                ElfW(Addr)* out_min_vaddr,
                                ElfW(Addr)* out_max_vaddr) {
  ElfW(Addr) min_vaddr = UINTPTR_MAX;
  ElfW(Addr) max_vaddr = 0;

  bool found_pt_load = false;
  for (size_t i = 0; i < phdr_count; ++i) {
    const ElfW(Phdr)* phdr = &phdr_table[i];
    if (phdr->p_type != PT_LOAD) {
      continue;
    }
    found_pt_load = true;
    if (phdr->p_vaddr < min_vaddr) {
      min_vaddr = phdr->p_vaddr;
    }
    if (phdr->p_vaddr + phdr->p_memsz > max_vaddr) {
      max_vaddr = phdr->p_vaddr + phdr->p_memsz;
    }
  }
  if (!found_pt_load) {
    min_vaddr = 0;
  }

  min_vaddr = PAGE_START(min_vaddr);
  max_vaddr = PAGE_END(max_vaddr);

  if (out_min_vaddr != NULL) {
    *out_min_vaddr = min_vaddr;
  }
  if (out_max_vaddr != NULL) {
    *out_max_vaddr = max_vaddr;
  }
  return max_vaddr - min_vaddr;
}

通俗点讲,此函数就是重临ELF文件中包罗的可加载segments总共须要占用的空间尺寸,并安装其最小设想地址的值(是页对齐的)。值得注意的是,原函数有4个参数,可是在ReserveAddressSpace中调用该函数时却只传递了3个参数,忽视了out_max_vaddr。在自己个人看来是因为已知了out_min_vaddr及两岸的差值load_size
,所以能够透过out_min_vaddr + load_size来求得out_max_vaddr。
这几天赶回ReserveAddressSpace函数。求得load_size之后,就必要为这个segments分配丰裕的内部存款和储蓄器空间。这里须求潜心的是mmap的第叁个参数并不是为Null,而是addr。那就表示将映射区间的开头地址坐落于进度的addr地址处(通常不会中标,而是由系统活动分配,所以能够看成是Null),mmap再次回到实际映射后的内部存款和储蓄器带头地址start。分明load_bias_ = start – addr正是实际热映射内部存款和储蓄器地址同linker期待的照耀地址的引用误差值。后边的操作中,linker就足以通过p_vaddr

  • load_bias_来赢得某蓬蓬勃勃segments在内部存款和储蓄器中的开始地址了。

1.1.3 LoadSegments
于今就起来加载ELF文件中的可加载segments了:

bool ElfReader::LoadSegments() {
  for (size_t i = 0; i < phdr_num_; ++i) {
    const ElfW(Phdr)* phdr = &phdr_table_[i];

    if (phdr->p_type != PT_LOAD) {
      continue;
    }

    // Segment addresses in memory.
    ElfW(Addr) seg_start = phdr->p_vaddr + load_bias_;
    ElfW(Addr) seg_end   = seg_start + phdr->p_memsz;

    ElfW(Addr) seg_page_start = PAGE_START(seg_start);
    ElfW(Addr) seg_page_end   = PAGE_END(seg_end);

    ElfW(Addr) seg_file_end   = seg_start + phdr->p_filesz;
    // File offsets.
    ElfW(Addr) file_start = phdr->p_offset;
    ElfW(Addr) file_end   = file_start + phdr->p_filesz;

    ElfW(Addr) file_page_start = PAGE_START(file_start);
    ElfW(Addr) file_length = file_end - file_page_start;

    if (file_length != 0) {
      void* seg_addr = mmap(reinterpret_cast<void*>(seg_page_start),
                            file_length, //是以文件大小为参照,而非内存大小
                            PFLAGS_TO_PROT(phdr->p_flags),
                            MAP_FIXED|MAP_PRIVATE,
                            fd_,
                            file_page_start);
      if (seg_addr == MAP_FAILED) {
        DL_ERR("couldn't map /"%s/" segment %zd: %s", name_, i, strerror(errno));
        return false;
      }
    }

    /*如果segments可写,并且该segments的实际结束地址不在某一页的边界的话,就将该segments实际结束地址到此页的边界之间的内存全置为0*/
    if ((phdr->p_flags & PF_W) != 0 && PAGE_OFFSET(seg_file_end) > 0) {
      memset(reinterpret_cast<void*>(seg_file_end), 0, PAGE_SIZE - PAGE_OFFSET(seg_file_end));
    }

    seg_file_end = PAGE_END(seg_file_end);

    // seg_file_end is now the first page address after the file
    // content. If seg_end is larger, we need to zero anything
    // between them. This is done by using a private anonymous
    // map for all extra pages.
    if (seg_page_end > seg_file_end) {
      void* zeromap = mmap(reinterpret_cast<void*>(seg_file_end),
                           seg_page_end - seg_file_end,
                           PFLAGS_TO_PROT(phdr->p_flags),
                           MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE,
                           -1,
                           0);
      if (zeromap == MAP_FAILED) {
        DL_ERR("couldn't zero fill /"%s/" gap: %s", name_, strerror(errno));
        return false;
      }
    }
  }
  return true;
}

此部分效率异常粗略:正是将ELF中的可加载segments依次映射到内部存款和储蓄器中,并实行部分帮助扫尾职业。

1.1.4 FindPhdr
归来程序尾部表在内部存款和储蓄器中地址。那与phdr_table_是见仁见智的,后面一个是三个权且的、在so被重定位早先会为刑释解教的变量:

bool ElfReader::FindPhdr() {
  const ElfW(Phdr)* phdr_limit = phdr_table_ + phdr_num_;

  //如果段类型是 PT_PHDR, 那么我们就直接使用该段的地址.
  for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {
    if (phdr->p_type == PT_PHDR) {
      return CheckPhdr(load_bias_ + phdr->p_vaddr);
    }
  }

  //否则,我们就检查第一个可加载段。如果该段的文件偏移值为0,那么就表示它是以ELF头开始的,我们就可以通过它来找到程序头表加载到内存的地址(虽然过程有点繁琐)。
  for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {
    if (phdr->p_type == PT_LOAD) {
      if (phdr->p_offset == 0) {
        ElfW(Addr)  elf_addr = load_bias_ + phdr->p_vaddr;
        const ElfW(Ehdr)* ehdr = reinterpret_cast<const ElfW(Ehdr)*>(elf_addr);
        ElfW(Addr)  offset = ehdr->e_phoff;
        return CheckPhdr((ElfW(Addr))ehdr + offset);
      }
      break;
    }
  }

  DL_ERR("can't find loaded phdr for /"%s/"", name_);
  return false;
}

要知道这段代码,大家供给精通段类型PT_PHDKuga所代表的含义:钦定程序头表在文件及程序内部存款和储蓄器印象中的地点和尺寸。此段项目不能够在三个文本中频仍产出。其余,仅当程序头表是前后相继内部存款和储蓄器映像的一片段时,才足以现身此段。此类型(假若存在卡塔尔必得放在任何可装入段的各式的前面。有关详细新闻,请参见程序的解释程序。
现今so文件的读取、加载专业就深入分析实现了。大家得以窥见,Android对so的加载操作只是以段为单位,跟section完全未有关联。其它,通过翻看VerifyElfHeader的代码,大家还足以窥见,Android系统仅仅对ELF文件头的e_ident、e_type、e_version、e_machine进行认证(当然,e_phnum也是无法错的),所以,那就表明了怎么有个别加壳so文件头的section相关字段能够自由改正,系统也不会报错了。

1.2 so的链机场接人制

在1.1我们详细剖判了Android so的加运载飞机制,现在就起来深入分析so的链机场接人制。在解析linker的有关链接的源代码从前,大家供给学习ELF文件有关动态链接方面包车型大巴文化。

1.2.1 动态节区
假定二个对象文件参与动态链接,它的主次底部表将富含类型为 PT_DYNAMIC 的成分。此“段”包罗.dynamic节区(这么些节区是三个数组)。该节区选择二个特殊符号_DYNAMIC来标志,个中带犹如下布局的数组:

typedef struct { 
Elf32_Sword d_tag; 
union { 
Elf32_Word d_val; 
Elf32_Addr d_ptr; 
} d_un; 
} Elf32_Dyn; 
extern Elf32_Dyn _DYNAMIC[]; //注意这里是一个数组
/*注意:
对每个这种类型的对象,d_tag控制d_un的解释含义: 
d_val 此 Elf32_Word 对象表示一个整数值,可以有多种解释。
d_ptr 此 Elf32_Addr 对象代表程序的虚拟地址。
关于d_tag的值、该值的意义,及其与d_un的关系,可查看ELF.PDF  p24。 */

该Elf32_Dyn数组正是soinfo构造体中的dynamic成员,大家在第3节介绍的load_library函数中发觉,si->dynamic被赋值为null,那就申明,在加载阶段是无需此值的,独有在链接阶段才需求。Android的动态库的链接专门的学业也许由linker实现,首要代码正是在linker.cpp的soinfo_link_image(find_library_internal方法中调用)中,此函数的代码比相当多,我们来分块剖析:

首先,大家必要从程序尾部表中获取dynamic节区音信:

/*in function soinfo_link_image */    
    /*抽取动态节区*/
    size_t dynamic_count;
    ElfW(Word) dynamic_flags;
    /*这里的si->dynamic 为ElfW(Dyn)指针,就是上面提到的Elf32_Dyn _DYNAMIC[]*/
    phdr_table_get_dynamic_section(phdr, phnum, base, &si->dynamic,
                                   &dynamic_count, &dynamic_flags);

此函数很简单:

/*返回ELF文件中的dynamic节区在内存中的地址和大小,如果没有该节区就返回null
 * Input:
 *   phdr_table  -> program header table
 *   phdr_count  -> number of entries in tables
 *   load_bias   -> load bias
 * Output:
 *   dynamic       -> address of table in memory (NULL on failure).
 *   dynamic_count -> number of items in table (0 on failure).
 *   dynamic_flags -> protection flags for section (unset on failure)
*/
void phdr_table_get_dynamic_section(const ElfW(Phdr)* phdr_table, size_t phdr_count,
                                    ElfW(Addr) load_bias,
                                    ElfW(Dyn)** dynamic, size_t* dynamic_count, ElfW(Word)* dynamic_flags) {
  const ElfW(Phdr)* phdr = phdr_table;
  const ElfW(Phdr)* phdr_limit = phdr + phdr_count;

  for (phdr = phdr_table; phdr < phdr_limit; phdr++) {
    if (phdr->p_type != PT_DYNAMIC) {
      continue;
    }

    *dynamic = reinterpret_cast<ElfW(Dyn)*>(load_bias + phdr->p_vaddr);
    if (dynamic_count) {
      *dynamic_count = (unsigned)(phdr->p_memsz / 8);
      //这里需要解释下,在2.2.1中我们介绍了Elf32_Dyn的结构,它占8字节。而PT_DYNAMIC段就是存放着Elf32_Dyn数组,所以dynamic_count的值就是该段的memsz/8。
    }
    if (dynamic_flags) {
      *dynamic_flags = phdr->p_flags; 
    }
    return;
  }
  *dynamic = NULL;
  if (dynamic_count) {
    *dynamic_count = 0;
  }
}

得逞博得了dynamic节区音讯,我们就足以依赖该节区中的Elf32_Dyn数组来进展so链接操作了。我们需求从dynamic节区中收取有用的音讯,linker选择遍历dynamic数组的章程,依据每一个元素的flags()进行相应的拍卖:

/*in function soinfo_link_image */ 
    // 从动态dynamic节区中抽取有用信息
    uint32_t needed_count = 0;

    //开始从头遍历dyn数组,根据数组中个元素的标记进行相应的处理
    for (ElfW(Dyn)* d = si->dynamic; d->d_tag != DT_NULL; ++d) { //标记为 DT_NULL 的项目标注了整个 _DYNAMIC 数组的末端,因此以它为结尾标志。 
        ........
        switch (d->d_tag) {
        case DT_HASH:
            ........
            break;
        case DT_STRTAB:
            si->strtab = reinterpret_cast<const char*>(base + d->d_un.d_ptr);
            break;
        case DT_SYMTAB:
            si->symtab = reinterpret_cast<ElfW(Sym)*>(base + d->d_un.d_ptr);
            break;
        case DT_JMPREL:
#if defined(USE_RELA)
            si->plt_rela = reinterpret_cast<ElfW(Rela)*>(base + d->d_un.d_ptr);
#else
            si->plt_rel = reinterpret_cast<ElfW(Rel)*>(base + d->d_un.d_ptr);
#endif
            break;
        case DT_PLTRELSZ:
#if defined(USE_RELA)
            si->plt_rela_count = d->d_un.d_val / sizeof(ElfW(Rela));
#else
            si->plt_rel_count = d->d_un.d_val / sizeof(ElfW(Rel));
#endif
            break;
#if defined(__mips__)
        case DT_PLTGOT:
            // Used by mips and mips64.
            si->plt_got = reinterpret_cast<ElfW(Addr)**>(base + d->d_un.d_ptr);
            break;
#endif
         ........
#if defined(USE_RELA)
         case DT_RELA:
            si->rela = reinterpret_cast<ElfW(Rela)*>(base + d->d_un.d_ptr);
            break;
         case DT_RELASZ:
            si->rela_count = d->d_un.d_val / sizeof(ElfW(Rela));
            break;
        case DT_REL:
            DL_ERR("unsupported DT_REL in /"%s/"", si->name);
            return false;
        case DT_RELSZ:
            DL_ERR("unsupported DT_RELSZ in /"%s/"", si->name);
            return false;
#else
        case DT_REL:
            si->rel = reinterpret_cast<ElfW(Rel)*>(base + d->d_un.d_ptr);
            break;
        case DT_RELSZ:
            si->rel_count = d->d_un.d_val / sizeof(ElfW(Rel));
            break;
         case DT_RELA:
            DL_ERR("unsupported DT_RELA in /"%s/"", si->name);
            return false;
#endif
        case DT_INIT: //只有可执行文件才有此节区
            si->init_func = reinterpret_cast<linker_function_t>(base + d->d_un.d_ptr);
            DEBUG("%s constructors (DT_INIT) found at %p", si->name, si->init_func);
            break;
        case DT_FINI:
            si->fini_func = reinterpret_cast<linker_function_t>(base + d->d_un.d_ptr);
            DEBUG("%s destructors (DT_FINI) found at %p", si->name, si->fini_func);
            break;
        case DT_INIT_ARRAY:
            si->init_array = reinterpret_cast<linker_function_t*>(base + d->d_un.d_ptr);
            DEBUG("%s constructors (DT_INIT_ARRAY) found at %p", si->name, si->init_array);
            break;
        case DT_INIT_ARRAYSZ:
            si->init_array_count = ((unsigned)d->d_un.d_val) / sizeof(ElfW(Addr));
            break;
        case DT_FINI_ARRAY:
            si->fini_array = reinterpret_cast<linker_function_t*>(base + d->d_un.d_ptr);
            DEBUG("%s destructors (DT_FINI_ARRAY) found at %p", si->name, si->fini_array);
            break;
        case DT_FINI_ARRAYSZ: 
            si->fini_array_count = ((unsigned)d->d_un.d_val) / sizeof(ElfW(Addr));
            break;
        case DT_PREINIT_ARRAY:
            si->preinit_array = reinterpret_cast<linker_function_t*>(base + d->d_un.d_ptr);
            DEBUG("%s constructors (DT_PREINIT_ARRAY) found at %p", si->name, si->preinit_array);
            break;
        case DT_PREINIT_ARRAYSZ:
            si->preinit_array_count = ((unsigned)d->d_un.d_val) / sizeof(ElfW(Addr));
            break;
        case DT_TEXTREL:
#if defined(__LP64__)
            DL_ERR("text relocations (DT_TEXTREL) found in 64-bit ELF file /"%s/"", si->name);
            return false;
#else
            si->has_text_relocations = true;
            break;
#endif
        case DT_SYMBOLIC:
            si->has_DT_SYMBOLIC = true;
            break;
        case DT_NEEDED:
            ++needed_count;
            break;
        case DT_FLAGS:
            if (d->d_un.d_val & DF_TEXTREL) {
                ........
                si->has_text_relocations = true;
            }
            if (d->d_un.d_val & DF_SYMBOLIC) {
                si->has_DT_SYMBOLIC = true;
            }
            break;
#if defined(__mips__)
        case DT_STRSZ:
        case DT_SYMENT:
        case DT_RELENT:
             break;
        case DT_MIPS_RLD_MAP:
            // Set the DT_MIPS_RLD_MAP entry to the address of _r_debug for GDB.
            {
              r_debug** dp = reinterpret_cast<r_debug**>(base + d->d_un.d_ptr);
              *dp = &_r_debug;
            }
            break;
        case DT_MIPS_RLD_VERSION:
        case DT_MIPS_FLAGS:
        case DT_MIPS_BASE_ADDRESS:
        case DT_MIPS_UNREFEXTNO:
            break;

        case DT_MIPS_SYMTABNO:
            si->mips_symtabno = d->d_un.d_val;
            break;

        case DT_MIPS_LOCAL_GOTNO:
            si->mips_local_gotno = d->d_un.d_val;
            break;

        case DT_MIPS_GOTSYM:
            si->mips_gotsym = d->d_un.d_val;
            break;
#endif

        default:
            DEBUG("Unused DT entry: type %p arg %p",
                  reinterpret_cast<void*>(d->d_tag), reinterpret_cast<void*>(d->d_un.d_val));
            break;
        }
    }

成就dynamic数组的遍历后,就认证我们早已赢得了内部的有用新闻了,那么现在就须求基于这个消息举办拍卖:

/*in function soinfo_link_image */ 

    //再检测一遍,这种做法总是明智的
    if (relocating_linker && needed_count != 0) {
        DL_ERR("linker cannot have DT_NEEDED dependencies on other libraries");
        return false;
    }
    if (si->nbucket == 0) {
        DL_ERR("empty/missing DT_HASH in /"%s/" (built with --hash-style=gnu?)", si->name);
        return false;
    }
    if (si->strtab == 0) {
        DL_ERR("empty/missing DT_STRTAB in /"%s/"", si->name);
        return false;
    }
    if (si->symtab == 0) {
        DL_ERR("empty/missing DT_SYMTAB in /"%s/"", si->name);
        return false;
    }

    // If this is the main executable, then load all of the libraries from LD_PRELOAD now.
    //如果是main可执行文件,那么就根据LD_PRELOAD信息来加载所有相关的库
    //这里面涉及到的gLdPreloadNames变量,我们知道在前面的整个分析过程中均没有涉及,这是因为,对于可执行文件而言,它的起始函数并不是dlopen,而是系统内核的execv函数,通过层层调用之后才会执行到linker的linker_init_post_ralocation函数,在这个函数中调用parse_LD_PRELOAD函数完成 gLdPreloadNames变量的赋值
    if (si->flags & FLAG_EXE) {
        memset(gLdPreloads, 0, sizeof(gLdPreloads));
        size_t preload_count = 0;
        for (size_t i = 0; gLdPreloadNames[i] != NULL; i++) {
            soinfo* lsi = find_library(gLdPreloadNames[i]);
            if (lsi != NULL) {
                gLdPreloads[preload_count++] = lsi;
            } else {
                ........
            }
        }
    }

    //分配一个soinfo*[]指针数组,用于存放本so库需要的外部so库的soinfo指针
    soinfo** needed = reinterpret_cast<soinfo**>(alloca((1 + needed_count) * sizeof(soinfo*)));
    soinfo** pneeded = needed;
    //依次获取dynamic数组中定义的每一个外部so库soinfo
    for (ElfW(Dyn)* d = si->dynamic; d->d_tag != DT_NULL; ++d) {
        if (d->d_tag == DT_NEEDED) {
            const char* library_name = si->strtab + d->d_un.d_val; //根据index值获取所需库的名字
            DEBUG("%s needs %s", si->name, library_name);
            soinfo* lsi = find_library(library_name);  //获取该库的soinfo
            if (lsi == NULL) {
                ........
            }
            *pneeded++ = lsi;
        }
    }
    *pneeded = NULL; 

#if !defined(__LP64__)
    if (si->has_text_relocations) {
        // Make segments writable to allow text relocations to work properly. We will later call
        // phdr_table_protect_segments() after all of them are applied and all constructors are run.
        DL_WARN("%s has text relocations. This is wasting memory and prevents "
                "security hardening. Please fix.", si->name);
        if (phdr_table_unprotect_segments(si->phdr, si->phnum, si->load_bias) < 0) {
            DL_ERR("can't unprotect loadable segments for /"%s/": %s",
                   si->name, strerror(errno));
            return false;
        }
    }
#endif

#if defined(USE_RELA)
    if (si->plt_rela != NULL) {
        DEBUG("[ relocating %s plt ]/n", si->name);
        if (soinfo_relocate(si, si->plt_rela, si->plt_rela_count, needed)) {
            return false;
        }
    }
    if (si->rela != NULL) {
        DEBUG("[ relocating %s ]/n", si->name);
        if (soinfo_relocate(si, si->rela, si->rela_count, needed)) {
            return false;
        }
    }
#else
    if (si->plt_rel != NULL) {
        DEBUG("[ relocating %s plt ]", si->name);
        if (soinfo_relocate(si, si->plt_rel, si->plt_rel_count, needed)) {
            return false;
        }
    }
    if (si->rel != NULL) {
        DEBUG("[ relocating %s ]", si->name);
        if (soinfo_relocate(si, si->rel, si->rel_count, needed)) {
            return false;
        }
    }
#endif

#if defined(__mips__)
    if (!mips_relocate_got(si, needed)) {
        return false;
    }
#endif

    si->flags |= FLAG_LINKED;
    DEBUG("[ finished linking %s ]", si->name);

#if !defined(__LP64__)
    if (si->has_text_relocations) {
        // All relocations are done, we can protect our segments back to read-only.
        if (phdr_table_protect_segments(si->phdr, si->phnum, si->load_bias) < 0) {
            DL_ERR("can't protect segments for /"%s/": %s",
                   si->name, strerror(errno));
            return false;
        }
    }
#endif

    /* We can also turn on GNU RELRO protection */
    if (phdr_table_protect_gnu_relro(si->phdr, si->phnum, si->load_bias) < 0) {
        DL_ERR("can't enable GNU RELRO protection for /"%s/": %s",
               si->name, strerror(errno));
        return false;
    }

    notify_gdb_of_load(si);
    return true;
}

0x02 起头施行so文件
上面的find_library_internal函数中的soinfo_link_image函数试行完后就赶回到上层函数find_library中,然后一发重回到do_dlopen函数:

soinfo* do_dlopen(const char* name, int flags) {
  if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) {
    DL_ERR("invalid flags to dlopen: %x", flags);
    return NULL;
  }
  set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
  soinfo* si = find_library(name);
  if (si != NULL) {
    si->CallConstructors();
  }
  set_soinfo_pool_protection(PROT_READ);
  return si;
}

倘若获得的si不为空,就申明so的加载和链接操作不易完成,那么就能够进行so的初阶化布局函数了:

void soinfo::CallConstructors() {
  ........
  // DT_INIT should be called before DT_INIT_ARRAY if both are present.
  //如果文件含有.init和.init_array节区的话,就先执行.init节区的代码再执行.init_array节区的代码
  CallFunction("DT_INIT", init_func);  
  CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
}

是因为大家只解析so库,所以只供给关切CallArray("DT_INIT_ARRAY", init_array, init_array_count, false)函数就能够:

void soinfo::CallArray(const char* array_name UNUSED, linker_function_t* functions, size_t count, bool reverse) {
  ........
  //这里的recerse变量用于指定.init_array中的函数是由前到后执行还是由后到前执行。默认是由前到后
  int begin = reverse ? (count - 1) : 0;
  int end = reverse ? -1 : count;
  int step = reverse ? -1 : 1;

  for (int i = begin; i != end; i += step) {
    TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);
    CallFunction("function", functions[i]); //依次调用init_array中的函数。
  }
 ........
}

这里要求对init_array节区的构造和效力加以注脚。
首先是init_array节区的数据结构。该节中包涵指针,那个指针指向了朝气蓬勃部分起首化代码。那个最先化代码日常是在main函数以前实施的。在C++程序中,这么些代码用来运维静态布局函数。此外叁个用处正是奇迹用来初步化C库中的一些IO系统。使用IDA查看全数init_array节区的so库文件就能够找到如下数据:

必威 3

p1

那边共四个函数指针,每一个指针指向三个函数地址。值得注意的是,上海教室中各样函数指针的值都加了1,那是因为地点的结尾1地方1注解必要使得拍卖器由ARM转为Thumb状态来管理Thumb指令。将目标地方处的代码解释为Thumb代码来施行。

下一场再来看CallFunction的现实性达成:

void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) {
  //如果函数地址为空或者为-1就直接退出。
  if (function == NULL || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {
    return;
  }
  ........
  function(); //执行该指针所指定的函数
  // The function may have called dlopen(3) or dlclose(3), so we need to ensure our data structures
  // are still writable. This happens with our debug malloc (see http://b/7941716).
  set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
}

于今,整个Android so的linker机制就深入分析实现了!

旁注
参考资料:
http://www.evil0x.com/posts/15502.html
https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-42444.html

外加开掘

我们在扭转这几个下载地址的时候是还是不是察觉那样大器晚成行代码:

  public String getDebugServerHost() {

    String hostFromSettings = mPreferences.getString(PREFS_DEBUG_SERVER_HOST_KEY, null);
    if (!TextUtils.isEmpty(hostFromSettings)) {
      return Assertions.assertNotNull(hostFromSettings);
    }
    return host;
  }

也正是说假诺PREFS_DEBUG_SERVER_HOST_KEY这么些相应的Preferences不为空那么大家就从这一个地方上加载相呼应的bundle文件,所以我们得以依附那几个规律弄个输入框只要写入ip以致端口大家就能够读取别人写好的奔驰M级N并落实调节和测量检验了,如:

PreferenceManager.getDefaultSharedPreferences(applicationContext)
.put("192.168.1.1:8081");

是啊,那样子大家就能够直接读取那个ip下的bundle文件了,当然前提是要留存相呼应的bundle文件。

稍许走了一回流程清楚多了只怎么个加载原理,当然Android跟PRADON交互作用的底部代码依然比很大的生龙活虎局地代码,有待深入分析清楚。

  有了DLL的句柄,大家必要再得到导出函数的地点就能够,获得地方,用此函数:GetProcAddress(),参数1是:DLL句柄,参数2:一个指南针,指向导出函数的名字。该函数再次来到值假若为NULL,则得到地址败北。成功,则赶回导出函数的导出地址。

1.C语言开立程序

0x00 知识思量

Linker是Android系统动态库so的加载器/链接器,要想轻巧地掌握Android linker的运转搭乘飞机制,我们须求先熟知ELF的文件结构,再驾驭ELF文件的装入/运营,最终学习Linker的加载和运维原理。
鉴于ELF文件布局网络有众多资料,这里就不做累述了。

加载

我们在DevSupportManagerImpl的布局函数中重要关切一些至关心注重要类的伊始化:

// DevServerHelper初始化
  public DevServerHelper(DevInternalSettings settings, String packageName) {
    mSettings = settings;
    mClient = new OkHttpClient.Builder()
      .connectTimeout(HTTP_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
      .readTimeout(0, TimeUnit.MILLISECONDS)
      .writeTimeout(0, TimeUnit.MILLISECONDS)
      .build();
    mBundleDownloader = new BundleDownloader(mClient);

    mRestartOnChangePollingHandler = new Handler();
    mPackageName = packageName;
  }

很声名远扬这里关键是初叶化了二个OkHttpClient对象,自然这里一定是用于央浼地址用的。

mJSBundleTempFile = new File(applicationContext.getFilesDir(), JS_BUNDLE_FILE_NAME);

开头化了一个ReactNativeDevBundle.js文件
很好一切都就绪之后起头实践mDevSupportManager.handleReloadJS那几个办法:

  public void handleReloadJS() {
  // 其他的代码都忽略只看这部分的代码即可
    PrinterHolder.getPrinter()
          .logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: load from Server");
      String bundleURL =
        mDevServerHelper.getDevServerBundleURL(Assertions.assertNotNull(mJSAppBundleName));
      reloadJSFromServer(bundleURL);
  }

最首要部分的来了mDevServerHelper.getDevServerBundleU宝马X5L:

  public String getDevServerBundleURL(final String jsModulePath) {
    return createBundleURL(
        mSettings.getPackagerConnectionSettings().getDebugServerHost(),
        jsModulePath,
        getDevMode(),
        getJSMinifyMode(),
        mSettings.isBundleDeltasEnabled());
  }

跟着流程继续看是怎么拼接host地址的getDebugServerHost进入这一个法子最后会意识:

public static final String EMULATOR_LOCALHOST = "10.0.2.2";
public static final String GENYMOTION_LOCALHOST = "10.0.3.2";
public static final String DEVICE_LOCALHOST = "localhost";

private static String getServerIpAddress(int port) {
    // Since genymotion runs in vbox it use different hostname to refer to adb host.
    // We detect whether app runs on genymotion and replace js bundle server hostname accordingly

    String ipAddress;
    if (isRunningOnGenymotion()) {
      ipAddress = GENYMOTION_LOCALHOST;
    } else if (isRunningOnStockEmulator()) {
      ipAddress = EMULATOR_LOCALHOST;
    } else {
      ipAddress = DEVICE_LOCALHOST;
    }

    return String.format(Locale.US, "%s:%d", ipAddress, port);
  }

那边系统还或然会咬定是还是不是是Genymotion只怕自带的Emulator模拟器,当然这一个大家都未有安装,所以那边一贯回到的是DEVICE_LOCALHOST那几个地面包车型客车地方,所以最后的拼接出来的的地址是:localHost:8081 ..
接下来我们回到createBundleU大切诺基L这一个方法中获取最最最最末尾的拼接地址是:

http://localHost:8081/index.android.bundle?platform=android&dev=true&jsMinify=false

下一场大家近日早已开首化好了OKHttpClient对象,接下去就是推行那一个地点文件的下载,并下载到mJSBundleTempFile也即是ReactNativeDevBundle.js那么些文件并保存到大家Context.getFilesDir路线上面。

最终大家下载成功今后会回调到ReactInstanceManager类的

  private void onJSBundleLoadedFromServer() {
    Log.d(ReactConstants.TAG, "ReactInstanceManager.onJSBundleLoadedFromServer()");
    recreateReactContextInBackground(
        mJavaScriptExecutorFactory,
        JSBundleLoader.createCachedBundleFromNetworkLoader(
            mDevSupportManager.getSourceUrl(), mDevSupportManager.getDownloadedJSBundleFile()));
  }

下一场继续recreateReactContextInBackground那几个法子下去,那些法子那就做太多职业了,里面涉及到jni相关的本领轻巧解析不下来了,所以就此甘休了。

(1)    新建项目,名字为:dll,增加八个源文件(.cpp卡塔 尔(阿拉伯语:قطر‎,编代码,编写翻译文件 。

1.4gcc命令图

必威 4QQ截图20170613151139.png

0x01 so的加载和开发银行

我们领略假诺多个APP必要运用某生龙活虎分享库so的话,它会在JAVA层声后汉码:

static{
  System.loadLibrary(“name”);
}

此代码达成library的加载工作。翻看system.loadLibrary的源代码,能够窥见:
System.loadLibrary也是二个native方法,它的调用的进度是:

Dalvik/vm/native/java_lang_Runtime.cpp: 
Dalvik_java_lang_Runtime_nativeLoad ->Dalvik/vm/Native.cpp:dvmLoadNativeCode

展开函数dvmLoadNativeCode,能够找到以下代码:

//获得指定库文件的句柄,这个handle是soinfo*//这个库文件就是System.loadLibrary(pathName)传递的参数
……..handle = dlopen(pathName, RTLD_LAZY); 
…..
//获取该文件的JNI_OnLoad函数的地址 
vonLoad = dlsym(handle, "JNI_OnLoad");   
//如果找不到JNI_OnLoad,就说明这是用javah风格的代码了,那么就推迟解析 
if (vonLoad == NULL) { 
  //这句话我们在logcat中经常看见!
  LOGD("No JNI_OnLoad found in %s %p, skipping init",pathName, classLoader); 
}else{
  ….
}

从地方的代码可以看来Android系统加载分享库的关键代码为dlopen函数。那个dlopen函数的代码在bionic/linker/dlfcn.c中:

void* dlopen(const char* filename, int flags) { 
  ScopedPthreadMutexLocker locker(&gDlMutex); 
  soinfo* result = do_dlopen(filename, flags); 
  if (result == NULL) { 
    __bionic_format_dlerror("dlopen failed", linker_get_error_buffer()); return NULL;
  } 
  return result;
}

此函数根本透过调用do_dlopen
函数来回到多少个动态链接库的句柄,该句柄为两个soinfo布局体。Soinfo构造体的绘影绘声定义在bionic/linker/linker.h中。
气贯长虹查看do_dlopen函数,代码在linker.cpp中:

soinfo* do_dlopen(const char* name, int flags) {
  if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) {
    DL_ERR("invalid flags to dlopen: %x", flags); 
    return NULL;
  } 
  set_soinfo_pool_protection(PROT_READ | PROT_WRITE); 
  soinfo* si = find_library(name); //查找动态链接库 
  if (si != NULL) { 
    si->CallConstructors(); 
  } 
  set_soinfo_pool_protection(PROT_READ); 
  return si;
}

显然,重点在find_library函数。此函数代码如下:

static soinfo* find_library(const char* name) { 
  soinfo* si = find_library_internal(name); 
  if (si != NULL) { 
    si->ref_count++; 
  } 
  return si;
}

继续往下深切:

static soinfo* find_library_internal(const char* name) { 
  …….. 
 //首先查看这个so是否已经加载,如果已经加载,就返回该so的soinfo
  soinfo* si = find_loaded_library(name);  
  if (si != NULL) { 
    if (si->flags & FLAG_LINKED) { 
      return si; 
    } 
    DL_ERR("OOPS: recursive link to /"%s/"", si->name); 
    return NULL; 
  } 
  TRACE("[ '%s' has not been loaded yet. Locating...]", name); 
  si = load_library(name); //说明该so没有被加载,就调用此函数进行加载 
  if (si == NULL) { 
    return NULL; 
  } 

  // At this point we know that whatever is loaded @ base is a valid ELF 
  // shared library whose segments are properly mapped in. 
  TRACE("[ find_library_internal base=%p size=%zu name='%s' ]", reinterpret_cast<void*>(si->base), si->size, si->name); 

  //加载完so后,根据si的反馈进行链接。会在第3节进行详细分析 
  if (!soinfo_link_image(si)) { 
    munmap(reinterpret_cast<void*>(si->base), si->size); 
    soinfo_free(si); 
    return NULL; 
  } 
  return si;
}

先不去关切那个错误管理音信,大家如若各样函数的再次回到值均在预期范围内,这一个函数的实践流程为:

  1. 使用find_loaded_library函数在已经加载的动态链接库链表里面查找该动态库。要是找到了,就再次来到该动态库的soinfo,不然实行第②步;
  2. 当时,表明钦点的动态链接库还一直不被加载,就接纳load_library函数来加载该动态库。

load_library函数是整个so加载进度的首要性!它创建了动态链接库的句柄,代码如下:

static soinfo* load_library(const char* name) { 
  // Open the file. 
  int fd = open_library(name); 
  if (fd == -1) { 
    DL_ERR("library /"%s/" not found", name); 
    return NULL; 
  } 
  // Read the ELF header and load the segments. 
  ElfReader elf_reader(name, fd); 
  if (!elf_reader.Load()) { 
    return NULL; 
  } 
  const char* bname = strrchr(name, '/'); 
  soinfo* si = soinfo_alloc(bname ? bname + 1 : name); 
  if (si == NULL) { 
    return NULL; 
  } 
  si->base = elf_reader.load_start(); 
  si->size = elf_reader.load_size(); 
  si->load_bias = elf_reader.load_bias(); 
  si->flags = 0; 
  si->entry = 0; //入口函数设为null 
  si->dynamic = NULL; 
  si->phnum = elf_reader.phdr_count(); 
  si->phdr = elf_reader.loaded_phdr(); 
  return si;
}

load_library 函数的实施进程能够包涵如下:

  1. 使用open_library函数展开内定so文件;
  2. 始建ElfReader类对象,并透过该对象的load方法,读取Elf文件头,然后经过剖析Elf文件来加载各样segments;
  3. 使用soinfo_alloc函数分配二个soinfo构造体,并为这几个布局体中的各类成员赋值。

下面对步骤二而且详细介绍。

开始

率先我们也在AndroidStudio中多多少少看过讴歌RDXN的源码,也驾驭它实在正是二个ReactRootView,并且是通过下边这段代码举办加载相呼应的视图展现大家要的UI效果:

mReactRootView.startReactApplication(
              getReactNativeHost().getReactInstanceManager(),
              mainComponentName,
              getLaunchOptions());

能够驾驭mainComponentName那个是我们重写了ReactActivity中相呼应的

public String getMainComponentName() {
        return "RN_Demo";
    }

但是ReactInstanceManager那些类通晓的依然相比较,根据代码的追踪依旧比较轻松的就找到了相呼应的起首化代码:

protected ReactInstanceManager createReactInstanceManager() {
  ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
    .setApplication(mApplication)
    .setJSMainModulePath(getJSMainModuleName())
    .setUseDeveloperSupport(getUseDeveloperSupport())
    .setRedBoxHandler(getRedBoxHandler())
    .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
    .setUIImplementationProvider(getUIImplementationProvider())
    .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);

  for (ReactPackage reactPackage : getPackages()) {
    builder.addPackage(reactPackage);
  }

  String jsBundleFile = getJSBundleFile();
  if (jsBundleFile != null) {
    builder.setJSBundleFile(jsBundleFile);
  } else {
    builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
  }
  return builder.build();
}

缘何要看那个代码,首假若为着在debug情况的时候能理解那几个参数是怎么从何地来,所以这里关键涉及上面几个函数:

// 返回用于拼接bundle的名字
protected String getJSMainModuleName() {
  return "index.android";
}

// debug的时候是返回true
public boolean getUseDeveloperSupport() {
  return BuildConfig.DEBUG;
}

记住那七个措施之后,我们后续看ReactRootView的加载代码:createReactContextInBackground -> recreateReactContextInBackgroundInner
接下来那边境海关系到了叁个崭新的类DevSupportManager那些是叁个接口他的求实实现类:
DisabledDevSupportManager: 用于线上的本子
DevSupportManagerImpl: 用于debug的版本

// DevSupportManager的初始化方式:
mDevSupportManager =
        DevSupportManagerFactory.create(
            applicationContext,
            createDevHelperInterface(),
            mJSMainModulePath,
            useDeveloperSupport,
            redBoxHandler,
            devBundleDownloadListener,
            minNumShakes);

基于ReactInstanceManager的开端化大家得以领会 mJSMainModulePath = getJSMainModuleName() = "index.android" .. 然后大家很当然的跟步入看看是怎么初叶化的原本是透过反射大法:

String className =
        new StringBuilder(DEVSUPPORT_IMPL_PACKAGE)
          .append(".")
          .append(DEVSUPPORT_IMPL_CLASS)
          .toString();
      Class<?> devSupportManagerClass =
        Class.forName(className);
      Constructor constructor =
        devSupportManagerClass.getConstructor(
          Context.class,
          ReactInstanceManagerDevHelper.class,
          String.class,
          boolean.class,
          RedBoxHandler.class,
          DevBundleDownloadListener.class,
          int.class);
      return (DevSupportManager) constructor.newInstance(
        applicationContext,
        reactInstanceManagerHelper,
        packagerPathForJSBundleName,
        true,
        redBoxHandler,
        devBundleDownloadListener,
        minNumShakes);

自然倘诺是非debug的时候会回去:

if (!enableOnCreate) {
  return new DisabledDevSupportManager();
}

不轻便呀,终于是找到debug相关的类,既然早先化达成了那么大家就进入看看里面做了哪些?

必威 5

今天大家相比一下对象文件和可实行文件的例外。

目标文件.text和.data段地址

必威 6QQ截图20170613113520.png

可实施文件.text和.data段地址

必威 7QQ截图20170613113500.png

对象文件中跳转指令

必威 8Screen Shot 2017-06-11 at 3.35.56 PM.png

可试行文件中跳转指令

必威 9Screen Shot 2017-06-11 at 3.36.18 PM.png

对象文件中内部存储器访谈指令

必威 10Screen Shot 2017-06-11 at 3.37.08 PM.png

可实施文件中内部存款和储蓄器访问指令

必威 11

  • 能够看来指令中的绝对地址都改成相对地址了。
  • 结合上2片段分析,大家得以见到。
  • .text和.data段代码加载到内部存款和储蓄器中的地点由空缺0形成了具体地址。
  • .text段代码中有的跳转指令和内部存款和储蓄器访谈指令中的地址由相对地址改成加载时的内存地址,
  • .data段代码也由相对地址改为相对地址。

正文转自:Android Linker学习笔记

总结

在大家写完GL450N代码的时候 react-native run-android 命令运转后您能来看:
作者们会运行多个Http服务并监听8081端口,然后大家把咱们写的成效经过Android后生可畏多种的布局流程打包成apk并安装。在大家敲入命令的时候我们会意识:

request:/index.android.bundle?platform=android&dev=true

大概原理:XC90N会在我们本地帮我们把相关的数量打包完毕并上盛传那个地点去,所以大家在debug的时候可以透过下载得到相对应的bundle文件。

必威 12

本文由必威发布于必威-运维,转载请注明出处:Linker学习笔记必威:,不涉及太底层的代码均是

相关阅读