arm裸机学习预备知识五:解析C程序内部机制

注:以下内容学习于韦东山老师arm裸机第一期视频教程

以JZ2440点灯的C程序为例来解析C程序的内部机制

    一.JZ2440点灯C程序

        1.1 启动代码start.S   

        .text
        .global _start

        _start:
            ldr sp, =4096

            /* main只需要一个参数,通过r0传递 */
            mov r0, #4
            bl main

            mov r0,#5
            bl main

        halt:
            b halt


        1.2 main函数

                
        int main(int which)
        {
            unsigned int *gpfcon = (unsigned int *)0x56000050;
            unsigned int *gpfdat = (unsigned int *)0x56000054;
            if (which == 4)
                *gpfcon = 0x100;                
            else if (which == 5)
                *gpfcon = 0x400;
            
            *gpfdat = 0x0;

            return 0;
        }

        1.3 Makefile

        all:
            arm-linux-gcc -c -o main.o main.c
            arm-linux-gcc -c -o led_on.o start.S
            arm-linux-ld -Ttext 0 led_on.o main.o -o led_on.elf
            arm-linux-objcopy -O  binary -S led_on.elf led_on.bin
            arm-linux-objdump -D led_on.elf > led_on.dis
            
        clean:
            rm *.o *.elf *.bin

    二.分析C程序内部机制

        2.1 问题1: 为什么要设置栈?

                    因为C函数要使用

                    栈要指向一块可读可写的内存,并且不与代码冲突

              问题2:栈的作用?

                    a. 保存局部变量

                    b. 保存lr等寄存器(加入在main函数中调用函数,返回地址要保存在lr寄存器中,会将之前的lr寄存器覆盖,因此要保存之前的Lr寄存器)

                    c. 保存调用者给被调用者传递的参数(通过r0-r3寄存器传递)

                    d. 保存被调用者的返回值给调用者(通过r0-r3寄存器传递)

                    e. 在函数中r4-r11可能被使用,因此需要在函数的入口保存他们,在函数的出口恢复他们。

            

        2.1 分析反汇编文件led_on.dis

            nand启动时会将nand的前4K拷贝到内存中,因此下面的代码会在内存的前4K中

            
            
            led_on.elf:     file format elf32-littlearm

            Disassembly of section .text:

            00000000 <_start>:
                                /* 设置栈,sp = 4096,在内存的顶部,我们的代码很小,在0地址处,与栈不会冲突 */
               0:    e3a0da01     mov    sp, #4096    ; 0x1000
                                /* r0 = 4 */
               4:    e3a00004     mov    r0, #4    ; 0x4
                                /* 调用main函数 */
               8:    eb000002     bl    18 
/* r0 = 5 */ c: e3a00005 mov r0, #5 ; 0x5 /* 调用main函数 */ 10: eb000000 bl 18
00000014 : 14: eafffffe b 14 00000018
: /* ip = sp */ 18: e1a0c00d mov ip, sp /* 将fp,ip,lr,pc寄存器的值存放到sp处,同时更新sp的值 * 寄存器高标号放在高地址 */ * pc = 10 + 8 = 0x18 */ * 因此 PC = 0x18 存放在4092~4095 * 然后再次减 lr = 8 存放在4088~4091 * 然后再次减 ip = 4096 存放在4084~4097 * 然后再次减 fp = 未定义 存放在4080~4083 * 最后更新sp的值 sp = 4080 */ 1c: e92dd800 stmdb sp!, {fp, ip, lr, pc} /* fp = ip - 4 = 4092 */ 20: e24cb004 sub fp, ip, #4 ; 0x4 /* sp = sp - 12 */ /* 12用来存放局部变量 */ 24: e24dd00c sub sp, sp, #12 ; 0xc /* 把r0存放到fp-16=4092-16=4076处 */ 28: e50b0010 str r0, [fp, #-16] /* r3 = 0x56000000 */ 2c: e3a03456 mov r3, #1442840576 ; 0x56000000 /* r3 = 0x56000000 + 0x50 = 0x56000050 */ 30: e2833050 add r3, r3, #80 ; 0x50 /* r3存入fp-20 = 4092 - 20 = 4072地址处 */ /* 局部变量gpfcon保存在了栈中 */ 34: e50b3014 str r3, [fp, #-20] /* r3 = 0x56000000 */ 38: e3a03456 mov r3, #1442840576 ; 0x56000000 /* r3 = r3 + 0x54 = 0x56000054 */ 3c: e2833054 add r3, r3, #84 ; 0x54 /* r3存入fp-24 = 4092 - 24 = 4068地址处 */ /* 局部变量gpfdat保存在了栈中 */ 40: e50b3018 str r3, [fp, #-24] /* 从sp-16 = 4076的地址取值,即寄存器r0存放的参数值,存放到r3寄存器 */ 44: e51b3010 ldr r3, [fp, #-16] /* 比较r3与4 */ 48: e3530004 cmp r3, #4 ; 0x4 /* 执行完调到60地址 */ 4c: 1a000003 bne 60 /* r3 == 4时执行下面的代码 */ /* 从fp-20=4072的地址取值,得到了gpfcon变量的值,也就是0x56000050这个地址 */ 50: e51b2014 ldr r2, [fp, #-20] /* r3 = 0x100 */ 54: e3a03c01 mov r3, #256 ; 0x100 /* 将0x100写入0x56000050这个地址 */ 58: e5823000 str r3, [r2] 5c: ea000005 b 78 /* 从sp-16 = 4076的地址取值,即寄存器r0存放的参数值,存放到r3寄存器 */ 60: e51b3010 ldr r3, [fp, #-16] /* 比较r3与4 */ 64: e3530005 cmp r3, #5 ; 0x5 /* 执行完调到78地址 */ 68: 1a000002 bne 78 /* r3 == 5时执行下面的代码 */ /* 从fp-20=4072的地址取值,得到了gpfcon变量的值,也就是0x56000050这个地址 */ 6c: e51b2014 ldr r2, [fp, #-20] /* r3 = 0x400 */ 70: e3a03b01 mov r3, #1024 ; 0x400 /* 将0x400写入0x56000050这个地址 */ 74: e5823000 str r3, [r2] /* 从4068的地址取值放到r3寄存器,得到了gpfdat的值,也就是0x560000054这个地址 */ 78: e51b3018 ldr r3, [fp, #-24] /* r2 = 0 */ 7c: e3a02000 mov r2, #0 ; 0x0 /* 将0写入0x560000054这个地址 */ 80: e5832000 str r2, [r3] /* r3 = 0 */ 84: e3a03000 mov r3, #0 ; 0x0 /* r0 = r3,r0保存返回值0 */ 88: e1a00003 mov r0, r3 /* sp = fp - 12 = 4092 - 12 = 4080 */ /* 释放了局部变量,恢复栈 */ 8c: e24bd00c sub sp, fp, #12 ; 0xc /* * 从栈中恢复寄存器,将sp的值加载到多个寄存器中 * 因此 fp存放4080~4083的值,等于原来保存的fp * 然后再次增加 sp存放4084~4087的值,等于原来保存的ip,sp = 4096(程序刚开始ip = sp,保存了栈的值) * 然后再次增加 pc存放4088~4091的值,等于原来保存的lr = 8 * pc = lr = 8,因此程序就会返回0x8的地址 * 最后再次增加 sp' = sp + 4 = 4096 ,由于sp后面无!号,sp修改后的地址并不存入sp中 */ 90: e89da800 ldmia sp, {fp, sp, pc} Disassembly of section .comment: /* 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.




    

你可能感兴趣的:(arm裸机学习笔记)