assembly 我一直在解决Nasm代码第32位的分段错误

ie3xauqp  于 11个月前  发布在  其他
关注(0)|答案(2)|浏览(110)

我是绿色与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;
}

字符串
这是我的函数. 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,并确认它工作正常。

ivqmmu1c

ivqmmu1c1#

你熟悉“范围”这个词吗?因为你的问题似乎就在那里。
让我们先从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 'scopes'每个例程都有自己的x副本,所以codeB的x不会覆盖codeA的副本。
然而,在汇编程序中情况有些不同,寄存器没有作用域,如果你将ebx寄存器设置为5,然后调用一个将其设置为6的例程,当它返回时,它仍然是6。
很吓人,对吧?如果每次你调用一个子程序,它都有可能改变你所有寄存器的值,这真的会造成混乱。为了安全起见,似乎你必须在调用一个函数之前保存所有寄存器,然后在返回时把它们都放回去。
虽然这可能会起作用,但它会 * 真的 * 减慢你的代码。如果你调用的函数实际上没有覆盖任何寄存器,这将是一个巨大的和毫无意义的时间浪费。
因此,做这些决定的人决定在调用函数时要遵循一些约定(恰当地称为“调用约定”)。他们会定义一些寄存器,被调用的例程可以随意更改这些寄存器。如果被调用者更改了任何其他寄存器,它必须在返回之前放回原始值。
有许多不同的调用约定。考虑到你正在编写32位汇编代码并从C调用它,C代码的编译器将假设你使用的是cdecl约定。我在上面链接了它的描述,但在这里它又出现了。该链接的关键点是:
寄存器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

sc4hvdpw

sc4hvdpw2#

你的 palindrome_checkis_palindrome 代码是两个不同的函数,你应该清楚地将它们分开。也许用途:

; --------------------------------------
is_palindrome:

字符串

palindrome_check

根据 is_palindrome_c 的结果,您应该只显示一条消息。目前您允许'is'代码在'is not'代码中失败!一个可能的快速修复方法是跳过“is not”代码,使用ret指令退出 palindrome_check 函数。(在这两种情况下,您也可以只使用int 0x80后跟ret
但考虑到调用约定希望您保留EBX,我建议如下:

mov  ecx, palindrome_msg
  mov  edx, lenpamsg
  cmp  eax, 1
  je   .msg
  mov  ecx, not_palindrome_msg
  mov  edx, lenntpamsg
.msg:
  mov  ebx, 1
  mov  eax, 4
  int  0x80
  pop  ebx                      ; Restore EBX
  ret

is_palindrome

  • 这个函数有一个参数,它是一个指向字符串的指针。你把它存储到内存中,但永远不会再使用它!您认为您使用它的两次都被写为mov esi,buf1,但遗憾的是,这并不检索指针。在NASM中,此指令更倾向于加载 * buf 1 * 标签的地址。要获得存储在 * buf 1 * 的内容,您需要像mov esi, [buf1]中那样解引用(就像你一开始存储时所做的那样)。这可能是你的分段错误的原因!
  • 说它计算“字符串长度”的代码不能科普空字符串,不必要地存储到一个名为 * len 1 * 的基于内存的变量中,并且根本没有真正存储实际长度。
  • 线路中
call is_palindrome_asm

is_palindrome_asm:


直接在标签下面的call是不需要的。它只是使 is_palindrome_asm 运行两次。在这个程序中,它似乎是无害的,但为什么你要这样做呢?
以下是我的回文检查器版本。
我认为空字符串和一个字符的字符串是回文。随意改变.

is_palindrome:
        push  ebx            ; (1)
        mov   eax, 1
        mov   ebx, [esp + 8]
        mov   ecx, ebx
.find:  cmp   byte [ecx], 10
        lea   ecx, [ecx + 1]
        jne   .find
        dec   ecx            ; Make ECX point at newline
        jmp   .first
.next:  movzx edx, byte [ebx]
        cmp   dl, [ecx]
        jne   .not
        inc   ebx
.first: dec   ecx
        cmp   ebx, ecx
        jb    .next
        pop   ebx            ; (1a)
        ret                  ; EAX = 1
.not:   dec   eax
        pop   ebx            ; (1b)
        ret                  ; EAX = 0

[edit]

我自己不使用C,但是C不存储零终止的字符串吗?因此,你的 is_palindrome 不应该查找10(C将其作为输入结束信号使用),而是查找0
经过一些分析,我得到了这个更快的版本,它只使用EAX、ECX和EDX。本地标签 .a:.b:.c: 将以16字节对齐。

is_palindrome:
        mov   ecx, [esp + 4]
        mov   edx, ecx

.a:     cmp   byte [edx], 0  ; Looking for the end-of-string marker
        lea   edx, [edx + 1]
        jne   .a
        sub   edx, 2         ; Make EDX point at last character
        cmp   ecx, edx
        jnb   .OK            ; Empty or 1-character string
        nop

.b:     movzx eax, byte [ecx]
        cmp   al, [edx]
        jne   .c
        inc   ecx
        dec   edx
        cmp   ecx, edx
        jb    .b
.OK:    mov   al, 1          ; Faster than `mov eax, 1`
        ret

.c:     xor   eax, eax
        ret


值得注意的是,在 .OK: 的情况下使用mov al, 1mov eax, 1快8%。我用一个合理大小的字符串'abcdefgfedcba'进行测试,并让调用者检查EAX:

...
call    is_palindrome    ; -> EAX (ECX EDX)
cmp     eax, 2
je      never_happens
...

相关问题