'I have an x86-64 program that only works properly when run from the gdb debugger

I have written a primitive version of malloc in x86 assembler as an exercise. The code uses a linked list to keep track of allocated memory blocks. I decided to add a function to walk the list and print out the meta data for each block and encountered this weird problem. When I run the code using gdb it works properly but when run directly without gdb it does not. When I print out an address returned by sbrk as a hex string it only prints correctly if run from gdb. If run repeatedly without gdb it prints a different number each run. I have cut the code down to the minimum needed to illustrate the problem. I have tried everything I can think of to find the problem. I'm sure that my itoh and printstring funcions are working correctly. I have tried linking with the c library and using puts but it does the same. I tried initializing all registers to zero. I have looked for any registers altered by the call to sbrk and saved and restored them across the call. Nothing has worked. Here is the code that illustrates the problem:

global _start,itoh,printstring

section .rodata
        TRUE            equ 1
        FALSE           equ 0
        NULL            equ 0
        LF              equ 10
        sys_brk         equ 12
        exit_ok         equ 0
        sys_exit        equ 60
        sys_write       equ 1
        stdout          equ 1
        

section .data
        current_brk     dq 1
        linefeed        db LF, NULL
        msg1            db 'Test should print 0x403000 from constant: ', NULL
        msg2            db 'Test should print 0x403000 from sys_brk return: ', NULL
        number          db '--------------------', NULL

section .text

_start: mov rdi, msg1
        call printstring
        mov rdi, 0x403000
        mov rsi, number 
        mov rdx, TRUE
        call itoh
        mov rdi, number 
        call printstring

        mov rax, sys_brk
        syscall
        mov [current_brk], rax
        mov rdi, msg2
        call printstring
        mov rdi, [current_brk]
        mov rsi, number 
        mov rdx, TRUE
        call itoh
        mov rdi, number 
        call printstring

.exit:  mov rax, sys_exit
        mov rdi, exit_ok
        syscall
;
; itoh  - rdi intger to convert
;       - rsi address of string to return result
;       - rdx if true add a newline to string
;       return nothing
itoh:   push rcx
        push rax
        xor r10, r10        ; r10 counts the digits pushed onto stack
        mov r9, rdx         ; save newline flag in r9
        mov rax, rdi        ; rax is bottom half of dividend
        mov rcx, 16         ; rcx is divisor

.div:   xor rdx, rdx        ; zero rdx, top half of 128 bit dividend
        div rcx             ; divide rdx:rax by rcx
        push rdx            ; rdx is remainder
        inc r10             ; increment digit counter
        cmp rax, 0          ; is quotient zero?
        jne .div            ; no - keep dividimg by 16 and pushing remainder

.pop:   mov byte[rsi], "0"
        inc rsi
        mov byte[rsi], "x"
        inc rsi
.p0:    pop r11             ; get a digit from stack
        cmp r11, 10
        jl .p1
        sub r11, 10
        add r11, "a"
        jmp .p2
.p1:    add r11, "0"        ; convert to ascii char
.p2:    mov byte[rsi],r11b  ; copy ascii digit to string buffer
        dec r10             ; decrement digit count
        inc rsi             ; point rsi to next char position
        cmp r10, 0          ; is digit counter 0
        jne .p0             ; no, go get another digit from stack
        cmp r9, 0
        je .exit
        mov byte[rsi], LF
        inc rsi

.exit:  mov byte[rsi], NULL ; terminate string
        pop rax
        pop rcx
        ret
;
; printstring - rdi is address of string
;               return nothing
printstring:
        push rcx            ; sys_write modifies rcx
        push rax            ; sys_write modifies rax
        xor rdx, rdx        ; zero rdx, char count
        mov rsi, rdi        ; use rsi to index into string
.countloop:
        cmp byte [rsi],NULL ; end of string?
        je .countdone       ; yes, finished counting
        inc rdx             ; no, count++
        inc rsi             ; point to next char
        jmp .countloop
.countdone:
        cmp rdx, 0          ; were there any characters?
        je .printdone       ; no - exit

        mov rax, sys_write  ; write system call
        mov rsi, rdi        ; address of string
        mov rdi, stdout     ; write to stdout
        syscall             ; number of bytes to write is in rdx
.printdone:
        pop rax
        pop rcx
        ret

yasm -felf64 -gdwarf2 test.asm
ld -g -otest test.o

gdb test
Type "apropos word" to search for commands related to "word"...
Reading symbols from test...
[?2004h(gdb) run
[?2004l
Starting program: /home/david/asm/test 
Test should print 0x403000 from constant: 0x403000
Test should print 0x403000 from sys_brk return: 0x403000
[Inferior 1 (process 28325) exited normally]
[?2004h[?2004l
[?2004h(gdb) q
[?2004l

./test
Test should print 0x403000 from constant: 0x403000
Test should print 0x403000 from sys_brk return: 0x14cf000


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source