Collat​​z功能在装配 - 分段故障

时间:2016-12-21 02:26:34

标签: c assembly x86-64

我正在尝试在C和x86-64汇编语言之间编写混合程序。该程序应使用Collat​​z函数计算1和给定参数 n 之间的数字的最大停止时间。 main函数用C语言编写,在for-loop中它调用用汇编语言编写的外部函数。

但是,在为大于2的值运行已编译的混合程序时,我遇到了分段错误。使用 gdb 我发现在进行递归调用时出错。这是我得到的错误:

Program received signal SIGSEGV, Segmentation fault.
0x00000000004006c3 in is_odd ()

C代码:

#include <stdio.h>
#include <stdlib.h>

int noOfOp = 0;

extern int collatz(long long n);

// The main function. Main expects one parameter n.
// Then, it computes collatz(1), colllatz(2), ..., collataz(n) and finds the
// a number m, 1 <= m <= n with the maximum stopping time.
int main(int argc, char *argv[]){
    if (argc != 2) {
        printf("Parameter \"n\" is missing. \n");
        return -1;
    } else {
        int max=0;
        long long maxn=0;
        int tmp=0;
        long long n = atoll(argv[1]);
        for (long long i=1 ; i<=n ; i++) {
            tmp = collatz(i);
            if (tmp > max) {
                max = tmp;
                maxn=i;
            }
        }
        printf("The largest stopping time between 1 and %lld was %lld ", n,maxn);
        printf("with the stopping time of %d. \n", max);
    }
}

这是我编写的x86-64汇编代码。我希望这段代码能够反映出我对装配缺乏正确的理解。这是一个课堂作业,我们已经有四天时间完成这个新主题。通常我会阅读更多文档,但我很简单,因为我没有时间。汇编语言很难。

.section .text
.global collatz

collatz:
    pushq   %rbp            # save old base pointer
    movq    %rsp, %rbp      # create new base pointer
    subq    $16, %rsp       # local variable space


    cmpq    $1, %rdi        # compare n to 1
    je      is_one          # if n = 1, return noOfOp

    incq    noOfOp          # else n > 1, then increment noOfOp
    movq    %rdi, %rdx      # move n to register rdx
    cqto                    # sign extend rdx:rax
    movq    $2, %rbx        # move 2 to register rbx
    idivq   %rbx            # n / 2 -- quotient is in rax, remainder in rdx
    cmpq    $1, %rdx        # compare remainder to 1
    je      is_odd          # if n is odd, jump to is_odd
    jl      is_even         # else n is even, jump to is_even
    leave                   # remake stack
    ret                     # return


is_odd:
    movq    %rdi, %rdx      # move n to register rdx
    cqto                    # sign extend rdx:rax
    movq    $3, %rbx        # move 3 to register rbx
    imulq   %rbx            # n * 3 -- result is in rax:rdx
    movq    %rax, %rdi      # move n to register rdi
    incq    %rdi            # n = n + 1
    call    collatz         # recursive call: collatz(3n+1) <---- this is where the segmentation fault seems to happen
    leave                   # remake stack
    ret                     # return

is_even:
    movq    %rax, %rdi      # n = n / 2 (quotient from n/2 is still in rax)
    call    collatz         # recursive call: collatz(n/2) <---- I seem to have gotten the same error here by commenting out most of the stuff in is_odd
    leave                   # remake stack
    ret                     # return

is_one:
    movq    noOfOp, %rax    # set return value to the value of noOfOp variable
    leave                   # remake stack
    ret                     # return

我感谢所有我能得到的帮助和建议。

3 个答案:

答案 0 :(得分:2)

我在检查代码时看到的两个问题:

  1. noOfOp被声明为int,在x86-64上将是32位类型。但是,汇编代码将其视为64位类型。具体而言,使用incq将其递增1。应该是incl noOfOpaddl $1, noOfOp

    同样,您的collatz函数原型为返回int,但您的代码建议您尝试在rax中返回64位值。这不会导致任何问题,因为调用者只会使用低32位,但可能会导致正确性问题。

  2. 在递归调用collatz函数时忽略调用约定。假设您使用的是Linux,则适用的版本为the System V AMD64 calling convention。这里,RBPRBX寄存器是被调用者保存的。因此,您需要保留其内容。请务必熟悉调用约定并遵循其规则。

    正如其中一位评论者建议的那样,在将函数转换为汇编之前,最好先用C或C ++编写函数。这也使调试变得更容易,并且还可以查看编译器发出的代码。您可以根据自己手写的汇编代码检查编译器的输出。

  3. 我的代码可能还有其他问题,我没有发现。您可以通过使用调试器单步执行代码来找到它们。您已经在使用GDB了,所以这应该很简单。

答案 1 :(得分:1)

在彼得在上面的评论中提出的建议之后,我读到了他和其他才华横溢的人在同一主题的另一个主题中所讨论的内容。这是我在实现其中一些想法后最终得到的代码。现在比使用gcc -O3编译的速度快30%。我无法相信程序可以更快更快地成为这些不同的&#34;技巧&#34; - 我真正学到了很多关于效率的知识。谢谢那些帮助过的人。

.section .text
.global collatz

collatz:
    pushq   %rbp                    # save old base pointer
    movq    %rsp, %rbp              # create new base pointer
    subq    $16, %rsp               # local variable space  
    movq    $-1, %r10               # start counter at -1

while_loop:
    incq    %r10                    # increment counter
    leaq    (%rdi, %rdi, 2), %rdx   # rdx = 2 * n + n
    incq    %rdx                    # rdx = 3n+1
    sarq    %rdi                    # rdi = n/2
    cmovc   %rdx, %rdi              # if CF, rdi = rdx
                                    # (if CF was set during right shift (i.e. n is odd) set rdi to 3n+1)
                                    # else keep rdi to n/2
    jnz     while_loop              # if n =/= 1 do loop again:
                                    # Z flag is only set if sarq shifts when n is 1 making result 0.
                                    # else
    movq    %r10, %rax              # set return value to counter
    leave                           # remake stack
    ret                             # return

答案 2 :(得分:0)

感谢您的所有答案。如果我的问题没有遵循Stack Overflow指南,我会道歉 我的意思是,如果我有更多的时间,通常我不会打扰别人。相反,我寻求指导 - 而不是推测调试服务 - 这可能会让我走上正确的道路。

对于任何有兴趣的人,我都让这个计划有效。我采用了与最初发布的方法不同的方法,并对加速进行了一些更改。下面是新的汇编代码。

.section .text
.global collatz

collatz:
    pushq   %rbp            # save old base pointer
    movq    %rsp, %rbp      # create new base pointer
    subq    $16, %rsp       # local variable space

    cmpq    $1, %rdi        # compare n to 1
    je      is_one          # if n = 1, jump to is_one
                            # else n > 1
    incl    noOfOp          # increment noOfOp
    movq    %rdi, %rax      # move n to rax
    andq    $1, %rax        # AND 1 with n
    jz      is_even         # if n is even jump to is_even
                            # else n is odd
    movq    $3, %rdx        # move 3 to rdx
    imul    %rdx, %rdi      # n = 3 * n
    incq    %rdi            # n = 3n + 1
    call    collatz         # recursive call: collatz(3n+1)
    leave                   # remake stack
    ret                     # return

is_even:
    sarq    %rdi            # arithmetic right shift by 1 - divide n by 2
    call    collatz         # recursive call: collatz(n/2)
    leave                   # remake stack
    ret                     # return

is_one:
    movl    noOfOp, %eax    # set return value to noOfOp
    movl    $0, noOfOp      # reset noOfOp
    leave                   # remake stack
    ret                     # return

这是有效的,约为。比我在C中编写的代码快30%。但我从作业中知道,我可以减少更多时间,使其更有效。如果有人有任何想法,请随时发表评论。

再次感谢你。