【GCC編譯優化系列】這種讓人看不懂的multiple-definition真的有點讓人頭疼

      網友投稿 3386 2022-05-30

      1 寫在前面

      有印象的朋友應該記得我之前寫過一篇 關于GCC編譯報錯及對應解決辦法,在該文的 3.5.3 章節有提到幾種很典型的 multiple-definition 鏈接錯誤,也簡要分析了其出現問題的原因及對應解決方法。

      multiple-definition 在GCC編譯報錯里面,它的報錯本質是 重復定義,可能是函數重復定義,也可能是變量重復定義。

      但今天我要介紹的這個 multiple-definition 跟常規遇到的還不太一樣,否則這個問題就不值得我寫篇文章來做記錄了,詳細請看下文。

      2 問題描述

      事情是這樣的,前幾天一個同事給我報了一個我們SDK的問題,我想著加快復現問題,于是我找了他要他的應用代碼,拿到我的編譯環境環境來編譯復現。

      結果,好巧不巧,拿他代碼一編譯,居然給我報錯了,而且這個報錯把我整不會了!朋友,請看:

      /home/xxx/compiler/riscv64_unkown_elf_gcc10.2.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/10.2.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o):/home/xxx/user_app/user_app.h:76: multiple definition of `mcu_ota_t'; /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o):/home/xxx/user_app/user_app.h:76: first defined here /home/xxx/compiler/riscv64_unkown_elf_gcc10.2.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/10.2.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o):/home/xxx/user_app/user_app.h:70: multiple definition of `notify_state_t'; /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o):/home/xxx/user_app/user_app.h:70: first defined here /home/xxx/compiler/riscv64_unkown_elf_gcc10.2.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/10.2.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o):/home/xxx/user_app/user_app.h:60: multiple definition of `wifi_state_t'; /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o):/home/xxx/user_app/user_app.h:60: first defined here /home/xxx/compiler/riscv64_unkown_elf_gcc10.2.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/10.2.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o):/home/xxx/user_app/user_app.h:15: multiple definition of `frame_num_t'; /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o):/home/xxx/user_app/user_app.h:15: first defined here collect2: error: ld returned 1 exit status

      里面error提示的 multiple definition 異常亮眼,但是又讓人摸不著頭腦,這有點不按常理出牌!

      要知道,他的應用代碼明明都可以release版本的呀,而我的編譯環境肯定也沒有問題,畢竟 sample app 在我這都是可以編譯通過的,所謂我大膽推測問題很有可能出在他們的應用代碼上,而編譯報錯也的確提示是應用代碼的問題。

      3 場景復現

      為了準確描述這個問題,排除其他的排除干擾因素,我把相關C代碼和頭文件捋一下。

      整個應用部分的工程包括2個C代碼和2個頭文件:

      主要處理應用入口的app_entry.c:

      /* app_entry.c */ #include "sdk.h" //SDK的統一頭文件 #include "app_entry.h" //app_entry的頭文件 #include "user_app.h" //user_app的頭文件 /* called by lower SDK */ int app_entry_main(void) { /* some code */ /* call user_app */ user_app_init(); /* some code */ return 0; }

      app_entry對應的頭文件app_entry.h:

      #ifndef __APP_ENTRY_H__ #define __APP_ENTRY_H__ /* external functions */ extern int app_entry_main(void); #endif /* end of __APP_ENTRY_H__ */

      主要處理用戶應用邏輯的user_app.c:

      /* user_app.c */ #include "sdk.h" //SDK的統一頭文件 #include "app_entry.h" //app_entry的頭文件 #include "user_app.h" //user_app的頭文件 /* other functions */ /* called by app_entry */ int user_app_init(void) { /* some code */ return 0; }

      user_app對飲的頭文件user_app.h:

      #ifndef __USER_APP_H__ #define __USER_APP_H__ /* some enum definition */ enum { UART_FRAME_1 = 0x01, UART_FRAME_2, UART_FRAME_3, UART_FRAME_4, UART_FRAME_5, } frame_num_t; enum { WIFI_STATE_1 = 0x01, WIFI_STATE_2, WIFI_STATE_3, WIFI_STATE_4, WIFI_STATE_5. } wifi_state_t; enum { NOTIFY_STATE_1 = 0x01, NOTIFY_STATE_2, NOTIFY_STATE_3, NOTIFY_STATE_4, NOTIFY_STATE_5, } notify_state_t; enum { MCU_OTA_NO_BIN = 0x00, MCU_OTA_DOWNLOAD_OK, MCU_OTA_DOWNLOAD_FAIL, } mcu_ota_t; /* external functions */ extern int user_app_init(void); #endif /* end of __USER_APP_H__ */

      另外,補充說明一下,我們使用的是交叉編譯工具是針對RISCV架構的 riscv64-unknown-gcc。

      簡化之后,應用代碼大概就是如上面所示,就這樣的代碼給報錯了,有點納悶。

      4 深入分析

      4.1 可能性分析

      頭文件被重復包含了?

      我看到這個報錯的第一反應是,難道頭文件被重復包含了?

      比如在某個頭文件中定義了一個變量(假設真有這么寫的),如果它的頭文件沒有按照標準的 ifndef 的那種寫法來寫,那么當這個頭文件被一個C文件直接或間接包含多次的時候,這個定義的變量就會存在多個副本,這個時候就會報 “multiple definition”。

      可是,我仔細檢查過user_app.h的頭部寫法,是正確的,不存在這種問題。

      某個C文件里面存在多個xxx_t的副本?

      這一種也是可能的,比如a.h中定義了一個xxx_t,然后b.h中也定義了同名的xxx_t,這時候某個C文件同時包含了a.h和b.h,那么xxx_t在這個C文件中就有兩個定義。

      這個時候,通過查看預處理后的文件(.i)文件就可以看得出來,是否存在這種情況。

      如何打開生成預編譯后的文件,可以參考 這篇文章的 4.2.2 章節介紹。

      以本案例中的 mcu_ota_t 為例,很顯然,并不存在這種情況,只有一個定義呢。

      xxx@ubuntu:~/user_app$ xxx@ubuntu:~/user_app$ find . -name user_app.i ./out/user_app@xxxevb/modules/home/xxx/user_app/user_app.i xxx@ubuntu:~/user_app$ cat ./out/user_app@xxxevb/modules/home/xxx/user_app/user_app.i | grep -nw mcu_ota_t 5547:}mcu_ota_t; xxx@ubuntu:~/user_app$

      4.2 分析map文件

      既然是 multiple definition,那么我搜搜看!

      給我上 grep大法,不搜不知道,一搜嚇一跳。以 mcu_ota_t 為例:

      xxx@ubuntu:~/user_app$ grep -rsnw mcu_ota_t user_app.h:77:}mcu_ota_t; out/user_app@xxxevb/modules/home/xxx/user_app/user_app.i:5547:}mcu_ota_t; out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:2488: .globl mcu_ota_t out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:2554: .section .sbss.mcu_ota_t,"aw",@nobits out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:2555: .type mcu_ota_t, @object out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:2556: .size mcu_ota_t, 1 out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:2557:mcu_ota_t: out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:3361: .4byte mcu_ota_t out/user_app@xxxevb/modules/home/xxx/user_app/user_app.s:9661: .string "mcu_ota_t" Binary file out/user_app@xxxevb/modules/home/xxx/user_app/user_app.o matches Binary file out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.o matches out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.i:3807:}mcu_ota_t; out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:87: .globl mcu_ota_t out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:94: .section .sbss.mcu_ota_t,"aw",@nobits out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:95: .type mcu_ota_t, @object out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:96: .size mcu_ota_t, 1 out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:97:mcu_ota_t: out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:574: .4byte mcu_ota_t out/user_app@xxxevb/modules/home/xxx/user_app/app_entry.s:1136: .string "mcu_ota_t" out/user_app@xxxevb/binary/user_app@xxxevb.map:1811: .sbss.mcu_ota_t out/user_app@xxxevb/binary/user_app@xxxevb.map:1879: .sbss.mcu_ota_t out/user_app@xxxevb/binary/user_app@xxxevb.map:47777:mcu_ota_t /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o) Binary file out/user_app@xxxevb/libraries/user_app.a matches Binary file out/user_app@xxxevb/libraries/user_app.stripped.a matches

      map文件清晰地顯示,在BSS段中有個object叫 mcu_ota_t,要知道在BSS段中出現,這玩意就是global的東西了。

      這什么意思?

      意思就是編譯器已經把mcu_ota_t當做一個 全局變量 了。

      那么我們來梳理一下,當user_app.h里面定義了一個 mcu_ota_t 的全局變量,這個user_app.h同時被app_entry.c和user_app.c包含,自然在這兩個C文件中,都有這個mcu_ota_t全局變量的副本存在;那么根據 【經驗總結】一文帶你了解C代碼到底是如何被編譯的 提及的,在鏈接階段,編譯器就會去查找并鏈接它們,這個時候多個同名全局變量,肯定是不允許的,自然而然,就報了 “multiple definition” 錯誤。

      4.3 扒一扒基礎語法

      為此,我特意去查了一下C語言教科書,找了一些關于C語言的枚舉定義的介紹,再學習了一下。

      果然user_app.h中的那幾個 xxx_t 枚舉并不是一種規范寫法,倒不是說不可以這么寫,只是這樣寫之后容易造成干擾,嚴重的情況下還會導致語法錯誤。

      相關學習資料,可以看 參考鏈接 附錄里面的文章。

      4.4 GCC的版本差異

      那么問題來了,為何同事的編譯環境沒報錯,而我的編譯環境報錯了呢?

      原來,前段時間我們的SDK因一個廠商私有庫比較新,特意升級了GCC的版本,由 riscv64_unkown_elf_gcc8.3.0 升級到了 riscv64_unkown_elf_gcc10.2.0,而應用那邊還沒來得及升級這個版本。所以才造成了這樣的沖突。

      至于為何兩個版本有差異呢?后面我也會提到,其實8.3.0版本對這種寫法是有報 警告 的,而10.2.0版本是報 錯誤 的;在我們的編譯環境中,除編譯器版本不一樣外,其他由構建層傳入的所有編譯選項都是一模一樣的。

      那么,下面分析下究竟的差異在哪里。

      4.4.1 對比map文件和匯編代碼

      如下圖所示:

      匯編文件顯示,兩者編譯出來的段分布是不一樣的,一個在.common段,一個在.global段;

      而map文件中,在.global段的被分配到了 .sbss段中,作為全局的object而存在;所以就報了 mutiple definiton 的錯誤。

      這個簡單分析,基本就可以確定是在編譯階段引入的問題,而不是在鏈接階段引入的問題,所以后面的排查中,應重點關注編譯選項,而不是鏈接選項。

      4.4.2 如何查看GCC默認使用的編譯選項

      如何你將 “****” 這幾個關鍵字去搜索,你很大概率拿到的是這個鏈接,它的方法是這樣的:

      echo "" | gcc -v -x c++ -E -

      然后查看輸出的內容中的:COLLECT_GCC_OPTIONS

      對應我這邊,替換掉對應的gcc版本,8.2.0和10.3.0版本的輸出分別是:

      GCC8.3.0 COLLECT_GCC_OPTIONS='-v' '-E' '-march=rv64imafdc' '-mabi=lp64d'GCC10.2.0 COLLECT_GCC_OPTIONS='-v' '-E' '-march=rv64imafdc' '-mabi=lp64d' '-march=rv64imafdc'

      眼看,壓根看不出差異,對不對。那我倒懷疑是方法有問題。

      我想起之前寫過 一篇文章關于GCC默認鏈接選項 的,里面倒是提到了取默認參數的蛛絲馬跡,立馬實踐下。

      這時候你先準備一個簡單得不能再簡單的helloworld.c:

      #include int main(void){ printf("hello world\r\n"); return 0;}

      然后在對應的目錄執行(注意替換gcc的路徑):

      arm-none-gcc -v -Q hello.c

      這個方法是我自己實踐摸索總結出來的參數組合,全網估計還沒人這么用!

      這個方法可以順利取得GCC默認使能的參數,留意輸出的 options enabled 即可!

      4.4.3 對比GCC的默認使能的編譯選項

      為了深究這個報錯問題,我使用關鍵字 “mutiple definition 10.2.0”,找到這么一個 有效鏈接,里面描述的情景,基本跟我的差不多。

      摘抄里面的一段話,理解下:

      The issue can be fixed with adding -fcommon to compiler options.

      A common mistake in C is omitting extern when declaring a global variable in a header file. If the header is included by several files it results in multiple definitions of the same variable. In previous GCC versions this error is ignored. GCC 10 defaults to -fno-common, which means a linker error will now be reported. To fix this, use extern in header files when declaring global variables, and ensure each global is defined in exactly one C file. If tentative definitions of particular variables need to be placed in a common block, attribute((common)) can be used to force that behavior even in code compiled without -fcommon. As a workaround, legacy C code where all tentative definitions should be placed into a common block can be compiled with -fcommon.

      順著這個編譯選項,我找到了GCC 10.x版本的 編譯選項在線說明文檔,摘抄下里面關于 -fcommon 選項 和 -fno-common 選項的說明,大家理解下:

      -fcommon

      In C code, this option controls the placement of global variables defined without an initializer, known as tentative definitions in the C standard. Tentative definitions are distinct from declarations of a variable with the extern keyword, which do not allocate storage.

      The default is -fno-common, which specifies that the compiler places uninitialized global variables in the BSS section of the object file. This inhibits the merging of tentative definitions by the linker so you get a multiple-definition error if the same variable is accidentally defined in more than one compilation unit.

      The -fcommon places uninitialized global variables in a common block. This allows the linker to resolve all tentative definitions of the same variable in different compilation units to the same object, or to a non-tentative definition. This behavior is inconsistent with C++, and on many targets implies a speed and code size penalty on global variable references. It is mainly useful to enable legacy code to link without errors.

      回過頭來,根據前面取得的默認編譯參數,我們對比下兩個GCC版本的默認選項,我們果然發現了 -fcommon 有差別.

      左邊是8.3.0版本,它默認使能了 -fcommon 這個參數就決定了 mcu_ota_t 編譯到 .common 段;從而鏈接的時候,并不會報警告,而僅僅是報了一個 warning: multiple common of 警告。

      而右邊的10.2.0版本沒有 -fcommon,根據在線說明可知,10.x版本默認是關閉了該選項,即使用的是 -fno-common,所以 mcu_ota_t 編譯到了 .global 段;這就直接導致在鏈接的時候,報了 mutiple-definiton 錯誤,因為位于 .global 段是不能有多份一樣的定義。

      按照這個分析,在10.2.0版本中,手動加上 -fcommon 選項,編譯也不會報 mutiple-definiton 錯誤。

      是否真是如此,留個小疑問,有心讀者可以自行驗證驗證。

      4.4.4 得出結論

      綜上幾個步驟下來,基本可以得出一個結論,外圍調用GCC發起編譯、鏈接等能看得見的步驟里,兩個版本的參數都是一模一樣的,很顯然不是因為上層傳入的編譯選項導致的;經過精準地資料輔助分析,得出是 GCC 10.2.0 版本默認使用的 -fno-common 選項惹的禍,但它的本意初衷是好的,只不過不被程序猿所熟知而已。

      【GCC編譯優化系列】這種讓人看不懂的multiple-definition真的有點讓人頭疼

      一個看似簡單的 mutiple-definiton 問題,繞了一圈,終于發現、理解并有效解決地解決這個問題。

      5 修復驗證

      5.1 問題修復

      明白了上面的基礎語法和GCC的編譯特性之后,修復的方法就很簡單了,只需要把 user_app.h 中所有的枚舉定義加上一個 typedef,正如 C語言–enum,typedef enum 枚舉類型詳解 所介紹的方法三那樣。

      修改后的代碼如下:

      #ifndef __USER_APP_H__#define __USER_APP_H__/* some enum definition */typedef enum { UART_FRAME_1 = 0x01, UART_FRAME_2, UART_FRAME_3, UART_FRAME_4, UART_FRAME_5,} frame_num_t; //注意:此處的frame_num_t為枚舉型enum frame_num_t的別名typedef enum { WIFI_STATE_1 = 0x01, WIFI_STATE_2, WIFI_STATE_3, WIFI_STATE_4, WIFI_STATE_5.} wifi_state_t; //注意:此處的wifi_state_t為枚舉型enum wifi_state_t的別名typedef enum { NOTIFY_STATE_1 = 0x01, NOTIFY_STATE_2, NOTIFY_STATE_3, NOTIFY_STATE_4, NOTIFY_STATE_5,} notify_state_t; //注意:此處的notify_state_t為枚舉型enum notify_state_t的別名typedef enum { MCU_OTA_NO_BIN = 0x00, MCU_OTA_DOWNLOAD_OK, MCU_OTA_DOWNLOAD_FAIL,} mcu_ota_t; //注意:此處的mcu_ota_t為枚舉型enum mcu_ota_t的別名/* external functions */extern int user_app_init(void);#endif /* end of __USER_APP_H__ */

      主要的核心修改,就是把enum的寫法糾正了,我跟對應的應用開發的童鞋聊過,他說可能就是寫代碼的時候 偷懶 了點,壓根沒寫到這樣的寫法有啥不妥,最最最重要的是 riscv64_unkown_elf_gcc8.3.0 的默認編譯參數,放任了這種有問題的寫法(僅僅是編譯警告,而不是編譯錯誤),從而沒有在第一時間暴露出來,造成代碼的語法隱患。

      riscv64_unkown_elf_gcc8.3.0 版本的編譯輸出,注意其實這里是有 警告 的!

      Making user_app@xxxevb.elf/home/xxx/compiler/riscv64_unkown_elf_gcc8.3.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o) and /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o): warning: multiple common of `mcu_ota_t'/home/xxx/compiler/riscv64_unkown_elf_gcc8.3.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o) and /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o): warning: multiple common of `notify_state_t'/home/xxx/compiler/riscv64_unkown_elf_gcc8.3.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o) and /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o): warning: multiple common of `wifi_state_t'/home/xxx/compiler/riscv64_unkown_elf_gcc8.3.0/Linux64/bin/../lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(user_app.o) and /home/xxx/user_app/out/user_app@xxxevb/libraries/user_app.a(app_entry.o): warning: multiple common of `frame_num_t'Making user_app@xxxevb.binMaking user_app@xxxevb.hex

      5.2 問題驗證

      代碼修復之后,使用 riscv64_unkown_elf_gcc8.3.0 版本的GCC和 riscv64_unkown_elf_gcc10.2.0版本的GCC,均一次編譯通過,這才是正統的C語言寫法,容不得半點偷懶??!

      同時,我們再分析下問題修復之后,map文件里面對這幾個定義的變化,以 mcu_ota_t 為例:

      如同我們所預料的,加上typedef之后,這個mcu_ota_t已經是一個枚舉類型的別名,并不是一個變量,自然在map文件肯定找不到它,但是原來的那種寫法能找到的原因是,它那種是定義了一個全局變量叫 mcu_ota_t。這才是兩者的本質區別。

      6 經驗總結

      嚴謹地寫好每一行代碼:了解每一行代碼背后的基礎語法,溫故而知新。

      對比確認是個好方法:選擇適當的比較方法,找出差異,往往差異的地方就是解決問題的突破口。

      回歸問題的本質:暫且認為 編譯器的報錯是不會騙人的,在這個基礎之上,逐步從問題報錯的表面往里面深究,為何會是 “multiple definition”,何時才會出現這種錯誤?

      typedef 是個好東西,用好它:熟悉它的基礎語法,每一種寫法的搭配代表什么含義,理解并應用它,很重要。

      認真對待每一個編譯器提示的 編譯警告:保不準這些警告哪天就把你帶入坑里,使用GCC的-Werror是個好選擇,把警告當錯誤處理,有助于你寫出更為嚴謹的代碼。

      GCC的默認編譯參數:這個了解非常有必要,不然下次遇到好端端的代碼編譯不過,就沒轍了。

      7 參考鏈接

      【經驗科普】實戰分析C工程代碼可能遇到的編譯問題及其解決思路

      【經驗總結】一文帶你了解C代碼到底是如何被編譯的

      【C語言之結構體】如何定義結構體并定義結構體變量

      【C語言之枚舉】如何定義枚舉并定義枚舉變量

      【C語言之typedef】typedef的基本用法

      8 更多分享

      歡迎關注我的github倉庫01workstation,日常分享一些開發筆記和項目實戰,歡迎指正問題。

      同時也非常歡迎關注我的CSDN主頁和專欄:

      【CSDN主頁:架構師李肯】

      【RT-Thread主頁:架構師李肯】

      【C/C++語言編程專欄】

      【GCC專欄】

      【信息安全專欄】

      【RT-Thread開發筆記】

      【freeRTOS開發筆記】

      有問題的話,可以跟我討論,知無不答,謝謝大家。

      gcc 移動APP

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:10種方法緩解所顯示器造成的眼睛疲勞(看屏幕眼睛疲勞 怎么緩解)
      下一篇:第十屆藍橋杯 2019年國賽真題(Java 大學C組)(第十屆藍橋杯scratch真題和答案)
      相關文章
      vvvv99日韩精品亚洲| 亚洲午夜无码久久| 亚洲av无码不卡私人影院| 亚洲剧场午夜在线观看| 久久精品国产亚洲AV无码麻豆| 亚洲中文字幕无码久久2017| 亚洲国产日韩在线观频| 亚洲成a人片在线观看老师| 亚洲成人国产精品| 色噜噜噜噜亚洲第一| 亚洲 无码 在线 专区| www国产亚洲精品久久久日本| mm1313亚洲国产精品无码试看| 看亚洲a级一级毛片| 国产偷国产偷亚洲高清人| 亚洲AⅤ优女AV综合久久久| 亚洲AV无码不卡在线观看下载| 亚洲欧洲中文日韩av乱码| 久久亚洲AV无码西西人体| 亚洲无人区午夜福利码高清完整版| 亚洲综合日韩久久成人AV| 国产亚洲无线码一区二区| 久久久亚洲精品视频| 久久久久亚洲av无码专区导航| 亚洲网址在线观看| 色噜噜亚洲男人的天堂| 亚洲国产精品无码久久98| 国产亚洲精品美女| 亚洲一区二区三区乱码A| 亚洲国产另类久久久精品| 亚洲∧v久久久无码精品| 亚洲综合在线成人一区| 中中文字幕亚洲无线码| 亚洲JLZZJLZZ少妇| 亚洲欧洲日本在线| 亚洲成AV人片在线观看| 亚洲高清资源在线观看| 亚洲日韩国产精品乱-久| 狼人大香伊蕉国产WWW亚洲| 亚洲欧洲日产国码高潮αv| 久久精品国产亚洲av麻豆|