对于所有的人来说,学习计算机编程的艺术,能够访问像Stack溢出这样的社区是多么幸运啊!我决定承担起学习计算机编程的任务,我是通过一本名为“从头编程”的电子书的知识来完成这一任务的,这本书教读者如何在GNU/Linux环境下用汇编语言创建程序。
我在书中的进展已经到了用函数计算整数4的阶乘的地步,我已经做了和做了,没有任何由GCC的汇编程序引起的错误,或者运行这个程序所造成的任何错误。但是,函数在我的程序中没有返回正确的答案!4的阶乘为24,但程序返回的值为0!正确地说,我不知道这是为什么!
下面是您需要考虑的代码:
.section .data
.section .text
.globl _start
.globl factorial
_start:
push $4 #this is the function argument
call factorial #the function is called
add $4, %rsp #the stack is restored to its original
#state before the function was called
mov %rax, %rbx #this instruction will move the result
#computed by the function into the rbx
#register and will serve as the return
#value
mov $1, %rax #1 must be placed inside this register for
#the exit system call
int $0x80 #exit interrupt
.type factorial, @function #defines the code below as being a function
factorial: #function label
push %rbp #saves the base-pointer
mov %rsp, %rbp #moves the stack-pointer into the base-
#pointer register so that data in the stack
#can be referenced as indexes of the base-
#pointer
mov $1, %rax #the rax register will contain the product
#of the factorial
mov 8(%rbp), %rcx #moves the function argument into %rcx
start_loop: #the process loop begins
cmp $1, %rcx #this is the exit condition for the loop
je loop_exit #if the value in %rcx reaches 1, exit loop
imul %rcx, %rax #multiply the current integer of the
#factorial by the value stored in %rax
dec %rcx #reduce the factorial integer by 1
jmp start_loop #unconditional jump to the start of loop
loop_exit: #the loop exit begins
mov %rbp, %rsp #restore the stack-pointer
pop %rbp #remove the saved base-pointer from stack
ret #return发布于 2017-10-17 09:25:10
%rax**,TL:DR:返回地址的阶乘溢出了,留下了0,**,因为您移植错了。
将32位代码移植到64位的并不像更改所有寄存器名那样简单,可以让它组装的,但是正如您所发现的,即使这个简单的程序也有不同的行为。在x86-64中,push %reg和call都推送64位值,并将rsp修改为8。如果使用调试器单步执行代码,您就会看到这一点。(请参阅x86标签维基底部以获得使用gdb进行asm的信息。)
您正在学习一本使用32位示例的书,因此您可能应该只使用 将它们构建为32位可执行文件。,而不是在您知道如何将它们移植到64位之前。
使用32位sys_exit() ABI的int 0x80仍然有效(如果在64位代码中使用32位INT0x80LinuxABI怎么办?),但是如果您试图传递64位指针,系统调用就会遇到麻烦。使用64位ABI。
如果要调用任何库函数,也会遇到问题,因为标准函数调用约定也不同。请参阅为什么参数存储在寄存器中而不是x86-64程序集中的堆栈上?和64位ABI链接,以及x86标记wiki中的其他调用约定文档。
但是你没有这样做,所以你的程序的问题很简单,就是没有考虑到x86-64中翻了一倍的“堆栈宽度”。factorial 函数将返回地址作为参数读取。
这是您的代码,注释以解释它实际上做了什么
push $4 # rsp-=8. (rsp) = qword 4
# non-standard calling convention with args on the stack.
call factorial # rsp-=8. (rsp) = return address. RIP=factorial
add $4, %rsp # misalign the stack, so it's pointing to the top half of the 4 you pushed earlier.
# if this was in a function that wanted to return, you'd be screwed.
mov %rax, %rbx # copy return value to first arg of system call
mov $1, %rax #eax = __NR_EXIT from asm/unistd_32.h, wasting 2 bytes vs. mov $1, %eax
int $0x80 # 32-bit ABI system call, eax=call number, ebx=first arg. sys_exit(factorial(4))所以调用方是好的(对于您发明的非标准64位调用约定,它传递堆栈上的所有arg)。您最好完全忽略add到%rsp,因为您即将退出而不进一步接触堆栈。
.type factorial, @function #defines the code below as being a function
factorial: #function label
push %rbp #rsp-=8, (rsp) = rbp
mov %rsp, %rbp # make a traditional stack frame
mov $1, %rax #retval = 1. (Wasting 2 bytes vs. the exactly equivalent mov $1, %eax)
mov 8(%rbp), %rcx #load the return address into %rcx
... and calculate the factorial对于静态可执行文件(以及动态链接的可执行文件没有用PIE启用ASLR的),_start通常位于0x4000c0。您的程序仍将在现代CPU上几乎瞬间运行,因为imul的imul* 3c延迟时间仍然只有1250万个核心时钟周期。在4 4GHz上,这是3毫秒的CPU时间。
如果您通过在最近的发行版上与gcc foo.o链接来创建一个与位置无关的可执行文件,_start将有一个类似于0x5555555545a0的地址,您的函数在4 4GHz上运行3周期imul延迟将花费大约70368秒。
4194496!它包含许多偶数,因此它的二进制表示有许多尾随零。当您完成时,整个%rax将为零,将从0x4000c0减为1的所有数字相乘。
Linux进程的退出状态仅为传递给sys_exit()的整数的低8位(因为wstatus只是一个32位int,并且包含其他东西,比如结束进程的信号)。见wait4(2))。所以即使用小的args,也不需要太多。
https://stackoverflow.com/questions/46778697
复制相似问题