只需将所有寄存器名称从eXX更改为rXX,即可将32位移植到64位,从而使阶乘返回0? [英] Porting from 32 to 64-bit by just changing all the register names from eXX to rXX makes factorial return 0?

查看:140
本文介绍了只需将所有寄存器名称从eXX更改为rXX,即可将32位移植到64位,从而使阶乘返回0?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

学习计算机编程技术的所有人能够使用Stack Overflow这样的社区真是太幸运了!我已经决定承担学习如何对计算机编程的任务,而这是通过了解一本名为《从头开始编程》的电子书来完成的,该书教读者如何用汇编语言创建程序

How fortunate it is for all of use learning the art of computer programming to have access to a community such as Stack Overflow! I have made the decision to take up the task of learning how to program computers and I am doing so by the knowledge of an e-book called 'Programming From the Ground Up', which teaches the reader how to create programs in the assembly language within the GNU/Linux environment.

我在书中所取得的进展是创建了一个程序,该程序使用一个函数来计算整数4的阶乘。所做和完成的操作不会因GCC的汇编程序或程序运行而引起任何错误。但是,我程序中的函数未返回正确答案! 4的阶乘为24,但是程序返回的值为0!正确地说,我不知道为什么会这样!

My progress in the book has come to the point of creating a program which computes the factorial of the integer 4 with a function, which I have made and done without any error caused by the assembler of GCC or caused by running the program. However, the function in my program does not return the right answer! The factorial of 4 is 24, but the program returns a value of 0! Rightly speaking, I do not know why this is!

以下是您要考虑的代码:

Here is the code for your consideration:

.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


推荐答案

TL:DR:寄回地址的阶乘溢出%rax ,留下0,,因为您移植的错误。

TL:DR: the factorial of the return address overflowed %rax, leaving 0, because you ported wrong.

将32位代码移植到64位并不像更改所有寄存器名称那样简单。正如您发现的那样,即使这个简单程序的行为也有所不同。在x86-64中, push%reg call 都推送64位值,并修改 rsp by 8.如果您使用调试器单步执行代码,则会看到此信息。 (有关使用 gdb 的信息,请参见 x86标签Wiki 的底部,

Porting 32-bit code to 64-bit is not as simple as changing all the register names. That might get it to assemble, but as you found even this simple program behaves differently. In x86-64, push %reg and call both push 64-bit values, and modify rsp by 8. You would see this if you single-stepped your code with a debugger. (See the bottom of the x86 tag wiki for info using gdb for asm.)

您正在跟踪一本使用32位示例的书,因此您可能只是将它们构建为32位可执行文件 ,而不是尝试移植

You're following a book that uses 32-bit examples, so you should probably just build them as 32-bit executables instead of trying to port them to 64-bit before you know how.

您的 sys_exit()使用32位 int 0x80 ABI仍然有效(如果您以64位代码使用32位int 0x80 Linux ABI会发生什么? ),但是如果您尝试传递64位指针,则会在系统调用中遇到麻烦。 使用64位ABI

Your sys_exit() using the 32-bit int 0x80 ABI still works (What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?), but you will run into trouble with system calls if you try to pass 64-bit pointers. Use the 64-bit ABI.

如果要调用任何库函数,也会遇到问题,因为标准函数调用约定也不同。请参阅为什么要存储参数在寄存器中,而不是在x86-64 Assembly中的堆栈中?,64位ABI链接以及标签Wiki。

You will also run into problems if you want to call any library functions, because the standard function-calling convention is different, too. See Why parameters stored in registers and not on the stack in x86-64 Assembly?, and the 64-bit ABI link, and other calling-convention docs in the x86 tag wiki.

但是您没有执行任何操作,因此程序的问题只是归结为不考虑x86-64中加倍的堆栈宽度。 您的阶乘函数读取返回地址作为其参数

But you're not doing any of that, so the problem with your program simply comes down to not accounting for the doubled "stack width" in x86-64. Your factorial function reads the return address as its argument.

这是您的代码,评论以解释其实际作用

Here's your code, commented to explain what it actually does

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您发明的位调用约定,该约定会传递堆栈上的所有args)。您最好完全省略 add %rsp 的位置,因为您将要退出而不接触堆栈

So the caller is sort of fine (for the non-standard 64-bit calling convention you've invented that passes all args on the stack). You might as well omit the add to %rsp entirely, since you're about to exit without touching the stack any further.

.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上运行,因为 0x4000c0 * 3c延迟 imul 仍然只有1,250万核心时钟周期。在4GHz CPU上,这是3毫秒的CPU时间。

For static executables (and dynamically linked executables that aren't ASLR enabled with PIE), _start is normally at 0x4000c0. Your program will still run nearly instantaneously on a modern CPU, because 0x4000c0 * 3c latency of imul is still only 12.5 million core clock cycles. On a 4GHz CPU, that's 3 milliseconds of CPU time.

如果通过与 gcc foo.o链接来制作位置独立的可执行文件, 在最近的发行版中, _start 的地址为 0x5555555545a0 ,而您的函数将在具有3个周期的脉冲延迟的4GHz CPU上运行大约需要70368秒。

If you'd made a position-independent executable by linking with gcc foo.o on a recent distro, _start would have an address like 0x5555555545a0, and your function would have taken ~70368 seconds to run on a 4GHz CPU with 3-cycle imul latency.

4194496!包括许多个偶数,因此其二进制表示形式有许多尾随零。整个%rax 会在您乘以每个数字后从 0x4000c0 降至1时为零。

4194496! includes many even numbers, so its binary representation has many trailing zeros. The whole %rax will be zero by the time you're done multiplying by every number from 0x4000c0 down to 1.

Linux进程的退出状态只是传递给 sys_exit()的整数的低8位。 (因为 wstatus 只是一个32位整数,并且包含其他内容,例如结束该过程的信号。请参见 wait4(2) )。因此,即使使用较小的参数,也不会花费太多。

The exit status of a Linux process is only the low 8 bits of the integer you pass to sys_exit() (because the wstatus is only a 32-bit int and includes other stuff, like what signal ended the process. See wait4(2)). So even with small args, it doesn't take much.

这篇关于只需将所有寄存器名称从eXX更改为rXX,即可将32位移植到64位,从而使阶乘返回0?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆