无法在Linux NASM中打印单个字符

3
我想要写一个程序,输入一些内容,找到字符串中奇数位置的字符并打印出来。比如你输入'somewords',程序会打印出'oeod'。
我最终使用了一个循环遍历了整个字符串,然后将计数器除以2,并且如果余数不为0,则打印出该位置上的字符。
但是程序在遇到单个字符时无法输出任何结果。
以下是完整代码:
SECTION .bss
inp: resb 255

SECTION .data
msg db "Enter the string: ", 0h

SECTION .text
global _start

_start:
    mov    eax, msg
    call   stprint 

    mov    edx, 255  ; take user input 
    mov    ecx, inp 
    mov    ebx, 0 
    mov    eax, 3 
    int    80h 

    call   findodd

    mov    ebx, 0
    mov    eax, 1
    int    80h

findodd:
    push   eax
    push   ecx
    push   edx
    push   esi
    push   ebx

    mov    ecx, 0     ; counter
    mov    esi, 2     ; divider

.iterstring:  
    mov    eax, inp           ; move input to eax
    cmp    byte [eax+ecx], 0  ; check for end of the string in position
    je     .finish            ; if equal, finish
    inc    ecx  

    push   eax
    mov    eax, ecx   ; move counter to eax 
    xor    edx, edx   ; divide it by 2
    idiv   esi  
    pop    eax
    cmp    edx, 0     ; check the remainder
    jnz    .printchar ; print character if != 0
    jmp    .iterstring

.printchar:  
    push   eax
    push   ebx
    movzx  ebx, byte [eax+ecx] ; move single byte to ebx

    push   ecx
    mov    ecx, ebx  ; move ebx to print
    mov    edx, 1    ; print the character
    mov    ebx, 1
    mov    eax, 4
    int    80h

    pop    ecx
    pop    eax
    pop    ebx
    jmp    .iterstring  

.finish:  
    pop    eax  
    pop    ecx   
    pop    edx
    pop    esi
    pop    ebx
    ret  

; print string function (taken from tutorial)
; if I try to print single character with it I get SEGFAULT
stprint:
    push    edx
    push    ecx
    push    ebx
    push    eax
    call    stlen

    mov     edx, eax
    pop     eax

    mov     ecx, eax
    mov     ebx, 1
    mov     eax, 4
    int     80h

    pop     ebx
    pop     ecx
    pop     edx
    ret

stlen:
    push    ebx
    mov     ebx, eax

nextch:
    cmp     byte [eax], 0
    jz      finish
    inc     eax
    jmp     nextch

finish:
    sub     eax, ebx
    pop     ebx
    ret

我尝试使用bl、al和cl,但没有成功。我还尝试了一些检查。例如,在.iterstring中打印计数器:

nasm -f elf lr3.asm && ld -m elf_i386 lr3.o -o lr3 && ./lr3
Enter the string: test
1
2
3
4
5

看起来迭代工作正常。

我在类似问题的回答中(如何在Linux x86 NASM中打印字符?)通过对代码进行以下更改获得了最大的运气:

.printchar:
  push   eax
  push   ebx
  push   esi
  mov    eax, inp
  movzx  ebx, byte [eax+ecx]
  mov    esi, ecx ; see below

  push   ecx
  push   ebx
  mov    ecx, esp
  mov    edx, 1    ; print the character
  mov    ebx, 1
  mov    eax, 4
  int    80h

  pop    ecx
  pop    ebx
  pop    eax
  pop    ebx
  mov    ecx, esi  ; without this it just prints 1 character and ends
  pop    esi       ; so ecx is not restored with pop for some reason?
  jmp    .iterstring

但是它会打印除第一个字符以外的所有内容:
nasm -f elf lr3.asm && ld -m elf_i386 lr3.o -o lr3 && ./lr3
Enter the string: somewords
mewords        

我卡住了,不知道我的错误在哪里。 编辑后的最终代码:
findodd:
    push   eax
    push   ecx
    push   edx
    push   esi
    push   ebx
    mov    esi, 0     ; counter
    mov    eax, inp

.iterstring:
    inc    esi
    cmp    byte [eax+esi], 0
    jz     .finish
    test   esi,1
    jz     .iterstring

    movzx   ecx, byte [eax+esi]
    push    ecx
    mov     ecx, esp
    mov     edx, 1
    mov     ebx, 1
    push    eax
    mov     eax, 4
    int     80h
    pop     eax
    pop     ecx
    jmp     .iterstring

.finish:
    pop    eax
    pop    ecx
    pop    edx
    pop    esi
    pop    ebx
    ret

现在它按预期工作:

nasm -f elf lr3.asm && ld -m elf_i386 lr3.o -o lr3 && ./lr3
Enter the string: somewords
oeod

我不得不删除更多的push-pop指令,并且把计数器移到esi中,因为推送和弹出寄存器并不能总是恢复它们在栈中的值,这对我来说很奇怪。
当我尝试将byte [eax+ecx]的地址移入到ecx中时,它可以工作,但是当我将其改为byte [eax+1]时,它会导致段错误,因为在弹出后恢复eax会破坏它。当我将ecx推入打印消息并将其弹回时,它最终导致了段错误,并且gdb显示在弹出ecx后内部有垃圾代码。
不过,使用当前的代码就可以正常工作。


1
你正在使用64位内核吗? - Joshua
1
@Joshua,只有在没有CONFIG_IA32_EMULATION构建它时才会出现问题,例如在WSL(Windows Subsystem for Linux)上。这些nasm + ld命令将创建一个32位可执行文件,该文件将以32位模式运行。因此,没有IA32支持的内核实际上根本无法运行它。 64位内核不是症状的可能解释,因为OP提供了一个很好的[mcve],而不仅仅是“不能工作” :) - Peter Cordes
你的iterstring循环如果在底部使用jz .iterstring而不是跳过相反条件的jmp,将会更有效率。那只是不必要地过于复杂,与惯用的do{}while()循环风格相比。此外,使用div除以2的幂是可怕的:使用AND获取余数,即低位(们)。或者更好的方法是使用test al, 1测试奇偶性,直接检查EAX的低位。 - Peter Cordes
谢谢你,@peter-cordes。我只了解基础知识,但会深入研究sse2,感谢你的指导。 - Aaron
如果你有兴趣学习SIMD,这似乎是一个有趣的用例,但如果你在指针与数据等基础知识上遇到困难,那么考虑每个指令处理16字节可能会很棘手。或者也许你会发现它非常合理,并成为手动矢量化编译器无法胜任的棘手问题的专家。:P 如果你想要一个更简单的挑战,尽可能删除尽可能多的push/pop和mov指令,这样你的代码仍然以1字节为单位循环,但具有更紧凑、简单、高效的代码。另请参见https://stackoverflow.com/tags/x86/info - Peter Cordes
显示剩余2条评论
1个回答

3

这一行是不正确的:

mov    ecx, ebx  ; move ebx to print

write (int 80h / eax=4)需要ecx包含要写入的数据的地址(请参见此表),但您正在传递数据本身。

在您修改后的代码中,您将字符放在堆栈上,然后将其地址传递给ecx,因此这是正确的。 然而,在您到达.printchar时,您已经增加了ecx,这就是为什么您的代码没有打印第一个字符的原因。

顺便说一句,您检查奇数/偶数的方法过于复杂。它可以简化为:

test ecx,1      ; set EFLAGS based on ecx AND 1
jnz .printchar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接