我正在尝试从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
你能帮我找到我错的地方吗?
答案 0 :(得分:3)
有几个问题:
1. printf的参数,如评论中所述。在x86-64中,前几个参数在寄存器中传递
2. printf不保留eax的值
3.堆栈未对齐
4.使用rbx时不保存来电者的值
5.正在加载integer
的地址而不是其值
6.由于printf是一个varargs函数,因此在调用之前需要将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
答案 1 :(得分:1)
您正在使用x86-64 System V调用约定正确调用scanf
。它将返回值保留在eax
中。成功转换一个操作数(%d
)后,返回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位传统ABI系统调用,以确定哪个系统调用。 (见What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?)。
eax=1
/ int 80h
为sys_exit
。 (unistd_32.h
有__NR_exit
= 1)。使用调试器;那本可以告诉你哪个指令让你的程序退出。
你的标题(在我更正之前)说你有分段错误,但我在我的x86-64桌面上进行了测试,情况并非如此。它使用int 80h
退出系统调用完全退出。 (但是在执行段错误的代码中,使用调试器找出哪条指令。)strace
decodes int 0x80
system calls incorrectly in 64-bit processes,使用来自syscall
的64位unistd_64.h
个调用号,而不是32位Assembling 32-bit binaries on a 64-bit system (GNU toolchain) 1}}电话号码。
您的代码已接近工作:您对unistd_32.h
正确使用int 0x80
32位ABI,并且只传递32位args。 (指针args符合32位,因为静态代码/数据总是放在x86-64的默认代码模型中的低2GiB虚拟地址空间中。正是因为这个原因,所以你可以使用像{{1}这样的紧凑指令将地址放在寄存器中,或将它们用作immediates或rel32 signed displacements。)
OTOH我认为你是出于错误的原因这样做的。正如@prl指出的那样,你忘了保持16字节的堆栈对齐。
此外,将系统调用与C stdio函数混合通常是一个坏主意。 Stdio使用内部缓冲区而不是总是在每个函数调用上进行系统调用,因此事情可能出现乱序,或者当{stdio缓冲区中的数据已经存在时sys_write
可以等待用户输入mov edi, formatin
。
你的循环在几个方面也被打破了。您似乎试图使用32位调用约定(堆栈中的args)调用read
。
即使在32位代码中也是如此,因为stdin
的返回值在printf
中。所以你的循环是无限的,因为printf
返回打印的字符数。 eax
格式字符串中至少有两个字符串,因此printf
/ %d\n
将始终跳转。
在x86-64 SysV ABI中,如果您没有在XMM寄存器中传递任何FP参数,则在调用dec rax
(使用jnz
)之前需要将al
归零。您还必须在printf
,xor eax,eax
,...中传递参数,与scanf一样。
在推送两个8字节值后,您还rdi
,因此堆栈会永远增长。 (但是你永远不会返回,所以最终的段错误将是堆栈溢出,而不是试图返回时rsi
没有指向返回地址。)
决定您是否制作32位或64位代码,并仅针对您要定位的模式和操作系统的示例进行复制/粘贴。 (注意,64位代码可以并且经常使用大多数32位寄存器。)
另请参阅What are the calling conventions for UNIX & Linux system calls on i386 and x86-64(其中包含一个带有便捷add rsp, 8
脚本的NASM部分,该脚本可汇编并链接到静态二进制文件中。但是,由于您正在编写rsp
而不是asm-link
并使用libc函数,因此您应该只使用main
(如果您决定使用32位代码而不是替换具有64位函数调用和系统调用约定的程序的32位部分。