大会 - NASM因子问题

时间:2011-12-10 01:27:35

标签: linux assembly x86 nasm

您好我正在使用NASM在Assembly中编写阶乘函数。为了我的任务,我必须使用俄语乘法代替mul。我使用的是32位linux

这是我的因子代码

section .text
global factorial
extern rpmult

factorial:
    push    ebp
    mov     ebp, esp
    sub     esp, 4 ;creates memory for local variable at ebp-4
    mov     esi, [ebp+8] ; put n in esi
    cmp     esi, 1 ; n <= 1
    jbe     .done ; if so jump to done

.try:
    mov     [ebp-4],esi ;adds n temporarily into ebp-4
    dec     esi ; n - 1
    push    esi ; push arugment
    call    factorial ;call factorial again stores result in esi
    add     esp, 4 ;gets rid of the argument    

    mov     edi, esi ;copies n - 1 into edi    
    mov     esi,[ebp+4] ;gets the original value back (n)
    call    rpmult ;multiply
    jmp     .done ;once it reaches here, finished the function

.done:
    mov     esp, ebp ;restores esp
    pop     ebp
    ret     ;return the value

这是我的rpmult代码:

section .text

global rpmult

rpmult:
    push    ebp
    mov     ebp, esp
    sub     esp, 4     ;allocate m

    mov     dword [ebp-4], 0   ; m = 0;
.while:
    test    edi, edi   ; x == 0?
    je      .done
    test    esi, esi   ; y == 0?
    je      .done

    test    edi, 0x01  ; x is odd?
    jz      .shifts
    add     [ebp-4], esi   ; m += y;

.shifts:
    shr     edi, 1     ; x >>= 1;
    shl     esi, 1     ; y <<= 1;
    jmp     .while

.done:
    mov     eax, [ebp-4]
    ;mov    esp, ebp
    ;pop    ebp
    leave
    ret

当我通过C程序使用该功能时,请说4的阶乘!我得到了

4! = 13803416593125867520

我相信我的代码是正确的,但我不知道该怎么做。我需要让factorial函数与我的final的rpmult函数一起使用。任何帮助表示赞赏! 谢谢!

1 个答案:

答案 0 :(得分:1)

(注意:在我清醒过来之后再次查看这个答案并阅读了@ lloydm的评论后,我重写了这个答案。)

有三个问题领域:

1)递归的基本情况

调试递归函数时,首先检查基本情况总是明智的。

那么在计算1!时会发生什么?

factorial:
push ebp
mov ebp, esp
sub esp, 4 ;creates memory for local variable at ebp-4
mov esi, [ebp+8] ; put n in esi
cmp esi, 1 ; n <= 1
jbe .done ; if so jump to done
...
.done:
mov esp, ebp ;restores esp
pop ebp
ret ;return the value

这里有两个问题:

  1. 从C调用时,您希望此代码正常工作,这意味着您需要遵循通常的调用约定(对于Linux,使用gcc意味着“cdecl” - 请参阅http://en.wikipedia.org/wiki/X86_calling_conventions)。因此,您需要保留esiediebpebx。但是这段代码会覆盖esi中的任何内容。当从C调用函数时,这将导致不可预测的行为,因为C编译器生成的代码将假定在调用esi之前factorial中的任何内容在返回时仍然存在。如果先将值保存在某处(并在返回前将其恢复),则只能使用这些寄存器。

  2. 返回值在eax中传递,但您没有在eax中添加任何内容。您希望1!的答案为1,而不是“目前eax中的任何随机垃圾”!

  3. 2)递归的递归情况

    ...
    .try:
    mov [ebp-4],esi ;adds n temporarily into ebp-4
    dec esi ; n - 1
    push esi ; push arugment
    call factorial ;call factorial again stores result in esi
    add esp, 4 ;gets rid of the argument    
    mov edi, esi ;copies n - 1 into edi    
    mov esi,[ebp+4] ;gets the original value back (n)
    call rpmult ;multiply
    jmp .done ;once it reaches here, finished the function
    ...
    
    1. ediesi一样,是一个需要保留的注册表,如上所述。

    2. mov edi, esi ;copies n - 1 into edi行是错误的。您不想将n - 1放入edi - 您在此处尝试计算(n-1)!*n,因此您希望将(n-1)!放入edi,即递归调用计算的答案。正如@lloydm指出的那样,在eax中返回。 (我在原始答案中被评论误导了,并且认为你真的试图将n - 1放入edi。这不会因为esi不再包含{{}而无效1}}在n - 1之后,因为你没有遵循调用约定。)

    3. call factorial错了(正如我最初指出的那样); mov esi,[ebp+4] ;gets the original value back (n)包含返回地址;这应该是[ebp+4]

    4. 3)奇怪的大值

      [ebp-4]是一个比第一次出现的更陌生的答案:它对于32位值来说太大了。 (在十六进制中:4! = 13803416593125867520,所以它是一个64位值,前32位有一个大数,后32位有零。)

      考虑到其他错误,您可能希望得到一个完全随机的值作为答案,但0xbf8f964200000000会返回一个32位的随机值。那么为什么要在这里打印64位值呢? (如果你没有故意这样做,我想它可能与C代码做了一些奇怪的事情有关,因为代码没有保留factorialesi。)

      调试提示

      首先要弄清楚为什么edi不起作用。使用factorial(5)尽可能简单地开始。然后一直工作到factorial(1)等。