¿Qué llamada al sistema se usa para cargar bibliotecas en Linux?

23

En las stracesalidas, las rutas a las bibliotecas a las que llaman los ejecutables están en llamadas a open(). ¿Es esta la llamada al sistema utilizada por ejecutables que están vinculados dinámicamente? ¿Qué hay de dlopen()? open()No es una llamada que supuse que jugaría un papel en la ejecución de programas.

Melab
fuente

Respuestas:

33

dlopenno es una llamada al sistema, es una función de biblioteca en la biblioteca libdl . Solo se muestran las llamadas al sistema strace.

En Linux y en muchas otras plataformas (especialmente aquellas que usan el formato ELF para ejecutables), dlopense implementa abriendo la biblioteca de destino open()y mapeándola en la memoria mmap(). mmap()es realmente la parte crítica aquí, es lo que incorpora la biblioteca en el espacio de direcciones del proceso, por lo que la CPU puede ejecutar su código. ¡Pero debes acceder al open()archivo antes de poder mmap()hacerlo!

Celada
fuente
2
"mmap () es realmente la parte crítica": y luego el enlazador dinámico tiene que hacer las reubicaciones, la inicialización y demás (pero esto no se ve a nivel de llamada del sistema).
ysdx
1
Dado que la carga de bibliotecas se realiza mediante una función de biblioteca, creo que es relevante agregar que el ejecutable en sí mismo y ld-linuxel núcleo los asigna como parte de la execvellamada al sistema.
kasperd
mmap según esta respuesta. Tenga en cuenta también que después de "abrir" -ing cada biblioteca, algunos (832) bytes se leen antes de la llamada mmap, supongo que comprobar que la biblioteca es válida.
Johan
@kasperd ¿Entonces el kernel de Linux conoce el cargador dinámico? ¿Lo llama cuando se ejecuta la aplicación? ¿O la aplicación misma hace eso? Si es lo último, ¿cómo otro ejecutable tiene acceso a la memoria de la aplicación?
Melab
@Melab Sí, el núcleo conoce el enlazador dinámico. El kernel leerá la ruta al vinculador dinámico desde el encabezado del ejecutable. Y el núcleo asignará ambos a la memoria. No sé si el punto de entrada al que el control de transferencia del núcleo al principio está dentro del vinculador o ejecutable. Si lo estuviera implementando, probablemente tendría el control de transferencia de kernel a un punto de entrada en el enlazador con una dirección de retorno en la pila que apunta al punto de entrada del ejecutable.
kasperd
5

dlopen no tiene nada que ver con las bibliotecas compartidas como piensas en ellas. Hay dos métodos para cargar un objeto compartido:

  1. Le dice al vinculador en tiempo de compilación (ld, aunque generalmente se llama a través del compilador) que desea usar funciones de una biblioteca compartida en particular. Con este enfoque, debe saber cuál será el nombre de la biblioteca cuando se ejecute el enlazador en tiempo de compilación, pero puede llamar a las funciones de la biblioteca como si estuvieran vinculadas estáticamente a su programa. Cuando se ejecuta la aplicación, se llamará al enlazador dinámico en tiempo de ejecución (ld.so) justo antes de llamar a la mainfunción, y configurará el espacio de proceso de la aplicación para que la aplicación encuentre las funciones de la biblioteca. Esto implica open()incorporar el lubrary, y luego mmap()ing, seguido de la configuración de algunas tablas de búsqueda.
  2. Le dice al enlazador en tiempo de compilación con el que desea enlazar libdl, desde el cual puede (usando el primer método) llamar al dlopen()ydlsym()funciones Con dlopen obtienes un identificador de la biblioteca, que luego puedes usar con dlsym para recibir un puntero de función a una función en particular. Este método es mucho más complicado para el programador que el primer método (ya que debe hacer la configuración manualmente, en lugar de que el enlazador lo haga automáticamente por usted), y también es más frágil (ya que no obtiene la compilación -time comprueba que está llamando a funciones con los tipos de argumento correctos a medida que obtiene el primer método), pero la ventaja es que puede decidir qué objeto compartido cargar en tiempo de ejecución (o incluso si cargarlo en absoluto), haciendo Esta es una interfaz destinada a la funcionalidad de tipo de complemento. Finalmente, la interfaz dlopen también es menos portátil que la otra manera, ya que su mecánica depende de la implementación exacta del enlazador dinámico (de ahí que libtool'slibltdl, que intenta abstraer estas diferencias).
Wouter Verhelst
fuente
interesante; Por lo tanto, las bibliotecas cargadas dinámicamente se denominan mejor bibliotecas vinculadas dinámicamente, ya que cargar archivos binarios en la memoria no es la parte difícil, por lo que las direcciones utilizadas en él tienen sentido. Cuando solicito cargar una biblioteca dinámica, en realidad estoy pidiendo vincular (o desvincular) la biblioteca dentro (o fuera) de mi espacio de direcciones.
Dmitry
4

Hoy, la mayoría de los sistemas operativos usan el método para bibliotecas compartidas introducido a fines de 1987 por SunOS-4.0. Este método se basa en la memoria de mapeo a través de mmap ().

Dado el hecho de que a principios de la década de 1990, Sun incluso donó el antiguo código basado en a.out (Solaris en ese momento ya estaba basado en ELF) a las personas de FreeBSD y que este código luego se entregó a muchos otros sistemas (incluido Linux) , puede entender por qué no hay una gran diferencia entre plataformas.

astuto
fuente
3

ltrace -SEl análisis de un ejemplo mínimo muestra que mmapse usa en glibc 2.23

En glibc 2.23, Ubuntu 16.04, ejecutándose latrace -Sen un programa mínimo que utiliza dlopencon:

ltrace -S ./dlopen.out

muestra:

dlopen("libcirosantilli_ab.so", 1 <unfinished ...>
SYS_open("./x86_64/libcirosantilli_ab.so", 524288, 06267650550)      = -2
SYS_open("./libcirosantilli_ab.so", 524288, 06267650550)             = 3
SYS_read(3, "\177ELF\002\001\001", 832)                              = 832
SYS_brk(0)                                                           = 0x244c000
SYS_brk(0x246d000)                                                   = 0x246d000
SYS_fstat(3, 0x7fff42f9ce30)                                         = 0
SYS_getcwd("/home/ciro/bak/git/cpp-cheat"..., 128)                   = 54
SYS_mmap(0, 0x201028, 5, 2050)                                       = 0x7f1c323fe000
SYS_mprotect(0x7f1c323ff000, 2093056, 0)                             = 0
SYS_mmap(0x7f1c325fe000, 8192, 3, 2066)                              = 0x7f1c325fe000
SYS_close(3)                                                         = 0
SYS_mprotect(0x7f1c325fe000, 4096, 1)                                = 0

entonces vemos de inmediato que las dlopenllamadas open+ mmap.

La asombrosa ltraceherramienta rastrea las llamadas a la biblioteca y las llamadas al sistema, y ​​por lo tanto es perfecta para examinar lo que está sucediendo en este caso.

Un análisis más detallado muestra que opendevuelve el descriptor de archivo 3(el siguiente libre después de stdin, out y err).

readluego usa ese descriptor de archivo, pero TODO por qué mmaplos argumentos están limitados a cuatro, y no podemos ver qué fd se utilizó allí ya que ese es el quinto argumento . straceconfirma como se esperaba que ese 3es, y el orden del universo se restaura.

Las almas valientes también pueden aventurarse en el código glibc, pero no pude encontrar el mmapdespués de un grep rápido y soy flojo.

Probado con este ejemplo mínimo con build boilerplate en GitHub .

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
fuente
2

straceinformes sobre llamadas del sistema (es decir, funciones implementadas directamente por el núcleo). Las bibliotecas dinámicas no son una función del núcleo; dlopenes parte de la biblioteca C, no el núcleo. La implementación de dlopenwill llamará open(que es una llamada al sistema) para abrir el archivo de la biblioteca y poder leerlo.

cjm
fuente
55
Las llamadas a la biblioteca se pueden ver usando ltrace.
kasperd
@kasperd ltrace -Ses perfecto para analizar esto, ya que también muestra syscalls: unix.stackexchange.com/a/462710/32558
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件