精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

如何實(shí)現(xiàn)一個(gè)iOS AOP框架?

開發(fā) 開發(fā)工具
Aspect使用了OC的消息轉(zhuǎn)發(fā)流程,有一定的性能消耗。本文作者使用C++設(shè)計(jì)語言,并使用libffi進(jìn)行核心trampoline函數(shù)的設(shè)計(jì),實(shí)現(xiàn)了一個(gè)iOS AOP框架——Lokie。相比于業(yè)內(nèi)熟知的Aspects,性能上有了明顯的提升。本文將分享Lokie的具體實(shí)現(xiàn)思路。

Aspect使用了OC的消息轉(zhuǎn)發(fā)流程,有一定的性能消耗。本文作者使用C++設(shè)計(jì)語言,并使用libffi進(jìn)行核心trampoline函數(shù)的設(shè)計(jì),實(shí)現(xiàn)了一個(gè)iOS AOP框架——Lokie。相比于業(yè)內(nèi)熟知的Aspects,性能上有了明顯的提升。本文將分享Lokie的具體實(shí)現(xiàn)思路。

前言

不自覺的想起自己從業(yè)的這十幾年,如白駒過隙?,F(xiàn)在談到上還熟悉的的語言以ASM/C/C++/OC/JS/Lua/Ruby/Shell等為主,其他的基本上都是用時(shí)拈來過時(shí)忘,語言這種東西變化是在太快了, 不過大體換湯不換藥,我感覺近幾年來所有的語言隱隱都有一種大統(tǒng)一的走勢(shì),一旦有個(gè)特性不錯(cuò),你會(huì)在不同的語言中都找到這種技術(shù)的影子。所以我對(duì)使用哪種語言并不是很執(zhí)著,不過C/C++是信仰罷了 : )

Lokie

工作中大部分用OC和Ruby、Shell之類的東西,前段時(shí)間一直想找一款合適的iOS下能用的AOP框架。iOS業(yè)內(nèi)比較被熟知的應(yīng)該就是Aspect了。但是Aspect性能比較差,Aspect的trampoline函數(shù)借助了OC語言的消息轉(zhuǎn)發(fā)流程,函數(shù)調(diào)用使用了NSInvocation,我們知道,這兩樣都是性能大戶。有一份測(cè)試數(shù)據(jù),基本上NSInvocation的調(diào)用效率是普通消息發(fā)送效率的100倍左右。事實(shí)上,Aspect只能適用于每秒中調(diào)用次數(shù)不超過1000次的場(chǎng)景。當(dāng)然還有一些其他的庫,雖然性能有所提升,但不支持多線程場(chǎng)景,一旦加鎖,性能又有明顯的損耗。

找來找去也沒有什么趁手的庫,于是想了想,自己寫一個(gè)吧。于是Lokie便誕生了。

Lokie的設(shè)計(jì)基本原則只有兩條,第一高效,第二線程安全。為了滿足高效這一設(shè)計(jì)原則,Lokie一方面采用了高效的C++設(shè)計(jì)語言,標(biāo)準(zhǔn)使用C++14。C++14因引入了一些非常棒的特性比如MOV語義,完美轉(zhuǎn)發(fā),右值引用,多線程支持等使得與C++98相比,性能有了顯著的提升。另一方面我們拋棄了對(duì)OC消息轉(zhuǎn)發(fā)和NSInvocation的依賴,使用libffi進(jìn)行核心trampoline函數(shù)的設(shè)計(jì),從而直接從設(shè)計(jì)上就砍倒性能大戶。此外,對(duì)于線程鎖的實(shí)現(xiàn)也使用了輕量的CAS無鎖同步的技術(shù),對(duì)于線程同步開銷也降低了不少。

通過一些真機(jī)的性能數(shù)據(jù)來看,以iPhone 7P為例, Aspect百萬次調(diào)用消耗為6s左右,而相同場(chǎng)景Lokie開銷僅有0.35s左右, 從測(cè)試數(shù)據(jù)上來看,性能提升還是非常顯著的。

我是個(gè)急性子,看書的時(shí)候也是喜歡先看代碼。所以我先帖lokie的開源地址:

??https://github.com/alibaba/Lokie??

喜歡翻代碼的同學(xué)可以先去看看。

Lokie的頭文件非常簡單, 如下所示只有兩個(gè)方法和一個(gè)LokieHookPolicy的枚舉。

typedef enum : NSUInteger { 
LokieHookPolicyBefore = 1 << 0,
LokieHookPolicyAfter = 1 << 1,
LokieHookPolicyReplace = 1 << 2,
} LokieHookPolicy;

@interface NSObject (Lokie)
+ (BOOL) Lokie_hookMemberSelector:(NSString *) selecctor_name
withBlock: (id) block
policy:(LokieHookPolicy) policy;

+ (BOOL) Lokie_hookClassSelector:(NSString *) selecctor_name
withBlock: (id) block
policy:(LokieHookPolicy) policy;

-(NSArray*) lokie_errors;
@end

這兩個(gè)方法的參數(shù)是一樣的,提供了對(duì)類方法和成員方法的切片化支持。

  • selecctor_name:是你感興趣的selector名稱,通常我們可以通過NSStringFromSelector 這個(gè)API來獲取。
  • block:是要具體執(zhí)行的命令,block的參數(shù)和返回值我們稍后討論。
  • policy:指定了想要在該selector執(zhí)行前,執(zhí)行后執(zhí)行block,或者是干脆覆蓋原方法。

監(jiān)控效果

拿一個(gè)場(chǎng)景來看看Lokie的威力。比如我們想監(jiān)控所有的頁面生命周期,是否正常。

比如項(xiàng)目中的 VC 基類叫 BasePageController,designated initializer 是 @selector(initWithConfig)。

我們暫時(shí)把這段測(cè)試代碼放在application: didFinishLaunchingWithOptions中,AOP就是這么任性!這樣我們?cè)赼pp初始化的時(shí)候?qū)λ械腂asePageController對(duì)象生命周期的開始和結(jié)束點(diǎn)進(jìn)行了監(jiān)控,是不是很酷?

Class cls = NSClassFromString(@"BasePageController"); 
[cls Lokie_hookMemberSelector:@"initWithConfig:"
withBlock:^(id target, NSDictionary *param){
NSLog(@"%@", param);
NSLog(@"Lokie: %@ is created", target);
} policy:LokieHookPolicyAfter];

[cls Lokie_hookMemberSelector:@"dealloc" withBlock:^(id target){
NSLog(@"Lokie: %@ is dealloc", target);
} policy:LokieHookPolicyBefore];

block的參數(shù)定義非常有意思, 第一個(gè)參數(shù)是永恒的id target,這個(gè)selector被發(fā)送的對(duì)象,剩下的參數(shù)和selector保持一致。比如 "initWithConfig:" 有一個(gè)參數(shù),類型是NSDNSDictionary *, 所以我們對(duì) initWithConfig: 傳遞的是^(id target, NSDictionary *param),而dealloc是沒有參數(shù)的,所以block變成了^(id target)。換句話說,在block回調(diào)當(dāng)中,你可以拿到當(dāng)前的對(duì)象,以及執(zhí)行這個(gè)方法的參數(shù)上下文,這基本上可以為你提供了足夠的信息。

對(duì)于返回值也很好理解,當(dāng)你使用LokieHookPolicyReplace對(duì)原方法進(jìn)行替換的時(shí)候,block的返回值一定和原方法是一致的。用其他兩個(gè)flag的時(shí)候,無返回值,使用void即可。

另外我們可以對(duì)同一個(gè)方法進(jìn)行多次hook,比如像這個(gè)樣子:

Class cls = NSClassFromString(@"BasePageController"); 
[cls Lokie_hookMemberSelector:@"viewDidAppear:" withBlock:^(id target, BOOL ani){
NSLog(@"LOKIE: viewDidAppear 調(diào)用之前會(huì)執(zhí)行這部分代碼");
}policy:LokieHookPolicyBefore];

[cls Lokie_hookMemberSelector:@"viewDidAppear:" withBlock:^(id target, BOOL ani){
NSLog(@"LOKIE: viewDidAppear 調(diào)用之后會(huì)執(zhí)行這部分代碼");
}policy:LokieHookPolicyAfter];

細(xì)心的你有木有感覺到,如果我們用個(gè)時(shí)間戳記錄前后兩次的時(shí)間,獲取某個(gè)函數(shù)的執(zhí)行時(shí)間就會(huì)非常容易。

前面兩個(gè)簡單的小例子算是拋磚引玉吧, AOP在做監(jiān)控、日志方面來說功能還是非常強(qiáng)大的。

實(shí)現(xiàn)原理

整個(gè)AOP的實(shí)現(xiàn)是基于iOS的runtime機(jī)制以及l(fā)ibffi打造的trampoline函數(shù)為核心的。所以這里我也聊聊iOS runtime的一些東西。這部分對(duì)于很多人來說,可能比較熟悉了。

OC runtime里有幾個(gè)基礎(chǔ)概念:SEL, IMP, Method。

SEL

typedef struct objc_selector  *SEL; 
typedef id (*IMP)(id, SEL, ...);

struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
} ;
typedef struct objc_method *Method;

objc_selector這個(gè)結(jié)構(gòu)體很有意思,我在源碼里面沒有找到他的定義。不過可以通過翻閱代碼來推測(cè)objc_selector的實(shí)現(xiàn)。在objc-sel.m當(dāng)中,有兩個(gè)函數(shù)代碼如下:

const char *sel_getName(SEL sel) { 
if (!sel) return "<null selector>";
return (const char *)(const void*)sel;
}

sel_getName這個(gè)函數(shù)出鏡率還是很高的,從它的實(shí)現(xiàn)來看,sel和const char *是可以直接互轉(zhuǎn)的,第二個(gè)函數(shù)看的則更加清晰:

static SEL __sel_registerName(const char *name, int copy) ; 
//! 在 __sel_registerName 中有通過const char *name 直接得到 SEL 的方法

...
if (!result) {
result = sel_alloc(name, copy);
}
...

//! sel_alloc的實(shí)現(xiàn)
static SEL sel_alloc(const char *name ,bool copy)
{
selLock.assertWriting();
return (SEL)(copy ? strdupIfMutable(name):name);
}

看到這里,我們基本上可以推測(cè)出來objc_selector的定義應(yīng)該是類似與以下這種形式:

typedef struct { 
char selector[XXX];
void *unknown;
...
}objc_selector;

為了提升效率, selecor的查找是通過字符串的哈希值為key的,這樣會(huì)比直接使用字符串做索引查找更加高效。

//!objc4-208  版本的哈希算法 
static CFHashCode _objc_hash_selector(const void *v) {
if (!v) return 0;
return (CFHashCode)_objc_strhash(v);
}

static __inline__ unsigned int _objc_strhash(const unsigned char *s) {
unsigned int hash = 0;
for (;;) {
int a = *s++;
if (0 == a) break;
hash += (hash << 8) + a;
}
return hash;
}
//! objc4-723 版本的hash算法 
static unsigned _mapStrHash(NXMapTable *table, const void *key) {
unsigned hash = 0;
unsigned char *s = (unsigned char *)key;
/* unsigned to avoid a sign-extend */
/* unroll the loop */
if (s) for (; ; ) {
if (*s == '\0') break;
hash ^= *s++;
if (*s == '\0') break;
hash ^= *s++ << 8;
if (*s == '\0') break;
hash ^= *s++ << 16;
if (*s == '\0') break;
hash ^= *s++ << 24;
}
return xorHash(hash);
}

static INLINE unsigned xorHash(unsigned hash) {
unsigned xored = (hash & 0xffff) ^ (hash >> 16);
return ((xored * 65521) + hash);
}

至于為什么會(huì)專門搞出一個(gè)objc_selector, 我想官方應(yīng)該是想強(qiáng)調(diào)SEL和const char 是不同的類型。

IMP

IMP的定義如下所示:

#if !OBJC_OLD_DISPATCH_PROTOTYPES 
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif

LLVM 6.0 后增加了 OBJC_OLD_DISPATCH_PROTOTYPES,需要在 build setting 中將 Enable Strict Checking of objc_msgSend Calls 設(shè)置為NO才可以使用 objc_msgSend(id self, SEL op, ...)。有些同學(xué)在調(diào)用objc_msgSend的時(shí)候,編譯器會(huì)報(bào)如下錯(cuò)誤,就是這個(gè)原因了。

Too many arguments to function call, expected 0, have 2

IMP 是一個(gè)函數(shù)指針,它是最終方法調(diào)用是的執(zhí)行指令入口。

objc_method可以說是非常關(guān)鍵了,它也是OC語言可以在運(yùn)行期進(jìn)行method swizzling 的設(shè)計(jì)基石, 通過objc_method 把函數(shù)地址,函數(shù)簽名以及函數(shù)名稱打包做個(gè)關(guān)聯(lián), 在 真正執(zhí)行類方法的時(shí)候,通過selector名稱,查找對(duì)應(yīng)的IMP。同樣,我們也可以通過在運(yùn)行期替換某個(gè)selector 名稱與之對(duì)應(yīng)的IMP來完成一些特殊的需求。

消息發(fā)送機(jī)制

這三個(gè)概念明確了之后,我們繼續(xù)聊下消息發(fā)送機(jī)制。我們知道當(dāng)向某個(gè)對(duì)象發(fā)送消息的時(shí)候,有一個(gè)關(guān)鍵函數(shù)叫objc_msgSend, 這個(gè)函數(shù)里到底干了些什么事情, 我們簡單聊一聊。

//! objc_msgSend 函數(shù)定義 
id objc_msgSend(id self, SEL op, ...);

這個(gè)函數(shù)內(nèi)部是用匯編寫的,針對(duì)不同的硬件系統(tǒng)提供了相應(yīng)的實(shí)現(xiàn)代碼。不同的版本實(shí)現(xiàn)應(yīng)該是存在差異, 包括函數(shù)名稱和實(shí)現(xiàn)(我查閱的版本是 objc4-208)。

objc_msgSend首先第一件事就是檢測(cè)消息發(fā)送對(duì)象self是否為空,如果為空,直接返回,啥事不做。這也就是為什么對(duì)象為nil時(shí),發(fā)送消息不會(huì)崩潰的原因。做完這些檢測(cè)之后,會(huì)通過self->isa->cache去緩存里查找selector對(duì)應(yīng)的Method, (cache里面存放的是Method ),查找到的話直接調(diào)用Method->method_imp。沒有找到的話進(jìn)入下一個(gè)處理流程,調(diào)用一個(gè)名為class_lookupMethodAndLoadCache的函數(shù)。

這個(gè)函數(shù)的定義如下所示:

IMP _class_lookupMethodAndLoadCache (Class  cls, SEL sel)  
{
...
if (methodPC == NULL)
{
//! 這里指定消息轉(zhuǎn)發(fā)入口
// Class and superclasses do not respond -- use forwarding
smt = malloc_zone_malloc (_objc_create_zone(), sizeof(struct objc_method));
smt->method_name = sel;
smt->method_types = "";
smt->method_imp = &_objc_msgForward;
_cache_fill (cls, smt, sel);
methodPC = &_objc_msgForward;
}

...
}

消息轉(zhuǎn)發(fā)機(jī)制這部分動(dòng)態(tài)方法解析,備援接收者,消息重定向應(yīng)該是很多面試官都喜歡問的環(huán)節(jié) : ) ,我想大家肯定是比較熟悉這部分內(nèi)容,這里就不再贅述了。

trampline函數(shù)的實(shí)現(xiàn)

接下來的內(nèi)容,我們簡單介紹下,從匯編的視角出發(fā),如何實(shí)現(xiàn)一個(gè)trampline函數(shù),完成c函數(shù)級(jí)別的函數(shù)轉(zhuǎn)發(fā)。以x86指令集為例,其他類型原理也相似。

從匯編的角度來看,函數(shù)的跳轉(zhuǎn),最直接的方式就是插入jmp指令。x86指令集中,每條指令都有自己的指令長度,比如說jmp指令, 長度為5,其中包含一個(gè)字節(jié)的指令碼,4個(gè)字節(jié)的相對(duì)偏移量。假定我們手頭有兩個(gè)函數(shù)A和B, 如果想讓B的調(diào)用轉(zhuǎn)發(fā)到A上去, 毫無疑問,jmp指令是可以幫上忙的。接著我們要解決的問題是如何計(jì)算出這兩個(gè)函數(shù)的相對(duì)偏移量。這個(gè)問題我們可以這樣考慮, 但cpu碰到j(luò)mp的時(shí)候,它的執(zhí)行動(dòng)作為ip = ip + 5 + 相對(duì)偏移量。

為了更加直接的解釋這個(gè)問題,我們看看下面的額匯編函數(shù)(不熟悉匯編的同學(xué)不用擔(dān)心, 這個(gè)函數(shù)沒有干任何事情,只是做一個(gè)跳轉(zhuǎn))。

你也可以跟我一起來做,先寫一個(gè)jump_test.s,定義了一個(gè)什么事情都沒做的函數(shù)。

先看看匯編代碼文件:(jump_test.s)翻譯成C函數(shù)的話,就是void jump_test(){ return ; }。

.global _jump_test  
_jump_test:
jmp jlable #!為了測(cè)試jmp指令偏移量,人為的給加幾個(gè)nop
nop
nop
nop
jlable:
rep;ret

接著,我們?cè)趧?chuàng)建一個(gè)C文件:在這個(gè)文件里,我們調(diào)用剛才創(chuàng)建的jump_test函數(shù)。

#include <stdio.h> 
extern void jump_test();
int main(){
jump_test();
}

最后就是編譯鏈接了, 我們創(chuàng)建一個(gè)build.sh生成可執(zhí)行文件portal 。

#! /bin/sh 
cc -c -o main.o main.c
as -o jump_test.o jump_test.s
cc -o portal main.c jump_test.o

我們使用 lldb 加載調(diào)試剛才生成的prtal文件,并把斷點(diǎn)打在函數(shù) jump_test 上。

lldb ./portal 
b jump_test
r

在我機(jī)器上,是如下的跳轉(zhuǎn)地址, 你的地址可能和我的不太一樣,不過沒關(guān)系,這并不影響我們的分析。

Process 22830 launched: './portal' (x86_64) 
Process 22830 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100000f9f portal`jump_test
portal`jump_test:
-> 0x100000f9f <+0>: jmp 0x100000fa7 ; jlable
0x100000fa4 <+5>: nop
0x100000fa5 <+6>: nop
0x100000fa6 <+7>: nop

演示到這里的時(shí)候,我們成功的從匯編的視角,看到了一些我們想要的東西。

首先看看當(dāng)前的 ip 是 0x100000f9f, 我們匯編中使用的jlable此時(shí)已經(jīng)被計(jì)算,變成了新的目標(biāo)地址(0x100000fa7)。我們知道,新的 ip 是通過當(dāng)前 ip 加偏移算出來的, jmp的指令長度是5,前面我們已經(jīng)解釋過了。所以我們可以知道下面的關(guān)系:

new_ip = old_ip + 5 + offset;

把從 lldb 中獲取的地址放進(jìn)來,就變成了:

0x100000fa7 = 0x100000f9f + 5 + offset ==> offset = 3.

回頭看看匯編代碼, 我們?cè)诖a中使用了三個(gè)nop, 每個(gè)nop指令為1個(gè)字節(jié), 剛好就是跳轉(zhuǎn)到三個(gè)nop指令之后。做了個(gè)簡單的驗(yàn)證之后,我們把這個(gè)等式做個(gè)變形,于是得到 offset = new_ip - old_ip - 5; 當(dāng)我們知道 A函數(shù)和B函數(shù)之后,就很容易算出jmp的操作數(shù)是多少了。

講到這里,函數(shù)的跳轉(zhuǎn)思路就非常清晰了,我們想在調(diào)用A的時(shí)候,實(shí)際跳轉(zhuǎn)到B。比如我們有個(gè)C api, 我們希望每次調(diào)用這個(gè)api的時(shí)候,實(shí)際上跳轉(zhuǎn)到我們自定義的函數(shù)里面, 我們需要把這個(gè)api的前幾個(gè)字節(jié)修改下,直接jmp到我們自己定義的函數(shù)中。前5個(gè)字節(jié)第一個(gè)當(dāng)然就是jmp的操作碼了,后面四個(gè)字節(jié)是我們計(jì)算出的偏移量。

最后給出一個(gè)完整的例子。匯編分析以及C代碼一并打包放上來。

#include <stdio.h> 
#include <mach/mach.h>

int new_add(int a, int b){
return a+b;
}

int add(int a, int b){
printf("my_add org is called!\n");
return 0;
}

typedef struct{
uint8_t jmp;
uint32_t off;
} __attribute__((packed)) tramp_line_code;

void dohook(void *src, void *dst){
vm_protect(mach_task_self(), (vm_address_t)src, 5, 0, VM_PROT_ALL);
tramp_line_code jshort;
jshort.jmp = 0xe9;
jshort.off = (uint32_t)(long)dst - (uint32_t)(long)src - 0x5;
memcpy(my_add, (const void*)&jshort, sizeof(tramp_line_code));
vm_protect(mach_task_self(), (vm_address_t)src, 5, 0, VM_PROT_READ|VM_PROT_EXECUTE);
}

int main(){
dohook(add, new_add);
int c = add(10, 20); //! 該函數(shù)默認(rèn)實(shí)現(xiàn)是返回 0, hook之后,返回 30
printf("res is %d\n", c);
return 0;
}

編譯腳本(系統(tǒng) macOS):

gcc -o portal ./main.c 
執(zhí)行: ./portal
輸出: res is 30

 

至此, 函數(shù)調(diào)用已經(jīng)被成功轉(zhuǎn)發(fā)了。

【本文為51CTO專欄作者“阿里巴巴官方技術(shù)”原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】

??戳這里,看該作者更多好文??

 

責(zé)任編輯:武曉燕 來源: 51CTO
相關(guān)推薦

2019-12-27 16:00:56

分布式事務(wù)框架Java

2020-11-02 08:19:18

RPC框架Java

2017-12-12 15:24:32

Web Server單線程實(shí)現(xiàn)

2009-10-01 09:19:45

PHP框架ZendFramewoCake

2020-08-07 10:40:56

Node.jsexpress前端

2023-02-26 01:37:57

goORM代碼

2023-03-01 09:39:40

調(diào)度系統(tǒng)

2018-10-11 21:00:18

2010-08-05 14:08:26

Flex框架

2011-07-22 16:29:53

IOS Demo CD

2022-04-01 15:18:42

Web 框架網(wǎng)絡(luò)通信

2024-08-02 09:49:35

Spring流程Tomcat

2016-09-28 17:34:27

JavaScriptvueWeb

2021-01-26 10:33:45

前端開發(fā)技術(shù)

2021-05-27 09:50:03

連接池FTP服務(wù)器

2022-03-24 14:58:02

Java散列表編程語言

2016-09-06 19:45:18

javascriptVue前端

2023-10-30 08:45:55

Spring容器攔截

2011-12-26 16:39:43

局部函數(shù)

2022-03-14 10:02:03

散列表鏈表哈希表
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

日韩 欧美 自拍| 国产区亚洲区欧美区| 男人的天堂影院| 亚洲精品mv| 国产精品国产自产拍高清av| 91九色蝌蚪嫩草| 五月激情六月丁香| 欧美好骚综合网| 日韩色在线观看| 精品一卡二卡三卡| jizz性欧美10| 久久青草国产手机看片福利盒子 | 麻豆91精品91久久久的内涵| 欧美福利在线观看| 夫妇露脸对白88av| 女同另类激情重口| 制服视频三区第一页精品| 女人和拘做爰正片视频| 尤物网在线观看| 99久久久久免费精品国产| 国产欧美精品va在线观看| 可以免费看av的网址| 青青久久av| 欧美一区二区三区系列电影| 国产视频在线视频| 波多野结衣精品| 一区在线播放视频| 日本一区二区在线| 午夜成人免费影院| 国产激情一区二区三区四区| 国产精品久久久久免费a∨| 国产一级性生活| 91精品国产麻豆国产在线观看| 亚洲欧美日韩天堂一区二区| 人妖粗暴刺激videos呻吟| 在线不卡一区| 欧美日韩综合在线免费观看| 日批视频在线免费看| 波多野结衣在线高清| 一区二区欧美视频| 国产一级黄色录像片| 欧美三级理伦电影| 国产精品少妇自拍| 午夜精品美女久久久久av福利| 五月天婷婷视频| 成人高清视频在线观看| 动漫美女被爆操久久久| wwwav网站| 国产成人精品免费| 99九九电视剧免费观看| 国产999久久久| 国产尤物一区二区在线| 91精品久久久久久久久青青| 中文文字幕一区二区三三| 视频一区二区不卡| 国产精品入口福利| 一区二区三区免费在线| 久久精品国产亚洲aⅴ| 91久久久久久久久久久久久| 国产乱码久久久| 国产一区二区电影| caoporn国产精品免费公开| 精品国产亚洲av麻豆| 国产成人免费在线观看| 国产精品日韩高清| 熟妇人妻一区二区三区四区 | 国产欧美日韩视频一区二区三区| www黄色在线观看| 高清久久久久久| 国产精品久久久久久久久久直播| 日韩在线一区二区三区四区| av午夜一区麻豆| 蜜桃视频成人| 最新97超碰在线| 亚洲色图在线看| 青青在线免费观看| 欧美片第一页| 9191国产精品| 色哟哟无码精品一区二区三区| 第四色在线一区二区| 日韩av最新在线观看| 91中文字幕永久在线| 色爱综合网欧美| 欧美日韩xxx| 九九热在线免费观看| 免播放器亚洲一区| 97人人澡人人爽| 深夜福利视频在线免费观看| 中文字幕欧美激情| av日韩在线看| 三级成人黄色影院| 欧美一区二区视频观看视频| 成人在线视频免费播放| 精品美女久久久| 欧美日韩爱爱视频| 精品一区二区无码| 国产91在线|亚洲| 日本在线播放不卡| 日韩精品卡一| 欧美性生活影院| 久久人妻少妇嫩草av无码专区| 欧美理论电影大全| 久久久久久久久久国产精品| 无码人妻丰满熟妇精品| 国产91富婆露脸刺激对白| 欧美日韩电影一区二区| 在线三级中文| 欧美性视频一区二区三区| 国产伦精品一区三区精东| 青青草成人影院| 久久成年人免费电影| 欧美成人精品欧美一级私黄| 日韩精品欧美精品| 国产一区免费观看| 中文字幕中文字幕在线十八区 | 日韩av免费在线| 精品人妻少妇AV无码专区 | 麻豆精品一区二区三区视频| 日韩中文字幕亚洲一区二区va在线| 亚洲综合第一页| sese在线视频| 色综合久久综合网欧美综合网| 国产九九九视频| 水蜜桃久久夜色精品一区| 欧美在线视频网| 天堂在线视频网站| 亚洲一区二区三区精品在线| 艹b视频在线观看| 国产麻豆一区二区三区精品视频| 国内精品久久久| 国产av一区二区三区| 中文字幕乱码日本亚洲一区二区 | 成人免费av在线| 看全色黄大色大片| 大胆国模一区二区三区| 色先锋资源久久综合5566| 天堂网视频在线| 久久这里只精品最新地址| 国产av天堂无码一区二区三区| 日韩欧美中文字幕一区二区三区| 久久久国产一区二区| 亚洲天堂中文在线| 国产精品视频一二| 手机视频在线观看| 欧美a级成人淫片免费看| 国产精品久久99久久| av资源种子在线观看| 91黄色在线观看| 成人黄色免费网址| 日韩电影在线观看一区| 五月天亚洲综合| 国产69精品久久久久9999人| 深夜福利91大全| 一区二区三区www污污污网站| 国产精品久久99| 夜夜爽久久精品91| 欧美色图麻豆| 精品国产一区二区三| 中文在线8资源库| 亚洲欧美综合另类中字| 欧美高清69hd| 国产精品美女久久久久久| 日本中文字幕观看| 欧美日韩国产综合网| 国产精品一区二| 一个人www视频在线免费观看| 亚洲欧美国内爽妇网| 天天射天天干天天| 亚洲欧洲av一区二区三区久久| 小早川怜子一区二区三区| 欧美精品偷拍| 久久久久久九九| 九九热这里有精品| 九九精品视频在线观看| 午夜av免费观看| 欧美日精品一区视频| 久久久久久久久久久网| 26uuu亚洲婷婷狠狠天堂| 午夜两性免费视频| 欧美亚韩一区| 麻豆av一区二区三区久久| 精品美女一区| 97视频在线免费观看| 99视频在线观看地址| 欧美变态口味重另类| 天干夜夜爽爽日日日日| 亚洲少妇30p| 一区二区三区免费在线观看视频| 久久国产三级精品| 老太脱裤子让老头玩xxxxx| 精品国产一区二区三区久久久樱花| 91精品久久久久久久久青青| 欧美另类老肥妇| 久久精品免费电影| 亚洲aaaaaaa| 91精品欧美一区二区三区综合在| 日日夜夜综合网| 亚洲视频一区在线| av直播在线观看| 国产毛片精品一区| 少妇人妻互换不带套| 欧美视频亚洲视频| 一本一道久久a久久精品综合 | 国产一区二区三区香蕉 | 精品成人免费一区二区在线播放| 欧美久久久精品| yw在线观看| 日韩av在线最新| 成人av免费播放| 欧美色图天堂网| 国产免费观看av| 亚洲最大色网站| 国产三级精品三级观看| 91麻豆视频网站| 丰满人妻一区二区三区大胸| 日本不卡免费在线视频| 内射国产内射夫妻免费频道| 中文在线日韩| 一区二区在线观看网站| 国产欧美日韩影院| 久热这里只精品99re8久| 91成人精品在线| 92福利视频午夜1000合集在线观看| 欧美日韩视频网站| 欧洲亚洲免费在线| 99热99re6国产在线播放| 欧美成人三级视频网站| 999在线视频| 在线日韩av观看| 国产主播福利在线| 亚洲欧美一区二区三区四区| 涩涩视频免费看| 亚洲电影免费观看高清| 精品黑人一区二区三区在线观看| 欧美人与禽zozo性伦| 日韩乱码一区二区三区| 91成人在线免费观看| 亚洲 欧美 视频| 婷婷成人综合网| 成人免费区一区二区三区| 污片在线观看一区二区| 国产中文字幕免费| 黄色一区二区在线| 国产午夜性春猛交ⅹxxx| 欧美午夜视频一区二区| 91玉足脚交嫩脚丫在线播放| 欧美性生活大片免费观看网址| 国产污视频在线观看| 性做久久久久久免费观看| 国产午夜福利精品| 亚洲18女电影在线观看| 久久青青草视频| 在线精品国精品国产尤物884a| 免费又黄又爽又猛大片午夜| 欧美在线视频不卡| 亚洲一区中文字幕永久在线| 欧美老人xxxx18| 99久久久久成人国产免费| 日韩欧美国产wwwww| 国精品人妻无码一区二区三区喝尿| 精品嫩草影院久久| 亚洲 小说区 图片区 都市| 一本一本久久a久久精品综合小说| 成人精品一区二区三区校园激情| 中文字幕欧美国内| 成人在线免费看片| 国产综合在线视频| 高清不卡亚洲| 国产精品免费久久久久久| 日韩午夜电影免费看| 福利视频久久| 国产欧美日韩| 99re8这里只有精品| 9久re热视频在线精品| 欧在线一二三四区| 国产一区福利在线| 国产精品第七页| 欧美国产精品专区| 青青草手机视频在线观看| 色综合网站在线| 99久久免费国产精精品| 亚洲精品国产精品自产a区红杏吧 亚洲精品国产精品乱码不99按摩 亚洲精品国产精品久久清纯直播 亚洲精品国产精品国自产在线 | 久久精品国产亚洲AV成人婷婷| 自拍偷拍欧美精品| 亚欧视频在线观看| 欧美日韩精品欧美日韩精品一 | 精品视频在线播放免| 97超碰人人在线| 韩国视频理论视频久久| 国产精品久久久久久妇女| 成人精品一区二区三区| 欧美国产不卡| 欧美 国产 精品| 天堂一区二区在线| 麻豆tv在线观看| 国产视频911| 国产网站在线看| 4438成人网| 精品视频一二区| 久久久久久久久久久国产| 国产福利91精品一区二区| 国产精品我不卡| 99久久亚洲精品| 成人性做爰aaa片免费看不忠| 粉嫩av一区二区三区在线播放 | 精品动漫一区二区三区| 国产又大又黄的视频| 国产性色av一区二区| 91制片在线观看| 亚洲精品免费av| 日韩精品第一区| 成年人视频在线免费| 波多野结衣中文字幕一区| 黄色香蕉视频在线观看| 在线免费不卡电影| 青春草在线观看 | 国产精品国产三级国产专区52| 在线不卡免费av| 在线免费观看的av网站| 26uuu日韩精品一区二区| 综合激情久久| 黄色污污在线观看| 国产一区二区在线免费观看| 亚洲最大成人综合网| 一本到高清视频免费精品| 人妻视频一区二区三区| 欧美高跟鞋交xxxxxhd| 精品视频一区二区三区| 在线精品亚洲一区二区| 青青草97国产精品免费观看无弹窗版| 久久久久久久久免费看无码 | 日本免费观看网站| 国产亚洲福利社区一区| 日日噜噜噜噜人人爽亚洲精品| 精品福利av导航| 国内小视频在线看| 97碰碰视频| 亚洲精品社区| 亚洲の无码国产の无码步美| 亚洲v精品v日韩v欧美v专区| 亚洲欧美另类日韩| 97久久超碰福利国产精品…| 九九热播视频在线精品6| 国产二区视频在线| 99国产精品99久久久久久| 在线观看免费国产视频| 亚洲精品xxxx| 日韩av中字| 色999五月色| 久久国产尿小便嘘嘘| 91香蕉视频在线播放| 91精品国产综合久久精品麻豆| av小次郎在线| 国产精品毛片va一区二区三区| 亚洲高清成人| 爱爱的免费视频| 欧美亚洲动漫精品| 国产激情视频在线| av一区二区三区四区电影| 日韩午夜激情| 国产一二三四五区| 欧美裸体一区二区三区| av观看在线| 激情五月综合色婷婷一区二区| 西西人体一区二区| 天堂网av2018| 日韩欧美在线一区二区三区| 超碰中文在线| 日韩高清av电影| 国产一区二区三区视频在线播放| 妺妺窝人体色www婷婷| 亚洲精品美女久久久| 日韩欧美看国产| 天天操天天干天天玩| 成人国产精品免费网站| 男人的天堂av网站| 久热在线中文字幕色999舞| 国产香蕉一区二区三区| 四虎精品一区二区三区| 亚洲精品亚洲人成人网| 特级毛片www| 最新日韩中文字幕| 69精品国产久热在线观看| 国产aaa一级片| 亚洲人成小说网站色在线| 人妻精品一区二区三区| 国产欧美精品一区二区三区介绍 | 看一级黄色录像| wwwwxxxxx欧美| 国产片高清在线观看| 欧美性在线观看| 欧美在线黄色| 亚洲黄色小说视频| 精品久久久久久久久久久久久久久 | 欧美黄在线观看| 波多野结衣片子| 欧美一级理论性理论a| 欧美日韩电影免费看| 免费在线看黄色片| 中文一区在线播放 |