0

I'm learning the NASM assembler and I have being stuck at the moment with handling the linefeed.

How do I print the linefeed? I'll show you. But, before I show it, it's important to tell you about my platform:

$ uname -a
Linux 4.4.0-97-generic #120-Ubuntu SMP Tue Sep 19 17:28:18 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID : Ubuntu
Description    : Ubuntu 16.04.3 LTS
Release        : 16.04
Codename       : xenial

My functions, which I have prepared for the learning:

sprint:
    push rdx
    push rbx
    push rax
    push rcx
    mov  rax, rcx
    pop  rcx
    jmp  next_char
    ret

sprint_syscall:
    mov rbx, 1
    mov rax, 4
    int 80h
    pop rax
    pop rbx
    pop rdx
    ret

sprint_linefeed:
    call sprint
    push rcx
    mov  rcx, 0Ah
    push rcx
    mov  rcx, rsp
    call sprint
    pop  rcx
    pop  rcx
    ret

next_char:
    cmp byte [rax], 0
    jz  count_length
    inc rax
    jmp next_char

count_length:
    sub rax, rcx
    mov rdx, rax
    jmp sprint_syscall

quit:
    push rbx
    push rax
    mov  rbx, 0
    mov  rax, 1
    int  80h
    pop  rax
    pop  rbx
    ret

Main app, which may use one of the given functions:

%include 'functions.asm'

SECTION .data
    msgFirst  db "Hello World!", 0h
    msgSecond db "Another string.", 0h

SECTION .text
global _start

_start:
    mov  rcx, msgFirst
    call sprint_linefeed
    mov  rcx, msgSecond
    call sprint_linefeed
    call quit

All is compiling fine and works pretty too. If to compile/link with:

nasm -g -f elf64 -l main.lst main.asm -o main.o && ld main.o -o main

And then execute the compiled app, we're able to see:

Hello World!Another string

As you can see, there weren't any linefeed, despite on calling the sprint_linefeed function, which uses the next instructions for printing the linefeed:

sprint_linefeed:
    call sprint
    push rcx      ; push rcx onto the stack to preserve it
    mov  rcx, 0Ah ; move 0Ah into rcx, 0Ah is the ASCII char for a linefeed
    push rcx      ; push the linefeed char onto the stack
    mov  rcx, rsp ; move the address of the pointer into rcx for the 'sys_write' syscall
    call sprint
    pop  rcx      ; remove the linefeed char from the stack
    pop  rcx      ; restore the original value of rcx
    ret

So, the explanation of my actions refers to sprint() call for the given string and then pushing the 0Ah char onto stack. Later, I'm getting the pointer from the RSP-register to call the sprint() function again.

It was done, because the SYS_WRITE syscall requires the pointer to print the give string (http://fresh.flatassembler.net/lscr/data/004.html).

If to add the OAh char to the string declaration:

SECTION .data
    msgFirst  db "Hello World!", 0Ah, 0h

Calling the same function, but with the different string value, gives us the awaited result, but it makes NO sense, because sprint_linefeed() was defined to pass the declaration of the redundant ASCII-char.

What should I fix in my source-code to make sprint_linefeed() work fine?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 2
    Do not use `int 80h` in 64 bit code, that is the 32 bit compatibility system call interface. See [What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?](https://stackoverflow.com/questions/46087730/what-happens-if-you-use-the-32-bit-int-0x80-linux-abi-in-64-bit-code) – Jester Oct 30 '17 at 20:32
  • @Jester Thanks for a piece of advice, I shall keep it in my mind. –  Oct 30 '17 at 20:38
  • 1
    The `rsp` very likely is beyond 32b boundary, so the `int 0x80` will fail to read the `0A` string from stack memory. I would suggest you to use `strace` to see what the system call returns, but actually the `int 0x80` in 64b binary will confuse even that, so the output is not that valuable. Either rewrite your asm as 32b and compile to elf32, or fix system calls. – Ped7g Oct 31 '17 at 00:05
  • Use `strace` and/or `gdb` to debug and you would have seen the `-EFAULT` return value from `sys_write`. – Peter Cordes Oct 31 '17 at 08:28
  • @PeterCordes I have solved my problem & have gained successful compilation & run. May you unlock my question? I can't answer to it, I want to share the working code with the description, what I have done to make it work. It may be useful for others. –  Oct 31 '17 at 08:53
  • My answer on the linked duplicate already has an example of printing from the stack with `sys_write` in 64-bit code. Was there anything beyond changing to `syscall` for the 64-bit ABI? – Peter Cordes Oct 31 '17 at 08:57
  • @Ped7g Thanks a lot. Yes, the whole problem with the 64 bit mode. I have used `syscall` instead of `int 80h`. Also, it was needed to use `rsi/rdi` registers, fix the `SYS_WRITE` & `SYS_EXIT` calls, because for 64 bit, those syscalls have other call-numbers (https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl). –  Oct 31 '17 at 08:58
  • @PeterCordes Now, it wasn't. But, I think people may want/prefer to get the answer with the working code. They may copy & try it quickly on their own PCs. I just want to provide people much more convenient way, if they match the similar problem. –  Oct 31 '17 at 09:01
  • Yes, https://stackoverflow.com/questions/2535989/what-are-the-calling-conventions-for-unix-linux-system-calls-on-i386-and-x86-6 explains all of that. Sorry to be a party pooper, but I honestly don't think there's anything new that needs to be said here. This is just another "tried to write stack memory with `int 0x80` in 64-bit code" duplicate. If I could re-open it temporarily for you to post your working `strlen()` + `write()` function as an answer, I would, but if I did that I couldn't re-close it again. – Peter Cordes Oct 31 '17 at 09:02
  • Normally this is frowned upon, but I think you should edit your working code into the bottom of your question if you think the actual function might be useful for others to copy/paste. (It prints a string to stdout while saving/restoring all registers instead of just following the usual calling convention which lets it clobber some registers? And it uses a loop to find the length?) Use `------` and a `### heading` to mark the separate section of your answer, and write something about why someone might want to use it. – Peter Cordes Oct 31 '17 at 09:05
  • Actually, if the fixed version of your code is useful, maybe you could post it as an answer to some other question. But if you want to say something about how you fixed it, or what led you into the mistake of using `int 0x80` in the first place, here might be better than an answer to an unrelated question. – Peter Cordes Oct 31 '17 at 09:07
  • @PeterCordes So, let's make it much more constructive, I really just want to provide convenience for other people, who may search it web the similar problem. I shall provide the new source-code: https://pastebin.com/jjRV0Ub7 , if it's okay by your opinion, then please reopen and I shall make the description about 64 bit syscalls, `rsi/rdi` and may be the more detailed description of my problem with providing the dump from the `strace`/`gdb`. I remember, that you have the remarks to my source-code, tell me what should I also fix and then I shall provide the full & constructive answer. –  Oct 31 '17 at 09:09
  • Here's a plan: post that over on http://codereview.stackexchange.com/. Make sure you write something about its intended purpose and *why* you designed it that way (with `push` / `pop` of all registers you use, instead of letting it clobber rax,rcx,rdx,rsi,rdi, and r8-r11 like the x86-64 SystemV ABI allows for the standard function calling convention. (Also don't forget that `syscall` clobbers `rcx` and `r11`.) Let me know in a comment when you're done, and I can post my review over there as an answer. *This* question is just about why `write(stack mem)` didn't work, and can stay closed. – Peter Cordes Oct 31 '17 at 09:19
  • My answer on https://stackoverflow.com/a/46087731/224132 already has details on `strace` and `gdb`, including what you actually see with `gdb`. Did you have more to add about debugging the problem that I didn't cover? Perhaps you'd like to post an answer to that question about how you went about debugging / fixing your problem? My answer may not be the most beginner-friendly (and doesn't have an example of converting broken code to working code), so a tutorial-style answer or whatever you want to write might be useful. More people will see an answer there than here, and this links there. – Peter Cordes Oct 31 '17 at 09:26
  • 1
    @PeterCordes Yes, I want to prepare the beginner tutorial exactly. So, later, I shall create a post at `CodeReview` with my explanation and give you a link, when it will be ready. I shall wait your critique. After that, I shall prepare the beginner tutorial in friendly-style and provide an answer to the question, that you have provided (https://stackoverflow.com/a/46087731/224132). –  Oct 31 '17 at 09:40
  • @PeterCordes I was ill last two weaks, so sorry for the late answer: https://codereview.stackexchange.com/questions/180376/nasm-tutorial-linux-syscalls-64-bit-code-with-ci-build-test-c-mstest –  Nov 14 '17 at 04:58

1 Answers1

0

@Neverlands I made a minor change to your functions.asm by removing sprint_linefeed and put the following at the top of the file:

%define newline 0Ah

SECTION .data
    lineEnding db newline, 0h

sprint_linefeed:
    call sprint
    mov  rcx, lineEnding
    call sprint
    ret

It may not be exactly what you want but it does give:

Hello World!
Another string.

as output. Have fun!

Ken Schumack
  • 719
  • 4
  • 11
  • Pity, but it wasn't what I wanted. I want to handle the linefeeds in internal function exactly, NOT defining the linefeed in string declaration. The way, you have provided, was mentioned in the end of my question content already (just without `%define`). –  Oct 30 '17 at 22:28
  • but look a little closer and a bit sideways and I think you will figure it out. Notice the lineEnding AND your msgFirst and msgSecond end with NUL – Ken Schumack Oct 30 '17 at 23:48
  • 2
    @KenSchumack the `mov rcx,0Ah` `push rcx` does set in stack memory eight bytes `0A 00 00 00 00 00 00 00`, so it **is** nul-terminated enough already. The OP problem is, that for his newline string in stack you have to look quite sideways, like way beyond 32b memory boundary, and `int 0x80` will be never able to do that. Your string in `.data` is reachable, that's why it works even with wrong system call. – Ped7g Oct 31 '17 at 07:12