5
\$\begingroup\$

I am trying to write an assembly program that takes integers and adds them all together and spits out the sum. However my code currently just has the same block of code repeating. How can I turn this into a cleaner, shorter code while maintaining the same functionality?

section .rodata
prompt1    db "Please enter a number: ",0   ; 0 is null character
prompt2    db "Enter another number: ",0
format_str db "The sum is: %ld.",10,0  ; 10 is LF 
num_format db "%ld",0

section .text
    global main              ; main and _start are both valid entry points
    extern printf, scanf     ; these will be linked in from glibc 

main:
    ; prologue
    push    rbp          ; save base pointer to the stack
    mov     rbp, rsp     ; base pointer = stack pointer 
    sub     rsp, 80      ; make room for integers on the stack
    push    rbx          ; push callee saved registers onto the stack 
    push    r12          ; push automatically decrements stack pointer
    push    r13          
    push    r14
    push    r15
    pushfq               ; push register flags onto the stack
; prompt for first integer 
mov    rdi, dword prompt1    ; double word is 4 bytes; a word is 2 bytes
xor    rax, rax              ; rax is return value register - zero it out
call   printf                ; call the C function from glibc 
lea    rsi, [rbp-8]          ; load effective address - this instruction
mov    rdi, dword num_format ; load rdi with address to format string
xor    rax, rax              ; zero out return value register
call   scanf                 ; call C function
                             ; scanf reads the input as an integer

; prompt for other integer
mov    rdi, dword prompt2
xor    rax, rax
call   printf
lea    rsi, [rbp-16]         ; read number 
mov    rdi, dword num_format
xor    rax, rax
call   scanf

; prompt for other integer
mov    rdi, dword prompt2
xor    rax, rax
call   printf
lea    rsi, [rbp-24]         ; read number 
mov    rdi, dword num_format
xor    rax, rax
call   scanf

; prompt for other integer
mov    rdi, dword prompt2
xor    rax, rax
call   printf
lea    rsi, [rbp-32]         ; read number 
mov    rdi, dword num_format
xor    rax, rax
call   scanf

; prompt for other integer
mov    rdi, dword prompt2
xor    rax, rax
call   printf
lea    rsi, [rbp-40]         ; read number 
mov    rdi, dword num_format
xor    rax, rax
call   scanf

; prompt for other integer
mov    rdi, dword prompt2
xor    rax, rax
call   printf
lea    rsi, [rbp-48]         ; read number 
mov    rdi, dword num_format
xor    rax, rax
call   scanf

; prompt for other integer
mov    rdi, dword prompt2
xor    rax, rax
call   printf
lea    rsi, [rbp-56]         ; read number 
mov    rdi, dword num_format
xor    rax, rax
call   scanf

; prompt for other integer
mov    rdi, dword prompt2
xor    rax, rax
call   printf
lea    rsi, [rbp-64]         ; read number 
mov    rdi, dword num_format
xor    rax, rax
call   scanf

; prompt for other integer
mov    rdi, dword prompt2
xor    rax, rax
call   printf
lea    rsi, [rbp-72]         ; read number 
mov    rdi, dword num_format
xor    rax, rax
call   scanf

; prompt for other integer
mov    rdi, dword prompt2
xor    rax, rax
call   printf
lea    rsi, [rbp-80]         ; read number 
mov    rdi, dword num_format
xor    rax, rax
call   scanf

                        ; add all numbers together
xor rbx, rbx            ; sum = 0
mov rcx, [rbp-8]        ; RBX = number
add rbx, rcx            ; add num + num - store in rbx
mov rcx, [rbp-16]       ; RBX = number
add rbx, rcx            ; add num + num - store in rbx
mov rcx, [rbp-24]       ; RBX = number
add rbx, rcx            ; add num + num - store in rbx
mov rcx, [rbp-32]       ; RBX = number
add rbx, rcx            ; add num + num - store in rbx
mov rcx, [rbp-40]       ; RBX = number
add rbx, rcx            ; add num + num - store in rbx
mov rcx, [rbp-48]       ; RBX = number
add rbx, rcx            ; add num + num - store in rbx
mov rcx, [rbp-56]       ; RBX = number
add rbx, rcx            ; add num + num - store in rbx
mov rcx, [rbp-64]       ; RBX = number
add rbx, rcx            ; add num + num - store in rbx
mov rcx, [rbp-72]       ; RBX = number
add rbx, rcx            ; add num + num - store in rbx
mov rcx, [rbp-80]       ; RBX = number
add rbx, rcx            ; add num + num - store in rbx
mov rsi, rbx
mov rdi, dword format_str
xor rax, rax            ; rax is return value register - zero it out
call printf             ; call the C function from glibc

exit:
      ; epilogue
      popfq
      pop     r15
      pop     r14
      pop     r13
      pop     r12
      pop     rbx
      add     rsp, 80       ; set back the stack level
      leave
      ret

If you have a nasm compiler you can use these commands to compile and run the code:

nasm -f elf64 -g -F dwarf lab9.asm    # -g -F dwarf adds debugging stuff
gcc -o lab9  lab9.o -lc
\$\endgroup\$
1
\$\begingroup\$

Peter Cordes did leave you several review comments in the original SO question, so I will post the code-only here which is sort of adjusted by his advices (and my small modifications).

Commands to compile:

nasm -f elf64 -g -w+all -F dwarf <filename>.asm
gcc -fpic -o <filename> <filename>.o -lc

Modified source:

section .rodata
prompt1:    db "Please enter a number: ",0   ; 0 is null character
prompt2:    db "Enter another number: ",0
format_str: db "The sum is: %ld.",10,0  ; 10 is LF
num_format: db "%ld",0

section .text
    global main             ; provide "main" entry point for glibc
    extern printf, scanf    ; these will be linked in from glibc

main:
    push    rbx             ; will be used to keep sum (r12-r15 not used/modified)
    push    r12             ; will be used as counter
    sub     rsp,8           ; input buffer in stack + stack 16B alignment fixed

    ; init "variables"
    xor     ebx, ebx        ; sum (rbx) = 0
    mov     r12, 10         ; counter: integers to sum at most
    ; first integer has special prompt message
    lea     edi, [rel prompt1]  ; "edi" <- exploiting 32b-ness of .rodata addresses
.prompt_for_integer:
    ; show prompt ("puts" can't be used, it would append "\n")
    xor     eax, eax        ; 0 floating point variadic arguments
    call    printf          ; call C function from glibc - prompt output
    ; take input from user (invalid input will terminate loop)
    mov     rsi, rsp        ; memory buffer address
    lea     edi, [rel num_format]   ; load rdi with address to format string
    xor     eax, eax        ; 0 floating point variadic arguments
    call    scanf           ; call C function - read user integer
    cmp     eax, 1          ; verify user did enter some integer
    jne     .output_sum     ; if not, terminate early (display current sum)
    ; update total sum
    add     rbx, [rsp]
    ; loop until 10 integers are read, but use different prompt for second+
    lea     edi, [rel prompt2]
    dec     r12
    jnz     .prompt_for_integer
.output_sum:
    mov     rsi, rbx
    lea     edi, [rel format_str]
    xor     eax, eax        ; 0 floating point variadic arguments
    call    printf          ; call C function from glibc - output sum

    ; restore modified registers and stack
    add     rsp,8
    pop     r12
    pop     rbx
    ret

I also modified the code to be PIC (position independent code). This is first time ever I tried that with assembly, so I'm not 100% sure I did+linked it correctly, but the code works, in debugger I see relative rip+... addressing, and stripped binary has only about 6kiB, so looks OK to me ... and now I tried to run it multiple times in debugger, and the code address is randomized, so the ASLR works too.

And fixed some of your comments.

And I test scanf return value, so now the code upon invalid input will simply output the current sum, and the program can be terminated early by entering non-integer, or Ctrl+D.


EDIT:

The invalidity of PIC-ness was nagging me, so I finally did dig into the issue, and fixed it, these are things to be modified in the answer above:

Commands to compile:

nasm -f elf64 <filename>.asm -l <filename>.lst -w+all
gcc -pie -o <filename> <filename>.o -lc

Source differences only, all the lea edi,... where in static code the 32 bit address part is enough, have to change to full 64 bit in PIE, i.e.:

    ...
    ; first integer has special prompt message
    lea     rdi, [rel prompt1]
.prompt_for_integer:
    ...

And the other three too (modify target register "edi" to "rdi").

And the call instructions into glibc themselves require extra ELF setting to make the linking dynamic ("abusing" a bit directive WRT "with regard" and special keyword ..plt):

    ...
    xor     eax, eax        ; 0 floating point variadic arguments
    call    printf WRT ..plt; call C function from glibc - prompt output
    ; take input from user (invalid input will terminate loop)
    ...

    ...
    xor     eax, eax        ; 0 floating point variadic arguments
    call    scanf WRT ..plt ; call C function - read user integer
    cmp     eax, 1          ; verify user did enter some integer
    ...

    ...
    lea     rdi, [rel format_str]
    xor     eax, eax        ; 0 floating point variadic arguments
    call    printf WRT ..plt; call C function from glibc - output sum

AFAIK this is the proper 64b PIE (position independent executable), dynamically linked with glibc and ASLR-ed by common linux OS to random address space. Stripped elf64 binary on my machine has 6320 bytes, not that bad, although the real ASM content from the source is only about 170 bytes (but the elf header/sections data are of quite some size, and then the C-runtime is linked it as well).

I know you didn't mention PIC/PIE in your original code and question, but in common current 64b linux OS it is highly desirable to produce PIE binaries only, as that's what the default settings are also for C/C++ compiler, and what makes ASLR and other security mechanisms most effective, so that was the reason why I myself set up to pursuit this side-goal.

\$\endgroup\$
3
  • \$\begingroup\$ Ooops, nope, linking with gcc -pie ... instead of -fpic (which is probably compile time option only) leads to error r_sum_values.o: relocation R_X86_64_PC32 against undefined symbol 'printf@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC So the usage of glibc looks to be wrong for fully PIE binary, but this is beyond my current knowledge. :/ \$\endgroup\$ – Ped7g Dec 4 '17 at 11:00
  • \$\begingroup\$ For some reason the code as is presented in answer works well and looks to be correct, but it may be not a full example of proper "PIC" executable. Unfortunately I can't dig into it more now, so I will keep it like this. \$\endgroup\$ – Ped7g Dec 4 '17 at 11:05
  • \$\begingroup\$ FIXED: Full "PIC/PIE" executable fixed, answer edited with the required changes. The comments above are with regard to the original version of review. \$\endgroup\$ – Ped7g Dec 7 '17 at 9:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.