乱码三千 – 分享实用IT技术

乱码三千 – 码出一个新世界


  • 首页

  • 归档

  • 搜索

xcode中三种编译器的区别

发表于 2021-02-01

在xcode中有三种编译器供我们选择,分别是GCC、LLVM GCC、LLVM compliler

三者区别

  • GCC :是一套由 GNU 开发的编程语言编译器。Linux/Unix操作系统的标准编译器。编译前端和后端皆使用GCC
  • LLVM GCC:编译前端使用GCC,后端使用LLVM
  • LLVM compliler :编译前端使用Clang,后端使用LLVM

为什么存在三种编译器

苹果公司最开始使用GCC编译器

为了实现对 Objective-C 新特性的支持,苹果公司结合LLVM改进GCC,从而衍生出了一个GCC分支,也就是LLVM GCC

由于LLVM GCC的笨重和局限性,苹果公司从零开始开发了一套自己的编译前端系统Clang,配合LLVM,就有了LLVM GCC

Clang的优势

相比GCC而言:

  • 编译速度快,占用内存小
  • 模块化设计,易于扩展,易于IDE集成
  • 错误提示人性化
  • 产出小

值得一提的是,Android NDK从r18开始就已经抛弃GCC,转而采用Clang编译,Clang未来可期

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

img

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

原生ARM环境中使用的编译工具集介绍

发表于 2021-02-01

这里说原生ARM环境指的是由ARM公司官方提供的的ARM编译工具集,主要包括以下几个工具:

  • armclang: 用于编译 c ,c++, GNU assembly language的源代码,不能编译ARM汇编代码

  • armasm:汇编器, 只能编译 arm汇编代码

  • armlink: 链接器,用来将目标代码,链接成可执行程序

  • armar: 打包,将目标代码打包成一个库

  • fromelf: 将可执行程序,转换为其他的镜像文件。

    以下是编译流程

这个编译器,和ARM-GCC编译器最大的区别在于,这个工具是要收费的,而ARM-GCC是不用收费的。

ArmClang编译器的参数介绍

1、–target选项

必选项,指定使用哪一种ARM指令集进行编译。只有两个选项:

  • –target=aarch64-arm-none-eabi : 指定使用aarch64

  • –target=arm-arm-none-eabi : 指定使用aarch32

2、–mcpu选项

通过指定特定的处理器型号进行编译。当指定了target,可以不指定这个。

如:

1
–mcpu=cortex-a53   #表示 指定处理器是cortex-a53,

如果要查看指定target下支持的处理器型号,可使用–mcpu=list指令选项,如下:

1
armclang –target=aarch64-arm-none-eabi  –mcpu=list #查看 aarch64下支持的处理器

3、–march选项

指定编译时针对哪一个架构进行编译。

可以使用armclang –target= aarch64-arm-none-eabi -march=list 查看aarch64下支持的架构,可以看出对于aarch64,总共支持3种架构。

而aarch32下支持的架构就多了,从armv6-armv8。

4、-g选项

表示编译器编译的时候,加上调试信息

1
armclang –target=aarch64-arm-none-eabi -g test.c

5、-Olevel选项

优化等级,level越高,优化等级越高。

  • -O0表示没有优化。

  • -Os表示减小代码密度,平衡代码大小和代码速度。

  • -Omax表示针对该target,使用最大的优化。

6、-marm -mthumb选项

-marm表示A32指令集 , -mthumb表示T32指令集

7、-Wpedantic选项

显示警告信息

8、-S选项

将c代码,或者.S代码,预处理,生成.s代码。

9、-c选项

只编译不链接,生成中间目标代码(.o)

例子:

ArmAsm汇编器的参数介绍

1、-I选项

指定搜索目录

2、–cpu选项

指定的指令集

如 –cpu=8-A.64 , 指定armv8架构的64bit的指令集

3、-g选项

汇编,加上调试信息

4、–predefine选项

定义一个宏,传递给汇编代码中使用。

例子:

Armlink连接器参数介绍

链接器,用来链接各个.o文件,得到最终的elf。

1、–ro_base选项

指定RO段的起始地址

2、–rw_base选项

指定RW段的起始地址

3、–zi_base选项

指定bss段的起始地址

4、–map –symbols选项

显示生成镜像的memory map,以及symbols信息

5、–scatter选项

指定链接脚本

6、–output选项

指定生成的文件

7、诊断信息

诊断信息的选项:

8、不使用链接脚本

使用armlink链接init.o 和 main.o,生成可执行文件。

其中:

  • 代码段起始地址是0,

  • 数据段起始地址是0x400000,

  • bss段的起始地址是0x405000。

init.o的init段,作为生成可执行文件的最开始段。

9、使用链接脚本

以下是使用链接脚本来进行链接。

下图中,链接脚本的格式。

外面指定LR1,是一个大段,从地址0x0000-0x2000。在这个RL1段,包括3个小段

  • ER_RO: 只读段,从0地址开始。其中init.o的INIT段作为这个段的开始,其他段放置在这个段的后面
  • ER_RW: 可读可写段,从0x400000开始
  • ER_ZI: bss段,从0x405000开始

10、#pragma pack(n)

#pragma用来指定打包数据时,各个数据的空间占据。

!

对于char型,占用1个字节, 对于short型,占用2个字节, 对于int型,占用4个字节。

fromelf工具介绍

反汇编工具。

1
fromelf  –text  –c  –a  elf文件  -o  文本文件

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

img

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

使用NDK下的GCC工具编译c代码至android设备中运行

发表于 2021-01-31

之前我们学过使用GCC子集之一arm-none-linux-gnueabi进行C代码编译,这次我们学习GCC另外一个子集arm-linux-androideabi,这是专门为android平台打造的一个GCC编译环境,该工具被包含在Android NDK库当中

实验环境

  • windows宿主机
  • 安卓模拟器(ARM架构+linux系统 已Root)
  • android-ndk-r9b

实验开始

第一步 编写测试代码 文件名为test.c

1
2
3
4
5
6
#include <stdio.h>

int main(){
printf("hello pangshu");
return 0;
}

第二步 将测试代码编译成可执行文件

如果直接编译:

1
arm-none-linux-androideabi-gcc.exe test.c -o main

则报错,提示:

1
clude-fixed\stdio.h:50:23: fatal error: sys/cdefs.h: No such file or directory

这是因为缺少相应的依赖库导致的,在我们使用arm-none-linux-gnueabi时直接在命令行后面添加-static即可,但是使用arm-linux-androideabi则不行

这里需要通过--sysroot指定依赖的库,指令改成如下:

1
arm-none-linux-androideabi-gcc.exe --sysroot=E:\android-ndk-r9b\platforms\android-19\arch-arm test.c -o main

此时编译通过,但是将生成的执行文件push到安卓设备上之后,却无法运行,提示:

1
error: only position independent executables (PIE) are supported.

因此我们需要借助-pie -fPIE将编译选项设置为PIE,指令最后改成如下:

1
arm-none-linux-androideabi-gcc.exe  -pie -fPIE --sysroot=E:\android-ndk-r9b\platforms\android-19\arch-arm test.c -o main

此时程序可正常执行

使用arm-linux-androideabi生成的可执行文件大小为6kB

使用arm-none-linux-gnueabi生成的可执行文件约为620KB

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

img

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

X86的安卓手机为什么可以运行ARM应用程序

发表于 2021-01-31

起因

从事android开发的小伙伴都知道,在我们进行so库适配的时候,只需要保留armabi这个库,就可以匹配市面上几乎所有的机型

但同时我们也知道,不同架构cpu只能执行与其对应架构编译出来的程序,也就是说x86设备只能执行x86程序,而不能执行ARM程序

那为什么x86的安卓设备却可以执行armabi的so库呢?

解答

我们知道从最一开始基于ARM的程序占据了几乎所有的Android生态环境。Inter为了顺利打入移动市场,一个至关重要的问题就是需要兼容ARM应用程序。

但是不同架构cpu对应的指令集不一致,为了解决这个问题,就需要一个中间翻译器,于是Inter开发了软件Houdini。Houdini可以把ARM指令集转化为X86指令集从而在Android X86设备上运行

同时由于市面上mips架构的安卓设备非常少,这也就是为什么只需一个armabi就可以通吃几乎所有设备,不过x86设备运行ARM程序虽然没什么问题,但是执行效率的损伤是必然的

附加

在adb shell模式下, 可以通过一下命令查看当前安卓设备支持的指令架构:

1
getprop

如果是x86的cpu,既可以运行x86的可执行文件,也可执行ARM架构的程序,具体的参考prop中对应的abi

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

img

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

GDB与GDBServer配合实现在安卓设备中进行程序调试

发表于 2021-01-30

实验环境

  • windows开发平台

  • 安卓模拟器(ARM架构+linux系统 已Root):

    可以通过adb shell getprop指令查看当前设备的cpu架构

  • arm-linux-androideabi编译工具 gdb和gdbserver皆存放在此开发包下

实验开始

第一步 编写测试代码 文件名为test.c

1
2
3
4
5
6
7
#include <stdio.h>

int main(){
char *a="he"
printf("hello pangshu");
return 0;
}

第二步 将测试代码编译成可执行文件

1
arm-linux-androideabi-gcc.exe -g test.c -o main -static #输出可执行文件名为main

第三步 将可执行文件传送到模拟器中

1
adb push main /data/local/tmp

第四步 将gdbserver工具传送到模拟器中

1
adb push gdbserver /data/local/tmp

第五步 进入模拟器shell窗口,使用gdbserver执行程序

1
./gdbserver 192.168.177.71:23946 main #这里的ip是宿主机的ip  23946为监听端口

第六步 在宿主机中运行gdb工具

1
gdb.exe main  #这里需要指定调试的执行文件

此时进入到gdb模式

第七步 在gdb模式下连接模拟器中的gdbserver

1
(gdb) target remote 192.168.0.1:23946  #这里的ip是模拟器的ip

连接成功后进入断点调试模式,该模式下可以使用~调试指令进行相关调试

开始调试

  1. 使用list指令列出所有源代码 可简写为l

    1
    (gdb) l
  2. 使用break指令设置断点位置 可简写为b

    1
    (gdb) b main #在main函数处打断点
  3. 使用continue指令运行到断点处 可简写为c

    1
    (gdb) c
  4. 使用step指令进行单步执行 可简写为s

    1
    (gdb) s   #会进入函数内部
  5. 使用next执行进行单步执行 可简写为n

    1
    (gdb) n  #不会进入函数内部
  6. 使用print指令打印变量的值 可简写为a

    1
    (gdb) p a #打印变量a的值
  7. 使用quit指令退出调试模式 可简写为q

    1
    (gdb) q

更多调试指令

GCC调试工具GDB的常用指令

关于arm-linux-androideabi的获取

建议下载Android NDK开发包,然后将里面的GCC套件单独抽出来使用,NDK提供了抽包脚本make-standalone-toolchain,在bulid目录的tools文件夹中可以找到,执行脚本自动抽取打包成压缩包到当前目录:

1
python make-standalone-toolchain.py --arch arm

附加

如果是在非安卓系统的ARM模拟器上,比如qemu模拟器,那么直接使用官方的arm-none-linux-eabi即可,即便是安卓设备,如果没有代码调试需求的话,仅仅是编译运行程序,也是完全够用了

但是你需要进行代码调试的话,由于android系统的安全策略,arm-none-linux-eabi包中的gdbserver无法运行,提示:

1
error: only position independent executables (PIE) are supported.

为了解决这个问题,我们因此采用arm-linux-androideabi,这里面的gdbserver可以正常运行

上面的例子中使用的NDKr17的版本

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

img

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

Makefile的详细介绍和使用

发表于 2021-01-29

待完善。。。

makefile详细介绍和使用

Makefile定义规则

参考链接1:https://seisman.github.io/how-to-write-makefile/invoke.html

参考链接2:https://www.cnblogs.com/LittleHann/p/3855905.html

规则是makefile中最重要的概念,其告诉make 目标文件的依赖关系,以及如何生成及更新这些目标文件。在makefile文件规则有2种,一种是显式规则,另一种是隐式规则。

  1. 显示规则

显式规则用于说明何时及如何重新生成目标,其列出了目标依赖的文件信息,并通过调用命令来创建或更新目标,其语法一般为:

1
2
3
4
targets : prerequisites

recipe
…
  • targets:为要生成或更新的目标

  • prerequisites:为目标依赖的关系

  • recipe:为生成目标的命令,

    一个规则可以有多条recipe,比如

    1
    2
    3
    foo.o : foo.c defs.h

    cc -c -g foo.c

其中foo.o为target,foo.c defs.h 为prerequisites,cc -c -g foo.c为recipe。

示例讲解

1
2
3
4
5
6
7
8
9
10
test:main.o channle.o
gcc main.o channle.o -o test
main.o:main.c function.h
gcc -c main.c -o main.o
channle.o:channle.c WavHead.h
gcc -c channle.c -o channle.o -std=c99

.PHONY:clean
clean:
-rm -rf *.o

执行的过程简单说就是最终需要生成一个名为test的文件,这个文件需要main.o和channle.o,于是继续往下执行,然后通过命令gcc -c main.c -o main.o得到了main.o文件,同理再得到channle.o文件,最后通过命令gcc main.o channle.o -o test进行链接最终就会得到一个名为test的可执行文件了。

上面的代码在linux的命令窗口下输入make命令就可以执行了,最终会生成一个test的可执行文件。 如果需要清除生成的中间.o文件,输入make clean就可以全部清除了。

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

img

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

GCC工具的具体使用

发表于 2021-01-29

编译工具链

高级语言翻译成机器语言不是一步到位的,以C语言为例,通常要经历以下四个步骤:

1
2
3
预处理--->编译--->汇编--->连接

源代码--->汇编代码--->目标代码--->可执行程序

每一步都需要使用不同的工具,比如源代码需要借助编译工具翻译成汇编代码,汇编代码需要借助汇编器翻译成目标代码,最后还要借助连接器帮忙整理汇总, 那么这些个工具集合到一块就叫做工具链

GCC工具链

由GNU提供的一整套的工具集,这套工具集中包含了汇编器,编译器和链接器,二进制转换,调试工具等

通过GCC,我们可以一步完成源码到可执行文件的编译, 也可以单步独立进行,方便程序员获取中间代码代码,进行调试

GCC常用命令选项

假设只编译单个源文件test.c

  1. 无选项编译

    1
    2
    3
    gcc test.c  
    或者
    gcc test.o

    在当前目录下直接生成可执行文件,默认名称为a.out

  2. 选项-o

    1
    gcc test.c  -o test

    在当前目录下生成名为test的可执行文件

  3. 选项-E

    1
    gcc -E test.c  -o test.i

    在当前目录下生成名为test.i的预处理文件

  4. 选项-S

    1
    gcc -S test.c  -o test.s

    在当前目录下生成名为test.s的汇编文件,将源码转成汇编代码

  5. 选项-c

    1
    gcc -c test.c

    在当前目录下生成名为test.o的目标文件

  6. 选项-O数字

    1
    gcc -O1 test.c  -o test

    在当前目录下生成名为test的可执行文件,并且使用编译优化级别1编译程序。可选级别为1~3,级别越大优化效果越好,但编译时间越长。

多文件编译

比如一个汇编文件aaa.s一个C源文件test.c:

  1. 方式一 多文件同时编译

    直接追加文件名进行同时编译输出即可

    1
    gcc aaa.s test.c -o bbb

    如果要编译的文件都在同一个目录下,可以用通配符gcc *.c -o 来进行编译

  2. 方式二 多文件分开编译

    首先将源文件编译成目标文件

    1
    2
    3
    gcc -c aaa.s  //生成aaa.o

    gcc -c test.c //生成test.o

    然后将所有目标文件连接成可执行文件

    1
    gcc  test.o aaa.o -o test
  3. 方式三 编写makefile文件进行编译

    第一步 makefile文件编写

    1
    2
    3
    4
    # 我是注释
    SRC=aaa.s test.c
    main: $(OBJS) #指定需要生成的文件名称以及相应的依赖关系
    gcc -o main $(SRC) #生成所需要的指令

    第二步 使用bin包下的cs-make工具,直接在命令窗口中执行该指令,默认在当前目录虚招makefile文件

    1
    # cs-make

    该指令实际上执行的就是makefile中我们编写的指令gcc -o main aaa.s test.c

以上三种方法相比较,第一中方法编译时需要所有文件重新编译,而第二种方法可以只重新编译修改的文件,未修改的文件不用重新编译 ,第三种方法适用于文件较多依赖关系复杂的工程编译

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

img

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

汇编语言之行x86_32汇编

发表于 2021-01-28

两大汇编语法

两种语法主要是针对x86架构汇编而言,对于其他架构没有这么一个说法

  • Intel型语法: Intel官方语法,在Windows平台原生环境中采用该语法
  • AT&T型 :非Intel官方语法,在Unix/Linux的GCC编译环境中采用该语法

原生环境搭建

  • windows系统
  • RedAsm集成工具

指令和语法

在让我们看看 x86_64 平台提供了哪些寄存器给我们使用,

  • 16个通用寄存器,例如,rax,rbx,rcx,rdx等
  • 6个16位段寄存器,例如:cs,ds,等

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

img

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

GCC工具链都包含哪些工具

发表于 2021-01-27

GCC

GCC原名为GNU C语言编译器(GNU C Compiler),原本只能处理C语言。后来随着功能的扩展,支持的语言种类越来越多 ,故更名为GCC(GNU Compiler Collection,GNU编译器套件)

因此 GCC既可以指代C编译器,也可以指代GNU编译套件

其套件囊括了许多子工具:

  • gcc: C 编译器

  • g++: C++ 编译器

  • cpp: C 预处理器

  • as: 汇编器

  • ld: 连接器

  • objcopy: 目标文件翻译器,用于从连接器输出中创建一个ROM 映像

  • objdump: 目标文件阅读器, 用于反汇编目标文件

  • make: make 工具

  • gdb: 源代码调试器

GCC工具链

一个程序从代码编译到机器执行,中间需要经历很多步骤,比如从预编译,编译,到汇编和连接, 这一系列环环相扣过程中涉及到的GNU工具集,称之为GCC工具链

在windows平台中,我们有许多的图形化IDE可以选择,一般编译工具链都集成进了软件内部,无需开发者关心,但是在Linux平台,基本以命令行的方式进行操作,那么对于开发者来说,需要了解每个工具的作用和具体使用方法

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

img

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

汇编语言之GNU ARM

发表于 2021-01-25

什么是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;
}

学习工具

  • 在线ARM汇编编辑器:https://azm.azerialabs.com/

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

img

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

1…252627…48

乱码三千

android程序员一枚,擅长java,kotlin,python,金融投资,欢迎交流~

479 日志
139 标签
RSS
© 2024 乱码三千
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%