亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

JVM源碼分析之javaagent原理完全解讀

2019-11-14 15:18:10
字體:
來源:轉載
供稿:網友

概述

本文重點講述javaagent的具體實現,因為它面向的是我們Java程序員,而且agent都是用Java編寫的,不需要太多的C/C++編程基礎,不過這篇文章里也會講到JVMTIAgent(C實現的),因為javaagent的運行還是依賴于一個特殊的JVMTIAgent。

對于javaagent,或許大家都聽過,甚至使用過,常見的用法大致如下:

java -javaagent:myagent.jar=mode=test Test

我們通過-javaagent來指定我們編寫的agent的jar路徑(./myagent.jar),以及要傳給agent的參數(mode=test),在啟動的時候這個agent就可以做一些我們希望的事了。

 

javaagent的主要功能如下:

  • 可以在加載class文件之前做攔截,對字節碼做修改
  • 可以在運行期對已加載類的字節碼做變更,但是這種情況下會有很多的限制,后面會詳細說
  • 還有其他一些小眾的功能
    • 獲取所有已經加載過的類
    • 獲取所有已經初始化過的類(執行過clinit方法,是上面的一個子集)
    • 獲取某個對象的大小
    • 將某個jar加入到bootstrap classpath里作為高優先級被bootstrapClassloader加載
    • 將某個jar加入到classpath里供AppClassloard去加載
    • 設置某些native方法的前綴,主要在查找native方法的時候做規則匹配

想象一下可以讓程序按照我們預期的邏輯去執行,聽起來是不是挺酷的。

JVMTI

JVMTI全稱JVM Tool Interface,是JVM暴露出來的一些供用戶擴展的接口集合。JVMTI是基于事件驅動的,JVM每執行到一定的邏輯就會調用一些事件的回調接口(如果有的話),這些接口可以供開發者擴展自己的邏輯。

 

比如最常見的,我們想在某個類的字節碼文件讀取之后、類定義之前修改相關的字節碼,從而使創建的class對象是我們修改之后的字節碼內容,那就可以實現一個回調函數賦給jvmtiEnv(JVMTI的運行時,通常一個JVMTIAgent對應一個jvmtiEnv,但是也可以對應多個)的回調方法集合里的ClassFileLoadHook,這樣在接下來的類文件加載過程中都會調用到這個函數中,大致實現如下:,

    jvmtiEventCallbacks callbacks;    jvmtiEnv *          jvmtienv = jvmti(agent);    jvmtiError          jvmtierror;    memset(&callbacks, 0, sizeof(callbacks));    callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;    jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv,                                                 &callbacks,                                                 sizeof(callbacks));

JVMTIAgent

JVMTIAgent其實就是一個動態庫,利用JVMTI暴露出來的一些接口來干一些我們想做、但是正常情況下又做不到的事情,不過為了和普通的動態庫進行區分,它一般會實現如下的一個或者多個函數:

JNIEXPORT jint JNICALLAgent_OnLoad(JavaVM *vm, char *options, void *reserved);JNIEXPORT jint JNICALLAgent_OnAttach(JavaVM* vm, char* options, void* reserved);JNIEXPORT void JNICALLAgent_OnUnload(JavaVM *vm); 
  • Agent_OnLoad函數,如果agent是在啟動時加載的,也就是在vm參數里通過-agentlib來指定的,那在啟動過程中就會去執行這個agent里的Agent_OnLoad函數。
  • Agent_OnAttach函數,如果agent不是在啟動時加載的,而是我們先attach到目標進程上,然后給對應的目標進程發送load命令來加載,則在加載過程中會調用Agent_OnAttach函數。
  • Agent_OnUnload函數,在agent卸載時調用,不過貌似基本上很少實現它。

其實我們每天都在和JVMTIAgent打交道,只是你可能沒有意識到而已,比如我們經常使用Eclipse等工具調試Java代碼,其實就是利用JRE自帶的jdwp agent實現的,只是Eclipse等工具在沒讓你察覺的情況下將相關參數(類似-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:61349)自動加到程序啟動參數列表里了,其中agentlib參數就用來跟要加載的agent的名字,比如這里的jdwp(不過這不是動態庫的名字,JVM會做一些名稱上的擴展,比如在linux下會去找libjdwp.so的動態庫進行加載,也就是在名字的基礎上加前綴lib,再加后綴.so),接下來會跟一堆相關的參數,將這些參數傳給Agent_OnLoad或者Agent_OnAttach函數里對應的options

javaagent

說到javaagent,必須要講的是一個叫做instrument的JVMTIAgent(Linux下對應的動態庫是libinstrument.so),因為javaagent功能就是它來實現的,另外instrument agent還有個別名叫JPLISAgent(Java PRogramming Language Instrumentation Services Agent),這個名字也完全體現了其最本質的功能:就是專門為Java語言編寫的插樁服務提供支持的。

instrument agent

instrument agent實現了Agent_OnLoadAgent_OnAttach兩方法,也就是說在使用時,agent既可以在啟動時加載,也可以在運行時動態加載。其中啟動時加載還可以通過類似-javaagent:myagent.jar的方式來間接加載instrument agent,運行時動態加載依賴的是JVM的attach機制(JVM Attach機制實現),通過發送load命令來加載agent。

instrument agent的核心數據結構如下:

struct _JPLISAgent {    JavaVM *                mJVM;                   /* handle to the JVM */    JPLISEnvironment        mNormalEnvironment;     /* for every thing but retransform stuff */    JPLISEnvironment        mRetransformEnvironment;/* for retransform stuff only */    jobject                 mInstrumentationImpl;   /* handle to the Instrumentation instance */    jmethodID               mPremainCaller;         /* method on the InstrumentationImpl that does the premain stuff (cached to save lots of lookups) */    jmethodID               mAgentmainCaller;       /* method on the InstrumentationImpl for agents loaded via attach mechanism */    jmethodID               mTransform;             /* method on the InstrumentationImpl that does the class file transform */    jboolean                mRedefineAvailable;     /* cached answer to "does this agent support redefine" */    jboolean                mRedefineAdded;         /* indicates if can_redefine_classes capability has been added */    jboolean                mNativeMethodPrefixAvailable; /* cached answer to "does this agent support prefixing" */    jboolean                mNativeMethodPrefixAdded;     /* indicates if can_set_native_method_prefix capability has been added */    char const *            mAgentClassName;        /* agent class name */    char const *            mOptionsString;         /* -javaagent options string */};struct _JPLISEnvironment {    jvmtiEnv *              mJVMTIEnv;              /* the JVM TI environment */    JPLISAgent *            mAgent;                 /* corresponding agent */    jboolean                mIsRetransformer;       /* indicates if special environment */};

這里解釋一下幾個重要項:

  • mNormalEnvironment:主要提供正常的類transform及redefine功能。
  • mRetransformEnvironment:主要提供類retransform功能。
  • mInstrumentationImpl:這個對象非常重要,也是我們Java agent和JVM進行交互的入口,或許寫過javaagent的人在寫`premain`以及`agentmain`方法的時候注意到了有個Instrumentation參數,該參數其實就是這里的對象。
  • mPremainCaller:指向`sun.instrument.InstrumentationImpl.loadClassAndCallPremain`方法,如果agent是在啟動時加載的,則該方法會被調用。
  • mAgentmainCaller:指向`sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain`方法,該方法在通過attach的方式動態加載agent的時候調用。
  • mTransform:指向`sun.instrument.InstrumentationImpl.transform`方法。
  • mAgentClassName:在我們javaagent的MANIFEST.MF里指定的`Agent-Class`。
  • mOptionsString:傳給agent的一些參數。
  • mRedefineAvailable:是否開啟了redefine功能,在javaagent的MANIFEST.MF里設置`Can-Redefine-Classes:true`。
  • mNativeMethodPrefixAvailable:是否支持native方法前綴設置,同樣在javaagent的MANIFEST.MF里設置`Can-Set-Native-Method-Prefix:true`。
  • mIsRetransformer:如果在javaagent的MANIFEST.MF文件里定義了`Can-Retransform-Classes:true`,將會設置mRetransformEnvironment的mIsRetransformer為true。

在啟動時加載instrument agent

正如前面“概述”里提到的方式,就是啟動時加載instrument agent,具體過程都在`InvocationAdapter.c`的`Agent_OnLoad`方法里,這里簡單描述下過程:

  • 創建并初始化JPLISAgent
  • 監聽VMInit事件,在vm初始化完成之后做下面的事情:
    • 創建InstrumentationImpl對象
    • 監聽ClassFileLoadHook事件
    • 調用InstrumentationImpl的`loadClassAndCallPremain`方法,在這個方法里會調用javaagent里MANIFEST.MF里指定的`Premain-Class`類的premain方法
  • 解析javaagent里MANIFEST.MF里的參數,并根據這些參數來設置JPLISAgent里的一些內容

在運行時加載instrument agent

在運行時加載的方式,大致按照下面的方式來操作:

VirtualMachine vm = VirtualMachine.attach(pid); vm.loadAgent(agentPath, agentArgs); 

上面會通過JVM的attach機制來請求目標JVM加載對應的agent,過程大致如下:

  • 創建并初始化JPLISAgent
  • 解析javaagent里MANIFEST.MF里的參數
  • 創建InstrumentationImpl對象
  • 監聽ClassFileLoadHook事件
  • 調用InstrumentationImpl的loadClassAndCallAgentmain方法,在這個方法里會調用javaagent里MANIFEST.MF里指定的Agent-Class類的agentmain方法

instrument agent的ClassFileLoadHook回調實現

不管是啟動時還是運行時加載的instrument agent,都關注著同一個jvmti事件——ClassFileLoadHook,這個事件是在讀取字節碼文件之后回調時用的,這樣可以對原來的字節碼做修改,那這里面究竟是怎樣實現的呢?

void JNICALLeventHandlerClassFileLoadHook(  jvmtiEnv *              jvmtienv,                                JNIEnv *                jnienv,                                jclass                  class_being_redefined,                                jobject                 loader,                                const char*             name,                                jobject                 protectionDomain,                                jint                    class_data_len,                                const unsigned char*    class_data,                                jint*                   new_class_data_len,                                unsigned char**         new_class_data) {    JPLISEnvironment * environment  = NULL;    environment = getJPLISEnvironment(jvmtienv);    /* if something is internally inconsistent (no agent), just silently return without touching the buffer */    if ( environment != NULL ) {        jthrowable outstandingException = preserveThrowable(jnienv);        transformClassFile( environment->mAgent,                            jnienv,                            loader,                            name,                            class_being_redefined,                            protectionDomain,                            class_data_len,                            class_data,                            new_class_data_len,                            new_class_data,                            environment->mIsRetransformer);        restoreThrowable(jnienv, outstandingException);    }}

先根據jvmtiEnv取得對應的JPLISEnvironment,因為上面我已經說到其實有兩個JPLISEnvironment(并且有兩個jvmtiEnv),其中一個是專門做retransform的,而另外一個用來做其他事情,根據不同的用途,在注冊具體的ClassFileTransformer時也是分開的,對于作為retransform用的ClassFileTransformer,我們會注冊到一個單獨的TransformerManager里。

接著調用transformClassFile方法,由于函數實現比較長,這里就不貼代碼了,大致意思就是調用InstrumentationImpl對象的transform方法,根據最后那個參數來決定選哪個TransformerManager里的ClassFileTransformer對象們做transform操作。

private byte[]    transform(  ClassLoader         loader,                String              classname,                Class               classBeingRedefined,                ProtectionDomain    protectionDomain,                byte[]              classfileBuffer,                boolean             isRetransformer) {        TransformerManager mgr = isRetransformer?                                        mRetransfomableTransformerManager :                                        mTransformerManager;        if (mgr == null) {            return null; // no manager, no transform        } else {            return mgr.transform(   loader,                                    classname,                                    classBeingRedefined,                                    protectionDomain,                                    classfileBuffer);        }    }  public byte[]    transform(  ClassLoader         loader,                String              classname,                Class               classBeingRedefined,                ProtectionDomain    protectionDomain,                byte[]              classfileBuffer) {        boolean someoneTouchedTheBytecode = false;        TransformerInfo[]  transformerList = getSnapshotTransformerList();        byte[]  bufferToUse = classfileBuffer;        // order matters, gotta run 'em in the order they were added        for ( int x = 0; x < transformerList.length; x++ ) {            TransformerInfo         transformerInfo = transformerList[x];            ClassFileTransformer    transformer = transformerInfo.transformer();            byte[]                  transformedBytes = null;            try {                transformedBytes = transformer.transform(   loader,                                                            classname,                                                            classBeingRedefined,                                                            protectionDomain,                                                            bufferToUse);            }            catch (Throwable t) {                // don't let any one transformer mess it up for the others.                // This is where we need to put some logging. What should go here? FIXME            }            if ( transformedBytes != null ) {                someoneTouchedTheBytecode = true;                bufferToUse = transformedBytes;            }        }        // if someone modified it, return the modified buffer.        // otherwise return null to mean "no transforms occurred"        byte [] result;        if ( someoneTouchedTheBytecode ) {            result = bufferToUse;        }        else {            result = null;        }        return result;    }   

以上是最終調到的java代碼,可以看到已經調用到我們自己編寫的javaagent代碼里了,我們一般是實現一個ClassFileTransformer類,然后創建一個對象注冊到對應的TransformerManager里。

Class Transform的實現

這里說的class transform其實是狹義的,主要是針對第一次類文件加載時就要求被transform的場景,在加載類文件的時候發出ClassFileLoad事件,然后交給instrumenat agent來調用javaagent里注冊的ClassFileTransformer實現字節碼的修改。

Class Redefine的實現

類重新定義,這是Instrumentation提供的基礎功能之一,主要用在已經被加載過的類上,想對其進行修改,要做這件事,我們必須要知道兩個東西,一個是要修改哪個類,另外一個是想將那個類修改成怎樣的結構,有了這兩個信息之后就可以通過InstrumentationImpl下面的redefineClasses方法操作了:

public void redefineClasses(ClassDefinition[]   definitions) throws  ClassNotFoundException {        if (!isRedefineClassesSupported()) {            throw new UnsupportedOperationException("redefineClasses is not supported in this environment");        }        if (definitions == null) {            throw new NullPointerException("null passed as 'definitions' in redefineClasses");        }        for (int i = 0; i < definitions.length; ++i) {            if (definitions[i] == null) {                throw new NullPointerException("element of 'definitions' is null in redefineClasses");            }        }        if (definitions.length == 0) {            return; // short-circuit if there are no changes requested        }        redefineClasses0(mNativeAgent, definitions);    }

在JVM里對應的實現是創建一個VM_RedefineClassesVM_Operation,注意執行它的時候會stop-the-world:

jvmtiErrorJvmtiEnv::RedefineClasses(jint class_count, const jvmtiClassDefinition* class_definitions) {//TODO: add locking  VM_RedefineClasses op(class_count, class_definitions, jvmti_class_load_kind_redefine);  VMThread::execute(&op);  return (op.check_error());} /* end RedefineClasses */

這個過程我盡量用語言來描述清楚,不詳細貼代碼了,因為代碼量實在有點大:

  • 挨個遍歷要批量重定義的jvmtiClassDefinition
  • 然后讀取新的字節碼,如果有關注ClassFileLoadHook事件的,還會走對應的transform來對新的字節碼再做修改
  • 字節碼解析好,創建一個klassOop對象
  • 對比新老類,并要求如下:
    • 父類是同一個
    • 實現的接口數也要相同,并且是相同的接口
    • 類訪問符必須一致
    • 字段數和字段名要一致
    • 新增的方法必須是private static/final的
    • 可以刪除修改方法
  • 對新類做字節碼校驗
  • 合并新老類的常量池
  • 如果老類上有斷點,那都清除掉
  • 對老類做JIT去優化
  • 對新老方法匹配的方法的jmethodId做更新,將老的jmethodId更新到新的method上
  • 新類的常量池的holer指向老的類
  • 將新類和老類的一些屬性做交換,比如常量池,methods,內部類
  • 初始化新的vtable和itable
  • 交換annotation的method、field、paramenter
  • 遍歷所有當前類的子類,修改他們的vtable及itable

上面是基本的過程,總的來說就是只更新了類里的內容,相當于只更新了指針指向的內容,并沒有更新指針,避免了遍歷大量已有類對象對它們進行更新所帶來的開銷。

Class Retransform的實現

retransform class可以簡單理解為回滾操作,具體回滾到哪個版本,這個需要看情況而定,下面不管那種情況都有一個前提,那就是javaagent已經要求要有retransform的能力了:

  • 如果類是在第一次加載的的時候就做了transform,那么做retransform的時候會將代碼回滾到transform之后的代碼
  • 如果類是在第一次加載的的時候沒有任何變化,那么做retransform的時候會將代碼回滾到最原始的類文件里的字節碼
  • 如果類已經加載了,期間類可能做過多次redefine(比如被另外一個agent做過),但是接下來加載一個新的agent要求有retransform的能力了,然后對類做redefine的動作,那么retransform的時候會將代碼回滾到上一個agent最后一次做redefine后的字節碼

我們從InstrumentationImpl的retransformClasses方法參數看猜到應該是做回滾操作,因為我們只指定了class:

    public void retransformClasses(Class<?>[] classes) {        if (!isRetransformClassesSupported()) {            throw new UnsupportedOperationException( "retransformClasses is not supported in this environment");        }        retransformClasses0(mNativeAgent, classes);    }

不過retransform的實現其實也是通過redefine的功能來實現,在類加載的時候有比較小的差別,主要體現在究竟會走哪些transform上,如果當前是做retransform的話,那將忽略那些注冊到正常的TransformerManager里的ClassFileTransformer,而只會走專門為retransform而準備的TransformerManager的ClassFileTransformer,不然想象一下字節碼又被無聲無息改成某個中間態了。

private:  void post_all_envs() {    if (_load_kind != jvmti_class_load_kind_retransform) {      // for class load and redefine,      // call the non-retransformable agents      JvmtiEnvIterator it;      for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {        if (!env->is_retransformable() && env->is_enabled(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {          // non-retransformable agents cannot retransform back,          // so no need to cache the original class file bytes          post_to_env(env, false);        }      }    }    JvmtiEnvIterator it;    for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {      // retransformable agents get all events      if (env->is_retransformable() && env->is_enabled(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {        // retransformable agents need to cache the original class file        // bytes if changes are made via the ClassFileLoadHook        post_to_env(env, true);      }    }  }

javaagent的其他小眾功能

javaagent除了做字節碼上面的修改之外,其實還有一些小功能,有時候還是挺有用的

  • 獲取所有已經被加載的類:Class[] getAllLoadedClasses(); 
  • 獲取所有已經初始化了的類: Class[] getInitiatedClasses(ClassLoader loader); 
  • 獲取某個對象的大?。?nbsp;long getObjectSize(Object objectToSize); 
  • 將某個jar加入到bootstrap classpath里優先其他jar被加載: void appendToBootstrapClassLoaderSearch(JarFile jarfile); 
  • 將某個jar加入到classpath里供appclassloard去加載:void appendToSystemClassLoaderSearch(JarFile jarfile); 
  • 設置某些native方法的前綴,主要在找native方法的時候做規則匹配: void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix)。
  • 全能程序員交流QQ群290551701,群內程序員都是來自,百度、阿里、京東、小米、去哪兒、餓了嗎、藍港等高級程序員 ,擁有豐富的經驗。加入我們,直線溝通技術大牛,最佳的學習環境,了解業內的一手的資訊。如果你想結實大牛,那 就加入進來,讓大牛帶你超神!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美网站在线观看| 欧美一区二区三区免费观看| 亚洲人成网站777色婷婷| 一区二区三区在线播放欧美| 亚洲视频在线视频| 欧美日韩国产丝袜美女| 高清欧美一区二区三区| 成人精品视频在线| 欧美在线欧美在线| 久久国产精品久久国产精品| 久久全国免费视频| 日韩精品欧美激情| 日韩视频免费在线观看| 欧美国产在线电影| 一色桃子一区二区| 国产精品久久久久久久久久小说| 俺也去精品视频在线观看| 精品亚洲夜色av98在线观看| 日韩有码片在线观看| 成人在线中文字幕| 成人在线一区二区| 欧美巨大黑人极品精男| 亚洲福利视频免费观看| 91夜夜揉人人捏人人添红杏| 国产精品久久久久不卡| 成人黄色av网站| 法国裸体一区二区| 欧美午夜精品伦理| 狠狠躁18三区二区一区| 国产精品香蕉在线观看| 亚洲国产美女精品久久久久∴| 欧美黑人狂野猛交老妇| 在线成人激情黄色| 亚洲区一区二区| 精品国产一区二区三区久久| 日韩美女在线看| 4438全国成人免费| 日韩在线视频观看正片免费网站| 亚洲一区二区三区乱码aⅴ| 日韩精品中文字幕久久臀| 91色在线视频| 久久久免费观看| 成人精品久久一区二区三区| 精品欧美一区二区三区| 国模视频一区二区| 欧美中文字幕在线播放| 欧美色道久久88综合亚洲精品| 欧美精品激情在线| 色天天综合狠狠色| 国产亚洲精品高潮| 麻豆乱码国产一区二区三区| 国产欧美在线视频| 亚洲激情久久久| 中文字幕无线精品亚洲乱码一区| 最近2019中文字幕第三页视频| 国产精品第七十二页| 中文字幕九色91在线| 欧美日韩国产色视频| 97超碰蝌蚪网人人做人人爽| 日韩激情在线视频| 亚洲人成人99网站| 欧美乱大交xxxxx| 欧美激情日韩图片| 黑人巨大精品欧美一区二区一视频| 38少妇精品导航| 夜夜嗨av色综合久久久综合网| 国产狼人综合免费视频| 欧美情侣性视频| 国产日韩欧美在线看| 欧美日韩中国免费专区在线看| 国产一区二区三区高清在线观看| 国产91网红主播在线观看| 97人人爽人人喊人人模波多| 欧美丰满少妇xxxx| 久久精品国产亚洲| 久久精品视频在线观看| 国产精品久久久精品| 国产成人+综合亚洲+天堂| 成人午夜在线视频一区| 97精品视频在线| 日韩网站在线观看| 日本久久久久亚洲中字幕| 中文字幕一区日韩电影| 国产日韩中文字幕在线| 日韩欧美视频一区二区三区| 亚洲精品99久久久久中文字幕| 亚洲精品影视在线观看| 欧美精品videosex性欧美| 久久天天躁狠狠躁夜夜av| 欧美日韩国产123| 欧美性猛交视频| 一区二区三区日韩在线| 日韩免费看的电影电视剧大全| 精品福利在线看| 91热福利电影| 国产精品h片在线播放| 色综久久综合桃花网| 日韩精品中文字幕在线播放| 亚洲精品视频久久| 国产91精品久久久| 精品一区二区三区三区| 欧美高清一级大片| 欧美理论电影在线观看| 国产精品自产拍在线观| 日韩av综合网| 日本aⅴ大伊香蕉精品视频| 视频在线观看99| 浅井舞香一区二区| 亚洲欧美另类中文字幕| 亚洲国产日韩欧美在线动漫| 久久久女人电视剧免费播放下载| 国产成人avxxxxx在线看| 欧美黑人xxxⅹ高潮交| 日本高清视频一区| 色偷偷偷综合中文字幕;dd| 亚洲欧美另类国产| 亚洲国产欧美一区| 久久久久久国产精品| 亚洲精品国产精品国产自| 91最新在线免费观看| 久久影视电视剧凤归四时歌| 精品视频在线播放色网色视频| 成人乱人伦精品视频在线观看| 神马国产精品影院av| 亚洲美女喷白浆| 国产精品video| 日韩av中文字幕在线免费观看| 国内精品国产三级国产在线专| 久久久久免费视频| 丝袜美腿精品国产二区| 欧美性猛交xxxx乱大交蜜桃| 免费不卡欧美自拍视频| 国产精品久久久91| 国产精品久久中文| 日本精品久久久| 国产日本欧美一区二区三区在线| 免费不卡在线观看av| 97国产精品免费视频| 欧美第一黄网免费网站| 久久精品视频中文字幕| 97国产一区二区精品久久呦| 青青在线视频一区二区三区| 国产在线精品成人一区二区三区| 久久久国产精品视频| 97色在线播放视频| 欧美人与性动交a欧美精品| 亚洲欧美中文日韩v在线观看| 97超级碰碰碰久久久| 日韩欧美一区视频| 中文字幕精品www乱入免费视频| 亚洲三级 欧美三级| 欧美高清理论片| 亚洲香蕉在线观看| 精品亚洲一区二区三区| 91精品国产综合久久香蕉的用户体验| 国产精品永久免费观看| 欧美在线视频a| 成人精品一区二区三区电影免费| 亚洲欧洲日产国码av系列天堂| 欧美人成在线视频| 国产日韩专区在线| 亚洲欧美精品中文字幕在线| 亚洲欧美变态国产另类| 欧美性20hd另类|