6

I am trying to make a simple assembly program that prints "Hello!" once, waits one second, then prints it again. Since sleep functions are relatively complex in assembly, and I'm not that good at it, I decided using C++ would be the way to go to make the Sleep subroutine. Here's the C++ program:

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

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

I then compiled this sleep function into an assembly program using "gcc -S Sleep.cpp" then compiled it into an object file using "gcc -c Sleep.s"

I am trying to call this C++ subroutine from assembly. I heard that you provide parameters to C++ subroutines by pushing them onto the stack, here's my assembly code so far:

        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

Both these files are saved to Desktop/Program. I'm trying to compile it using NASM and GCC, my compiler invocation is:

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

But I get the error:

"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)

Changing the code to "extern __Z5Sleepi" and calling "__Z5Sleepi" instead of Sleep doesn't seem to fix the problem. (I get the same error message just without the "Maybe you meant __Z5Sleepi" bit. I also tried using _Sleep instead of Sleep without success.) What am I doing wrong? How do I properly use and link this C++ subroutine with my assembly program? Is the method I am using so far to do this just wrong from the ground up?

Any help is much appreciated, browsing stack overflow, there seem to be a lot of questions regarding this but none of them actually go into the linking process. (And they seem to be asking about linking assembly with C++, not C++ with assembly.) I am using NASM and GCC to compile, and my platform is Mac OSX.

SectorSam
  • 253
  • 2
  • 12
  • 4
    First, use `g++` for linking. To get rid of name mangling, use `extern C` in your c++ source. – Jester Sep 24 '18 at 14:47
  • @Jester Scratch that last comment. My asm file was still using __Z5Sleepi instead of Sleep. Now, using _Sleep the program compiles and executes. Except now, when it's supposed to pause for 1 second, I get a segmentation fault. Any idea why that's happening? – SectorSam Sep 24 '18 at 14:52
  • 2
    As you are probably aware (since you used it for `puts` correctly) you have to put argument in `rdi` not on the stack. Hence `push 1000 ` should be `mov edi, 1000`. – Jester Sep 24 '18 at 14:58
  • @Jester It works now! Thank you so much, I never knew that rdi applied to my own C++ functions as well. Thank you for enlightening me to that. Would you post your comment as an answer so I can mark it? – SectorSam Sep 24 '18 at 15:04
  • 1
    The arg-passing part is a duplicate of [What are the calling conventions for UNIX & Linux system calls on i386 and x86-64](https://stackoverflow.com/q/2535989). There's probably a duplicate of the name-mangling part, too, somewhere. Yup, `extern "C"` for [How to call C++ functions in my assembly code](https://stackoverflow.com/q/3911578) – Peter Cordes Sep 24 '18 at 15:11
  • 2
    Even though you got this working, I strongly recommend that you write your main function in C++ and have it call your assembly function. Also link using g++, as @Jester also recommended. This will ensure that the C++ runtime library is initialized properly. – prl Sep 24 '18 at 15:23
  • @prl I totally agree. The only reason I am doing this in assembly is for learning purposes. I'm trying to teach myself assembly. Maybe one day it'll come in handy. One day... – SectorSam Sep 24 '18 at 15:38
  • @SectorSam Generally, the easiest way to find out how to do things like this is to call the C or C++ compiler with the option `-S` to let it generate assembly code. Then you can see how the C compiler does it and copy its code. I learned a lot about assembly this way. – fuz Sep 24 '18 at 16:06

1 Answers1

8

As Jester pointed out, the problem arose from two things. One was I needed to change the Sleep.cpp program to use extern "C", like this:

#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));
   }
}

This prevents the compiler from "name mangling" the function. Doing that changed the compiled function name of Sleep() from "__Z5Sleepi" to "_Sleep" and alleviated my linker errors.

Then I changed my compiler invocation to link with g++ instead of gcc, to link the C++ standard library for functions like std::__1::this_thread::sleep_for, as well as the C standard library.

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

After this, the compiler told me I needed to change extern Sleep to extern _Sleep and much the same with call _Sleep instead of call Sleep, because OS X decorates C symbol names with a leading _.

After I did all this, the program linked properly but produced a segmentation fault. Jester pointed out the reason for this is that x86-64 calling conventions don't pass integer/pointer function parameters on the stack. You use the registers in the same way you would call _printf or _puts, because those library functions also follow the same standard calling convention.

In the x86-64 System V calling convention (used on OS X, Linux, and everything other than Windows), rdi is parameter 1.

So I changed push 1000 to mov rdi, 1000

After all these changes were made, the program compiles correctly and does exactly what it should: Print Hello!, wait 1 second, then print it again.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
SectorSam
  • 253
  • 2
  • 12
  • you need use decorated name of the enclosing function. dont know how get it in gcc, in msvc it returned by `__FUNCDNAME__` macro. can be looked in object file for example. in msvc possible print `__FUNCDNAME__ ` macro at compile time - for get exactly name and use it in asm code – RbMm Sep 24 '18 at 20:14
  • You don't *need* to use the mangled name if `extern "C"` works for you. But if you need to call a member function, or don't want an extra wrapper around a C++ function that you need to not be `extern "C"`, then yeah you'd want to call the mangled name from asm. Probably best to just copy/paste it from `nm` output or something, but there might be a way to have gcc make it for you. – Peter Cordes Sep 25 '18 at 02:22
  • *but there might be a way to have gcc make it for you* - in *msvc* we can put `__pragma(message(__FUNCDNAME__ ))` inside function body and this print exactly function name, wich need use in asm. also possible situation when in *c++* code exist several functions with the same name (different by params). this can not be used with `extern "C"`. `extern "C"` of course most simply solution but not always the best – RbMm Sep 25 '18 at 12:23
  • The reason I used `extern "C"` is simply because I am writing this program in assembly, and typing in a name like "__Z5Sleepi" is very tedious and error prone. (I know I personally would type it in wrong almost every time.) I'm not saying there aren't other ways of doing these types of things, this is just the way I use. – SectorSam Sep 26 '18 at 17:55