0

writing some simple assembly code, the program segfaults at the second call of subroutine _printint. This only happens if i remove push rdx and pop rdx from either the _printint subroutine or the PrintNewline macro.

section .data
    digit db 0
    newline db 10

section .text
    global _start

%macro PrintNewline 0
        push rdx           ;this push
    mov rax, 1
    mov rdi, 1
    mov rsi, newline
    mov rdx, 1
    syscall
        pop rdx            ;this pop
%endmacro

%macro exit 0
    mov rax, 60
    mov rdi, 0
    syscall
%endmacro

_printdigit:
    add al, 48
    mov [digit], al
    mov rax, 1
    mov rdi, 1
    mov rsi, digit
    mov rdx, 1
    syscall
    ret

_printint:
    push rdx           ;this push
    mov r10, 0
    mov rcx, 10
_printint0:

    div rcx
    push rdx
    inc r10

    cmp rax, 0
    jne _printint0

_printint1:
    pop rax
    call _printdigit
    dec r10
    cmp r10, 0
    jne _printint1
    pop rdx             ;this pop
    ret


_start:
    mov rax, 10
    call _printint
    PrintNewline
    mov rax, 10
    call _printint
    PrintNewline
    exit

I am unable to understand what is the purpose of them.

4

1 Answer 1

5

The DIV instruction divides RDX:RAX by the operand. But you don't initialize RDX correctly. Without the push/pop in PrintNewline the

mov rdx, 1

instruction from the PrintNewline macro leaks down to the div rcx instruction. So now you do division by 10, in a loop, where in each iteration you have a number above 64-bit, because by accident rdx is always 1 after second iteration (we start with rax = 10), and by another accident division by 10 fits in 64-bit, which means div rcx won't ever throw an exception. So that's an infinite loop, which finally means that push rdx will eventually overflow the stack. And hence the segfault.

Note that with those push/pop instruction your code still is invalid, because now you don't initialize rdx anywhere. If this code works, then this is only accidentally, meaning rdx = 0 when starting this entire assembly. Which is not guaranteed AFAIK.

What you need is to add say mov rdx, 0 (or equivalently xor rdx, rdx) just before the div rcx instruction (this is important because the div instruction treats the rdx register both as an input and output). Then you can simplify the macro. But you should not simplify the subroutines, you need to follow some calling convention, otherwise random register values will keep messing with your code. And the "push at the beginning, pop at the end" pattern is in fact some calling convention.

Of course there are many many other issues with your code, e.g. why do you use this digit thing? Just do mov rsi, rax after add rax, 48 (don't use al btw), or even just lea rsi, [rax + 48] if you want to be fancy. And many many other small issues in the code.

Sign up to request clarification or add additional context in comments.

2 Comments

Correct, RDX=0 on entry to user-space is a Linux implementation detail, not guaranteed by the AMD64 System V ABI. It only applies for statically-linked executables; in a dynamically-linked executable, your _start isn't the first thing that runs user-space, and ld.so's code will leave garbage in most regs, so this code is broken in practice if you were to link it with -lc or something.
Thanks for the input. Which resources do you reccomend for learning? I was just following a couple of tutorials online.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.