Maison  >  Article  >  développement back-end  >  Analyser les variables dans le code source du noyau sous-jacent de PHP (3)

Analyser les variables dans le code source du noyau sous-jacent de PHP (3)

藏色散人
藏色散人avant
2021-06-10 14:22:362062parcourir

Cet article vous présente "Analyse des variables dans le code source sous-jacent du noyau PHP (3)". Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il sera utile à tout le monde.

Articles connexes recommandés : "Analyse des variables dans le code source du noyau sous-jacent de PHP (1) " "Analyse des variables dans le code source du noyau sous-jacent de PHP (2) zend_string "

Le code source de la structure zend_string a été lu ci-dessus.

struct _zend_string {
zend_refcounted_h gc; //占用8个字节 用于gc的计数和字符串类型的记录
zend_ulong        h;        // 占用8个字节 用于记录 字符串的哈希值
size_t            len;       //占用8个字节    字符串的长度
char              val[1];   //占用1个字节    字符串的值存储位置
};

La variable len donne à zend_string des fonctionnalités de sécurité binaires

gc, c'est-à-dire que la structure zend_refcounted_h peut réaliser la fonction de copie sur écriture

typedef struct _zend_refcounted_h {
uint32_t         refcount;//引用数
union {
uint32_t type_info;   //字符串所属的变量类别
} u;
} zend_refcounted_h;

Copie sur- la technologie d'écriture est largement utilisée dans Redis et le noyau Linux

Par exemple, Redis doit créer un processus enfant du processus serveur actuel, et la plupart des systèmes d'exploitation utilisent la copie sur écriture (copie sur écriture). en écriture) pour optimiser l'efficacité d'utilisation du processus enfant, ainsi pendant l'existence du processus enfant, le serveur augmentera le seuil du facteur de charge, évitant ainsi les opérations d'extension de table de hachage et les opérations d'écriture mémoire inutiles pendant l'existence du processus enfant. processus enfant. Économisez de la mémoire autant que possible.

PHP 7 utilise également la copie sur écriture pour économiser de la mémoire lors des opérations d'affectation. Lorsqu'une chaîne est assignée, il ne copie pas directement une copie des données, mais copie plutôt les données dans _zend_refcounted_h dans la structure zend_string. . Le refcount effectue une opération +1, et lorsque la chaîne est détruite, le refcount dans _zend_refcounted_h dans la structure zend_string effectue une opération -1.

Si vous avez lu le livre "PHP Underlying Source Code Design and Implementation" écrit par M. Chen Lei, vous constaterez peut-être qu'il est légèrement différent car ma version est PHP7.4. différent de celui que j'ai installé localement, je suppose, probablement pour la gestion unifiée de la mémoire.

Le champ gc.u.flags dans la structure zend_string, gc.u.flags a un total de 8 bits, un pour chaque catégorie, et peut être étiqueté de manière répétée. En théorie, jusqu'à 8 types d'étiquettes. peut être imprimé. Le code source actuel de PHP 7 implique principalement les catégories suivantes : 1) Pour les chaînes ordinaires temporaires, le champ flags est marqué à 0. 2) Pour les chaînes internes, utilisées pour stocker des littéraux, des identifiants, etc. dans le code PHP, le champ flags est Identifié comme IS_STR_PERSISTENT |IS_STR_INTERNED. 3) Pour les chaînes PHP connues, le champ flags sera identifié comme IS_STR_PERSISTENT|IS_STR_INTERNED|IS_STR_PERMANENT.

--------Extrait de "Conception et implémentation du code source sous-jacent PHP"

Au bas du code source PHP7.4, les variables seront classées pour faciliter la gestion de la mémoire , qui s'appuie sur la structure zend_zval Le champ u1.v.type_flags

struct _zval_struct {
 197         zend_value        value;         //变量
 198         union {
 199                 struct {
 200                         ZEND_ENDIAN_LOHI_3(
 201                                 zend_uchar    type,  //变量类型           
 202                                 zend_uchar    type_flags,//可以用于变量的分类
 203                                 union {
 204                                         uint16_t  extra;        /* not further specified */
 205                                 } u)
 206                 } v;
 207                 uint32_t type_info;//变量类型
 208         } u1;
 209           u2;
 222 };

à la ligne 555 a le code suivant

/* zval.u1.v.type_flags */
#define IS_TYPE_REFCOUNTED(1<<0) //REFCOUNTED 可以计数的
#define IS_TYPE_COLLECTABLE(1<<1) // TYPE_COLLECTABLE可收集的
#if 1
/* This optimized version assumes that we have a single "type_flag" */
/* IS_TYPE_COLLECTABLE may be used only with IS_TYPE_REFCOUNTED */
/*优化后的版本假设我们有一个单一的"type_flag" */
/* IS_TYPE_COLLECTABLE只能与IS_TYPE_REFCOUNTED一起使用*/
# define Z_TYPE_INFO_REFCOUNTED(t)(((t) & Z_TYPE_FLAGS_MASK) != 0)
#else
# define Z_TYPE_INFO_REFCOUNTED(t)(((t) & (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT)) != 0)
#endif

, il n'y a donc que deux types de zval.u1.v.type_flags dans la version PHP7.4, 0 ou 1. En parallèle, j'ai également regardé le dernier code de la version PHP8 et c'est pareil

Afin de mieux comprendre le code source en profondeur, nous avons également rassemblez les deux sections précédentes. Nous avons installé gdb pour déboguer PHP

GDB (débogueur symbolique GNU) est simplement un outil de débogage. Il s'agit d'un logiciel libre protégé par la Licence Publique Générale, la GPL. Comme tous les débogueurs, GDB vous permet de déboguer un programme, y compris d'arrêter le programme là où vous le souhaitez, auquel moment vous pouvez afficher les variables, les registres, la mémoire et la pile. De plus, vous pouvez modifier les variables et les valeurs de la mémoire. GDB est un débogueur très puissant capable de déboguer plusieurs langues. Ici, nous couvrons uniquement le débogage du C et du C++, pas d’autres langages. Un autre point à noter est que GDB est un débogueur, contrairement à VC qui est un environnement intégré. Vous pouvez utiliser certains outils frontaux tels que XXGDB, DDD, etc. Ils ont tous des interfaces graphiques, ils sont donc plus pratiques à utiliser, mais ils ne sont qu'un shell pour GDB. Par conséquent, vous devez toujours être familier avec les commandes GDB. En effet, lorsque vous utiliserez longtemps ces interfaces graphiques, vous découvrirez l'importance de vous familiariser avec les commandes GDB.

-----Extrait d'oschina

[root@a3d3f47671d9 /]# php -v
PHP 7.4.15 (cli) (built: Feb 21 2021 09:07:07) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
[root@a3d3f47671d9 /]# gbv    
bash: gbv: command not found
[root@a3d3f47671d9 /]# gdb
bash: gdb: command not found
[root@a3d3f47671d9 /]# yum install gdb

.........

Créer un nouveau fichier PHP

[root@a3d3f47671d9 cui]# vim php7-4-test-zval.php
 php7-4-test-zval.php                                                                              Buffers 
  <?php
   $a="abcdefg";
   echo $a;
   $b=88;
   echo $b;
   $c = $a;
   echo $c;
   echo $a;
   $c ="abc";
   echo $c;
   echo $a;

Exécuter PHP avec gdb

[root@a3d3f47671d9 cui]# gdb php
GNU gdb (GDB) Red Hat Enterprise Linux 8.2-12.el8
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from php...done.
(gdb) b ZEND_ECHO_SPEC_CV_HANDLER   # b 命令意思是打断点
Breakpoint 1 at 0x6dfe80: file /cui/php-7.4.15/Zend/zend_vm_execute.h, line 36987.
(gdb) r php7-4-test-zval.php
Starting program: /usr/local/bin/php php7-4-test-zval.php
warning: Error disabling address space randomization: Operation not permitted
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-127.el8.x86_64
warning: Loadable section ".note.gnu.property" outside of ELF segments
warning: Loadable section ".note.gnu.property" outside of ELF segments
warning: Loadable section ".note.gnu.property" outside of ELF segments
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
warning: Loadable section ".note.gnu.property" outside of ELF segments
warning: Loadable section ".note.gnu.property" outside of ELF segments
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /cui/php-7.4.15/Zend/zend_vm_execute.h:36987
36987SAVE_OPLINE();
Missing separate debuginfos, use: yum debuginfo-install libxcrypt-4.1.1-4.el8.x86_64 libxml2-2.9.7-8.el8.x86_64 sqlite-libs-3.26.0-11.el8.x86_64 xz-libs-5.2.4-3.el8.x86_64 zlib-1.2.11-16.el8_2.x86_64

Vous pouvez voir mon rapport d'erreur car j'ai exécuté l'image centos dans docker, j'ai vérifié certaines informations et la solution est la suivante

Modifier /etc/yum.repos.d/. Fichier .repo CentOS-Debuginfo

Modifier activer=1

Puis miam, installez yum-utils

Puis dnf installez glibc-langpack-en

yum debuginfo -installer libxcrypt -4.1.1-4.el8.x86_64 libxml2-2.9.7-8.el8.x86_64 sqlite-libs-3.26.0-11.el8.x86_64 xz-libs-5.2.4-3.el8.x86_64 zlib-1.2 .11-16.el8_2.x86_64

yum debuginfo-install glibc-2.28-127.el8.x86_64

Exécutons à nouveau gdb

[root@a3d3f47671d9 cui]# vim php7-4-test-zval.php
[root@a3d3f47671d9 cui]# gdb php
GNU gdb (GDB) Red Hat Enterprise Linux 8.2-12.el8
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from php...done.
(gdb)

en mode gdb commande b Vous pouvez définir des points d'arrêt, que vous pouvez comprendre comme xdebug de PHP

Vous souvenez-vous encore du contenu de notre fichier php7-4-test-zval.php

<?php
   $a="abcdefg";
   echo $a;
   $b=88;
   echo $b;
   $c = $a;
   echo $c;
   echo $a;
   $c ="abc";
   echo $c;
   echo $a;

Cette structure de langage d'écho est pour notre utilisation de débogage Voici une petite astuce

(ps La structure du langage dont je parle ici ne dit pas que echo est une fonction. Il y a une question d'entretien sur la principale différence entre echo() et var_dump( ) en php ?)

Cet écho En fait, c'est à nous de définir le point d'arrêt ZEND_ECHO_SPEC_CV_HANDLER

ZEND_ECHO_SPEC_CV_HANDLER est en fait une macro Cela sera expliqué en détail lors de l'analyse lexicale et de l'analyse syntaxique. exécuter, comme indiqué sur la figure

Analyser les variables dans le code source du noyau sous-jacent de PHP (3)

我们设置这个断点的意义是为了让程序在拼接echo 的时候暂停代码 以便我们分析

(gdb) b ZEND_ECHO_SPEC_CV_HANDLER
Breakpoint 1 at 0x6dfe80: file /cui/php-7.4.15/Zend/zend_vm_execute.h, line 36987.

在gdb中 使用 r 运行文件

(gdb) r php7-4-test-zval.php 
Starting program: /usr/local/bin/php php7-4-test-zval.php
warning: Error disabling address space randomization: Operation not permitted
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /cui/php-7.4.15/Zend/zend_vm_execute.h:36987
36987SAVE_OPLINE();

在gdb中 用 n 可以执行下一步操作

(gdb) n
36988z = EX_VAR(opline->op1.var);

这里我们暂且忽略继续往下走

ZEND_ECHO_SPEC_CV_HANDLER的完整代码如下(我贴出来只是想告诉你代码里有这行代码 让你知道为什么往下走,你现阶段不需要理解代码,慢慢来 )

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ECHO_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval *z;
SAVE_OPLINE();
//****************走到了此处**************
z = EX_VAR(opline->op1.var);
if (Z_TYPE_P(z) == IS_STRING) {
zend_string *str = Z_STR_P(z);
if (ZSTR_LEN(str) != 0) {
zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
}
} else {
zend_string *str = zval_get_string_func(z);
if (ZSTR_LEN(str) != 0) {
zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
} else if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(z) == IS_UNDEF)) {
ZVAL_UNDEFINED_OP1();
}
zend_string_release_ex(str, 0);
}
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
(gdb) n
441return pz->u1.v.type;
(gdb) n
36991zend_string *str = Z_STR_P(z);

这里到了关键位置 变量z出现了

gdb中 用p 查看变量

(gdb) p z
$1 = (zval *) 0x7f4235a13070

这是一个 zval 结构体的指针地址

(gdb) p *z
$2 = {
  value = {lval = 139922344256128, dval = 6.9130823382525114e-310, counted = 0x7f4235a02280, 
    str = 0x7f4235a02280, arr = 0x7f4235a02280, obj = 0x7f4235a02280, res = 0x7f4235a02280, ref = 0x7f4235a02280, 
    ast = 0x7f4235a02280, zv = 0x7f4235a02280, ptr = 0x7f4235a02280, ce = 0x7f4235a02280, func = 0x7f4235a02280, 
    ww = {w1 = 899687040, w2 = 32578}},
  u1 = {v = {type = 6 &#39;\006&#39;, type_flags = 0 &#39;\000&#39;, u = {extra = 0}}, 
    type_info = 6}, 
  u2 = {next = 0, cache_slot = 0, opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, 
    fe_iter_idx = 0, access_flags = 0, property_guard = 0, constant_flags = 0, extra = 0}}

看到这里应该很熟悉了 这就是源码里的 结构体 格式

再次复习下 zval

struct _zval_struct {
          zend_value        value;         //变量
          union {
                 struct {
                         ZEND_ENDIAN_LOHI_3(
                                  zend_uchar    type,  //变量类型           
                                  zend_uchar    type_flags,//可以用于变量的分类
                                  union {
                                          uint16_t  extra;        /* not further specified */
                                  } u)
                  } v;
                  uint32_t type_info;//变量类型
          } u1;
            u2;
  };

gdb中变量$2 中 u1.v.type=6 我们拿出第二节的 类型定义源码部分对比下

/* regular data types */
#define IS_UNDEF0
#define IS_NULL1
#define IS_FALSE2
#define IS_TRUE3
#define IS_LONG4
#define IS_DOUBLE5
#define IS_STRING6
#define IS_ARRAY7
#define IS_OBJECT8
#define IS_RESOURCE9
#define IS_REFERENCE10
.....
//其实有20种  剩下的不是常用类型 代码就不全部粘出来了
u1.v.type=6 类型是 IS_STRING

再看下 zval种 value 对应的 zend_value联合体中的代码

ypedef union _zend_value {
zend_long         lval;/* long value */
double            dval;/* double value */
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;

还记得联合体的特性吗 ? 所有值公用一个内存空间

上面的gdb中变量$2 的v.type=6 所以 在value中 值被str占用了 同时str 前面有个*

*星号 在C语言里代表指针 指向另外一个值的地址 所以指向 zend_string结构体

关于C语言指针您可以参考 菜鸟学院-指针

所以 接下来我们可以通过获取value中的str来获取 查看值

(gdb) p *z.value .str 
$4 = {gc = {refcount = 1, u = {type_info = 70}},
 h = 9223601495925209889, len = 7, val = "a"}

对比下 zend_string 源码

struct _zend_string {
zend_refcounted_h gc;//引用计数
zend_ulong        h;                /* hash value */
size_t            len;//字符串长度
char              val[1];
};

* 你可能有疑问 val为啥 是val=“a” 我们不是定义$a="abcdefg"; 吗 ? 还记得柔性数组吗?:)

接下来继续往下走

gdb中 用c 来执行到下一个断点处

(gdb) c
Continuing.
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /cui/php-7.4.15/Zend/zend_vm_execute.h:36987
36987SAVE_OPLINE();
(gdb) n
36988z = EX_VAR(opline->op1.var);
(gdb) n
441return pz->u1.v.type;
(gdb) n
36997zend_string *str = zval_get_string_func(z);
(gdb) p *z
$6 = {
  value = {lval = 88, dval = 4.3477776834029696e-322, counted = 0x58, str = 0x58, arr = 0x58, obj = 0x58, 
    res = 0x58, ref = 0x58, ast = 0x58, zv = 0x58, ptr = 0x58, ce = 0x58, func = 0x58, ww = {w1 = 88, w2 = 0}}, 
  u1 = {v = {type = 4 '\004', type_flags = 0 '\000', u = {extra = 0}}, type_info = 4}, 
  u2 = {next = 0, 
    cache_slot = 0, opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, 
    property_guard = 0, constant_flags = 0, extra = 0}}

u1.v.type=4 对应的是IS_LONG 代表整型 所以 在value中 值被lval占用了

可以看到值就是88 (lval不是指针 无需再跟进去查看了)

至此 我们用gdb 结合之前所看的核心源码 亲自实战了 PHP的zval

下一节我们继续 进行写时复制 的gdb跟踪

看完此文 希望你务必也用gdb调试下 深度体会zval的巧妙之处

感谢陈雷前辈的《PHP7源码底层设计与实现》

▏本文经原作者PHP崔雪峰同意,发布在php中文网,原文地址:https://zhuanlan.zhihu.com/p/353173325

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer