我试图弄清楚一个字符串在汇编中是否是回文。实际上我试图将字符串'my_string'的字节反向复制到'tmp_str'。然后我试图使用repe
和cmpsb
来比较这两个字符串。
我面临的问题是,在GDB中设置断点时,程序随机挂起或抛出SIGILL或SIGSEV。这种行为是真正随机的,或者足够随机,以至于我无法将错误的来源归结到一行。我非常困惑,希望能了解导致这些错误的原因。注意,如果我没有设置断点或设置其他断点,如第25、27、28行,它工作得很好。
我已经检查过了,for循环的迭代次数和值都是正确的,初始字符串的格式也是我所期望的。
错误示例:
- 挂起 *〈-如果断点设置为第30行
Program received signal SIGILL, Illegal instruction. _start.for_1.end () at 4.asm:32 32 jmp .for_1.start
〈-当断点设置为第28行时(错误不可重现,但确实发生了)Program received signal SIGSEGV, Segmentation fault. 0x0000000000400feb in ?? ()
〈-当断点设置为第31行时
;; Write an assembly program to determine if a string stored in memory is a palindrome
;; (A palindrome is a string which is the same after being reversed, like "refer")
;; Use at least one repeat instruction
segment .data
my_string db "bob", 0
segment .bss
tmp_str resb 4
segment .text
global _start
_start:
;/* Copy initial string BACKWARDS */
xor rcx, rcx ; have to use manual for loop
.for_1.start:
cmp ecx, 4
je .for_1.end
mov al, byte [my_string+ecx] ; get character from original string from i'th place
mov rbx, tmp_str+2 ; go to end of tmp_str (writing my_string to tmp_str backwards)
sub rbx, rcx ; we can't minus from address expression thingy, so just deduct from register stored above
mov [rbx], byte al ; copy byte in ebx into mem address stored atueax
inc ecx ; increment counter
jmp .for_1.start ; unconditional start to stop (for loops check and do conditional jumps at top)
.for_1.end:
;/* Compare strings */
mov rsi, my_string ; now we want to compare strings (remember, pointer in reg moves in movsb)
mov rdi, tmp_str ; rdi, set to tmp_str address
mov rcx, 2 ; since rcx was modified by rep, we need to reset that though
repe cmpsb ; compare strings whilst each val (byte) is equal
; now, if rcx is NOT 0, the bytes do not match and is not a palindrome!
;/* EOP */
mov eax, 1
xor ebx, ebx
int 0x80
如果有人能提供一些建议,我将不胜感激。
编译命令:yasm -f elf64 -g dwarf2 <file>.asm ; ld <file>.o -o <file>
2条答案
按热度按时间a0x5cqrl1#
**YASM产生错误的DWARF 2两柴信息。**它是旧的且未经维护。请改用NASM。
NASM 2.15.05
nasm -felf64 -g
对我也不起作用:GDB12.1说我试b 30
的时候没有第30行,但是一般还是用NASM,我没有试NASM的DWARF调试信息格式;我在过去的IIRC中遇到过一些问题,比如搞砸了objdump
的拆卸,所以它可能不是很好。不要依赖来自NASM或YASM调试信息.使用
layout asm
并在数字地址上设置断点,layout reg
/layout n
是寄存器+反汇编视图的一个好方法。您可以从那里复制/粘贴地址或disas
来做b *0x40101b
之类的事情。用starti
启动程序,这样GDB在执行第一条用户空间指令之前就停止了;从那里你可以si
单步执行指令。参见https://stackoverflow.com/tags/x86/info底部的asm调试提示。(更新:带有调试信息的NASM错误在 *GDB不从NASM加载源代码行 * 中描述。希望在NASM的未来版本中得到修复。)
汇编语言与机器码的Map是1:1的,所以在调试时查看它的规范反汇编实际上是 * 有帮助的 *,可能会帮助你发现你偶然写错了东西的地方。
当我用YASM1.3.0构建并尝试用
layout reg
单步执行时(因此register + source view),调试信息似乎不太匹配,因为有时我会在同一个源代码行上得到两个步骤(除了预期的repe
;我的意思是随着RIP的增加)。我用
yasm -felf64 -gdwarf2
在裸机(Skylake CPU)上用YASM 1.3.0,ld
2.38,GDB 12.1在Linux 5.18(Arch Linux)上构建。它运行时不会崩溃。调试信息将源代码行Map到内存地址,因此在GDB中设置断点将修改机器代码的一个字节,而不是指令的第一个字节(到
0xcc
INT 3 software breakpoint)。这将导致偶尔的非法指令,或者更常见的是有效但不同的指令(例如,改变绝对地址的字节),如果ModRM字节被修改,则可能具有较短的长度,从而导致后面的字节被解码为操作码。(当用户空间试图运行特权指令时,Linux会发出SIGSEGV,因此各种问题都将发出相同的信号,即使CPU异常是#GP而不是#PF)。此外,用0xCC覆盖ModRM字节将改变寄存器操作数的内容,因此后面的指令可能使用错误的寄存器值。
作为ModRM字节的
0xCC
是带有AH与CL或ESP与ECX的寄存器(非内存)操作数。例如,对于前4个操作码(顺序与大小不同的add
),通过将db 0, 0xcc
等放入.asm来生成此示例:想象一下,如果您的
mov
或sub
指令的操作数被替换为esp,ecx
,会发生什么!(如果inc
发生这种情况,它实际上会改变指令。您的一些mov-immediate指令可能也有modrm字节,因为与NASM不同,YASM不会将mov rcx,2
优化为mov ecx,2
;它使用mov r/m64, sign_extended_imm32
。)当然,如果把
jmp rel8
搞砸,就会跳到错误的位置(但是CC
是一个负的8位整数,所以它会向后跳)。使用GDB来尝试检查情况(例如,反汇编出错的机器代码)可能不起作用,因为GDB会为
x /i
或disas
之类的命令放回原始机器代码字节来尝试反汇编。如果先前的0xCC字节解码不同步,您可能仍然会看到RIP指向特权指令或错误的寄存器值,但您将无法看到先前指令的执行是如何导致这一点的,因为您将看不到CPU实际执行的先前指令。
我可以重现此情况,确认0xCC字节丢失
我可以通过在第29行、第31行和第30行设置断点来重现它。当它分段时,RIP是
0x40103f
,刚刚超过int 0x80
的结尾。通过观察反汇编视图和使用
si
(stepi
)的单步执行指令,执行在一个步骤中直接通过repe cmpsb
,并通过int 0x80
,在它之后的00 00 add [rax],al
上出错。mov rdi,0x402004
加载了0x4020cc
,地址错误,但仍在同一页中。因此,字符串的第一个字节不同,这解释了为什么repe cmpsb
只运行一条指令。mov eax,0x1
加载了带有0xcc
的RAX。在32位int 0x80
ABI(您通常使用don't want to use in 64-bit code,顺便说一句)中,该值为__NR_setregid32
(请检查asm/unistd_32.h)。因此,int 0x80
返回RAX= -1,-EPERM
(asm-generic/errno-base.h)。在这两种情况下,
0xcc
字节是指令的第二个字节,立即数的第一个字节。x86是小端序,因此会打乱加载的值的低字节。vjrehmav2#
问题是你运行循环的迭代次数太多了,所以你试图把
[my_string+3]
中的NUL终止符拷贝到[tmp_str-1]
中,而你不应该写它。把cmp ecx, 4
改为cmp ecx, 3
,你的程序应该能正确运行。