¿Qué son los enlazadores y cargadores? ¿Cómo trabajan?

8

Estoy tratando de entender mejor cosas como enlaces y cargadores.

¿A qué área de informática pertenecen? ¿Compilador, sistema operativo, arquitectura de computadora?

¿Dónde entran en juego los enlazadores y cargadores durante el desarrollo?

Nishant
fuente
Edité esta pregunta para tratar más sobre qué son los enlazadores y cargadores que sobre otros lugares para aprender sobre ellos, ya que este sitio es el lugar adecuado para ese tipo de cosas. Si hay buenos recursos por ahí, déjelos emerger naturalmente a través de respuestas en lugar de simplemente tratar de construir una lista de recursos externos.
Adam Lear
99
Sin ofender, pero asegúrese de leer los artículos de Wikipedia, ya que pueden ayudar a enfocar su pregunta: en.wikipedia.org/wiki/Linker_%28computing%29 y en.wikipedia.org/wiki/Loader_%28computing%29
Eric Wilson
No estoy seguro de por qué rechacé el voto. Pensé que esta pregunta también podría ayudar a otros novatos que querían saber esto, junto con que yo obtuve alguna orientación. ¿Es esta una pregunta no programada?
Nishant
2
Pude ver los votos negativos dado que esta pregunta es bastante amplia y no muestra mucho trabajo realizado para descubrir la respuesta por sí mismo.
JB King

Respuestas:

17

La relación exacta varía un poco. Para empezar, consideraré (casi) el modelo más simple posible, utilizado por algo como MS-DOS, donde un ejecutable siempre estará vinculado estáticamente. Por ejemplo, consideremos el canónico "¡Hola, mundo!" programa, que asumiremos que está escrito en C.

Compilador

El compilador compilará esto en un par de piezas. Tomará el literal de cadena "¡Hola, Mundo!" Y lo colocará en una sección marcada como datos constantes, y sintetizará un nombre para esa cadena en particular (por ejemplo, "$ L1"). Compilará la llamada a printfotra sección que está marcada como código. En este caso, dirá que el nombre es main(o, con frecuencia _main). También tendrá algo que decir que este fragmento de código tiene una longitud de N bytes y (lo que es más importante) contiene una llamada al printfdesplazamiento M en ese código.

Linker

Una vez que el compilador haya terminado de producir eso, se ejecutará el enlazador. Normalmente se considera parte de la cadena de herramientas de desarrollo (aunque hay excepciones: MS-DOS solía incluir un vinculador, aunque rara vez se usaba). Aunque normalmente no es visible externamente, normalmente se le pasarán algunos argumentos de línea de comandos, uno que especifica un archivo de objeto que contiene algún código de inicio y otro que especifica cualquier archivo que contenga la biblioteca estándar C.

El enlazador luego mirará el archivo objeto que contiene el código de inicio y descubrirá que tiene, digamos, 1112 bytes de longitud, y que tiene una llamada al _maindesplazamiento 784 en eso.

Basado en eso, comenzará a construir una tabla de símbolos. Tendrá una entrada que diga ".startup" (o cualquier nombre) tiene una longitud de 1112 bytes, y (hasta ahora) nada se refiere a ese nombre. Tendrá otra entrada que dice "printf" es una longitud desconocida actual, pero se hace referencia desde ".startup + 784".

Luego buscará a través de la biblioteca (o bibliotecas) especificada para tratar de encontrar definiciones de los nombres en la tabla de símbolos que actualmente no están definidas, en este caso printf. Encontrará el archivo de objeto para printf que dice que tiene 4087 bytes de largo y tiene referencias a otras rutinas para hacer cosas como convertir un int en una cadena, así como cosas como putchar(o tal vez fputc) escribir la cadena resultante en la salida archivo.

El enlazador volverá a escanear para tratar de encontrar definiciones de esos símbolos, de forma recursiva, hasta que llegue a una de dos conclusiones: o encuentra definiciones de todos los símbolos, o bien hay un símbolo para el que no puede encontrar una definición.

Si se encuentra una referencia pero no una definición, se detendrá y emitirá un mensaje de error que generalmente dice algo sobre un "XXX externo indefinido", y dependerá de usted averiguar qué otra biblioteca o archivo de objetos necesita vincular .

Si encuentra definiciones de todos los símbolos, pasa a la siguiente fase: recorre la lista de lugares que hacen referencia a cada símbolo, y completará la dirección donde se guardó ese símbolo en la memoria, entonces (por ejemplo ) donde se llama el código de inicio main, completará la dirección 1112como la dirección de main. Una vez que haya hecho todo eso, escribirá todo el código y los datos en un archivo ejecutable.

Hay algunos otros detalles menores que probablemente deben mencionarse: por lo general, mantendrá el código y los datos separados, y después de que cada uno esté completo, los reunirá a todos en (más o menos) direcciones consecutivas (por ejemplo, todas las piezas de código, luego todos los datos). Por lo general, también habrá algunas reglas sobre cómo combinar definiciones para secciones / segmentos; por ejemplo, si diferentes archivos de objetos tienen segmentos de código, solo organizará los fragmentos de código uno tras otro. Si se definen dos o más literales de cadena idénticos (u otras constantes), normalmente los combinará para que todos se refieran al mismo lugar. También hay algunas reglas sobre qué hacer cuando / si encuentra definiciones duplicadas del mismo símbolo. En un caso típico, esto simplemente será un error. En algunos casos, tendrá cosas como "si alguien más también lo define, no lo considere un error, solo use esa definición en lugar de esta.

Una vez que tiene entradas para todos los símbolos, el enlazador tiene que organizar las "piezas" y asignarles direcciones. El orden en el que organiza las piezas variará un poco; por lo general, tendrá algunos indicadores sobre los tipos de piezas diferentes, por lo que (por ejemplo) todos los datos constantes terminan uno al lado del otro, todas las piezas de código al lado de entre sí y así sucesivamente. Sin embargo, en nuestro sistema simple similar a MS-DOS, la mayor parte de esto no importará mucho.

Cargador

Eso nos lleva a la siguiente fase: el cargador. el cargador normalmente es parte del sistema operativo, que carga el ejecutable. En versiones antiguas (p. Ej., Archivos CP / M, MS_DOS .com, el cargador simplemente lee los datos de un archivo ejecutable en la memoria y luego comienza a ejecutarlos en alguna dirección. Los cargadores un poco más recientes (p. Ej., Para archivos MS-DOS .exe) comenzar (más o menos) de la misma manera: leer un archivo en la memoria. En este caso, sin embargo, en función de las entradas puestas allí por el enlazador, "arreglará" cualquier referencia absoluta en el ejecutable para referirse al dirección correcta. En el ejemplo anterior, nuestro código de inicio hace referencia amain en la dirección 1112, pero el ejecutable se está cargando en una dirección base de (digamos) 4000. En este caso, el cargador arreglará esa dirección para referirse a 5112. Sin embargo, en este sistema simple, el cargador sigue siendo un pieza de código bastante simple, básicamente solo recorriendo la lista de reubicaciones y agregando la dirección base a cada una.

Ahora consideremos un sistema operativo un poco más moderno que admite algo como archivos de objetos compartidos o DLL. Básicamente, esto cambia parte del trabajo del vinculador al cargador. En particular, para un símbolo que se define en un archivo .so / DLL, el vinculador no intentará asignar una dirección en sí.

En su lugar, creará una entrada en la tabla de símbolos que básicamente dice "definido en el archivo .so / DLL XXX". Cuando el vinculador escribe el ejecutable, la mayoría de estas entradas de la tabla de símbolos básicamente se copiarán en el ejecutable, diciendo "el símbolo XXX se define en el archivo AAA". Entonces depende del cargador encontrar el archivo AAAA, y la dirección del símbolo XXX en ese archivo, y completar la dirección correcta donde sea que se use en el ejecutable. Al igual que en el enlazador, esto será recursivo, por lo que DLL A puede referirse a símbolos en DLL B, que pueden referirse a DLL C, y así sucesivamente. Aunque la cadena del ejecutable a todas las definiciones puede ser larga, la idea básica del proceso es bastante simple: escanee la lista de referencias externas y encuentre una definición para cada una. También tenga en cuenta que en la mayoría de los casos,

Una vez más, hay algunas partes y piezas diversas a considerar. Por ejemplo, el intercambio normalmente solo se realizará sección por sección, no archivo por archivo. Si un archivo tiene algún código y algunos datos (no constantes), por ejemplo, todos los procesos compartirán las mismas secciones de código, pero cada uno obtendrá su propia copia de los datos.

Jerry Coffin
fuente
Los cargadores más simples ni siquiera tienen ninguna capacidad de reparación. Estoy pensando en el viejo TRS-80. Los archivos ejecutables eran simplemente dirección / tamaño / datos, repetidos según fuera necesario (el tamaño era de 1 byte, por lo que normalmente necesitaba varios bloques). Incluso podría crear pantallas de bienvenida especificando una dirección de carga en la memoria de video.
Loren Pechtel
1

Para obtener más información sobre los vinculadores, creo que generalmente se discutirán en combinación con compiladores. Son para unir sus diversos módulos en una unidad cohesiva, finalizando las direcciones dentro de ese código. Algunos incluso pueden intentar realizar optimizaciones.

Para obtener más información sobre los cargadores, creo que generalmente se discutirán en combinación con compiladores de escritura para arquitecturas particulares a menos que se refiera a cargador como sinónimo de enlazador. Estoy pensando en el cargador como la parte del encabezado del archivo ejecutable que le dice al sistema operativo cómo abrir y ejecutar su software compilado.

Estoy de acuerdo en que leer los artículos de Wikipedia probablemente imparta más información de la que estás buscando. En cuanto a dónde entran en desarrollo ... generalmente están más allá del control del proyecto, y son parte de la selección del sistema operativo y el paquete de desarrollo que elige utilizar. Es muy raro que use (por ejemplo) MSVC pero quiera ejecutar un enlazador basado en GCC ... puede que ni siquiera sea posible. El ÚNICO lugar en el que he usado un enlazador no estándar fue en IBM cuando estábamos usando copias de desarrollo.

Si tiene preguntas más particulares y específicas sobre estos temas, creo que encontrará una respuesta mucho mejor.

Stephen
fuente
1

Los enlazadores y cargadores son dos conceptos relacionados pero separados.

Los enlazadores son parte de la teoría del compilador. Cuando compila un proyecto compuesto por más de un módulo (archivo de código fuente), es común que el compilador genere un único archivo intermediario para cada módulo fuente. Esto tiene varios beneficios, uno de los cuales es que si solo realiza cambios en un archivo y luego tiene que volver a compilar, no tiene que reconstruir todo el proyecto cuando solo ha realizado un cambio local.

Pero esto significa que si tiene código en un módulo que llama a una función en un módulo diferente, el compilador no puede generarle una CALLinstrucción, porque no tiene la ubicación de esa otra función. Está en un archivo intermediario diferente, y la ubicación exacta de la función puede cambiar si realiza un cambio local en el archivo fuente de ese intermediario y lo vuelve a compilar. Entonces, en cambio, inserta un "token de referencia externo" (no importa exactamente qué es o qué aspecto tiene, solo piense en él como un concepto abstracto) que dice "Necesito esta función cuya dirección exacta no sé En el momento."

Una vez que todo se ha compilado en archivos intermedios, el vinculador es lo que termina el trabajo. Revisa todos los archivos intermedios y los une en un binario final. Dado que está juntando cosas, conoce las direcciones reales de todas las funciones, por lo que puede reemplazar los tokens de referencia externos con CALLinstrucciones reales para las ubicaciones correctas en el binario.

El cargador, por otro lado, pertenece al sistema operativo, no al compilador. Su trabajo es cargar el binario en la memoria para que pueda ejecutarse y finalizar el proceso de vinculación, ya que el vinculador solo puede resolver el código que conoce. Si su programa está utilizando cualquier DLL, son externas incluso al binario compilado, por lo que el enlazador no conoce su dirección. Deja los tokens de referencia externos en el binario final en un formato que el cargador del sistema operativo conoce, y luego el cargador revisa y hace coincidir estos tokens con las direcciones de las funciones reales en las DLL una vez que todo se ha cargado en la memoria.

Mason Wheeler
fuente
-1

Las computadoras trabajan básicamente con números binarios.
La gente habla sus idiomas nativos.

Sí, los lenguajes de programación son para la comunicación entre personas y computadoras.
Si dice: agregue 2 y 3 y luego reste 1, dudo que la computadora entienda algo (tal vez en algún lenguaje de programación lo haría).

Por lo tanto, debe traducir su código fuente a un formato que la computadora entienda, por lo que necesita un compilador, que traduce un lenguaje de programación a co llamado código de objeto. Pero el código objeto todavía no es el lenguaje que una computadora entiende y ejecuta directamente. Por lo tanto, necesita un vinculador que haga un archivo ejecutable que contenga instrucciones en el llamado lenguaje de máquina; Un lenguaje de máquina es un conjunto de operaciones codificadas en números binarios que el procesador comprende. Todas las instrucciones binarias tienen su estructura y están publicadas por un fabricante de procesadores. Puede buscarlo en el sitio de Intel y ver cómo se ven.

spc
fuente
Aquí hay una guía para desarrolladores de AMD: developer.amd.com/documentation/guides/Pages/default.aspx
spc