日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

深入字节码操作:使用ASM和Javassist创建审核日志

發布時間:2025/3/21 java 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入字节码操作:使用ASM和Javassist创建审核日志 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

深入字節碼操作:使用ASM和Javassist創建審核日志

原文鏈接:https://blog.newrelic.com/2014/09/29/diving-bytecode-manipulation-creating-audit-log-asm-javassist/

在堆棧中使用spring和hibernate,您的應用程序的字節碼可能會在運行時被增強或處理。 字節碼是Java虛擬機(JVM)的指令集,所有在JVM上運行的語言都必須最終編譯為字節碼。 操作字節碼原因如下:

  • 程序分析:?
    • 查找應用bug
    • 檢查代碼復雜性
    • 查找特定注解的類
  • 類生成:?
    • 使用代理從數據庫中懶惰加載數據
  • 安全性?
    • 特定API限制訪問權限
    • 代碼混淆
  • 無Java源碼類轉換?
    • 代碼分析
    • 代碼優化
  • 最后,添加日志

有幾種可用于操作字節碼的工具,從非常低級的工具(如需要字節碼級別工作的ASM)到諸如AspectJ等高級框架(允許編寫純Java)。

本博文,我將演示分別使用Javassist和ASM實現一種審計日志的方法。

審計日志例子

假定我沒有如下代碼:

public class BankTransactions {public static void main(String[] args) {BankTransactions bank = new BankTransactions();for (int i = 0; i < 100; i++) {String accountId = "account" + i;bank.login("password", accountId, "Ashley");bank.unimportantProcessing(accountId);bank.withdraw(accountId, Double.valueOf(i));}System.out.println("Transactions completed");} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

我們要記錄重要的操作以及關鍵信息以確定操作。 以上,我將確定登錄退出的重要動作。 對于登錄,重要信息將是帳戶ID和用戶。 對于退出,重要信息將是帳戶ID和撤回的金額。 記錄重要操作的一種方法是將日志記錄語句添加到每個重要的方法,但這將是乏味的。 相反,我們可以為重要的方法添加注釋,然后使用工具來注入日志記錄。 在這種情況下,該工具將是一個字節碼操作框架。

@ImportantLog(fields = { "1", "2" }) public void login(String password, String accountId, String userName) {// login logic } @ImportantLog(fields = { "0", "1" }) public void withdraw(String accountId, Double moneyToRemove) {// transaction logic }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

@ImportantLog注釋表示我們要在每次調用該方法時記錄一條消息,而@ImportantLog注釋中的fields參數表示應記錄的每個參數的索引位置。 例如,對于登錄,我們要記錄第1位和第2位的輸入參數。它們是accountIduserName。 我們不會記錄第0位的密碼參數。

使用字節碼和注釋來執行日志記錄有兩個主要優點:

  • 日志記錄與業務邏輯分離,這有助于保持代碼清潔和簡單。
  • 在不修改源代碼的情況下,輕松刪除審核日志記錄。
  • 在哪里實際修改字節碼?

    我們可以使用1.5中引入的核心Java功能來操縱字節碼。 此功能稱為Java代理。?
    要了解Java代理,讓我們來看一下典型的Java處理流程。

    使用包含我們的main方法的類作為輸入參數執行命令java。 這將啟動Java運行時環境,使用classloader來加載輸入類,并調用該類的main方法。 在我們具體的例子中,調用了BankTransactions的main方法,這將導致一些處理發生,并打印“完成交易”。

    現在來看一下使用Java代理的Java進程。

    命令java運行兩個輸入參數。第一個是JVM參數-javaagent,指向代理jar。第二個是包含我們主要方法的類。javaagent標志告訴JVM首先加載代理。 代理的主類必須在代理jar的清單中指定。 一旦類被加載,類的premain方法被調用。 這個premain方法充當代理的安裝鉤子。 它允許代理注冊一個類變換器。 當類變換器在JVM中注冊時,該變換器將在類加載到JVM前接收每個類的字節。 這為類變換器提供了根據需要修改類的字節的機會。 一旦類變換器修改了字節,它將修改的字節返回給JVM。 這些字節接著由JVM驗證和加載。

    在我們具體的例子中,當BankTransaction加載時,字節將首先進入類變換器進行潛在的修改。修改后的字節將被返回并加載到JVM中。 加載完之后,調用類中的main方法,進行一些處理,并打印“事務完成”。

    讓我們來看看代碼。 下面我有代理的premain方法:

    public class JavassistAgent {public static void premain(String agentArgs, Instrumentation inst) {System.out.println("Starting the agent");inst.addTransformer(new ImportantLogClassTransformer());} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    premain方法打印出一個消息,然后注冊一個類變換器。 類變換器必須實現方法轉換,加載到JVM中的每個類都會調用它。它以該類的字節數組作為方法的輸入,然后返回修改后的字節數組。如果類變換器決定不修改特定類的字節,則可以返回null。

    public class ImportantLogClassTransformer implements ClassFileTransformer {public byte[] transform(ClassLoader loader, String className,Class classBeingRedefined, ProtectionDomain protectionDomain,byte[] classfileBuffer) throws IllegalClassFormatException {// manipulate the bytes herereturn modified bytes;} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    現在我們知道在哪里修改一個類的字節,接著需要知道如何修改字節。

    如何使用Javassist修改字節碼?

    Javassist是一個具有高級和低級API的字節碼操作框架。我將重點關注高級的面向對象的API,首先從Javassist中的對象的解釋開始。接下來,我將實現審核日志應用程序的實際代碼。

    Javassist使用CtClass對象來表示一個類。 這些CtClass對象可以從ClassPool獲得,用于修改Classes。ClassPool是一個基于HashMap實現的CtClass對象容器,其中鍵是類名稱,值是表示該類的CtClass對象。默認的ClassPool使用與底層JVM相同的類路徑。因此,在某些情況下,可能需要向ClassPool添加類路徑或類字節。

    類似于包含字段,方法和構造函數的Java類,CtClass對象包含CtFieldsCtConstructorsCtMethods。所有這些對象都可以修改。我將重點關注方法操作,因為審核日志應用程序需要這種行為。

    以下是修改方法的幾種方法:

    上圖顯示了Javassist的主要優點之一。實際上不必寫字節碼。而是編寫Java代碼。一個復雜的情況是Java代碼必須在引號內。

    現在我們了解了Javassist的基本構建塊,現在來看看應用程序的實際代碼。 類變換器的變換方法需要執行以下步驟:

  • 將字節數組轉換為CtClass對象
  • 檢查CtClass中每個帶注解@ImportantLog的方法
  • 如果方法中存在@ImportantLog注釋,那么:?
    • 獲取方法重要參數索引
    • 函數開始增加日志語句
  • 使用Javassist編寫Java代碼時,請注意以下問題:

    • JVM在包之間使用斜杠,而Javassist使用點。
    • 當插入多行Java代碼時,代碼需要在括號內。
    • 當使用12等引用方法參數值時,知道0this1。
    • 注釋擁有可見和不可見的簽。 不可見的注釋在運行時無法獲取。

    實際的Java代碼如下:

    public class ImportantLogClassTransformer implements ClassFileTransformer {private static final String METHOD_ANNOTATION = "com.example.spring2gx.mains.ImportantLog";private static final String ANNOTATION_ARRAY = "fields";private ClassPool pool;public ImportantLogClassTransformer() {pool = ClassPool.getDefault();}public byte[] transform(ClassLoader loader, String className,Class classBeingRedefined, ProtectionDomain protectionDomain,byte[] classfileBuffer) throws IllegalClassFormatException {try {pool.insertClassPath(new ByteArrayClassPath(className,classfileBuffer));CtClass cclass = pool.get(className.replaceAll("/", "."));if (!cclass.isFrozen()) {for (CtMethod currentMethod : cclass.getDeclaredMethods()) {Annotation annotation = getAnnotation(currentMethod);if (annotation != null) {List parameterIndexes = getParamIndexes(annotation);currentMethod.insertBefore(createJavaString(currentMethod, className, parameterIndexes));}}return cclass.toBytecode();}} catch (Exception e) {e.printStackTrace();}return null;}private Annotation getAnnotation(CtMethod method) {MethodInfo mInfo = method.getMethodInfo();// the attribute we are looking for is a runtime invisible attribute// use Retention(RetentionPolicy.RUNTIME) on the annotation to make it// visible at runtimeAnnotationsAttribute attInfo = (AnnotationsAttribute) mInfo.getAttribute(AnnotationsAttribute.invisibleTag);if (attInfo != null) {// this is the type name meaning use dots instead of slashesreturn attInfo.getAnnotation(METHOD_ANNOTATION);}return null;}private List getParamIndexes(Annotation annotation) {ArrayMemberValue fields = (ArrayMemberValue) annotation.getMemberValue(ANNOTATION_ARRAY);if (fields != null) {MemberValue[] values = (MemberValue[]) fields.getValue();List parameterIndexes = new ArrayList();for (MemberValue val : values) {parameterIndexes.add(((StringMemberValue) val).getValue());}return parameterIndexes;}return Collections.emptyList();}private String createJavaString(CtMethod currentMethod, String className,List indexParameters) {StringBuilder sb = new StringBuilder();sb.append("{StringBuilder sb = new StringBuilder");sb.append("(\"A call was made to method '\");");sb.append("sb.append(\"");sb.append(currentMethod.getName());sb.append("\");sb.append(\"' on class '\");");sb.append("sb.append(\"");sb.append(className);sb.append("\");sb.append(\"'.\");");sb.append("sb.append(\"\\n Important params:\");");for (String index : indexParameters) {try {// add one because 0 is "this" for instance variable// if were a static method 0 would not be anythingint localVar = Integer.parseInt(index) + 1;sb.append("sb.append(\"\\n Index \");");sb.append("sb.append(\"");sb.append(index);sb.append("\");sb.append(\" value: \");");sb.append("sb.append($" + localVar + ");");} catch (NumberFormatException e) {e.printStackTrace();}}sb.append("System.out.println(sb.toString());}");return sb.toString();} }
    • 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
    • 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

    完成了!我們可以運行應用程序,并將日志記錄輸出到“System.out”。

    積極的一面是寫入的代碼量非常小,而且實際上不需要使用Javassist編寫字節碼。 最大的缺點是用引號編寫Java代碼可能會變得乏味。幸運的是,其他一些字節碼操作框架更快。我們來看看其中一個更快的框架。

    如何使用ASM修改字節?

    ASM是一個字節碼操作框架,使用較小的內存占用并且速度相對較快。我認為ASM是字節碼操作的行業標準,即使是Javassist也在使用ASM。ASM提供基于對象和事件的庫,但在這里我將重點介紹基于事件的模型。

    要理解ASM,我將從ASM自己的文檔的一個Java類圖(下圖)開始。它表明Java類由幾個部分組成,包括一個超類,接口,注釋,字段和方法。在ASM基于事件的模型中,所有這些類組件都可以被認為是事件。

    可以在ClassVisitor上找到ASM的類事件。為了“看到”這些事件,必須創建一個classVisitor來覆蓋您想要查看的所需組件。

    除了類訪問者,我們需要一些東西來解析類并生成事件。為此,ASM提供了一個名為ClassReader的對象。reader解析課程并產生事件。類被解析后,需要ClassWriter來消耗事件,將它們轉換成一個類字節數組。在下圖中,我們BankTransactions類的字節傳遞給ClassReader,該字節將字節發送到ClassWriter,該ClassWriter會輸出生成的BankTransaction。當沒有ClassVisitor存在時,輸入BankTransactions字節應基本上匹配其輸出字節。

    public byte[] transform(ClassLoader loader, String className,Class<?> classBeingRedefined, ProtectionDomain protectionDomain,byte[] classfileBuffer) throws IllegalClassFormatException {ClassReader cr = new ClassReader(classfileBuffer);ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);cr.accept(cw, 0);return cw.toByteArray(); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ClassReader得到類的字節,ClassWriter從類讀取器獲取。ClassReader的accept調用解析該類。接下來,我們從ClassWriter訪問生成的字節。

    現在我們想修改BankTransaction字節。首先,我們需要鏈接在ClassVisitor中。 此ClassVisitor將覆蓋諸如visitField或visitMethod之類的方法來接收關于該特定類組件的通知。

    以下是上圖的代碼實現。 類訪問者LogMethodClassVisitor已添加。請注意,可以添加多個類訪問者。

    public byte[] transform(ClassLoader loader, String className,Class<?> classBeingRedefined, ProtectionDomain protectionDomain,byte[] classfileBuffer) throws IllegalClassFormatException {ClassReader cr = new ClassReader(classfileBuffer);ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);ClassVisitor cv = new LogMethodClassVisitor(cw, className);cr.accept(cv, 0);return cw.toByteArray(); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    對于審核日志應用,我們需要檢查類中的每個方法。這意味著ClassVisitor只需要覆蓋’visitMethod’。

    public class LogMethodClassVisitor extends ClassVisitor {private String className;public LogMethodClassVisitor(ClassVisitor cv, String pClassName) {super(Opcodes.ASM5, cv);className = pClassName;}@Overridepublic MethodVisitor visitMethod(int access, String name, String desc,String signature, String[] exceptions) {//put logic in here} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    請注意,visitMethod返回一個MethodVisitor。 就像一個類有多個組件,一個方法也有很多的組件,當解析該方法時,它可以被認為是事件。

    MethodVisitor在方法上提供事件。對于審核日志應用,我們要檢查帶注釋的方法上。基于注釋,我們可能需要修改方法中的實際代碼。為了進行這些修改,我們需要在一個methodVisitor鏈接,如下所示。

    @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature,exceptions);return new PrintMessageMethodVisitor(mv, name, className); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    這個PrintMessageMethodVisitor將需要覆蓋visitAnnotation和visitCode。請注意,visitAnnotation返回一個AnnotationVisitor。就像類和方法具有組件一樣,還有一個注釋的多個組件。AnnotationVisitor允許我們訪問注釋的所有部分。

    下面我簡要介紹了visitAnnotation和visitCode的步驟。

    public class PrintMessageMethodVisitor extends MethodVisitor {@Overridepublic AnnotationVisitor visitAnnotation(String desc, boolean visible) {// 1. check method for annotation @ImportantLog// 2. if annotation present, then get important method param indexes}@Overridepublic void visitCode() {// 3. if annotation present, add logging to beginning of the method} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    當使用ASM編寫Java代碼時,請注意以下問題:

    • 在事件模型中,類或方法的事件將始終以特定順序發生。 例如,帶注解的方法將始終在實際代碼之前訪問。
    • 當使用12等引用方法參數值時,知道0this1。

    實際Java代碼如下:

    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {if ("Lcom/example/spring2gx/mains/ImportantLog;".equals(desc)) {isAnnotationPresent = true;return new AnnotationVisitor(Opcodes.ASM5,super.visitAnnotation(desc, visible)) {public AnnotationVisitor visitArray(String name, Object value) {if ("fields".equals(name)) {return new AnnotationVisitor(Opscodes.ASM5,super.visitArray(name)) { public void visit(String name, Object value) {parameterIndexes.add((String) value);super.visit(name, value);}};} else {return super.visitArray(name);}}};}return super.visitAnnotation(desc, visible); } public void visitCode() {if (isAnnotationPresent) {// create string buildermv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out","Ljava/io/PrintStream;");mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");mv.visitInsn(Opcodes.DUP);// add everything to the string buildermv.visitLdcInsn("A call was made to method \"");mv.visitMethodInsn(Opcodes.INVOKESPECIAL,"java/lang/StringBuilder", "","(Ljava/lang/String;)V", false);mv.visitLdcInsn(methodName);mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder", "append","(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); . . .
    • 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
    • 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

    以上可以看出Javassist和ASM之間的主要區別之一。使用ASM,必須在修改方法時在字節碼級別編寫代碼,這意味著需要很好地了解JVM的工作原理。需要在給定的時刻確切知道堆棧和局部變量的內容。 在字節碼級別的編寫方面,在功能和優化方面提高了門檻,這意味著開發人員需要較長的時間熟悉ASM開發。

    家庭作業

    現在你已經看到如何使用ASM和Javassist的一個場景,我鼓勵你嘗試一個字節碼操作框架。字節碼操作不僅可以讓您更好地了解JVM,而且還有無數的應用程序。一旦開始,你會發現天空的極限。

    from:?http://blog.csdn.net/lihenair/article/details/69948918

    《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

    總結

    以上是生活随笔為你收集整理的深入字节码操作:使用ASM和Javassist创建审核日志的全部內容,希望文章能夠幫你解決所遇到的問題。

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

    主站蜘蛛池模板: 欧美精品不卡 | 久久久久久久黄色片 | 国内精品999 | jizzjizz日本免费视频 | 中文字幕亚洲精品在线 | 久在线观看视频 | 又黄又爽又色视频 | 亚洲欧美另类日韩 | 国产精品乱码一区二区 | 国产a√| 青青操影院 | 视频二区 | 久久中文一区 | 久久久久久久久久综合 | 国产精品白嫩白嫩大学美女 | 久草免费在线视频 | 天天操天天干天天插 | 99久久精品日本一区二区免费 | 国产精品一区麻豆 | 国产乱人视频 | 高潮又黄又刺激 | 91精品久久久久久 | 网站免费在线观看 | 九九热只有精品 | 久久三级网站 | 亚洲精品一区二区三区蜜臀 | 伊人免费 | 成人欧美一区二区三区在线播放 | 8x国产一区二区三区精品推荐 | 内射中出日韩无国产剧情 | 日本高清不卡视频 | 免费视频久久 | 国产精品黄色网 | 2019中文在线观看 | 国产美女啪啪 | 超碰在线小说 | 欧美黄色网络 | 国产乱free国语对白 | a级黄色片免费看 | 亚洲黄色片网站 | 国产视频一区二区在线 | 国产91免费观看 | 白丝美女被草 | 日本中文字幕在线视频 | 成人 黄 色 免费播放 | 国语对白一区二区 | 色哟哟在线观看 | 美女福利在线观看 | 日韩精品成人在线 | 一级α片免费看刺激高潮视频 | 亚洲精品视频91 | 亚洲精品视频免费看 | 久久日本精品字幕区二区 | 蝌蚪自拍网站 | brazzers欧美极品少妇 | 中文字幕在线第一页 | 香蕉视频在线观看www | 色拍拍视频 | 青青草免费看 | 蜜臀av夜夜澡人人爽人人 | 美女被草出水 | 国产91免费看 | 欧美 日韩 国产 在线观看 | 最新黄色网址在线观看 | 日本十大三级艳星 | 一本大道东京热无码aⅴ | 欧美性视频一区二区三区 | 日本一区二区三区成人 | 国产热视频 | 日韩国产小视频 | 欧美综合在线视频 | 丰满人妻熟妇乱偷人无码 | 可以在线观看的av网站 | 亚洲精品一区三区三区在线观看 | 高清国产午夜精品久久久久久 | 婷婷六月激情 | 影音先锋中文字幕第一页 | 99re在线 | 成人影视免费观看 | 亚洲a视频在线 | 美女乱淫| 91网站在线观看视频 | 乱淫的女高中暑假调教h | 生活片av | 日韩欧美视频免费在线观看 | 国产乱码久久久久 | 久久久综合精品 | www插插插无码免费视频网站 | 国产乱子伦精品无码专区 | 经典三级av在线 | 欧美变态网站 | 亚洲女人18毛片水真多 | 男男啪啪网站 | 亚洲天堂激情 | www.97超碰| 久久久久久69 | 国产日韩欧美一区二区东京热 | 韩国无码一区二区三区精品 | 日本jizzjizz |