19

I'm trying to figure out how to use scanf to get user input. I know to use printf: all I have to do is push the data I want to write on the screen into the stack like this:

global _main
extern _printf
extern _scanf

section .data
msg db "Hi", 0

section .text
_main:
  push ebp
  mov ebp, esp  

  push msg
  call _printf

  mov esp, ebp
  pop ebp
ret

But I can't figure out how to use scanf. Can someone please just give me the simplest possible source code you can for scanf? I really just want to put what the user types in.

I'm not used to 32bit Assembly. I've only ever used 16bit, and I know in 16bit (DOS) you can just do this:

mov ah, 3fh
mov dx, input
int 21h

input rb 100d

And whatever you type in will the placed at the address of "input."

Gaurang Tandon
  • 6,504
  • 11
  • 47
  • 84
user1432532
  • 349
  • 1
  • 5
  • 13

5 Answers5

22

I found this 'Programming in NASM.PDF'

; add1.asm
SECTION .data
    message1: db "Enter the first number: ", 0
    message2: db "Enter the second number: ", 0
    formatin: db "%d", 0
    formatout: db "%d", 10, 0 ; newline, nul terminator
    integer1: times 4 db 0 ; 32-bits integer = 4 bytes
    integer2: times 4 db 0 ;
SECTION .text
   global _main 
   extern _scanf 
   extern _printf     

_main:

   push ebx ; save registers
   push ecx
   push message1
   call printf

   add esp, 4 ; remove parameters
   push integer1 ; address of integer1 (second parameter)
   push formatin ; arguments are right to left (first parameter)
   call scanf

   add esp, 8 ; remove parameters
   push message2
   call printf

   add esp, 4 ; remove parameters
   push integer2 ; address of integer2
   push formatin ; arguments are right to left
   call scanf

   add esp, 8 ; remove parameters

   mov ebx, dword [integer1]
   mov ecx, dword [integer2]
   add ebx, ecx ; add the values          ; the addition
   push ebx
   push formatout
   call printf                            ; call printf to display the sum
   add esp, 8                             ; remove parameters
   pop ecx
   pop ebx ; restore registers in reverse order
   mov eax, 0 ; no error
   ret

Which is the asm version of this C function:

#include <stdio.h>
int main(int argc, char *argv[])
{
    int integer1, integer2;
    printf("Enter the first number: ");
    scanf("%d", &integer1);
    printf("Enter the second number: ");
    scanf("%d", &integer2);
    printf("%d\n", integer1+integer2);
    return 0;
}
Preet Sangha
  • 64,563
  • 18
  • 145
  • 216
  • The current version of the i386 System V ABI (used on Linux) requires 16-byte stack alignment before a `call`. Your `printf` calls are made with a properly aligned stack (return address + 3 pushes), but the scanf calls are misaligned. glibc scanf would be allowed to segfault (like it will in 64-bit mode), but the 32-bit version probably happens not to. Also, `ecx` isn't a call-preserved register. It makes no sense to save it; printf / scanf clobber ECX and EDX, and your caller expects you to clobber them, too. (Use EDX instead of EBX and you can avoid any save/restore). – Peter Cordes Feb 26 '19 at 04:43
  • @PeterCordes I gleaned the answer from the above document in 2012. Please feel free to correct the answer with more appropriate examples. – Preet Sangha Nov 25 '20 at 08:26
6

Thanks Preet. I made a simple example based on your code to illustrate the use of scanf.

Program that requests an integer and prints it out to the screen:

section .text
  global main
  extern printf
  extern scanf

section .data
  message: db "The result is = %d", 10, 0
  request: db "Enter the number: ", 0
  integer1: times 4 db 0 ; 32-bits integer = 4 bytes
  formatin: db "%d", 0

main:
  ;  Ask for an integer
  push request
  call printf
  add esp, 4    ; remove the parameter

  push integer1 ; address of integer1, where the input is going to be stored (second parameter)
  push formatin ; arguments are right to left (first  parameter)
  call scanf
  add esp, 8    ; remove the parameters

  ; Move the value under the address integer1 to EAX
  mov eax, [integer1]

  ; Print out the content of eax register
  push eax
  push message
  call printf
  add esp, 8

  ;  Linux terminate the app
  MOV AL, 1
  MOV EBX, 0 
  INT 80h 
mateuszb
  • 1,072
  • 13
  • 26
4

This is the first post that shows up when you search for scanf in assembly, so, even if its a 4 years old post, I think it should be correct.

Oukei, so, to call scanf in NASM assembly you need to:

  1. Extern scanf
  2. Prepare a formatation for you scanf
  3. Prepare the variables or single variable to store the values expected
  4. Push the parameters in backward order
  5. Call scanf
  6. Restore stack

So, lets say you're traying to

scanf ("%d %d", &val1, &val2);

and following the list:
... 1.

section .text
extern scanf

... 2. This is the first parameter you pass to your C scanf, it says what will you get. One integer, two, a float, string, char... In this case, two integers separated by a space (also works with Enter)

section .data
fmt: db "%d %d",0

... 3.

section .bss
val1: resd 1
val2: resd 1

... 4 5 6. Note that you push the address of the variables, not its content (i.e. [var])

push val2
push val1
push fmt
call scanf
add esp, 12

Also note that you have to add 12 to the stack pointer because you pushed 3 double word parameters. So you add 12 bytes (3*4 bytes) to the stack to "jump" the parameters.

*I declared dword for the variables because %d uses dword, just as printf.
**The ,0 in the end of the formatation string is a sentinel character.

Community
  • 1
  • 1
  • You can merge your accounts (search on [meta] to find out how). Or you can delete your previous answer from your other account, since it doesn't have any upvotes anyway. And please [edit] this one to stand on its own, instead of referencing your other post. – Peter Cordes Sep 18 '16 at 02:45
  • I answered as guest with the name of PunditPoe, can't figure out if I can "enter" this guest account and delete the post. But, anyways, I corrected this one to stand on its own. Thanks. –  Sep 19 '16 at 14:42
  • Excellent explanation. Thank you. – Ayush Jain Apr 12 '20 at 06:42
1

For 64 bit nasm:

For using scanf with nasm, you need to first have the statement before the .text section.

extern scanf

Now you need to first setup your stack using

push rbp

This is important if you don't want a segmentation fault. The stack pointer rsp must be aligned to a 16-byte boundary before making a call. The process of making a call pushes the return address (8 bytes) on the stack, so when a function gets control, rsp is not aligned. You have to make that extra space yourself, by pushing something or subtracting 8 from rsp. You can read more about it here.

Now, that your stack is ready, you need to first move your input formatted string in rdi register, followed by the arguments in rsi, rdx, rcx, r8, r9 in strict order.

Let's take the example of mimicking the c statement

scanf("%d %d", &a, &b);

The equivalent nasm code would be:

section .data

Input_format db "%d %d", 0

section .bss

var1: resd 1 ;reserves one double(4 bytes) for int variable
var2: resd 1

extern scanf
global main
default rel  ; use this by default for efficiency. This is even mandatory if you run your code on macOS.


section .text
main:

push rbp
lea rdi, [Input_format] ;loading format
lea rsi, [var1] 
lea rdx, [var2]
call scanf

pop rbp  ;restores stack

;simulates return 0
mov rax, 0
ret

Below is the code which is prettier version of the above code. It prompts user for input, and prints the inputted number.

        section .data

int_inMsg:    db        "Enter an integer value" , 10, 0 ;10 for new line, 0 for null
real_inMsg:   db        "Enter a real value", 10, 0
bool_inMsg:   db        "Enter a boolean value", 10, 0
arr_inMsg:    db        "Enter %d elements for array range %d to %d", 10, 0
intFormat     db        "%d", 0


        section .bss
var1:         resd      1


global main
extern printf
extern scanf
extern puts
extern exit
default rel

section .text

main: 

        push rbp ;setup stack

        ;printf("Enter blah blah\n");

        lea rdi, [int_inMsg] ;first argument
        xor rax, rax
        call printf


        ;take input from the user
        ;scanf("%d", &var1);

        lea rdi, [intFormat]
        lea rsi, [var1]
        xor rax, rax
        call scanf

        lea rdi, [intFormat]
        mov esi, [var1]  ;notice the [] for sending value of var1 instead of pointer to var1
        xor rax, rax
        call printf

        ; return
        pop rbp ;restore stack
        mov rax, 0 ;normal exit
        ret

Thanks to @peter for his helpful and insightful comments.

Ayush Jain
  • 518
  • 5
  • 8
  • 1
    It would be better to point out *why* `push rbp` is needed: to re-align the stack by 16 after entering a function (`main`). If someone doesn't understand that, they might also `sub rsp, 8` to reserve some space for a local var and misalign the stack again. What's actually needed is `sub rsp, 8 + 16*n` or equivalent via `push`es. Otherwise yes, this is a good example; that lack of explanation for that part of the "recipe" is all that's stopping me from upvoting. – Peter Cordes Apr 12 '20 at 14:52
  • 1
    Note you can use `xor eax,eax` everywhere you want RAX=0, not just before printf. Also that `mov r64, imm64` is inefficient; `lea rdi, [rel intFormat]` is the standard way to put a pointer into a register in 64-bit code. Or for Linux in a non-PIE executable, `mov edi, intFormat` because absolute symbol addresses fit in a 32-bit immediate. But perhaps that would be distracting for a beginner that isn't totally clear on how symbols and 64-bit vs. 32-bit registers work. – Peter Cordes Apr 12 '20 at 14:56
  • 1
    Oh, you have one sort of bug: `mov rsi, [var1]` is a qword load from a location where you only reserved one dword. Use `mov esi, [var1]`. `int` is 32-bit, `long` and pointers are 64-bit. And don't forget `default rel`; you always want that for x86-64 and it probably should have been NASM's default. Otherwise you have to write `mov esi, [rel var1]` unless you want an inefficient 32-bit absolute address. – Peter Cordes Apr 12 '20 at 14:58
  • 1
    Thank you so much for your insightful comments. Learned so much! I will edit the answer accordingly. – Ayush Jain Apr 13 '20 at 01:49
  • Glad it helped. If you want to read more, see links in https://stackoverflow.com/tags/x86/info. [How to load address of function or label into register in GNU Assembler](https://stackoverflow.com/q/57212012) / [32-bit absolute addresses no longer allowed in x86-64 Linux?](https://stackoverflow.com/q/43367427) / [glibc scanf Segmentation faults when called from a function that doesn't align RSP](https://stackoverflow.com/q/51070716) – Peter Cordes Apr 13 '20 at 01:55
0

Lets say you want to do something like

scanf("%d %d", &var1, &var2);

That takes two values and store them in the variables.

In assembly you would push the addres of the variables into the stack (in backward order) and then call scanf.

Something like you have two variables

var1 resd 1
var2 resd 1

... and then

push var2
push var1
call scanf

*Note that I pushed them in backward order, the first value will be stored in var1.

After execution the values you entered will be stored in the variables.

If you want only one value just push one variable.

  • Don't forget to pop the stack after scanf returns. (`add esp, 8` for 32-bit code). 64-bit code passes the first up-to-6 args in registers, so you wouldn't have stack args. Also, the current i386 System V ABI on Linux requires 16-byte stack alignment before a `call`, so you can't just push an arbitrary number of things. – Peter Cordes Feb 26 '19 at 04:46