标题: 利用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
0001008c
00010098
当前系统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
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发生了什么?这段测试代码最初是无意中形成的,不能解释背后的原理, 留给那些充满好奇心的人们吧。