1

all. I was trying to get into programming with NASM, and I also wanted to learn how to make those functions callable in C. I am fairly certain the code that I have so far is correct, in that I need to set up a stack frame, and then undo that stack frame before I return from the routine. I also know I need to return a zero to ensure that there were no errors. I am using debian linux as well, in case I need to adjust for my OS.

The code:

global hello

section .data
message:    db "Hello, world!",0 ; A C string needs the null terminator.

section .text
hello:
        push    rbp         ; C calling convention demands that a
        mov     rbp,rsp     ; stack frame be set up like so.

        ;THIS IS WHERE THE MAGIC (DOESN'T) HAPPEN

        pop     rbp         ; Restore the stack
        mov     rax,0       ; normal, no error, return value
        ret                 ; return

I feel as if I should point out that I ask this because all of the programs I found made external calls to printf. I do not wish to do this, I would really like to learn how to print things in assembly. So I suppose my questions are: What are the calling conventions for C functions in NASM? How do I print a string in NASM 64bit assembly?

Also, to make sure I have this part right, is this the proper way to call the assembly function in C?

#include <stdio.h>

int main() {
    hello();
    return 0;
}

EDIT: Okay, I was able to work this out. Here's the assembly code. I assembled the .asm file along with the .c file using nasm -f elf64 -l hello.lst hello.asm && gcc -o hello hello.c hello.o

section .text
global  hello

hello:
        push    rbp         ; C calling convention demands that a
        mov     rbp,rsp     ; stack frame be set up like so.
        mov     rdx,len     ; Message length
        mov     rcx,message ; Message to be written
        mov     rax,4       ; System call number (sys_write)
        int     0x80        ; Call kernel

        pop     rbp         ; Restore the stack
        mov     rax,0       ; normal, no error, return value
        ret             

section .data
message:    db "Hello, world!",0xa ; 0xa represents the newline char.
len:        equ $ - message

The relevant C code (hello.c) looked like this:

int main(int argc, char **argv) {
    hello();
    return 0;
}

Some explanations include the lack of an #include, due to the I/O being done in the assembly file. Another thing that I needed to see to believe was that all the work was not done in assembly, as I did not have a _start identifier, or whatever that's called. Definitely need to learn more about system calls. Thank you so much to everyone who pointed me in the right direction.

Azhraam
  • 77
  • 1
  • 10
  • 1
    There is lots of information online discussing [x86-64 calling conventions](http://unixwiz.net/techtips/win32-callconv-asm.html), particularly from C. Your example code is not 64-bit, although you indicate you wish to use 64-bit. How you print a string in assembler is done by making a system call to the operating system you are using. So it depends upon the operating system. – lurker Sep 29 '15 at 00:13
  • The 64bit registers begin with r, right? such as `rax`, `rdi`, etc? I'll edit the post to fix that. – Azhraam Sep 29 '15 at 00:17
  • 1
    Yes, they do. But when you get into 64-bit assembler, there's more to it than just putting an `r` in front of the register name. Calling conventions are different than x86. – lurker Sep 29 '15 at 00:19
  • 1
    A better approach might be to first code your thing in C, then ask the compiler to output the assembler (e.g. `gcc -Wall -fverbose-asm -S -O code.c`, then look into `code.s`), to get some initial inspiration – Basile Starynkevitch Sep 29 '15 at 00:19
  • @lurker, you're right, I should probably have assumed that it wouldn't be that easy. I am using debian linux, if that clarifies anything. – Azhraam Sep 29 '15 at 00:34
  • You'll want to make a call to the `write` system call, then. – Crowman Sep 29 '15 at 00:35
  • You can find various instantiations of the [Linux 64-bit system calls](http://blog.rchapman.org/post/36801038863/linux-system-call-table-for-x86-64) table online. – lurker Sep 29 '15 at 00:47
  • Yes, now I have a better understanding of the system calls, that was definitely my problem. I was actually able to figure it out, I'll edit my post with the solution, though I don't remember how to mark it as answered. – Azhraam Sep 29 '15 at 01:00
  • Note that in x86_64, `syscall` is preferred to `int 0x80` for making system calls. – Crowman Sep 29 '15 at 01:24
  • Okay, I'll keep that in mind. Thank you – Azhraam Sep 29 '15 at 02:26
  • Apologies for the double post, but @PaulGriffiths, when I replaced `int 0x80` with `syscall`, the program no longer outputted "Hello, world!", and printed nothing. I have a feeling this means the call was not being made. Any idea why that is? – Azhraam Sep 29 '15 at 02:36
  • @Azhraam: Yes, you can't just replace it, the calling convention is different too. – Crowman Sep 29 '15 at 03:14

1 Answers1

0

As was cleared up in comments, any interaction between the world outside and your code is done through system calls. C stdio functions format text into an output buffer, then write it with write(2). Or read(2) into an input buffer, and scanf or read lines from that.

Writing in asm doesn't mean you should avoid libc functions when they're useful, e.g. printf/scanf. Usually it only makes sense to write small parts of a program in asm for speed. e.g. write one function that has a hot loop in asm, and call it from C or whatever other language. Doing the I/O with all the necessary error-checking of system call return values would not be very fun in asm. If you're curious what happens under the hood, read the compiler output and/or single-step the asm. You'll sometimes learn nice tricks from the compiler, and sometimes you'll see it generate less efficient code than you could have written by hand.


This is a problem:

mov     rax,4       ; System call number (sys_write)
int     0x80        ; Call kernel

Although 64bit processes can use the i386 int 0x80 system call ABI, it is the 32bit ABI, with only 32bit pointers and so on. You will have a problem as soon as you go to write(2) a char array that's on the stack (since amd64 Linux processes start with a stack pointer that has the high bits set. Heap memory, and .data and .rodata memory mapped from the executable are mapped into the lower 32b of address space.)

The native amd64 ABI uses syscall, and the system call numbers aren't the same as the i386 ABI. I found this table of syscalls listing the number and which parameter goes in which register. sys/syscall.h eventually includes /usr/include/x86_64-linux-gnu/asm/unistd_64.h to get the actual #define __NR_write 1 macros, and so on. There are standard rules for mapping arguments in order to registers. (Given in the ABI doc, IIRC).

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Ahh, you're right! I initially thought that I had to place the system call number in the `%rdi` register. Then I noticed that the return value was meant to be placed in `%rax`, and once I did that, along with placing the message in `%rsi` and the string length in `%rdx`, the output returned and all was right with the world. Thank you so much. I'd throw you an upvote, but it seems I don't have the reputation for it. Oh, and I understand and accept that using `libc` isn't the devil thanks to your explanation, but this was an exercise in understanding how these things work in assembly – Azhraam Sep 29 '15 at 03:28
  • @Azhraam: ya, I figured it was just a test-your-knowledge exercise. IIRC, I grokked system calls separately from asm when I learned how things work under the hood. C is a great language for messing around with system calls. For making sure my asm does what I thought it did, I usually call it from C or just look at values in a debugger instead of actually printing anything. `strace` is also a great tool for saving on log-printing while experimenting with system calls. :P `strace -o /dev/pts/XX` works well, where the pty is the `tty` from an xterm, to avoid mixing strace with normal output – Peter Cordes Sep 29 '15 at 03:40