摘要:操作數(shù)本身并無(wú)數(shù)據(jù)類(lèi)型,它的數(shù)據(jù)類(lèi)型由操作碼確定任何架構(gòu)的計(jì)算機(jī)都會(huì)對(duì)外提供指令集合運(yùn)算器通過(guò)執(zhí)行指令直接發(fā)出控制信號(hào)控制計(jì)算機(jī)各項(xiàng)操作。
順風(fēng)車(chē)運(yùn)營(yíng)研發(fā)團(tuán)隊(duì) 李樂(lè)
1.從物理機(jī)說(shuō)起虛擬機(jī)也是計(jì)算機(jī),設(shè)計(jì)思想和物理機(jī)有很多相似之處;
1.1馮諾依曼體系結(jié)構(gòu)馮·諾依曼是當(dāng)之無(wú)愧的數(shù)字計(jì)算機(jī)之父,當(dāng)前計(jì)算機(jī)都采用的是馮諾依曼體系結(jié)構(gòu);設(shè)計(jì)思想主要包含以下幾個(gè)方面:
指令和數(shù)據(jù)不加區(qū)別混合存儲(chǔ)在同一個(gè)存儲(chǔ)器中,它們都是內(nèi)存中的數(shù)據(jù)。現(xiàn)代CPU的保護(hù)模式,每個(gè)內(nèi)存段都有段描述符,這個(gè)描述符記錄著這個(gè)內(nèi)存段的訪問(wèn)權(quán)限(可讀,可寫(xiě),可執(zhí)行)。這就變相的指定了哪些內(nèi)存中存儲(chǔ)的是指令哪些是數(shù)據(jù));
存儲(chǔ)器是按地址訪問(wèn)的線性編址的一維結(jié)構(gòu),每個(gè)單元的位數(shù)是固定的;
數(shù)據(jù)以二進(jìn)制表示;
指令由操作碼和操作數(shù)組成。操作碼指明本指令的操作類(lèi)型,操作數(shù)指明操作數(shù)本身或者操作數(shù)的地址。操作數(shù)本身并無(wú)數(shù)據(jù)類(lèi)型,它的數(shù)據(jù)類(lèi)型由操作碼確定;任何架構(gòu)的計(jì)算機(jī)都會(huì)對(duì)外提供指令集合;
運(yùn)算器通過(guò)執(zhí)行指令直接發(fā)出控制信號(hào)控制計(jì)算機(jī)各項(xiàng)操作。由指令計(jì)數(shù)器指明待執(zhí)行指令所在的內(nèi)存地址。指令計(jì)數(shù)器只有一個(gè),一般按順序遞增,但執(zhí)行順序可能因?yàn)檫\(yùn)算結(jié)果或當(dāng)時(shí)的外界條件而改變;
1.2匯編語(yǔ)言簡(jiǎn)介任何架構(gòu)的計(jì)算機(jī)都會(huì)提供一組指令集合;
指令由操作碼和操作數(shù)組成;操作碼即操作類(lèi)型,操作數(shù)可以是一個(gè)立即數(shù)或者一個(gè)存儲(chǔ)地址;每條指令可以有0、1或2個(gè)操作數(shù);
指令就是一串二進(jìn)制;匯編語(yǔ)言是二進(jìn)制指令的文本形式;
push %ebx mov %eax, [%esp+8] mov %ebx, [%esp+12] add %eax, %ebx pop %ebx
push、mov、add、pop等就是操作碼;
%ebx寄存器;[%esp+12]內(nèi)存地址;
操作數(shù)只是一塊可存取數(shù)據(jù)的存儲(chǔ)區(qū);操作數(shù)本身并無(wú)數(shù)據(jù)類(lèi)型,它的數(shù)據(jù)類(lèi)型由操作碼確定;
如movb傳送字節(jié),movw傳送字,movl傳送雙字等
過(guò)程(函數(shù))是對(duì)代碼的封裝,對(duì)外暴露的只是一組指定的參數(shù)和一個(gè)可選的返回值;可以在程序中不同的地方調(diào)用這個(gè)函數(shù);假設(shè)過(guò)程P調(diào)用過(guò)程Q,Q執(zhí)行后返回過(guò)程P;為了實(shí)現(xiàn)這一功能,需要考慮三點(diǎn):
指令跳轉(zhuǎn):進(jìn)入過(guò)程Q的時(shí)候,程序計(jì)數(shù)器必須被設(shè)置為Q的代碼的起始地址;在返回時(shí),程序計(jì)數(shù)器需要設(shè)置為P中調(diào)用Q后面那條指令的地址;
數(shù)據(jù)傳遞:P能夠向Q提供一個(gè)或多個(gè)參數(shù),Q能夠向P返回一個(gè)值;
內(nèi)存分配與釋放:Q開(kāi)始執(zhí)行時(shí),可能需要為局部變量分配內(nèi)存空間,而在返回前,又需要釋放這些內(nèi)存空間;
大多數(shù)的語(yǔ)言過(guò)程調(diào)用都采用了棧數(shù)據(jù)結(jié)構(gòu)提供的內(nèi)存管理機(jī)制;如下圖所示:
函數(shù)的調(diào)用與返回即對(duì)應(yīng)的是一系列的入棧與出棧操作;
函數(shù)在執(zhí)行時(shí),會(huì)有自己私有的棧幀,局部變量就是分配在函數(shù)私有棧幀上的;
平時(shí)遇到的棧溢出就是因?yàn)檎{(diào)用函數(shù)層級(jí)過(guò)深,不斷入棧導(dǎo)致的;
虛擬機(jī)也是計(jì)算機(jī),參考物理機(jī)的設(shè)計(jì),設(shè)計(jì)虛擬機(jī)時(shí),首先應(yīng)該考慮三個(gè)要素:指令,數(shù)據(jù)存儲(chǔ),函數(shù)棧幀;
下面從這三點(diǎn)詳細(xì)分析PHP虛擬機(jī)的設(shè)計(jì)思路;
2.1指令 2.1.1 指令類(lèi)型任何架構(gòu)的計(jì)算機(jī)都需要對(duì)外提供一組指令集,其代表計(jì)算機(jī)支持的一組操作類(lèi)型;
PHP虛擬機(jī)對(duì)外提供186種指令,定義在zend_vm_opcodes.h文件中;
//加、減、乘、除等 #define ZEND_ADD 1 #define ZEND_SUB 2 #define ZEND_MUL 3 #define ZEND_DIV 4 #define ZEND_MOD 5 #define ZEND_SL 6 #define ZEND_SR 7 #define ZEND_CONCAT 8 #define ZEND_BW_OR 9 #define ZEND_BW_AND 10 ……………………2.1.2 指令 2.1.2.1指令的表示
指令由操作碼和操作數(shù)組成;操作碼指明本指令的操作類(lèi)型,操作數(shù)指明操作數(shù)本身或者操作數(shù)的地址;
PHP虛擬機(jī)定義指令格式為:操作碼 操作數(shù)1 操作數(shù)2 返回值;其使用結(jié)構(gòu)體_zend_op表示一條指令:
struct _zend_op { const void *handler; //指針,指向當(dāng)前指令的執(zhí)行函數(shù) znode_op op1; //操作數(shù)1 znode_op op2; //操作數(shù)2 znode_op result; //返回值 uint32_t extended_value;//擴(kuò)展 uint32_t lineno; //行號(hào) zend_uchar opcode; //指令類(lèi)型 zend_uchar op1_type; //操作數(shù)1的類(lèi)型(此類(lèi)型并不代表字符串、數(shù)組等數(shù)據(jù)類(lèi)型;其表示此操作數(shù)是常量,臨時(shí)變量,編譯變量等) zend_uchar op2_type; //操作數(shù)2的類(lèi)型 zend_uchar result_type; //返回值的類(lèi)型 };2.1.2.2 操作數(shù)的表示
從上面可以看到,操作數(shù)使用結(jié)構(gòu)體znode_op表示,定義如下:
constant、var、num等都是uint32_t類(lèi)型的,這怎么表示一個(gè)操作數(shù)呢?(既不是指針不能代表地址,也無(wú)法表示所有數(shù)據(jù)類(lèi)型);
其實(shí),操作數(shù)大多情況采用的相對(duì)地址表示方式,constant等表示的是相對(duì)于執(zhí)行棧幀首地址的偏移量;
另外,_znode_op結(jié)構(gòu)體中有個(gè)zval *zv字段,其也可以表示一個(gè)操作數(shù),這個(gè)字段是一個(gè)指針,指向的是zval結(jié)構(gòu)體,PHP虛擬機(jī)支持的所有數(shù)據(jù)類(lèi)型都使用zval結(jié)構(gòu)體表示;
typedef union _znode_op { uint32_t constant; uint32_t var; uint32_t num; uint32_t opline_num; #if ZEND_USE_ABS_JMP_ADDR zend_op *jmp_addr; #else uint32_t jmp_offset; #endif #if ZEND_USE_ABS_CONST_ADDR zval *zv; #endif } znode_op;2.2 數(shù)據(jù)存儲(chǔ)
PHP虛擬機(jī)支持多種數(shù)據(jù)類(lèi)型:整型、浮點(diǎn)型、字符串、數(shù)組,對(duì)象等;PHP虛擬機(jī)如何存儲(chǔ)和表示多種數(shù)據(jù)類(lèi)型?
2.1.2.2節(jié)指出結(jié)構(gòu)體_znode_op代表一個(gè)操作數(shù);操作數(shù)可以是一個(gè)偏移量(計(jì)算得到一個(gè)地址,即zval結(jié)構(gòu)體的首地址),或者一個(gè)zval指針;PHP虛擬機(jī)使用zval結(jié)構(gòu)體表示和存儲(chǔ)多種數(shù)據(jù);
struct _zval_struct { zend_value value; //存儲(chǔ)實(shí)際的value值 union { struct { //一些標(biāo)志位 ZEND_ENDIAN_LOHI_4( zend_uchar type, //重要;表示變量類(lèi)型 zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) /* call info for EX(This) */ } v; uint32_t type_info; } u1; union { //其他有用信息 uint32_t next; /* hash collision chain */ uint32_t cache_slot; /* literal cache slot */ uint32_t lineno; /* line number (for ast nodes) */ uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ uint32_t access_flags; /* class constant access flags */ uint32_t property_guard; /* single property guard */ } u2; };
zval.u1.type表示數(shù)據(jù)類(lèi)型, zend_types.h文件定義了以下類(lèi)型:
#define IS_UNDEF 0 #define IS_NULL 1 #define IS_FALSE 2 #define IS_TRUE 3 #define IS_LONG 4 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10 …………
zend_value存儲(chǔ)具體的數(shù)據(jù)內(nèi)容,結(jié)構(gòu)體定義如下:
_zend_value占16字節(jié)內(nèi)存;long、double類(lèi)型會(huì)直接存儲(chǔ)在結(jié)構(gòu)體;引用、字符串、數(shù)組等類(lèi)型使用指針存儲(chǔ);
代碼中根據(jù)zval.u1.type字段,判斷數(shù)據(jù)類(lèi)型,以此決定操作_zend_value結(jié)構(gòu)體哪個(gè)字段;
可以看出,字符串使用zend_string表示,數(shù)組使用zend_array表示…
typedef union _zend_value { zend_long lval; double dval; zend_refcounted *counted; zend_string *str; zend_array *arr; zend_object *obj; zend_resource *res; zend_reference *ref; zend_ast_ref *ast; zval *zv; void *ptr; zend_class_entry *ce; zend_function *func; struct { uint32_t w1; uint32_t w2; } ww; } zend_value;
如下圖為PHP7中字符串結(jié)構(gòu)圖:
2.3 再談指令2.1.2.1指出,指令使用結(jié)構(gòu)體_zend_op表示;其中最主要2個(gè)屬性:操作函數(shù),操作數(shù)(兩個(gè)操作數(shù)和一個(gè)返回值);
操作數(shù)的類(lèi)型(常量、臨時(shí)變量等)不同,同一個(gè)指令對(duì)應(yīng)的handler函數(shù)也會(huì)不同;操作數(shù)類(lèi)型定義在 Zend/zend_compile.h文件:
//常量 #define IS_CONST (1<<0) //臨時(shí)變量,用于操作的中間結(jié)果;不能被其他指令對(duì)應(yīng)的handler重復(fù)使用 #define IS_TMP_VAR (1<<1) //這個(gè)變量并不是PHP代碼中聲明的變量,常見(jiàn)的是返回的臨時(shí)變量,比如$a=time(), 函數(shù)time返回值的類(lèi)型就是IS_VAR,這種類(lèi)型的變量是可以被其他指令對(duì)應(yīng)的handler重復(fù)使用的 #define IS_VAR (1<<2) #define IS_UNUSED (1<<3) /* Unused variable */ //編譯變量;即PHP中聲明的變量; #define IS_CV (1<<4) /* Compiled variable */
操作函數(shù)命名規(guī)則為:ZEND_[opcode]_SPEC_(操作數(shù)1類(lèi)型)_(操作數(shù)2類(lèi)型)_(返回值類(lèi)型)_HANDLER
比如賦值語(yǔ)句就有以下多種操作函數(shù):
ZEND_ASSIGN_SPEC_VAR_CONST_RETVAL_UNUSED_HANDLER, ZEND_ASSIGN_SPEC_VAR_TMP_RETVAL_UNUSED_HANDLER, ZEND_ASSIGN_SPEC_VAR_VAR_RETVAL_UNUSED_HANDLER, ZEND_ASSIGN_SPEC_VAR_CV_RETVAL_UNUSED_HANDLER, …
對(duì)于$a=1,其操作函數(shù)為: ZEND_ASSIGN_SPEC_CV_CONST_RETVAL_UNUSED_HANDLER;函數(shù)實(shí)現(xiàn)為:
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_SPEC_CV_CONST_RETVAL_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE zval *value; zval *variable_ptr; SAVE_OPLINE(); //獲取op2對(duì)應(yīng)的值,也就是1 value = EX_CONSTANT(opline->op2); //在execute_data中獲取op1的位置,也就是$a(execute_data類(lèi)似函數(shù)棧幀,后面詳細(xì)分析) variable_ptr = _get_zval_ptr_cv_undef_BP_VAR_W(execute_data, opline->op1.var); //賦值 value = zend_assign_to_variable(variable_ptr, value, IS_CONST); if (UNEXPECTED(0)) { ZVAL_COPY(EX_VAR(opline->result.var), value); } ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); }2.4 函數(shù)棧幀 2.4.1指令集
上面分析了指令的結(jié)構(gòu)與表示,PHP虛擬機(jī)使用_zend_op_array表示指令的集合:
struct _zend_op_array { ………… //last表示指令總數(shù);opcodes為存儲(chǔ)指令的數(shù)組; uint32_t last; zend_op *opcodes; //變量類(lèi)型為IS_CV的個(gè)數(shù) int last_var; //變量類(lèi)型為IS_VAR和IS_TEMP_VAR的個(gè)數(shù) uint32_t T; //存放IS_CV類(lèi)型變量的數(shù)組 zend_string **vars; ………… //靜態(tài)變量 HashTable *static_variables; //常量個(gè)數(shù);常量數(shù)組 int last_literal; zval *literals; … };
注意: last_var代表IS_CV類(lèi)型變量的個(gè)數(shù),這種類(lèi)型變量存放在vars數(shù)組中;在整個(gè)編譯過(guò)程中,每次遇到一個(gè)IS_CV類(lèi)型的變量(類(lèi)似于$something),就會(huì)去遍歷vars數(shù)組,檢查是否已經(jīng)存在,如果不存在,則插入到vars中,并將last_var的值設(shè)置為該變量的操作數(shù);如果存在,則使用之前分配的操作數(shù)
2.4.2 函數(shù)棧幀PHP虛擬機(jī)實(shí)現(xiàn)了與1.3節(jié)物理機(jī)類(lèi)似的函數(shù)棧幀結(jié)構(gòu);
使用 _zend_vm_stack表示棧結(jié)構(gòu);多個(gè)棧之間使用prev字段形成單向鏈表;top和end指向棧低和棧頂,分別為zval類(lèi)型的指針;
struct _zend_vm_stack { zval *top; zval *end; zend_vm_stack prev; };
考慮如何設(shè)計(jì)函數(shù)執(zhí)行時(shí)候的幀結(jié)構(gòu):當(dāng)前函數(shù)執(zhí)行時(shí),需要存儲(chǔ)函數(shù)編譯后的指令,需要存儲(chǔ)函數(shù)內(nèi)部的局部變量等(2.1.2.2節(jié)指出,操作數(shù)使用結(jié)構(gòu)體znode_op表示,其內(nèi)部使用uint32_t表示操作數(shù),此時(shí)表示的就是當(dāng)前zval變量相對(duì)于當(dāng)前函數(shù)棧幀首地址的偏移量);
PHP虛擬機(jī)使用結(jié)構(gòu)體_zend_execute_data存儲(chǔ)當(dāng)前函數(shù)執(zhí)行所需數(shù)據(jù);
struct _zend_execute_data { //當(dāng)前指令指令 const zend_op *opline; //當(dāng)前函數(shù)執(zhí)行棧幀 zend_execute_data *call; //函數(shù)返回?cái)?shù)據(jù) zval *return_value; zend_function *func; zval This; /* this + call_info + num_args */ //調(diào)用當(dāng)前函數(shù)的棧幀 zend_execute_data *prev_execute_data; //符號(hào)表 zend_array *symbol_table; #if ZEND_EX_USE_RUN_TIME_CACHE void **run_time_cache; #endif #if ZEND_EX_USE_LITERALS //常量數(shù)組 zval *literals; #endif };
函數(shù)開(kāi)始執(zhí)行時(shí),需要為函數(shù)分配相應(yīng)的函數(shù)棧幀并入棧,代碼如下:
static zend_always_inline zend_execute_data *zend_vm_stack_push_call_frame(uint32_t call_info, zend_function *func, uint32_t num_args, zend_class_entry *called_scope, zend_object *object) { //計(jì)算當(dāng)前函數(shù)棧幀需要內(nèi)存空間大小 uint32_t used_stack = zend_vm_calc_used_stack(num_args, func); //根據(jù)棧幀大小分配空間,入棧 return zend_vm_stack_push_call_frame_ex(used_stack, call_info, func, num_args, called_scope, object); } //計(jì)算函數(shù)棧幀大小 static zend_always_inline uint32_t zend_vm_calc_used_stack(uint32_t num_args, zend_function *func) { //_zend_execute_data大?。?0字節(jié)/16字節(jié)=5)+參數(shù)數(shù)目 uint32_t used_stack = ZEND_CALL_FRAME_SLOT + num_args; if (EXPECTED(ZEND_USER_CODE(func->type))) { //當(dāng)前函數(shù)臨時(shí)變量等數(shù)目 used_stack += func->op_array.last_var + func->op_array.T - MIN(func->op_array.num_args, num_args); } //乘以16字節(jié) return used_stack * sizeof(zval); } //入棧 static zend_always_inline zend_execute_data *zend_vm_stack_push_call_frame_ex(uint32_t used_stack, uint32_t call_info, zend_function *func, uint32_t num_args, zend_class_entry *called_scope, zend_object *object) { //上一個(gè)函數(shù)棧幀地址 zend_execute_data *call = (zend_execute_data*)EG(vm_stack_top); //移動(dòng)函數(shù)調(diào)用棧top指針 EG(vm_stack_top) = (zval*)((char*)call + used_stack); //初始化當(dāng)前函數(shù)棧幀 zend_vm_init_call_frame(call, call_info, func, num_args, called_scope, object); //返回當(dāng)前函數(shù)棧幀首地址 return call; }
從上面分析可以得到函數(shù)棧幀結(jié)構(gòu)圖如下所示:
總結(jié)PHP虛擬機(jī)也是計(jì)算機(jī),有三點(diǎn)是我們需要重點(diǎn)關(guān)注的:指令集(包含指令處理函數(shù))、數(shù)據(jù)存儲(chǔ)(zval)、函數(shù)棧幀;
此時(shí)虛擬機(jī)已可以接受指令并執(zhí)行指令代碼;
但是,PHP虛擬機(jī)是專(zhuān)用執(zhí)行PHP代碼的,PHP代碼如何能轉(zhuǎn)換為PHP虛擬機(jī)可以識(shí)別的指令呢——編譯;
PHP虛擬機(jī)同時(shí)提供了編譯器,可以將PHP代碼轉(zhuǎn)換為其可以識(shí)別的指令集合;
理論上你可以自定義任何語(yǔ)言,只要實(shí)現(xiàn)編譯器,能夠?qū)⒛阕约旱恼Z(yǔ)言轉(zhuǎn)換為PHP可以識(shí)別的指令代碼,就能被PHP虛擬機(jī)執(zhí)行;
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/29184.html
摘要:在中,源代碼首先將進(jìn)行詞法分析,將源代碼切割為多個(gè)字符串單元,分割后的字符串稱(chēng)之為。圖以為例解釋型語(yǔ)言的執(zhí)行示意圖第步源碼通過(guò)詞法分析得到第步基于語(yǔ)法分析器生成抽象語(yǔ)法樹(shù)第步抽象語(yǔ)法樹(shù)轉(zhuǎn)換為指令集合,解釋執(zhí)行。 順風(fēng)車(chē)運(yùn)營(yíng)研發(fā)團(tuán)隊(duì) 李志 發(fā)表在程序人生 公眾號(hào)我們常用的高級(jí)語(yǔ)言有很多種,比較出名的有CC++、Python、 PHP、Go、Pascal等。而這些語(yǔ)言根據(jù)運(yùn)行的方式不同,...
摘要:中詞法語(yǔ)法分析,生成抽象語(yǔ)法樹(shù),然后編譯成及被執(zhí)行均由虛擬機(jī)完成。通常情況下這部分是可選部分,主要為便于程序的讀寫(xiě)方便而使用。指令虛擬機(jī)的指令稱(chēng)為,每條指令對(duì)應(yīng)一個(gè)。 作者 陳雷編程語(yǔ)言的虛擬機(jī)是一種可以運(yùn)行中間語(yǔ)言的程序。中間語(yǔ)言是抽象出的指令集,由原生語(yǔ)言編譯而成,作為虛擬機(jī)執(zhí)行階段的輸入。很多語(yǔ)言都實(shí)現(xiàn)了自己的虛擬機(jī),比如Java、C#和Lua。PHP語(yǔ)言也有自己的虛擬機(jī),稱(chēng)為Z...
摘要:那些瑣碎的知識(shí)點(diǎn)作者記錄的的很奇特很難記的知識(shí)點(diǎn)。易錯(cuò)知識(shí)點(diǎn)整理注意和的區(qū)別中和都是輸出的作用,但是兩者之間還是有細(xì)微的差別。今天手頭不忙,總結(jié)一下,分享過(guò)程中掌握的知識(shí)點(diǎn)。 深入理解 PHP 之:Nginx 與 FPM 的工作機(jī)制 這篇文章從 Nginx 與 FPM 的工作機(jī)制出發(fā),探討配置背后的原理,讓我們真正理解 Nginx 與 PHP 是如何協(xié)同工作的。 PHP 那些瑣碎的知識(shí)...
摘要:我們修改上面代碼,再來(lái)看下返回值類(lèi)型限制的情況運(yùn)行結(jié)果這段代碼我們額外聲明了返回值的類(lèi)型為型。對(duì)函數(shù)返回值的聲明做了擴(kuò)充,可以定義其返回值為,無(wú)論是否開(kāi)啟嚴(yán)格模式,只要函數(shù)中有以外的其他語(yǔ)句都會(huì)報(bào)錯(cuò)。 順風(fēng)車(chē)運(yùn)營(yíng)研發(fā)團(tuán)隊(duì) 王坤 發(fā)表至21CTO公眾號(hào)(https://mp.weixin.qq.com/s/ph...) showImg(https://segmentfault.c...
閱讀 2003·2021-11-22 15:33
閱讀 3025·2021-11-18 10:02
閱讀 2640·2021-11-08 13:16
閱讀 1654·2021-10-09 09:57
閱讀 1397·2021-09-30 09:47
閱讀 2033·2019-08-29 13:05
閱讀 3096·2019-08-29 12:46
閱讀 1035·2019-08-29 12:19