17

我试图弄清楚如何使用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 位汇编。我只使用过 16 位,而且我知道在 16 位(DOS)中你可以这样做:

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

input rb 100d

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

4

5 回答 5

19

我发现了这个“在 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;
}
于 2012-06-11T01:43:30.457 回答
6

谢谢普里特。我根据你的代码做了一个简单的例子来说明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 
于 2013-02-24T19:45:25.347 回答
3

这是您在汇编中搜索时出现的第一个帖子scanf,因此,即使它是 4 年前的帖子,我认为它应该是正确的。

Oukei,因此,call scanf在 NASM 组装中,您需要:

  1. 外部扫描
  2. 为你准备一个格式scanf
  3. 准备变量或单个变量以存储预期的值
  4. 倒序推送参数
  5. 调用 scanf
  6. 恢复堆栈

所以,假设你正在托盘

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

并遵循列表:
... 1。

section .text
extern scanf

... 2. 这是您传递给 C 的第一个参数scanf,它说明您将获得什么。一个整数、两个、一个浮点数、字符串、字符...在这种情况下,两个整数之间用空格隔开(也适用于 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 字节)添加到堆栈中以“跳转”参数。

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

于 2016-09-18T02:33:35.930 回答
1

对于 64 位 nasm:

要将 scanf 与 nasm 一起使用,您首先需要在 .text 部分之前添加语句。

extern scanf

现在您需要首先使用

push rbp

如果您不想出现分段错误,这一点很重要。在进行调用之前,堆栈指针 rsp 必须与 16 字节边界对齐。进行调用的过程会将返回地址(8 字节)压入堆栈,因此当函数获得控制权时,rsp 未对齐。你必须自己创造额外的空间,通过推动一些东西或从 rsp 中减去 8。你可以在这里阅读更多关于它的信息。

现在,您的堆栈已准备就绪,您需要首先将输入格式化字符串移动到 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的有益和有见地的评论。

于 2020-04-12T13:25:57.937 回答
0

假设你想做类似的事情

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

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

在汇编中,您会将push变量的地址放入堆栈(按倒序排列),然后call scanf.

像你有两个变量

var1 resd 1
var2 resd 1

... 进而

push var2
push var1
call scanf

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

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

如果您只想要一个值,只需推送一个变量。

于 2016-09-13T22:48:17.343 回答