JZ2440裸板开发练习#2 - 点亮LED程序反汇编分析

平台:JZ2440开发板——CPU:S3C2440(ARM920)


前言

上一个练习中,使用C语言编写了一个点亮LED的程序,本次练习将对该程序的反汇编文件做一次分析,挖掘其中的知识点。

预备知识

ARM寄存器

ATPCS(arm-thumb 程序调用标准)

目的

为了让C语言程序和汇编程序之间能够互相调用而指定的调用规则,只要所用语言遵守该标准,它们就能相互调用且正常运行。

内容

1.寄存器使用规则

一个ARM模式下能够使用的寄存器共有16个,为R0-R15。

R0-R3传递参数用,子函数使用完后可以不用恢复原值。

R4-R11保存局部变量,如果使用到需要先保存原值,并在返回值恢复原值。

R12用作子程序的scratch寄存器,别名ip,常用来保存其他寄存器的值做入栈保存等操作,如下文中mov ip,sp的操作

R13作为栈指针,SP,这个是所有架构包括ARM,IA-32都存在的寄存器,需要准确保存和恢复,否则会影响正常运行,也称保持栈平衡。

R14保存返回地址,LR,bl指令会将返回值保存到该地址,常见的用法是:lr保存地址入栈保存,然后将函数退出时lr赋值到pc寄存器,达到运行到返回地址的目的。

R15,PC,程序计数器,该寄存器指向的地址为程序即将执行的地址,根据ARM的流水线机制,三级流水线情况下,PC将指向当前正在运行指令地址+8的地址。该寄存器中还有其他位用来保存程序状态,如溢出,进位,0等。PC不能被用在除上述描述外的功能上,与IA-32架构有差异的是,该寄存器可以被直接赋值,而IA-32只能通过jmp等指令间接修改。

2.数据栈使用规则

规定好栈的生长方向,这个一般都为降序栈。以降序栈为例,入栈时,sp需要db(decrease before)还是da(decrease after),出栈时sp需要ia(increase after)还是ib(increase before),这些都是需要统一化才能让栈正常使用。

3.参数传递规则

函数调用中,传递参数使用R0-R3,参数个数超出4个时,通过栈传递剩下的参数,同时返回值也是使用R0-R3返回。

4.现场保护和恢复规则

函数调用时会破坏一些寄存器或者其他存储的值,需要进行现场保护才能在函数返回后上一级函数正常运行。相应的函数退出时也要根据约定的规则恢复现场。目前采用的是子函数中需要用到的寄存器按照寄存器顺序(高序寄存器先入栈,如R15)入栈,出栈时则依次退栈即可。

分析


led.elf:     file format elf32-littlearm

Disassembly of section .text:

00008074 <_start>:
    8074:	e3a00000 	mov	r0, #0	; 0x0
    8078:	e5901000 	ldr	r1, [r0]
    807c:	e5800000 	str	r0, [r0]
    8080:	e5902000 	ldr	r2, [r0]
    8084:	e1520000 	cmp	r2, r0
    8088:	e59fd00c 	ldr	sp, [pc, #12]	; 809c <.text+0x28>
    808c:	03a0da01 	moveq	sp, #4096	; 0x1000
    8090:	05801000 	streq	r1, [r0]
    8094:	eb000001 	bl	80a0 <main>    //bl跳转main,返回地址lr=8098

00008098 <halt>:
    8098:	eafffffe 	b	8098 <halt>
    809c:	40001000 	andmi	r1, r0, r0

000080a0 <main>:
    80a0:	e1a0c00d 	mov	ip, sp
    80a4:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc} 
    80a8:	e24cb004 	sub	fp, ip, #4	; 0x4
    80ac:	e24dd008 	sub	sp, sp, #8	; 0x8
    80b0:	e3a03456 	mov	r3, #1442840576	; 0x56000000
    80b4:	e2833050 	add	r3, r3, #80	; 0x50
    80b8:	e50b3010 	str	r3, [fp, #-16]
    80bc:	e3a03456 	mov	r3, #1442840576	; 0x56000000
    80c0:	e2833054 	add	r3, r3, #84	; 0x54
    80c4:	e50b3014 	str	r3, [fp, #-20]
    80c8:	e51b2010 	ldr	r2, [fp, #-16]
    80cc:	e51b3010 	ldr	r3, [fp, #-16]
    80d0:	e5933000 	ldr	r3, [r3]
    80d4:	e3c33c3f 	bic	r3, r3, #16128	; 0x3f00
    80d8:	e5823000 	str	r3, [r2]
    80dc:	e51b2010 	ldr	r2, [fp, #-16]
    80e0:	e51b3010 	ldr	r3, [fp, #-16]
    80e4:	e5933000 	ldr	r3, [r3]
    80e8:	e1e03003 	mvn	r3, r3
    80ec:	e2033c15 	and	r3, r3, #5376	; 0x1500
    80f0:	e1e03003 	mvn	r3, r3
    80f4:	e5823000 	str	r3, [r2]
    80f8:	e51b2014 	ldr	r2, [fp, #-20]
    80fc:	e51b3014 	ldr	r3, [fp, #-20]
    8100:	e5933000 	ldr	r3, [r3]
    8104:	e3c33070 	bic	r3, r3, #112	; 0x70
    8108:	e5823000 	str	r3, [r2]
    810c:	e3a03000 	mov	r3, #0	; 0x0
    8110:	e1a00003 	mov	r0, r3
    8114:	e24bd00c 	sub	sp, fp, #12	; 0xc
    8118:	e89da800 	ldmia	sp, {fp, sp, pc}
Disassembly of section .comment:

00000000 <.comment>:
   0:	43434700 	cmpmi	r3, #0	; 0x0
   4:	4728203a 	undefined
   8:	2029554e 	eorcs	r5, r9, lr, asr #10
   c:	2e342e33 	mrccs	14, 1, r2, cr4, cr3, {1}
  10:	Address 0x10 is out of bounds.

反汇编代码如上,假设本次以nandflash方式启动,则sp被设置为4096。启动文件bl main跳转到C程序中的main函数,此时lr寄存器保存了返回的地址8098。

进入到main后,首先保存需要保存现场,将需要用到的寄存器入栈保存

 80a0:	e1a0c00d 	mov	ip, sp                      //fp=? ip=4096 sp=4096 lr=8098 pc=80A8
 80a4:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
stack address   value comment  
4092   0x80A8 pc  
4088   0x8098 lr  
4084   4096 ip=old sp  
4080   unknowvalue fp <-sp

然后将fp调整到保存现场的首地址,根据后面fp的使用情况,fp寄存器我认为是和IA-32架构的EBP类似作用的寄存器,作为栈帧的基准,在此基础上添加偏移值来寻址栈帧中的任意变量。而后sp - 8是汇编中常见的为局部变量开辟内存,这点在IA-32架构也是一样的,对应到C程序是我们指向两个寄存器的指针。

80a8:	e24cb004 	sub	fp, ip, #4	; 0x4           //fp = ip - 4 =4096-4 =4092
80ac:	e24dd008 	sub	sp, sp, #8	; 0x8           //sp =sp -8 = 4072  //两个int 局部变量
stack address   value comment  
4092   0x80A8 pc  
4088   0x8098 lr  
4084   4096 ip=old sp  
4080   unknowvalue fp  
4076        
4072       <-sp

开辟完内存后,以fp为基准寻址到刚开辟内存的地址处,赋值寄存器值

    80b0:	e3a03456 	mov	r3, #1442840576	; 0x56000000//r3=0x56000000
    80b4:	e2833050 	add	r3, r3, #80	; 0x50          //r3=0x56000050
    80b8:	e50b3010 	str	r3, [fp, #-16]              //fp - 16 = 4092 - 16 = 4076
    80bc:	e3a03456 	mov	r3, #1442840576	; 0x56000000
    80c0:	e2833054 	add	r3, r3, #84	; 0x54
    80c4:	e50b3014 	str	r3, [fp, #-20]
stack address   value comment  
4092   0x80A8 pc  
4088   0x8098 lr  
4084   4096 ip=old sp  
4080   unknowvalue fp  
4076   0x56000050    
4072   0x56000054   <-sp

接着是一段对局部变量处理的程序,对应着C程序可以很清晰地看懂,没有什么知识点,因此不做说明。

    
    //*pGPFCON &= ~((3<<8) | (3<<10) | (3<<12));
    80c8:	e51b2010 	ldr	r2, [fp, #-16]      
    80cc:	e51b3010 	ldr	r3, [fp, #-16]      
    80d0:	e5933000 	ldr	r3, [r3]            
    80d4:	e3c33c3f 	bic	r3, r3, #16128	; 0x3f00       
    80d8:	e5823000 	str	r3, [r2]            

    //*pGPFCON |= ~((1<<8) | (1<<10) | (1<<12));
    80dc:	e51b2010 	ldr	r2, [fp, #-16]      
    80e0:	e51b3010 	ldr	r3, [fp, #-16]      
    80e4:	e5933000 	ldr	r3, [r3]            
    80e8:	e1e03003 	mvn	r3, r3              
    80ec:	e2033c15 	and	r3, r3, #5376	; 0x1500
    80f0:	e1e03003 	mvn	r3, r3
    80f4:	e5823000 	str	r3, [r2]            

    //*pGPFDAT &=~((1<<4) | (1<<5) | (1<<6));
    80f8:	e51b2014 	ldr	r2, [fp, #-20]
    80fc:	e51b3014 	ldr	r3, [fp, #-20]
    8100:	e5933000 	ldr	r3, [r3]
    8104:	e3c33070 	bic	r3, r3, #112	; 0x70
    8108:	e5823000 	str	r3, [r2]

最后是返回值的赋值,根据ATPCS可知,此处使用R0寄存器作为返回值存储的寄存器,返回值为0,此处反汇编代码经过R3作为中介没有什么特殊的意义,如果编译器智能点应该会把此处优化掉。

    810c:	e3a03000 	mov	r3, #0	; 0x0
    8110:	e1a00003 	mov	r0, r3

至此,main函数执行结束,准备返回到lr指向的地址处,此时需要把栈保存的值恢复到寄存器中,同时恢复sp。首先将分配给局部变量的栈空间回收,此处回收仅仅是将sp指向分配局部变量地址的上端,而非堆内存那样做相应的回收操作,因为下次需要使用到该内存时,该地址必然会被覆盖,因此不需要做其他处理。

    8114:	e24bd00c 	sub	sp, fp, #12	; 0xc   //局部变量退栈
stack address   value comment  
4092   0x80A8 pc  
4088   0x8098 lr  
4084   4096 ip=old sp  
4080   unknowvalue fp <-sp
4076   0x56000050    
4072   0x56000054    

最后ldmia对应开头的stmdb,将栈中存放的数据恢复到{}中的寄存器中。对比上个栈表可以看到,栈中保存的fp会恢复到寄存器fp,ip保存着旧sp的值,由此sp恢复到main调用前的sp,最后存放的lr保存的是返回地址,刚好恢复到pc寄存器,因此下一条执行的指令pc指向的是返回值地址,main函数正常退出。(ldmia涉及对sp的赋值,但是sp仍旧会ia继续正常运行,这是因为指令中ia操作是对sp的tmp做操作的,具体可以查看 https://blog.csdn.net/G_METHOD/article/details/104126283 中对于ldm的描述)

    8118:	e89da800 	ldmia	sp, {fp, sp, pc}
stack address   value comment  
4092   0x80A8 pc <-sp
4088   0x8098 lr   ======》pc  
4084   4096 ip=old sp===》sp  
4080   unknowvalue fp=======》fp  
4076   0x56000050    
4072   0x56000054    
发布了19 篇原创文章 · 获赞 7 · 访问量 6928

猜你喜欢

转载自blog.csdn.net/G_METHOD/article/details/104156501