【问题标题】:Can't get single character to print in Linux NASM无法在 Linux NASM 中打印单个字符
【发布时间】:2019-05-20 01:07:20
【问题描述】:

我尝试制作一个程序,它接受一些输入,在字符串中找到奇数位置并打印相应的字符,因此您输入“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

所以看起来迭代工作正常。

对类似问题 (How to print a character in 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 内部有垃圾代码。
使用当前代码,它可以正常工作。

【问题讨论】:

  • 您使用的是 64 位内核吗?
  • @Joshua 只有在没有CONFIG_IA32_EMULATION 的情况下构建它才会成为问题,例如在 WSL(Linux 的 Windows 子系统)上。这些 nasm + ld 命令将创建一个 32 位可执行文件,该可执行文件将以 32 位模式运行。所以没有 IA32 支持的内核实际上根本不会运行它。 64 位内核不能解释这些症状,因为 OP 提供了很好的 minimal reproducible example,而不仅仅是“不起作用”:)
  • 如果您在底部使用jz .iterstring,而不是跳过具有相反条件的jmp,您的iterstring 循环会更有效。与惯用的 do{}while() 循环风格相比,这只是不必要的过于复杂。同样使用 div 除以 2 的幂是可怕的:使用 AND 来获得余数,即低位。或者更好的是,使用test al, 1 测试奇数/偶数以直接检查 EAX 的低位。
  • 或者更简单,将循环展开 2,这样您就无需分支以在两种行为之间交替,而是按顺序执行它们。首先只检查 0 终止符(偶数字节),然后检查奇数字节并打印。然后循环重复。您不需要在任何地方复制数据,只需让 ECX 指向数组即可。 (虽然将奇数字符复制到 tmp 数组并为整个输出进行一次 write 系统调用会更有效。您可以使用 SSE2 psrlw xmm0, 8 / packuswb 有效地执行此操作以打包奇数字节2 个 16 字节向量合而为一。
  • 谢谢你,@peter-cordes。我只知道基础知识,但会深入研究 sse2,谢谢指出。

标签: assembly x86 nasm


【解决方案1】:

此行不正确:

mov    ecx, ebx  ; move ebx to print

write (int 80h / eax=4) 期望 ecx 包含要写入的数据的地址(请参阅this table)。但你是在传递数据本身。

在您修改后的代码中,您将字符放入堆栈,然后将其地址传递给ecx,所以这是正确的。 但是,当您到达.printchar 时,您已经增加了ecx。这就是您的代码不打印第一个字符的原因。

附带说明,您对偶数/奇数的检查过于复杂。它可以简化为:

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

【讨论】:

    猜你喜欢
    • 2014-02-21
    • 2021-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-17
    • 1970-01-01
    • 2014-12-31
    • 2014-11-30
    相关资源
    最近更新 更多