Как связать подпрограмму C ++ с программой сборки x86? - PullRequest
0 голосов
/ 24 сентября 2018

Я пытаюсь сделать простую программу сборки, которая печатает "Hello!"один раз, ждет одну секунду, затем печатает снова.Поскольку функции сна относительно сложны в сборке, и я не очень хорош в этом, я решил, что использование C ++ было бы подходящим способом для создания функции Sleep.Вот программа на C ++:

// Sleep.cpp
#include <thread>
#include <chrono>

void Sleep(int TimeMs) {
    std::this_thread::sleep_for(std::chrono::milliseconds(TimeMs));
}

Затем я скомпилировал эту функцию сна в программу сборки, используя "gcc -S Sleep.cpp", затем скомпилировал ее в объектный файл, используя "gcc -c Sleep.s"

Я пытаюсь вызвать эту подпрограмму C ++ из сборки.Я слышал, что вы предоставляете параметры для подпрограмм C ++, помещая их в стек, вот мой код сборки на данный момент:

        global    _main
        extern    _puts
        extern    Sleep
        section   .text
_main:    
        push    rbp
        mov     rbp,    rsp
        sub     rsp,    32


        ;Prompt user:
        lea     rdi,    [rel prompt]        ; First argument is address of message
        call    _puts                       ; puts(message)

        push    1000 ; Wait 1 second (Sleep time is in milliseconds)
        call    Sleep

        lea     rdi,    [rel prompt] ; Print hello again
        call    _puts

        xor     rax,    rax                 ; Return 0
        leave
        ret

        section   .data

prompt:
    db      "Hello!", 0

Оба эти файла сохранены в Desktop / Program.Я пытаюсь скомпилировать его, используя NASM и GCC, мой вызов компилятора:

nasm -f macho64 Program.asm && gcc Program.o Sleep.s -o Program && ./Program

Но я получаю ошибку:

"Sleep", referenced from:
      _main in Program.o
     (maybe you meant: __Z5Sleepi)
  "std::__1::this_thread::sleep_for(std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000000000l> > const&)", referenced from:
      void std::__1::this_thread::sleep_for<long long, std::__1::ratio<1l, 1000l> >(std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000l> > const&) in Sleep-7749e0.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Изменение кода на "extern __Z5Sleepi" ивызов "__Z5Sleepi" вместо Sleep, похоже, не решает проблему.(Я получаю то же сообщение об ошибке только без бита «Возможно, вы имели в виду __Z5Sleepi». Я также безуспешно пытался использовать _Sleep вместо Sleep.) Что я делаю не так?Как правильно использовать и связать эту подпрограмму C ++ с моей программой сборки?Является ли метод, который я использую до сих пор, чтобы сделать это просто неправильно с нуля?

Любая помощь очень ценится, переполнение стека просмотра, кажется, есть много вопросов по этому поводу, но ни один из них на самом деле не идетв процессе связывания.(И они, кажется, спрашивают о связывании сборки с C ++, а не C ++ с помощью сборки.) Я использую NASM и GCC для компиляции, и моя платформа - Mac OSX.

1 Ответ

0 голосов
/ 24 сентября 2018

Как указывал Шут, проблема возникла из двух вещей.Во-первых, мне нужно было изменить программу Sleep.cpp, чтобы она использовала extern «C», например:

#include <thread>
#include <chrono>

extern "C" void Sleep(int TimeMS);
extern "C"
{
   void Sleep(int TimeMs) {
    std::this_thread::sleep_for(std::chrono::milliseconds(TimeMs));
   }
}

Это не позволяет компилятору «манипулировать именами» функции.Это изменило имя скомпилированной функции Sleep () с «__Z5Sleepi» на «_Sleep» и облегчило ошибки моего компоновщика.

Затем я изменил вызов компилятора так, чтобы он связывался с g++ вместо gcc, чтобысвяжите стандартную библиотеку C ++ для таких функций, как std::__1::this_thread::sleep_for, а также стандартную библиотеку C.

nasm -f macho64 Program.asm && g++ Program.o Sleep.o -o Program && ./Program

После этого компилятор сказал мне, что мне нужно изменить extern Sleep на extern _Sleep и многое другоето же самое с call _Sleep вместо call Sleep, потому что OS X украшает имена символов C начальным _.

После того, как я все это сделал, программа соединилась правильно, но вызвала ошибку сегментации.Джестер указал, что причина этого в том, что соглашения о вызовах x86-64 не передают целочисленные / указательные параметры функции в стек.Вы используете регистры точно так же, как вы вызываете _printf или _puts, потому что эти библиотечные функции также следуют одному и тому же стандартному соглашению о вызовах.

В соглашении о вызовах System V в x86-64 (используется в OS X, Linux,и все, кроме Windows), rdi является параметром 1 .

Так что я изменил push 1000 на mov rdi, 1000

После всех этих изменений,программа компилируется правильно и делает именно то, что должна: напечатайте Hello !, подождите 1 секунду, затем напечатайте снова.

...