日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android Linker学习笔记

發布時間:2025/3/15 Android 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android Linker学习笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文地址:?http://drops.wooyun.org/tips/12122

0x00 知識預備


Linker是Android系統動態庫so的加載器/鏈接器,要想輕松地理解Android linker的運行機制,我們需要先熟悉ELF的文件結構,再了解ELF文件的裝入/啟動,最后學習Linker的加載和啟動原理。

鑒于ELF文件結構網上有很多資料,這里就不做累述了。

0x01 so的加載和啟動


我們知道如果一個APP需要使用某一共享庫so的話,它會在JAVA層聲明代碼:

1 2 3 Static{ System.loadLibrary(“name”); }

此代碼完成library的加載工作。翻看system.loadLibrary的源代碼,可以發現:

System.loadLibrary也是一個native方法,它的調用的過程是:

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

打開函數dvmLoadNativeCode,可以找到以下代碼:

1 2 3 4 5 6 7 8 9 10 …….. handle = dlopen(pathName, RTLD_LAZY);//獲得指定庫文件的句柄,這個handle是soinfo* //這個庫文件就是System.loadLibrary(pathName)傳遞的參數 ….. vonLoad = dlsym(handle,"JNI_OnLoad");//獲取該文件的JNI_OnLoad函數的地址 ???if(vonLoad == NULL) { //如果找不到JNI_OnLoad,就說明這是用javah風格的代碼了,那么就推遲解析 ?LOGD("No JNI_OnLoad found in %s %p, skipping init",pathName, classLoader); //這句話我們在logcat中經常看見! }else{ …. }

從上面的代碼可以看出Android系統加載共享庫的關鍵代碼為dlopen函數。這個dlopen函數的代碼在bionic/linker/dlfcn.c中:

1 2 3 4 5 6 7 8 9 void* dlopen(constchar* filename,int flags) { ??ScopedPthreadMutexLocker locker(&gDlMutex); ??soinfo* result = do_dlopen(filename, flags); ??if(result == NULL) { ????__bionic_format_dlerror("dlopen failed", linker_get_error_buffer()); ????returnNULL; ??} ??returnresult; }

此函數主要通過調用do_dlopen函數來返回一個動態鏈接庫的句柄,該句柄為一個soinfo結構體。Soinfo結構體的具體定義在bionic/linker/linker.h中。

繼續查看do_dlopen函數,代碼在linker.cpp中:

1 2 3 4 5 6 7 8 9 10 11 12 13 soinfo* do_dlopen(constchar* name,int flags) { ??if((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) { ????DL_ERR("invalid flags to dlopen: %x", flags); ????returnNULL; ??} ??set_soinfo_pool_protection(PROT_READ | PROT_WRITE); ??soinfo* si = find_library(name);//查找動態鏈接庫 ??if(si != NULL) { ????si->CallConstructors(); ??} ??set_soinfo_pool_protection(PROT_READ); ??returnsi; }

顯然,重點在find_library函數。此函數代碼如下:

1 2 3 4 5 6 7 staticsoinfo* find_library(constchar* name) { ??soinfo* si = find_library_internal(name); ??if(si != NULL) { ????si->ref_count++; ??} ??returnsi; }

繼續往下深入:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 staticsoinfo* find_library_internal(constchar* name) { ??…….. ??soinfo* si = find_loaded_library(name);?//首先查看這個so是否已經加載,如果已經加載,就返回該so的soinfo ??if(si != NULL) { ????if(si->flags & FLAG_LINKED) { ??????returnsi; ????} ????DL_ERR("OOPS: recursive link to \"%s\"", si->name); ????returnNULL; ??} ??TRACE("[ '%s' has not been loaded yet.? Locating...]", name); ??si = load_library(name);?//說明該so沒有被加載,就調用此函數進行加載 ??if(si == NULL) { ????returnNULL; ??} ??// 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); ??if(!soinfo_link_image(si)) {? //加載完so后,根據si的反饋進行鏈接。會在第3節進行詳細分析 ????munmap(reinterpret_cast<void*>(si->base), si->size); ????soinfo_free(si); ????returnNULL; ??} ??returnsi; }

先不去關心那些錯誤處理信息,我們假設各個函數的返回值均在預期范圍內,這個函數的執行流程為:

  • 使用find_loaded_library函數在已經加載的動態鏈接庫鏈表里面查找該動態庫。如果找到了,就返回該動態庫的soinfo,否則執行第②步;
  • 此時,說明指定的動態鏈接庫還沒有被加載,就使用load_library函數來加載該動態庫。
  • load_library函數是整個so加載過程的重中之重!它創建了動態鏈接庫的句柄,代碼如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 staticsoinfo* load_library(constchar* name) { ????// Open the file. ????intfd = open_library(name); ????if(fd == -1) { ????????DL_ERR("library \"%s\" not found", name); ????????returnNULL; ????} ????// Read the ELF header and load the segments. ????ElfReader elf_reader(name, fd); ????if(!elf_reader.Load()) { ????????returnNULL; ????} ????constchar* bname =strrchr(name,'/'); ????soinfo* si = soinfo_alloc(bname ? bname + 1 : name); ????if(si == NULL) { ????????returnNULL; ????} ????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(); ????returnsi; }

    load_library函數的執行過程可以概括如下:

  • 使用open_library函數打開指定so文件;
  • 創建ElfReader類對象,并通過該對象的load方法,讀取Elf文件頭,然后通過分析Elf文件來加載各個segments;
  • 使用soinfo_alloc函數分配一個soinfo結構體,并為這個結構體中的各個成員賦值。
  • 下面對步驟二加以詳細介紹。

    1.1 SO文件的讀取與加載工作

    Linker使用ElfRead類的load函數完成so文件的分析工作。該類的源代碼在linker_phdr.cpp中。Load函數代碼如下:

    1 2 3 4 5 6 7 8 boolElfReader::Load() { ??returnReadElfHeader() && ?????????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_paddr和p_align成員。

    可以加載的segments能在虛擬地址范圍[p_vaddr…p_vaddr+p_memsz)以列表的形式展現。其中有如下幾個規則:

  • 各個segments的虛擬地址范圍不可重疊;
  • 如果一個segment的p_filesz小于p_memsz,那么兩者之間的額外數據將被初始化為0;
  • segment的虛擬地址范圍的起、始地址不是必須在某一頁的邊界。兩個不同的segments的起、始地址可以在同一頁,在這種情況,該頁繼承后一segment的映射標記(mapping flags)
  • 每一個segment實際加載的地址并非p_vaddr。而是由加載器決定將第一個segment加載到內存中的哪個位置,然后剩下的segments就以第一個segment為參照物,進行加載。比如:
  • 下面是兩個loadable segments的信息:

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

    相當于這兩個segments的虛擬地址范圍分別為:

    1 2 0x30000...0x34000 0x40000...0x48000

    如果加載器決定將第一個segment加載到0xa0000000的話(通過后面的分析會知道,這個加載地址是在加載程序頭部表的時候由系統確定的),那么它們的實際虛擬地址范圍就是:

    1 2 0xa0030000...0xa0034000 0xa0040000...0xa0048000

    換句話說,所有的segments的實際加載開始地址與其vaddr的偏差值是固定的(0xa0030000 – 0x30000 = 0xa0040000 – 0x40000)。

    但是,在實際情況下,segments的地址并不是在每一頁的邊界出開始的。考慮到我們只能在頁面邊界進行內存映射,因此,這就意味著加載地址的偏差bias應當按照如下方法進行計算:

    1 2 3 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的真正內容的開始地址為:

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

    注意:ELF要求如下條件,以滿足mmap正常工作:

    1 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:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 boolElfReader::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); ??returntrue; }
  • 首先讀取elf文件的程序頭部表項數目phdr_num;
  • 然后分別獲取程序頭部表在頁邊界對齊后的起始地址page_min、結束地址page_max和偏移地址page_offset。并根據page_max與page_start計算出程序頭部表占據的頁面大小phdr_size;
  • 再以只讀模式建立一個私有映射,該映射將elf文件中偏移值為page_min,大小為phdr_size的區域映射到內存中。將映射后的內存地址賦給phdr_mmap_,簡單一句話:將程序頭部表映射到內存中,并將內存地址賦值;
  • 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:

    1 2 3 4 5 6 7 8 9 10 11 12 13 /*預備一塊足夠大的虛擬地址范圍,用來加載所有可加載的segments.我們可以通過mmap創建一個帶有PROT_NONE屬性的私有匿名內存映射。PROT_NONE表示頁不可訪問,匿名映射表示映射區不與任何文件關聯(要求fd為-1),私有映射表示對該映射區域的寫入操作會產生一個映射文件的復制,對此區域做的任何修改夠不會寫會原來的文件*/ boolElfReader::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); ??intmmap_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; ??returntrue; }

    這里有一個關鍵函數phdr_table_get_load_siz:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 /*返回ELF文件程序頭部表中所指定的所有可加載segments(這些segments可能是非連續的)的區間大小,如果沒有可加載的segments,就返回0 如果out_min_vaddr 或 out_max_vadd是非空的,它們就會被設置成將被存儲的頁的最小/大地址(如果沒有可加載segments的話,就設為0) */ size_tphdr_table_get_load_size(constElfW(Phdr)* phdr_table, size_tphdr_count, ????????????????????????????????ElfW(Addr)* out_min_vaddr, ????????????????????????????????ElfW(Addr)* out_max_vaddr) { ??ElfW(Addr) min_vaddr = UINTPTR_MAX; ??ElfW(Addr) max_vaddr = 0; ??boolfound_pt_load = false; ??for(size_ti = 0; i < phdr_count; ++i) { ????constElfW(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; ??} ??returnmax_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了:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 boolElfReader::LoadSegments() { ??for(size_ti = 0; i < phdr_num_; ++i) { ????constElfW(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)); ????????returnfalse; ??????} ????} ????/*如果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)); ????????returnfalse; ??????} ????} ??} ??returntrue; }

    此部分功能很簡單:就是將ELF中的可加載segments依次映射到內存中,并進行一些輔助掃尾工作。

    1.1.4 FindPhdr

    返回程序頭部表在內存中地址。這與phdr_table_是不同的,后者是一個臨時的、在so被重定位之前會為釋放的變量:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 boolElfReader::FindPhdr() { ??constElfW(Phdr)* phdr_limit = phdr_table_ + phdr_num_; ??//如果段類型是 PT_PHDR, 那么我們就直接使用該段的地址. ??for(constElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) { ????if(phdr->p_type == PT_PHDR) { ??????returnCheckPhdr(load_bias_ + phdr->p_vaddr); ????} ??} ??//否則,我們就檢查第一個可加載段。如果該段的文件偏移值為0,那么就表示它是以ELF頭開始的,我們就可以通過它來找到程序頭表加載到內存的地址(雖然過程有點繁瑣)。 ??for(constElfW(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; ????????constElfW(Ehdr)* ehdr = reinterpret_cast<constElfW(Ehdr)*>(elf_addr); ????????ElfW(Addr)? offset = ehdr->e_phoff; ????????returnCheckPhdr((ElfW(Addr))ehdr + offset); ??????} ??????break; ????} ??} ??DL_ERR("can't find loaded phdr for \"%s\"", name_); ??returnfalse; }

    要理解這段代碼,我們需要知道段類型PT_PHDR所表示的意義:指定程序頭表在文件及程序內存映像中的位置和大小。此段類型不能在一個文件中多次出現。此外,僅當程序頭表是程序內存映像的一部分時,才可以出現此段。此類型(如果存在)必須位于任何可裝入段的各項的前面。有關詳細信息,請參見程序的解釋程序。

    至此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來標記,其中包含如下結構的數組:

    1 2 3 4 5 6 7 8 9 10 11 12 13 typedefstruct { Elf32_Sword d_tag; union{ Elf32_Word d_val; Elf32_Addr d_ptr; } d_un; } Elf32_Dyn; externElf32_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成員,我們在第2節介紹的load_library函數中發現,si->dynamic被賦值為null,這就說明,在加載階段是不需要此值的,只有在鏈接階段才需要。Android的動態庫的鏈接工作還是由linker完成,主要代碼就是在linker.cpp的soinfo_link_image(find_library_internal方法中調用)中,此函數的代碼相當多,我們來分塊分析:

    首先,我們需要從程序頭部表中獲取dynamic節區信息:

    1 2 3 4 5 6 7 /*in function soinfo_link_image */??? ????/*抽取動態節區*/ ????size_tdynamic_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);

    此函數很簡單:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 /*返回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) */ voidphdr_table_get_dynamic_section(constElfW(Phdr)* phdr_table, size_tphdr_count, ????????????????????????????????????ElfW(Addr) load_bias, ????????????????????????????????????ElfW(Dyn)** dynamic,size_t* dynamic_count, ElfW(Word)* dynamic_flags) { ??constElfW(Phdr)* phdr = phdr_table; ??constElfW(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()進行相應的處理:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 /*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) { ????????caseDT_HASH: ????????????........ ????????????break; ????????caseDT_STRTAB: ????????????si->strtab =reinterpret_cast<constchar*>(base + d->d_un.d_ptr); ????????????break; ????????caseDT_SYMTAB: ????????????si->symtab =reinterpret_cast<ElfW(Sym)*>(base + d->d_un.d_ptr); ????????????break; ????????caseDT_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; ????????caseDT_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__) ????????caseDT_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) ?????????caseDT_RELA: ????????????si->rela =reinterpret_cast<ElfW(Rela)*>(base + d->d_un.d_ptr); ????????????break; ?????????caseDT_RELASZ: ????????????si->rela_count = d->d_un.d_val /sizeof(ElfW(Rela)); ????????????break; ????????caseDT_REL: ????????????DL_ERR("unsupported DT_REL in \"%s\"", si->name); ????????????returnfalse; ????????caseDT_RELSZ: ????????????DL_ERR("unsupported DT_RELSZ in \"%s\"", si->name); ????????????returnfalse; #else ????????caseDT_REL: ????????????si->rel =reinterpret_cast<ElfW(Rel)*>(base + d->d_un.d_ptr); ????????????break; ????????caseDT_RELSZ: ????????????si->rel_count = d->d_un.d_val /sizeof(ElfW(Rel)); ????????????break; ?????????caseDT_RELA: ????????????DL_ERR("unsupported DT_RELA in \"%s\"", si->name); ????????????returnfalse; #endif ????????caseDT_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; ????????caseDT_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; ????????caseDT_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; ????????caseDT_INIT_ARRAYSZ: ????????????si->init_array_count = ((unsigned)d->d_un.d_val) /sizeof(ElfW(Addr)); ????????????break; ????????caseDT_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; ????????caseDT_FINI_ARRAYSZ: ????????????si->fini_array_count = ((unsigned)d->d_un.d_val) /sizeof(ElfW(Addr)); ????????????break; ????????caseDT_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; ????????caseDT_PREINIT_ARRAYSZ: ????????????si->preinit_array_count = ((unsigned)d->d_un.d_val) /sizeof(ElfW(Addr)); ????????????break; ????????caseDT_TEXTREL: #if defined(__LP64__) ????????????DL_ERR("text relocations (DT_TEXTREL) found in 64-bit ELF file \"%s\"", si->name); ????????????returnfalse; #else ????????????si->has_text_relocations =true; ????????????break; #endif ????????caseDT_SYMBOLIC: ????????????si->has_DT_SYMBOLIC =true; ????????????break; ????????caseDT_NEEDED: ????????????++needed_count; ????????????break; ????????caseDT_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__) ????????caseDT_STRSZ: ????????caseDT_SYMENT: ????????caseDT_RELENT: ?????????????break; ????????caseDT_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; ????????caseDT_MIPS_RLD_VERSION: ????????caseDT_MIPS_FLAGS: ????????caseDT_MIPS_BASE_ADDRESS: ????????caseDT_MIPS_UNREFEXTNO: ????????????break; ????????caseDT_MIPS_SYMTABNO: ????????????si->mips_symtabno = d->d_un.d_val; ????????????break; ????????caseDT_MIPS_LOCAL_GOTNO: ????????????si->mips_local_gotno = d->d_un.d_val; ????????????break; ????????caseDT_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數組的遍歷后,就說明我們已經獲取了其中的有用信息了,那么現在就需要根據這些信息進行處理:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 /*in function soinfo_link_image */ ????//再檢測一遍,這種做法總是明智的 ????if(relocating_linker && needed_count != 0) { ????????DL_ERR("linker cannot have DT_NEEDED dependencies on other libraries"); ????????returnfalse; ????} ????if(si->nbucket == 0) { ????????DL_ERR("empty/missing DT_HASH in \"%s\" (built with --hash-style=gnu?)", si->name); ????????returnfalse; ????} ????if(si->strtab == 0) { ????????DL_ERR("empty/missing DT_STRTAB in \"%s\"", si->name); ????????returnfalse; ????} ????if(si->symtab == 0) { ????????DL_ERR("empty/missing DT_SYMTAB in \"%s\"", si->name); ????????returnfalse; ????} ????// 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_tpreload_count = 0; ????????for(size_ti = 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) { ????????????constchar* 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)); ????????????returnfalse; ????????} ????} #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)) { ????????????returnfalse; ????????} ????} ????if(si->rela != NULL) { ????????DEBUG("[ relocating %s ]\n", si->name); ????????if(soinfo_relocate(si, si->rela, si->rela_count, needed)) { ????????????returnfalse; ????????} ????} #else ????if(si->plt_rel != NULL) { ????????DEBUG("[ relocating %s plt ]", si->name); ????????if(soinfo_relocate(si, si->plt_rel, si->plt_rel_count, needed)) { ????????????returnfalse; ????????} ????} ????if(si->rel != NULL) { ????????DEBUG("[ relocating %s ]", si->name); ????????if(soinfo_relocate(si, si->rel, si->rel_count, needed)) { ????????????returnfalse; ????????} ????} #endif #if defined(__mips__) ????if(!mips_relocate_got(si, needed)) { ????????returnfalse; ????} #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)); ????????????returnfalse; ????????} ????} #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)); ????????returnfalse; ????} ????notify_gdb_of_load(si); ????returntrue; }

    0x02 開始執行so文件


    上面的find_library_internal函數中的soinfo_link_image函數執行完后就返回到上層函數find_library中,然后進一步返回到do_dlopen函數:

    1 2 3 4 5 6 7 8 9 10 11 12 13 soinfo* do_dlopen(constchar* name,int flags) { ??if((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) { ????DL_ERR("invalid flags to dlopen: %x", flags); ????returnNULL; ??} ??set_soinfo_pool_protection(PROT_READ | PROT_WRITE); ??soinfo* si = find_library(name); ??if(si != NULL) { ????si->CallConstructors(); ??} ??set_soinfo_pool_protection(PROT_READ); ??returnsi; }

    如果獲取的si不為空,就說明so的加載和鏈接操作正確完成,那么就可以執行so的初始化構造函數了:

    1 2 3 4 5 6 7 voidsoinfo::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)函數即可:

    1 2 3 4 5 6 7 8 9 10 11 12 13 voidsoinfo::CallArray(constchar* array_name UNUSED, linker_function_t* functions,size_t count, bool reverse) { ??........ ??//這里的recerse變量用于指定.init_array中的函數是由前到后執行還是由后到前執行。默認是由前到后 ??intbegin = reverse ? (count - 1) : 0; ??intend = reverse ? -1 : count; ??intstep = reverse ? -1 : 1; ??for(inti = 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庫文件就可以找到如下數據:

    這里共三個函數指針,每個指針指向一個函數地址。值得注意的是,上圖中每個函數指針的值都加了1,這是因為地址的最后1位置1表明需要使得處理器由ARM轉為Thumb狀態來處理Thumb指令。將目標地址處的代碼解釋為Thumb代碼來執行。

    然后再來看CallFunction的具體實現:

    1 2 3 4 5 6 7 8 9 10 11 voidsoinfo::CallFunction(constchar* 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機制就分析完畢了!

    總結

    以上是生活随笔為你收集整理的Android Linker学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    五月香视频在线观看 | www.夜色321.com | 国产99久久久国产精品成人免费 | 成人精品福利 | 91完整版在线观看 | 国产色在线观看 | 久久久久97国产 | 国产视频精品免费播放 | 五月黄色 | 国产精品久久久久久一区二区 | 成人99免费视频 | 日韩专区在线 | 国产91影院 | 国偷自产中文字幕亚洲手机在线 | 天天射天 | 免费下载高清毛片 | 国产中文字幕91 | 精品视频免费久久久看 | 欧美一区二区三区四区夜夜大片 | 欧美在线a视频 | 视频在线精品 | 丝袜制服综合网 | 五月天堂网| 99色99| 国产精品久久久久一区二区三区共 | 黄色日本片| 999国产在线 | 五月天狠狠操 | 99热在| 日韩av电影免费观看 | 色网站在线免费 | 久久久久高清毛片一级 | 久久亚洲婷婷 | 婷婷福利影院 | 久久九九免费 | 精品久久久久久久久久久久 | 99久久精品国产亚洲 | 国产日韩视频在线 | 黄色网址a | 免费福利在线视频 | 日日爱网址 | 日日操操 | 国内精品久久久精品电影院 | 日韩午夜在线播放 | 日韩欧美在线播放 | 欧美成年人在线观看 | 一级α片| 成人免费视频免费观看 | 91成人精品在线 | 国产精品久久久久久久久久久杏吧 | 久久国产美女视频 | 亚洲国产精品视频 | 国产一级在线视频 | 91黄色在线视频 | 免费91在线| 日本在线观看一区二区 | 国产第页 | 久久久久久久久久久成人 | 天天射天天操天天干 | 亚洲五月六月 | 久久精品二区 | 久久综合给合久久狠狠色 | 日产中文字幕 | 日日碰狠狠添天天爽超碰97久久 | 操操操综合 | 91香蕉视频黄色 | 91av在线电影| 在线黄色国产电影 | 亚洲精品视频二区 | 黄色免费网 | 国产精品1区2区在线观看 | 欧美午夜精品久久久久 | 免费看黄色大全 | 97电影手机 | 深夜免费福利 | 免费精品视频在线观看 | 亚洲日日日 | 91精品婷婷国产综合久久蝌蚪 | 日本精品一区二区 | 国产亚洲视频在线免费观看 | 国语精品久久 | 欧美日韩精品免费观看视频 | 亚洲精品美女在线观看播放 | 一级黄色片在线免费看 | 国产亚洲一区二区三区 | 国产精品一区久久久久 | 精品a级片 | 性色va| 视频在线91 | 成年人免费看片网站 | 在线观看的av网站 | 中文字幕一区2区3区 | 操操日日 | 久久久影院一区二区三区 | 国产糖心vlog在线观看 | 在线v片免费观看视频 | 国产精品久久久久永久免费看 | 91完整版在线观看 | 亚洲经典视频 | 免费av黄色 | 午夜精品一区二区三区视频免费看 | 精品国产乱码一区二 | 97视频在线观看视频免费视频 | 麻豆精品在线 | 黄网站污| 蜜臀久久99精品久久久久久网站 | 激情视频综合网 | 日本激情视频中文字幕 | 欧美激情综合五月色丁香小说 | 精品国产乱码久久久久久三级人 | 国产精品久久久777 成人手机在线视频 | 中文字幕人成不卡一区 | 午夜精品一区二区三区免费视频 | 亚洲精品美女久久久久网站 | 国产视频在线观看免费 | 中文字幕二区在线观看 | 日韩美女黄色片 | 亚洲影音先锋 | 日韩电影在线观看中文字幕 | 在线观看国产高清视频 | 一级欧美黄 | 国产999精品视频 | av在线之家电影网站 | 亚洲国产成人久久 | 天天操天天艹 | 久久久久久久久久久久久9999 | 亚洲国产剧情 | 日批在线看 | 日本中文字幕网址 | 亚洲国产精品99久久久久久久久 | 亚洲精品午夜久久久 | 999超碰| 日韩一区在线播放 | 免费看片网址 | 五月婷社区 | 日韩精品电影在线播放 | 亚洲黄色小说网址 | x99av成人免费| 国内精品久久久久久久影视简单 | 国产九九九视频 | 五月天婷婷视频 | 国产剧情亚洲 | 亚洲欧美视频在线播放 | 亚洲视频资源在线 | 波多野结衣电影一区 | 国产精品精品国产婷婷这里av | 欧美日韩国产精品一区二区 | 在线看av的网址 | 成人免费在线看片 | 国产中文字幕一区二区 | 最近中文字幕视频完整版 | 免费看黄在线网站 | 91丨九色丨蝌蚪丨老版 | 一区二区成人国产精品 | 久久99久久99精品中文字幕 | 欧美五月婷婷 | 天天曰 | 国内精品久久久精品电影院 | 国产a国产| 91完整版| 国产 一区二区三区 在线 | 国产福利av | 久久久亚洲麻豆日韩精品一区三区 | 午夜精品久久久久久久久久久久 | 亚洲日本色 | 日日干 天天干 | 国产成人黄色 | 欧美午夜精品久久久久久浪潮 | 日韩三级视频在线观看 | 国产91精品看黄网站在线观看动漫 | 久久天天躁狠狠躁亚洲综合公司 | 亚洲永久精品在线 | 欧美日韩国产高清视频 | 天天综合网 天天综合色 | 91精品久久香蕉国产线看观看 | 韩国三级一区 | 日本视频高清 | 天天在线操 | 日韩一区二区在线免费观看 | 亚洲麻豆精品 | 91九色视频| 操操操人人 | 国产黄av | 久久免费精彩视频 | 色综合网在线 | 国产福利精品在线观看 | 亚洲精品一区二区三区四区高清 | 欧美视频在线观看免费网址 | 在线视频观看成人 | 五月婷婷丁香 | 国产三级视频在线 | 亚洲综合欧美日韩狠狠色 | 99久久这里有精品 | 国内久久精品视频 | 中文字幕在线观看你懂的 | 午夜精选视频 | 在线天堂日本 | 久久国产成人午夜av影院潦草 | 91九色视频观看 | 992tv在线 | 国产一区黄色 | 精品国产一区二区三区久久影院 | 日韩在线免费播放 | 国产福利免费看 | 天天干天天做天天爱 | 国产一级免费观看 | 99久久精品国产欧美主题曲 | 色久av| 91麻豆操 | 久久成视频 | 91av在线播放视频 | 久久99久久精品国产 | 欧美另类亚洲 | 久久日韩精品 | 一级α片 | 中文字幕一区二区三区在线观看 | 婷婷婷国产在线视频 | 国产 亚洲 欧美 在线 | 国产免费黄视频在线观看 | 99精品免费久久久久久日本 | 黄色录像av | 99热99热| 亚洲专区中文字幕 | 日韩av男人的天堂 | 欧洲精品一区二区 | 免费在线一区二区 | 色在线国产 | 中文久久精品 | freejavvideo日本免费 | 日本不卡123区 | 欧美日韩99 | 日韩一区二区三区免费视频 | 麻豆视频国产精品 | 一区 二区 精品 | 不卡中文字幕av | 国产淫片免费看 | 4hu视频 | 中文字幕免费播放 | 综合婷婷| 一区二区精品在线 | 亚洲精品久久激情国产片 | 亚洲精品在线一区二区三区 | 色香com.| 麻豆国产视频下载 | 视频在线观看入口黄最新永久免费国产 | 精品不卡视频 | 91中文在线| 亚洲成人精品在线观看 | 精品久久国产精品 | 日韩欧美第二页 | 欧美专区国产专区 | 国产 色| 最近免费中文字幕mv在线视频3 | 超级碰碰碰免费视频 | 国产午夜不卡 | 91精品第一页 | 国产裸体视频网站 | 美女黄视频免费 | 视频一区二区三区视频 | 久久99精品国产麻豆婷婷 | 伊人狠狠 | 久草青青在线观看 | a级国产毛片 | 中文字幕在线不卡国产视频 | www色,com | 女人魂免费观看 | 久久久九色精品国产一区二区三区 | 玖玖玖影院 | 久久综合国产伦精品免费 | 欧美精品在线观看免费 | 97精品国产97久久久久久久久久久久 | 久久综合操| 99在线观看精品 | 日产乱码一二三区别免费 | 天堂入口网站 | 欧美三级高清 | 人人草人 | 午夜精品一区二区三区可下载 | 福利一区视频 | 麻豆视频免费入口 | 激情喷水| 69久久久| 特级黄色片免费看 | 亚洲国产成人在线播放 | 成人黄色大片在线免费观看 | 精品免费 | 久久不射电影院 | 精品在线观看一区二区三区 | 天天操夜夜逼 | 精品美女在线观看 | 在线最新av| 日韩精品视频免费在线观看 | 五月婷婷久久综合 | 久久婷婷一区 | 国产黄在线免费观看 | 国产精品成人免费 | 色吧久久 | 久草免费在线观看 | 欧美-第1页-屁屁影院 | 成人免费一区二区三区在线观看 | 成人小视频在线 | 日韩一级片大全 | 黄色在线视频网址 | 一级黄色免费 | 久久久国产精品视频 | 九九色网| 精品中文字幕在线 | 亚洲精品女 | 香蕉影院在线播放 | 亚洲精品久久久久中文字幕二区 | 天天看天天干天天操 | 美女福利视频一区二区 | 成人免费在线观看av | 中文在线资源 | 最新av在线播放 | 日韩免费av网址 | 色是在线视频 | 日批视频在线播放 | 成人在线观看你懂的 | 欧美色久| 美女免费视频一区 | 日韩在线播放欧美字幕 | 久久激情五月激情 | 国产福利a| 国产精品视频地址 | 六月丁香婷婷网 | 日韩免费电影网站 | 日本精品免费看 | 国产一区二区久久 | 婷婷丁香导航 | 欧美另类重口 | 天天射狠狠干 | 色网站在线看 | 91在线视频免费 | 国产精品不卡在线播放 | 国产精品久久久久久99 | 美女视频黄免费网站 | 91精品人成在线观看 | 欧美激情第一页xxx 午夜性福利 | 96av在线视频 | 久久久免费av | 国产精品美女网站 | 99re视频在线观看 | 97狠狠干| 91精品视频在线播放 | 97电影院在线观看 | 久久这里精品视频 | 国产成人久久精品77777 | 国产精品激情偷乱一区二区∴ | 免费av观看 | 亚洲欧美日韩不卡 | 国内精品久久久久久久久久久久 | 在线观看日本高清mv视频 | 在线观看视频97 | 中文字幕在线播放日韩 | 国产麻豆精品在线观看 | 中文字幕乱视频 | 免费观看av| 99精品久久精品一区二区 | 免费午夜av | 亚洲日韩精品欧美一区二区 | 国产成人不卡 | 欧美人人 | 日韩区在线观看 | 奇米影音四色 | 亚洲乱亚洲乱亚洲 | 四虎国产精 | 青青河边草免费观看 | 国产黄影院色大全免费 | 一色av| 九九免费观看视频 | 国产精品久久久久久久久久久久久久 | 少妇激情久久 | av久久在线 | 免费h精品视频在线播放 | 成人毛片久久 | 在线观看中文字幕dvd播放 | 天天干天天射天天插 | 国产黄色一级片 | 成人黄在线 | 婷婷在线免费观看 | 天天摸日日操 | 中文在线中文资源 | 日本三级在线观看中文字 | 91成人午夜 | 婷婷亚洲五月色综合 | 成人精品电影 | 看毛片的网址 | 中文字幕在线乱 | 日批视频在线 | 成人三级网站在线观看 | 国产 在线 高清 精品 | 在线亚洲高清视频 | www天天干com | 97在线精品国自产拍中文 | 欧美极品少妇xxxxⅹ欧美极品少妇xxxx亚洲精品 | 麻豆影视在线播放 | 亚洲精选久久 | 狠狠色丁香久久婷婷综合丁香 | 精品国模一区二区 | av免费电影在线观看 | 91大神精品视频在线观看 | 精品国产一区二区三区久久影院 | 国产99久久久国产精品成人免费 | 久久免费视频在线观看 | www.福利视频 | 三级免费黄 | 国产中文字幕在线观看 | 亚洲精品五月天 | 国产一区二区三区在线 | 国产精品久久亚洲 | 玖玖色在线观看 | 免费美女久久99 | a在线播放 | 成人全视频免费观看在线看 | 中文字幕久久精品 | 青青久视频 | 夜夜骑日日 | 日本中文字幕免费观看 | 天天综合网久久 | 美女精品久久 | 国产精品久久久久久久久久三级 | 日韩久久久久久久久久 | 国产精品毛片一区二区在线看 | 丝袜av网站 | 日韩手机视频 | 欧美天堂视频在线 | 日韩在线理论 | 99久久婷婷国产精品综合 | 9在线观看免费 | 中文免费在线观看 | 国产五月 | 国产视频资源在线观看 | 亚洲精品视频一二三 | 日本中文乱码卡一卡二新区 | 久草在线高清视频 | 在线视频1卡二卡三卡 | 射久久 | 亚洲精品九九 | 日韩电影中文字幕在线 | 国产又粗又猛又黄又爽的视频 | 国产精品av在线免费观看 | 成人黄色片在线播放 | 丁香婷婷色月天 | 香蕉视频网站在线观看 | 伊人资源视频在线 | 久色 网 | 视频成人免费 | 美女黄久久 | 久久66热这里只有精品 | 日韩在线首页 | 国产成人福利在线 | 国产a网站| 日韩伦理一区二区三区av在线 | 在线成人看片 | 狠狠狠狠干 | 国产精品9999久久久久仙踪林 | 青青啪| 国产精品成 | 亚洲美女久久 | 国产在线黄色 | www五月天com| 精品在线免费视频 | 免费亚洲片| 国产成人精品一二三区 | 99999精品视频 | 最近中文字幕免费大全 | 午夜99| 天天在线免费视频 | 亚洲免费在线播放视频 | 久久黄色a级片 | 521色香蕉网站在线观看 | 丁香5月婷婷 | www.亚洲精品 | 天天射网 | av大全免费在线观看 | 最新日韩中文字幕 | 天天操天天干天天操天天干 | av手机在线播放 | 探花视频网站 | 午夜电影中文字幕 | 欧美在线观看视频一区二区三区 | 久久久久9999亚洲精品 | 午夜12点 | 日韩黄色av网站 | 日韩午夜一级片 | 国产精品igao视频网网址 | 亚洲激情影院 | 国产一区二区精品久久91 | 97超碰.com | 99热在线免费观看 | 色婷婷www| 日日夜夜精品免费观看 | 插久久 | 四虎影视精品 | 国产精品久久久久久久久大全 | 91亚洲激情 | 九九热在线视频 | 久久久91精品国产一区二区三区 | 亚洲成av | 国产日韩精品在线观看 | av中文字幕在线免费观看 | 麻豆视频免费在线播放 | 激情网五月婷婷 | 久久久久欠精品国产毛片国产毛生 | 天干啦夜天干天干在线线 | 国产高清 不卡 | 超碰人人国产 | www.成人精品 | 91视频在线看 | 国产精品成人一区二区 | 免费毛片一区二区三区久久久 | 亚洲精品国产拍在线 | 欧美日韩一区久久 | 亚洲五月六月 | 伊人网av | 国产日韩精品一区二区三区 | 国产美腿白丝袜足在线av | 97精品国产91久久久久久久 | 国产精品久久久久久久久久直播 | 激情综合婷婷 | 国产精品黄色在线观看 | 欧美a级成人淫片免费看 | 国产精品h在线观看 | 中文字幕av在线不卡 | 亚洲日本在线视频观看 | 国产在线观看二区 | 狠狠色香婷婷久久亚洲精品 | 亚洲精品www久久久 www国产精品com | 国产一级电影网 | 亚洲免费精品视频 | 久久久久人人 | 成人一级免费电影 | 免费看v片网站 | www黄色软件 | 日韩国产在线观看 | 日韩在线视频不卡 | 国产福利一区二区在线 | 色综合久久久久综合 | 97精品视频在线播放 | 一区二区久久 | 丁香五月缴情综合网 | 国产精品欧美久久久久天天影视 | 欧美日韩精品在线播放 | 久草爱 | 国产中文字幕三区 | 蜜桃视频日韩 | 日韩精品在线观看视频 | 2019中文字幕网站 | 国产精品久久久99 | 国内精品视频在线 | 国产精品免费久久久久影院仙踪林 | 81国产精品久久久久久久久久 | 成人午夜电影在线观看 | 国产999精品久久久久久绿帽 | 免费黄色网址网站 | 国产成人久久精品一区二区三区 | 精品色综合| 国产午夜三级一区二区三 | 日韩综合一区二区 | 久久国产亚洲视频 | 亚洲激情在线观看 | 天天射天天做 | 欧美人牲| 色88久久 | 超碰人人草| 极品国产91在线网站 | 午夜久久久影院 | 日韩一区二区三区免费视频 | 色婷婷色 | 青春草视频| 韩国av一区二区三区在线观看 | 性色在线视频 | 欧美激情另类文学 | 超碰国产人人 | 精品特级毛片 | 久久精品国产免费 | 在线a人v观看视频 | 免费黄a| 亚洲国产精品成人精品 | 亚洲欧美在线视频免费 | 91在线精品观看 | 日韩视频一区二区 | 国产成人精品午夜在线播放 | 国产高清在线免费视频 | 亚洲成人中文在线 | 欧美日韩在线第一页 | 亚洲欧美视频一区二区三区 | 精品亚洲免费视频 | 国产精品成久久久久三级 | 国产专区在线视频 | 色人久久 | 欧美成年性 | 婷婷四房综合激情五月 | 极品中文字幕 | 久综合网 | 九九热.com | 色偷偷88888欧美精品久久久 | 免费视频网 | 欧美视频国产视频 | 黄色看片 | 99热播精品 | 成年人免费观看国产 | www亚洲精品 | 精品国产一区二区久久 | 午夜色性片 | 91精品一区国产高清在线gif | 久久99热精品 | 日韩av免费一区 | 狠狠操操| 亚洲激精日韩激精欧美精品 | 日韩视频精品在线 | 国产老妇av | 日韩av片无码一区二区不卡电影 | 天天色天天爱天天射综合 | 黄色av播放| 手机成人av| 依人成人综合网 | 在线免费色视频 | 亚洲全部视频 | 国产精品久久久久婷婷 | 国产在线理论片 | 国产中文字幕第一页 | 国产精品一区二区三区免费看 | 五月婷婷中文网 | 五月天婷亚洲天综合网鲁鲁鲁 | 国产色拍拍拍拍在线精品 | 韩国av一区二区三区在线观看 | 色资源网免费观看视频 | 国产尤物在线观看 | 久久99精品久久久久久 | 国产亚洲日 | 亚洲精品国产精品99久久 | 日韩大陆欧美高清视频区 | 99久久婷婷国产综合亚洲 | 久久久久久久久久久免费视频 | 精品视频www | 美女视频黄是免费的 | 国产高清黄色 | 欧美精品免费一区二区 | 亚洲精品国产精品国自产观看浪潮 | 黄色软件网站在线观看 | 国产色在线视频 | 精品伦理一区二区三区 | 中文字幕在线字幕中文 | 国产一区 在线播放 | 日韩女同一区二区三区在线观看 | 三级av网 | 国产精品综合久久久 | 欧美综合久久 | 久久亚洲免费 | 国产黄色一级大片 | 在线视频日韩 | 超碰在线人人 | 在线观看 国产 | 99热在线免费观看 | 韩日av一区二区 | 91免费看片黄 | 日韩一区二区三区免费视频 | 欧美日本三级 | 久久人人爽爽 | 天天婷婷 | 国际精品久久久 | 在线欧美国产 | 91视频麻豆| 国产不卡在线观看 | 97精品国产97久久久久久粉红 | 亚洲精品黄色在线观看 | wwxxxx日本 | 日日操日日插 | 日韩精品视频在线观看网址 | 国产精品3 | 亚洲美女视频在线 | 日本性视频 | 亚洲成 人精品 | 欧美乱码精品一区二区 | 91成人免费观看视频 | 最新国产精品视频 | av三级在线免费观看 | 麻豆久久一区二区 | 免费高清无人区完整版 | 四虎影视精品永久在线观看 | 日韩视频在线一区 | 亚洲一区日韩在线 | 最近日本中文字幕 | 精品国产区 | 在线免费av网| 蜜桃麻豆www久久囤产精品 | 丁香六月网 | 欧美 激情 国产 91 在线 | 国产成人av综合色 | 亚洲免费国产视频 | 日韩首页 | 欧美日韩一区久久 | 啪啪免费视频网站 | 草久久久久 | 91日韩在线视频 | 日韩日韩日韩日韩 | 精品一区二区在线免费观看 | 成人免费xxxxxx视频 | 不卡视频在线看 | 在线天堂中文在线资源网 | 97超碰在线播放 | 天天射天天拍 | 91人人澡人人爽 | 色全色在线资源网 | 五月天激情电影 | 国产一级不卡毛片 | 深爱五月网 | 日韩精品视频在线观看免费 | 五月天色网站 | 国产美腿白丝袜足在线av | 国内久久精品 | 久久精品婷婷 | 久久99深爱久久99精品 | 欧美视频www | 免费在线观看毛片网站 | 日韩在线观看第一页 | 欧美天天综合网 | 欧美视频国产视频 | 久草视频国产 | 日韩美精品视频 | 99久久99 | 中文字幕一区二区三区乱码不卡 | 亚洲精品婷婷 | 久久久精品国产免费观看一区二区 | 天天摸夜夜操 | 久草在线免费资源 | 亚洲精品乱码久久久久久蜜桃91 | 欧美午夜理伦三级在线观看 | 久久久久国产精品一区 | 黄色免费国产 | 久久另类小说 | 又爽又黄又刺激的视频 | 欧美极品一区二区三区 | 高清av中文在线字幕观看1 | 精品国产一区二区三区男人吃奶 | 国产婷婷一区二区 | 亚洲婷婷综合色高清在线 | 2020天天干夜夜爽 | 欧美韩日在线 | 国产黄色精品在线观看 | 粉嫩av一区二区三区入口 | 久久艹99| 激情xxxx| 一区二区不卡高清 | 国产亚洲精品久久久久久电影 | 97视频在线免费 | 在线观看午夜 | 夜夜爽www | av字幕在线 | 久久乱码卡一卡2卡三卡四 五月婷婷久 | 国产一线二线三线性视频 | 精品二区视频 | 亚洲区另类春色综合小说校园片 | 西西4444www大胆艺术 | 国产成人精品一区二区三区在线 | 有码中文字幕 | 国产精品一区二区av麻豆 | 亚洲成av片人久久久 | 久久久久久久久久久久影院 | av在线网站观看 | 中文字幕在线视频国产 | 五月天激情在线 | 丁香六月婷婷综合 | 国产精品久久久久久久久久不蜜月 | 精品视频久久 | 狠狠干婷婷色 | av中文天堂在线 | 婷婷网址| 亚洲女同ⅹxx女同tv | 草久久av | 久艹视频在线观看 | 久久久www成人免费毛片麻豆 | 色九色 | 国产一区二区视频在线 | 亚洲欧洲精品一区二区精品久久久 | 亚洲资源 | 五月丁香 | 美女网站视频久久 | av线上免费看 | 久久 国产一区 | 丝袜av网站 | 精品九九九 | 夜夜操夜夜干 | 成人综合日日夜夜 | 亚洲黄色成人 | 欧美激情xxxx性bbbb | 国产黄色特级片 | 黄色小说视频网站 | 成年人免费av网站 | 福利一区视频 | 国产免费又爽又刺激在线观看 | 亚洲午夜精品久久久 | 欧美精品一区二区蜜臀亚洲 | 黄网站污 | 91精品一区国产高清在线gif | 在线看黄色的网站 | 天天操狠狠干 | 亚洲精品乱码久久久久久9色 | 日韩电影中文 | 中文字幕4| 午夜黄网 | 欧美人交a欧美精品 | 日本精品小视频 | 久久精品一二三区 | 狠狠干在线 | 久草青青在线观看 | 中文字幕一区二区三区在线视频 | www中文在线| 国产又粗又长的视频 | www,黄视频| 国产一区二区在线视频观看 | 国产白浆视频 | 国产九色在线播放九色 | 综合色站 | 欧美日韩亚洲精品在线 | 久久你懂得| 一区二区精品 | 5月丁香婷婷综合 | 青青网视频 | 久草爱| 亚洲国产精品va在线看黑人 | 亚洲免费国产视频 | 久久精品一区二区三区视频 | 日韩在线欧美在线 | 韩日在线一区 | 亚洲成人av在线 | 久久综合色一综合色88 | 一区二区三区久久精品 | 色综合天天综合网国产成人网 | 精品欧美小视频在线观看 | 国产精品网址在线观看 | 婷婷丁香六月 | 国产在线观看91 | 伊人亚洲精品 | 国产人成看黄久久久久久久久 | 久久国际影院 | 日韩免费av片 | 色婷婷激婷婷情综天天 | 色婷婷狠| 天天爽天天爽天天爽 | av在线com| 国产一区二区三区四区大秀 | 网站在线观看你们懂的 | 免费在线激情电影 | 亚洲日韩精品欧美一区二区 | 日韩av黄 | 婷婷网站天天婷婷网站 | 久草av在线播放 | 国产一区二区在线影院 | 在线中文字幕观看 | 91成人免费看 | 99久久精品国产网站 | 区一区二区三区中文字幕 | 国产精品福利在线播放 | 黄色毛片一级片 | 狠狠色丁香久久婷婷综 | 黄网站a | 蜜桃av久久久亚洲精品 | 欧美一区在线观看视频 | 欧美日韩电影在线播放 | 最近中文字幕免费 | 成人一区电影 | 欧美一二在线 | 日韩欧美电影在线观看 | 日韩免费小视频 | 亚洲一级片免费观看 | 99精彩视频在线观看免费 | 欧美日韩在线播放 | 97在线成人 | 久久久久9999亚洲精品 | 中文在线字幕免费观 | 人人人爽 | 操操操日日日 | 免费a级观看 | 国产九九在线 | 国产专区精品视频 | 国产成人黄色片 | 超碰在线资源 | 亚洲人成人99网站 | 韩国av免费在线观看 | 九九热在线精品 | 狠狠干美女 | www.97色.com | 国产一区二区精 | 在线播放日韩av | 99免费在线视频 | 午夜精品中文字幕 | 一区二区视 | 亚洲无吗天堂 | 91成人在线视频观看 | 成年人在线观看网站 | 成人av中文字幕在线观看 | 亚洲人xxx | 视频99爱 | 2023年中文无字幕文字 | 婷婷激情5月天 | 国产精品成人免费 | 欧美激情一区不卡 | 天天天操天天天干 | 国产欧美精品一区aⅴ影院 99视频国产精品免费观看 | 91日韩在线视频 | 国产三级视频 | 婷婷久久一区二区三区 | 丁香色婷| 欧美日韩视频在线 | 日韩精品在线免费观看 | 国产一区二区三区久久久 | 国产很黄很色的视频 | 一区二区精品视频 | 麻豆传媒视频在线免费观看 | 免费看国产a | 免费看片在线观看 | 天天操天天谢 | 中文国产成人精品久久一 | 日日干天天爽 | 午夜精品久久久久99热app | 久久精品国产免费看久久精品 | 中文字幕在线观看不卡 | 欧洲精品在线视频 | 亚洲精品中文字幕在线观看 | 九九视频精品在线 | 久草国产在线观看 | 精品久久久免费视频 | 久久理论视频 | 国产91对白在线播 | 337p日本欧洲亚洲大胆裸体艺术 | 亚洲最新视频在线 | 97在线免费观看 | 在线黄色观看 | 日免费视频 | 黄色三几片| 午夜在线观看一区 | 亚洲自拍偷拍色图 | 日韩中文字幕免费看 | 亚洲一级黄色av | av中文在线 | 欧美精品一二三 | 国产aa精品| 在线国产精品一区 | 日韩电影中文字幕在线观看 | 三级视频片 | 91网页版免费观看 | 国产在线观看91 | 在线观看中文字幕亚洲 | 在线免费精品视频 | 久久五月天婷婷 | 久久久久久久国产精品视频 | www国产亚洲精品久久麻豆 | 在线欧美小视频 | 国产精品毛片一区视频播不卡 | 麻花豆传媒mv在线观看 | 成人久久18免费网站 | 国产精品成人在线观看 | 久久好看免费视频 | 最近2019好看的中文字幕免费 | 成人免费xxx在线观看 | 综合久久久 | 亚洲黄色片一级 | bbbb操bbbb| 一区二区欧美日韩 | 天天干天天做天天爱 | 中文字幕免费播放 | 天天综合视频在线观看 | 在线观看91久久久久久 | 国产99区 | 日韩欧美一区二区在线 | 91看片麻豆 | 国产热re99久久6国产精品 | 久久五月婷婷综合 | 日韩欧美一区二区在线观看 | 97免费中文视频在线观看 | 免费高清男女打扑克视频 | 免费看搞黄视频网站 | 精品久久1 | 97精品国产97久久久久久免费 | 日韩精品第1页 | 人人舔人人射 | 在线观看免费成人av | 波多野结衣在线观看一区 | 久久久综合 | 日日夜夜狠狠 | 四虎成人av | 国产精品1区2区3区 久久免费视频7 | 最新99热| 久久伊人五月天 | 丁香婷婷综合激情五月色 | 日韩在线视频精品 | 99视频在线精品国自产拍免费观看 | 国产专区在线视频 | 久久免费99精品久久久久久 | 久久久www免费电影网 | 色综合天天视频在线观看 | 亚洲精品免费观看视频 | 久久久久久久久久久久av | 99国产免费网址 | 探花视频免费观看高清视频 |