我一直致力于解决 Nasm asm 代码第 32 位的分段错误

问题描述 投票:0回答:1

我对 asm 编程很陌生。我一直在尝试解决一些任务作为学习的方式。其中两个任务运行良好,但两个任务因分段错误而失败。我读了很多文章,但我无法理解它。

这是我的C驱动程序

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


extern int is_palindrome(char *s);
extern void palindrome_check();

int is_palindrome(char *s){
int len=strlen(s);

    for(int i=0;i<len/2;i++){
        if(s[i] != s[len -i -1]){
        return 0;
        }
    }
    return 1;

}

int main() {
   int choice;
   int result1;
   
   char str2[1024];

  do {
     printf("\nMenu:\n");
     printf("1) Test if a string is a palindrome (from C to ASM)\n");
     printf("2) Test if a string is a palindrome (from ASM to C)\n");
     printf("Enter your choice: ");
     scanf("%d", &choice);

    switch (choice) {
      case 1:
       printf("Enter a string: ");
       scanf("%s",str2);
      result1=is_palindrome(str2);
      if(result1==1){
         printf("It is a palindrome");
      }
      else {
         printf("It is NOT a palindrome");}
      break;

     case 2:
      palindrome_check();
      break;
     }
    }
    return 0;
}

这是我的functions.asm

BITS 32
extern is_palindrome

GLOBAL is_palindrome
GLOBAL palindrome_check

SECTION .bss
buf1 resb 1024
buf resb 1024

SECTION .data
    prompt db "Please enter a string:",0x0A
    lenprompt equ $ - prompt
    palindrome_msg db "It is a palindrome",0x0A
    lenpamsg equ $ - palindrome_msg
    not_palindrome_msg db "It is NOT a palindrome",0x0A
    lenntpamsg equ $ - not_palindrome_msg

SECTION .text
palindrome_check:
    mov eax,4
    mov ebx,1
    mov ecx,prompt
    mov edx,lenprompt
    int 0x80

    mov eax,3
    mov ebx,0
    mov ecx,buf
    mov edx,1024
    int 0x80

    push ecx

    call is_palindrome_c
    add esp,4

    cmp eax,1
    jne .palindrome_failed
        mov eax,4
        mov ebx,1
        mov ecx,palindrome_msg
        mov edx,lenpamsg
        int 0x80

    .palindrome_failed:
        mov eax,4
        mov ebx,1
        mov ecx,not_palindrome_msg
        mov edx,lenntpamsg
        int 0x80

is_palindrome:
    mov eax,[esp+4]
    mov [buf1],eax


    ;Calculate the length of the string
    xor eax,eax
    xor edi,edi
    mov esi,buf1

len_loop:
    inc eax
    inc esi
    cmp BYTE[esi],10
    je set_len
    jmp len_loop

set_len:
    dec eax
    mov [len1],eax

    ;For the palindrome check function
    mov ecx,eax
    mov ebx,buf1
    call is_palindrome_asm

is_palindrome_asm:
    mov eax,[len1]
    mov edi,eax
    xor ecx,ecx
    mov esi,ebx

palindrome_loop:
    cmp ecx,edi
    jae .done

    mov al,[esi+ecx]
    mov bl,[esi+edi]

    cmp al,bl
    jne .not_palindrome
    inc ecx
    dec edi
    jmp palindrome_loop

    .not_palindrome:
    mov eax,0
    ret

    .done:
    mov eax,1
    ret

为了组装文件,我使用了以下命令:

nasm -g -f elf32 -F dwarf -o functions.o functions.asm
gcc -g -Wall -static -m32 -o backandforth backandforth.c functions.o

我已根据所提供的更正编辑了代码。任何方向都会有所帮助。请注意,我说我是绿色的,我安装了 GEF 调试器,但我无法弄清楚错误是什么。 palindrome_check 不再因分段错误错误而退出,但我传递的每个字符串都不是回文。我已经测试了c函数is_palindome_c并确认它工作正常。

c x86 segmentation-fault nasm palindrome
1个回答
0
投票

您熟悉“范围界定”这个词吗?因为你的问题很可能就在那里。

让我们先从一些 C 代码开始:

void codeA(void)
{
   int x = 5;
   codeB();
   printf("%d\n", x);
}

void codeB(void)
{
   int x = 6;
   printf("%d\n", x);
}

如果您要调用 codeA,您希望输出是什么样的? codeB 首先打印,所以它将打印 6。但是由于它将

x
设置为 6,这是否意味着 codeA 也会打印 6?

不,当然不是。 C “范围”每个例程都有自己的 x 副本,因此 codeB 的 x 不会覆盖 codeA 的副本。

但是,汇编程序中的情况有些不同。寄存器没有范围。如果将 ebx 寄存器设置为 5,然后调用将其设置为 6 的例程,则返回时它仍然是 6。

可怕吧?如果每次调用子例程时,它都有可能更改所有寄存器的值,那确实会造成混乱。为了安全起见,您似乎必须在调用函数之前保存所有寄存器,然后在返回时将它们全部放回去。

虽然这可行,但它会真的减慢你的代码速度。如果您调用的函数实际上没有覆盖您的任何寄存器,那将是巨大且毫无意义的时间浪费。

因此,做出这些决定的人决定在调用函数时遵循一些约定(恰当地称为“调用约定”)。他们会定义一些寄存器,被调用的例程可以随意更改。如果被调用者更改任何其他寄存器,则必须在返回之前放回原始值。

有许多不同的调用约定。鉴于您正在编写 32 位汇编代码并从 C 调用它,C 代码的编译器将假定您正在使用

cdecl
约定。我链接到了上面的描述,但是here又是这样。该链接的关键点是:

寄存器 EAX、ECX 和 EDX 由调用者保存,其余寄存器由被调用者保存。

所以当

main
调用
palindrome_check
时,那么main是调用者,palindrome_check是被调用者。那么当 palindrome_check 这样做时会发生什么:

mov eax,4
mov ebx,1
mov ecx,prompt
mov edx,lenprompt
int 0x80

调用者已保存了 eax、ecx 和 edx,因此您可以随意更改它们。但是 ebx 呢?您(被调用者)正在更改它,这意味着当您返回时,调用者将不会获得其期望的 ebx 值。你违反了规则,结果现在发生了不好的事情。

那么,你能做什么呢?毕竟,您需要使用 ebx 来打印字符串。这就是 int 0x80 的工作原理。因此,您需要做的是保存旧值,通常在被调用例程的顶部,使用如下内容:

push ebx

然后在例程的底部,就在返回之前,您可以使用以下命令恢复原始值:

pop ebx

查看您的代码,您还可以修改其他“被调用者保存”寄存器(例如 edi)。他们也需要被拯救。

这里面有一个技巧,所以我可以让你不必回来问下一个明显的问题。

如果您推送多个寄存器:

push ebx
push edi

然后当你

pop
他们时,你需要以相反的顺序进行:

pop edi
pop ebx

修改“被调用者保存”寄存器(可能)是导致代码崩溃的原因。

您还有另一个问题(我上面提到过)。再看一下codeA,它完成printf后会发生什么?它到达例行程序的末尾并返回,对吧?

但是汇编并不能做到这一点。不存在“例程结束”,并且缩进代码根本没有任何效果。因此,在打印完第一条消息后,它就会继续执行下一条指令。

可能您需要的是在例行程序结束时使用

jmp
,其中包含
pop
说明,然后是
ret

© www.soinside.com 2019 - 2024. All rights reserved.