Почему link_whole не добавляет все символы в Linux

У меня есть тестовая библиотека Заголовочный файл 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

Как вы собрали и связали окончательный бинарный файл? Я думаю, что если вы посмотрите на библиотеку, то найдете в ней обе функции, но неиспользуемая была удалена, когда вы связали библиотеку с main.o для создания main_executable_path. Кроме того, вы создаете общую библиотеку? В этом случае у main_executable_path нет смысла сохранять ссылку на неиспользуемую функцию.

Ted Lyngmo 20.04.2024 08:30

@TedLyngmo Я также обновил пост, указав деньги за main… и да, это общая библиотека… Когда я смотрю на символы в библиотеке, они оба присутствуют. Что тогда делает link_whole? В документации четко сказано: «Всегда включайте всю библиотеку», поэтому ничего не удаляйте.

Shivanshu Arora 20.04.2024 09:06
cpp_library , cpp_binary? Вы имели в виду cxx_library, cxx_binary?
Mike Kinghan 20.04.2024 10:08

@ShivanshuArora Бинарный файл связан с библиотекой (которая содержит обе функции), но в двоичной привязке к неиспользуемой функции нет ничего, что не было бы видно при просмотре двоичного файла. Зачем вам это вообще нужно? Создайте статическую библиотеку, и она, вероятно, появится и в двоичном виде, если только оптимизатор времени компоновки не выбросит ее.

Ted Lyngmo 20.04.2024 10:41

@TedLyngmo По сути, я просто пытаюсь узнать, что делает link_whole и как я могу поместить символ AgainTesting в основной исполняемый файл, даже если он используется не только для целей тестирования. Каков пример использования link_whole и как?

Shivanshu Arora 20.04.2024 20:24

@MikeKinghan да, именно это я и имею в виду, cpp_ — это просто внутреннее расширение, которое мы используем

Shivanshu Arora 20.04.2024 20:26
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Здесь вы ожидаете достичь:

  • Вы создаете общую библиотеку 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_librarypreferred_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_binarylink_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.


  1. Но можно скомпилировать объектные файлы с большей степенью детализации, чем по умолчанию. разрешение компоновщику отбрасывать определения, поступающие из объекта файлы, если он наконец определит, что они не нужны, поэтому они никогда не появляются в глобальной таблице символов.

  2. $ORIGIN имеет значение для компоновщика среды выполнения. Это означает: каталог, содержащий файл в котором написано $ORIGIN.

спасибо за подробный ответ, развеявший многие сомнения.

Shivanshu Arora 23.04.2024 02:04

Другие вопросы по теме