Fallo de segmentación

Ajustar Compartir Imprimir Citar
Fallo por computadora causado por el acceso a la memoria restringida

En informática, una falla de segmentación (a menudo abreviada como falla de segmento) o infracción de acceso es una falla o condición de falla provocada por el hardware. con protección de memoria, notificando a un sistema operativo (SO) que el software ha intentado acceder a un área restringida de la memoria (una violación de acceso a la memoria). En las computadoras x86 estándar, esta es una forma de falla de protección general. El kernel 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, pero de lo contrario, 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 la memoria de bajo nivel y pocas o ninguna verificación de seguridad. Surgen principalmente debido a errores en el uso de punteros para el direccionamiento de la memoria virtual, en particular el acceso ilegal. Otro tipo de error de acceso a la memoria es un error de bus, que también tiene varias causas, pero 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 de que un proceso no es permitido abordar.

Muchos lenguajes de programación pueden emplear mecanismos diseñados para evitar errores de segmentación y mejorar la seguridad de la memoria. Por ejemplo, Rust emplea un modelo basado en la propiedad para garantizar la seguridad de la memoria. Otros lenguajes, como Lisp y Java, emplean recolección de elementos no utilizados, lo que evita ciertas clases de errores de memoria que podrían provocar fallas de segmentación.

Resumen

Ejemplo de señal generada humana
Fallo por segmento que afecta a Krita en el entorno de escritorio KDE
Una dereferencia puntero nula en Windows 8

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

El término "segmentación" tiene varios usos en informática; en el contexto de "falla de segmentación", un término utilizado desde la década de 1950, se refiere al espacio de direcciones de un programa. Con protección de memoria, solo el propio programa el espacio de direcciones es legible y, de este, solo se puede escribir en la pila y la parte de lectura/escritura del segmento de datos de un programa, mientras que los datos de solo lectura y el segmento de código no se pueden 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, da como resultado una falla de segmentación, de ahí el nombre.

En los sistemas que utilizan segmentación de memoria de hardware para proporcionar memoria virtual, se produce un error 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 de una manera no permitida por los permisos otorgados para ese segmento. En los sistemas que solo usan paginación, una falla de página no válida generalmente conduce a una falla de segmentación, y las fallas de segmentación y las fallas de página son fallas generadas por el sistema de administración de memoria virtual. Las fallas de segmentación también pueden ocurrir independientemente de las fallas de página: el acceso ilegal a una página válida es una falla de segmentación, pero no una falla de página no válida, y las fallas de segmentación pueden ocurrir en medio de una página (por lo tanto, no hay falla de página), por ejemplo en un desbordamiento de 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 existe la memoria a la que se hace referencia), 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, esta falla se detecta y se transmite una señal al proceso infractor, lo que activa 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 similares a Unix, se envía una señal llamada SIGSEGV (abreviado 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 en las que se producen las infracciones de segmentación y cómo se manifiestan son específicas del hardware y del sistema operativo: cada hardware genera diferentes errores para determinadas condiciones y cada sistema operativo los convierte en señales diferentes que se transmiten a los procesos. La causa próxima es una violació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á constantemente una falla de segmentación (por ejemplo, eliminar la referencia de un puntero nulo), mientras que en otros casos el error puede ser difícil de reproducir y depende de la asignación de memoria. en cada ejecución (por ejemplo, desreferenciando un puntero colgante).

Las siguientes son algunas de las causas típicas de un error de segmentación:

Estos, a su vez, a menudo son causados por errores de programación que dan como resultado 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 C. La desreferenciación de un puntero nulo, 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 la memoria que puede o no existir, y puede o no ser legible o escribible y, por lo tanto, puede generar errores transitorios. Por ejemplo:

char *p1 = NULL; // Null pointerchar *p2; // Punto salvaje: no inicializado en absoluto.char *p3 = malloc()10 * tamaño()char)); // Indicador inicializado para la memoria asignada // (suponiendo que el malloc no falló)gratis()p3); // p3 es ahora un puntero brillante, ya que la memoria ha sido liberada

La desreferenciación de cualquiera de estas variables podría causar un error de segmentación: la desreferenciación del puntero nulo generalmente provocará un error de segmentación, mientras que la lectura desde el puntero salvaje puede dar como resultado datos aleatorios pero no un error de segmentación, y la lectura desde el puntero colgante puede dar como resultado datos válidos. datos durante un tiempo, y luego datos aleatorios a medida que se sobrescriben.

Manejo

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

En algunos sistemas, como Linux y Windows, es posible que el propio programa maneje una falla de segmentación. Según 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, los valores de registro del procesador, la línea del código fuente cuando se activó, la dirección de memoria que fue accedido inválidamente y si la acción fue una lectura o una escritura.

Aunque una falla de segmentación generalmente significa que el programa tiene un error que necesita 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 este último caso, el sistema debe permitir que el programa se ejecute incluso después de que ocurra la falla. En este caso, cuando el sistema lo permita, es posible manejar el evento e incrementar el contador del programa del procesador a "saltar" sobre la instrucción fallida para continuar con la ejecución.

Ejemplos

Fallo de segmentación en un teclado EMV

Escribir en la memoria de solo lectura

Escribir en la memoria de solo lectura genera un error de segmentación. A 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.

Este es un ejemplo de código ANSI C que generalmente causará una falla de segmentación en plataformas con protección de memoria. Intenta modificar un literal de cadena, 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 principal()vacío){} 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 el memoria, provocando un fallo de segmentación. Compilar un programa de este tipo con un compilador que no comprueba la asignación de ubicaciones de solo lectura en tiempo de compilación y ejecutarlo en un sistema operativo similar a Unix produce el siguiente error de tiempo de ejecución:

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

Retroceso del archivo central de GDB:

Programa recibidos señal de señal SIGSEGV, Segmentation culpa.0x1c0005c2 dentro principal () a segfault.c:66 *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 de la cadena literal:

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 del tipo static char [], por lo que no hay una conversión implícita en el código original (que apunta un char * a esa matriz), mientras que en C++ son del tipo static const char [] y, por lo tanto, hay una conversión implícita, por lo que los compiladores generalmente detectar este error en particular.

Desreferencia de puntero nulo

En lenguajes C y similares a C, los punteros nulos se utilizan para indicar "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 provoque un error de segmentación. Este comportamiento no está garantizado por el estándar C. Eliminar la referencia de un puntero nulo es un comportamiento indefinido en C, y una implementación conforme puede asumir que cualquier puntero al que se le haya quitado la referencia 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). Si lo hace, provoca una falla de segmentación en tiempo de ejecución en muchos sistemas operativos.

Eliminar la referencia de un puntero nulo y luego asignarle (escribir un valor en un destino inexistente) también suele provocar un error de segmentación:

int *ptr = NULL;*ptr = 1;

El siguiente código incluye una desreferencia de puntero nulo, pero cuando se compila a menudo no resultará en una falla de segmentación, ya que el valor no se usa y, por lo tanto, la desreferencia a menudo se optimizará eliminando el código inactivo:

int *ptr = NULL;*ptr;

Desbordamiento de búfer

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

char s[] = "hola mundo";char 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 da como resultado un error de segmentación. La recursividad infinita puede no resultar necesariamente en un desbordamiento de pila según el idioma, 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 devolución) no está definido, por lo que el compilador puede eliminarlo y usar una optimización de llamada final que podría resultar en que no se use la pila. Otras optimizaciones podrían incluir la traducción de la recurrencia en iteración, lo que, dada la estructura de la función de ejemplo, daría como resultado que el programa se ejecute para siempre, aunque probablemente no desborde su pila.