【问题标题】:Calling C function in Assembly Segfaults在程序集段错误中调用 C 函数
【发布时间】:2013-05-07 08:02:29
【问题描述】:

我正在尝试编写一个汇编程序,该程序在 c 中调用一个函数,如果 char 数组中的当前字符符合某些条件,它将用预定义的字符替换字符串中的某些字符。

我的c文件:

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

//display *((char *) $edi)
// These functions will be implemented in assembly:
//

int strrepl(char *str, int c, int (* isinsubset) (int c) ) ;


int isvowel (int c) {

   if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') 
      return 1 ;

   if (c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U') 
      return 1 ;

   return 0 ;
}

int main(){
    char *str1;
    int r;
// I ran my code through a debugger again, and it seems that when displaying 
// the character stored in ecx is listed as "A" (correct) right before the call
// to "add ecx, 1" at which point ecx somehow resets to 0 when it should be "B"

    str1 = strdup("ABC 123 779 Hello World") ;
    r = strrepl(str1, '#', &isdigit) ;
    printf("str1 = \"%s\"\n", str1) ;
    printf("%d chararcters were replaced\n", r) ;
    free(str1) ;
    return 0;
}

还有我的 .asm 文件:

; File: strrepl.asm
; Implements a C function with the prototype:
;
;   int strrepl(char *str, int c, int (* isinsubset) (int c) ) ;
;
; 
; Result: chars in string are replaced with the replacement character and string is returned.

    SECTION .text
    global  strrepl


_strrepl:   nop
strrepl:
    push    ebp         ; set up stack frame
    mov ebp, esp

    push    esi         ; save registers
    push    ebx
    xor eax, eax
    mov ecx, [ebp + 8]      ;load string (char array) into ecx
    jecxz   end         ;jump if [ecx] is zero
    mov esi, [ebp + 12]     ;move the replacement character into esi
    mov edx, [ebp + 16]     ;move function pointer into edx
    xor bl, bl          ;bl will be our counter


firstLoop:
    add bl, 1           ;inc bl would work too
    add ecx, 1
    mov eax, [ecx]  
    cmp eax, 0
    jz  end
    push    eax         ; parameter for (*isinsubset)
    ;BREAK
    call    edx         ; execute (*isinsubset)

    add esp, 4          ; "pop off" the parameter
    mov ebx, eax        ; store return value




end:
    pop ebx         ; restore registers
    pop esi
    mov esp, ebp        ; take down stack frame
    pop ebp
    ret

当通过 gdb 运行它并在 ;BREAK 处放置断点时,在我对 call 命令采取步骤后,它会出现段错误,并出现以下错误:

Program received signal SIGSEGV, Segmentation fault.
0x0081320f in isdigit () from /lib/libc.so.6

isdigit 是我的 c 文件中包含的标准 c 库的一部分,所以我不知道该怎么做。

编辑:我已经编辑了我的 firstLoop 并包含了一个 secondLoop,它应该用“#”替换任何数字,但它似乎替换了整个数组。

firstLoop:

    xor eax, eax

    mov edi, [ecx]
    cmp edi, 0
    jz  end

    mov edi, ecx        ; save array


    movzx   eax, byte [ecx]     ;load single byte into eax  
    mov ebp, edx        ; save function pointer
    push    eax         ; parameter for (*isinsubset)           
    call    edx         ; execute (*isinsubset)

    ;cmp    eax, 0
    ;jne    end

    mov ecx, edi        ; restore array
    cmp eax, 0
    jne secondLoop  
    mov edx, ebp        ; restore function pointer
    add esp, 4          ; "pop off" the parameter
    mov ebx, eax        ; store return value
    add ecx, 1
    jmp firstLoop

secondLoop:
    mov [ecx], esi
    mov edx, ebp
    add esp, 4
    mov ebx, eax
    add ecx, 1
    jmp     firstLoop

使用 gdb,当代码进入 secondloop 时,一切正常。 ecx 显示为“1”,这是从 .c 文件传入的字符串中的第一个数字。 Esi 应显示为“#”。但是,在我执行 mov [ecx] 之后,esi 它似乎分崩离析。 ecx 此时显示为“#”,但是一旦我增加 1 以到达数组中的下一个字符,它就会被列为“/000”并显示。 1 之后的每个字符被“#”替换为“/000”并显示。在我让 secondLoop 尝试用“#”替换字符之前,我只是让 firstLoop 用它自己循环,看看它是否可以在不崩溃的情况下通过整个数组。确实如此,并且在每次增加 ecx 后都显示为正确的字符。我不知道为什么做 mov [ecx], esi 会将 ecx 的其余部分设置为 null。

【问题讨论】:

  • 如果已经组装,它仍然无法工作:clecx 的低字节,所以你会丢失那个字节。你还没有提到edi,它可能是免费的。
  • cl 是 8 位寄存器,eax 是 32 位寄存器。您可以将“al”或“ah”移动到 cl;您可以将 eax 移入 ecx ...但不能将 eax 移入 cl。
  • 我决定使用 ebx,因为无论如何我都将在完成后恢复寄存器,并且在它到达代码中的那个点之前我最终出现了段错误。我已经编辑了我的问题以反映这一点
  • 我是否错过了某个地方的 firstLoop 跳转(如我所料)?
  • firstloop 只是在 strrepl 中的最后一个异或命令之后立即执行。但是,我在修复另一个问题后编辑了我的帖子以包含堆栈跟踪,该问题是 ecx 被 0 替换为调用 add bl, 1。

标签: c arrays assembly


【解决方案1】:

在您的firstLoop: 中,您正在使用以下方法从字符串中加载字符:

mov eax, [ecx]

加载 4 个字节而不是单个字节。因此,您传递给isdigit()int 可能远远超出它的处理范围(它可能使用简单的表查找)。

您可以使用以下 Intel asm 语法加载单个字节:

movzx eax, byte ptr [ecx]

其他一些事情:

  • 它还可能无法正确检测到字符串的结尾,因为空终止符后面可能没有其他三个零字节。
  • 我不确定为什么在处理字符串中的第一个字符之前增加 ecx
  • 您发布的汇编代码似乎并未真正循环遍历字符串

【讨论】:

  • 所以为了不加载字符的所有 4 个字节,我可以简单地移动 eax、[ecx-3] 或类似的东西吗?至于增加 ecx 而不是循环,我只是想让我的代码运行一次,然后再将其配置为实际循环整个字符串。我还发现我没有处理我修复的 A。但是,同样的错误仍然存​​在,我需要弄清楚如何不将所有 4 个字节都传递给 isdigit()。
  • 经过进一步检查,由于 chars 加载了 4 个字节,因此我需要确保 4 字节值的前 24 位在通过之前清零。但是我只知道清除前 16,32 和 64 位的命令。不是 24 岁。
  • 刚刚使用 DevSutdio 2010 在我的 PC 上尝试了代码,并且在调试版本中,由于输入超出范围,isdigit 函数正在断言 - gcc 是否有相同的检查?
  • 我不确定,但我与教授进行了简短的交谈,我的问题似乎如下:当您将字符传递给 isdigit()、isxdigit() 或 isvowel 时,它被传递为一个 4 字节的整数。确保将 4 字节值的前 24 位清零,然后再将其压入堆栈。如果调用 isdigit() 或 isxdigit() 导致 seg 错误,请在 CALL 指令之前使用 gdb 检查堆栈上的整个 32 位。我现在正试图弄清楚在将寄存器的最后 24 位推入堆栈之前,我可以使用什么 xor 或 etc 命令的组合来清零,但没有运气。
  • @rajraj: isdigit() 可能会在所有isxxx() 函数使用的属性位表中进行查找 - 没有范围检查参数。在我的 Linux 机器上确实如此。
【解决方案2】:

我在你的代码中加入了一些 cmets:-

  ; this is OK: setting up the stack frame and saving important register
  ; on Win32, the registers that need saving are: esi, edi and ebx
  ; the rest can be used without needing to preserve them
  push    ebp
  mov ebp, esp
  push    esi
  push    ebx

  xor eax, eax
  mov ecx, [ebp + 8]

  ; you said that this checked [ecx] for zero, but I think you've just written
  ; that wrong, this checks the value of ecx for zero, the [reg] form usually indicates
  ; the value at the address defined by reg
  ; so this is effectively doing a null pointer check (which is good)
  jecxz   end

  mov esi, [ebp + 12]
  mov edx, [ebp + 16]
  xor bl, bl

firstLoop:
  add bl, 1
  ; you increment ecx before loading the first character, this means
  ; that the function ignores the first character of the string
  ; and will therefore produce an incorrect result if the string
  ; starts with a character that needs replacing
  add ecx, 1
  ; characters are 8 bit, not 32 bit (mentioned in comments elsewhere)
  mov eax, [ecx]  
  cmp eax, 0
  jz  end
  push    eax
  ; possibly segfaults due to character out of range
  ; also, as mentioned elsewhere, the function you call here must conform to the 
  ; the standard calling convention of the system (e.g, preserve esi, edi and ebx for
  ; Win32 systems), so eax, ecx and edx can change, so next time you call
  ; [edx] it might be referencing random memory
  ; either save edx on the stack (push before pushing parameters, pop after add esp)
  ; or just load edx with [ebp+16] here instead of at the start
  call    edx

  add esp, 4
  mov ebx, eax

  ; more functionality required here!



end:
  ; restore important values, etc
  pop ebx
  pop esi
  mov esp, ebp
  pop ebp
  ; the result of the function should be in eax, but that's not set up properly yet
  ret

对你的内部循环的评论:-

firstLoop:

    xor eax, eax

    ; you're loading a 32 bit value and checking for zero,
    ; strings are terminated with a null character, an 8 bit value,
    ; not a 32 bit value, so you're reading past the end of the string
    ; so this is unlikely to correctly test the end of string

    mov edi, [ecx]
    cmp edi, 0
    jz  end

    mov edi, ecx        ; save array


    movzx   eax, byte [ecx]     ;load single byte into eax  
    ; you need to keep ebp! its value must be saved (at the end, 
    ; you do a mov esp,ebp)
    mov ebp, edx        ; save function pointer
    push    eax         ; parameter for (*isinsubset)           
    call    edx         ; execute (*isinsubset)

    mov ecx, edi        ; restore array
    cmp eax, 0
    jne secondLoop  
    mov edx, ebp        ; restore function pointer
    add esp, 4          ; "pop off" the parameter
    mov ebx, eax        ; store return value
    add ecx, 1
    jmp firstLoop

secondLoop:
    ; again, your accessing the string using a 32 bit value, not an 8 bit value
    ; so you're replacing the matched character and the three next characters
    ; with the new value
    ; the upper 24 bits are probably zero so the loop will terminate on the
    ; next character
    ; also, the function seems to be returning a count of characters replaced,
    ; but you're not recording the fact that characters have been replaced
    mov [ecx], esi
    mov edx, ebp
    add esp, 4
    mov ebx, eax
    add ecx, 1
    jmp     firstLoop

您似乎确实对内存的工作方式有疑问,您对 8 位和 32 位内存访问感到困惑。

【讨论】:

  • 查看我的编辑,我修复了迭代问题并添加了一个 secondLoop,它实际上用提供的替换字符(在本例中为“#”)替换了字符,但它似乎正在替换第一个数字带有“#”并将数组的其余部分设置为 null。
  • @user2357446:我已经包含了您的新代码并添加了一些 cmets。
  • 评论访问 32 位值与 8 位值,您是说 mov edi, [ecx] 将是 movzx edi, byte [ecx]?如果是这样,我将如何将其应用于 secondLoop? esi 不存储为字符数组。它作为 int 传入,所以当我尝试使用“byte esi”时,它显然会引发错误。有什么建议吗?
  • @user2357446:参数只定义为一个int,因为它原本是一种更优化的传值方式。在汇编程序中,由您决定如何组织内存,因此在 C 中存储为 int 的 char 不需要读取为 int,您可以只读取低 8 位并忽略其余部分。不幸的是,没有反向 movzx(即将 32 位存储到 8 位地址)。您只能将 8 位值从 8 位寄存器写入内存。所以使用 esi 是行不通的。 'mov eax,[ebp+12] ; mov [edi],al' 会起作用。
  • 啊,我明白了,所以要么是你提到的第一个选项,要么只是用 mov al, [ebp+12] 加载。显然,您提到的第一个选项更加精简,但我认为两者都可以。但是,示例实现很棘手,因为您的“mov eax, [ebp+12] 示例将用存储在 [ebp+12] 中的任何内容替换所有 eax,而不是替换单个字符,不是吗?我这样说是因为通常您将使用方括号访问 ecx 中的单个字符
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-10-31
  • 1970-01-01
  • 1970-01-01
  • 2018-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多