我正在尝试在C和x86-64汇编语言之间编写混合程序。该程序应使用Collatz函数计算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
我感谢所有我能得到的帮助和建议。
答案 0 :(得分:2)
我在检查代码时看到的两个问题:
noOfOp
被声明为int
,在x86-64上将是32位类型。但是,汇编代码将其视为64位类型。具体而言,使用incq
将其递增1。应该是incl noOfOp
或addl $1, noOfOp
。
同样,您的collatz
函数原型为返回int
,但您的代码建议您尝试在rax
中返回64位值。这不会导致任何问题,因为调用者只会使用低32位,但可能会导致正确性问题。
在递归调用collatz
函数时忽略调用约定。假设您使用的是Linux,则适用的版本为the System V AMD64 calling convention。这里,RBP
和RBX
寄存器是被调用者保存的。因此,您需要保留其内容。请务必熟悉调用约定并遵循其规则。
正如其中一位评论者建议的那样,在将函数转换为汇编之前,最好先用C或C ++编写函数。这也使调试变得更容易,并且还可以查看编译器发出的代码。您可以根据自己手写的汇编代码检查编译器的输出。
我的代码可能还有其他问题,我没有发现。您可以通过使用调试器单步执行代码来找到它们。您已经在使用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%。但我从作业中知道,我可以减少更多时间,使其更有效。如果有人有任何想法,请随时发表评论。
再次感谢你。