程序是如何运行的——基于linux-ARM架构

  本文基于arm架构,通过一些简单的C语言demo,编译成汇编语言,通过对汇编语言进行解析,从而解释程序是如何在cpu核上运行的。
  arm各个模式寄存器如下图:
在这里插入图片描述
  本文不对arm寄存器进行详细的解释。具体可度娘关键词“arm寄存器
  linux内核工作在svc模式,应用程序工作在User模式。本文demo都为linux应用程序,所以都工作在User模式下。

基本概念介绍:

1,R0-R12是通用寄存器,放通用数据、临时数据,然后每个寄存器都是32位的。
2,各个模式的R0到R12与USR模式是共享的(除了FIQ,R8-R12),PC,CPSR是共享的。
3,USR模式没有SPSR

4、r13:sp 用于指向不同模式的栈顶。栈,每种模式都需要开辟一块内存,用于在该模式下 函数调用,临时分配的数据存放在此处,
5、r14 : lr 程序跳转的时候,返回地址保存到此处
7、r15 :pc 要执行的西一条指令地址,就存放在此处,每次指令执行完,就自动+4

7、CPSR:程序状态寄存器。程序执行的时候,有很多临时标记位,结果是0 是否溢出,是否有借位,是否有 进位,当前cpu模式,
8、SPSR:用于模式切换,将切换前的 cpsr 保存到 新的模式的 spsr,模式切换回去的时候,再将spsr的内容还原到cpsr。

程序示例:

编译环境:ubuntu16.04
交叉编译工具链:arm-linux-gnueabi-gcc(gcc version 4.9.4 (Linaro GCC 4.9-2017.01) )
编译执行arm-linux-gnueabi-gcc -S demo.c,就能生成demo.s了。

demo1

源码:

#include <stdio.h>

int main()
{
        return 0;
}

汇编:
(后文将省略.syntax unified此类代码)

        .syntax unified
        .arch armv7-a
        .fpu softvfp
        .eabi_attribute 20, 1
        .eabi_attribute 21, 1
        .eabi_attribute 23, 3
        .eabi_attribute 24, 1
        .eabi_attribute 25, 1
        .eabi_attribute 26, 2
        .eabi_attribute 30, 6
        .eabi_attribute 34, 1
        .eabi_attribute 18, 4
        .thumb
        .file   "demo1.c"
        .text
        .align  2
        .global main
        .thumb
        .thumb_func
        .type   main, %function
main:
        @ args = 0, pretend = 0, frame = 0
        @ frame_needed = 1, uses_anonymous_args = 0
        @ link register save eliminated.
        push    {r7}											(1)
        add     r7, sp, #0										(2)
        movs    r3, #0											(3)
        mov     r0, r3											(4)
        mov     sp, r7											(5)
        @ sp needed
        pop     {r7}											(6)
        bx      lr												(7)
        .size   main, .-main
        .ident  "GCC: (Linaro GCC 4.9-2017.01) 4.9.4"
        .section        .note.GNU-stack,"",%progbits

(1)r7寄存器中的数据入栈(此时r7中的数值为跳转到当前main函数前的值,和当前程序无关,需要先暂存到栈中)
(2)将SP指针地址赋值给r7,意思为r7 = sp + 0
(3)将0赋值给r3
(4)将r3赋值给r0,r0中存的函数返回值(main函数),r0 = r3 = 0,函数返回值为0。
(5)将r7的地址重新返还给SP指针。
(6)出栈,把内存中的数据赋值给r7(恢复程序运行前的r7值)
(7)PC跳转到LR所在地址执行代码(LR中保存了运行demo1前的PC寄存器地址)

demo2(函数临时变量)

源码:

#include <stdio.h>

int main()
{
        int i = 0;
        int j = 1;
        return 0;
}

汇编:

......
main:
        @ args = 0, pretend = 0, frame = 8
        @ frame_needed = 1, uses_anonymous_args = 0
        @ link register save eliminated.
        push    {r7}
        sub     sp, sp, #12										(1)
        add     r7, sp, #0
        movs    r3, #0											(2)
        str     r3, [r7, #4]									(3)
        movs    r3, #1											(4)
        str     r3, [r7]										(5)
        movs    r3, #0
        mov     r0, r3
        adds    r7, r7, #12										(6)
        mov     sp, r7
        @ sp needed
        pop     {r7}
        bx      lr
        .size   main, .-main
        .ident  "GCC: (Linaro GCC 4.9-2017.01) 4.9.4"
        .section        .note.GNU-stack,"",%progbits

(1)SP寄存器存的地址-12。(arm栈向下生长,等于空出12字节空间)
(2)r3做临时变量,将r3赋值为0(i=0)
(3)r3中的数据存到r7+4所在内存地址中。
(4)r3做临时变量,将r3赋值为1(i=0)
(5)r3中的数据存到r7所在内存地址中。
在这里插入图片描述
(6)main函数执行完毕,r7地址+12,即恢复函数初始的SP指针地址。
其余操作都和demo1中相同。

demo3(静态变量、全局变量)

源码:

#include <stdio.h>

static int x = 3;
int y = 4;

int main()
{
        static int k = 2;

        k = k + 1;
        x = x + 1;
        y = y + 1;

        return 0;
}

汇编:

......
        .data
        .align  2
        .type   x, %object
        .size   x, 4
x:
        .word   3
        .global y
        .align  2
        .type   y, %object
        .size   y, 4
y:
        .word   4
        .text
        .align  2
        .global main
        .thumb
        .thumb_func
        .type   main, %function
main:
        @ args = 0, pretend = 0, frame = 0
        @ frame_needed = 1, uses_anonymous_args = 0
        @ link register save eliminated.
        push    {r7}
        add     r7, sp, #0
        movw    r3, #:lower16:k.4588						(1)
        movt    r3, #:upper16:k.4588						(2)
        ldr     r3, [r3]									(3)
        adds    r2, r3, #1									(4)
        movw    r3, #:lower16:k.4588						(5)
        movt    r3, #:upper16:k.4588						(6)
        str     r2, [r3]									(7)
        movw    r3, #:lower16:x
        movt    r3, #:upper16:x
        ldr     r3, [r3]
        adds    r2, r3, #1
        movw    r3, #:lower16:x
        movt    r3, #:upper16:x
        str     r2, [r3]
        movw    r3, #:lower16:y
        movt    r3, #:upper16:y
        ldr     r3, [r3]
        adds    r2, r3, #1
        movw    r3, #:lower16:y
        movt    r3, #:upper16:y
        str     r2, [r3]
        movs    r3, #0
        mov     r0, r3
        mov     sp, r7
        @ sp needed
        pop     {r7}
        bx      lr
        .size   main, .-main
        .data
        .align  2
        .type   k.4588, %object
        .size   k.4588, 4
k.4588:
        .word   2
        .ident  "GCC: (Linaro GCC 4.9-2017.01) 4.9.4"
        .section        .note.GNU-stack,"",%progbits

  我们都知道,C语言全局变量、静态变量存放在全局存储区(静态存储区),这里可以看到代码中的X、Y、K并没有存放在栈中,而是存在某一内存地址,所有函数执行完,并不会随着栈的变化而消失。
(1)MOVW 把 16 位立即数放到寄存器的底16位,高16位清0
(2)MOVT 把 16 位立即数放到寄存器的高16位,低 16位不影响((1)(2)等于将变量K所在内存地址赋值给r3)
(3)r3当前存储的为内存地址,取内存地址中的数据赋值给r3(存储地址->存储地址中的数据)
(4)做加法,结果存放在r2中。
(5)(6)重复1、2步骤,等于将变量K所在内存地址赋值给r3
(7)将r2的值存储到r3代表的内存地址内,即将改变后的k值重新存储回k所在的内存。

demo4(函数)

源码:

#include <stdio.h>

static void test2(void)
{
}

static void test1(void)
{
        test2();
}

int main()
{
        test1();
        return 0;
}

汇编:

......
test2:
        @ args = 0, pretend = 0, frame = 0
        @ frame_needed = 1, uses_anonymous_args = 0
        @ link register save eliminated.
        push    {r7}										(5)
        add     r7, sp, #0
        mov     sp, r7
        @ sp needed
        pop     {r7}
        bx      lr
        .size   test2, .-test2
        .align  2
        .thumb
        .thumb_func
        .type   test1, %function
test1:
        @ args = 0, pretend = 0, frame = 0
        @ frame_needed = 1, uses_anonymous_args = 0
        push    {r7, lr}									(3)
        add     r7, sp, #0
        bl      test2										(4)
        pop     {r7, pc}									(6)
        .size   test1, .-test1
        .align  2
        .global main
        .thumb
        .thumb_func
        .type   main, %function
main:
        @ args = 0, pretend = 0, frame = 0
        @ frame_needed = 1, uses_anonymous_args = 0
        push    {r7, lr}									(1)
        add     r7, sp, #0
        bl      test1										(2)
        movs    r3, #0
        mov     r0, r3
        pop     {r7, pc}									(7)
        .size   main, .-main
        .ident  "GCC: (Linaro GCC 4.9-2017.01) 4.9.4"
        .section        .note.GNU-stack,"",%progbits

(1)和demo123中不同,这里不止要将r7入栈,还要将lr入栈(lr中保存函数返回的地址,因为main函数中调用别的函数,在调用别的函数时,pc跳转到test1前会将pc修改,所以要事先存储main函数返回地址LR)
(2)pc跳转到test1函数执行。
(3)同(1)步骤(test1中也调用了函数(test2)所以lr也需要入栈存储)
(4)跳转到test2中执行
(5)test2中没有包含任何函数,所以不需要存储lr地址,函数执行完pc跳转lr地址即可。
(6)恢复r7,原来lr寄存器的地址直接赋值给pc,相当于直接跳转(省去先恢复到lr,pc再跳转lr地址的过程)
(7)同(6)操作
注意:内联函数(inline)在编译时会展开,就会省去bl test1过程,而是直接在当前函数中操作内联函数中的命令。

总结

  暂时就写这么多,有空想到了再补。

发布了9 篇原创文章 · 获赞 0 · 访问量 390

猜你喜欢

转载自blog.csdn.net/weixin_42262944/article/details/103449225
今日推荐