一个简单的 ARM 汇编程序和 C 程序及深入分析

芯片框架

本实验开发板所用 ARM 芯片是 S3C2440 芯片,其框架如下:


01_010 S3C2440框架 .png

S3C2440 芯片是从 0 地址开始启动的,有 Nor 启动和 Nand 启动两种启动方式。

Nor 启动时,Nor Flash 的基地址为 0,片内 SDRAM 地址为0x4000 0000 (见芯片手册 Memory Map)。启动后,CPU 从Nor Flash 0 地址读出第 1 个指令(前4字节),执行;CPU 继续读出其它指令执行。

Nand 启动时,片内 4kSDRAM 基地址为 0,Nor Flash 不可访问。S3C2440 芯片硬件会把 Nand Flash 前 4K 内容复制到片内的SDRAM,然后 CPU 从0地址取出第1条指令执行。

原理图确定引脚

第一,从原理图确定 LCD 的引脚。


01_007 LED电路图.png

引脚 nLED1、2、4 输出高电平时灯熄灭,低电平时灯点亮。

GPF 寄存器地址和设置说明:


01_009 GPF 寄存器.png

所以,要控制 LED1 :

  • 需要设置 GPFCON[9:8] = 0b01, 把 GPF4 配置为输出引脚。即把 0x100 写入 GPFCON 这个寄存器,地址为0x5600 0050
  • 控制 LED 的亮灭,需要设置 GPFDAT[4] = 1 或者0,输出高电平或低电平。即把 0x10 或 0x0 写到地址 0x5600 0054

注: 上面的写法会破坏寄存器的其它位,其它位是控制其它引脚的。但为了让第一个裸板程序尽可能的简单,此实验才简单粗暴的这样处理。实际要用位操作,只设置对应的位!

编写汇编程序

汇编程序 led_on.S 代码如下:

/*
 * 点亮LED1: gpf4
 */
.text
.global _start

_start:

/* 配置GPF4为输出引脚
 * 把0x100写到地址0x56000050
 */
    ldr r1, =0x56000050
    ldr r0, =0x100  /* mov r0, #0x100 */
    str r0, [r1]

/* 设置GPF4输出低电平 
 * 把0写到地址0x56000054
 */
    ldr r1, =0x56000054
    ldr r0, =0  /* mov r0, #0 */
    str r0, [r1]

    /* 死循环 */
halt:
    b halt

Makefile:

all:
    arm-linux-gcc -c -o led_on.o led_on.S  //编译
    arm-linux-ld -Ttext 0 led_on.o -o led_on.elf  //链接
    arm-linux-objcopy -O binary -S led_on.elf led_on.bin  //生成bin文件
    arm-linux-objdump -D led_on.elf > led_on.dis  //反汇编
clean:
    rm *.bin *.o *.elf *.dis

编译后烧写到开发板上,即可看到LED1亮。

分析反汇编代码

代码中的ldr r1, =0x56000050这条伪指令的真实指令时什么呢?

可以通过反汇编来查看。生成的 led_on.dis 就是反汇编文件。led_on.dis 如下:

led_on.elf:     file format elf32-littlearm

Disassembly of section .text:

00000000 <_start>:
   0:   e59f1014    ldr r1, [pc, #20]   ; 1c <.text+0x1c>
   4:   e3a00c01    mov r0, #256    ; 0x100
   8:   e5810000    str r0, [r1]
   c:   e59f100c    ldr r1, [pc, #12]   ; 20 <.text+0x20>
  10:   e3a00000    mov r0, #0  ; 0x0
  14:   e5810000    str r0, [r1]

00000018 <halt>:
  18:   eafffffe    b   18 <halt>
  1c:   56000050    undefined
  20:   56000054    undefined
  
  //第一列是地址,第二列是机器码,第三列是汇编;
  //在反汇编文件里可以看到,ldr r1, =0x56000050 被转换成 ldr r1, [pc, #20], pc+20地址的值为0x56000050,通过这种方式为r1赋值。 对于立即数0x100而言,ldr r0,=0x100即是转换成了mov r0,#256;

上面的反汇编程序解析如下:

0:  e59f1014    ldr r1, [pc, #20]   ; 1c <.text+0x1c>
// r1 = [pc + 20] = [0 + 8 + 20] = [0x1c] = 0x56000050
4:  e3a00c01    mov r0, #256    ; 0x100
8:  e5810000    str r0, [r1]
// 把r0 即0x100写r1对应的内存,即0x100写到地址0x56000050
c:  e59f100c    ldr r1, [pc, #12]   ; 20 <.text+0x20>
// r1 = [pc + 12] = [0xc + 8 + 12] = [32] = [0x20] = 56000054
10: e3a00000    mov r0, #0  ; 0x0
14: e5810000    str r0, [r1]

00000018 <halt>:
  18:   eafffffe    b   18 <halt>
  1c:   56000050    undefined
  20:   56000054    undefined

编写 C 程序

lcd.c

int main()
{
    unsigned int *pGPFCON = (unsigned int *)0x56000050;
    unsigned int *pGPFDAT = (unsigned int *)0x56000054;

    /*配置GPF4为输出引脚*/
    *pGPFCON = 0x100;
    
    /*配置GPF4输出0*/
    *pGPFDAT = 0;

    return 0;
}

还需要写一个汇编程序, 给 main 函数设置内存, 调用main函数。

start.S:

.text
.global _start
_start:
    /*设置内存:sp栈*/
    ldr sp,=4096 /*nand启动*/
        
//  ldr sp, =0x40000000 /*nor启动*/

    /*调用main*/
    bl main
halt:
    b halt

bl main 跳转执行main函数,并把"返回地址"(即下一条指令地址)保存在lr寄存器中,这里返回地址是halt。

Makefile:

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

最后即可编译,烧写到开发板,看到LED1亮。

用反汇编代码分析C程序的执行过程

汇编程序 start.S所做的事情是 :1. 设置栈;2. 调用 main,并把返回值地址保存到 lr 中。

C程序 led.c 的 main() 所做的事情是 :1. 定义2个局部变量;2. 设置变量;3. return 0。

从反汇编代码来分析一下C程序执行过程,解答下面的问题。

  1. 为什么要设置栈?因为c函数要用。
  2. 栈的作用是什么? a.保存局部变量;b.保存 lr、fp、ip、sp、pc 等寄存器的值;
  3. 调用者如何传参数给被调用者?
  4. 被调用者如何传返回值给调用者?
  5. 怎么从栈中恢复那些寄存器?

ATPCS规则

在arm中有个ATPCS规则,约定 r0 - r15 寄存器的用途。即ARM-THUMB procedure call standard(ARM-Thumb子程序调用规则)

r0 - r3: 用于调用者和被调用者之间传参数;

r4-r11: 可能被子函数使用,所以在函数的入口保存它们,在函数的出口恢复它们;

反汇编程序 led.dis :

led.elf:     file format elf32-littlearm

Disassembly of section .text:

00000000 <_start>:
   0:   e3a0da01    mov sp, #4096   ; 0x1000
   4:   eb000000    bl  c <main>

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

0000000c <main>:
   c:   e1a0c00d    mov ip, sp
  10:   e92dd800    stmdb   sp!, {fp, ip, lr, pc}
  14:   e24cb004    sub fp, ip, #4  ; 0x4
  18:   e24dd008    sub sp, sp, #8  ; 0x8
  1c:   e3a03456    mov r3, #1442840576 ; 0x56000000
  20:   e2833050    add r3, r3, #80 ; 0x50
  24:   e50b3010    str r3, [fp, #-16]
  28:   e3a03456    mov r3, #1442840576 ; 0x56000000
  2c:   e2833054    add r3, r3, #84 ; 0x54
  30:   e50b3014    str r3, [fp, #-20]
  34:   e51b2010    ldr r2, [fp, #-16]
  38:   e3a03c01    mov r3, #256    ; 0x100
  3c:   e5823000    str r3, [r2]
  40:   e51b2014    ldr r2, [fp, #-20]
  44:   e3a03000    mov r3, #0  ; 0x0
  48:   e5823000    str r3, [r2]
  4c:   e3a03000    mov r3, #0  ; 0x0
  50:   e1a00003    mov r0, r3
  54:   e24bd00c    sub sp, fp, #12 ; 0xc
  58:   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.

Nand 启动时,硬件机制会把 nand flash 上前 4K 的机器码拷贝到片内的 4K SDRAM上。然后从0地址开始执行。

开发板上电后,将从0地址开始执行,即开始执行:

mov sp, #4096  //:设置栈地址在4k RAM的最高处,sp=4096;
bl    c <main>  //:跳到c地址处的main函数,并保存下一行代码地址到lr,即lr=8;
mov ip, sp  //:给ip赋值sp的值,ip=sp=4096
stmdb   sp!, {fp, ip, lr, pc}  //:按高编号寄存器存在高地址,依次将pc、lr、ip、fp存入sp-4中;
sub fp, ip, #4  //:fp的值为ip-4=4096-4=4092;
sub sp, sp, #8  //:sp的值为sp-8=(4096-4x4)-8=4072;
mov r3, #1442840576  //:r3赋值0x5600 0000; 
add r3, r3, #80  //:r3的值加0x50,即r3=0x5600 0050;
str r3, [fp, #-16]  //:r3存入[fp-16]所在的地址,即地址4076处存放0x5600 0050;
mov r3, #1442840576  //:r3赋值0x5600 0000; 
add r3, r3, #84  //:r3的值加0x54,即r3=0x5600 0054;
str r3, [fp, #-20]  //:r3存入[fp-20]所在的地址,即地址4072处存放0x5600 0054;
ldr r2, [fp, #-16]  //:r2取[fp-16]地址处的值,即[4076]地址的值,r2=0x5600 0050;
mov r3, #256  //:r3赋值为0x100;
str r3, [r2]  //:将r3写到r2内容所对应的地址,即0x5600 0050地址处的值为0x100;;对应c语言*pGPFCON = 0x100;;
ldr r2, [fp, #-20]  //:r2取[fp-20]地址处的值,即[4072]地址的值,r2=0x5600 0054;
mov r3, #0  //:r3赋值为0x00;
str r3, [r2]  //:将r3写到r2内容所对应的地址,即0x5600 0054地址处的值为0x00;对应c语言*pGPFDAT = 0;
mov r3, #0  //:r3赋值为0x00;
mov r0, r3  //:r0=r3=0x00;
sub sp, fp, #12  //:sp=fp-12=4092-12=4080;
ldmia   sp, {fp, sp, pc}  //:从栈中恢复寄存器,fp=4080地址处的值=原来的fp,sp=4084地址处的值=4096,pc=4088地址处的值=8,随后跳到0x08地址处继续执行。

//注意点: 调用main函数之前sp = 4096, main函数返回后sp仍然是4096

汇编程序传递参数给C程序分析

前面的例子,汇编程序调用 main.c 并没有传递参数,这里修改下 c 程序,添加带参数的函数。

led.c:

void delay(volatile int d) //这里volatile关键字是为了避免编译器自作聪明的把delay函数优化掉. 编译器可能从逻辑上看,省掉delay函数也没什么关系。
{
    while (d--);
}

int led_on(int which)
{
    unsigned int *pGPFCON = (unsigned int *)0x56000050;
    unsigned int *pGPFDAT = (unsigned int *)0x56000054;

    if (which == 4)
    {
        /* 配置GPF4为输出引脚 */
        *pGPFCON = 0x100;
    }
    else if (which == 5)
    {
        /* 配置GPF5为输出引脚 */
        *pGPFCON = 0x400;
    }
    
    /* 设置GPF4/5输出0 */
    *pGPFDAT = 0;

    return 0;
}

start.S:

.text
.global _start

_start:

    /* 设置内存: sp 栈 */
    ldr sp, =4096  /* nand启动 */
//  ldr sp, =0x40000000+4096  /* nor启动 */

    mov r0, #4    //r0-r3:调用者和被调用者之间传参数;
    bl led_on

    ldr r0, =100000
    bl delay

    mov r0, #5
    bl led_on

halt:
    b halt

led.elf:

分析见注释:

led.elf:     file format elf32-littlearm

Disassembly of section .text:

00000000 <_start>:
   0:   e3a0da01    mov sp, #4096   ; 0x1000   //:设置栈地址在4k RAM的最高处,sp=4096;
   4:   e3a00004    mov r0, #4  ; 0x4  //:r0=4,作为参数;
   8:   eb000012    bl  58 <led_on>  //:调到58地址处的led_on函数,并保存下一行代码地址到lr,即lr=8;在led_on中会使用到r0;
   c:   e59f000c    ldr r0, [pc, #12]   ; 20 <.text+0x20>  //:r0=[pc+12]处的值=[0xc+8+12]=0x20 处的值 = 0x186a0 = 1000000,作为参数;
  10:   eb000003    bl  24 <delay> //:调用24地址处的delay函数,并保存下一行代码地址到lr,即lr=24;在delay中会使用到r0;
  14:   e3a00005    mov r0, #5  ; 0x5 //:r0=5,作为参数;
  18:   eb00000e    bl  58 <led_on> //:调到58地址处的led_on函数,并保存下一行代码地址到lr,即lr=58;在led_on中会使用到r0;

0000001c <halt>:
  1c:   eafffffe    b   1c <halt>
  20:   000186a0    andeq   r8, r1, r0, lsr #13

00000024 <delay>:
  24:   e1a0c00d    mov ip, sp
  28:   e92dd800    stmdb   sp!, {fp, ip, lr, pc}
  2c:   e24cb004    sub fp, ip, #4  ; 0x4
  30:   e24dd004    sub sp, sp, #4  ; 0x4
  34:   e50b0010    str r0, [fp, #-16]
  38:   e51b3010    ldr r3, [fp, #-16]
  3c:   e2433001    sub r3, r3, #1  ; 0x1
  40:   e50b3010    str r3, [fp, #-16]
  44:   e51b3010    ldr r3, [fp, #-16]
  48:   e3730001    cmn r3, #1  ; 0x1
  4c:   0a000000    beq 54 <delay+0x30>
  50:   eafffff8    b   38 <delay+0x14>
  54:   e89da808    ldmia   sp, {r3, fp, sp, pc}

00000058 <led_on>:
  58:   e1a0c00d    mov ip, sp
  5c:   e92dd800    stmdb   sp!, {fp, ip, lr, pc}
  60:   e24cb004    sub fp, ip, #4  ; 0x4
  64:   e24dd00c    sub sp, sp, #12 ; 0xc
  68:   e50b0010    str r0, [fp, #-16]
  6c:   e3a03456    mov r3, #1442840576 ; 0x56000000
  70:   e2833050    add r3, r3, #80 ; 0x50
  74:   e50b3014    str r3, [fp, #-20]
  78:   e3a03456    mov r3, #1442840576 ; 0x56000000
  7c:   e2833054    add r3, r3, #84 ; 0x54
  80:   e50b3018    str r3, [fp, #-24]
  84:   e51b3010    ldr r3, [fp, #-16]
  88:   e3530004    cmp r3, #4  ; 0x4
  8c:   1a000003    bne a0 <led_on+0x48>
  90:   e51b2014    ldr r2, [fp, #-20]
  94:   e3a03c01    mov r3, #256    ; 0x100
  98:   e5823000    str r3, [r2]
  9c:   ea000005    b   b8 <led_on+0x60>
  a0:   e51b3010    ldr r3, [fp, #-16]
  a4:   e3530005    cmp r3, #5  ; 0x5
  a8:   1a000002    bne b8 <led_on+0x60>
  ac:   e51b2014    ldr r2, [fp, #-20]
  b0:   e3a03b01    mov r3, #1024   ; 0x400
  b4:   e5823000    str r3, [r2]
  b8:   e51b3018    ldr r3, [fp, #-24]
  bc:   e3a02000    mov r2, #0  ; 0x0
  c0:   e5832000    str r2, [r3]
  c4:   e3a03000    mov r3, #0  ; 0x0
  c8:   e1a00003    mov r0, r3
  cc:   e24bd00c    sub sp, fp, #12 ; 0xc
  d0:   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.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容