tipos quimicos polar metalico lewis ionico estructura enlaces enlace covalente c++ linker

c++ - quimicos - estructura de lewis



¿Cómo funciona el enlace C++ en la práctica? (3)

Esta pregunta ya tiene una respuesta aquí:

¿Cómo funciona el enlace C ++ en la práctica? Lo que estoy buscando es una explicación detallada sobre cómo ocurre la vinculación, y no qué comandos hacen los enlaces.

Ya hay una pregunta similar sobre la compilación que no entra en demasiados detalles: ¿cómo funciona el proceso de compilación / vinculación?



En realidad, uno podría decir que la vinculación es relativamente simple.

En el sentido más simple, se trata de agrupar los archivos de objetos 1, ya que los que ya contienen el ensamblaje emitido para cada una de las funciones / globales / datos ... contenidos en sus respectivas fuentes. El enlazador puede ser extremadamente tonto y tratar todo como un símbolo (nombre) y su definición (o contenido).

Obviamente, el vinculador necesita producir un archivo que respete un determinado formato (el formato ELF generalmente en Unix) y separará las diversas categorías de código / datos en diferentes secciones del archivo, pero eso es solo el envío.

Las dos complicaciones que conozco son:

  • la necesidad de duplicar los símbolos: algunos símbolos están presentes en varios archivos de objeto y solo uno debe crearse en la biblioteca / ejecutable resultante; es el trabajo del enlazador para incluir solo una de las definiciones

  • optimización de tiempo de enlace: en este caso los archivos de objetos no contienen el ensamblaje emitido sino una representación intermedia y el enlazador combina todos los archivos de objetos, aplica pases de optimización (en línea, por ejemplo), los compila para ensamblar y finalmente emitir su resultado .


1 : el resultado de la compilación de las diferentes unidades de traducción (más o menos, archivos fuente preprocesados)


EDITAR : He movido esta respuesta al duplicado: https://.com/a/33690144/895245

Esta respuesta se centra en la reubicación de direcciones , que es una de las funciones cruciales de la vinculación.

Se usará un ejemplo mínimo para aclarar el concepto.

0) Introducción

Resumen: la reubicación edita la sección .text de los archivos de objetos para traducir:

  • dirección de archivo de objeto
  • en la dirección final del ejecutable

Esto debe hacerlo el vinculador porque el compilador solo ve un archivo de entrada a la vez, pero debemos conocer todos los archivos de objetos a la vez para decidir cómo hacerlo:

  • resolver símbolos indefinidos como funciones indefinidas declaradas
  • no .text múltiples secciones .text y .data de múltiples archivos de objeto

Prerrequisitos: comprensión mínima de:

El enlace no tiene nada que ver con C o C ++ específicamente: los compiladores solo generan los archivos objeto. El vinculador los toma como entrada sin saber qué idioma los compiló. Podría ser Fortran.

Para reducir la corteza, estudiemos un NASM x86-64 ELF Linux hello world:

section .data hello_world db "Hello world!", 10 section .text global _start _start: ; sys_write mov rax, 1 mov rdi, 1 mov rsi, hello_world mov rdx, 13 syscall ; sys_exit mov rax, 60 mov rdi, 0 syscall

compilado y ensamblado con:

nasm -o hello_world.o hello_world.asm ld -o hello_world.out hello_world.o

con NASM 2.10.09.

1) .text de .o

Primero descompilamos la sección .text del archivo objeto:

objdump -d hello_world.o

lo que da:

0000000000000000 <_start>: 0: b8 01 00 00 00 mov $0x1,%eax 5: bf 01 00 00 00 mov $0x1,%edi a: 48 be 00 00 00 00 00 movabs $0x0,%rsi 11: 00 00 00 14: ba 0d 00 00 00 mov $0xd,%edx 19: 0f 05 syscall 1b: b8 3c 00 00 00 mov $0x3c,%eax 20: bf 00 00 00 00 mov $0x0,%edi 25: 0f 05 syscall

las líneas cruciales son:

a: 48 be 00 00 00 00 00 movabs $0x0,%rsi 11: 00 00 00

que debería mover la dirección de la cadena hello world al registro rsi , que se pasa a la llamada del sistema de escritura.

¡Pero espera! ¿Cómo puede el compilador saber dónde "Hello world!" terminará en la memoria cuando se carga el programa?

Bueno, no puede, especialmente después de que vinculamos un grupo de archivos .o junto con múltiples secciones .data .

Solo el enlazador puede hacerlo, ya que solo él tendrá todos esos archivos de objeto.

Entonces el compilador simplemente:

  • pone un valor de marcador 0x0 en la salida compilada
  • proporciona información adicional al vinculador sobre cómo modificar el código compilado con las buenas direcciones

Esta "información adicional" está contenida en la sección .rela.text del archivo de objeto

2) .rela.text

.rela.text significa "reubicación de la sección .text".

La palabra reubicación se usa porque el enlazador tendrá que reubicar la dirección del objeto en el ejecutable.

Podemos desmontar la sección .rela.text con:

readelf -r hello_world.o

que contiene;

Relocation section ''.rela.text'' at offset 0x340 contains 1 entries: Offset Info Type Sym. Value Sym. Name + Addend 00000000000c 000200000001 R_X86_64_64 0000000000000000 .data + 0

El formato de esta sección está resuelto documentado en: http://www.sco.com/developers/gabi/2003-12-17/ch4.reloc.html

Cada entrada le dice al vinculador acerca de una dirección que necesita ser reubicada, aquí tenemos solo una para la cadena.

Simplificando un poco, para esta línea en particular tenemos la siguiente información:

  • Offset = C : ¿cuál es el primer byte del .text que cambia esta entrada?

    Si miramos hacia atrás al texto descompilado, está exactamente dentro de los movabs $0x0,%rsi críticos movabs $0x0,%rsi , y aquellos que conocen la codificación de la instrucción x86-64 notarán que esto codifica la parte de la dirección de 64 bits de la instrucción.

  • Name = .data : la dirección apunta a la sección .data

  • Type = R_X86_64_64 , que especifica qué tipo de cálculo se debe realizar para traducir la dirección.

    Este campo es realmente dependiente del procesador y, por lo tanto, está documentado en la sección de extensión AMD64 Sistema V ABI 4.4 "Reubicación".

    Ese documento dice que R_X86_64_64 hace:

    • Field = word64 : 8 bytes, por lo tanto, 00 00 00 00 00 00 00 00 en la dirección 0xC

    • Calculation = S + A

      • S es el valor en la dirección que se reubica, por lo tanto 00 00 00 00 00 00 00 00
      • A es el sumando que es 0 aquí. Este es un campo de la entrada de reubicación.

      Entonces S + A == 0 y seremos reubicados a la primera dirección de la sección .data .

3) .text de .out

Ahora veamos el área de texto del ld ejecutable generado por nosotros:

objdump -d hello_world.out

da:

00000000004000b0 <_start>: 4000b0: b8 01 00 00 00 mov $0x1,%eax 4000b5: bf 01 00 00 00 mov $0x1,%edi 4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi 4000c1: 00 00 00 4000c4: ba 0d 00 00 00 mov $0xd,%edx 4000c9: 0f 05 syscall 4000cb: b8 3c 00 00 00 mov $0x3c,%eax 4000d0: bf 00 00 00 00 mov $0x0,%edi 4000d5: 0f 05 syscall

Entonces, lo único que cambió del archivo objeto son las líneas críticas:

4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi 4000c1: 00 00 00

que ahora apuntan a la dirección 0x6000d8 ( d8 00 60 00 00 00 00 00 en little-endian) en lugar de 0x0 .

¿Es este el lugar correcto para la cadena hello_world ?

Para decidir, debemos verificar los encabezados del programa, que indican a Linux dónde cargar cada sección.

Los desarmamos con:

readelf -l hello_world.out

lo que da:

Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000000d7 0x00000000000000d7 R E 200000 LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8 0x000000000000000d 0x000000000000000d RW 200000 Section to Segment mapping: Segment Sections... 00 .text 01 .data

Esto nos dice que la sección .data , que es la segunda, comienza en VirtAddr = 0x06000d8 .

Y lo único en la sección de datos es nuestra cadena hello world.