Introducción a las funciones básicas de valgrind e instrucciones sobre cómo utilizarlo.

Introducción a las funciones básicas de valgrind e instrucciones sobre cómo utilizarlo.

Introducción a las funciones básicas de valgrind e instrucciones de uso básicas_Cómo usar valgrind_HNU Latecomer's Blog-CSDN Blog

El efecto de copia no es bueno, lea el texto original.

1. Descripción general de Valgrind
Valgrind es una colección de herramientas de simulación y depuración de código abierto (GPL V2) en Linux.
Valgrind consta de un núcleo y otras herramientas de depuración basadas en el núcleo. El kernel es similar a un marco, que simula un entorno de CPU y proporciona servicios a otras herramientas; otras herramientas son similares a los complementos y utilizan los servicios proporcionados por el kernel para completar varias tareas específicas de depuración de memoria.



2. Dirección de referencia de instalación y descarga de herramientas : https://www.valgrind.org/downloads/Installation
:

1. tar –xf valgrind-3.17.0.tar.bz2
2. cd valgrind-3.17.0
3. ./configure // Ejecute el script de configuración para generar un archivo MAKE. Puede usar --help para ver los elementos de configuración y configúrelos según sea necesario, como modificar las herramientas de compilación, modificar la ruta de instalación, etc.
4. make
5. make install //La instalación genera un archivo ejecutable. La ruta del archivo ejecutable se especifica mediante el parámetro --prefix. necesita agregar variables de entorno a PATH; si no agrega el parámetro --prefix Especifique, use solo la configuración predeterminada, se asociará automáticamente con
1
2
3
4
5.
Puede usarlo después de la instalación:
valgrind --help to mira cómo usarlo.

3. Usar opciones básicas
3.1 Introducción a las herramientas básicas
(1) Memcheck. Esta es la herramienta más utilizada de valgrind. Es un verificador de memoria pesado que puede detectar la mayor parte del uso incorrecto de la memoria en el desarrollo, como el uso de memoria no inicializada, el uso de memoria que se ha liberado, el acceso a la memoria fuera de límites, etc. Esta es también la parte en la que se centrará este artículo.
(2) Llamar a la rutina. Se utiliza principalmente para comprobar problemas que ocurren durante las llamadas a funciones en el programa.
(3) Molienda de caché. Se utiliza principalmente para comprobar problemas con el uso de la caché en los programas.
(4) Helgrind. Se utiliza principalmente para comprobar problemas de competencia que ocurren en programas multiproceso.
(5) Macizo. Se utiliza principalmente para comprobar problemas que ocurren en el uso de la pila en el programa.
(6) Prórroga. Puede utilizar las funciones proporcionadas por el núcleo para escribir herramientas de depuración de memoria específicas usted mismo.

3.2 Opciones de uso común
(1) Aplicable a todas las herramientas Valgrind
–tool=<nombre> Las opciones más utilizadas. Ejecute la herramienta denominada nombreherramienta en valgrind. Comprobación de memoria predeterminada.
-h --help Muestra información de ayuda.
–version muestra la versión del kernel valgrind, cada herramienta tiene su propia versión.
-q --quiet Ejecuta silenciosamente, imprimiendo sólo mensajes de error.
-v --verbose Información más detallada, aumenta las estadísticas de recuento de errores.
–trace-children=no|yes ¿Rastrear subprocesos secundarios? [no]
–track-fds=no|yes ¿Rastrear descripciones de archivos abiertos? [no]
–time-stamp=no|yes ¿Agregar marca de tiempo a la información de LOG? [no]
–log-fd=< número > Enviar LOG al archivo descriptor [2=stderr]
–log-file=< archivo > Generará la información se escribe en el archivo nombre de archivo.PID. PID es el ID del proceso del programa en ejecución
. –log-file-exactly=<archivo> Envía la información de LOG al archivo
. –log-file-qualifier=<VAR>Obtiene el valor de la variable de entorno.El nombre del archivo utilizado como información de salida. [ninguno]
–log-socket=ipaddr:puerto Salida LOG al socket, ipaddr:puerto

(2) Salida de información de registro
–xml=yes Muestra la información en formato xml, solo memcheck está disponible
–num-callers=<número> muestra <número> personas que llaman en los seguimientos de la pila [12]
–error-limit=no|yes Si también Si hay varios errores, ¿dejar de mostrar nuevos errores? [yes]
–error-exitcode=< número > Si se encuentra un error, devuelve el código de error [0=disable]
–db-attach=no|yes Cuando ocurre un error, valgrind iniciará automáticamente el depurador gdb. [no]
–db-command=< comando > Opción de línea de comando para iniciar el depurador [gdb -nw %f %p]

(3) Opciones relacionadas aplicables a la herramienta Memcheck:
–leak-check=no|summary|full ¿Solicitar información detallada sobre la fuga? [resumen]
–leak-solving=low|med|high ¿cuánto BT se fusiona en la verificación de fugas? [bajo]
–show-reachable=no|yes ¿mostrar bloques alcanzables en la verificación de fugas? [no]
Para obtener información de uso más detallada, consulte el archivo de ayuda, el manual de usuario o el sitio web oficial: http://valgrind.org/docs/manual/manual- núcleo .html

(4) Nota:
① Valgrind no verificará automáticamente cada línea de código en el programa, sino que solo verificará la rama de código que se ejecuta, por lo que las pruebas unitarias o los casos de prueba funcionales son muy importantes; ② Valgrind puede considerarse como
una caja de arena y ejecutar a través de valgrind El programa en realidad se ejecuta en la zona de pruebas de valgrind, así que no pruebe el rendimiento, se sentirá decepcionado. Se recomienda realizar solo pruebas funcionales
③ Al compilar el código, se recomienda agregar -g -o0 opción, no use las opciones -o1, -o2

3.3 常用选项示例
–tool=< nombre > : use la herramienta Valgrind llamada < nombre > [memcheck]
–log-file=< archivo > : registre mensajes en < archivo >

Ejemplo:

valgrind --tool=memcheck --log-file=log.txt --leak-check=yes ./test
1
Descripción: use la herramienta memcheck para verificar si el programa de prueba tiene pérdidas de memoria y guarde el registro en log.txt

4. Introducción a la herramienta Memcheck
Memcheck es la herramienta más utilizada en valgrind y puede detectar la mayor parte del uso incorrecto de la memoria en el desarrollo. Esta herramienta puede verificar principalmente los siguientes errores
(1) Uso de memoria no inicializada
(2) Usar memoria que ha sido liberada (Leer/escribir memoria después de haber sido liberada)
(3) Usar más que la asignación malloc (Leer/escribir) el final de los bloques malloc'd)
(4) Acceso ilegal a la pila (lectura/escritura de áreas inapropiadas en la pila)
(5) Si se ha liberado el espacio aplicado (pérdidas de memoria: donde los punteros a los bloques malloc'd se pierden para siempre )
(6) Malloc/free/new/delete aplica y libera memoria (Uso no coincidente de malloc/new/new [] vs free/delete/delete []) (7) src y dst superpuestos (superposición de punteros src y dst
en memcpy() y funciones relacionadas)

#include<iostream>
int main()
{     int *pInt;     std::cout<<"Usar memoria no inicializada";     int a=*pInt; //Usar memoria no inicializada } 1 2 3 4 5 6 7 # include<iostream> int main() {     int *pArray=(int *)malloc(sizeof(int) *5);     std::cout<<"Usar memoria liberada";     free(pArray);     pArray[0]=0; //Usar la memoria liberada } 1 2 3 4 5 6 7 8 #include<iostream> int main() { int *pArray=(int *)malloc(sizeof (     int) *5);































    std::cout<<"Usa más espacio de memoria que el asignado por malloc";
    pArray[5]=5; //Usa más espacio de memoria que el asignado por malloc
    free(pArray);
}
1
2
3
4
5
6
7
8
#include<iostream >
int main()
{     int *pArray=(int *)malloc(sizeof(int) *5);     std::cout<<"malloc carece de libertad"; } 1 2 3 4 5 6 #include<iostream> int main () {     char a[10];     for (char c=0; c < sizeof(a); c++)     {         a[c]=c;     }     std::cout<<"El src y dst copiados se superponen";


















    memcpy(&a[4],&a[0],6);
}
1
2
3
4
5
6
7
8
9
10
11Nota
: el programa a veces se aplica a muchos nodos residentes y estos nodos no publicados no deben considerarse un problema;
generalmente como Mientras el programa se ejecuta, malloc o nuevas operaciones que hacen que el nodo aumente en una dirección se consideran pérdidas de memoria.

4.1 Ejemplo 1:
Código fuente:

#include<iostream>
int main()
{     int *pArray=(int *)malloc(sizeof(int) *5);     std::cout<<"Usar más espacio de memoria del asignado por malloc";     pArray[5]=5 ; //Utiliza más espacio de memoria que el asignado por malloc     free(pArray); } 1 2 3 4 5 6 7 8 Compile:













g++ test1.cpp -g -o test1_g //-g: deja que la herramienta memcheck obtenga el número de línea específico del error
1
Depuración:

valgrind --leak-check=yes --log-file=1_g ./test1_g
1
genera el archivo de registro 1_g:

(1) El número de proceso del programa actual (./test1_g)
(2) La descripción de la licencia de la herramienta valgrind memcheck (3)
Cómo se ejecuta el cargador
(4) El número de proceso principal, el proceso del terminal actual
(5) El mensaje de error detectado
(6) Resumen y resumen de la pila. En este ejemplo, hay un total de dos asignaciones y dos liberaciones, y no hay pérdida de memoria. (
7) La cantidad de errores detectados, aquí hay 1 mensaje

4.2 Ejemplo 2
#include<iostream>
int main()
{     int *pArray=(int *)malloc(sizeof(int) *5);     std::cout<<"Usar memoria liberada";     free(pArray);     pArray[ 0]=0; //Usa la memoria liberada } 1 2 3 4 5 6 7 8 Compilar:













g++ test7.cpp -g -o test7_g //-g: deja que la herramienta memcheck obtenga el número de línea específico del error
1
Depuración:

valgrind --leak-check=yes --log-file=7_g ./test7_g
1
genera el archivo de registro 7_g:

(1) Debido a que todavía se usa el mismo terminal, el proceso principal sigue siendo 8248
(2) Hay dos errores de lectura y escritura ilegales.

Compilar:

g++ test7.cpp -g -o test2_g_O2 -O2 
1
depuración:

valgrind --leak-check=yes --log-file=7_g_O2 ./test7_g_O2
1
Generar archivo de registro 7_g_O2:

Puede ver el mismo programa. Después de agregar -O2, la declaración pArray[0]=0; se optimiza, por lo que no se detecta.
Para lograr una detección más estricta, debe asegurarse de que el compilador no optimice al compilar, es decir, el nivel de optimización es -O0. Gcc y g++ usan -O0 de forma predeterminada, pero la mayoría de los diseños reales agregarán -O1 o - O2 a los parámetros Makefile, por lo que es mejor comprobarlo.

4.3 Resumen del formato general del documento de salida del informe Memcheck

copyright Declaración de derechos de autor
Informe de lectura y escritura anormales
(1) Lectura y escritura anormales del subproceso principal Informe de lectura
y escritura anormales del subproceso A Informe de lectura
y escritura anormales del subproceso B
(2) Otros subprocesos
(3) Informe de pérdida de memoria del montón
Descripción general del uso de la memoria del montón (RESUMEN DEL HEAP)
Convencido Informe de pérdida de memoria (definitivamente perdido)
Informe de operación de memoria sospechosa (show-reachable=no off, abierto: –show-reachable=yes)
Resumen de fugas (RESUMEN DE FUGAS)

4.4 Formato básico del informe de registro de Memcheck

{Descripción del problema}
en {dirección, nombre de función, módulo o línea de código}
por {dirección, nombre de función, línea de código}
por... {Mostrar la pila de llamadas capa por capa}
Dirección 0x??? {Describe la relación relativa de direcciones}

4.5 7 tipos de errores incluidos en memcheck
(1) errores de lectura/escritura ilegales
solicitan información: [lectura no válida de tamaño 4]
(2) uso de valores no inicializados
solicitan información: [El salto o movimiento condicional depende del valor no inicializado]
( 3) uso de valores no inicializados o no direccionables en llamadas al sistema
información de solicitud: [syscall param write(buf) apunta a bytes no inicializados] (4) información de solicitud de
liberaciones ilegales : [free inválido()] (5) cuando un bloque de montón es liberado con una función de desasignación inapropiada que solicita información: [No coincide free()/delete/delete[]] (6) bloques de origen y destino superpuestos solicita información: [el origen y el destino se superponen en memcpy(,)] (7) detección de pérdida de memoria ① puntero de memoria aún accesible todavía Cuando todavía existe la posibilidad de usar o liberar, se sale de la memoria dinámica a la que apunta el puntero antes de liberarse.









Definitivamente pérdida de memoria perdida, ya no se puede acceder a esta memoria
③ Perdida indirecta
Todos los punteros que apuntan a la memoria están ubicados en la pérdida de memoria
④ Posible
pérdida de memoria, todavía hay un puntero que puede acceder rápidamente a un determinado bloque de memoria, pero el puntero El punto señalado ya no es la primera ubicación en la memoria.

4.6 Principio de la herramienta memcheck
Memcheck implementa una CPU simulada. El programa monitoreado es interpretado y ejecutado por la CPU simulada. La CPU simulada puede detectar la legalidad de la dirección y la legalidad de la operación de lectura cuando ocurren todas las instrucciones de lectura y escritura de la memoria.

La clave de la capacidad de Memcheck para detectar problemas de memoria es que crea dos tablas globales.
(1) Tabla de valores válidos:
para cada byte en todo el espacio de direcciones del proceso, hay 8 bits correspondientes; para cada registro de la CPU, también hay un vector de bits correspondiente. Estos bits son responsables de registrar si el valor del byte o del registro tiene un valor inicializado válido.
(2) Tabla de direcciones válidas
Para cada byte en todo el espacio de direcciones del proceso, hay un bit correspondiente, que es responsable de registrar si la dirección se puede leer o escribir.
Principio de detección:
cuando desee leer o escribir un byte en la memoria, primero verifique el bit A correspondiente a este byte. Si el bit A muestra que la ubicación no es válida, memcheck informa un error de lectura y escritura.
El núcleo es similar a un entorno de CPU virtual, por lo que cuando un determinado byte de la memoria se carga en la CPU real, el bit V correspondiente al byte también se carga en el entorno de CPU virtual. Una vez que el valor en el registro se usa para generar una dirección de memoria, o el valor puede afectar la salida del programa, memcheck verificará los bits V correspondientes. Si el valor no se ha inicializado, se informará un error de memoria no inicializada.

En pocas palabras:
(1) ¿Cómo saber qué direcciones son legales (se ha asignado memoria)?
Mantenga una tabla de direcciones válida (bits de dirección válida (A)), en la que todas las direcciones que actualmente se pueden leer y escribir (asignar) legalmente tengan entradas correspondientes. Esta tabla se mantiene a través de las siguientes medidas:
① Datos globales (datos, sección bss): marcados como una dirección legal cuando se inicia el programa
② Variables locales: monitorean los cambios en sp (puntero de pila) y mantienen dinámicamente
③ Memoria asignada dinámicamente: interceptan la asignación / Llamadas para liberar memoria: malloc, calloc, realloc, valloc, memalign, free, new, new[], eliminar y eliminar[] ④
Llamada al sistema: intercepta la dirección asignada por mmap
⑤ Otros: puede mostrar e informar a memcheck si hay cierto campo es legal (2) ¿Cómo
saber si se ha asignado una determinada memoria?
① Mantenga una tabla de valores válidos (bits de valor válido (V)) para indicar si al bit correspondiente se le ha asignado un valor. Debido a que la CPU virtual puede capturar todas las instrucciones de escritura en la memoria, esta tabla es fácil de mantener.

5. La herramienta Callgrind presenta
la herramienta de análisis de rendimiento Callgrind, que no requiere opciones especiales al compilar el código fuente. Callgrind utiliza la información estadística Ir de cachegrind (lecturas en caché, es decir, el número de veces que se ejecuta una instrucción) para contar las llamadas a funciones en el programa, establecer un gráfico de relación de llamadas a funciones y realizar una simulación de caché de forma selectiva. Al final de la ejecución, escribirá los datos del análisis en un archivo y callgrind_annotate puede convertir el contenido de este archivo en un formato legible.

5.1 Operaciones básicas del análisis de texto de Callgrind
Ejemplo:
(1) cd linux/bin
(2) valgrind --tool=callgrind ./Devtest
genera un archivo: callgrind.out.27439
o
valgrind --tool=callgrind --separate-threads= sí ./Devtest
genera tres archivos: callgrind.out.1234 (vacío), callgrind.out.1234-01 (hilo 1), callgrind.out.1234-02 (hilo 2)
(3) callgrind_annotate callgrind.out.27439 > log
callgrind_annotate puede convertir el contenido del archivo callgrind.out.pid en un formato legible y redirigirlo al archivo de registro. Abra los archivos callgrind.out.pid y de registro respectivamente, y encontrará sus diferencias (callgrind.out.pid Es un formato que no es fácil de entender directamente para los humanos (callgrind_annotate es equivalente a una traducción que muestra callgrind.out.pid de la forma que queramos
).
El archivo de registro generado por callgrind_annotate analizando callgrind.out.pid tiene el siguiente contenido después de abrirlo:

Puede ver la biblioteca dinámica a la que pertenece cada función. El número de instrucciones consumidas por la llamada a la función se ordena de mayor a menor de forma predeterminada.
callgrind_annotate también tiene varios parámetros opcionales:
① --inclusive=yes: no solo cuenta los tiempos de ejecución de cada declaración por separado, sino que también calcula la relación de llamada. Por ejemplo, si la función foo llama a bar, entonces el costo de bar se agregará a el costo de foo. .
② --tree=both: muestra la relación de llamada.
③ --auto=yes: asociará automáticamente información estadística con el código fuente. Mostrará el código fuente de cada función y mostrará el costo de ejecución de cada declaración al frente
(4) Puede asociar archivos individuales:
callgrind_annotate callgrind.out.9441 main.c | grep -v "???"
Nota: El Las llamadas con el prefijo "???" son todas llamadas de bajo nivel a la biblioteca del sistema, no son importantes y pueden filtrarse mediante grep -v.

5.2 Operaciones básicas del análisis del diagrama de flujo de Callgrind:
tomando como ejemplo el "código de muestra proporcionado por el sitio web oficial" en el lado derecho del proyecto, será más intuitivo: (
1) gcc –g test.c -o test
( 2) valgrind --tool=callgrind ./test
generación Un archivo: callgrind.out.pid
(3) python gprof2dot.py -f callgrind -n10 -s callgrind.out.[pid] > valgrind.dot
(4) punto - Tpng valgrind.dot -o valgrind.png
(5) Abra la imagen y se dice que puede conocer la distribución del consumo de tiempo de ejecución de un vistazo.


6. Introducción a la herramienta Cachegrind
6.1 Introducción básica
(1) Los sistemas informáticos de perfilador (perfilador) basados ​​en Cachegrind Valgrind se están volviendo cada vez más complejos, la creación de perfiles de sistemas de almacenamiento es a menudo un cuello de botella en el sistema y es necesario perfilar la caché (2) Función ①
Simular
L1 , Caché L2
② Analizar el comportamiento de la caché, tiempos de ejecución, tasa de fallas, etc. ③ Analizar (3) funciones
según archivos, funciones, líneas de código e instrucciones de ensamblaje ① Análisis detallado de la caché para encontrar cuellos de botella en el programa ② Instrucciones para mejorar los programas y mejorar la ejecución eficiencia ③ Simulador de caché basado en seguimiento (4) Ventajas ① Fácil de usar, no es necesario volver a compilar ② Analiza todo el código ejecutado, incluidas las bibliotecas ③ Sin restricciones de idioma ④ Relativamente rápido ⑤ Flexible, simula diferentes configuraciones de caché









6.2 Pasos de uso
(1) valgrind --tool=cachegrind ./test

Al mismo tiempo, se genera el archivo cachegrind.out.pid
(2) callgrind_annotate cachegrind.out.4599 | grep -v “???”

Al igual que callgrind, también se puede traducir a información legible a través de callgrind_annotate. Puede ver
el estado de aciertos de la caché I1 (caché de instrucciones), la caché D1 (caché de datos) y la caché LL (caché pública de segundo nivel).

7. Introducción a la herramienta Massif
Massif es una herramienta de análisis de memoria. El propósito de monitorear la asignación de memoria del programa se logra tomando instantáneas continuamente del montón del programa.

7.1 Ejemplo
(1) g++ test.cc -o test
(2) valgrind --tool=massif ./test obtendrá un archivo macizo: massif.out.pid
(3) Utilice ms_print para analizar el archivo de salida: ms_print massif.out .pid
(4) Vea los cambios de memoria de la pila a través de instantáneas gráficas:


8. Introducción a la herramienta Helgrind
Helgrind es una función clave de Valgrind. Esta sección se centra principalmente en detectar problemas de seguridad básicos relacionados con subprocesos múltiples.
① Acceso inseguro a recursos
② Problema de interbloqueo
③ Uso incorrecto de la API pthreads POSIX
④ Si los conceptos básicos anteriores son seguros y están libres de errores, los programas multiproceso deben poder minimizar los bloques de sincronización tanto como sea posible

8.1 Acceso inseguro a los recursos de Helgrind
Problema resuelto:
​Problema 1: se puede resolver bien llamando a Helgrind. Tome el programa básico de la derecha como ejemplo.

#incluir <pthread.h>

int var = 0;
void* child_fn (void* arg)
{    var++;    devolver NULO; } int main (void) {     pthread_t niño;     pthread_t niño2;     pthread_create(&child,NULL, child_fn, NULL);     pthread_create(&child2,NULL,child_fn,NULL);     pthread_join(niño,NULL);     pthread_join(niño2,NULL);     devolver 0; }







 


 


 

1
2
3
4 5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 20 21 Obviamente var es un acceso compartido inseguro, llama a Helgrind para ver cómo se puede detectar:
​​gcc -g test.c -o test –lpthread valgrind --tool=helgrind ./test Después de ejecutar helgrind, se generarán los siguientes resultados: en el mensaje de información, puede ver que hay dos errores, preferencia de la variable global val.






Pregunta 2:
El problema del interbloqueo debe evitarse tanto como sea posible. Helgrind puede detectar el problema del interbloqueo causado por problemas con la secuencia de bloqueo y desbloqueo. Podemos analizar este problema más de cerca: https://blog.csdn.net/ sfwtoms11/article/details /38438253
Veamos la situación de agregar 2 bloqueos consecutivos

#incluir <pthread.h>

pthread_mutex_t mut_thread;
int var = 0;
void* child_fn ( void* arg )
{     pthread_mutex_lock(&mut_thread);     var++;     pthread_mutex_lock(&mut_thread);     devolver NULO; }




int principal (vacío)
{     pthread_t niño;     pthread_t niño2;     pthread_mutex_init(&mut_thread,NULL);     pthread_create(&child,NULL, child_fn, NULL);     pthread_create(&child2,NULL,child_fn,NULL);     pthread_join(niño,NULL);     pthread_join(niño2,NULL);     devolver 0; }








1
2
3
4 5 6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Problemas causados ​​por mutex y orden de desbloqueo:

#incluir <pthread.h>

pthread_mutex_t mut_thread;
pthread_mutex_t mut_thread1;
int var = 0;
void* child_fn ( void* arg ) {     pthread_mutex_lock(&mut_thread);     pthread_mutex_lock(&mut_thread1);     var++;     pthread_mutex_unlock(&mut_thread);     pthread_mutex_unlock(&mut_thread1);     devolver NULO; } void* child_fn1(void *arg) {     pthread_mutex_lock(&mut_thread1);     pthread_mutex_lock(&mut_thread);     var++;     pthread_mutex_unlock(&mut_thread1);     pthread_mutex_unlock(&mut_thread);     devolver NULO; }















int principal (vacío) {     pthread_t hijo;     pthread_t niño2;     pthread_mutex_init(&mut_thread,NULL);     pthread_mutex_init(&mut_thread1,NULL);     pthread_create(&child,NULL, child_fn, NULL);     pthread_create(&child2,NULL,child_fn1,NULL);     pthread_join(niño,NULL);     pthread_join(niño2,NULL);     devolver 0; }









——————————————
Declaración de derechos de autor: este artículo es un artículo original del blogger de CSDN "HNU Latecomer" y sigue el acuerdo de derechos de autor CC 4.0 BY-SA. Adjunte el enlace de la fuente original y esta copia. al reimprimir la declaración.
Enlace original: https://blog.csdn.net/weixin_45518728/article/details/119865117

Supongo que te gusta

Origin blog.csdn.net/thanklife/article/details/130992946
Recomendado
Clasificación