扫描一个整数并打印NASM中的区间(1,整数)
Scan an integer and print the interval (1, integer) in NASM
我正在尝试从 Linux Ubuntu 16.04 x64 学习汇编语言。
现在我有以下问题:
- 扫描一个整数 n 并打印从 1 到 n 的数字。
对于 n = 5 我应该有 1 2 3 4 5。
我试着用 scanf 和 printf 来做,但在我输入数字后,它退出了。
密码是:
;nasm -felf64 code.asm && gcc code.o && ./a.out
SECTION .data
message1: db "Enter the number: ",0
message1Len: equ $-message1
message2: db "The numbers are:", 0
formatin: db "%d",0
formatout: db "%d",10,0 ; newline, nul
integer: times 4 db 0 ; 32-bits integer = 4 bytes
SECTION .text
global main
extern scanf
extern printf
main:
mov eax, 4
mov ebx, 1
mov ecx, message1
mov edx, message1Len
int 80h
mov rdi, formatin
mov rsi, integer
mov al, 0
call scanf
int 80h
mov rax, integer
loop:
push rax
push formatout
call printf
add esp, 8
dec rax
jnz loop
mov rax,0
ret
我知道在这个循环中我会得到反向输出 (5 4 3 2 1 0),但我不知道如何设置条件。
我使用的命令如下:
nasm -felf64 code.asm && gcc code.o && ./a.out
你能帮我找出我哪里出错了吗?
存在几个问题:
1. printf 的参数,如评论中所述。在x86-64中,前几个参数是在寄存器中传递的。
2. printf不保留eax的值。
3. 堆栈错位。
4.使用rbx不保存调用者的值
5. 正在加载 integer
的地址而不是它的值。
6. 由于printf是可变参数函数,调用前需要将eax设为0
7. 调用 scanf.
后虚假 int 80h
我将重复整个函数以显示上下文中的必要更改。
main:
push rbx ; This fixes problems 3 and 4.
mov eax, 4
mov ebx, 1
mov ecx, message1
mov edx, message1Len
int 80h
mov rdi, formatin
mov rsi, integer
mov al, 0
call scanf
mov ebx, [integer] ; fix problems 2 and 5
loop:
mov rdi, formatout ; fix problem 1
mov esi, ebx
xor eax, eax ; fix problem 6
call printf
dec ebx
jnz loop
pop rbx ; restore caller's value
mov rax,0
ret
P.S。要使其向上计数而不是向下计数,请像这样更改循环:
mov ebx, 1
loop:
<call printf>
inc ebx
cmp ebx, [integer]
jle loop
您正在使用 x86-64 System V 调用约定正确调用 scanf
。它在 eax
中保留其 return 值。成功转换一个操作数 (%d
) 后,它 returns 与 eax
= 1.
... correct setup for scanf, including zeroing AL.
call scanf ; correct
int 80h ; insane: system call with eax = scanf return value
然后你 运行 int 80h
,它使用 eax=1
作为代码进行 32 位 legacy-ABI 系统调用来确定 哪个 系统调用。 (参见 What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?)。
eax=1
/ int 80h
在 Linux 上是 sys_exit
。 (unistd_32.h
有 __NR_exit
= 1)。 使用调试器;那会告诉你是哪条指令让你的程序退出。
您的标题(在我更正之前)说您遇到了分段错误,但我在我的 x86-64 桌面上进行了测试,事实并非如此。它使用 int 80h
退出系统调用干净地退出。 (但在发生段错误的代码中,使用调试器找出是哪条指令。)strace
decodes int 0x80
system calls incorrectly in 64-bit processes,使用来自 unistd_64.h
的 64 位 syscall
调用编号,而不是 32 位 unistd_32.h
电话号码。
您的代码接近工作:您为 sys_write
正确使用 int 0x80
32 位 ABI,并且只传递 32 位参数。 (指针参数适合 32 位,因为在 x86-64 上的默认代码模型中,static code/data 始终位于虚拟地址 space 的低 2GiB 中。正是出于这个原因,您可以使用 compact mov edi, formatin
之类的指令将地址放入寄存器,或将它们用作立即数或 rel32 有符号位移。)
OTOH 我认为你这样做是出于错误的原因。正如@prl 指出的那样,您忘记保持 16 字节堆栈对齐。
此外,将系统调用与 C stdio 函数混合通常不是一个好主意。 Stdio 使用内部缓冲区而不是总是对每个函数调用进行系统调用,因此事情可能会出现乱序,或者当 [=32= 的 stdio 缓冲区中已经有数据时,read
可以等待用户输入].
你的循环在几个方面也被打破了。您似乎正在尝试使用 32 位调用约定(堆栈上的参数)调用 printf
。
即使在 32 位代码中,这也是错误的,因为 printf
的 return 值在 eax
中。所以你的循环是无限的,因为 printf
returns 打印的字符数。这至少是 %d\n
格式字符串中的两个,因此 dec rax
/ jnz
将始终跳转。
在 x86-64 SysV ABI 中,如果您没有在 XMM 寄存器中传递任何 FP 参数,则在调用 printf
(使用 xor eax,eax
)之前需要将 al
清零.您还必须在 rdi
、rsi
、... 中传递参数,例如 scanf.
在压入两个 8 字节的值后,您还 add rsp, 8
,因此堆栈会永远增长。 (但是你永远不会 return,所以最终的段错误将在堆栈溢出时发生,而不是在尝试 return 时 rsp
不指向 return 地址。)
决定您是在制作 32 位还是 64 位代码,并且仅 copy/paste 来自模式示例和您的目标 OS. (不过请注意,64 位代码可以而且经常使用大部分 32 位寄存器。)
另请参阅 Assembling 32-bit binaries on a 64-bit system (GNU toolchain)(其中包含一个 NASM 部分,其中包含一个方便的 asm-link
脚本,可将 link 和 link 汇编成静态二进制文件)。但是因为你正在写 main
而不是 _start
并且正在使用 libc 函数,你应该只 link 和 gcc -m32
(如果你决定使用 32 位代码而不是用 64 位 function-calling 和 system-call 约定替换程序的 32 位部分。
参见 What are the calling conventions for UNIX & Linux system calls on i386 and x86-64。
我正在尝试从 Linux Ubuntu 16.04 x64 学习汇编语言。 现在我有以下问题: - 扫描一个整数 n 并打印从 1 到 n 的数字。
对于 n = 5 我应该有 1 2 3 4 5。 我试着用 scanf 和 printf 来做,但在我输入数字后,它退出了。
密码是:
;nasm -felf64 code.asm && gcc code.o && ./a.out
SECTION .data
message1: db "Enter the number: ",0
message1Len: equ $-message1
message2: db "The numbers are:", 0
formatin: db "%d",0
formatout: db "%d",10,0 ; newline, nul
integer: times 4 db 0 ; 32-bits integer = 4 bytes
SECTION .text
global main
extern scanf
extern printf
main:
mov eax, 4
mov ebx, 1
mov ecx, message1
mov edx, message1Len
int 80h
mov rdi, formatin
mov rsi, integer
mov al, 0
call scanf
int 80h
mov rax, integer
loop:
push rax
push formatout
call printf
add esp, 8
dec rax
jnz loop
mov rax,0
ret
我知道在这个循环中我会得到反向输出 (5 4 3 2 1 0),但我不知道如何设置条件。
我使用的命令如下:
nasm -felf64 code.asm && gcc code.o && ./a.out
你能帮我找出我哪里出错了吗?
存在几个问题:
1. printf 的参数,如评论中所述。在x86-64中,前几个参数是在寄存器中传递的。
2. printf不保留eax的值。
3. 堆栈错位。
4.使用rbx不保存调用者的值
5. 正在加载 integer
的地址而不是它的值。
6. 由于printf是可变参数函数,调用前需要将eax设为0
7. 调用 scanf.
int 80h
我将重复整个函数以显示上下文中的必要更改。
main:
push rbx ; This fixes problems 3 and 4.
mov eax, 4
mov ebx, 1
mov ecx, message1
mov edx, message1Len
int 80h
mov rdi, formatin
mov rsi, integer
mov al, 0
call scanf
mov ebx, [integer] ; fix problems 2 and 5
loop:
mov rdi, formatout ; fix problem 1
mov esi, ebx
xor eax, eax ; fix problem 6
call printf
dec ebx
jnz loop
pop rbx ; restore caller's value
mov rax,0
ret
P.S。要使其向上计数而不是向下计数,请像这样更改循环:
mov ebx, 1
loop:
<call printf>
inc ebx
cmp ebx, [integer]
jle loop
您正在使用 x86-64 System V 调用约定正确调用 scanf
。它在 eax
中保留其 return 值。成功转换一个操作数 (%d
) 后,它 returns 与 eax
= 1.
... correct setup for scanf, including zeroing AL.
call scanf ; correct
int 80h ; insane: system call with eax = scanf return value
然后你 运行 int 80h
,它使用 eax=1
作为代码进行 32 位 legacy-ABI 系统调用来确定 哪个 系统调用。 (参见 What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?)。
eax=1
/ int 80h
在 Linux 上是 sys_exit
。 (unistd_32.h
有 __NR_exit
= 1)。 使用调试器;那会告诉你是哪条指令让你的程序退出。
您的标题(在我更正之前)说您遇到了分段错误,但我在我的 x86-64 桌面上进行了测试,事实并非如此。它使用 int 80h
退出系统调用干净地退出。 (但在发生段错误的代码中,使用调试器找出是哪条指令。)strace
decodes int 0x80
system calls incorrectly in 64-bit processes,使用来自 unistd_64.h
的 64 位 syscall
调用编号,而不是 32 位 unistd_32.h
电话号码。
您的代码接近工作:您为 sys_write
正确使用 int 0x80
32 位 ABI,并且只传递 32 位参数。 (指针参数适合 32 位,因为在 x86-64 上的默认代码模型中,static code/data 始终位于虚拟地址 space 的低 2GiB 中。正是出于这个原因,您可以使用 compact mov edi, formatin
之类的指令将地址放入寄存器,或将它们用作立即数或 rel32 有符号位移。)
OTOH 我认为你这样做是出于错误的原因。正如@prl 指出的那样,您忘记保持 16 字节堆栈对齐。
此外,将系统调用与 C stdio 函数混合通常不是一个好主意。 Stdio 使用内部缓冲区而不是总是对每个函数调用进行系统调用,因此事情可能会出现乱序,或者当 [=32= 的 stdio 缓冲区中已经有数据时,read
可以等待用户输入].
你的循环在几个方面也被打破了。您似乎正在尝试使用 32 位调用约定(堆栈上的参数)调用 printf
。
即使在 32 位代码中,这也是错误的,因为 printf
的 return 值在 eax
中。所以你的循环是无限的,因为 printf
returns 打印的字符数。这至少是 %d\n
格式字符串中的两个,因此 dec rax
/ jnz
将始终跳转。
在 x86-64 SysV ABI 中,如果您没有在 XMM 寄存器中传递任何 FP 参数,则在调用 printf
(使用 xor eax,eax
)之前需要将 al
清零.您还必须在 rdi
、rsi
、... 中传递参数,例如 scanf.
在压入两个 8 字节的值后,您还 add rsp, 8
,因此堆栈会永远增长。 (但是你永远不会 return,所以最终的段错误将在堆栈溢出时发生,而不是在尝试 return 时 rsp
不指向 return 地址。)
决定您是在制作 32 位还是 64 位代码,并且仅 copy/paste 来自模式示例和您的目标 OS. (不过请注意,64 位代码可以而且经常使用大部分 32 位寄存器。)
另请参阅 Assembling 32-bit binaries on a 64-bit system (GNU toolchain)(其中包含一个 NASM 部分,其中包含一个方便的 asm-link
脚本,可将 link 和 link 汇编成静态二进制文件)。但是因为你正在写 main
而不是 _start
并且正在使用 libc 函数,你应该只 link 和 gcc -m32
(如果你决定使用 32 位代码而不是用 64 位 function-calling 和 system-call 约定替换程序的 32 位部分。
参见 What are the calling conventions for UNIX & Linux system calls on i386 and x86-64。