深度剖析ARM内核寄存器及基本汇编语言3

电子说

1.2w人已加入

描述

三、代码反汇编简析

  • 汇编 汇编文件转换为目标文件(里面是机器码,机器码是给CPU使用的,烧录保存在Flash空间的就是机器码)。
  • 反汇编 可执行文件(目标文件,里面是机器码),转换为汇编文件。

3.1 不同编译器的反汇编

3.1.1 Keil下面生成反汇编文件

fromelf –text -a -c –output=(改成你想生成的反汇编名字一般是工程名字).dis (需要的axf文件,根据你工程生成axf的路径填写).axfARM设置好以后编译之后就会生成反汇编.dis文件:ARM

打开如下所示:ARM对于上图中的红色圈出来的语句,我们可以根据本文 第 二 章节的第2小节 ARM汇编格式中的介绍来分析一下:ARM

简单分析如下(立即数就不分析了= =!):ARM

3.1.2 gcc下生成反汇编文件

在X86架构下的电脑上生成ARM架构的汇编代码有两种方式:

  • 使用交叉编译工具链 指定-S选项可以生成汇编中间文件。ex:gcc -S test.c
  • 使用 objdump 反汇编 arm二进制文件。

上述两种方法的区别为:

(1)反汇编可以生成ARM指令操作码,-S生成的汇编没有指令码 (2)反汇编的代码是经过编译器优化过的。(3)反汇编代码量很大。

对于ARM Cortex-M,使用的是 arm-none-eabi-objdump,常用指令如下:

  • arm-none-eabi-objdump -d -S(可省) a1.o 查看a1.o反汇编可执行段代码
  • arm-none-eabi-objdump -D -S(可省) a1.o 查看a1.o反汇编所有段代码
  • arm-none-eabi-objdump -D -b binary -m arm ab.bin 查看ab.bin反汇编所有代码段

对于使用 arm-none-eabi-gcc 工具链(以STM32CUbeMX)的内核来说,使用如下方式生成反汇编文件:

$(OBJDUMP) -D -b binary -m arm (需要的elf文件,一般是工程名字).elf > (改成你想生成的反汇编名字,一般是工程名字).dis # OBJDUMP = arm-none-eabi-objdump

-D表示对全部文件进行反汇编,-b表示二进制,-m表示指令集架构

Makefile修改如下:

...
TARGET = D6TPir
#######################################
# paths
#######################################
# Build path
BUILD_DIR = build
...
PREFIX = arm-none-eabi-
...
OBJDUMP = $(PREFIX)objdump

dis:
 $(OBJDUMP) -D -b binary -m arm $(BUILD_DIR)/$(TARGET).elf > $(BUILD_DIR)/$(TARGET).dis
# $(OBJDUMP) -D -b binary -m arm $(BUILD_DIR)/$(TARGET).bin > $(BUILD_DIR)/$(TARGET).dis

执行 make dis 即可生成 .dis 文件:ARMARM打开文件查看,发现怎么这个汇编语言有点不一样:ARM经过研究了一段时间,加上了-M force-thumb后稍微有点样子了:ARM! 在网上有各种参考,但是我都测试过了,并没有找到合适的生成完全和标准汇编一致的那种,-M后面的参数也不能乱加,需要根据自己的交叉编译器,因为这里用的是 arm-none-eabi-gcc,所以可以通过arm-none-eabi-objdump --help 查看能用的命令和参数: gcc工具链下的汇编还是不太熟悉,所以我们下面反汇编文件与 C语言的对比,使用Keil下的反汇编进行说明。

3.2 C 和 汇编 比较分析

前面介绍了那么多,最终用一个简单的程序对比一下C语言反汇编后的汇编语言,加深一下印象,当作个实战总结。

基于STM32L051(Cortex-M0)内核,目的是为了比较C和汇编,用了个最简单的程序来分析,没有用到任务外设,程序如下:

//前面省略...
void delay(u32 count)
{
 while(count--);
}

u32 add(u16 val1,u16 val2)
{
 u32 add_val;
 
 add_val = val1 + val2;
 
 return add_val;
}
 int main(void)
 {
 u16 a,b;
 u32 c;
 a = 12345;
 b = 45678;
 c = add(a,b);
 while(1)
 {
   c--;
   delay(200000);
  }
 }

反汇编的代码对应部分如下(因为基于硬件平台,其他异常中断,堆,栈,包括其他一些也有汇编代码,这里省略):

;省略前面
    delay
        0x080001ae:    bf00        ..      NOP      
        0x080001b0:    1e01        ..      SUBS     r1,r0,#0
        0x080001b2:    f1a00001    ....    SUB      r0,r0,#1
        0x080001b6:    d1fb        ..      BNE      0x80001b0 ; delay + 2
        0x080001b8:    4770        pG      BX       lr
    add
        0x080001ba:    4602        .F      MOV      r2,r0
        0x080001bc:    1850        P.      ADDS     r0,r2,r1
        0x080001be:    4770        pG      BX       lr
    main
        0x080001c0:    f2430439    C.9.    MOV      r4,#0x3039
        0x080001c4:    f24b256e    K.n%    MOV      r5,#0xb26e
        0x080001c8:    4629        )F      MOV      r1,r5
        0x080001ca:    4620         F      MOV      r0,r4
        0x080001cc:    f7fffff5    ....    BL       add ; 0x80001ba
        0x080001d0:    4606        .F      MOV      r6,r0
        0x080001d2:    e003        ..      B        0x80001dc ; main + 28
        0x080001d4:    1e76        v.      SUBS     r6,r6,#1
        0x080001d6:    4804        .H      LDR      r0,[pc,#16] ; [0x80001e8] = 0x30d40
        0x080001d8:    f7ffffe9    ....    BL       delay ; 0x80001ae
        0x080001dc:    e7fa        ..      B        0x80001d4 ; main + 20
    $d
        0x080001de:    0000        ..      DCW    0
        0x080001e0:    e000ed0c    ....    DCD    3758157068
        0x080001e4:    05fa0000    ....    DCD    100270080
        0x080001e8:    00030d40    @...    DCD    200000
;省略后面

3.2.1 MOV后面 立即数的疑问

在对比分析这段代码前,在 main 函数中的第一句:

0x080001c0:    f2430439    C.9.    MOV      r4,#0x3039

就有一个大大的疑问, MOV r4,#0x3039中 0x3039 并不是立即数(按照我们第二章 立即数的说明) ,包括接下来的 0xb26e 也不是立即数,怎么可以直接用 mov,按理来说需要用 LDR伪指令的??

至于这个问题,网上简单查找了一下,找到一篇有关说明的文章:ARM 汇编的mov操作立即数的疑问 其中有说到,在 keil 公司方网站里关于arm汇编的说明里有这么一段:

Syntax MOV{cond} Rd, #imm16 where: imm16 is any value in the range 0-65535.

所以是不是在 Keil 中的arm汇编 立即数可以使16位的?

为了验证一下,我稍微修改了一下程序,就是把a的值赋值超过16位(当然定义函数之类的也要跟着改,测试代码中a为u16的无符号整形),测试了一下。

a赋值为 65535,结果如下(65535不是立即数,也可以直接mov):

0x080001c0:    f64f75ff    O..u    MOV      r5,#0xffff

a赋值为 65536,结果如下(65536是立即数,可以直接mov):

0x080001c0:    f44f3580    O..5    MOV      r5,#0x10000

a赋值为一个大于16位的,不是立即数的数,比如:0x1FFFF :

0x080001c0:    4d08        .M      LDR      r5,[pc,#32] ; [0x80001e4] = 0x1ffff

果然,最后当 a 大于16位,不是立即数时候,会使用伪指令 LDR,所以我们可以得出结论:

在 Keil 中的arm汇编中,16位内(包括16位)的数都直接使用 MOV 赋值,大于16位,如果是立即数,直接使用MOV,不是立即数用LDR (立即数的判断方式还是前面讲的那样)

3.2.2 反汇编文件解析

对于上面的示例程序的汇编码,简单解析如下:ARM添加一个有意思的测试对于delay函数中的语句,上图是while(count--);改成while(--count);后汇编代码如下:ARM

对于上面的测试程序,汇编中并没有使用到 PUSH 和 POP 指令,因为程序太简单了,不需要使用到栈,为了能够熟悉下单片机中必须且经常需要用到的 栈,我们稍微修改一下add函数,在add函数中调用了delay函数:

u32 add(u16 val1,u16 val2)
{
 u32 add_val;
 
 add_val = val1 + val2;
 
 delay(10);
 
 return add_val;
}

对于的add函数汇编代码如下:

add
        0x080001ba:    b530        0.      PUSH     {r4,r5,lr}   ;把r4 r5 lr的值入栈
        0x080001bc:    4603        .F      MOV      r3,r0
        0x080001be:    460c        .F      MOV      r4,r1
        0x080001c0:    191d        ..      ADDS     r5,r3,r4
        0x080001c2:    200a        .       MOVS     r0,#0xa
        0x080001c4:    f7fffff3    ....    BL       delay ; 0x80001ae
        0x080001c8:    4628        (F      MOV      r0,r5
        0x080001ca:    bd30        0.      POP      {r4,r5,pc}  ;把r4 r5 lr的值出栈,

(汇编中可以看到指令后面后面加了个S ,MOVS 、ADDS,这就是我们前面说到的,带了S 会影响 xPSR 寄存器中的值)

可以看到,因为存在函数的多次调用,main函数中调用add函数,add函数中调用delay函数,所以在add函数运行之前,通过 push 把 r4,r5,lr 寄存器的值先存入栈中,等待程序执行完(函数调用结束)再吧 r4,r5,lr 寄存器的值恢复。

上面的程序虽然简单,但是通过我们C程序 与 汇编程序的对比分析,能够让我们更加深入的理解汇编语言。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分