Notas de autocultivo del programador: Capítulo 11

Capítulo 11 Biblioteca de tiempo de ejecución
1. Los pasos típicos de ejecución de un programa son los siguientes:
después de que el sistema crea un proceso, entrega el control a la entrada del programa, y ​​esta entrada se denomina función de entrada o punto de entrada. Esta entrada suele ser una entrada en el biblioteca en tiempo de ejecución.función.
La función de entrada inicializa la biblioteca en tiempo de ejecución y el entorno de ejecución del programa, incluido el montón, IO, subprocesos, construcción de variables globales, etc.
Una vez que la función de entrada completa la inicialización, llame a la función principal para ejecutar formalmente la parte principal del programa.
Después de ejecutar la función principal, regresa a la función de entrada, que realiza trabajos de limpieza, incluida la destrucción de variables globales, destrucción del montón, cierre de IO, etc., y luego realiza una llamada al sistema (__exit) para finalizar el proceso.

2. Función de entrada de glibc
El punto de entrada del programa de glibc es _start (esta entrada está especificada por el script de enlace predeterminado de la conexión ld, y puede configurar su propia entrada a través de los parámetros relevantes). _start se implementa en ensamblaje y depende de la plataforma.
Luego se introduce la implementación _start de i386.
En Linux, el cargador inserta las variables de entorno y los parámetros del programa en la pila.
_start establece ebp en 0 y obtiene argc y argv. Llame a _libc_start_main, sus parámetros son:
dirección de la función principal,
argc,
argv,
init (trabajo de inicialización antes de la llamada principal),
fini (trabajo finalizado después de main),
rtld_fini (trabajo finalizado relacionado con la carga dinámica),
stack_end (es decir, parte inferior de la pila)
_libc_start_main establece el puntero de la variable de entorno y llama a una serie de funciones. Por ejemplo, verifique la versión del sistema operativo. Lo que es digno de atención es la función _cxa_atexit, que se usa para llamar a la función especificada por el parámetro después del final de main, por lo que fini y rtld_fini del parámetro se llaman después del final de main. Finalmente
resultado = main(argc,argv,__environ);
salida(resultado).
La función de salida atraviesa la lista enlazada de funciones (__exit_funcs) registrada por las funciones __cxa_atexit y atexit. Finalmente, se llama a _exit (implementación del ensamblado, dependiente de la plataforma), que llama a la llamada del sistema de salida. Se puede ver que ya sea que el programa llame explícitamente a la salida o salga normalmente, ingresará a la función de salida.
instrucciones hlt después de _start y _exit.
Hlt después de _exit es para detectar si la llamada al sistema AIDS es exitosa. Si falla, el programa no se detendrá y la instrucción hlt puede funcionar para forzar la detención del programa.
El hlt después de _start es para evitar que se llame a la función de salida. (Por ejemplo, la salida al final de _libc_start_main se eliminó accidentalmente).
3. Detalles de la biblioteca de tiempo de ejecución en Windows La función de entrada predeterminada del CRT de MSVC se llama mainCRTStartup (frente a crt0.c en 2003).
El proceso es el siguiente:
1. Inicializar las variables globales relacionadas con la versión del sistema operativo
2. Inicializar el montón.
3. Inicialice las E/S.
4. Obtenga parámetros de línea de comando y variables de entorno.
5. Inicialice algunos datos en la biblioteca C.
6. Llame a main y registre el valor de retorno.
7. Verifique si hay errores y devuelva el valor de retorno principal.

4. E/S de inicialización de MSVC
Los conceptos de operaciones de archivos en Linux y Windows son similares a los de Archivo, que se denominan descriptores de archivos y identificadores respectivamente.
La plataforma para manipular archivos en lenguaje C es la estructura de archivos, y la estructura de archivos debe contener fd.
La inicialización de la función de entrada de MSVC incluye la inicialización del montón y de E/S.
En condiciones de compilación de 32 bits, la inicialización del montón de MSVC solo llama a la API HeapCreate.
La inicialización de E/S es relativamente responsable.
La tabla de archivos abiertos en el espacio de usuario está representada por ioinfo, y es una matriz bidimensional simulada por una matriz de punteros (ioinfo __pioinfo [64] [32]), que puede acomodar un total de 2048 identificadores. Se utiliza una matriz porque ahorra espacio y no requiere Allocate 2048 ioinfo al mismo tiempo.
El _file del ARCHIVO de MSVC es fd, que indexa la tabla de archivos abiertos.
Primero inicialice una tabla de archivos abierta de segunda dimensión.
Luego
obtenga el identificador heredado del proceso principal de acuerdo con la API GetStartUpInfo
y cópielo en la tabla de archivos actualmente abierta (se puede asignar una tabla de archivos de segunda dimensión).
Luego inicialice la entrada y salida estándar.
El trabajo de inicialización de MSVC es el siguiente:
Cree una tabla de trabajo abierta,
si se hereda del proceso principal, cópiela del identificador del proceso principal a la tabla de archivos abiertos.
Inicialice la entrada y salida estándar.

5. La biblioteca de tiempo de ejecución del lenguaje C incluye aproximadamente las siguientes funciones:
inicio y salida: incluida la función de entrada, es decir, otras funciones de las que depende la función de entrada, etc.
E/S: inicialización de E/S.
Montón:
implementación del lenguaje de inicialización del montón.
depurar.
La biblioteca estándar del lenguaje C contiene funciones como:
entrada y salida estándar, operaciones de archivos, operaciones uniformes, operaciones de cadenas, funciones matemáticas, gestión de recursos, conversión de formato, etc.
Los parámetros de longitud variable se implementan según las convenciones de llamada del lenguaje C.

6. La biblioteca en tiempo de ejecución es una biblioteca en tiempo de ejecución del lenguaje C relacionada con la plataforma. Hasta cierto punto, es una capa de abstracción entre los programas en lenguaje C y las diferentes plataformas del sistema operativo.
glibc ha evolucionado con el tiempo y se ha convertido en la biblioteca estándar c en Linux.
La versión de lanzamiento de glibc se compone principalmente de archivos de encabezado, como stdio.h, stdlib.h, ubicados en /usr/include, la parte del archivo binario de la helibrary. La parte binaria es principalmente la biblioteca estándar del lenguaje C, que tiene Dos versiones estática y dinámica Versión. La dinámica se encuentra en /lib/libc.so.6, la estática se encuentra en /usr/lib/libc.a. Además de la biblioteca estándar de C, glibc también tiene varias bibliotecas de tiempo de ejecución para la ejecución de programas auxiliares. Son
/usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o
crt1.o, que contiene la función de entrada _start del programa.
Para satisfacer la construcción y destrucción de objetos globales en C. Las secciones .init y .fini se introducen en cada archivo objeto. La biblioteca en tiempo de ejecución garantiza que el código de estas dos secciones se ejecutará antes o después de la función principal. Al vincular, el vinculador recopilará .init y .fini en todos los archivos de objetos de entrada en orden, los fusionará y los enviará a las dos secciones correspondientes en el archivo de salida. Sin embargo, las instrucciones contenidas en los dos segmentos de salida también requieren algún código auxiliar para ayudarlas a comenzar, como calcular GOT, por lo que se introdujeron crti.o y crtn.o para ayudar a implementar la función de inicialización.
Se garantiza que las dos secciones de crti.o se ejecutarán al principio de las secciones correspondientes del archivo de salida y crtn.o al final. Por lo tanto, la secuencia del archivo de entrada del conector es generalmente:
ld crt1.o crti.o [objeto_usuario] [bibliotecas_sistema] crtn.o
Dado que crt1.o no contiene las secciones .init y .fini, no afectará el resultado final generado. dos El orden de los segmentos.
El constructor y el destructor del objeto global en C no se colocan directamente en estas dos secciones, pero todas las llamadas al constructor y al destructor se colocan dentro.
Además de la construcción y destrucción de objetos globales, estas dos secciones tienen otras funciones. Por ejemplo, a menudo se utilizan herramientas como programas de supervisión de usuarios y depuración de rendimiento para realizar cierta inicialización y desinicialización. También podemos usar " atributo ((sección(".init")))" para colocar la función en la sección .init. Pero normalmente aún destruirá su estructura, porque su instrucción de retorno hará que _init regrese antes. Se deben utilizar las instrucciones de montaje.

7. Archivos relacionados con la plataforma Gcc
Además de crt1.o crti.o crtn.o, al vincular hello.c en el Capítulo 4, también están
crtbeginT.o,
libgcc.a,
libgcc_eh.a y
crtend.o
, que son todo ubicado en el directorio de instalación de gcc
/usr/lib/gcc/i486-gnu/4.1.3/

crtbeginT.o y crtend.o realmente se utilizan para implementar la construcción y destrucción global de C++. Debido a que glibc es solo una biblioteca en tiempo de ejecución del lenguaje C, no comprende la implementación de C++ y gcc es el verdadero implementador de C++. Estos dos cooperan con glibc para implementar la construcción y destrucción global de C ++.
Dado que gcc admite múltiples plataformas y algunas 32 plataformas no admiten operaciones de tipo largo de 64 bits, se necesitan algunas rutinas auxiliares. libgcc.a contiene dichas funciones, así como operaciones de punto flotante. Y su versión vinculada dinámicamente se llama libgcc.so.
libgcc_eh.a contiene funciones relacionadas con la plataforma de manejo de excepciones compatibles con c++.

8.MSVC CRT parece más organizado que glibc. MSVC proporciona múltiples subversiones según diferentes atributos, como estático/dinámico; subproceso único/multiproceso; versión de depuración/versión de lanzamiento: c puro/soporte c++. Algunos de ellos se pueden combinar, otros no.
La versión estática de CRT se encuentra en /lib en el directorio de instalación de MSVC. Sus reglas de nomenclatura son:
libc [p] [mt] [d] .lib.
La versión de enlace dinámico incluye .lib para vincular y .dll para tiempo de ejecución. Tienen nombres similares a las versiones estáticas, pero incluyen un número de versión.
De forma predeterminada, si no especifica qué CRT al compilar y vincular, se seleccionará libcmt.lib de forma predeterminada.
MSVC proporciona bibliotecas estándar de C++ correspondientes adicionales, que solo incluyen la parte de C++. Cuando su programa incluye el archivo de encabezado de la biblioteca estándar de C++, el compilador MSVC guardará la información del enlace de la biblioteca estándar de C++ correspondiente en el .drectve del archivo de destino.

9. Biblioteca en tiempo de ejecución y subprocesos múltiples
Para la biblioteca estándar, las partes relacionadas con subprocesos no pertenecen a su contenido. Pero los crts convencionales tendrán el correspondiente contenido multiproceso. Por un lado, es la interfaz de operación de subprocesos múltiples y, por otro lado, la biblioteca en tiempo de ejecución de c debe poder ejecutarse normalmente en subprocesos múltiples.
1) problema de error
2) printf/fprintf
3) malloc/free
Para resolver el problema de la biblioteca estándar de C en subprocesos múltiples, muchos compiladores vienen con una versión de subprocesos múltiples de la biblioteca en tiempo de ejecución. En MSVC, utilice los parámetros /MT y /MTd para especificar el uso de una biblioteca de tiempo de ejecución multiproceso.

10. Mejora de CRT
: use TLS
para errno, y las direcciones devueltas por errno en diferentes subprocesos son diferentes.
Bloquee
malloc y printf
para mejorar los métodos de llamada de funciones.
Proporcione una versión segura para subprocesos de strtok, strtok_s
, pero en muchos casos este método no es factible. Un mejor enfoque sería no cambiar ninguno de los prototipos de la biblioteca estándar, simplemente mejorar su implementación.

11.Implementación de TLS.
Declaración de variable tls
MSVC:__declspec(thread) int number;
GCC:__thread int number
En Windows, la variable tls se coloca en la sección ".tls". Cada vez que se inicia un nuevo hilo, se asignará una parte de la memoria en el Se asignará el montón y la sección ".tls". El contenido se copia en este espacio.
Pero para C ++, no es tan simple como copiar: estos objetos deben inicializarse y destruirse uno por uno cuando sale el hilo.
Uno de los 16 elementos en el directorio de datos (DATADIRECTORY) del archivo PE almacena la dirección y la longitud de la tabla tls, que almacena las direcciones del constructor y destructor de todas las variables tls. Windows se inicia o sale cada vez que un hilo se basa en el tabla tls La variable tls se construye y destruye al mismo tiempo. Estas tablas suelen estar ubicadas en la sección ".rdata".
Entonces, ¿cómo accede cada hilo a las variables tls?
Para cada subproceso de Windows, habrá un TEB (bloque de entorno de subprocesos), que guarda la dirección de pila del subproceso, la ID del subproceso, etc. Uno de los campos es la matriz TLS, y su desplazamiento en el TEB es 0x2C, y el segmento al que apunta el registro FS para cada subproceso es el TEB, por lo que se puede acceder a la matriz TLS de un subproceso a través de FS:[0x2C] .
La matriz tls generalmente tiene 64 elementos. El primer elemento es la sección ".tls" del hilo. Junto con el desplazamiento de la variable en la sección ".tls", es la dirección del tls.
Display tls se utiliza para aplicar, asignar y destruir variables tls a través de la API de Windows. Debido a sus numerosas limitaciones, actualmente no se utiliza mucho.
En Windows, es mejor utilizar _beginthread() y _endthread() proporcionados por MSVC CRT para iniciar y salir de subprocesos. El uso de la API de Windows provocará una pérdida de memoria porque la estructura _tiddata aplicada en el montón a través de algunas funciones CRT como strtok() o _beginthread() no se puede destruir normalmente bajo un enlace estático (este problema ocurrirá cuando cada hilo se inicie durante el enlace dinámico). ) /Se libera el DllMain de cada dll que se debe llamar al salir).

12.Construcción y destrucción de objetos globales C++.
Construcción y destrucción global de glibc.
Como se mencionó anteriormente, el punto de entrada del programa glibc _start, uno de los parámetros pasados ​​a __libc_start_main es __libc_csu_init: llama
a la función _init(), y esta función es el .init del archivo ejecutable. Por Al desensamblar un archivo ejecutable, encontramos que en su sección .init se llamó a una función llamada __do_global_ctors_aux, que no pertenece a glibc, sino que proviene de un archivo de destino crtbegin.o proporcionado por gcc. Como se mencionó anteriormente, algunos de los archivos objeto finalmente vinculados por el vinculador provienen de gcc, y esas son funciones estrechamente relacionadas con el lenguaje. Vea su código fuente:
se llaman todas las funciones de una matriz de punteros de función denominada __CTOR_LIST_. Obviamente lo que guarda es el puntero constructor del objeto global.
Mire hacia atrás, cuando el compilador compila cada unidad de compilación (.cpp), genera una función especial, cuya función es inicializar el objeto global de este archivo.
No solo llama a la función de inicialización de cada objeto global, sino que también registra una función especial __tcf_1 con atexit.
Después de que el compilador genera esta función que inicializa el objeto global del archivo actual, coloca su puntero en la sección .ctor del archivo de destino.
De esta manera, el conector fusionará los segmentos con el mismo nombre al vincular estos archivos .o, y .ctor guardará los punteros de todas las funciones de inicialización globales. Las secciones .ctors de crtbegin.o y crtend.o también están vinculadas antes y después. crtbegin.o almacena un valor de cuatro palabras y define la dirección inicial de este valor como el símbolo __CTOR_LIST. El conector es responsable de vincular Complete el número de constructores globales. crtend.o simplemente guarda nulo y define el símbolo __CRT_END__.
destruir
En los primeros días de la destrucción de glibc, se utilizó casi el mismo método que la construcción mencionada anteriormente. Sin embargo, esto debe garantizar que el orden de construcción y destrucción de objetos globales sea exactamente el opuesto, lo que aumenta la carga de trabajo del conector, por lo que la función de devolución de llamada de salida del proceso se registra en la función de salida en __cxa_atexit para implementar la destrucción.
La función misteriosa __tcf_1 mencionada anteriormente es el destructor global opuesto al constructor global. El orden en que llama al destructor es opuesto al orden en que el constructor global llama al constructor.
Dado que la biblioteca en tiempo de ejecución completa la construcción y destrucción de objetos globales, las opciones "-nonstartfiles" o "-nostdlib" no se pueden utilizar durante la construcción. Los ensambladores y enlazadores de
algunas plataformas no admiten los mecanismos .init y .ctor. Para implementar main El código se ejecuta antes de la función. Al vincular, el programa Collect2 recopilará los símbolos especiales de todos los archivos .o. Estos símbolos indican que son constructores globales o se ejecutan antes de Main. Collect2 guarda las direcciones de estos símbolos. en una matriz y los almacena en un archivo temporal. El archivo .c se compila y vincula con otros archivos .o en el archivo de salida final.
En estas plataformas, el compilador gcc generará una llamada _main antes de main, que es responsable de las funciones recopiladas por Collect2. _main es parte del archivo .o proporcionado por gcc. Al usar -nostdlib, puede obtener un error de definición _main. En este caso, necesita vincularlo con -lgcc.

13. Construcción y destrucción global de MSVC CRT
Hay una llamada a _initterm (__xc_a, __xc_z) en la función de entrada mainCRTStartup de MSVC, y el contenido de initterm es llamar a funciones con estos dos punteros como límites izquierdo y derecho. Se puede ver que se ve exactamente igual que __do_global_ctors_aux.
typedef void (__cdel *_PVFV)();
_CRTALLOC(". Error de análisis CRT KaTeX: grupo esperado después de '_' en la posición 13: XCA") _PVFV _̲_xc_a[]={NULL};… XCA",long,read)
arriba El código muestra que define la sección .CRT, el grupo XCA en el archivo objeto y guarda la matriz de puntero de función __xc_a en este grupo de esta sección. Al compilar, cada unidad de compilación generará un archivo llamado grupo .CRT $XCU,
y agregue su propia función de inicialización global.
Al vincular, los segmentos se fusionarán y los grupos se organizarán en orden alfabético. Al final, estos segmentos a menudo se colocan en el segmento .rdata porque son de solo lectura y previamente se llaman Los dos punteros utilizados por _initterm son uno al principio y otro al final. Por lo tanto, es muy diferente de glibc
. En términos de destrucción, la función de salida del proceso también se registra con atexit en el constructor global. También es similar a glibc
14.
Archivo IO fread->fread_s->_fread_nolock_s->_read->ReadFile
Excepto el último que es la API de Windows, los anteriores son todas bibliotecas de tiempo de ejecución de MSVC.
fread simplemente llama a fread_s
fread_s agrega protección contra desbordamiento del búfer: bloqueo
_fread_nolock_s lectura en bucle, almacenamiento en búfer
_read conversión de nueva línea
ReadFile API de lectura de archivos de Windows

Supongo que te gusta

Origin blog.csdn.net/weixin_45719581/article/details/123207828
Recomendado
Clasificación