如何在NASM中使用scanf?

时间:2012-06-11 01:11:09

标签: assembly nasm

我正在尝试弄清楚如何使用scanf来获取用户输入。我知道使用printf:我所要做的就是将我想要在屏幕上写入的数据推送到堆栈中,如下所示:

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

但我无法弄清楚如何使用scanf。有人可以给我scanf最简单的源代码吗?我真的只想把用户输入的内容写进去。

我不习惯32位装配。我只使用过16bit,而且我知道16bit(DOS)你可以这样做:

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

input rb 100d

无论你输入什么,都会放在“输入”地址。

5 个答案:

答案 0 :(得分:16)

我发现了'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

这个C函数的asm版本是什么:

#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;
}

答案 1 :(得分:3)

谢谢Preet。我根据你的代码做了一个简单的例子来说明scanf的使用。

请求整数并将其打印到屏幕的程序:

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 

答案 2 :(得分:1)

这是您在装配中搜索scanf时显示的第一个帖子,因此,即使它是一个4年的帖子,我认为它应该是正确的。

Oukei,所以,在NASM装配中call scanf你需要:

  1. 外部扫描
  2. 为您准备格式化scanf
  3. 准备变量或单个变量以存储预期值
  4. 按向后顺序推送参数
  5. 致电scanf
  6. 恢复堆栈
  7. 所以,我们可以说你正在追踪到

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

    并在列表后面:
    ...... 1。

    section .text
    extern scanf
    

    ... 2.这是您传递给C scanf的第一个参数,它表示您会得到什么。一个整数,两个,一个浮点数,一个字符串,一个char ...在这种情况下,两个整数用空格分隔(也适用于Enter)

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

    ... 3。

    section .bss
    val1: resd 1
    val2: resd 1
    

    ... 4 5 6.请注意,您推送变量的地址,而不是其内容(即[var])

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

    另请注意,您必须向堆栈指针添加12,因为您按下了3个双字参数。所以你将12个字节(3 * 4个字节)添加到堆栈中以跳转&#34;参数。

    *我为变量声明了dword,因为%d使用了dword,就像printf一样 **格式化字符串末尾的,0是一个标记字符。

答案 3 :(得分:1)

对于64位nasm:

要在nasm中使用scanf,首先需要在.text部分之前添加该语句。

extern scanf

现在您需要先使用

设置堆栈
push rbp

如果您不希望出现分段错误,这一点很重要。进行调用之前,堆栈指针rsp必须对齐16字节边界。进行调用的过程将返回地址(8个字节)压入堆栈,因此当函数获得控制权时,rsp不会对齐。您必须自己推动额外的空间,方法是推入一些东西或从rsp中减去8。您可以详细了解here

现在,您的堆栈已准备就绪,您需要首先将输入格式的字符串移动到rdi寄存器中,然后按严格的顺序移动rsi,rdx,rcx,r8,r9中的参数。

让我们以模仿c语句为例

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

等效的nasm代码为:

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

下面的代码是上面代码的更漂亮的版本。它提示用户输入,并打印输入的号码。

        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

感谢@peter的有益和深刻见解。

答案 4 :(得分:0)

假设您想要做类似

的事情
scanf("%d %d", &var1, &var2);

这需要两个值并将它们存储在变量中。

在装配中,您将push变量的地址放入堆栈(按向后顺序),然​​后call scanf

像你这样的东西有两个变量

var1 resd 1
var2 resd 1

......然后

push var2
push var1
call scanf

*请注意,我按向后顺序推送它们,第一个值将存储在var1中。

执行后,您输入的值将存储在变量中。

如果你只需要一个值,只需按一个变量。