Общий объект ELF на языке ассемблера x86-64 - PullRequest
6 голосов
/ 18 февраля 2012

Я пытаюсь создать общую библиотеку (* .so) в ASM, и я не уверен, что я делаю это правильно ...

Мой код:

    .section .data
    .globl var1
var1:
    .quad     0x012345

    .section .text
    .globl func1
func1:
    xor %rax, %rax
  # mov var1, %rcx       # this is commented
    ret

Для компиляции я запускаю

gcc ker.s -g -fPIC -m64 -o ker.o
gcc ker.o -shared -fPIC -m64 -o libker.so

Я могу получить доступ к переменной var1 и вызвать func1 с помощью dlopen () и dlsym () из программы на C.

Проблема в переменной var1,Когда я пытаюсь получить к нему доступ из func1, т. Е. Раскомментировать эту строку, компилятор выдает ошибку:

/usr/bin/ld: ker.o: relocation R_X86_64_32S against `var1' can not be used when making a shared object; recompile with -fPIC
ker.o: could not read symbols: Bad value
collect2: ld returned 1 exit status

Я не понимаю.Я уже скомпилировал с -fPIC, так что не так?

Ответы [ 3 ]

11 голосов
/ 18 февраля 2012

Я уже скомпилирован с -fPIC, так что не так?

Эта часть сообщения об ошибке предназначена для людей, которые связывают сгенерированный компилятором код.

Вы пишете asm вручную, поэтому, как правильно написал datenwolf, при написании разделяемой библиотеки в сборке вы должны позаботиться о том, чтобы код не зависел от позиции.

Это означает, что файл не должен содержать никаких32-битные абсолютные адреса (поскольку перемещение на произвольную 64-битную базу невозможно).64-разрядные абсолютные перемещения поддерживаются , но обычно их следует использовать только для таблиц переходов.


mov var1, %rcx использует 32-разрядный режим абсолютной адресации .Обычно вы никогда не должны делать этого, даже в позиционно-зависимом коде x86-64.Обычные сценарии использования для 32-разрядных абсолютных адресов: помещение адреса в 64-разрядный регистр с mov $var1, %edi (расширение нуля в RDI)
и индексация статических массивов: mov arr(,%rdx,4), %edx

mov var1(%rip), %rcx использует относительное RIP 32-битное смещение .Это эффективный способ обращения со статическими данными, и компиляторы всегда используют его даже без -fPIE или -fPIC для статических / глобальных переменных.

У вас есть в основном две возможности:

  • Обычные частные библиотечные статические данные , подобно компиляторам C, будут иметь для __attribute__((visibility("hidden"))) long var1;, то же самое, что и для -fno-PIC.

    .data
        .globl var1       # linkable from other .o files in the same shared object / library
        .hidden var1      # not visible for *dynamic* linking outside the library
    var1:
        .quad     0x012345
    
    .text
        .globl func1
    func1:
        xor  %eax, %eax             # return 0
        mov  var1(%rip), %rcx   
        ret
    
  • полный код с учетом взаимного расположения символов, который генерируют компиляторы для -fPIC.

    Вам необходимо использовать таблицу глобальных смещений.Вот как это делает компилятор, если вы скажете ему создать код для разделяемой библиотеки.Обратите внимание, что это приводит к снижению производительности из-за дополнительной косвенности.

    См. Извините за состояние динамических библиотек в Linux для получения дополнительной информации о взаимозаменяемости символов и накладных расходах, которые это накладывает на генерацию кода для разделяемых библиотек, если вы не осторожны с ограничением видимости символов, чтобыinlining.

    var1@GOTPCREL - это адрес указателя на ваш var1, сам указатель доступен с помощью относительной rip-адресации, а содержимое (адрес var1) заполняется компоновщикомво время загрузки библиотеки.Это поддерживает случай, когда программа, использующая вашу библиотеку, определила var1, поэтому var1 в вашей библиотеке должен разрешаться в эту область памяти вместо той, которая находится в .data или .bss (или .text) вашего .so.

        .section .data
        .globl var1
        # without .hidden
    var1:
        .quad     0x012345
    
        .section .text
        .globl func1
    func1:
        xor %eax, %eax
        mov var1@GOTPCREL(%rip), %rcx
        mov (%rcx), %rcx
        ret
    

Дополнительную информацию см. В http://www.bottomupcs.com/global_offset_tables.html

Пример использования проводника компилятора Godbolt из -fPIC против -fPIE показывает разницу между символами и интерполяцией для получения адреса скрытых глобальных переменных :

  • movl $x, %eax 5 байт, -fno-pie
  • leaq x(%rip), %rax 7 байтов, -fPIE и скрытые глобалы или static с -fPIC
  • y@GOTPCREL(%rip), %rax 7 байтов и нагрузкой вместо просто ALU, -fPIC с нескрытые глобалы.

На самом деле при загрузке всегда используются x(%rip), за исключением не скрытых / не static переменных с -fPIC, где он должен сначала получить адрес времени выполнения из GOT,потому что это не постоянное смещение времени соединения относительно кода.

Связано: 32-битные абсолютные адресабольше не разрешено в x86-64 Linux? (исполняемые файлы PIE).


В предыдущей версии этого ответа говорилось, что сегменты DATA и BSS могут перемещаться относительно TEXT при загрузке динамической библиотеки.Это неверно, перемещается только базовый адрес библиотеки.RIP-относительный доступ к другим сегментам в той же библиотеке гарантированно будет в порядке, и компиляторы выдают код, который делает это.Заголовки ELF определяют, как сегменты (которые содержат разделы) должны быть загружены / отображены в память.

4 голосов
/ 18 февраля 2012

Я не понимаю.Я уже скомпилировал с -fPIC, так что не так?

-fPIC - это флаг относительно создания машинного кода из не машинного кода, т.е. какие операции использовать.На этапе компиляции. Сборка не компилируется, однако! Каждая мнемоника сборки отображается непосредственно в машинную инструкцию, ваш код не компилируется.Он просто транскрибируется в немного другой формат.

Поскольку вы пишете его в сборке, ваш ассемблерный код должен быть независимым от позиции, чтобы его можно было связать с общей библиотекой.-fPIC не действует в вашем случае, поскольку влияет только на генерацию кода.

0 голосов
/ 19 февраля 2012

Хорошо, я думаю, что нашел что-то ...

Первое решение из drhirsch дает почти ту же ошибку, но тип перемещения изменился.И тип всегда заканчивается 32. Почему это?Почему 64-битная программа использует 32-битное перемещение?

Я нашел это из поиска в Google: http://www.technovelty.org/code/c/relocation-truncated.html

В нем говорится:

Для целей оптимизации кода по умолчаниюнепосредственный размер для команд mov - это 32-битное значение

Так что это так.Я использую 64-битную программу, но перемещение 32-битное, и все, что мне нужно, это заставить его быть 64-битным с инструкцией movabs.

Этот код собирается и работает (доступ к var1 изнутрифункция func1 и из внешней программы на C через dlsym()):

    .section .data 
    .globl var1 
var1: 
    .quad     0x012345

    .section .text 
    .globl func1 
func1: 
    movabs var1, %rax       # if one is symbol, other must be %rax
    inc %rax
    movabs %rax, var1
    ret

Но я сомневаюсь в Global Offset Table.Должен ли я использовать его, или этот «прямой» доступ абсолютно корректен?

...