Jakie wywołanie systemowe służy do ładowania bibliotek w systemie Linux?


23

W stracewyjściach ścieżki do bibliotek wywoływanych przez pliki wykonywalne znajdują się w wywołaniach open(). Czy jest to wywołanie systemowe używane przez pliki wykonywalne, które są dynamicznie połączone? Co dlopen()? open()nie jest to połączenie, które, jak przypuszczam, odegra pewną rolę w wykonywaniu programów.

Odpowiedzi:


33

dlopennie jest wywołaniem systemowym, to funkcja biblioteczna w bibliotece libdl . Wyświetlane są tylko wywołania systemowe strace.

W Linuksie i na wielu innych platformach (szczególnie tych, które używają formatu ELF dla plików wykonywalnych), dlopenjest implementowany poprzez otwarcie biblioteki docelowej open()i odwzorowanie jej w pamięci mmap(). mmap()jest tu naprawdę kluczową częścią, to właśnie włącza bibliotekę do przestrzeni adresowej procesu, aby procesor mógł wykonać swój kod. Ale musisz to zrobić open(), zanim będziesz mógł mmap()!


2
„mmap () jest naprawdę częścią krytyczną”: A następnie dynamiczny linker musi dokonać relokacji, inicjalizacji i tak dalej (ale nie widać tego na poziomie wywołania systemowego).
ysdx

1
Ponieważ ładowanie bibliotek odbywa się za pomocą funkcji bibliotecznej, myślę, że należy dodać, że sam plik wykonywalny ld-linuxjest mapowany przez jądro jako część execvewywołania systemowego.
kasperd

mmap zgodnie z tą odpowiedzią. Zauważ też, że po „otwarciu” każdej biblioteki niektóre (832) bajty są czytane przed wywołaniem mmap, zakładam, że sprawdzam, czy biblioteka jest poprawna.
Johan

@kasperd Czy więc jądro Linuksa zna dynamiczny moduł ładujący? Czy to się nazywa, gdy aplikacja jest uruchomiona? Czy sama aplikacja to robi? Jeśli to drugie, w jaki sposób inny plik wykonywalny ma dostęp do pamięci aplikacji?
Melab

@Melab Tak, jądro zna dynamiczny linker. Jądro odczyta ścieżkę do dynamicznego linkera z nagłówka pliku wykonywalnego. Jądro zamapuje oba w pamięci. Nie wiem, czy punkt wejścia, do którego kontrola przesyłania jądra na początku znajduje się w linkerze, czy jest wykonywalny. Gdybym to zaimplementował, prawdopodobnie miałbym kontrolę transferu jądra do punktu wejścia w linkerze z adresem zwrotnym na stosie wskazującym punkt wejścia pliku wykonywalnego.
kasperd

5

dlopen nie ma nic wspólnego z bibliotekami współdzielonymi, o których myślisz. Istnieją dwie metody ładowania obiektu udostępnionego:

  1. Mówisz linkerowi podczas kompilacji (ld, chociaż zwykle jest on wywoływany przez kompilator), że chcesz korzystać z funkcji z konkretnej biblioteki współdzielonej. Dzięki takiemu podejściu musisz wiedzieć, jaka będzie nazwa biblioteki po uruchomieniu konsolidatora w czasie kompilacji, ale możesz wywoływać funkcje biblioteki tak, jakby były statycznie połączone z twoim programem. Gdy aplikacja jest uruchomiona, dynamiczny linker w czasie wykonywania (ld.so) zostanie wywołany tuż przed mainwywołaniem funkcji i skonfiguruje przestrzeń procesową aplikacji, aby aplikacja znalazła funkcje biblioteki. Wymaga open()to włożenia smarowania, a następnie włożenia mmap()go, a następnie skonfigurowania niektórych tabel odnośników.
  2. Powiesz łącznik kompilacji, które chcesz połączyć z libdl, z którego następnie (za pomocą pierwszej metody) może wywołać dlopen()idlsym()Funkcje. Z dlopen dostajesz uchwyt do biblioteki, której możesz następnie użyć z dlsym, aby otrzymać wskaźnik funkcji do określonej funkcji. Ta metoda jest o wiele bardziej skomplikowana dla programisty niż pierwsza metoda (ponieważ musisz wykonać konfigurację ręcznie, a nie linker robi to za ciebie automatycznie), a także jest bardziej delikatna (ponieważ nie dostajesz kompilacji -time sprawdza, czy wywołujesz funkcje z poprawnymi typami argumentów, jak w pierwszej metodzie), ale zaletą jest to, że możesz zdecydować, który obiekt współdzielony ma zostać załadowany w czasie wykonywania (lub nawet czy w ogóle go załadować), dzięki czemu jest to interfejs przeznaczony do funkcji typu wtyczek. Wreszcie interfejs dlopen jest również mniej przenośny niż w inny sposób, ponieważ jego mechanika zależy od dokładnej implementacji dynamicznego linkera (stąd libtool'slibltdl, który próbuje pozbyć się tych różnic).

ciekawy; dlatego dynamicznie ładowana biblioteka jest lepiej nazywana bibliotekami dynamicznie połączonymi, ponieważ ładowanie plików binarnych do pamięci nie jest trudną częścią, dlatego sensowne jest używanie adresów w niej użytych. Kiedy proszę o załadowanie biblioteki dynamicznej, tak naprawdę chcę połączyć (lub odłączyć) bibliotekę z (lub poza) moją przestrzenią adresową.
Dmitry

4

Obecnie większość systemów operacyjnych korzysta z metody bibliotek współdzielonych wprowadzonej pod koniec 1987 roku przez SunOS-4.0. Ta metoda opiera się na mapowaniu pamięci za pomocą mmap ().

Biorąc pod uwagę fakt, że na początku lat 90. firma Sun przekazała nawet stary kod oparty na a.out (w tym czasie Solaris był już oparty na ELF) ludziom FreeBSD i że ten kod został później przekazany wielu innym systemom (w tym Linuxowi) , możesz zrozumieć, dlaczego nie ma dużej różnicy między platformami.


3

ltrace -Sanaliza minimalnego przykładu pokazuje, że mmapjest używany w glibc 2.23

W glibc 2.23, Ubuntu 16.04, działający latrace -Sna minimalnym programie, który używa dlopenz:

ltrace -S ./dlopen.out

przedstawia:

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

więc natychmiast widzimy, że dlopenwzywa open+ mmap.

To niesamowite ltracenarzędzie śledzi zarówno wywołania biblioteki, jak i wywołania systemowe, dlatego idealnie nadaje się do sprawdzenia, co się dzieje w tym przypadku.

Bliższa analiza pokazuje, że openzwraca deskryptor pliku 3(następny wolny po stdin, out i err).

readnastępnie używa tego deskryptora pliku, ale TODO, dlaczego mmapargumenty są ograniczone do czterech, i nie możemy zobaczyć, który fd został tam użyty, ponieważ jest to piąty argument . stracepotwierdza zgodnie z oczekiwaniami, że 3jest to jeden, i porządek wszechświata jest przywrócony.

Dzielne dusze mogą również odważyć się na kod glibc, ale nie mogłem znaleźć mmapszybkiego grepa i jestem leniwy.

Przetestowano na tym minimalnym przykładzie z kompilacją płyty grzewczej na GitHub .


2

straceraporty o wywołaniach systemowych (tj. funkcjach implementowanych bezpośrednio przez jądro). Biblioteki dynamiczne nie są funkcją jądra; dlopenjest częścią biblioteki C, a nie jądra. Implementacja dlopenwill call open(która jest wywołaniem systemowym), aby otworzyć plik biblioteki, aby można go było odczytać.


5
Wywołania z biblioteki można zobaczyć za pomocą ltrace.
kasperd

@kasperd ltrace -Sdoskonale nadaje się do analizy tego, ponieważ pokazuje także syscalls: unix.stackexchange.com/a/462710/32558
Ciro Santilli 事件 改造 中心 法轮功 六四 事件
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.