PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/book/chapt02/02-03-03-from-opcode-to-handler.markdown

http://github.com/reeze/tipi
Markdown | 183 lines | 149 code | 34 blank | 0 comment | 0 complexity | d8f453685ce4d7788dfce6bd40b574ae MD5 | raw file
Possible License(s): GPL-2.0
  1. # opcode处理函数查找
  2. 从上一小节读者可以了解到opcode在PHP内部的实现那怎么找到某个opcode的处理函数呢
  3. 为了方便读者在追踪代码的过程中找到各种opcode对应的处理函数实现下面介绍几种方法
  4. >**NOTE**
  5. >从PHP5.1开始PHP对opcode的分发方式可以用户自定义分为CALLSWITCH和GOTO三种类型
  6. >默认使用的CALL的方式本文也应用于这种方式有关Zend虚拟机的介绍请阅读后面相关内容
  7. ## Debug法
  8. 在学习研究PHP内核的过程中经常通过opcode来查看代码的执行顺序opcode的执行由在文件Zend/zend_vm_execute.h中的execute函数执行
  9. [c]
  10. ZEND_API void execute(zend_op_array *op_array TSRMLS_DC)
  11. {
  12. ...
  13. zend_vm_enter:
  14. ....
  15. if ((ret = EX(opline)->handler(execute_data TSRMLS_CC)) > 0) {
  16. switch (ret) {
  17. case 1:
  18. EG(in_execution) = original_in_execution;
  19. return;
  20. case 2:
  21. op_array = EG(active_op_array);
  22. goto zend_vm_enter;
  23. case 3:
  24. execute_data = EG(current_execute_data);
  25. default:
  26. break;
  27. }
  28. ...
  29. }
  30. 在执行的过程中EX(opline)->handler展开后为 *execute_data->opline->handler存储了处理当前操作的函数指针
  31. 使用gdb调试在execute函数处增加断点使用p命令可以打印出类似这样的结果
  32. [c]
  33. (gdb) p *execute_data->opline->handler
  34. $1 = {int (zend_execute_data *)} 0x10041f394 <ZEND_NOP_SPEC_HANDLER>
  35. 这样就可以方便的知道当前要执行的处理函数了这种debug的方法这种方法比较麻烦需要使用gdb来调试
  36. ##计算法
  37. 在PHP内部有一个函数用来快速的返回特定opcode对应的opcode处理函数指针zend_vm_get_opcode_handler()函数
  38. [c]
  39. static opcode_handler_t
  40. zend_vm_get_opcode_handler(zend_uchar opcode, zend_op* op)
  41. {
  42. static const int zend_vm_decode[] = {
  43. _UNUSED_CODE, /* 0 */
  44. _CONST_CODE, /* 1 = IS_CONST */
  45. _TMP_CODE, /* 2 = IS_TMP_VAR */
  46. _UNUSED_CODE, /* 3 */
  47. _VAR_CODE, /* 4 = IS_VAR */
  48. _UNUSED_CODE, /* 5 */
  49. _UNUSED_CODE, /* 6 */
  50. _UNUSED_CODE, /* 7 */
  51. _UNUSED_CODE, /* 8 = IS_UNUSED */
  52. _UNUSED_CODE, /* 9 */
  53. _UNUSED_CODE, /* 10 */
  54. _UNUSED_CODE, /* 11 */
  55. _UNUSED_CODE, /* 12 */
  56. _UNUSED_CODE, /* 13 */
  57. _UNUSED_CODE, /* 14 */
  58. _UNUSED_CODE, /* 15 */
  59. _CV_CODE /* 16 = IS_CV */
  60. };
  61. return zend_opcode_handlers[
  62. opcode * 25 + zend_vm_decode[op->op1.op_type] * 5
  63. + zend_vm_decode[op->op2.op_type]];
  64. }
  65. 由上面的代码可以看到opcode到php内部函数指针的查找是由下面的公式来进行的
  66. [c]
  67. opcode * 25 + zend_vm_decode[op->op1.op_type] * 5
  68. + zend_vm_decode[op->op2.op_type]
  69. 然后将其计算的数值作为索引到zend_init_opcodes_handlers数组中进行查找
  70. 不过这个数组实在是太大了有3851个元素手动查找和计算都比较麻烦
  71. ## 命名查找法
  72. 上面的两种方法其实都是比较麻烦的在定位某一opcode的实现执行代码的过程中
  73. 都不得不对程序进行执行或者计算中间值而在追踪的过程中笔者发现处理函数名称是有一定规则的
  74. 这里以函数调用的opcode为例调用某函数的opcode及其对应在php内核中实现的处理函数如下
  75. [c]
  76. //函数调用:
  77. DO_FCALL ==> ZEND_DO_FCALL_SPEC_CONST_HANDLER
  78. //变量赋值:
  79. ASSIGN => ZEND_ASSIGN_SPEC_VAR_CONST_HANDLER
  80. ZEND_ASSIGN_SPEC_VAR_TMP_HANDLER
  81. ZEND_ASSIGN_SPEC_VAR_VAR_HANDLER
  82. ZEND_ASSIGN_SPEC_VAR_CV_HANDLER
  83. //变量加法:
  84. ASSIGN_SUB => ZEND_ASSIGN_SUB_SPEC_VAR_CONST_HANDLER,
  85. ZEND_ASSIGN_SUB_SPEC_VAR_TMP_HANDLER,
  86. ZEND_ASSIGN_SUB_SPEC_VAR_VAR_HANDLER,
  87. ZEND_ASSIGN_SUB_SPEC_VAR_UNUSED_HANDLER,
  88. ZEND_ASSIGN_SUB_SPEC_VAR_CV_HANDLER,
  89. ZEND_ASSIGN_SUB_SPEC_UNUSED_CONST_HANDLER,
  90. ZEND_ASSIGN_SUB_SPEC_UNUSED_TMP_HANDLER,
  91. ZEND_ASSIGN_SUB_SPEC_UNUSED_VAR_HANDLER,
  92. ZEND_ASSIGN_SUB_SPEC_UNUSED_UNUSED_HANDLER,
  93. ZEND_ASSIGN_SUB_SPEC_UNUSED_CV_HANDLER,
  94. ZEND_ASSIGN_SUB_SPEC_CV_CONST_HANDLER,
  95. ZEND_ASSIGN_SUB_SPEC_CV_TMP_HANDLER,
  96. ZEND_ASSIGN_SUB_SPEC_CV_VAR_HANDLER,
  97. ZEND_ASSIGN_SUB_SPEC_CV_UNUSED_HANDLER,
  98. ZEND_ASSIGN_SUB_SPEC_CV_CV_HANDLER,
  99. 在上面的命名就会发现其实处理函数的命名是有以下规律的
  100. [c]
  101. ZEND_[opcode]_SPEC_(变量类型1)_(变量类型2)_HANDLER
  102. 这里的变量类型1和变量类型2是可选的如果同时存在那就是左值和右值归纳有下几类
  103. VAR TMP CV UNUSED CONST
  104. 这样可以根据相关的执行场景来判定
  105. ## 日志记录法
  106. 这种方法是上面**计算法**的升级同时也是比较精准的方式**zend_vm_get_opcode_handler** 方法中添加以下代码
  107. [c]
  108. static opcode_handler_t
  109. zend_vm_get_opcode_handler(zend_uchar opcode, zend_op* op)
  110. {
  111. static const int zend_vm_decode[] = {
  112. _UNUSED_CODE, /* 0 */
  113. _CONST_CODE, /* 1 = IS_CONST */
  114. _TMP_CODE, /* 2 = IS_TMP_VAR */
  115. _UNUSED_CODE, /* 3 */
  116. _VAR_CODE, /* 4 = IS_VAR */
  117. _UNUSED_CODE, /* 5 */
  118. _UNUSED_CODE, /* 6 */
  119. _UNUSED_CODE, /* 7 */
  120. _UNUSED_CODE, /* 8 = IS_UNUSED */
  121. _UNUSED_CODE, /* 9 */
  122. _UNUSED_CODE, /* 10 */
  123. _UNUSED_CODE, /* 11 */
  124. _UNUSED_CODE, /* 12 */
  125. _UNUSED_CODE, /* 13 */
  126. _UNUSED_CODE, /* 14 */
  127. _UNUSED_CODE, /* 15 */
  128. _CV_CODE /* 16 = IS_CV */
  129. };
  130. //很显然,我们把opcode和相对应的写到了/tmp/php.log文件中
  131. int op_index;
  132. op_index = opcode * 25 + zend_vm_decode[op->op1.op_type] * 5 + zend_vm_decode[op->op2.op_type];
  133. FILE *stream;
  134. if((stream = fopen("/tmp/php.log", "a+")) != NULL){
  135. fprintf(stream, "opcode: %d , zend_opcode_handlers_index:%d\n", opcode, op_index);
  136. }
  137. fclose(stream);
  138. return zend_opcode_handlers[
  139. opcode * 25 + zend_vm_decode[op->op1.op_type] * 5
  140. + zend_vm_decode[op->op2.op_type]];
  141. }
  142. 然后就可以在**/tmp/php.log**文件中生成类似如下结果:
  143. [c]
  144. opcode: 38 , zend_opcode_handlers_index:970
  145. 前面的数字是opcode的我们可以这里查到 http://php.net/manual/en/internals2.opcodes.list.php
  146. 后面的数字是static const opcode_handler_t labels[] 索引里面对应了处理函数的名称
  147. 对应源码文件是Zend/zend_vm_execute.h 第30077行左右 这是一个超大的数组php5.3.4中有3851个元素
  148. 在上面的例子里看样子我们要数到第970个了当然有很多种方法来避免人工去计算这里就不多介绍了