标题: 利用THUMB模式向ARM模式的切换干挠GDB的单步调试 创建: 2019-01-25 17:52 更新: 2019-01-30 14:23 链接: https://scz.617.cn/unix/201901251752.txt $ vi adjustpc.s -------------------------------------------------------------------------- .syntax unified .arch armv6t2 .section .text .globl _start _start: .thumb mov r2,#12 adr r1,msg_0 mov r0,#1 mov r7,#4 svc #0x2e add ip,pc,#0 bx ip .align 2 .arm mov r2,#10 adr r1,msg_1 mov r0,#1 mov r7,#4 svc #0x2e eor r0,r0,r0 mov r7,#1 svc #1 msg_0: .ascii "thumb mode.\n" msg_1: .ascii "arm mode.\n" -------------------------------------------------------------------------- $ uname -m armv7l $ as -o adjustpc.o adjustpc.s $ ld -N -thumb-entry=_start -o adjustpc adjustpc.o ld必须使用"-thumb-entry=_start",因为一上来就是THUMB模式代码。如果不使用 "-thumb-entry=_start",最终的ELF无法直接执行,但可以在GDB中执行。 $ file -b adjustpc ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped adjustpc.s是精心设计过的非正常代码,从THUMB模式切换至ARM模式时错误地调整PC 寄存器,但其实际执行效果符合原始意图,可以看到两次输出: $ ./adjustpc thumb mode. arm mode. $ objdump -d adjustpc adjustpc: file format elf32-littlearm Disassembly of section .text: 00010054 <_start>: 10054: f04f 020c mov.w r2, #12 10058: a10c add r1, pc, #48 ; (adr r1, 1008c ) 1005a: f04f 0001 mov.w r0, #1 1005e: f04f 0704 mov.w r7, #4 10062: df2e svc 46 ; 0x2e 10064: f20f 0c00 addw ip, pc, #0 10068: 4760 bx ip 1006a: bf00 nop 1006c: e3a0200a mov r2, #10 10070: e28f1020 add r1, pc, #32 10074: e3a00001 mov r0, #1 10078: e3a07004 mov r7, #4 1007c: ef00002e svc 0x0000002e 10080: e0200000 eor r0, r0, r0 10084: e3a07001 mov r7, #1 10088: ef000001 svc 0x00000001 0001008c : 1008c: 6d756874 .word 0x6d756874 10090: 6f6d2062 .word 0x6f6d2062 10094: 0a2e6564 .word 0x0a2e6564 00010098 : 10098: 206d7261 .word 0x206d7261 1009c: 65646f6d .word 0x65646f6d 100a0: 0a2e .short 0x0a2e ... 当前系统ASLR是关闭的,所以"strace -i"的输出有参考价值: $ strace -i ./adjustpc ... [00010064] write(1, "thumb mode.\n", 12thumb mode.) = 12 [00010080] write(1, "arm mode.\n", 10arm mode.) = 10 [0001008c] exit(0) = ? ... 对于ARM,"strace -i"输出的地址是svc指令之后的地址。 $ gdb -q -nx ./adjustpc (gdb) starti Starting program: /tmp/adjustpc Program stopped. 0x00010054 in _start () (gdb) display/5i $pc 1: x/5i $pc => 0x10054 <_start>: mov.w r2, #12 0x10058 <_start+4>: add r1, pc, #48 ; (adr r1, 0x1008c ) 0x1005a <_start+6>: mov.w r0, #1 0x1005e <_start+10>: mov.w r7, #4 0x10062 <_start+14>: svc 46 ; 0x2e (gdb) tb *0x10064 Temporary breakpoint 1 at 0x10064 (gdb) c Continuing. thumb mode. Temporary breakpoint 1, 0x00010064 in _start () 1: x/5i $pc => 0x10064 <_start+16>: addw r12, pc, #0 0x10068 <_start+20>: bx r12 0x1006a <_start+22>: nop 0x1006c <_start+24>: mov r2, #10 0x10070 <_start+28>: add r1, pc, #32 (gdb) i r pc pc 0x10064 0x10064 <_start+16> (gdb) si 0x00010068 in _start () 1: x/5i $pc => 0x10068 <_start+20>: bx r12 0x1006a <_start+22>: nop 0x1006c <_start+24>: mov r2, #10 0x10070 <_start+28>: add r1, pc, #32 0x10074 <_start+32>: mov r0, #1 (gdb) i r r12 r12 0x10068 65640 如果对THUMB模式"add ip,pc,#0"理解正确,就会知道"bx ip"将跳向自身,同时由 THUMB模式切换至ARM模式。 (gdb) set arm force-mode arm (gdb) x/5i 0x10068 => 0x10068 <_start+20>: svclt 0x00004760 0x1006c <_start+24>: mov r2, #10 0x10070 <_start+28>: add r1, pc, #32 0x10074 <_start+32>: mov r0, #1 0x10078 <_start+36>: mov r7, #4 AMR模式0x10068处的指令变成"svclt 0x4760",这个"lt"不用理会,就当这条指令是 "svc 0x4760"好了。检查r0、r1、r2、r7寄存器,还是0x10062处的那些值: (gdb) set arm force-mode auto (gdb) i r r0 r1 r2 r7 r0 0xc 12 r1 0x1008c 65676 r2 0xc 12 r7 0x4 4 在THUMB模式的0x10068处单步一下: (gdb) si thumb mode. ... thumb mode. GDB没有报错,而是无限循环输出"thumb mode."。 Ctrl-C打断: Program received signal SIGINT, Interrupt. 0x00010064 in _start () 1: x/5i $pc => 0x10064 <_start+16>: addw r12, pc, #0 0x10068 <_start+20>: bx r12 0x1006a <_start+22>: nop 0x1006c <_start+24>: mov r2, #10 0x10070 <_start+28>: add r1, pc, #32 PC寄存器居然指向0x10064,此时CPSR寄存器的T位仍然置位中: (gdb) p/x $cpsr&0x20 $1 = 0x20 不知单步中断处理逻辑面对这种故意搞怪的代码到底发生了什么?总之GDB无法单步 经过0x10068。但是,如果在0x1006c设置断点,c可以正常继续,不再无限循环输出 "thumb mode."。 (gdb) tb *0x1006c Temporary breakpoint 2 at 0x1006c (gdb) c Continuing. Temporary breakpoint 2, 0x0001006c in _start () 1: x/5i $pc => 0x1006c <_start+24>: mov r2, #10 0x10070 <_start+28>: add r1, pc, #32 0x10074 <_start+32>: mov r0, #1 0x10078 <_start+36>: mov r7, #4 0x1007c <_start+40>: svc 0x0000002e (gdb) c Continuing. arm mode. [Inferior 1 (process 17238) exited normally] 为什么直接执行adjustpc时其输出符合预期?"svclt 0x4760"没带来麻烦?GDB单步 时adjustpc发生了什么?这段测试代码最初是无意中形成的,不能解释背后的原理, 留给那些充满好奇心的人们吧。