分段错误(内核转储)- C调用汇编代码进行主要测试

mznpcxlj  于 2022-12-03  发布在  其他
关注(0)|答案(2)|浏览(136)

我有一个汇编语言代码,我从C调用,但我一直得到错误:**分段错误(核心转储)**我不知道原因。

; this is the assembly code
section .text

global isPrime

isPrime: mov ecx, 2
.test:   cmp ecx, [rsi]
         jle .doif
         jg .prime

.doif:   mov eax, [rdi]
         mov edx, 0
         mov ebx, [rsi]
         div ebx
         cmp edx, 0
         jle .notPrime
         jg .checkAgain
.notPrime:
         mov eax, 1
         ret

.checkAgain:
         inc ecx
         jmp .test

.prime: mov eax, 0
         ret

C代码:

// C code

#include <stdio.h>

extern int isPrime(int *number, int *mValue);

int main() {
    int limit, m, input = 0;
    printf("Enter the limit of the prime numbers:");
    input = scanf("%d", &limit);
    while (input != 1) {
        printf("Not a number!\n");
        scanf("%*[^\n]");
        printf("Enter the limit of the prime numbers:");
        input = scanf("%d", &limit);
    }
    for (int i = 2; i <= limit; ++i) {
        m = i / 2;
        int flag = isPrime(&i, &m); //this is what I'm trying to implement
        // for (int j = 2; j <= m; j++) {
        //     if (i % j == 0) {
        //         printf("Number %d is not prime\n", i);
        //         flag = 1;
        //         break;
        //     }
        // }
        printf("%d\n", flag);
        if (flag == 0)
            printf("Number %d is prime\n", i);
    }
  return 0;
}

错误:

Enter the limit of the prime numbers:10
0
0
Segmentation fault (core dumped)

C代码中的注解部分是我想用汇编语言编写的,但出现了上面提到的错误。根据我的研究,我试图编写一个我没有访问权限的内存地址。错误来自汇编代码,但我不知道具体在哪里,请问有什么可能的解决方法吗?

ni65a41a

ni65a41a1#

System V x86_64调用约定要求在函数调用期间保留寄存器rbxrsprbpr12r13r14r15。您将ebx寄存器用作rbx的一部分,但不保留它。
简单的解决方案是将其保留在堆栈上:

bits 64
section .text

global isPrime

isPrime: push rbx
         mov ecx, 2
.test:   cmp ecx, [rsi]
         jle .doif
         jg .prime

.doif:   mov eax, [rdi]
         mov edx, 0
         mov ebx, [rsi]
         div ebx
         cmp edx, 0
         jle .notPrime
         jg .checkAgain
.notPrime:
         pop rbx
         mov eax, 1
         ret

.checkAgain:
         inc ecx
         jmp .test

.prime:  pop rbx
         mov eax, 0
         ret

注意:有两个返回点,所以每次返回前都要恢复rbx
也可以在每次迭代修改之前保存寄存器。它可以节省一个指令的代码大小,代价是每次通过循环执行push/pop。div很慢,但在一些现代CPU上可能会更慢。(示例如何不这样做):

...
.doif:   push rbx       ; don't do this, save/restore around the whole function
         mov eax, [rdi]
         mov edx, 0
         mov ebx, [rsi]
         div ebx
         pop rbx
...

更好的解决方案是使用允许修改的暂存寄存器,而不是ebx

mov r8d, [rsi]
         div r8d

或者甚至除以内存中的值:

div dword [rsi]

另一些改进:

  • 出于性能原因,您可以在开始时将值加载到寄存器中。
  • jle指令是冗余的:如果不执行jump if greater,CPU继续执行正确分支。
  • cdq指令可用于将eax中的值扩展到edx:eax。注:它的行为与mov edx,0不同,因为它扩展符号位。但是我们接收指向有符号整数的指针,所以理论上我们应该使用cdqidiv,而不是零扩展和div。(您不需要cdq + div;如果您输入是负数,div会将其视为无符号的巨型值,而fault when the quotient doesn't fit in 32 bits会将其视为无符号的巨型值。)
  • 对于等于零的情况,可以使用test edx,edx来代替cmp edx,0,这些指令比and equal or faster短一个字节,那么jz/jnz条件跳转在语义上更合适,但是机器指令与je/jne相同。
  • 类似的x86窥视孔优化是zeroing a register with xor eax, eax
  • 您可以无条件地递增ecx。它节省了一条跳转指令。
    **但您的原始代码中存在逻辑错误:所有的除法都是以相同的值进行。**对于质数测试,您应该依序除以所有的值。(或者只除以奇数值,在检查一次偶数之后,这样您就可以将计数器递增2。)
div ecx

最后的代码是:

section .text

global isPrime

isPrime: mov edi, [rdi]      ; load the pointed-to integers
         mov esi, [rsi]      ;  better: pass by value in the first place
         mov ecx, 2          ; starting divisor, and anything signed-less-than this is assumed prime.

                           ; do {
.test:   cmp ecx, esi
         jg .prime           ; if(ecx > endpoint) return yes

         mov eax, edi
         xor edx, edx        ; zero-extend EAX into EDX:EAX, or use cdq/idiv for signed division
         div ecx
         inc ecx
         test edx, edx
         jnz .test         ; }while( n % ecx == 0)
     ;; composite if we left the loop this way
         mov eax, 1
         ret

.prime:  xor eax, eax      ; eax=0
         ret

您将C变量声明为有符号的int,但在asm中使用的是无符号的div。通常人们不会谈论负素数。
在ESI/RSI中传递第二个参数没有多大意义;上限除数限制是素数测试算法的一部分,而不是调用者应该需要为其计算的东西。mov esi, edi; shr esi, 1 .
对于大的数字,你可以更快地停止,在sqrt(n)而不是n/2。或者在n/divisor <= divisor时,所以你实际上不必计算平方根。请参见代码评审答案,了解一个循环,它可以做到这一点,并以2为增量。

km0tfn4u

km0tfn4u2#

以防有人遇到类似问题并需要帮助:

section .text
global isPrime

isPrime: mov ecx, 2
.test:   cmp ecx, [rsi]
         jle .doif
         jg .prime

.doif:   mov eax, [rdi]
         mov edx, 0
         div ecx
         cmp edx, 0
         jg .checkAgain
         mov eax, 1
         ret

.checkAgain:
         inc ecx
         jmp .test

.prime:  mov eax, 0
         ret

相关问题