assembly 使用YASM -g dwarf 2在GDB中设置断点会改变程序行为和segfaults或SIGILL

kognpnkq  于 2022-11-13  发布在  其他
关注(0)|答案(2)|浏览(109)

我试图弄清楚一个字符串在汇编中是否是回文。实际上我试图将字符串'my_string'的字节反向复制到'tmp_str'。然后我试图使用repecmpsb来比较这两个字符串。
我面临的问题是,在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>

a0x5cqrl

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来生成此示例:

401000:       00 cc                   add    ah,cl
  401002:       01 cc                   add    esp,ecx
  401004:       02 cc                   add    cl,ah
  401006:       03 cc                   add    ecx,esp

想象一下,如果您的movsub指令的操作数被替换为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 /idisas之类的命令放回原始机器代码字节来尝试反汇编。
如果先前的0xCC字节解码不同步,您可能仍然会看到RIP指向特权指令或错误的寄存器值,但您将无法看到先前指令的执行是如何导致这一点的,因为您将看不到CPU实际执行的先前指令。

我可以重现此情况,确认0xCC字节丢失

我可以通过在第29行、第31行和第30行设置断点来重现它。当它分段时,RIP是0x40103f,刚刚超过int 0x80的结尾。

通过观察反汇编视图和使用sistepi)的单步执行指令,执行在一个步骤中直接通过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是小端序,因此会打乱加载的值的低字节。

vjrehmav

vjrehmav2#

问题是你运行循环的迭代次数太多了,所以你试图把[my_string+3]中的NUL终止符拷贝到[tmp_str-1]中,而你不应该写它。把cmp ecx, 4改为cmp ecx, 3,你的程序应该能正确运行。

相关问题