繁体   English   中英

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

[英]Segmentation fault (core dumped) - C calling Assembly code for prime testing

我有一个从 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 代码中的注释部分是我想在汇编中编写的,但得到了我上面提到的错误。 根据我的研究,我正在尝试编写一个我无权访问的 memory 地址。 错误来自汇编代码,但我不知道确切位置,请问有什么可能的解决方案吗?

System V x86_64 调用约定要求在 function 调用期间保留寄存器rbxrbp rsp r12r13r14r15 您将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

或者甚至除以 memory 中的值:

        div dword [rsi]

其他一些改进:

  • 出于性能原因,您可以在开始时将值加载到寄存器中。
  • jle指令是冗余的:CPU 继续执行正确的分支如果不执行jump if greater
  • cdq指令可用于将eax中的值扩展到edx:eax中。 注意:它的行为与mov edx,0不同,因为它扩展了符号位。 但是我们收到指向有符号整数的指针,所以理论上我们应该使用cdqidiv ,而不是零扩展和div (您不需要cdq + div ;如果您的输入为负数, div会将其视为巨大的无符号,并在商不适合 32 位时出错。)
  • 对于等于零test edx,edx可以使用 edx 代替cmp edx,0 ,这些指令短一个字节并且等于或更快 那么jz/jnz条件跳转更符合语义,但是和je / jne一样的机器指令。
  • 类似的 x86 窥视孔优化是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 变量声明为 signed int ,但您在 asm 中使用 unsigned div Unsigned 最有意义; 通常人们不会谈论负素数。

在 ESI/RSI 中传递第二个参数没有多大意义; 上限除数限制是质数测试算法的一部分,而不是调用者需要为其计算的东西。 mov esi, edi ; shr esi, 1 .

实际上,对于大数,您可以在sqrt(n)而不是n/2处更快地停止。 或者当n/divisor <= divisor时,您实际上不必计算平方根。 请参阅执行此操作并递增 2 的循环的代码审查答案

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM