rig análisis de código fuente extendido

rig análisis de código fuente extendido

diagrama de flujo

Inserte la descripción de la imagen aquí

Puede preguntar qué es rig. Rig significa el santo patrón en la mitología nórdica. Rig es un middleware de monitoreo importante para php-fpm, que juega un papel fundamental en el monitoreo diario y los indicadores de datos de informes.

Rig se integra en el uso diario en forma de extensión en el uso de php. Analicemos el proceso de implementación de rig.

¿Cómo analizar cómo se hace una extensión php? Primero debe observar lo que se hace en algunas funciones de gancho en el ciclo de vida de zend

Como analizar global_init, rinit, minit, etc.

Primero mire lo que hace la función de inicialización del módulo minit

PHP_MINIT_FUNCTION (rig) {


	ZEND_INIT_MODULE_GLOBALS(rig, php_rig_init_globals, NULL);
	//data_register_hashtable();
	REGISTER_INI_ENTRIES();
	/* If you have INI entries, uncomment these lines
	*/
	if (RIG_G(enable)) {
        if (strcasecmp("cli", sapi_module.name) == 0 && cli_debug == 0) {
            return SUCCESS;
        }

        // 用户自定义函数执行器(php脚本定义的类、函数)
        ori_execute_ex = zend_execute_ex;
        zend_execute_ex = rig_execute_ex;

        // 内部函数执行器(c语言定义的类、函数)
//        ori_execute_internal = zend_execute_internal;
//        zend_execute_internal = rig_execute_internal;

		// bind curl
		zend_function *old_function;
		if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_exec", sizeof("curl_exec") - 1)) != NULL) {
			orig_curl_exec = old_function->internal_function.handler;
			old_function->internal_function.handler = rig_curl_exec_handler;
		}


        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_setopt", sizeof("curl_setopt")-1)) != NULL) {
            orig_curl_setopt = old_function->internal_function.handler;
            old_function->internal_function.handler = rig_curl_setopt_handler;
        }

        // 批量设置URL和相应的选项
        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_setopt_array", sizeof("curl_setopt_array")-1)) != NULL) {
            orig_curl_setopt_array = old_function->internal_function.handler;
            old_function->internal_function.handler = rig_curl_setopt_array_handler;
        }

        //关闭 cURL 会话并且释放所有资源。cURL 句柄 ch 也会被删除。
        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_close", sizeof("curl_close")-1)) != NULL) {
            orig_curl_close = old_function->internal_function.handler;
            old_function->internal_function.handler = rig_curl_close_handler;
        }
	}

	return SUCCESS;
}

Se inicializa la variable global php_rig_init_globals. Si rig está activado en el alcance global y solo se puede ejecutar bajo fpm, podemos ver una operación muy crítica, desconectando la ejecución de la máquina virtual zend, desconectando la función de ejecución zend Core zend_execute

// 用户自定义函数执行器(php脚本定义的类、函数)
    ori_execute_ex = zend_execute_ex;
    zend_execute_ex = rig_execute_ex;

Entonces, todavía tenemos que ver qué se hace exactamente en rig_execute_ex. De hecho, vi esta tecnología por primera vez en xhprof, ya no es una tecnología nueva

ZEND_API void rig_execute_ex(zend_execute_data *execute_data) {
    if (application_instance == 0 || rig_enable==0) {
        ori_execute_ex(execute_data);
        return;
    }

    zend_function *zf = execute_data->func;
    const char *class_name = (zf->common.scope != NULL && zf->common.scope->name != NULL) ? ZSTR_VAL(
            zf->common.scope->name) : NULL;
    const char *function_name = zf->common.function_name == NULL ? NULL : ZSTR_VAL(zf->common.function_name);


    // char *str =NULL;
    // spprintf(&str, 0, "php 拦截点:%s://%s", class_name, function_name);
    // test_log(str);


    if (class_name != NULL) {
        if (strcmp(class_name, "Monolog\\Logger") == 0 && ( strcmp(function_name, "info") == 0 || strcmp(function_name, "warn") == 0 || strcmp(function_name, "debug") == 0)) {
            //params
            zval *p = ZEND_CALL_ARG(execute_data, 1);
            zend_string *pstr=zval_get_string(p);
            if(pstr!=NULL && startsWith(ZSTR_VAL(pstr),log_metrics_prefix)==0){
                save_metrics_log(ZSTR_VAL(pstr));
            }
       } else if (strcmp(class_name, "Psr\\Log\\AbstractLogger") == 0 && ( strcmp(function_name, "info") == 0 || strcmp(function_name, "warn") == 0 || strcmp(function_name, "debug") == 0)) {
            // params
            zval *p = ZEND_CALL_ARG(execute_data, 1);
            zend_string *pstr = zval_get_string(p);
            if(pstr!=NULL && startsWith(ZSTR_VAL(pstr),log_metrics_prefix)==0){
                save_metrics_log(ZSTR_VAL(pstr));
            }
       }
    }
    ori_execute_ex(execute_data);
}

Obtenga aquí el nombre y la función de la clase que se está ejecutando actualmente. Si se considera que es el nombre de la clase Monolog \ Logger, o el nombre de la función, como info, warn, debug, etc., obtenga el contenido del protocolo pasado por la llamada y luego llamar a save_metrics_log, lo que significa que la mayoría La idea central básica es que cada vez que la máquina virtual zend ejecuta zend_execute_ex, volverá a juzgar si hay una llamada a Monolog \ Logger o Psr \ Log \ AbstractLogger, y luego llamar a save_metrics_log para informes de datos.

Bueno, hemos leído parte del código fuente de la máquina virtual hook, y luego seguimos mirando hacia abajo

	// bind curl
	zend_function *old_function;
	if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_exec", sizeof("curl_exec") - 1)) != NULL) {
		orig_curl_exec = old_function->internal_function.handler;
		old_function->internal_function.handler = rig_curl_exec_handler;
	}


        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_setopt", sizeof("curl_setopt")-1)) != NULL) {
            orig_curl_setopt = old_function->internal_function.handler;
            old_function->internal_function.handler = rig_curl_setopt_handler;
        }

        // 批量设置URL和相应的选项
        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_setopt_array", sizeof("curl_setopt_array")-1)) != NULL) {
            orig_curl_setopt_array = old_function->internal_function.handler;
            old_function->internal_function.handler = rig_curl_setopt_array_handler;
        }

        //关闭 cURL 会话并且释放所有资源。cURL 句柄 ch 也会被删除。
        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_close", sizeof("curl_close")-1)) != NULL) {
            orig_curl_close = old_function->internal_function.handler;
            old_function->internal_function.handler = rig_curl_close_handler;
        }

Esto es en realidad runtime de curl on hook. Por supuesto, estas tecnologías no son nuevas tecnologías durante mucho tiempo. También usé esta tecnología en la extensión sff en ese entonces.

Mira el primer lugar


		zend_function *old_function;
		if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_exec", sizeof("curl_exec") - 1)) != NULL) {
			orig_curl_exec = old_function->internal_function.handler;
			old_function->internal_function.handler = rig_curl_exec_handler;
		}

Es demasiado obvio que no hablaré de enganchar old_function-> internal_function.handler directamente. Cualquiera que haya conocido la máquina virtual zend sabe que el proceso de ejecución de la máquina virtual zend es un gran ciclo while. Los manejadores se ejecutan uno por uno desde el principio, y deberíamos analizar principalmente 4 Qué se hace en hook rig_curl_exec_handler, rig_curl_setopt_handler, rig_curl_setopt_array_handler, rig_curl_close_handler

Primero echemos un vistazo a rig_curl_exec_handler

void rig_curl_exec_handler(INTERNAL_FUNCTION_PARAMETERS)
{

    if(application_instance == 0 || rig_enable==0) {
        orig_curl_exec(INTERNAL_FUNCTION_PARAM_PASSTHRU);
        return;
    }

	zval  *zid;
	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zid) == FAILURE) {
		return;
	}

	int is_send = 1;

    zval function_name,curlInfo;
    zval params[1];
    ZVAL_COPY(&params[0], zid);
    ZVAL_STRING(&function_name,  "curl_getinfo");
    call_user_function(CG(function_table), NULL, &function_name, &curlInfo, 1, params);
    zval_dtor(&function_name);
    zval_dtor(&params[0]);

    zval *z_url = zend_hash_str_find(Z_ARRVAL(curlInfo),  ZEND_STRL("url"));
    if(z_url==NULL || strlen(Z_STRVAL_P(z_url)) <= 0) {
        zval_dtor(&curlInfo);
        is_send = 0;
    }

    char *url_str = Z_STRVAL_P(z_url);
    php_url *url_info = NULL;
    if(is_send == 1) {
        url_info = php_url_parse(url_str);
        if(url_info->scheme == NULL || url_info->host == NULL) {
            zval_dtor(&curlInfo);
            php_url_free(url_info);
            is_send = 0;
        }
    }

    char *peer = NULL;
    char *full_url = NULL;


    if (is_send == 1) {

// for php7.3.0+
#if PHP_VERSION_ID >= 70300
        char *php_url_scheme = ZSTR_VAL(url_info->scheme);
        char *php_url_host = ZSTR_VAL(url_info->host);
        char *php_url_path = ZSTR_VAL(url_info->path);
        char *php_url_query = ZSTR_VAL(url_info->query);
#else
        char *php_url_scheme = url_info->scheme;
        char *php_url_host = url_info->host;
        char *php_url_path = url_info->path;
        char *php_url_query = url_info->query;
#endif

        int peer_port = 0;
        if (url_info->port) {
            peer_port = url_info->port;
        } else {
            if (strcasecmp("http", php_url_scheme) == 0) {
                peer_port = 80;
            } else {
                peer_port = 443;
            }
        }

        if (url_info->query) {
            if (url_info->path == NULL) {
                spprintf(&full_url, 0, "%s?%s", "/", php_url_query);
            } else {
                spprintf(&full_url, 0, "%s?%s", php_url_path, php_url_query);
            }
        } else {
            if (url_info->path == NULL) {
                spprintf(&full_url, 0, "%s", "/");
            } else {
                spprintf(&full_url, 0, "%s", php_url_path);
            }
        }

        spprintf(&peer, 0, "%s:%d", php_url_host, peer_port);
    }

    zval curl_upstream;
    array_init(&curl_upstream);

    add_assoc_long(&curl_upstream, "application_instance", application_instance);
   // add_assoc_stringl(&curl_upstream, "uuid", application_uuid, strlen(application_uuid));
    add_assoc_long(&curl_upstream, "pid", getppid());
    add_assoc_long(&curl_upstream, "application_id", application_id);
    add_assoc_string(&curl_upstream, "version", RIG_G(version));
    add_assoc_bool(&curl_upstream, "isEntry", 0);
	//SKY_ADD_ASSOC_ZVAL(&curl_upstream, "body");

    zval curl_upstream_body;
    array_init(&curl_upstream_body);

    char *l_millisecond;
    long millisecond;
    if(is_send == 1) {
        l_millisecond = get_millisecond();
        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
        efree(l_millisecond);
        add_assoc_long(&curl_upstream_body, "startTime", millisecond);
    }

	 orig_curl_exec(execute_data, return_value);
	 if(return_value!=NULL){
         zend_string *result_string;
         result_string = zval_get_string(return_value);
         if(result_string!=NULL){
             add_assoc_long(&curl_upstream_body, "responseSize", ZSTR_LEN(result_string));
             // zend_string_free(result_string);
         }
	 }

     zval *http_method = zend_hash_index_find(Z_ARRVAL_P(&RIG_G(curl_header)), Z_RES_HANDLE_P(zid));
     if( http_method == NULL){
        add_assoc_string(&curl_upstream_body, "method", "GET");
     }else{
        add_assoc_string(&curl_upstream_body, "method", Z_STRVAL_P(http_method));
     }

    if (is_send == 1) {
        zval function_name_1, curlInfo_1;
        zval params_1[1];
        ZVAL_COPY(&params_1[0], zid);
        ZVAL_STRING(&function_name_1, "curl_getinfo");
        call_user_function(CG(function_table), NULL, &function_name_1, &curlInfo_1, 1, params_1);
        zval_dtor(&params_1[0]);
        zval_dtor(&function_name_1);

        l_millisecond = get_millisecond();
        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
        efree(l_millisecond);

        zval *z_http_code;
        z_http_code = zend_hash_str_find(Z_ARRVAL(curlInfo_1), ZEND_STRL("http_code"));
        if(z_http_code!=NULL){
           add_assoc_long(&curl_upstream_body, "responseCode", Z_LVAL_P(z_http_code));
        }


        char *path = (char*)emalloc(strlen(full_url) + 5);
        bzero(path, strlen(full_url) + 5);

        int i;
        for(i = 0; i < strlen(full_url); i++) {
            if (full_url[i] == '?') {
                break;
            }
            path[i] = full_url[i];
        }
        path[i] = '\0';

        add_assoc_string(&curl_upstream_body, "path", path);
        efree(path);


        add_assoc_long(&curl_upstream_body, "endTime", millisecond);
        add_assoc_string(&curl_upstream_body, "peer", peer);
        add_assoc_zval(&curl_upstream, "body", &curl_upstream_body);

        write_log(rig_json_encode(&curl_upstream),1,1);

        zval *http_header = zend_hash_index_find(Z_ARRVAL_P(&RIG_G(curl_header)), Z_RES_HANDLE_P(zid));
        if (http_header != NULL) {
            zend_hash_index_del(Z_ARRVAL_P(&RIG_G(curl_header)), Z_RES_HANDLE_P(zid));
        }

        efree(peer);
        efree(full_url);

        php_url_free(url_info);
        zval_ptr_dtor(&curlInfo_1);
        zval_ptr_dtor(&curlInfo);

        zval_ptr_dtor(&curl_upstream);

    }
}

Obtener parámetros

	zval  *zid;
	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zid) == FAILURE) {
		return;
	}

	int is_send = 1;

Este parámetro es el parámetro de curl_exec, que es un identificador de tipo de recurso curl, y luego obtén este parámetro y llama a curl_getinfo para obtener información de curl, y luego empaqueta la información en un curl_upstream, comprímelo en json y haz una llamada write_log. Aquí de hecho tenemos una pregunta sobre la diferencia entre save_metrics_log y writelog, continuaremos mirando rig_curl_setopt_handler más adelante, por supuesto, para asegurarnos de que la ejecución normal del programa no se vea alterada, debemos llamar a orig_curl_exec (execute_data, return_value);

void rig_curl_setopt_handler(INTERNAL_FUNCTION_PARAMETERS) {
    if(application_instance == 0 || rig_enable==0) {
        orig_curl_setopt(INTERNAL_FUNCTION_PARAM_PASSTHRU);
        return;
    }

    zval *zid, *zvalue;
    zend_long options;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlz", &zid, &options, &zvalue) == FAILURE) {
        return;
    }


   if(zid !=NULL && options!=NULL){
        if(CURLOPT_CUSTOMREQUEST == options){
           if(zvalue!=NULL){
               add_index_string(&RIG_G(curl_header), Z_RES_HANDLE_P(zid), Z_STRVAL_P(zvalue));
           }
        }else{
            zend_long value = zvalue==NULL ? 0 :zval_get_long(zvalue);
                if(value == 0 ){
                    add_index_string(&RIG_G(curl_header), Z_RES_HANDLE_P(zid), "GET");
                }else{
                    if(CURLOPT_POST == options){
                        add_index_string(&RIG_G(curl_header), Z_RES_HANDLE_P(zid), "POST");
                    }else if(CURLOPT_HTTPGET == options){
                        add_index_string(&RIG_G(curl_header), Z_RES_HANDLE_P(zid), "GET");
                    }else if(CURLOPT_PUT == options){
                        add_index_string(&RIG_G(curl_header), Z_RES_HANDLE_P(zid), "PUT");
                    }else if(CURLOPT_HEADER == options){
                        add_index_string(&RIG_G(curl_header), Z_RES_HANDLE_P(zid), "HEADER");
                    }
                }
        }
    }

    orig_curl_setopt(INTERNAL_FUNCTION_PARAM_PASSTHRU);
    return;
}

Ponlo en la variable global RIG_G (curl_header), ¿de qué sirve ponerlo aquí? Tenemos que seguir mirando rig_curl_setopt_handler, rig_curl_setopt_array_handler es la función de gancho de curl_setopt, echemos un vistazo a su efecto

void rig_curl_setopt_array_handler(INTERNAL_FUNCTION_PARAMETERS) {

    if(application_instance == 0 || rig_enable==0) {
        orig_curl_setopt_array(INTERNAL_FUNCTION_PARAM_PASSTHRU);
        return;
    }

    zval *zid, *arr, *entry;
    zend_ulong option;
    zend_string *string_key;

    ZEND_PARSE_PARAMETERS_START(2, 2)
            Z_PARAM_RESOURCE(zid)
            Z_PARAM_ARRAY(arr)
    ZEND_PARSE_PARAMETERS_END();

  if( zend_hash_index_find(Z_ARRVAL_P(arr), CURLOPT_POST )!= NULL){
       add_index_string(&RIG_G(curl_header), Z_RES_HANDLE_P(zid), "POST");
    }else if( zend_hash_index_find(Z_ARRVAL_P(arr), CURLOPT_HTTPGET ) != NULL){
       add_index_string(&RIG_G(curl_header), Z_RES_HANDLE_P(zid), "GET");
    }else if( zend_hash_index_find(Z_ARRVAL_P(arr), CURLOPT_PUT) != NULL){
       add_index_string(&RIG_G(curl_header), Z_RES_HANDLE_P(zid), "PUT");
    }else if( zend_hash_index_find(Z_ARRVAL_P(arr), CURLOPT_HEADER) != NULL){
       add_index_string(&RIG_G(curl_header), Z_RES_HANDLE_P(zid), "HEADER");
    }else if( zend_hash_index_find(Z_ARRVAL_P(arr), CURLOPT_CUSTOMREQUEST) != NULL){
         zval *zvalue=zend_hash_index_find(Z_ARRVAL_P(arr), CURLOPT_CUSTOMREQUEST);
         if(zvalue!=NULL){
             add_index_string(&RIG_G(curl_header), Z_RES_HANDLE_P(zid), Z_STRVAL_P(zvalue));
         }

    }
    orig_curl_setopt_array(INTERNAL_FUNCTION_PARAM_PASSTHRU);
}

Es principalmente para registrar información clave en RIG_G (curl_header), y el gancho es principalmente para registrar los métodos de solicitud HTTP get, post, put, delete, etc.

RIG_G (curl_header) es principalmente para informar cuando se llama a curl_exec, es decir, cuando se llama a rig_curl_exec_handler

Veamos save_metrics_log nuevamente

void save_metrics_log(char *log){
    write_log(log,2,0);

}

Descubrí que en realidad está escrito en el registro, así que continúe con el seguimiento para leer el registro escrito.

static void write_log(char *text,int prefix, int isFree) {
    if (application_instance != 0  && rig_enable==1) {
        // to stream
        if(text == NULL || strlen(text) <= 0) {
            return;
        }

        struct sockaddr_un un;
        un.sun_family = AF_UNIX;
        strcpy(un.sun_path, RIG_G(sock_path));
        int fd;
        char *message = (char*) emalloc(strlen(text) + 10);
        bzero(message, strlen(text) + 10);

        fd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (fd >= 0) {
            struct timeval tv;
            tv.tv_sec = 0;
            tv.tv_usec = 100000;
            setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
            int conn = connect(fd, (struct sockaddr *) &un, sizeof(un));

            if (conn >= 0) {
                sprintf(message, "%d%s\n", prefix,text);
                write(fd, message, strlen(message));
                //test_log(message);
            }
            close(fd);
        }
        efree(message);
        if(isFree==1){
            efree(text);
        }

    }

}

De hecho, descubrí que este es el establecimiento de un envío de socket Unix en el pasado. De hecho, no estoy de acuerdo con este enfoque, porque cada llamada a Writelog crea y destruye descriptores con frecuencia, pero esto tiene la ventaja de que No es un problema pegar paquetes, y la dificultad de desarrollo se reducirá.

Una vez finalizada una serie de análisis MINIT, analicemos una ola de RINIT para ver qué hace la inicialización de la solicitud posterior a la extensión.

Solo mira las líneas centrales

	rig_register()
	
	
        request_init();

        php_output_handler *handler;
        handler = php_output_handler_create_internal("myext handler", sizeof("myext handler") -1, rig_output_handler, /* PHP_OUTPUT_HANDLER_DEFAULT_SIZE */ 2048, PHP_OUTPUT_HANDLER_STDFLAGS);
        php_output_handler_start(handler);

Inicialice la solicitud y abra el búfer de salida. Finalmente, veamos las funciones reportadas por los nodos en nuestro programa. Entre ellas se encuentra una función clave rig_register, que se usa para registrar rig

static int rig_register( ) {

        int instance_id=0;
        struct sockaddr_un un;
        un.sun_family = AF_UNIX;
        // rig.sock_path=/var/run/rig-agent.sock 通讯
        strcpy(un.sun_path, RIG_G(sock_path));
        int fd;
        char message[4096];
        char return_message[4096];

        fd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (fd >= 0) {
            struct timeval tv;
            tv.tv_sec = 0;
            tv.tv_usec = 100000;
            setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *) &tv, sizeof tv);
            int conn = connect(fd, (struct sockaddr *) &un, sizeof(un));

            if (conn >= 0) {
                bzero(message, sizeof(message));
                 char *uri = get_page_request_uri();
                sprintf(message, "0{\"app_code\":\"%s\",\"pid\":%d,\"version\":\"%s\",\"php_version\":\"%s\",\"url\":\"%s\"}\n", RIG_G(app_code),
                        getppid(), RIG_G(version),PHP_VERSION,uri);
                write(fd, message, strlen(message));

                bzero(return_message, sizeof(return_message));
                read(fd, return_message, sizeof(return_message));

                 if (uri != NULL) {
                       efree(uri);
                  }

                char *ids[10] = {0};
                int i = 0;
               // C 库函数 char *strtok(char *str, const char *delim) 分解字符串 str 为一组字符串,delim 为分隔符。
                char *p = strtok(return_message, ",");

                while (p != NULL) {
                    ids[i++] = p;
                    p = strtok(NULL, ",");
                }

                if (ids[0] != NULL && ids[1] != NULL && ids[2] != NULL) {
                    application_id = atoi(ids[0]);
                    instance_id = atoi(ids[1]);
                   // strncpy(application_uuid, ids[2], sizeof application_uuid - 1);
                }
            }

            close(fd);
        }

        return instance_id;

}

El programa de incrustación es demasiado simple, directamente desde la memoria, informe al equipo a través del socket de dominio

PHP_FUNCTION(rig_biz_metrics)
{

    zval *res=NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &res) == FAILURE) {
        return;
    }

   save_metrics_log(Z_STRVAL_P(res));

   RETURN_TRUE;
}

La última imagen concluye esta simple extensión.

Inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/qq_32783703/article/details/108908681
Recomendado
Clasificación