C程序的内部机制

C程序的内部机制

1. 概述

通过分析C语言点亮LED的程序来了解C程序的内部机制


C程序:在main函数中,主要做了如下的事情:

  • 1. 定义两个局部变量;
  • 2.给这两个局部变量设置值;
  • 3.返回0值
int main(void *argc, void **argv)
{
    unsigned int *pGPFCON = (unsigned int*)0x56000050;
    unsigned int *pGPFDAT = (unsigned int*)0x56000054;

    //将GPF4功能配置为输出(GPF4[9:8] = 0x01)
    *pGPFCON = 0x100;
    //设置GPFDAT的bit设置为0,点亮LED
    *pGPFDAT = 0x00;    

    return 0;
}

汇编代码:在start.S文件中的汇编程序,主要做了如下的事情:

  • 1. 设置栈在内存中的地址;
  • 2. 跳转到main函数,并将从main返回的值保存到lr中;
  • 3:然后死循环halt
.text
.global _start

_start:
    /*设置栈的地址*/
    ldr sp, =4096                   //nand Flash方式启动时,SRAM的最高地址设置为栈首地址
    //ldr sp, = 0x40000000 + 4096     //nor Flash方式启动时,SRAM的基地址是0x40000000
    /*跳转到main执行main程序*/
    bl main

halt:
    b halt 

反汇编C程序:

  • 对于以Nand Flash方式启动,S3C2440A的硬件会将Nand Flash中的前4K拷贝到4K SRAM中
  • 以下的反汇编代码会完完全全的赋值到4K SRAM中。
led.elf:     file format elf32-littlearm

Disassembly of section .text:

00000000 <_start>:
   //[0]: CPU读取0地址,第一条指令就是设置栈的地址,栈指向的是4K SRAM的最高地址
   0:	e3a0da01 	mov	sp, #4096	; 0x1000
   //[1]: 第二条指令,就是跳转到C程序的main函数,main函数的地址在0x0000000c
   4:	eb000000 	bl	c <main>

00000008 <halt>:
   8:	eafffffe 	b	8 <halt>

0000000c <main>:
   //[2]: 跳转到main函数中,第一条指令就是将栈地址保存到CPU的ip寄存器中
   c:	e1a0c00d 	mov	ip, sp
   //[3]: main函数中,第二条指令就是按内存高地址到内存低地址异常将pc、lr、ip、fp寄存器中的值保存到栈内存中。
  10:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
  //[4]: 执行 fp = ip - 4
  14:	e24cb004 	sub	fp, ip, #4	; 0x4
  //[5]: 执行 sp = sp - 16
  18:	e24dd010 	sub	sp, sp, #16	; 0x10
  1c:	e50b0010 	str	r0, [fp, #-16]
  20:	e50b1014 	str	r1, [fp, #-20]
  24:	e3a03456 	mov	r3, #1442840576	; 0x56000000
  28:	e2833050 	add	r3, r3, #80	; 0x50
  2c:	e50b3018 	str	r3, [fp, #-24]
  30:	e3a03456 	mov	r3, #1442840576	; 0x56000000
  34:	e2833054 	add	r3, r3, #84	; 0x54
  38:	e50b301c 	str	r3, [fp, #-28]
  3c:	e51b2018 	ldr	r2, [fp, #-24]
  40:	e3a03c01 	mov	r3, #256	; 0x100
  44:	e5823000 	str	r3, [r2]
  48:	e51b201c 	ldr	r2, [fp, #-28]
  4c:	e3a03000 	mov	r3, #0	; 0x0
  50:	e5823000 	str	r3, [r2]
  54:	e3a03000 	mov	r3, #0	; 0x0
  58:	e1a00003 	mov	r0, r3
  5c:	e24bd00c 	sub	sp, fp, #12	; 0xc
  60:	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.

其中.comment表示的注释,在bin文件中是没有这些注释内容的。

2. 几个问题

2.1 为什么要设置栈?

2.2 怎么使用栈?

  • 1. 使用栈来保存局部变量
  • 2. 使用栈保存lr等寄存器
    • lr用来保存main函数的返回值。

2.3 调用者如何给被调用者传参?

根据ATPCS即ARM-THUMB procedure call standard(ARM-Thumb过程调用标准),调用者通过r0、r1、r2、r3将参数传递给被调用者。

2.4 被调用者如何给调用者传返回值?

根据ATPCS即ARM-THUMB procedure call standard(ARM-Thumb过程调用标准),被调用者也是通过r0、r1、r2、r3寄存器将结果返回给调用者。

2.5 如何恢复保存在栈中的寄存器?

在程序中CPU的r4~r11寄存器可能会被使用。因此需要在函数的入口需要保存这些寄存器。在函数的出口,需要恢复它们。如果程序比较简单,程序用到哪个寄存器就保存哪个寄存器。

3. 汇编程序执行分析

以下是反汇编程序指令存放在4K SRAM中的图示:
C程序的内部机制

3.1 程序指令执行的过程

  • 1. 板子上电,CPU从0地址读取第一条指令0xe3a0da01,即mov sp, #4096,它将栈地址设置为SRAM的最高地址4096, sp = 4096
  • 2. 然后,CPU读取第二条指令0xeb000000,跳转到main函数(地址0xc)执行,同时将下一条指令的地址0x8保存到lr寄存器中。
  • 3. CPU读取保存在0xc内存地址的指令0xe1a0c00d执行,即mov ip, sp,它将栈地址保存到ip寄存器中。
  • 4. CPU读取保存在0x10内存地址中的指令0xe92dd800执行,即stmdb sp!, {fp, ip, lr, pc},它会将pc、lr、ip、fp寄存器中的值依次从栈的高地址到地址保存到栈中。都保存到栈中后,此时sp指向内存地址4076
    • pc中保存的是当指令地址 + 8的地址,当前指令0xe92dd800的地址是0x10,所以pc = 0x18
    • lr = 0x8
    • ip = 4096
    • fp中的值未知
      此时栈中,保存的值是
      C程序的内部机制
  • 5. CPU读取0xe24cb004指令,即sub fp, ip, #4 ----> fp = ip - 4 = 4096 - 4 = 4092
  • 6. CPU读取0xe24dd010指令,即sub sp, sp, #16 ----> sp = sp -16 = 4080 - 16 = 4064
  • 7. CPU读取0xe50b0010指令,即str r0, [fp, #-16],----> [fp - 16] = [4092 - 16] = [4076] = r0,即将在r0寄存器中的数据保存到地址4080中,r0保存的是main函数的第一个形参argc局部变量的值,即r0 = argc,此时栈:
    C程序的内部机制
  • 8. CPU读取0xe50b1014指令,即str r1, [fp, #-20] ----> [ fp, #-20] = [4092 - 20] = [4072] = r1,将r1寄存器中的值保存到内存地址4072中,r1中保存的是main中第二个形参argv局部变量的值,此时的栈:
    C程序的内部机制
  • 9. CPU读取0xe3a03456指令,即mov r3, #1442840576 ; 0x56000000 —> r3 = 0x56000000
  • 10. CPU读取0xe2833050指令,即add r3, r3, #80 ; 0x50 —> r3 = r3 + 0x50 = 0x56000050
  • 11. CPU读取0xe50b3018指令,即str r3, [fp, #-24] —> [fp - 24] = [4092 - 24] = [4068] = r3,即将寄存器r3中的值保存到内存地址4068中,r3中的值是0x56000050,此时栈:
    C程序的内部机制
  • 12. CPU读取0xe3a03456指令,即mov r3, #1442840576 ; 0x56000000 —> r3 = 0x56000000
  • 13. CPU读取0xe2833054指令,即add r3, r3, #84 ; 0x54 —> r3 = r3 + 0x54 = 0x56000054
  • 14. CPU读取0xe50b301c指令,即str r3, [fp, #-28] —> [fp - 28]= [4092 - 28] = [4064] = r3,即将寄存器r3中的值保存到内存地址4064中,r3中的值是0x56000054,此时栈:
    C程序的内部机制
  • 15. CPU读取0xe51b2018指令,即ldr r2, [fp, #-24] ----> r2 = [fp -24] = [4092 - 24] = [4068],即将存储在内存地址4068的值保存到r2寄存器中,即r2 = 0x56000050
  • 16. CPU读取0xe3a03c01指令,即mov r3, #256 ; 0x100 —> r3 = 0x100,将立即数0x100保存到r3寄存器中
  • 17. CPU读取0xe5823000指令,即str r3, [r2],将保存在r3中的值0x100,保存到内存地址0x56000050中
  • 18. CPU读取0xe51b201c指令,即ldr r2, [fp, #-28] ----> r2 = [fp - 28] = [4092 - 28] = [4064],即将存储在内存地址4064的值保存到r2寄存器中,即r2 = 0x56000054
  • 19. CPU读取0xe3a03000指令,即mov r3, #0 ; 0x0 ----> r3 = 0x0,将0保存到r3寄存器中
  • 20. CPU读取0xe5823000指令,即str r3, [r2],即将保存在r3寄存器中的值0存储到内存地址0x56000054中
  • 21. CPU读取0xe3a03000指令,即mov r3, #0 ; 0x0,将0保存到r3寄存器中,这里0其实是return 0的0值
  • 22. CPU读取0xe1a00003指令,即mov r0, r3 ----> 将r3中的0值保存到r0中,即r0中保存的值就是main程序的返回值0。
  • 23. CPU读取0xe24bd00c指令,即sub sp, fp, #12 ; 0xc ----> sp = fp -12 = 4092 - 12 = 4080,恢复栈指针sp在进入main程序之前的最后sp指向的地址,开始准备恢复进入main之前的环境
  • 24. CPU读取0xe89da800指令,即ldmia sp, {fp, sp, pc},开始恢复调用main之前的环境:
    • 1. 首先,将内存地址4080中的值,加载到fp寄存器中,即fp = 未知数
    • 2. 然后, sp’ = 4080 + 4 = 4084,实际的sp仍然是sp = 4080
    • 3. 再然后,将4084中的值,加载到sp栈寄存器中,即sp此时指向4096
    • 4. 接着,sp’ = 4084 + 4 = 4088,实际的sp 仍然是sp = 4096
    • 5. 然后, 将地址4088中的值加载到pc寄存器中,即pc = 0x8
    • 6. 最后,跳转到halt标签,死循环。

[上一页][下一页]

上一篇:ElasticSearch(九)基于version进行乐观锁并发控制


下一篇:ospf分流实验