stringtranslate.com

Fallo de segmentación

En informática , una falla de segmentación (a menudo abreviada como segfault ) o violación de acceso es una falla o condición de falla, provocada por un hardware con protección de memoria , que notifica a un sistema operativo (SO) que el software ha intentado acceder a un área restringida de la memoria (un violación de acceso a la memoria). En computadoras x86 estándar , esta es una forma de falla de protección general . El núcleo del sistema operativo , en respuesta, generalmente realizará alguna acción correctiva, generalmente pasando la falla al proceso infractor enviándole una señal . En algunos casos, los procesos pueden instalar un controlador de señales personalizado, lo que les permite recuperarse por sí solos, [1] pero en otros casos se utiliza el controlador de señales predeterminado del sistema operativo, lo que generalmente provoca una terminación anormal del proceso (un bloqueo del programa ) y, a veces, un volcado del núcleo. .

Las fallas de segmentación son una clase común de error en programas escritos en lenguajes como C que brindan acceso a memoria de bajo nivel y pocas o ninguna verificación de seguridad. Surgen principalmente debido a errores en el uso de punteros para direccionar la memoria virtual , particularmente el acceso ilegal. Otro tipo de error de acceso a la memoria es un error de bus , que también tiene diversas causas, pero que hoy en día es mucho más raro; Estos ocurren principalmente debido a un direccionamiento incorrecto de la memoria física o debido a un acceso a la memoria desalineado; estas son referencias de memoria que el hardware no puede abordar, en lugar de referencias que un proceso no puede abordar.

Muchos lenguajes de programación tienen mecanismos diseñados para evitar fallas de segmentación y mejorar la seguridad de la memoria. Por ejemplo, Rust emplea un modelo basado en propiedad [2] para garantizar la seguridad de la memoria. [3] Otros lenguajes, como Lisp y Java , emplean recolección de basura , [4] lo que evita ciertas clases de errores de memoria que podrían conducir a fallas de segmentación. [5]

Descripción general

Ejemplo de señal generada por humanos.
Fallo de segmentación que afecta a Krita en el entorno de escritorio KDE
Una desreferencia de puntero nulo en Windows 8

Una falla de segmentación ocurre cuando un programa intenta acceder a una ubicación de memoria a la que no está permitido, o intenta acceder a una ubicación de memoria de una manera que no está permitida (por ejemplo, intentando escribir en una ubicación de solo lectura , o para sobrescribir parte del sistema operativo ).

El término "segmentación" tiene varios usos en informática; en el contexto de "fallo de segmentación", se refiere al espacio de direcciones de un programa. [6] Con protección de memoria, solo el espacio de direcciones del programa es legible, y de este, solo la pila y la porción de lectura/escritura del segmento de datos de un programa son escribibles, mientras que los datos de solo lectura asignados en el segmento constante y el segmento de código no se puede escribir. Por lo tanto, intentar leer fuera del espacio de direcciones del programa, o escribir en un segmento de solo lectura del espacio de direcciones, produce un error de segmentación, de ahí el nombre.

En los sistemas que utilizan la segmentación de memoria de hardware para proporcionar memoria virtual , se produce una falla de segmentación cuando el hardware detecta un intento de hacer referencia a un segmento inexistente, o de hacer referencia a una ubicación fuera de los límites de un segmento, o de hacer referencia a una ubicación en una moda no permitida por los permisos otorgados para ese segmento. En sistemas que utilizan solo paginación , un error de página no válida generalmente conduce a un error de segmentación, y tanto los errores de segmentación como los de página son errores generados por el sistema de administración de memoria virtual . Los errores de segmentación también pueden ocurrir independientemente de los errores de página: el acceso ilegal a una página válida es un error de segmentación, pero no un error de página no válida, y los errores de segmentación pueden ocurrir en medio de una página (por lo tanto, no hay error de página), por ejemplo en una Desbordamiento del búfer que permanece dentro de una página pero sobrescribe ilegalmente la memoria.

A nivel de hardware, la unidad de administración de memoria (MMU) genera inicialmente la falla en caso de acceso ilegal (si la memoria a la que se hace referencia existe), como parte de su función de protección de memoria, o una falla de página no válida (si la memoria a la que se hace referencia no existe). ). Si el problema no es una dirección lógica no válida sino una dirección física no válida, se genera un error de bus , aunque no siempre se distinguen.

A nivel del sistema operativo, se detecta esta falla y se pasa una señal al proceso infractor, activando el controlador del proceso para esa señal. Los diferentes sistemas operativos tienen diferentes nombres de señales para indicar que se ha producido un error de segmentación. En los sistemas operativos tipo Unix , se envía una señal llamada SIGSEGV (abreviada de violación de segmentación ) al proceso infractor. En Microsoft Windows , el proceso infractor recibe una excepción STATUS_ACCESS_VIOLATION .

Causas

Las condiciones bajo las cuales ocurren las violaciones de segmentación y cómo se manifiestan son específicas del hardware y del sistema operativo: diferentes hardware generan diferentes fallas para condiciones dadas, y diferentes sistemas operativos las convierten en diferentes señales que se transmiten a los procesos. La causa inmediata es una infracción de acceso a la memoria, mientras que la causa subyacente es generalmente un error de software de algún tipo. Determinar la causa raíz ( depurar el error) puede ser simple en algunos casos, donde el programa causará consistentemente un error de segmentación (por ejemplo, desreferenciar un puntero nulo ), mientras que en otros casos el error puede ser difícil de reproducir y depender de la asignación de memoria. en cada ejecución (por ejemplo, eliminar la referencia a un puntero colgante ).

Las siguientes son algunas causas típicas de una falla de segmentación:

Estos, a su vez, suelen ser causados ​​por errores de programación que provocan un acceso no válido a la memoria:

En el código C, las fallas de segmentación ocurren con mayor frecuencia debido a errores en el uso del puntero, particularmente en la asignación de memoria dinámica de C. Eliminar la referencia a un puntero nulo, lo que da como resultado un comportamiento indefinido , generalmente provocará un error de segmentación. Esto se debe a que un puntero nulo no puede ser una dirección de memoria válida. Por otro lado, los punteros salvajes y los punteros colgantes apuntan a una memoria que puede existir o no, y que puede ser legible o escribible o no, y por lo tanto pueden provocar errores transitorios. Por ejemplo:

carácter * p1 = NULO ; // Puntero nulo char * p2 ; // Puntero salvaje: no inicializado en absoluto. char * p3 = malloc ( 10 * tamaño de ( char )); // Puntero inicializado a la memoria asignada // (asumiendo que malloc no falló) free ( p3 ); // p3 ahora es un puntero colgante, ya que se ha liberado la memoria              

Eliminar la referencia a cualquiera de estas variables podría provocar un error de segmentación: eliminar la referencia al puntero nulo generalmente provocará un error de segmentación, mientras que la lectura del puntero salvaje puede generar datos aleatorios pero no un error de segmentación, y la lectura del puntero colgante puede generar datos válidos para un while, y luego datos aleatorios a medida que se sobrescriben.

Manejo

La acción predeterminada ante una falla de segmentación o un error de bus es la finalización anormal del proceso que lo desencadenó. Se puede generar un archivo principal para ayudar en la depuración y también se pueden realizar otras acciones dependientes de la plataforma. Por ejemplo, los sistemas Linux que utilizan el parche grsecurity pueden registrar señales SIGSEGV para monitorear posibles intentos de intrusión mediante desbordamientos de búfer .

En algunos sistemas, como Linux y Windows, es posible que el propio programa maneje un error de segmentación. [7] Dependiendo de la arquitectura y el sistema operativo, el programa en ejecución no solo puede manejar el evento, sino que también puede extraer información sobre su estado, como obtener un seguimiento de la pila , valores de registro del procesador , la línea del código fuente cuando se activó, memoria. dirección a la que se accedió de forma no válida [8] y si la acción fue de lectura o escritura. [9]

Aunque una falla de segmentación generalmente significa que el programa tiene un error que debe corregirse, también es posible causar dicha falla intencionalmente con fines de prueba, depuración y también para emular plataformas donde se necesita acceso directo a la memoria. En el último caso, el sistema debe poder permitir que el programa se ejecute incluso después de que ocurra la falla. En este caso, cuando el sistema lo permite, es posible manejar el evento e incrementar el contador del programa del procesador para "saltar" sobre la instrucción fallida para continuar la ejecución. [10]

Ejemplos

Fallo de segmentación en un teclado EMV

Escribir en memoria de solo lectura

Escribir en la memoria de sólo lectura genera un error de segmentación. En el nivel de errores de código, esto ocurre cuando el programa escribe en parte de su propio segmento de código o en la parte de solo lectura del segmento de datos , ya que el sistema operativo los carga en la memoria de solo lectura.

A continuación se muestra un ejemplo de código ANSI C que generalmente provocará un error de segmentación en plataformas con protección de memoria. Intenta modificar una cadena literal , que es un comportamiento indefinido según el estándar ANSI C. La mayoría de los compiladores no detectarán esto en el momento de la compilación y, en su lugar, lo compilarán en un código ejecutable que fallará:

int main ( void ) { char * s = "hola mundo" ; * s = 'H' ; }        

Cuando se compila el programa que contiene este código, la cadena "hola mundo" se coloca en la sección rodata del archivo ejecutable del programa : la sección de solo lectura del segmento de datos . Cuando se carga, el sistema operativo lo coloca con otras cadenas y datos constantes en un segmento de memoria de solo lectura. Cuando se ejecuta, se configura una variable, s , para que apunte a la ubicación de la cadena y se intenta escribir un carácter H a través de la variable en la memoria, lo que provoca un error de segmentación. La compilación de un programa de este tipo con un compilador que no verifica la asignación de ubicaciones de solo lectura en el momento de la compilación y su ejecución en un sistema operativo tipo Unix produce el siguiente error de ejecución :

$ gcc  segfault.c  -g  -o  segfault $ ./segfault Fallo de segmentación

Seguimiento del archivo principal de GDB :

Programa recibido señal SIGSEGV , Fallo de segmentación . 0x1c0005c2 en main () en segfault . c : 6 6 * s = 'H' ;             

Este código se puede corregir usando una matriz en lugar de un puntero de carácter, ya que esto asigna memoria en la pila y la inicializa con el valor del literal de cadena:

char s [] = "hola mundo" ; s [ 0 ] = 'H' ; // equivalentemente, *s = 'H';      

Aunque los literales de cadena no deben modificarse (esto tiene un comportamiento indefinido en el estándar C), en C son de static char []tipo, [11] [12] [13] por lo que no hay una conversión implícita en el código original (que apunta char *a esa matriz), mientras que en C++ son de static const char []tipo y, por lo tanto, hay una conversión implícita, por lo que los compiladores generalmente detectarán este error en particular.

Desreferencia de puntero nulo

En los lenguajes C y similares a C, los punteros nulos se utilizan para significar "puntero a ningún objeto" y como indicador de error, y desreferenciar un puntero nulo (una lectura o escritura a través de un puntero nulo) es un error de programa muy común. El estándar C no dice que el puntero nulo sea el mismo que el puntero a la dirección de memoria  0, aunque ese puede ser el caso en la práctica. La mayoría de los sistemas operativos asignan la dirección del puntero nulo de modo que acceder a él provoca un error de segmentación. Este comportamiento no está garantizado por el estándar C. Desreferenciar un puntero nulo es un comportamiento indefinido en C, y una implementación conforme puede asumir que cualquier puntero al que se le desreferencia no es nulo.

int * ptr = NULL ; printf ( "%d" , * ptr );    

Este código de muestra crea un puntero nulo y luego intenta acceder a su valor (leer el valor). Hacerlo provoca un error de segmentación en tiempo de ejecución en muchos sistemas operativos.

Desreferenciar un puntero nulo y luego asignarlo (escribir un valor en un objetivo inexistente) también suele causar un error de segmentación:

int * ptr = NULO ; * ptr = 1 ;     

El siguiente código incluye una desreferencia de puntero nulo, pero cuando se compila no suele generar un error de segmentación, ya que el valor no se utiliza y, por lo tanto, la desreferencia a menudo se optimiza mediante la eliminación del código inactivo :

int * ptr = NULL ; * ptr ;   

Desbordamiento del búfer

El siguiente código accede a la matriz de caracteres smás allá de su límite superior. Dependiendo del compilador y del procesador, esto puede provocar un error de segmentación.

char s [] = "hola mundo" ; carbón c = s [ 20 ];      

Desbordamiento de pila

Otro ejemplo es la recursividad sin un caso base:

int principal ( vacío ) { retorno principal (); }   

lo que hace que la pila se desborde, lo que resulta en una falla de segmentación. [14] La recursividad infinita puede no necesariamente resultar en un desbordamiento de pila dependiendo del lenguaje, las optimizaciones realizadas por el compilador y la estructura exacta de un código. En este caso, el comportamiento del código inalcanzable (la declaración de retorno) no está definido, por lo que el compilador puede eliminarlo y utilizar una optimización de llamada final que podría dar como resultado que no se utilice la pila. Otras optimizaciones podrían incluir traducir la recursividad a iteración, lo que, dada la estructura de la función de ejemplo, daría como resultado que el programa se ejecutara para siempre, sin desbordar probablemente su pila.

Ver también

Referencias

  1. ^ Programación experta en C: secretos profundos de C Por Peter Van der Linden, página 188
  2. ^ "El lenguaje de programación Rust: propiedad".
  3. ^ "Simultaneidad intrépida con Rust: el blog del lenguaje de programación Rust".
  4. ^ McCarthy, John (abril de 1960). "Funciones recursivas de expresiones simbólicas y su cálculo por máquina, Parte I". Comunicaciones de la ACM . 4 (3): 184-195. doi : 10.1145/367177.367199 . S2CID  1489409 . Consultado el 22 de septiembre de 2018 .
  5. ^ Dhurjati, Dinakar; Kowshik, Sumant; Adve, Vikram; Lattner, Chris (1 de enero de 2003). "Seguridad de la memoria sin comprobaciones de tiempo de ejecución ni recolección de basura" (PDF) . Actas de la conferencia ACM SIGPLAN de 2003 sobre lenguaje, compilador y herramienta para sistemas integrados . vol. 38. ACM. págs. 69–80. doi :10.1145/780732.780743. ISBN 1581136471. S2CID  1459540 . Consultado el 22 de septiembre de 2018 .
  6. ^ "Depuración de errores de segmentación y problemas de puntero - Cprogramming.com". www.cprogramming.com . Consultado el 3 de febrero de 2021 .
  7. ^ "Recuperación limpia de Segfaults en Windows y Linux (32 bits, x86)" . Consultado el 23 de agosto de 2020 .
  8. ^ "Implementación del controlador SIGSEGV/SIGABRT que imprime el seguimiento de la pila de depuración". GitHub . Consultado el 23 de agosto de 2020 .
  9. ^ "¿Cómo identificar operaciones de lectura o escritura de fallas de página cuando se usa el controlador de sigaction en SIGSEGV? (LINUX)" . Consultado el 23 de agosto de 2020 .
  10. ^ "LINUX - MANEJADORES DE FALLOS DE ESCRITURA". 12 de noviembre de 2017 . Consultado el 23 de agosto de 2020 .
  11. ^ "6.1.4 Literales de cadena". ISO/IEC 9899:1990 - Lenguajes de programación - C .
  12. ^ "6.4.5 Literales de cadena". ISO/IEC 9899:1999 - Lenguajes de programación - C .
  13. ^ "6.4.5 Literales de cadena". ISO/IEC 9899:2011 - Lenguajes de programación - C.
  14. ^ "¿Cuál es la diferencia entre un error de segmentación y un desbordamiento de pila?". Desbordamiento de pila . Consultado el 11 de noviembre de 2023 .

enlaces externos