摘要:前言使用和進(jìn)行語法分析和詞法分析,本文以語法定義文件為起點(diǎn),使用等命令行工具搜索相關(guān)源碼,以此來展示探索語法分析源碼思路語法定義文件在源代碼根目錄下通過命令查找文件我們找到了文件,里面定義了腳本的語法語法分析樹節(jié)點(diǎn)類型在查看具體的語法規(guī)則
前言
php 使用 lex 和 bison 進(jìn)行語法分析和詞法分析,本文以 bison 語法定義文件為起點(diǎn),使用 find, grep 等命令行工具搜索相關(guān)源碼,以此來展示探索 PHP 語法分析源碼思路
bison 語法定義文件在 源代碼 根目錄下通過 find 命令查找 *.y 文件
# find . -name *.y ./sapi/phpdbg/phpdbg_parser.y ./ext/json/json_parser.y ./Zend/zend_ini_parser.y ./Zend/zend_language_parser.y
我們找到了 zend_language_parser.y 文件,里面定義了 PHP 腳本 的語法
語法分析樹(AST) AST 節(jié)點(diǎn)類型: YYSTYPE在查看具體的語法規(guī)則前,我們先看看 PHP 使用什么樣的數(shù)據(jù)結(jié)構(gòu)表示 AST 根節(jié)點(diǎn),使用 grep 命令 搜索 YYSTYPE
# grep -rin --color --include=*.h --include=*.c "#define YYSTYPE" Zend/zeng_language_parser.c:108:#define YYSTYPE zend_parser_stack_elemzend_parser_stack_elem
grep zend_parser_stack_elem 結(jié)構(gòu)體定義
# grep -rin --color --include=*.h --include=*.c "zend_parser_stack_elem" Zend/zend_compile.h:126:typedef union _zend_parser_stack_elem
打開 zend_compile.h 文件
typedef union _zend_parser_stack_elem { zend_ast *ast; zend_string *str; zend_ulong num; } zend_parser_stack_elem;
zend_parser_stack_elem 是一個(gè)聯(lián)合體,一個(gè) AST 節(jié)點(diǎn)可能是 num(數(shù)值),str(字符串)或者 ast(非終結(jié)符)
zend_ast搜索 _zend_ast 結(jié)構(gòu)體定義
# grep -rin --color --include=*.h --include=*.c _zend_ast * Zend/zend_ast.h:153:struct _zend_ast {
打開 zend_ast.h 文件
struct _zend_ast { zend_ast_kind kind; zend_ast_attr attire; uint32_t linen; zend_ast *child[1]; }kind
Type of the node(ZEND_AST_* enum constant)
zend_ast.h 文件頭部 enum 枚舉類型包含了各個(gè) ZEND_AST_* 定義
enum _zend_ast_kind { /* special nodes */ ZEND_AST_ZVAL = 1 << ZEND_AST_SPECIAL_SHIFT, ZEND_AST_ZNODE, /* declaration nodes */ ZEND_AST_FUNC_DECL, ZEND_AST_CLOSURE, ZEND_AST_METHOD, ZEND_AST_CLASS, ... }attr
Additional attribute,use depending on node type
linenLine number
childArray of children(using struct hack)
zend_ast 其它子類zend_ast.h 文件中還包含其它 和 zend_ast 在結(jié)構(gòu)上類似的結(jié)構(gòu),類似 OOP 中的 子類
zend_ast_list zend_ast_zval zend_ast_decl 創(chuàng)建 zend_astzend_ast.c 中有一系列的函數(shù)用于創(chuàng)建 zend_ast, zend_list
zend_ast_create
zend_ast_create_ex
zend_ast_create_list
zend_ast_createzend_ast_create 函數(shù)根據(jù) kind 和一個(gè)或多個(gè) child zend_ast 創(chuàng)建一個(gè)新的 zend_ast,它在內(nèi)部調(diào)用 zend_ast_create_from_va_list
ZEND_API zend_ast *zend_ast_create(zend_ast_kind kind, ...) { va_list va; zend_ast *ast; va_start(va, kind); ast = zend_ast_create_from_va_list(kind, 0, va); va_end(va); return ast; }zend_ast_create_from_va_list yyparse
bison 語法分析工具一般以 yyparse 函數(shù)為入口
# grep --color -rinw --include=*.h --include=*.c yyparse * Zend/zend_language_parser.c:63:#define yyparse zendparse
看來 PHP 給 yyparse 起了個(gè)別名,我們?cè)倏纯创a里面哪些地方引用了 zendparse
# grep --color -rinw --include=*.h --include=*.c zendparse * Zend/zend_language_scanner.c:587: if (!zendparse()) { Zend/zend_language_parser.c:436:int zendparse (void);
我們看到 zend_language_scanner.c 文件中使用了 zenparse()
static zend_op_array *zend_compile(int type) { ... if (!zendparse()) { ... } ... }
PHP 命名規(guī)范還是不錯(cuò)的,從 zend_compile 可以推測(cè)出這個(gè)函數(shù)應(yīng)該是用來編譯一段 PHP 代碼,返回值 zend_op_array 估計(jì)是 PHP 虛擬機(jī)字節(jié)碼數(shù)組
# grep --color -rinw --include=*.h --include=*.c zend_compile Zend/zend_language_scanner.c:637: op_array = zend_compile Zend/zend_language_scanner.c:769: op_array = zend_compile
我們?cè)?zend_language_scanner.c 的 637 和 769 行找到了兩處對(duì) zend_compile 的引用
623 ZEND_API zend_op_array *compile_file(...) 645 zend_op_array *compile_filename(int type, zval *filename)
順藤摸瓜,我們接著查找 compile_file,compile_filename
# grep --color -rinw --include=*.h --include=*.c compile_file Zend/zend.c:705 zend_compile_file = compile_file; Zend/zend.c:711 zend_compile_file = compile_file;
zend.c 705 在函數(shù) zend_startup 內(nèi)
int zend_startup(zend_utility_functions *utility_functions, char **extensions)zend_compile compiler globals(CG)
語法分析和中間代碼生成過程中使用了 全局變量 CG 來保存中間結(jié)果(AST),搜索 CG 宏定義
/* Compiler */ #ifdef ZTS # define CG(v) ZEND_TSRMG(compiler_globals_id, zend_compiler_globals *, v) #else # define CG(v) (compiler_globals.v) extern ZEND_API struct _zend_compiler_globals compiler_globals; #endif
這里有一個(gè)條件編譯開關(guān) ZTS,PHP 老鳥因該都知道 "要使用 pthreads 擴(kuò)展,需要構(gòu)建 PHP 時(shí)啟用 ZTS (Zend Thread Safety)",很自然想到:因?yàn)?CG 是一個(gè)全局變量,所以為了在多線程環(huán)境下保證線程安全,需要使用特殊機(jī)制(類似 Java 中的 TLS,thread local storage)訪問 CG 中的字段
實(shí)現(xiàn)有了關(guān)于 CG 的鋪墊,我們馬上來看 zend_compile 函數(shù)的實(shí)現(xiàn)
// zend_language_scanner.c static zend_op_array *zend_compile(int type) { // 編譯生成的字節(jié)碼數(shù)組 zend_op_array *op_array = NULL; zend_bool original_in_compilation = CG(in_compilation); CG(in_compilation) = 1; CG(ast) = NULL; CG(ast_arena) = zend_arena_create(1024 * 32); if (!zendparse()) { ... op_array = emalloc(sizeof(zend_op_array)); init_op_array(op_array, type, INITIAL_OP_ARRAY_SIZE); ... zend_compile_top_stmt(CG(ast)); zend_emit_final_return(type == ZEND_USER_FUNCTION); pass_two(op_array); CG(active_op_array) = original_active_op_array; } ... CG(in_compilation) = original_in_compilation; return op_array; }
這里忽略了一些細(xì)節(jié),突出語法分析和字節(jié)碼生成的流程
調(diào)用 zend_parse 進(jìn)行語法分析,生成的 AST 根節(jié)點(diǎn)保存在 GC(ast) 中
為字節(jié)碼數(shù)組分配內(nèi)存
調(diào)用 zend_compile_top_stmt 根據(jù) AST 生成字節(jié)碼數(shù)組保存在 GC(active_op_array) 中
總結(jié)文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/30535.html
摘要:前言字節(jié)碼生成編譯的代碼主要集中在,文件中包含大量的函數(shù),基本上一個(gè)函數(shù)對(duì)應(yīng)語法規(guī)則文件一個(gè)非終結(jié)符,函數(shù)是所有函數(shù)的入口數(shù)據(jù)結(jié)構(gòu)結(jié)構(gòu)體是字節(jié)碼抽象結(jié)構(gòu)體并沒有像名字那樣簡(jiǎn)單,它包含了大量的字段供虛擬機(jī)在運(yùn)行時(shí)使用一如既往的簡(jiǎn)單,直觀,相比 前言 字節(jié)碼生成(編譯)的代碼主要集中在 zend_compile.c ,文件中包含大量的 zend_compile_xxx 函數(shù),基本上一個(gè)函數(shù)...
摘要:前言本文簡(jiǎn)要介紹虛擬機(jī)解釋執(zhí)行字節(jié)碼的基本邏輯以及相關(guān)的數(shù)據(jù)結(jié)構(gòu),關(guān)于源代碼的下載,編譯,調(diào)試可以參考之前的系列文章我們來看看執(zhí)行一個(gè)簡(jiǎn)單的腳本的調(diào)用棧由于是執(zhí)行腳本文件,所以調(diào)用了函數(shù),最終調(diào)用函數(shù)和其它語言編寫的系統(tǒng)軟件類似,函數(shù)中 前言 本文簡(jiǎn)要介紹 zend 虛擬機(jī)解釋執(zhí)行字節(jié)碼的基本邏輯以及相關(guān)的數(shù)據(jù)結(jié)構(gòu),關(guān)于 PHP 源代碼的下載,編譯,調(diào)試可以參考之前的系列文章 exec...
摘要:前言本文通過分析這個(gè)語句的編譯和執(zhí)行來窺探解釋執(zhí)行邏輯準(zhǔn)備參考之前的系列文章,在環(huán)境下下載,編譯源代碼將代碼導(dǎo)入中編輯運(yùn)行選項(xiàng),增加運(yùn)行參數(shù)設(shè)置斷點(diǎn)開始調(diào)試是一個(gè)測(cè)試腳本,放在目錄下,中只包含一條簡(jiǎn)單的賦值語句調(diào)用堆棧參考之前的系列文章 前言 本文通過分析 $a=1 這個(gè) PHP 語句的編譯和執(zhí)行來窺探 php-cli 解釋執(zhí)行邏輯 準(zhǔn)備 參考之前的系列文章,在 ubuntu 環(huán)境下...
摘要:前言語法分析器調(diào)用獲取詞法單元,對(duì)于復(fù)雜的語言實(shí)現(xiàn)一般都會(huì)自定義,搜索的宏定義搜索函數(shù)的定義打開文件查看函數(shù)定義這里出現(xiàn)了兩個(gè)新的數(shù)據(jù)類型,,從命名推測(cè)是語法分析棧元素語法分析樹節(jié)點(diǎn),搜索代碼里面哪些地方引用了 前言 yylex bison 語法分析器調(diào)用 yylex 獲取詞法單元,對(duì)于復(fù)雜的語言實(shí)現(xiàn)一般都會(huì)自定義 yylex,搜索 yylex 的宏定義 # grep -rin --c...
摘要:前言本文從函數(shù)定義的語法規(guī)則開始,簡(jiǎn)要介紹解釋器如何編譯函數(shù)定義函數(shù)對(duì)應(yīng)的節(jié)點(diǎn)為了看起來清楚一些,我們將語法規(guī)則定義與語法動(dòng)作分開根據(jù)語法動(dòng)作,這條函數(shù)定義規(guī)則會(huì)創(chuàng)建一個(gè)類型的結(jié)點(diǎn),我們來看看方法是一個(gè)通用的方法,通 前言 本文從函數(shù)定義的語法規(guī)則開始,簡(jiǎn)要介紹 PHP 解釋器如何 編譯 函數(shù)定義 函數(shù)對(duì)應(yīng)的 AST 節(jié)點(diǎn) 為了看起來清楚一些,我們將 語法規(guī)則定義 與 語法動(dòng)作分開: ...
閱讀 1819·2019-08-30 13:54
閱讀 2734·2019-08-29 17:27
閱讀 1123·2019-08-29 17:23
閱讀 3357·2019-08-29 15:20
閱讀 1234·2019-08-29 11:28
閱讀 1576·2019-08-26 10:39
閱讀 1324·2019-08-26 10:29
閱讀 649·2019-08-26 10:13