У меня есть тестовая библиотека Заголовочный файл ObjectA.h
#pragma once
namespace sarora::testing {
void testingObject();
void againTesting();
} // namespace sarora::testing
CPP-файл ObjectA.cpp
#include "ObjectA.h"
#include <iostream>
namespace sarora::testing {
void testingObject() {
std::cout << "testingObject" << std::endl;
}
void againTesting() {
std::cout << "againTesting" << std::endl;
}
} // namespace sarora::testing
Теперь у меня есть деньги за это, определяемые как
cpp_library(
name = "object",
srcs = ["ObjectA.cpp"],
headers = ["ObjectA.h"],
link_whole = True,
)
Закончив работу с библиотекой cpp, я добавляю ее в файл main.cpp.
#include "ObjectA.h"
#include <iostream>
using namespace std;
int main(int argc, char* argv[]) {
sarora::testing::testingObject();
}
Это деньги за финальный основной
cpp_binary(
name = "test",
srcs = ["test.cpp"],
deps = [
":object",
],
)
Теперь обратите внимание, что я использовал только объект тестирования в файле main.cpp. Когда я пытаюсь проверить таблицу символов, я выполняю «nm main_executable_path | grep TestingObject» и получаю символ
Но когда я снова провожу тестирование grep, я не вижу символа, так какая функция link_whole определена здесь в buck https://buck.build/rule/cxx_library.html#link_whole
@TedLyngmo Я также обновил пост, указав деньги за main… и да, это общая библиотека… Когда я смотрю на символы в библиотеке, они оба присутствуют. Что тогда делает link_whole? В документации четко сказано: «Всегда включайте всю библиотеку», поэтому ничего не удаляйте.
cpp_library
, cpp_binary
? Вы имели в виду cxx_library
, cxx_binary
?
@ShivanshuArora Бинарный файл связан с библиотекой (которая содержит обе функции), но в двоичной привязке к неиспользуемой функции нет ничего, что не было бы видно при просмотре двоичного файла. Зачем вам это вообще нужно? Создайте статическую библиотеку, и она, вероятно, появится и в двоичном виде, если только оптимизатор времени компоновки не выбросит ее.
@TedLyngmo По сути, я просто пытаюсь узнать, что делает link_whole и как я могу поместить символ AgainTesting в основной исполняемый файл, даже если он используется не только для целей тестирования. Каков пример использования link_whole и как?
@MikeKinghan да, именно это я и имею в виду, cpp_ — это просто внутреннее расширение, которое мы используем
Здесь вы ожидаете достичь:
libobject
с опцией link_whole
test
, связанную с libobject
, имея
libobject
в целом статически связан с test
, так что -test
вы увидите, что все символы
определенные в libobject
, также определены в test
.Почему вы не можете сделать это с общей библиотекой
Это невозможно по природе разделяемой библиотеки, в отличие от
статическая библиотека. (Я буду придерживаться обычных соглашений об именах в стиле Unix для
библиотеки — libfoo.so
— это общая сборка библиотеки foo
; libfoo.a
это сборка статической библиотеки, хотя система сборки баксов немного другая
те.)
Когда вы связываете исполняемый файл с общей библиотекой libfoo.so
, никакая часть libfoo.so
статически транскрибируется в вашу программу. Если ваша программа не содержит неопределенных ссылок на
символы, определенные libfoo.so
, то по умолчанию абсолютно ничего о libfoo.so
не
записано в вашей программе. Его могло бы и не быть. Если ваша программа делает
сделать неопределенную ссылку на любой символ sym
, определенный libfoo.so
- и libfoo.so
первая библиотека, которую находит статический компоновщик и определяет sym
- затем статическая
компоновщик просто:
sym
из глобальной таблицы символов
исполняемый файл в свою динамическую таблицу символов.libfoo.so
.По умолчанию это все, что происходит. sym
остается неопределенным символом в исполняемом файле.
Компоновщику времени выполнения остается заметить это примечание, когда исполняемый файл загружается для запуска.
программе, найдите libfoo.so
, загрузите ее в адресное пространство программы и разрешите любые
неопределенные динамические символы в программе, определяемые libfoo.so
или любым другим
общие библиотеки, необходимые программе.
Вы можете переопределить поведение по умолчанию, передав параметр --no-as-needed
статическому объекту.
линкер. Но это просто приведет к тому, что в исполняемом файле появится пометка «Эта программа требует libfoo.so
».
даже если это неправда, т. е. если программа на самом деле не делает неопределенных ссылок на символы
определяется libfoo.so
. Вот и все. Общая библиотека – это библиотека, связь с которой
программа полностью оставляет динамическое разрешение символов компоновщику среды выполнения. Вы не
даже нужно заставить статический компоновщик писать заметки в исполняемом файле, чтобы сообщить среде выполнения
компоновщику, какие общие библиотеки ему нужны. Сама программа может вызывать компоновщик времени выполнения
найти и загрузить libfoo.so
и передать ему адреса определенных в нем символов.
Это делается на основе первых принципов.
Почему вы можете сделать это с помощью статической библиотеки.
С другой стороны, когда вы связываете исполняемый файл со статической библиотекой libfoo.a
, что происходит?
совершенно другое, как описано в теге Stackoverflow для статических библиотек.
libfoo.a
— это пакет объектных файлов, из которого статический компоновщик выберет только те, которые
ему необходимо разрешить символы, на которые ссылаются, но еще не определены в исполняемом файле, возьмите
их из сумки и статически связать с исполняемым файлом, как и любые другие объектные файлы.
в связи. Ничто, кроме объектного файла, не может быть статически связано с исполняемым файлом. Это значит
что ничто, кроме связывания объектного файла, не может заставить компоновщик физически включить символ
определения в исполняемый файл.
Иногда вам может потребоваться, чтобы статический компоновщик извлек все объектные файлы из сумки и свяжите их в исполняемом файле независимо от того, нужны они или нет. Для этого вы используете варианты компоновщика:
--whole-archive libfoo.a ... --no-whole-archive
если вы вызываете статический компоновщик напрямую. Или:
-Wl,--whole-archive libfoo.a... -Wl,--no-whole-archive
если вы вызываете его через GCC/Clang, как обычно. (Жизненно важно выключить --whole-archive
после всех
библиотеки, к которым вы хотите применить это, потому что оно будет продолжать применяться к последующим библиотекам
пока ты это не сделаешь.)
libfoo.a
можно просто заменить обычной опцией привязки -lfoo
, если при этом действует статическая привязка:
опция компоновщика -Bstatic
, активируемая опцией связи GCC/Clang -static
. Хотя -Bstatic
есть
по сути, компоновщик не будет разрешать -lfoo
в общую библиотеку libfoo.so
- что он и делает по умолчанию -
и примет статическую библиотеку libfoo.a
только в том случае, если сможет ее найти. (-Bstatic
также продолжает действовать до
и если поведение по умолчанию не будет восстановлено с помощью -Bdynamic
).
Почему ваш BUCK-файл не создает то, что вы ожидаете.
Опция link_whole = True
в вашем:
cpp_library(
name = "object",
srcs = ["ObjectA.cpp"],
headers = ["ObjectA.h"],
link_whole = True,
)
должно означать, что:
-Wl,--whole-archive <object-library-name> Wl,--no-whole-archive
записывается в командной строке связывания инструментальной цепочки для программы test
как построено вашим:
cpp_binary(
name = "test",
srcs = ["test.cpp"],
deps = [
":object",
],
)
Вот что произошло бы, если бы <object-library-name>
была статической библиотекой. Но ваш <object-library-name>
= libobject.so
,
общая библиотека. И это потому, что:
libobject.a
. По умолчанию используется общий доступ.и:
test
. Если бы да, то это означало бы, что вы хотите поставить ссылку против libobject.a
и построил его вместо libobject.so
, но по умолчанию Бак предпочитает
общие библиотеки, потому что это предпочтение компоновщика по умолчанию.Поэтому по умолчанию бакс строит libobject.so
и ссылается test
на него. Он знает
что --whole-archive
ничего не значит применительно к libobject.so
, поэтому он игнорирует
link_whole = True
: нет ошибок, нет предупреждений. Даже если:
-Wl,--whole-archive libobject.so Wl,--no-whole-archive
был передан компоновщику, он просто проигнорировал -[no]-whole-archive
; ни ошибки, ни предупреждения.
Бак успешно завершает сборку test
. test
имеет динамику
зависимость от libobject.so
, представленная:
sarora::testing::testingObject()
в
таблица динамических символов test
test
необходимо сделать примечание libobject.so
.Это все, от чего получилось libobject.so
Что вам нужно сделать с файлом BUCK, чтобы увидеть то, что вы ожидали
Прежде чем идти сюда, помните, что если вы свяжете программу с
общую библиотеку, чтобы разрешить символ sym
, тогда вы не хотите и
вам не нужно иметь определение sym
в вашей программе, и вы не получите
один. Если бы в вашей связанной программе было определение sym
, оно могло бы
попали туда только из объектного файла, который определил sym
до любого
общая библиотека, которая определила, что она была достигнута, и любое такое определение общей библиотеки
был бы проигнорирован, поскольку sym
уже был определен.
Чтобы увидеть ожидаемый результат link_whole = True
, вам нужно выполнить одно из следующих действий:
object
.
в ссылках как libobject.a
, а не как по умолчанию libobject.so
. Это потребует:
cxx_library(
...
preferred_linkage = "static",
...
)
или:
cxx_binary(
...
link_style = "static",
...
)
В любом случае (или оба вместе) библиотека object
будет создана как статическая библиотека libobject.a
, и
тогда --whole-archive
и будет иметь смысл, и Бак его применит. Тот и
только объектный файл libobject.a(object.o)
будет извлечен из libobject.a
и
статически связан с test
, принося с собой все определения символов в object.o
, и вы увидите их в глобальном символе
таблица test
. (Но не в его динамическом символе
таблицу, потому что им больше не нужно разрешение во время выполнения.)
Поскольку в libobject.a
будет только один объектный файл, --whole-archive
в данном конкретном случае конечно избыточно: понадобится связка libobject(object.o)
чтобы разрешить sarora::testing::testingObject()
, поэтому он извлечет и свяжет это
объектный файл без принуждения, и этот объектный файл принесет с собой все
символы, которые он определяет, или ссылки, в том числе те, которые test
не нужны. Когда
компоновщик использует объектный файл, он использует его целиком.1.
По той же причине сам libobject.a
в данном конкретном случае является избыточным. С таким же успехом вы можете просто скомпилировать объектный файл object.o
из ObjectA.cpp
и связать его напрямую.
Итог: link_whole
имеет смысл тогда и только тогда, когда вы уверены, что применяете библиотеку.
это статическая библиотека. link_whole
полезен тогда и только тогда, когда вы хотите связать все
объектные файлы в статической библиотеке независимо от того, нужны ли они компоновщику.
Нет необходимости читать дальше, если вы не заинтересованы в демонстрации всего этого.
Демонстрация всего этого с баксом
Исходные файлы:
$ cat foo.cpp
#include <iostream>
void hello_world()
{
std::cout << "Hello World" << std::endl;
}
void goodbye_world()
{
std::cout << "Goodbye World" << std::endl;
}
$ cat main.cpp
#include <iostream>
extern void hello_world();
int main() {
hello_world();
return 0;
}
BUCK-файл, v1:
$ cat BUCK
cxx_library(
name = "foo",
srcs = ["foo.cpp"],
link_whole = True,
)
cxx_binary(
name = "main",
srcs = ["main.cpp"],
deps = [
':foo',
],
)
# toolchains/BUCK
load("@prelude//toolchains:cxx.bzl", "system_cxx_toolchain")
load("@prelude//toolchains:python.bzl", "system_python_bootstrap_toolchain")
system_cxx_toolchain(
name = "cxx",
visibility = ["PUBLIC"],
)
system_python_bootstrap_toolchain(
name = "python_bootstrap",
visibility = ["PUBLIC"],
)
Построй, возьми №1:
$ buck2 build //...
Starting new buck2 daemon...
Connected to new buck2 daemon.
Build ID: b0ed2f4f-3d43-47cc-b9e4-19a53158dc3e
Jobs completed: 62. Time elapsed: 0.3s.
Cache hits: 0%. Commands: 4 (cached: 0, remote: 0, local: 4)
BUILD SUCCEEDED
Запустите программу:
$ ./buck-out/v2/gen/root/904931f735703749/__main__/main
Hello World
Все хорошо. Теперь посмотрите на его глобальную таблицу символов и таблицу динамических символов (разобранную).
нажимает hello_world()
:
$ readelf -W --syms ./buck-out/v2/gen/root/904931f735703749/__main__/main | \
c++filt | egrep '(Symbol table|Ndx|hello_world)'
Symbol table '.dynsym' contains 8 entries:
Num: Value Size Type Bind Vis Ndx Name
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND hello_world()
Symbol table '.symtab' contains 32 entries:
Num: Value Size Type Bind Vis Ndx Name
31: 0000000000000000 0 FUNC GLOBAL DEFAULT UND hello_world()
hello_world()
— неопределенный (Ndx
= UND
) символ, упоминаемый один раз в динамике
таблицу символов (.dynsym
) и один раз в глобальной таблице символов (.symtab
).
Компоновщик среды выполнения (ld.so
) смог определить hello_world()
и запустить программу, поскольку
статический компоновщик написал следующий раздел dynamic
в исполняемом файле:
$ readelf --dynamic ./buck-out/v2/gen/root/904931f735703749/__main__/main
Dynamic section at offset 0x840 contains 31 entries:
Tag Type Name/Value
0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN/./__main__shared_libs_symlink_tree]
0x0000000000000001 (NEEDED) Shared library: [lib_foo.so]
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
...[cut]...
который сообщил ld.so
, что lib_foo.so
нужен раньше любого другого
динамические зависимости, а также рассказал, где искать lib_foo.so
, а именно:
(RUNPATH) Library runpath: [$ORIGIN/./__main__shared_libs_symlink_tree]
где действительно мы находим символическую ссылку2:
$ ls -l ./buck-out/v2/gen/root/904931f735703749/__main__/__main__shared_libs_symlink_tree
total 0
lrwxrwxrwx 1 imk imk 24 Apr 22 11:49 lib_foo.so -> ../../__foo__/lib_foo.so
в реальную общую библиотеку:
./buck-out/v2/gen/root/904931f735703749/__foo__/lib_foo.so
Невызываемая функция void goodbye_world()
:
$ readelf -W --syms ./buck-out/v2/gen/root/904931f735703749/__main__/main | \
c++filt | egrep '(Symbol table|Ndx|goodbye_world)'
Symbol table '.dynsym' contains 8 entries:
Num: Value Size Type Bind Vis Ndx Name
Symbol table '.symtab' contains 32 entries:
Num: Value Size Type Bind Vis Ndx Name
не отображается ни в одной таблице символов.
А что касается статической библиотеки, которая link_whole = True
может применяться
к:
$ find . -name lib*.a; echo Done
Done
его не существует. Давайте посмотрим на фактические аргументы связи:
$ cat ./buck-out/v2/gen/root/904931f735703749/__main__/main.linker.argsfile;
"-fuse-ld=lld"
-o
buck-out/v2/gen/root/904931f735703749/__main__/main
"-Wl,-rpath,\$ORIGIN/./__main__shared_libs_symlink_tree"
buck-out/v2/gen/root/904931f735703749/__main__/__objects__/main.cpp.pic.o
buck-out/v2/gen/root/904931f735703749/__foo__/lib_foo.so
lib_foo.so
связан, т.е. заметка NEEDED
была записана в main
; его путь выполнения (-rpath
) также был записан в main
; --whole-archive
отсутствует.
Сборочный дубль №2. Вариант cxx_library
preferred_linkage
Теперь давайте изменим сборку так, чтобы libfoo
собирался как libfoo.a
.
cxx_library(
name = "foo",
srcs = ["foo.cpp"],
preferred_linkage = "static", # New
link_whole = True,
)
Очистите и восстановите:
$ buck2 clean
...
$ buck2 build //...
...
BUILD SUCCEEDED
Программа работает как раньше:
$ ./buck-out/v2/gen/root/904931f735703749/__main__/main
Hello World
Но:
$ find . -name lib*.so; echo Done
Done
Общая библиотека не была создана. Вместо:
$ find . -name lib*.a; echo Done
./buck-out/v2/tmp/root/904931f735703749/__foo__/archive/libfoo.pic.a
./buck-out/v2/gen/root/904931f735703749/__foo__/libfoo.pic.a
Done
Была собрана статическая библиотека libfoo.pic.a
, содержащая объектный файл:
$ ar -t ./buck-out/v2/gen/root/904931f735703749/__foo__/libfoo.pic.a
foo.cpp.pic.o
в котором определены:
$ nm -C ./buck-out/v2/gen/root/904931f735703749/__foo__/libfoo.pic.a | egrep '(foo.cpp.pic.o|world)'
foo.cpp.pic.o:
0000000000000000 T hello_world()
0000000000000030 T goodbye_world()
T
= определено в разделе text
программы. И оба определения были включены в программу:
$ readelf -W --syms ./buck-out/v2/gen/root/904931f735703749/__main__/main | \
c++filt | egrep '(Symbol table|Ndx|world)'
Symbol table '.dynsym' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
Symbol table '.symtab' contains 38 entries:
Num: Value Size Type Bind Vis Ndx Name
32: 0000000000001940 40 FUNC GLOBAL DEFAULT 14 hello_world()
37: 0000000000001970 40 FUNC GLOBAL DEFAULT 14 goodbye_world()
Но только в .symtab
, а не в .dynsym
: среда выполнения
компоновщику не нужно их определять. А определение goodbye_world()
— это мертвый груз.
Посмотрите динамический раздел нового исполняемого файла:
$ readelf --dynamic buck-out/v2/gen/root/904931f735703749/__main__/main
Dynamic section at offset 0xa20 contains 29 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
...[cut]...
Все то же самое, что и раньше, за исключением того, что:
0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN/./__main__shared_libs_symlink_tree]
0x0000000000000001 (NEEDED) Shared library: [lib_foo.so]
теперь нет. И посмотрите новые аргументы связи:
$ cat ./buck-out/v2/gen/root/904931f735703749/__main__/main.linker.argsfile
"-fuse-ld=lld"
-o
buck-out/v2/gen/root/904931f735703749/__main__/main
buck-out/v2/gen/root/904931f735703749/__main__/__objects__/main.cpp.pic.o
-Wl,--whole-archive
buck-out/v2/gen/root/904931f735703749/__foo__/libfoo.pic.a
-Wl,--no-whole-archive
libfoo.pic.a
статически связан, --whole-archive libfoo.pic.a --no-whole-archive
Что это за подрасширение .pic.
, например libfoo.pic.a
и foo.cpp.pic.o
?
Это намек на то, что объектный файл libfoo.pic.a(foo.cpp.pic.o)
скомпилирован.
как позиционно-независимый код, опция компилятора -fPIC
и подходит для
статическая привязка к независимому от позиции двоичному файлу, т. е. к общей библиотеке; не только в
программа, не требующая PIC-кода. На самом деле нам не нужен код PIC для
статическая привязка к нашей main
программе; но мы все равно это получили. В следующей сборке
мы увидим, что это исчезнет.
Сборочный дубль №3. Вариант cxx_binary
link_style
Давайте снова изменим сборку, сказав, что статические библиотеки предпочтительнее в
привязка программы main
. Файл BUCK
теперь имеет:
cxx_library(
name = "foo",
srcs = ["foo.cpp"],
link_whole = True,
)
cxx_binary(
name = "main",
srcs = ["main.cpp"],
link_style = "static", # New
deps = [
':foo',
],
)
с cxx_library
, вернувшимся к оригиналу.
Очистите и восстановите:
$ buck2 clean
...
$ buck2 build //...
...
BUILD SUCCEEDED
Программа работает как раньше:
$ ./buck-out/v2/gen/root/904931f735703749/__main__/main
Hello World
Но:
$ find . -name lib*.a
./buck-out/v2/tmp/root/904931f735703749/__foo__/archive/libfoo.a
./buck-out/v2/gen/root/904931f735703749/__foo__/libfoo.a
теперь у нас есть обычный libfoo.a
вместо libfoo.pic.a
, и это
содержит регулярные:
$ ar -t ./buck-out/v2/gen/root/904931f735703749/__foo__/libfoo.a
foo.cpp.o
Мы сказали, что программа main
предпочитает статические библиотеки; программы не
нужен код PIC, поэтому Бак отказался от компиляции -fPIC
. Ничего больше
отличается от сборки №2.
Но можно скомпилировать объектные файлы с большей степенью детализации, чем по умолчанию. разрешение компоновщику отбрасывать определения, поступающие из объекта файлы, если он наконец определит, что они не нужны, поэтому они никогда не появляются в глобальной таблице символов.
$ORIGIN
имеет значение для компоновщика среды выполнения. Это означает: каталог, содержащий файл
в котором написано $ORIGIN
.
спасибо за подробный ответ, развеявший многие сомнения.
Как вы собрали и связали окончательный бинарный файл? Я думаю, что если вы посмотрите на библиотеку, то найдете в ней обе функции, но неиспользуемая была удалена, когда вы связали библиотеку с
main.o
для созданияmain_executable_path
. Кроме того, вы создаете общую библиотеку? В этом случае уmain_executable_path
нет смысла сохранять ссылку на неиспользуемую функцию.