【发布时间】: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系统调用会更有效。您可以使用 SSE2psrlw xmm0, 8/packuswb有效地执行此操作以打包奇数字节2 个 16 字节向量合而为一。 -
谢谢你,@peter-cordes。我只知道基础知识,但会深入研究 sse2,谢谢指出。