How do i create a static library file of my test.c
with gcc?
You can create a static library .lib
file using the GCC compiler by following these steps:
- Compile the source files into object files:
- Create the static library file:
Note that the ar
command creates an archive (a static library) from a set of object files. The options used here are:
rcs
: Create a new archive, replace existing members, and store the member names as strings.libtest.a
: The name of the archive file to be created.test.o
: The object file to be added to the archive.
The above commands will create a static library file named libtest.a
that you can link to your application. To link the library to your application, you can use the -l
option with the GCC compiler:
gcc -o test main.c -L. -ltest
In the above command, -L.
specifies the current directory as the location of the library, and -ltest
specifies the name of the library to link (without the lib
prefix and the .a
suffix).
How do i create a .lib
file for windows?
To create a .lib
file for Windows, you need to compile your source code files into object files and then use the Microsoft Library Manager (lib.exe
) to create the library.
Here’s an example of how you can create a .lib
file for Windows using the Microsoft Visual C++ compiler (cl.exe
) and the Microsoft Library Manager:
- Compile the source files into object files:
Note the /c
option is used to compile the source file into an object file without linking it.
- Create the library file:
lib /nologo /out:test.lib test.obj
Note the /out:
option is used to specify the name of the library file to be created, and the test.obj
option specifies the name of the object file to be added to the library.
The /nologo
flag suppresses display of the LIB copyright message and version number and prevents echoing of command files.
This will create a library file named test.lib
that you can link to your application. To link the library to your application, you can use the /L
and /l
options with the Microsoft Visual C++ compiler:
cl /Fe:test.exe main.c /I. /L. /ltest
In the above command, /Fe:test.exe
specifies the name of the executable file to be created, /I.
specifies the current directory as the location of the header files, /L.
specifies the current directory as the location of the library, and /ltest
specifies the name of the library to link (without the .lib
extension).
The command cl
is not recognized, but i have visual studio c++ installed.
If you have Visual Studio C++ installed, you can use the Visual Studio Command Prompt to create a .lib
file.
Here’s an example of how you can create a .lib
file using Visual Studio:
-
Open the Visual Studio Command Prompt. You can find it in the Start menu under the Visual Studio folder.
-
Compile the source files into object files:
Note the /c
option is used to compile the source file into an object file without linking it.
- Create the library file:
lib /nologo /out:test.lib test.obj
Note the /out:
option is used to specify the name of the library file to be created, and the test.obj
option specifies the name of the object file to be added to the library.
The /nologo
flag suppresses display of the LIB copyright message and version number and prevents echoing of command files.
This will create a library file named test.lib
that you can link to your application. To link the library to your application, you can use the /L
and /l
options with the cl.exe compiler:
less
cl /Fe:test.exe main.c /I. /L. /ltest
In the above command, /Fe:test.exe
specifies the name of the executable file to be created, /I.
specifies the current directory as the location of the header files, /L.
specifies the current directory as the location of the library, and /ltest
specifies the name of the library to link (without the .lib
extension).
How do i open the Visual Studio Command Prompt
?
You can open the Visual Studio Command Prompt in several ways, depending on which version of Visual Studio you have installed:
-
Open the Start menu and search for «Visual Studio Command Prompt». This should bring up the «Developer Command Prompt for Visual Studio» or the «Visual Studio Command Prompt» option, which you can click to open.
-
Open the Start menu and navigate to the «Visual Studio» folder. Depending on your version of Visual Studio, you may find the command prompt under a subfolder such as «Visual Studio 2019» or «Visual Studio 2017».
-
If you have Visual Studio installed, you can open the Visual Studio Developer Command Prompt from the Start menu by navigating to «Visual Studio [version] Developer Command Prompt».
-
If you have Visual Studio installed, you can open the Visual Studio Developer Command Prompt from the Start menu by navigating to «Visual Studio Tools Visual Studio Command Prompt».
Once you’ve opened the Visual Studio Command Prompt, you can use it to run the commands required to create a .lib
file.
How do i specify x64 target architecture for windows with cl
?
For what target architecture the cl
compiler will compile to depends on the chosen cl.exe
. There are two: a x86 — and a x64.
Type in the Visual Studio Developer Command Prompt: where cl
and see where cl.exe
is located:
Example:
C:\Program Files\Microsoft Visual Studio\2022\Professional>where cl
C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.34.31933\bin\Hostx86\x86\cl.exe
C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.34.31933\bin\Hostx64\x64\cl.exe
Choose Hostx86\x86\cl.exe
to compile for the x86 architecture.
Choose Hostx64\x64\cl.exe
to compile for the x64 architecture.
Once you have compiled the source files into object files, you can create the «.lib» file as described in my previous answer. Note that the architecture specified when compiling the object files must match the architecture specified when linking the «.lib» file to your application.
lib /machine:x64 /out:foo.lib foo.obj
How do i do specify the target architecture for linux with gcc
?
To specify the target architecture when building a static library on Linux with GCC, you can use the -march
flag. The -march
flag is used to specify the target architecture for the generated machine code.
Here’s an example of how you can build a static library for an x86-64 architecture using GCC:
gcc -c -march=x86-64 test.c
The -c
option is used to compile the source file into an object file without linking it. The -march=x86-64
option specifies the target architecture for the generated machine code.
Once you have compiled the source files into object files, you can create the static library as described in my previous answer:
ar rcs test.a test.o
Note that the architecture specified when compiling the object files must match the architecture specified when linking the static library to your application.
Библиотеки
Библиотека — в языке C, файл содержащий объектный код, который может быть присоединен к использующей библиотеку программе на этапе линковки. Фактически библиотека это набор особым образом скомпонованных объектных файлов.
Назначение библиотек — предоставить программисту стандартный механизм повторного использования кода, причем механизм простой и надёжный.
С точки зрения операционной системы и прикладного программного обеспечения библиотеки бывают статическими и разделяемыми (динамическими).
Код статических библиотек включается в состав исполняемого файла в ходе линковки последнего. Библиотека оказывается «зашитой» в файл, код библиотеки «сливается» с остальным кодом файла. Программа использующая статические библиотеки становиться автономной и может быть запущена практически на любом компьютере с подходящей архитектурой и операционной системой.
Код разделяемой библиотеки загружается и подключается к коду программы операционной системой, по запросу программы в ходе её исполнения. В состав исполняемого файла программы код динамической библиотеки не входит, в исполняемый файл включается только ссылка на библиотеку. В результате, программа использующая разделяемые библиотеки перестает быть автономной и может быть успешно запущена только в системе где задействованные библиотеки установлены.
Парадигма разделяемых библиотек предоставляет три существенных преимущества:
1. Многократно сокращается размер исполняемого файла. В системе, включающей множество бинарных файлов, использующих один и тот же код, отпадает необходимость хранить копию этого кода для каждого исполняемого файла.
2. Код разделяемой библиотеки используемый несколькими приложениями храниться в оперативной памяти в одном экземпляре (на самом деле не всё так просто…), в результате сокращается потребность системы в доступной оперативной памяти.
3. Отпадает необходимость пересобирать каждый исполняемый файл в случае внесения изменений в код общей для них библиотеки. Изменения и исправления кода динамической библиотеки автоматически отразятся на каждой из использующих её программ.
Без парадигмы разделяемых библиотек не существовало бы прекомпиллированных (бинарных) дистрибутивов Linux (да ни каких бы не существовало). Представьте размеры дистрибутива, в каждый бинарный файл которого, был бы помещен код стандартной библиотеки C (и всех других подключаемых библиотек). Так же представьте что пришлось бы делать для того, что бы обновить систему, после устранения критической уязвимости в одной из широко задействованных библиотек…
Теперь немного практики.
Для иллюстрации воспользуемся набором исходных файлов из предыдущего примера. В нашу самодельную библиотеку поместим код (реализацию) функций first() и second().
В Linux принята следующая схема именования файлов библиотек (хотя соблюдается она не всегда) — имя файла библиотеки начинается с префикса lib, за ним следует собственно имя библиотеки, в конце расширение .a (archive) — для статической библиотеки, .so (shared object) — для разделяемой (динамической), после расширения через точку перечисляются цифры номера версии (только для динамической библиотеки). Имя, соответствующего библиотеке заголовочного файла (опять же как правило), состоит из имени библиотеки (без префикса и версии) и расширения .h. Например: libogg.a, libogg.so.0.7.0, ogg.h.
В начале создадим и используем статическую библиотеку.
Функции first() и second() составят содержимое нашей библиотеки libhello. Имя файла библиотеки, соответственно, будет libhello.a. Библиотеке сопоставим заголовочный файл hello.h.
/* hello.h */ void first(void); void second(void); Разумеется, строки: #include "first.h" #include "second.h" в файлах main.c, first.c и second.c необходимо заменить на: #include "hello.h" Ну а теперь, введем следующую последовательность команд: $ gcc -Wall -c first.c $ gcc -Wall -c second.c $ ar crs libhello.a first.o second.o $ file libhello.a libhello.a: current ar archive
Как уже было сказано — библиотека это набор объектных файлов. Первыми двумя командами мы и создали эти объектные файлы.
Далее необходимо объектные файлы скомпоновать в набор. Для этого используется архиватор ar — утилита «склеивает» несколько файлов в один, в полученный архив включает информацию требуемую для восстановления (извлечения) каждого индивидуального файла (включая его атрибуты принадлежности, доступа, времени). Какого-либо «сжатия» содержимого архива или иного преобразования хранимых данных при этом не производится.
Опция c arname — создать архив, если архив с именем arname не существует он будет создан, в противном случае файлы будут добавлены к имеющемуся архиву.
Опция r — задает режим обновления архива, если в архиве файл с указанным именем уже существует, он будет удален, а новый файл дописан в конец архива.
Опция s — добавляет (обновляет) индекс архива. В данном случае индекс архива это таблица, в которой для каждого определенного в архивируемых файлах символического имени (имени функции или блока данных) сопоставлено соответствующее ему имя объектного файла. Индекс архива необходим для ускорения работы с библиотекой — для того чтобы найти нужное определение, отпадает необходимость просматривать таблицы символов всех файлов архива, можно сразу перейти к файлу, содержащему искомое имя. Просмотреть индекс архива можно с помощью уже знакомой утилиты nm воспользовавшись её опцией -s (так же будут показаны таблицы символов всех объектных файлов архива):
$ nm -s libhello.a Archive index: first in first.o second in second.o first.o: 00000000 T first U puts second.o: U puts 00000000 T second
Для создания индекса архива существует специальная утилита ranlib. Библиотеку libhello.a можно было сотворить и так:
$ ar cr libhello.a first.o second.o
$ ranlib libhello.a
Впрочем библиотека будет прекрасно работать и без индекса архива.
Теперь воспользуемся нашей библиотекой:
$ gcc -Wall -c main.c
$ gcc -o main main.o -L. -lhello
$ ./main
First function…
Second function…
Main function…
Работает…
Ну теперь комментарии… Появились две новые опции gcc:
Опция -lname — передаётся линковщику, указывает на необходимость подключить к исполняемому файлу библиотеку libname. Подключить значит указать, что такие-то и такие-то функции (внешние переменные) определены в такой-то библиотеке. В нашем примере библиотека статическая, все символьные имена будут ссылаться на код находящийся непосредственно в исполняемом файле. Обратите внимание в опции -l имя библиотеки задается как name без приставки lib.
Опция -L/путь/к/каталогу/с/библиотеками — передаётся линковщику, указывает путь к каталогу содержащему подключаемые библиотеки. В нашем случае задана точка ., линковщик сначала будет искать библиотеки в текущем каталоге, затем в каталогах определённых в системе.
Здесь необходимо сделать небольшое замечание. Дело в том, что для ряда опций gcc важен порядок их следования в командной строке. Так линковщик ищет код, соответствующий указанным в таблице символов файла именам в библиотеках, перечисленных в командной строке после имени этого файла. Содержимое библиотек перечисленных до имени файла линковщик игнорирует:
$ gcc -Wall -c main.c
$ gcc -o main -L. -lhello main.o
main.o: In function `main’:
main.c:(.text+0xa): undefined reference to `first’
main.c:(.text+0xf): undefined reference to `second’
collect2: ld returned 1 exit status
$ gcc -o main main.o -L. -lhello
$ ./main
First function…
Second function…
Main function…
Такая особенность поведения gcc обусловлена желанием разработчиков предоставить пользователю возможность по разному комбинировать файлы с библиотеками, использовать пересекающие имена… На мой взгляд, если возможно, лучше этим не заморачиваться. В общем подключаемые библиотеки необходимо перечислять после имени ссылающегося на них файла.
Существует альтернативный способ указания местоположения библиотек в системе. В зависимости от дистрибутива, переменная окружения LD_LIBRARY_PATH или LIBRARY_PATH может хранить список разделенных знаком двоеточия каталогов, в которых линковщик должен искать библиотеки. Как правило, по умолчанию эта переменная вообще не определена, но ни чего не мешает её создать:
$ echo $LD_LIBRARY_PATH
$ gcc -o main main.o -lhello
/usr/lib/gcc/i686-pc-linux-gnu/4.4.3/../
collect2: выполнение ld завершилось с кодом возврата 1
$ export LIBRARY_PATH=.
$ gcc -o main main.o -lhello
$ ./main
First function…
Second function…
Main function…
Манипуляции с переменными окружения полезны при создании и отладке собственных библиотек, а так же если возникает необходимость подключить к приложению какую-нибудь нестандартную (устаревшую, обновленную, изменённую — в общем отличную от включенной в дистрибутив) разделяемую библиотеку.
Теперь создадим и используем библиотеку динамическую.
Набор исходных файлов остается без изменения. Вводим команды, смотрим что получилось, читаем комментарии:
$ gcc -Wall -fPIC -c first.c
$ gcc -Wall -fPIC -c second.c
$ gcc -shared -o libhello.so.2.4.0.5 -Wl,-soname,libhello.so.2 first.o second.o
Что получили в результате?
$ file libhello.so.2.4.0.5
libhello.so.2.4.0.5: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not stripped
Файл libhello.so.2.4.0.5, это и есть наша разделяемая библиотека. Как её использовать поговорим чуть ниже.
Теперь комментарии:
Опция -fPIC — требует от компилятора, при создании объектных файлов, порождать позиционно-независимый код (PIC — Position Independent Code), его основное отличие в способе представления адресов. Вместо указания фиксированных (статических) позиций, все адреса вычисляются исходя из смещений заданных в глобальной таблицы смещений (global offset table — GOT). Формат позиционно-независимого кода позволяет подключать исполняемые модули к коду основной программы в момент её загрузки. Соответственно, основное назначение позиционно-независимого кода — создание динамических (разделяемых) библиотек.
Опция -shared — указывает gcc, что в результате должен быть собран не исполняемый файл, а разделяемый объект — динамическая библиотека.
Опция -Wl,-soname,libhello.so.2 — задает soname библиотеки. О soname подробно поговорим в следующем абзаце. Сейчас обсудим формат опции. Сея странная, на первый взгляд, конструкция с запятыми предназначена для непосредственного взаимодействия пользователя с линковщиком. По ходу компиляции gcc вызывает линковщик автоматически, автоматически же, по собственному усмотрению, gcc передает ему необходимые для успешного завершения задания опции. Если у пользователя возникает потребность самому вмешаться в процесс линковки он может воспользоваться специальной опцией gcc -Wl,-option,value1,value2…. Что означает передать линковщику (-Wl) опцию -option с аргументами value1, value2 и так далее. В нашем случае линковщику была передана опция -soname с аргументом libhello.so.2.
Теперь о soname. При создании и распространении библиотек встает проблема совместимости и контроля версий. Для того чтобы система, конкретно загрузчик динамических библиотек, имели представление о том библиотека какой версии была использована при компиляции приложения и, соответственно, необходима для его успешного функционирования, был предусмотрен специальный идентификатор — soname, помещаемый как в файл самой библиотеки, так и в исполняемый файл приложения. Идентификатор soname это строка, включающая имя библиотеки с префиксом lib, точку, расширение so, снова точку и оду или две (разделенные точкой) цифры версии библиотеки — libname.so.x.y. То есть soname совпадает с именем файла библиотеки вплоть до первой или второй цифры номера версии. Пусть имя исполняемого файла нашей библиотеки libhello.so.2.4.0.5, тогда soname библиотеки может быть libhello.so.2. При изменении интерфейса библиотеки её soname необходимо изменять! Любая модификация кода, приводящая к несовместимости с предыдущими релизами должна сопровождаться появлением нового soname.
Как же это все работает? Пусть для успешного исполнения некоторого приложения необходима библиотека с именем hello, пусть в системе таковая имеется, при этом имя файла библиотеки libhello.so.2.4.0.5, а прописанное в нем soname библиотеки libhello.so.2. На этапе компиляции приложения, линковщик, в соответствии с опцией -lhello, будет искать в системе файл с именем libhello.so. В реальной системе libhello.so это символическая ссылка на файл libhello.so.2.4.0.5. Получив доступ к файлу библиотеки, линковщик считает прописанное в нем значение soname и наряду с прочим поместит его в исполняемый файл приложения. Когда приложение будет запущено, загрузчик динамических библиотек получит запрос на подключение библиотеки с soname, считанным из исполняемого файла, и попытается найти в системе библиотеку, имя файла которой совпадает с soname. То есть загрузчик попытается отыскать файл libhello.so.2. Если система настроена корректно, в ней должна присутствовать символическая ссылка libhello.so.2 на файл libhello.so.2.4.0.5, загрузчик получит доступ к требуемой библиотеки и далее не задумываясь (и ни чего более не проверяя) подключит её к приложению. Теперь представим, что мы перенесли откомпилированное таким образом приложение в другую систему, где развернута только предыдущая версия библиотеки с soname libhello.so.1. Попытка запустить программу приведет к ошибке, так как в этой системе файла с именем libhello.so.2 нет.
Таким образом, на этапе компиляции линковщику необходимо предоставить файл библиотеки (или символическую ссылку на файл библиотеки) с именем libname.so, на этапе исполнения загрузчику потребуется файл (или символическая ссылка) с именем libname.so.x.y. При чем имя libname.so.x.y должно совпадать со строкой soname использованной библиотеки.
В бинарных дистрибутивах, как правило, файл библиотеки libhello.so.2.4.0.5 и ссылка на него libhello.so.2 будут помещены в пакет libhello, а необходимая только для компиляции ссылка libhello.so, вместе с заголовочным файлом библиотеки hello.h будет упакована в пакет libhello-devel (в devel пакете окажется и файл статической версии библиотеки libhello.a, статическая библиотека может быть использована, также только на этапе компиляции). При распаковке пакета все перечисленные файлы и ссылки (кроме hello.h) окажутся в одном каталоге.
Пример именования библиотек в Linux. Ubuntu
Убедимся, что заданная строка soname действительно прописана в файле нашей библиотеки. Воспользуемся мега утилитой objdump с опцией -p:
$ objdump -p libhello.so.2.4.0.5 | grep SONAME SONAME libhello.so.2
Утилита objdump — мощный инструмент, позволяющий получить исчерпывающую информацию о внутреннем содержании (и устройстве) объектного или исполняемого файла. В man странице утилиты сказано, что objdump прежде всего будет полезен программистам, создающими средства отладки и компиляции, а не просто пишущих какие-нибудь прикладные программы В частности с опцией -d это дизассемблер. Мы воспользовались опцией -p — вывести различную метаинформацию о объектном файле.
В приведенном примере создания библиотеки мы неотступно следовали принципам раздельной компиляции. Разумеется скомпилировать библиотеку можно было бы и вот так, одним вызовом gcc:
$ gcc -shared -Wall -fPIC -o libhello.so.2.4.0.5 -Wl,-soname,libhello.so.2 first.c second.c
Теперь попытаемся воспользоваться получившейся библиотекой:
$ gcc -Wall -c main.c
$ gcc -o main main.o -L. -lhello -Wl,-rpath,.
/usr/bin/ld: cannot find -lhello
collect2: ld returned 1 exit status
Линковщик ругается. Вспоминаем, что было сказано выше о символических ссылках. Создаем libhello.so и повторяем попытку:
$ ln -s libhello.so.2.4.0.5 libhello.so
$ gcc -o main main.o -L. -lhello -Wl,-rpath,.
Теперь все довольны. Запускаем созданный бинарник:
$ ./main
./main: error while loading shared libraries: libhello.so.2: cannot open shared object file: No such file or directory
Ошибка… Ругается загрузчик, не может найти библиотеку libhello.so.2. Убедимся, что в исполняемом файле действительно прописана ссылка на libhello.so.2:
$ objdump -p main | grep NEEDED NEEDED libhello.so.2 NEEDED libc.so.6
Создаем соответствующую ссылку и повторно запускаем приложение:
$ ln -s libhello.so.2.4.0.5 libhello.so.2
$ ./main
First function…
Second function…
Main function…
Заработало… Теперь комментарии по новым опциям gcc.
Опция -Wl,-rpath,. — уже знакомая конструкция, передать линковщику опцию -rpath с аргументом .. С помощью -rpath в исполняемый файл программы можно прописать дополнительные пути по которым загрузчик разделяемых библиотек будет производить поиск библиотечных файлов. В нашем случае прописан путь . — поиск файлов библиотек будет начинаться с текущего каталога.
$ objdump -p main | grep RPATH RPATH .
Благодаря указанной опции, при запуске программы отпала необходимость изменять переменные окружения. Понятно, что если перенести программу в другой каталог и попытаться запустить, файл библиотеки будет не найден и загрузчик выдаст сообщение об ошибке:
$ mv main ..
$ ../main
First function…
Second function…
Main function…
$ cd ..
$ ./main
./main: error while loading shared libraries: libhello.so.2: cannot open shared object file: No such file or directory
Узнать какие разделяемые библиотеки необходимы приложению можно и с помощью утилиты ldd:
$ ldd main
linux-vdso.so.1 => (0x00007fffaddff000)
libhello.so.2 => ./libhello.so.2 (0x00007f9689001000)
libc.so.6 => /lib/libc.so.6 (0x00007f9688c62000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9689205000)
В выводе ldd для каждой требуемой библиотеки указывается её soname и полный путь к файлу библиотеки, определённый в соответствии с настройками системы.
Сейчас самое время поговорить о том где в системе положено размещать файлы библиотек, где загрузчик пытается их найти и как этим процессом управлять.
В соответствии с соглашениями FHS (Filesystem Hierarchy Standard) в системе должны быть два (как минимум) каталога для хранения файлов библиотек:
/lib — здесь собраны основные библиотеки дистрибутива, необходимые для работы программ из /bin и /sbin;
/usr/lib — здесь хранятся библиотеки необходимые прикладным программам из /usr/bin и /usr/sbin;
Соответствующие библиотекам заголовочные файлы должны находиться в каталоге /usr/include.
Загрузчик по умолчанию будет искать файлы библиотек в этих каталогах.
Кроме перечисленных выше, в системе должен присутствовать каталог /usr/local/lib — здесь должны находиться библиотеки, развернутые пользователем самостоятельно, минуя систему управления пакетами (не входящие в состав дистрибутива). Например в этом каталоге по умолчанию окажутся библиотеки скомпилированные из исходников (программы установленные из исходников будут размещены в /usr/local/bin и /usr/local/sbin, разумеется речь идет о бинарных дистрибутивах). Заголовочные файлы библиотек в этом случае будут помещены в /usr/local/include.
В ряде дистрибутивов (в Ubuntu) загрузчик не настроен просматривать каталог /usr/local/lib, соответственно, если пользователь установит библиотеку из исходников, система её не увидит. Сиё авторами дистрибутива сделано специально, что бы приучить пользователя устанавливать программное обеспечение только через систему управления пакетами. Как поступить в данном случае будет рассказано ниже.
В действительности, для упрощения и ускорения процесса поиска файлов библиотек, загрузчик не просматривает при каждом обращении указанные выше каталоги, а пользуется базой данных, хранящейся в файле /etc/ld.so.cache (кэшем библиотек). Здесь собрана информация о том, где в системе находится соответствующий данному soname файл библиотеки. Загрузчик, получив список необходимых конкретному приложению библиотек (список soname библиотек, заданных в исполняемом файле программы), посредством /etc/ld.so.cache определяет путь к файлу каждой требуемой библиотеки и загружает её в память. Дополнительно, загрузчик может просмотреть каталоги перечисленные в системных переменных LD_LIBRARY_PATH, LIBRARY_PATH и в поле RPATH исполняемого файла (смотри выше).
Для управления и поддержания в актуальном состоянии кэша библиотек используется утилита ldconfig. Если запустить ldconfig без каких-либо опций, программа просмотрит каталоги заданные в командной строке, доверенные каталоги /lib и /usr/lib, каталоги перечисленные в файле /etc/ld.so.conf. Для каждого файла библиотеки, оказавшегося в указанных каталогах, будет считано soname, создана основанная на soname символическая ссылка, обновлена информация в /etc/ld.so.cache.
Убедимся в сказанном:
$ ls
hello.h libhello.so libhello.so.2.4.0.5 main.c
$ gcc -Wall -o main main.c -L. -lhello
$ ./main
./main: error while loading shared libraries: libhello.so.2: cannot open shared object file: No such file or directory
$ sudo ldconfig /полный/путь/к/катаогу/c/примером
$ ls
hello.h libhello.so libhello.so.2 libhello.so.2.4.0.5 main main.c
$ ./main
First function…
Second function…
Main function…
$ sudo ldconfig
$ ./main
./main: error while loading shared libraries: libhello.so.2: cannot open shared object file: No such file or directory
Первым вызовом ldconfig мы внесли в кэш нашу библиотеку, вторым вызовом исключили. Обратите внимание, что при компиляции main была опущена опция -Wl,-rpath,., в результате загрузчик проводил поиск требуемых библиотек только в кэше.
Теперь должно быть понятно как поступить если после установки библиотеки из исходников система её не видит. Прежде всего необходимо внести в файл /etc/ld.so.conf полный путь к каталогу с файлами библиотеки (по умолчанию /usr/local/lib). Формат /etc/ld.so.conf — файл содержит список разделённых двоеточием, пробелом, табуляцией или символом новой строки, каталогов, в которых производится поиск библиотек. После чего вызвать ldconfig без каких-либо опций, но с правами суперпользователя. Всё должно заработать.
Ну и в конце поговорим о том как уживаются вместе статические и динамические версии библиотек. В чем собственно вопрос? Выше, когда обсуждались принятые имена и расположение файлов библиотек было сказано, что файлы статической и динамической версий библиотеки хранятся в одном и том же каталоге. Как же gcc узнает какой тип библиотеки мы хотим использовать? По умолчанию предпочтение отдается динамической библиотеки. Если линковщик находит файл динамической библиотеки, он не задумываясь цепляет его к исполняемому файлу программы:
$ ls
hello.h libhello.a libhello.so libhello.so.2 libhello.so.2.4.0.5 main.c
$ gcc -Wall -c main.c
$ gcc -o main main.o -L. -lhello -Wl,-rpath,.
$ ldd main
linux-vdso.so.1 => (0x00007fffe1bb0000)
libhello.so.2 => ./libhello.so.2 (0x00007fd50370b000)
libc.so.6 => /lib/libc.so.6 (0x00007fd50336c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd50390f000)
$ du -h main
12K main
Обратите внимание на размер исполняемого файла программы. Он минимально возможный. Все используемые библиотеки линкуются динамически.
Существует опция gcc -static — указание линковщику использовать только статические версии всех необходимых приложению библиотек:
$ gcc -static -o main main.o -L. -lhello
$ file main
main: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.15, not stripped
$ ldd main
не является динамическим исполняемым файлом
$ du -h main
728K main
Размер исполняемого файла в 60 раз больше, чем в предыдущем примере — в файл включены стандартные библиотеки языка C. Теперь наше приложение можно смело переносить из каталога в каталог и даже на другие машины, код библиотеки hello внутри файла, программа полностью автономна.
Как же быть если необходимо осуществить статическую линковку только части использованных библиотек? Возможный вариант решения — сделать имя статической версии библиотеки отличным от имени разделяемой, а при компиляции приложения указывать какую версию мы хотим использовать на этот раз:
$ mv libhello.a libhello_s.a
$ gcc -o main main.o -L. -lhello_s
$ ldd main
linux-vdso.so.1 => (0x00007fff021f5000)
libc.so.6 => /lib/libc.so.6 (0x00007fd0d0803000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd0d0ba4000)
$ du -h main
12K main
Так как размер кода библиотеки libhello ничтожен,
$ du -h libhello_s.a
4,0K libhello.a
Убедимся, что заданная строка soname действительно
размер получившегося исполняемого файла практически не отличается от размера файла созданного с использованием динамической линковки.
http://pyviy.blogspot.ru/2010/12/gcc.html
This chapter describes the basic knowledge of GCC compilation and provides examples for demonstration. For more information about GCC, run the man gcc command.
- Using GCC for Compilation
- Overview
- Basics
- File Type
- Compilation Process
- Compilation Options
- Multi-file Compilation
- Libraries
- Dynamic Link Library
- Static Link Library
- Examples
- Example for Using GCC to Compile C Programs
- Example for Creating and Using a DLL Using GCC
- Example for Creating and Using an SLL Using GCC
Overview
The GNU Compiler Collection (GCC) is a powerful and high-performance multi-platform compiler developed by GNU. The GCC compiler can compile and link source programs, assemblers, and target programs of C and C++ into executable files. By default, the GCC software package is installed in the openEuler OS.
Basics
File Type
For any given input file, the file type determines which compilation to perform. Table 1 describes the common GCC file types.
Table 1 Common GCC file types
Extension (Suffix) |
Description |
---|---|
.c |
C source code file. |
.C, .cc, or .cxx |
C++ source code file. |
.m |
Objective-C source code file. |
.s |
Assembly language source code file. |
.i |
Preprocessed C source code file. |
.ii |
Preprocessed C++ source code file. |
.S |
Pre-processed assembly language source code file. |
.h |
Header file contained in the program. |
.o |
Target file after compilation. |
.so |
Dynamic link library, which is a special target file. |
.a |
Static link library. |
.out |
Executable files, which do not have a fixed suffix. The system distinguishes executable files from inexecutable files based on file attributes. If the name of an executable file is not given, GCC generates a file named a.out. |
Compilation Process
Using GCC to generate executable files from source code files requires preprocessing, compilation, assembly, and linking.
- Preprocessing: Preprocess the source program (such as a .c file) to generate an .i file.
- Compilation: Compile the preprocessed .i file into an assembly language to generate an .s file.
- Assemble: Assemble the assembly language file to generate the target file .o.
- Linking: Link the .o files of each module to generate an executable program file.
The .i, .s, and .o files are intermediate or temporary files. If the GCC is used to compile programs in C language at a time, these files will be deleted.
Compilation Options
GCC compilation command format: gcc [options] [filenames]
In the preceding information:
options : compilation options.
filenames : file name.
GCC is a powerful compiler. It has many options, but most of them are not commonly used. Table 2 describes the common options.
Table 2 Common GCC compilation options
options Value |
Description |
Example |
---|---|---|
-c |
Compiles and assembles specified source files to generate target files without linking them. It is usually used to compile subprogram files. |
# Use the -c option to compile the source files test1.c and test2.c. gcc -c test1.c test2.c |
-S |
Compiles the specified source file to generate an assembly language file with the .s suffix but without assembling it. |
# Use the compiler to preprocess circle.c, translate it into assembly language, and store the result in circle.s. gcc -S circle.c |
-E |
Preprocesses specified source files without compiling them. By default, the output of the preprocessor is imported to a standard output stream, such as a display. You can use the -o option to import it to an output file. |
# Export the preprocessing result to the circle.i file. gcc -E circle.c -o circle.i |
-o file |
Generates a specified output file when an executable file is generated. The name must be different from that of the source file. If this option is not given, GCC generates the preset executable file a.out. |
# Use the source file as the input file and the executable file as the output file. That is, compile the entire program. gcc main.c func.c -o app.out |
-g |
Contains standard debugging information in executable programs. |
— |
-L library_path |
Adds the library_path to the library file search path list. |
— |
-I library |
Searches for the specified function library during linking. When GCC is used to compile and link programs, GCC links libc.a or libc.so by default. However, other libraries (such as non-standard libraries and third-party libraries) need to be manually added. |
# Use the -l option to link the math library. gcc main.c -o main.out -lm NOTE: The file name of the math library is libm.a. The prefix lib and suffix .a are standard, and m is the basic name. GCC automatically adds these prefixes and suffixes to the basic name following the -l option. In this example, the basic name is m. |
-I head_path |
Adds the head_path to the search path list of the header file. |
— |
-static |
Performs static compilation and links static libraries. Do not link dynamic libraries. |
— |
-shared |
Default option, which can be omitted.
|
— |
-fPIC (or -fpic) |
Generates location-independent target code that uses a relative address. Generally, the -static option is used to generate a dynamic library file from the PIC target file. |
— |
Multi-file Compilation
There are two methods provided for compiling multiple source files.
-
Multiple source files are compiled at the same time. All files need to be recompiled during compilation.
Example: Compile test1.c and test2.c and link them to the executable file test.
gcc test1.c test2.c -o test
-
Compile each source file, and then link the target files generated after compilation. During compilation, only modified files need to be recompiled.
For example, compile test1.c and test2.c, and link the target files test1.o and test2.o to the executable file test.
gcc -c test1.c gcc -c test2.c gcc test1.o test2.o -o test
Libraries
A library is mature and reusable code that has been written for use. Each program depends on many basic underlying libraries.
The library file name is prefixed with lib and suffixed with .so (dynamic library) or .a (static library). The middle part is the user-defined library file name, for example, libfoo.so or libfoo.a. Because all library files comply with the same specifications, the lib prefix can be omitted when the -l option specifies the name of the linked library file. That is, when GCC processes -lfoo, the library file libfoo.so or libfoo.a is automatically linked. When creating a library, you must specify the full file name libfoo.so or libfoo.a.
Libraries are classified into static libraries and dynamic libraries based on the linking time. The static library links and packs the target file .o generated by assembly and the referenced library into an executable file in the linking phase. The dynamic library is not linked to the target code when the program is compiled, but is loaded when the program is run. The differences are as follows:
-
The resource usage is different.
The static library is a part of the generated executable file, while the dynamic library is a separate file. Therefore, the sizes and occupied disk space of the executable files of the static library and dynamic library are different, which leads to different resource usage.
-
The scalability and compatibility are different.
If the implementation of a function in the static library changes, the executable file must be recompiled. For the executable file generated by dynamic linking, only the dynamic library needs to be updated, and the executable file does not need to be recompiled.
-
The dependency is different.
The executable file of the static library can run without depending on any other contents, while the executable file of the dynamic library must depend on the dynamic library. Therefore, the static library is convenient to migrate.
-
The loading speeds are different.
Static libraries are linked together with executable files, while dynamic libraries are linked only when they are loaded or run. Therefore, for the same program, static linking is faster than dynamic linking.
Dynamic Link Library
You can use the -shared and -fPIC options to create a dynamic link library (DLL) with the source file, assembly file, or target file. The -fPIC option is used in the compilation phase. This option is used when the target file is generated, so as to generate location-independent code.
Example 1: Generate a DLL from the source file.
gcc -fPIC -shared test.c -o libtest.so
Example 2: Generate a DLL from the target file.
gcc -fPIC -c test.c -o test.o
gcc -shared test.o -o libtest.so
To link a DLL to an executable file, you need to list the name of the DLL in the command line.
Example: Compile main.c and libtest.so into app.out. When app.out is running, the link library libtest.so is dynamically loaded.
gcc main.c libtest.so -o app.out
In this mode, the libtest.so file in the current directory is used.
If you choose to search for a DLL, to ensure that the DLL can be linked when the program is running, you must implement by using one of the following methods:
-
Save the DLL to a standard directory, for example, /usr/lib.
-
Add the DLL path libraryDIR to the environment variable LD_LIBRARY_PATH.
$ export LD_LIBRARY_PATH=libraryDIR:$LD_LIBRARY_PATH
NOTE:
LD_LIBRARY_PATH is an environment variable of the DLL. If the DLL is not in the default directories (/lib and /usr/lib), you need to specify the environment variable LD_LIBRARY_PATH. -
Add the DLL path libraryDIR to /etc/ld.so.conf and run ldconfig, or use the DLL path libraryDIR as a parameter to run ldconfig.
gcc main.c -L libraryDIR -ltest -o app.out
export LD_LIBRARY_PATH=libraryDIR:$LD_LIBRARY_PATH
Static Link Library
To create a static link library (SLL), you need to compile the source file to the target file, and then run the ar command to compress the target file into an SLL.
Example: Compile and compress source files test1.c, test2.c, and test3.c into an SLL.
gcc -c test1.c test2.c test3.c
ar rcs libtest.a test1.o test2.o test3.o
The ar command is a backup compression command. You can compress multiple files into a backup file (also called an archive file) or extract member files from the backup file. The most common use of ar is to compress the target files into an SLL.
The format of the ar command to compress the target files into an SLL is as follows:
ar rcs Sllfilename Targetfilelist
- Sllfilename : Name of the static library file.
- Targetfilelist : Target file list.
- r: replaces the existing target file in the library or adds a new target file.
- c: creates a library regardless of whether the library exists.
- s: creates the index of the target file. The speed can be improved when a large library is created.
Example: Create a main.c file to use the SLL.
gcc main.c -L libraryDIR -ltest -o test.out
In the preceding command, libraryDIR indicates the path of the libtest.a library.
Examples
Example for Using GCC to Compile C Programs
-
Run the cd command to go to the code directory. The ~/code directory is used as an example. The command is as follows:
-
Compile the Hello World program and save it as helloworld.c. The following uses the Hello World program as an example. The command is as follows:
Code example:
#include <stdio.h> int main() { printf("Hello World!\n"); return 0; }
-
Run the following command to compile the code in the code directory:
gcc helloworld.c -o helloworld
If no error is reported, the execution is successful.
-
After the compilation is complete, the helloworld file is generated. Check the compilation result. The following is an example:
$ ./helloworld Hello World!
Example for Creating and Using a DLL Using GCC
-
Run the cd command to go to the code directory. The ~/code directory is used as an example. Create the src, lib, and include subdirectories in the directory to store the source file, DLL file, and header file, respectively.
cd ~/code mkdir src lib include
-
Run the cd command to go to the ~/code/src directory and create two functions add.c and sub.c to implement addition and subtraction, respectively.
cd ~/code/src vi add.c vi sub.c
The following is an example of the add.c code:
#include "math.h" int add(int a, int b) { return a+b; }
The following is an example of the sub.c code:
#include "math.h" int sub(int a, int b) { return a-b; }
-
Compile the source files add.c and sub.c into the DLL libmath.so, and store the DLL in the ~/code/lib directory.
gcc -fPIC -shared add.c sub.c -o ~/code/lib/libmath.so
-
Go to the ~/code/include directory, create a header file math.h, and declare the header file of the function.
cd ~/code/include vi math.h
The following is an example of the math.h code:
#ifndef __MATH_H_ #define __MATH_H_ int add(int a, int b); int sub(int a, int b); #endif
-
Run the cd command to go to the ~/code/src directory and create a main.c function that invokes add() and sub().
The following is an example of the math.c code:
#include <stdio.h> #include "math.h" int main() { int a, b; printf("Please input a and b:\n"); scanf("%d %d", &a, &b); printf("The add: %d\n", add(a,b)); printf("The sub: %d\n", sub(a,b)); return 0; }
-
Compile main.c and libmath.so into math.out.
gcc main.c -I ~/code/include -L ~/code/lib -lmath -o math.out
-
Add the path of the DLL to the environment variable.
export LD_LIBRARY_PATH=~/code/lib:$LD_LIBRARY_PATH
-
Run the following command to execute math.out:
The command output is as follows:
Please input a and b: 9 2 The add: 11 The sub: 7
Example for Creating and Using an SLL Using GCC
-
Run the cd command to go to the code directory. The ~/code directory is used as an example. Create the src, lib, and include subdirectories in the directory to store the source file, SLL file, and header file respectively.
cd ~/code mkdir src lib include
-
Run the cd command to go to the ~/code/src directory and create two functions add.c and sub.c to implement addition and subtraction, respectively.
cd ~/code/src vi add.c vi sub.c
The following is an example of the add.c code:
#include "math.h" int add(int a, int b) { return a+b; }
The following is an example of the sub.c code:
#include "math.h" int sub(int a, int b) { return a-b; }
-
Compile the source files add.c and sub.c into the target files add.o and sub.o.
-
Run the ar command to compress the add.o and sub.o target files into the SLL libmath.a and save the SLL to the ~/code/lib directory.
ar rcs ~/code/lib/libmath.a add.o sub.o
-
Go to the ~/code/include directory, create a header file math.h, and declare the header file of the function.
cd ~/code/include vi math.h
The following is an example of the math.h code:
#ifndef __MATH_H_ #define __MATH_H_ int add(int a, int b); int sub(int a, int b); #endif
-
Run the cd command to go to the ~/code/src directory and create a main.c function that invokes add() and sub().
The following is an example of the math.c code:
#include <stdio.h> #include "math.h" int main() { int a, b; printf("Please input a and b:\n"); scanf("%d %d", &a, &b); printf("The add: %d\n", add(a,b)); printf("The sub: %d\n", sub(a,b)); return 0; }
-
Compile main.c and libmath.a into math.out.
gcc main.c -I ~/code/include -L ~/code/lib -lmath -o math.out
-
Run the following command to execute math.out:
The command output is as follows:
Please input a and b: 9 2 The add: 11 The sub: 7
There are three stages of making your C code run: compilation, linking, and runtime.
gcc
is mostly responsible for the compilation, while when linking, it uses a linker program (e.g., ld
) with some additional flags.
- In compilation,
gcc
requires only header (.h
) files to ensure that external function calls have correct parameters matched with the definition. - Next, at the linking stage, the linker
ld
connects/links all compiled objects and libraries to an executable file by wrapping some appropriate headers after combining the objects. This stage produces the executable file or linkable library. In windows, it is PE (Portable Executables format: EXE, DLL, or OCX), in Linux, it is ELF (Executable and Linkable format: no extension,.so
,.dynlib
). - Runtime is when you request the OS to execute the executable file.
There are two types of libraries: static library (in Linux .a
, in Window .lib
) and dynamic library ( .so
, .dynlib
, dll
, .ocx
). The static libraries are copied and embedded directly into the executable file. In opposite, the dynamic libraries are not. The dynamic libraries are supposed to exist in the system where the executable file is executed at runtime.
So, where does gcc
look for the header files and the dynamic libraries?
Check the configuration
In compilation
For c
: echo | gcc -x c -E -Wp,-v - >/dev/null
For c++
: echo | gcc -x c++ -E -Wp,-v - >/dev/null
These are the outputs on my PC.
For C
ignoring nonexistent directory "/usr/lib/gcc/x86_64-pc-linux-gnu/12.1.0/../../../../x86_64-pc-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-pc-linux-gnu/12.1.0/include
/usr/local/include
/usr/lib/gcc/x86_64-pc-linux-gnu/12.1.0/include-fixed
/usr/include
End of search list.
echo | gcc -x c -E -Wp,-v - >/dev/null
For c++
ignoring nonexistent directory "/usr/lib/gcc/x86_64-pc-linux-gnu/12.1.0/../../../../x86_64-pc-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-pc-linux-gnu/12.1.0/include
/usr/local/include
/usr/lib/gcc/x86_64-pc-linux-gnu/12.1.0/include-fixed
/usr/include
End of search list.
echo | gcc -x c++ -E -Wp,-v - >/dev/null
In linking
With ld command
To list ld
‘s search directories:
ld --verbose | grep SEARCH_DIR | tr -s ' ;' \\012
The output from my PC.
In Arch Linux:
SEARCH_DIR("/usr/x86_64-pc-linux-gnu/lib64")
SEARCH_DIR("/usr/lib")
SEARCH_DIR("/usr/local/lib")
SEARCH_DIR("/usr/x86_64-pc-linux-gnu/lib")
ld --verbose | grep SEARCH_DIR | tr -s ' ;' \\012
In Ubuntu:
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu")
SEARCH_DIR("=/lib/x86_64-linux-gnu")
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu")
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64")
SEARCH_DIR("=/usr/local/lib64")
SEARCH_DIR("=/lib64")
SEARCH_DIR("=/usr/lib64")
SEARCH_DIR("=/usr/local/lib")
SEARCH_DIR("=/lib")
SEARCH_DIR("=/usr/lib")
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64")
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib")
ld --verbose | grep SEARCH_DIR | tr -s ' ;' \\012
To debug ld
, use the LD_DEBUG
environment variable. See help with LD_DEBUG=help ld
.
Valid options for the LD_DEBUG environment variable are:
libs display library search paths
reloc display relocation processing
files display progress for input file
symbols display symbol table processing
bindings display information about symbol binding
versions display version dependencies
scopes display scope information
all all previous options combined
statistics display relocation statistics
unused determined unused DSOs
help display this help message and exit
To direct the debugging output into a file instead of standard output
a filename can be specified using the LD_DEBUG_OUTPUT environment variable.
LD_DEBUG=help ld
The most useful value to debug library path searching is LD_DEBUG=libs ld foo
. Sample output on my PC:
96426: find library=libbfd-2.38.so [0]; searching
96426: search cache=/etc/ld.so.cache
96426: trying file=/usr/lib/libbfd-2.38.so
96426:
96426: find library=libctf.so.0 [0]; searching
96426: search cache=/etc/ld.so.cache
96426: trying file=/usr/lib/libctf.so.0
96426:
96426: find library=libc.so.6 [0]; searching
96426: search cache=/etc/ld.so.cache
96426: trying file=/usr/lib/libc.so.6
96426:
96426: find library=libz.so.1 [0]; searching
96426: search cache=/etc/ld.so.cache
96426: trying file=/usr/lib/libz.so.1
96426:
96426:
96426: calling init: /lib64/ld-linux-x86-64.so.2
96426:
96426:
96426: calling init: /usr/lib/libc.so.6
96426:
96426:
96426: calling init: /usr/lib/libz.so.1
96426:
96426:
96426: calling init: /usr/lib/libbfd-2.38.so
96426:
96426:
96426: calling init: /usr/lib/libctf.so.0
96426:
96426:
96426: initialize program: ld
96426:
96426:
96426: transferring control: ld
96426:
ld: /home/transang/foo/dqrdc2.o: undefined reference to symbol 'memmove@@GLIBC_2.2.5'
ld: /usr/lib/libc.so.6: error adding symbols: DSO missing from command line
96426:
96426: calling fini: ld [0]
96426:
96426:
96426: calling fini: /usr/lib/libctf.so.0 [0]
96426:
96426:
96426: calling fini: /usr/lib/libbfd-2.38.so [0]
96426:
96426:
96426: calling fini: /usr/lib/libz.so.1 [0]
96426:
LD_DEBUG=libs ld foo
With GCC command
gcc
wraps some flags when calling ld
. Therefore, the directories list is different. To list them:
gcc -print-search-dirs | sed '/^lib/b 1;d;:1;s,/[^/.][^/]*/\.\./,/,;t 1;s,:[^=]*=,:;,;s,;,; ,g' | tr \; \\012 | tr : \\012
The output from my PC.
In Arch Linux:
libraries
/usr/lib/gcc/x86_64-pc-linux-gnu/12.1.0/
/usr/x86_64-pc-linux-gnu/lib/x86_64-pc-linux-gnu/12.1.0/
/usr/x86_64-pc-linux-gnu/lib/
/usr/lib/x86_64-pc-linux-gnu/12.1.0/
/usr/lib/
/lib/x86_64-pc-linux-gnu/12.1.0/
/lib/
/usr/lib/x86_64-pc-linux-gnu/12.1.0/
/usr/lib/
/usr/x86_64-pc-linux-gnu/lib/
/usr/lib/
/lib/
/usr/lib/
gcc -print-search-dirs | sed '/^lib/b 1;d;:1;s,/[^/.][^/]*/\.\./,/,;t 1;s,:[^=]*=,:;,;s,;,; ,g' | tr \; \\012 | tr : \\012
In Ubuntu:
libraries
/usr/lib/gcc/x86_64-linux-gnu/7/
/usr/x86_64-linux-gnu/lib/x86_64-linux-gnu/7/
/usr/x86_64-linux-gnu/lib/x86_64-linux-gnu/
/usr/x86_64-linux-gnu/lib/
/usr/lib/x86_64-linux-gnu/7/
/usr/lib/x86_64-linux-gnu/
/usr/lib/
/lib/x86_64-linux-gnu/7/
/lib/x86_64-linux-gnu/
/lib/
/usr/lib/x86_64-linux-gnu/7/
/usr/lib/x86_64-linux-gnu/
/usr/lib/
/usr/x86_64-linux-gnu/lib/
/usr/lib/
/lib/
/usr/lib/
gcc -print-search-dirs | sed '/^lib/b 1;d;:1;s,/[^/.][^/]*/\.\./,/,;t 1;s,:[^=]*=,:;,;s,;,; ,g' | tr \; \\012 | tr : \\012
- My
gcc
‘s build information withgcc --verbose
.
In Arch Linux:
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/12.1.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: /build/gcc/src/gcc/configure --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-bootstrap --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --with-linker-hash-style=gnu --with-system-zlib --enable-__cxa_atexit --enable-cet=auto --enable-checking=release --enable-clocale=gnu --enable-default-pie --enable-default-ssp --enable-gnu-indirect-function --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-multilib --enable-plugin --enable-shared --enable-threads=posix --disable-libssp --disable-libstdcxx-pch --disable-werror --with-build-config=bootstrap-lto --enable-link-serialization=1
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 12.1.0 (GCC)
gcc --verbose
In Ubuntu:
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.4.0-1ubuntu1~18.04.1' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)
gcc --verbose
Note: gcc
looks for the libraries’ names from left to right and stops finding when it matches the first library with the searching term.
To pass additional flags to the underlining ld
command when invoking gcc
. Use -Wl,<command separated flags>
. The following command options have the same effect:
gcc -Wl,flag1,-flag2,flag3,flag4
# same as
gcc -Wl,flag1,-flag2 -Wl,flag3,flag4
# same as
ld flag1 -flag2 flag3 flag4
- You also can show the library search directories list by adding the verbose flag
-v
when linking.
For example:gcc -v foo.o bar.o -o foo
In runtime
There are many tools to analyze ELF files, namely: ldd
(Linux only), otool
(Mac only), objdump
(Linux only), nm
(POSIX), readelf
.
ldd command (Linux only)
To figure out which libraries are linked with a program and the libraries’ full path, use ldd
. For example, ldd foo
. ldd
is the most useful command because it recursively looks for required dynamic libraries and outputs their paths in the running system, while other commands only look for direct dependencies.
Sample output on my PC.
linux-vdso.so.1 (0x00007fff08bf3000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fbc85170000)
libblas.so.3 => /lib/x86_64-linux-gnu/libblas.so.3 (0x00007fbc85110000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbc84f1f000)
/lib64/ld-linux-x86-64.so.2 (0x00007fbc852e8000)
libopenblas.so.0 => /lib/x86_64-linux-gnu/libopenblas.so.0 (0x00007fbc82d3a000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fbc82d17000)
libgfortran.so.5 => /lib/x86_64-linux-gnu/libgfortran.so.5 (0x00007fbc82a77000)
libquadmath.so.0 => /lib/x86_64-linux-gnu/libquadmath.so.0 (0x00007fbc82a2b000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fbc82a11000)
Similar to ld
command, LD_DEBUG
environment variable can be used to debug ldd
. In fact, setting LD_DEBUG
also outputs the debug information when executing a command, e.g., LD_DEBUG=all ./foo
.
otool command (Mac)
Use otool -L foo
. Sample output:
foo:
mylib.so (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.100.3)
objdump command (Linux)
Use objdump -p foo | grep NEEDED
. Sample output:
NEEDED libm.so.6
NEEDED libmvec.so.1
NEEDED libc.so.6
objdump -p foo | grep NEEDED
readelf command
Use readelf -d foo | grep NEEDED
. Sample output:
0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
0x0000000000000001 (NEEDED) Shared library: [libmvec.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
readelf -d foo | grep NEEDED
How to add custom directories as a library search directory
Custom library paths, which are added to the compilation/linking, by the following method have higher priority than the default library search directories. In other words, if a library is found in a customized directory, it will be selected rather than the library in system default.
Compilation
Method 1: use CPATH
(c or c++), C_INCLUDE_PATH
(c only), CPLUS_INCLUDE_PATH
(c++ only). For example: CPATH=/home/transang/my_libs_headers gcc -c foo.c -o foo.o
.
Method 2: use -I<dir_path>
to gcc
when compiling. For example: gcc -I/home/transang/my_libs_headers -c foo.c -o foo.o
.
Linking
Method 1: To add custom directories to the library linking search directories list, use LIBRARY_PATH
environment variable. For example: LIBRARY_PATH=./mylib:/home/transang/my_libs gcc foo.o bar.o -o foo
.
Method 2: add the flag -L<dir_path>
to gcc
when linking. For example: gcc -L/home/transang/my_libs -L./mylib foo.o bar.o -o foo
.
From GCC man page:
-Ldir Add directory dir to the list of directories to be searched for -l.
Method 3: add the flag -rpath-link <dir_path>
to ld
. For example: ld -rpath-link /home/transang/my_libs
or gcc -Wl,-rpath-link,/home/transang/my_libs
.
Method 4:
add the flag -rpath <dir_path>
to ld
. For example: ld -rpath /home/transang/my_libs
or gcc -Wl,-rpath,/home/transang/my_libs
. Note: this affects the library searching path in runtime
.
Note 1: LIBRARY_PATH
environment variable’s value does not affect the results of ld --verbose
and gcc -print-search-dirs
commands.
Note 2: method 3 and 4 do not work when I tried but the ld
manpage says it works so I leave them for reference.
The linker uses the following search paths to locate required shared libraries.
1. Any directories specified by-rpath-link
options.
2. Any directories specified by-rpath
options. The difference between-rpath
and-rpath-link
is that directories specified by-rpath
options are included in the executable and used at runtime, whereas the-rpath-link
option is only effective at link time.
3. On an ELF system, if the-rpath
andrpath-link
options were not used, search the contents of the environment variableLD_RUN_PATH
.
4. On SunOS, if the-rpath
option was not used, search any directories specified using-L
options.
5. For a native linker, the contents of the environment variableLD_LIBRARY_PATH
.
6. The default directories, normally `/lib’ and `/usr/lib’.
Runtime
For dynamic libraries, regardless of what you have configured for library searching paths. At runtime, the OS uses an independent configuration.
To add customized directories, use LD_LIBRARY_PATH
environment variable when executing the executable file. For e.g. LD_LIBRARY_PATH=./mylib:/home/transang/my_libs ./foo
.
How to add dynamic libraries when linking
So far, I have introduced a way to figure out the current configuration and modify it to add more directories for library searching.
To link a library, add -l<lib_name>
flag to the gcc
command when linking. If the lib_name
does not start with :
, gcc
will look for a library named lib<lib_name>.so
. Otherwise, the file name lib_name
will be searched.
For example: with -lfoo
, gcc
looks for libfoo.so
file. With -l:foo.so
, gcc
looks for foo.so
file (Look at it precisely, -l:
requires specifying library name extension, -l
adds lib
prefix and extension suffix to the library name).
Clear environment
In some cases, you may want a clean build that isolates from the running machine. You can use the following command:
LDFLAGS= LIBRARY_PATH= LD_LIBRARY_PATH= CPATH= C_INCLUDE_PATH= CPLUS_INCLUDE_PATH= CPPFLAGS= CFLAGS= gcc <command>
Moreover, the system library header searching path is configured in /etc/ld.so.conf
.
# cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
cat /etc/ld.so.conf
By default, /etc/ld.so.conf
refers to the content of all files in the directory /etc/ld.so.conf.d
. You can look into the directory to control the configuration.
total 24
drwxr-xr-x 2 root root 4096 Jun 4 15:55 .
drwxr-xr-x 90 root root 4096 Jun 14 14:00 ..
-rw-r--r-- 1 root root 66 Apr 4 08:20 cuda.conf
-rw-r--r-- 1 root root 21 May 24 07:30 fakeroot.conf
-rw-r--r-- 1 root root 17 Nov 27 2021 openmpi.conf
-rw-r--r-- 1 root root 16 May 27 06:09 R.conf
ls -al /etc/ld.so.conf.d/
/opt/cuda/lib64
/opt/cuda/nvvm/lib64
/opt/cuda/extras/CUPTI/lib64
cat /etc/ld.so.conf.d/cuda.conf
/usr/lib/libfakeroot
cat /etc/ld.so.conf.d/fakeroot.conf
/usr/lib/openmpi
cat /etc/ld.so.conf.d/openmpi.conf
/usr/lib/R/lib
cat /etc/ld.so.conf.d/R.conf
Embedding library paths for runtime at linking
rpath/runpath
can be configured at the linking stage to add library paths to the ELF file header which will be used to scan for dynamic libraries at runtime. See dynamic array tags table here: DT_RPATH (value 15) and DT_RUNPATH (value 29). More about rpath at its wiki page.
Compare RPATH and RUNPATH
runpath and rpath are nearly similar with runpath being newer and gradually replacing rpath.
Search order with LD_LIBRARY_PATH
The search order is as follows, from high to low:
- rpath
- LD_LIBRARY_PATH
- runpath
Nested dependencies
DT_RUNPATH is used only for direct dependencies, while DT_RPATH is used for nested dependencies (ref).
Set rpath with ld
Use ld -rpath <path>
or gcc -Wl,-rpath,<path>
.
For example: gcc main.o -o main -l:mylib.so -Wl,-rpath,/home/transang/mylib -L.
. Then at runtime, there is no need to specify LD_LIBRARY_PATH
, just use ./main
.
Note: the correct flag to embed the search path to the ELF file is -rpath
, not -rpath-link
.
By default, ld
/ gcc
use RPATH. To use RUNPATH, use --enable-new-dtags
. For example: gcc main.o -o main -l:mylib.so -Wl,-rpath,./libs,--enable-new-dtags -L.
To explicitly use RPATH, use --disable-new-dtags
.
Although, RUNPATH is considered newer, setting it as default caused many compilations failed (ref). So that if you want to use -rpath
, it is better to also explicitly specify its usage with --disable-new-dtags
/ --enable-new-dtags
.
Set runpath with patchelf
Use patchelf --set-rpath <path>
. For example:
gcc main.o -o main -l:mylib.so -L.
patchelf --set-rpath . main
./main
By default patchelf
use RUNPATH. patchelf
also clears existing RPATH/RUNPATH headers.
Get configured rpath/runpath
Use one of the following commands
objdump -x foo | grep RPATH
readelf -d foo | grep RPATH
objdump -x foo | grep RUNPATH
readelf -d foo | grep RUNPATH
Sample outputs:
RUNPATH /home/transang/mylib
objdump -x main | grep RUNPATH
0x000000000000001d (RUNPATH) Library runpath: [/home/transang/mylib]
readelf -d main | grep RUNPATH
0x000000000000000f (RPATH) Library rpath: [/home/transang/mylib]
readelf -d main | grep RPATH
RPATH /home/transang/mylib
objdump -x main | grep RPATH
Relative search path
rpath/runpath supports the following path patterns:
- Absolute paths: starting with
/
. - Paths relative to the current working dir: starting with
./
. - Paths relative to the executable file: the
$ORIGIN
variable is expanded at runtime to the absolute path of the directory containing the executable file.
For example, you can use patchelf --set-rpath '$ORIGIN/libs' main
to add the libs
directory next to main
to the library search path.
Security concern with the setuid/setgid flag
For files with the setuid/setgid flag set (via chmod a+s <file>
command), LD_LIBRARY_PATH
is ignored, any rpath/runpath containing $ORIGIN
expansion are also ignored.
Also note that ldd
does not consider the setuid flag checking in its output.
Задача
Передо мной возникла задача написать загрузчик библиотек, имеющий возможность предоставить какие-то интерфейсные функции внешней динамической библиотеке. Решение должно быть максимально кроссплатформенно (как минимум, работать на Linux и Windows). Загружаться должны библиотеки, написанные на различных языках программирования, поддерживающих создание динамических библиотек. В качестве примера были выбраны языки C и Pascal.
Решение
Основной загрузчик библиотек написан на языке C. Для того, чтобы загружаемые библиотеки имели возможность использовать функции основной программы, основная программа разделена на 2 части: на основной и подгружаемый модули. Основной модуль нужен просто для запуска программы, подгружаемый модуль — это также динамическая библиотека, связываемая с основным модулем во время его запуска. В качестве компиляторов были выбраны gcc (MinGW для Windows) и fpc.
Здесь будет приведён упрощённый пример программы, позволяющий разобраться в данном вопросе и учить первокурсников писать модули к своей программе (в школе часто преподают именно Pascal).
Загрузчик библиотек
Главный файл загрузчика библиотек выглядит очень просто:
main.c
#include "loader.h"
#ifdef __cplusplus
extern "C" {
#endif
int main(int argc, char *argv[]) {
if (argc > 1) {
loadRun(argv[1]);
}
return 0;
}
#ifdef __cplusplus
}
#endif
А это модуль, отвечающий за загрузку динамических библиотек, который сам вынесен в динамическую библиотеку для того, чтобы подгружаемые библиотеки имели возможность использовать предоставляемые им функции:
loader.c
#include "loader.h"
#include "functions.h"
#include <stdio.h>
#ifndef WIN32
#include <dlfcn.h>
#else
#include <windows.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
void printString(const char * const s) {
printf("String from library: %s\n", s);
}
void loadRun(const char * const s) {
void * lib;
void (*fun)(void);
#ifndef WIN32
lib = dlopen(s, RTLD_LAZY);
#else
lib = LoadLibrary(s);
#endif
if (!lib) {
printf("cannot open library '%s'\n", s);
return;
}
#ifndef WIN32
fun = (void (*)(void))dlsym(lib, "run");
#else
fun = (void (*)(void))GetProcAddress((HINSTANCE)lib, "run");
#endif
if (fun == NULL) {
printf("cannot load function run\n");
} else {
fun();
}
#ifndef WIN32
dlclose(lib);
#else
FreeLibrary((HINSTANCE)lib);
#endif
}
#ifdef __cplusplus
}
#endif
Заголовочные файлы
Это всё была реализация, а теперь заголовочные файлы.
Вот интерфейс модуля, загружающего динамические библиотеки, для основного модуля:
loader.h
#ifndef LOADER_H
#define LOADER_H
#ifdef __cplusplus
extern "C" {
#endif
extern void loadRun(const char * const s);
#ifdef __cplusplus
}
#endif
#endif
Вот интерфейс загрузчика для загружаемых им динамических библиотек (перечень функций, которые динамические библиотеки могут использовать в основной программе):
functions.h
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
#ifdef __cplusplus
extern "C" {
#endif
extern void printString(const char * const s);
#ifdef __cplusplus
}
#endif
#endif
Как видно, здесь всего одна функция printString для примера.
Компиляция загрузчика
Пример недистрибутивной компиляции (в случае Windows в опции компилятора просто нужно добавить -DWIN32):
$ gcc -Wall -c main.c
$ gcc -Wall -fPIC -c loader.c
$ gcc -shared -o libloader.so loader.o -ldl
$ gcc main.o -ldl -o run -L. -lloader -Wl,-rpath,.
Дистрибутивная компиляция от недистрибутивной отличается тем, что в дистрибутивном случае динамические библиотеки ищутся в /usr/lib и имеют вид lib$(NAME).so.$(VERSION), в случае недистрибутивной компиляции они называются lib$(NAME).so, а ищутся в каталоге запуска программы.
Теперь посмотрим, что у нас получилось после компиляции:
$ nm run | tail -n 2
U loadRun
08048504 T main
$ nm libloader.so| tail -n 4
000005da T loadRun
000005ac T printString
U printf@@GLIBC_2.0
U puts@@GLIBC_2.0
Тут видим, что функции, отмечаемые как U ищутся во внешних динамических библиотеках, а функции, отмечаемые как T предоставляются модулем. Это бинарный интерфейс программы (ABI).
Динамические библиотеки
Теперь приступим к описанию самих динамических библиотек.
Библиотека на языке C
Вот пример простейшей библиотеки на языке C:
lib.c
#include "functions.h"
#ifdef __cplusplus
extern "C" {
#endif
void run(void) {
printString("Hello, world!");
}
#ifdef __cplusplus
}
#endif
Здесь везде окружение extern «C» {} нужно для того, чтобы нашу программу можно было компилировать при помощи C++-компилятора, такого, как g++. Просто в C++ можно объявлять функции с одним и тем же именем, но с разной сигнатурой, соответственно в этом случае используется так называемая декорация имён функций, то есть сигнатура функций записывается в ABI. Окружение extern «C» {} нужно для того, чтобы не использовалась эта декорация (тем более, что эта декорация зависит от используемого компилятора).
Компиляция
$ gcc -Wall -fPIC -c lib.c
$ gcc -shared -o lib.so lib.o
ABI:
$ nm lib.so | tail -n 2
U printString
0000043c T run
Запуск:
$ ./run lib.so
String from library: Hello, world!
Если мы уберём в нашем модуле окружение extern «C» {} и скомпилируем при помощи g++ вместо gcc, то увидим следующее:
$ nm lib.so | grep run
0000045c T _Z3runv
То есть, как и ожидалось, ABI библиотеки изменился, теперь наш загрузчик не сможет увидеть функцию run в этой библиотеке:
$ ./run lib.so
cannot load function run
Библиотека на языке Pascal
Как мы увидели выше, для того, чтобы наш загрузчик видел функции в динамических библиотеках, созданных компилятором C++, пришлось дополнять наш код вставками extern «C» {}, это притом, что C/C++-компиляторы и языки родственные. Что уж говорить про компилятор FreePascal совершенно другого языка — Pascal? Естественно, что и тут без дополнительных телодвижений не обойтись.
Для начала нам нужно научиться использовать экспортированные в C функции для динамических библиотек. Вот пример аналогичного C/C++ заголовочного файла на языке Pascal:
func.pas
unit func;
interface
procedure printString(const s:string); stdcall; external name 'printString';
implementation
end.
Вот пример самого модуля на языке Pascal:
modul.pas
library modul;
uses
func;
procedure run; stdcall;
begin
printString('Hello from module!');
end;
exports
run;
begin
end.
Компиляция
$ fpc -Cg modul.pas
Компилятор Free Pascal версии 2.5.1 [2011/02/21] для i386
Copyright (c) 1993-2010 by Florian Klaempfl
Целевая ОС: Linux for i386
Компиляция modul.pas
Компоновка libmodul.so
/usr/bin/ld: warning: link.res contains output sections; did you forget -T?
/usr/bin/ld: warning: creating a DT_TEXTREL in a shared object.
13 строк скомпилиpовано, 6.6 сек
Смотрим ABI получившейся библиотеки:
$ nm libmodul.so
U printString
000050c0 T run
Как видим, ничего лишнего, однако настораживает предупреждение ld во время компиляции. Находим в гугле возможную причину предупреждения, это связано с компиляцией без PIC (Position Independent Code — код не привязан к физическому адресу), однако в man fpc находим, что наша опция -Cg должна генерировать PIC-код, что само по себе странно, видимо fpc не выполняет своих обещаний, либо я делаю что-то не так.
Теперь попробуем убрать в нашем заголовочном файле кусок name ‘printString’ и посмотрим, что выдаст компилятор теперь:
$ nm libmodul.so
U FUNC_PRINTSTRING$SHORTSTRING
000050d0 T run
Как видно, декорация в FreePascal совсем другого рода, чем в g++ и тоже присутствует.
При запуске с этим модулем получаем:
$ ./run libmodul.so
./run: symbol lookup error: ./libmodul.so: undefined symbol: FUNC_PRINTSTRING$SHORTSTRING
А с правильным модулем получаем:
$ ./run libmodul.so
String from library: Hello from module!
Вот и всё, своей задачи — использование динамических библиотек, написанных на различных языках — мы достигли, и наш код работает как под Linux, так и под Windows (у самого Mac’а нет, поэтому не смотрел, как там с этим). Кроме того, загруженные библиотеки имеют возможность использовать предоставляемые в основной программе функции для взаимодействия с ней (то есть являются плагинами основной программы).
Сам загрузчик может выполняться в отдельном процессе, чтобы эти плагины не смогли сделать ничего лишнего с основной программой. Соответственно, основная программа может быть написана на любом другом удобном языке программирования (например, на Java или на том же C++).
Литература
- Загрузка DLL в C++.
- Linux первые шаги.
- Создание модуля Apache на языке Pascal.