汇编语言之GNU ARM

什么是GNU

GNU最开始其实是一个操作系统,旨为打造一个开源免费自由的操作系统,目前操作系统还在完善中

GNU计划: 最初目标是创建一套完全自由的操作系统GNU 和相应的软件

GCC :(GNU Compiler Collection)GNU编译器套件,GNU提供的一整套的工具集,这套工具集中包含了汇编器,编译器和链接器,二进制转换,调试工具等

GCC优势:

  • 免费开源
  • 贴近系统底层,功能强大,灵活性高
  • 跨平台,方便交叉编译

GCC劣势:

  • 工具基本采用命令行方式,学习和使用门槛较高

接下来我们要学习的就是GNU计划众多的产物之一GNU FOR ARM

汇编器与指令集

  1. 什么是汇编器

    将汇编语言翻译成机器码的工具

  2. 什么是编译器

    将高级语言翻译成机器语言或者汇编语言的工具

  3. 汇编器和编译器的区别

    汇编器的服务对象是汇编语言,编译器的服务对象是高级语言

  4. 汇编器和汇编语法伪指令的关系

    不同的CPU对应不同的指令集 ,不同的汇编器对应不同的伪指令集和汇编语法。

    每种汇编器都可以有自己的伪指令集和自己的语法

使用不同的汇编器汇编同一个cpu架构的汇编代码,所对应的指令绝对是一致的,但伪指令各有千秋

1
2
3
4
5
6
7
8
9
;使用ARM官方的汇编器
AREA test, CODE
mov R3,#5
END

;使用GNU的汇编器
.text ;伪指令
mov R3,#5 ;传送指令皆为mov
.end

常见的汇编器

  • MASM汇编器:微软旗下专为x86架构打造的一款汇编器,支持8086汇编和win32汇编
  • GNU汇编器 : 简称为GAS,是GNU旗下的一款免费开源跨平台汇编器其子集中包含了支持多种架构的汇编器,比如GNU FOR ARM就是单独面向ARM架构的汇编器,此外还有GNU FOR X86
  • NASM汇编器: 是一款面向x86架构的汇编器,支持8086汇编和win32汇编,同时可跨平台, 免费开源
  • ARMASM汇编器:ARM官方原生的汇编器,集成在了ADS工具上,适用于ARM架构,我们也一般称之为ADS汇编器

两种ARM汇编器的各自用途

  • ARMASM汇编器:一般用于windows平台
  • GAS汇编器:支持windows平台和linux平台,方便跨平台交叉编译

由于移动设备如安卓和iphone底层都是采用GNU的编译环境,我们如果要进行移动端的开发,那么势必需要掌握GNU ARM, 同时和ADS和KEIL收费工具相比,GUN工具全部免费,方便开发者进行使用

如果你是从事android开发,有兴趣可以去翻NDK r17以下版本的库,里面用的编译工具就是GCC

GNU ARM开发环境搭建

我们需要准备以下两个工具:

  1. GCC编译套件
  2. 安卓模拟器

GCC编译套件根据cpu架构和操作系统的不同,又分为了很多子类:

  • 纯ARM裸机: 对应arm-none-eabi工具包
  • ARM架构+Linux操作系统:对应arm-none-linux-eabi工具包

由于接下来我们选择在安卓模拟器上进行开发学习,因此我们选择arm-none-linux-eabi这套工具来进行代码的编译

工具下载

GCC工具的具体使用

伪指令和伪操作

  1. 注释

    1
    @我是注释
  2. 段的声明

    • 代码段

      1
      2
      .text
      @代码
    • 数据段

      1
      2
      .data
      @代码
  • 自定义一个段

    1
    .section .pangshu @定义一个名为.panghsu的段
  1. 函数或者标签的声明

    1
    2
    3
    fun:  @在GNU环境中标签后面需要加冒号,而原生环境则不用
    mov R0,#4
    bx lr
  2. 数据的声明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    .byte @定义单字节数据,例如:.byte 0x14;
    .short @定义双字节数据,例如:.short 0x1000;
    .long @定义四字节数据,例如:.long 0x10001000;
    .quad @定义8字节,如:.quad 0x1234567890abcd;
    .float @定义浮点数,如:.float 0f-314159265358979323846264338327/95028841971.693993751E-40 @ -pi

    @字符串定义
    .string “abcd”, “efgh”, “hello!”
    .asciz “qwer”, “sun”, “world!”
    .ascii “welcome/0”
    @需要注意的是:.ascii伪操作定义的字符串需要自行添加结尾字符’/0’。
  3. 数据的批量定义

    • 格式如下:

      1
      2
      3
      .rept @重复次数
      @数据定义代码
      .endr @结束重复定义
    • 示例

      1
      2
      3
      .rept 3
      .byte 0x23
      .endr
  4. 关于align

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    .global _start
    _start:
    mov r0, #0x12

    .byte 0x12
    .byte 0x34

    ;.align 后跟为0,1,2或者不跟参数时都当作是4字节对齐
    .align
    .byte 0x56

    ; 2^3次方,8字节对齐
    .align 3
    .word 0x1

    ; 2^5次访,32字节对齐
    .align 5
    .word 0x2

    ; 填充0x12345678,直到16字节对齐
    .balignl 16,0x12345678

    ;字符串
    .ascii "abc123"

    .align
    ;.ascii和.asciz的区别是,.asciz会在字符串后自动添加结束符\0.
    .asciz "def456"

    mov r0, #0xab

    ;.balign[wl] align, fill_value, max_padding
    .align
    ; .balign 后跟的align参数必须为2的次方,否则会报错,按字节填充, fill_value要为字节
    .balign 8, 0x12

    ; .balignw 按2字节填充
    .balignw 8, 0x3456

    ; .balignl 按4字节填充
    .balignl 8, 0xabcdef01

    反汇编后的结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    00000000 <_start>:
    0: e3a00012 mov r0, #18
    4: 3412 .short 0x3412 // .byte 0x12 和 .byte 0x34
    6: 0000 .short 0x0000 // .align 引起的填充
    8: 00000056 andeq r0, r0, r6, asr r0 // .byte 0x56
    c: e1a00000 nop ; (mov r0, r0) // .align 3 8字节对齐
    10: 00000001 .word 0x00000001 // .word 0x1
    14: e1a00000 nop ; (mov r0, r0) // .align 5 ,32字节对齐
    18: e1a00000 nop ; (mov r0, r0)
    1c: e1a00000 nop ; (mov r0, r0)
    20: 00000002 .word 0x00000002 // .word 0x2
    24: 12345678 .word 0x12345678 // .balignl 16,0x12345678,填充0x12345678,直到16字节对齐
    28: 12345678 .word 0x12345678
    2c: 12345678 .word 0x12345678
    30: 31636261 .word 0x31636261 // .ascii "abc123"
    34: 3332 .short 0x3332
    36: 0000 .short 0x0000
    38: 34666564 .word 0x34666564 // .asciz "def456"
    3c: 3635 .short 0x3635
    3e: 00 .byte 0x00
    3f: e3a000ab mov r0, #171 ; 0xab // mov r0, #0xab, 这里就不对齐了
    43: 00 .byte 0x00
    44: 12121212 .word 0x12121212 // .balign 8, 0x12
    48: e1a00000 nop ; (mov r0, r0)
    4c: e1a00000 nop ; (mov r0, r0)
    50: e1a00000 nop ; (mov r0, r0)
    54: e1a00000 nop ; (mov r0, r0)
    58: e1a00000 nop ; (mov r0, r0)
    5c: e1a00000 nop ; (mov r0, r0)

指令和伪指令的区别

  • 指令: 有与之对应的机器码,能被cpu所识别,和编译器无关

  • 伪指令:没有与之对应的机器码,无法被cpu识别,只能被编译器识别,不同编译器伪指令不一样

    不同的CPU对应不同的指令集;不同的汇编器对应不同的语法和伪指令集

例子:ARM原生编译器和GNU FOR ARM

两种汇编器语法对比一览表

GNU ARM汇编 ADS ARM汇编
“@”或“//” “;”
.include GET
.equ EQU
.global EXPORT
.extern IMPORT
.long DCD
.end END
entry: ENTRY
.text AREA Init,CODE,READONLY
.data AREA Block,DATA,READWRITE
.macro MACRO
.endm MEND

汇编语言和C语言交互

1.引入其他源文件函数

使用import或者extern伪指令

1
2
3
4
5
6
7
8
9
;使用import伪指令
AREA code, CODE
import fun1 ;导入其他源文件中名为fun1的函数
END

;使用extern伪指令
AREA code, CODE
extern fun1
END

两者区别:

  • import:不管当前文件是否使用该引入的函数,该标签都会加入当前文件符号表,即为静态引用
  • extern:只有当前文件使用了该函数,才会将此标签加入符号表,即为动态引用

2.导出当前源文件中函数供其他文件访问

使用export或者global伪指令

1
2
3
4
5
6
7
8
;使用import伪指令
AREA code, CODE
export fun ;导出fun函数供其他源文件使用

fun
mov R0,#4
bx lr
END

3.外链汇编之C语言调汇编函数

第一步,在汇编原文件中将函数暴露出来给供外部调用,使用export或者global伪指令:

1
2
3
4
5
6
7
8
9
10
11
12
AREA code, CODE
export arm_strcpy ;或者使用global

arm_strcpy
loop
ldrb R4,[R0],#1 ;如果使用ldr 那么将偏移值改成4
cmp R4,#0
beq over
strb R4,[R1],#1
b loop
over
END

第二步,在C文件中引用汇编中的函数,C文件中只能使用extern伪指令:

1
2
3
4
5
6
7
extern arm_strcpy(char *src,char*des);

int main2(){
char *a="hello pangshu";
char b[64];
arm_strcpy(a,b);
}

4.外链汇编之汇编调c语言函数

第一步,在C文件中编写好函数

1
2
3
int c_sum(int a,int b){
return a+b;
}

第二步, 在汇编文件中引入函数,使用import或者extern伪指令

1
2
3
4
5
6
7
AREA code, CODE
import c_sum

mov R0,#1 ;第一个参数
mov R1,#2 ;第二个参数

END

第三步, 使用BL指令调用函数

1
2
3
4
5
6
7
AREA code, CODE
import c_sum

mov R0,#1 ;第一个参数
mov R1,#2 ;第二个参数
BL c_sum
END

在ARM中函数参数使用R0~R3这三个寄存器来进行传递,最多传递4个参数,超过4个参数使用栈进行处理,函数返回值通过R0进行传递

5.内嵌汇编

GNU内嵌汇编,格式如下:

1
2
3
4
5
6
7
int main2(){
__asm__( //大括号改成中括号
"mov R5,#0x00000005\n" //汇编指令需要使用引号包裹,多条语句之间使用回车换行符进行分隔
"mov R6,#0x00000005"
); //需要以分号结尾
return 0;
}

学习工具

本文为作者原创 转载时请注明出处 谢谢

img

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

0%