Попытка вызова функции C из glibc из программы сборки (64 бит) - PullRequest
0 голосов
/ 31 декабря 2018

Я работал над Язык ассемблера. Шаг за шагом: третье издание , и я нахожусь в заключительной главе "Направляясь к C".Я пытаюсь получить согласованный метод преобразования 32-битного кода, который вызывает функцию библиотеки C (glibc) puts в моей 64-битной системе Ubuntu.(Я хотел бы проследить последние 50 страниц текста, который, предположительно, углубляется в C [более отвратительные каламбуры], но из сборочной базы, написанной с 32-битным кодом).Код:

SECTION .data           ; Section containing initialised data
EatMsg: db "Eat at Joe's!",0

SECTION .text           ; Section containing code
extern puts             ; Simple "put string" routine from clib
global main             ; Required so linker can find entry point
main:
        push ebp        ; Set up stack frame for debugger
        mov ebp,esp
        push ebx        ; Must preserve ebp, ebx, esi, & edi
        push esi
        push edi

;;; Everything before this is boilerplate; use it for all ordinary apps!
        push EatMsg     ; Push address of message on the stack
        call puts       ; Call clib function for displaying strings
        add esp,4       ; Clean stack by adjusting ESP back 4 bytes

;;; Everything after this is boilerplate; use it for all ordinary apps!
        pop edi         ; Restore saved registers
        pop esi
        pop ebx
        mov esp,ebp     ; Destroy stack frame before returning
        pop ebp
        ret             ; Return control to Linux

Предлагаемые команды nasm и linker:

nasm -f elf -g -F stabs eatclib.asm
gcc eatclib.o -o eatclib

Самое близкое приближение к решению, которое я нашел, здесь: Вызовите функции C из64-битная сборка .

Я попытался преобразовать расширенные регистры в rbp, rsp и т. Д .;настройте указатель стека на 8 бит вместо четырех после вызова puts и настройте make-файл, используя:

nasm -f elf64 -g -F dwarf eatclib.asm

и

gcc eatclib.o -o eatclib -m64 -static

, но получили ошибку сегментации.

Мое понимание соглашения о вызовах в C все еще туманно / достаточно сомнительно, так что я не особо углублялся в попытки найти ошибку, когда пытался следовать вместе с отладчиком gdb (обе проблемы только несколькознаком с 32-битными соглашениями и не так много с C).Эта книга предназначена для ознакомления начинающих программистов на ассемблере, у которых практически нет C-фона.

В другом направлении простая программа на C, которая использует put со строкой, создает файл (используя gcc-S option) из:

.file   "SayHello.c"
        .text
        .section        .rodata
        .align 8
.LC0:
        .string "This is based on an example from C Primer Plus"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        leaq    .LC0(%rip), %rdi
        call    puts@PLT
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret

Скомпилированный код работал (и я понимаю большую часть этого, за исключением директив .cfi, что означает .rodata, и почему газ застрял в этом @PLTна puts.) Это, конечно, синтаксис газа и текст, который я в основном использую с функциями NASM.

Я также пытался использовать загрузчик вместо gcc со строкой, найденной на стр. 89 из Professional Assembly Language (Ричард Блюм)

ld -dynamic-linker /lib/ld-linux.so.2 -o eatclib -lc eatclib.o

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

ld: i386 architecture of input file `eatclib.o' is incompatible with i386:x86-64 output
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400250
makefile:2: recipe for target 'eatclib' failed

Я пыталсяпередача опции -m32 компоновщику тоже безрезультатна.

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

Ответы [ 2 ]

0 голосов
/ 31 декабря 2018

Предложение Джестера установить gcc-multilib и затем использовать аргумент gcc -m32, работающий с 32-битным кодом.(Это определенно дубликат из другого источника в stackoverflow ... я видел предложение где-то вчера, но не доверял капитальному ремонту gcc, который, как мне казалось, требовался.)

0 голосов
/ 31 декабря 2018

Чтобы собрать и связать 64-битный код NASM, который использует libc, введите:

nasm -f elf64 program.asm
gcc -o program program.o

В зависимости от вашей системы и стиля программирования, вам может потребоваться передать -no-pie в gcc, чтобы он принималпозиционно-зависимый код.

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

Обратите внимание на elf64, чтобы заставить nasm излучать 64-битный объектный файл.gcc работает с 64-битным кодом на 64-битной платформе, если не указано иное, поэтому никаких других опций не требуется.Возможно, вы захотите добавить символы отладки, но имейте в виду, что stabs является устаревшим форматом.Возможно, вы захотите:

nasm -f elf64 -gdwarf program.asm

Механическое преобразование исходного кода более или менее возможно.Помните о следующих различиях:

  • указатели и слоты стека имеют длину 8 байтов, а все регистры общего назначения расширены до 8 байтов;64-битные варианты первых 8 регистров называются rax, rcx, rdx, rbx, rsp, rbp, rsi и rdi.
  • Существуют 8 новых регистров общего назначения от r8 до r15.Их 32-разрядные, 16-разрядные и 8-разрядные версии называются r8d, r8w, r8b` и т. Д.
  • Инструкции SSE используются для операций с плавающей запятой вместо команд x87
  • 64битовый код обычно подчиняется другому соглашению о вызовах, чем 32-битный код.В UNIX-подобных системах, таких как Linux, обычно используется amd64 SysV ABI .В этом ABI скалярные аргументы передаются слева направо в регистрах rdi, rsi, rdx, rcx, r8 и r9.Регистры rbx, rbp, rsp, r12, r13, r14 и r15 должны быть сохранены вызывающим абонентом, все другие регистры общего назначения могут быть перезаписаны свободно.Аргументы с плавающей точкой передаются и возвращаются в регистрах SSE.Если аргументов слишком много, в стек передаются дополнительные аргументы.
  • SysV ABI требует, чтобы указатель стека выравнивался по 16 байтов при вызове функции.Так как инструкция call выталкивает 8 байтов, а инструкция push rbp в прологе функции добавляет еще 8 байтов, это так по умолчанию, если только вы вручную не выделите место в стеке.Только не забывайте делать это с шагом 16 байтов.

Вот код из вашего вопроса, переведенный в 64-битный код.Все изменения были отмечены:

        SECTION .data
EatMsg: db "Eat at Joe's!",0

        SECTION .text
        extern puts
        global main
main:                           ; function entry (stack alignment: 16 bytes + 8 bytes)
        push rbp                ; setup...
        mov rbp, rsp            ; the stack frame (stack now aligned to 16 bytes + 0 bytes)

                                ; since we have so many registers, I only preserve those
                                ; I want to use and that must be preserved, of which there
                                ; are none in this program.

        lea rdi, [rel EatMsg]   ; load address of EatMsg into rdi
        call puts               ; call puts
                                ; no cleanup needed as we have not pushed anything

        pop rbp                 ; restore rbp
        ret                     ; return

Обратите внимание, что я пропустил кучу шаблонов.lea используется для загрузки адреса EatMsg вместо простого mov rdi, EatMsg, поэтому ваша программа не зависит от позиции.Если вы не знаете, что это значит, вы можете смело игнорировать этот тидбит до позднего времени.

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

...