macOS 汇编指南

现在很多汇编的学习资料、途径和工具都是关于 Windows 下的,所以这里来介绍一下 macOS 上学习使用汇编需要的资料和工具。

本文持续更新中,也是作为个人笔记来使用的。

为什么需要学习汇编(使用途径)

汇编是计算机的“魔法”,虽然做个只会高级语言的“战士”也可以,但是当给“武器”附魔之后,战斗力也会大大增加(当然也有“玩火自焚”的)。

在现代,学习汇编之后的使用途径有几种:

  • 直接用汇编指令写程序的代码,然后使用汇编器(Assembler)汇编成程序(这种学习的过程中可能使用比较多,在实际情况下很少用,因为太复杂了)。
  • 用在 C 语言代码中,提高性能和速度,或者实现一些特别的功能。例如 UNIX 和 Linux 中的汇编代码就是为了提高运行速度,不然完全可以用纯 C 语言写出来;还有苹果不让开发者 iOS 平台获取 CPU 频率之后,一些开发者通过在 Objective-C 代码中使用汇编指令来推测频率。

注1:不过苹果后来加了转换,让推测的结果很不准,让这种 app 彻底完犊子了。
注2:Objective-C 是 C 语言的一个超集,就像 C++ 也是 C 语言的一个超集,它们都属于 C 语言家族。在苹果推出 Swift 之前,苹果平台的开发全靠 Objective-C。

  • 能看懂反编译出来的内容(这种一般常规程序员用不到)。

不过在现在,汇编并不是编程的门槛,而是通往高手的必修课。但是新手还是建议先学会 C 语言再接触汇编(这里的学会是指能看明白各种结构即可)。

相关资料推荐

在现在各种高级语言、脚本语言遍地开花的今天,汇编语言越来越不受欢迎,因为繁琐、复杂,学习成本极高。例如, Intel CPU 开发手册就有 5000 页(每次发新的 CPU 之后都会更新,或增或减)。

第一个推荐的是苹果官方的汇编指南:《Mac OS X Assembler Guide》,下载地址在这里
这本指南包含了 Mac OS X 汇编器as的使用,汇编指令和地址模式的介绍等内容。
需要注意的是,它不光包括了 Intel CPU 的汇编内容,还包括更早期的 Power PC 的汇编内容。
但是由于该指南最新版本是 2005 年的,Intel 的指令有了巨大的更新,例如 AVX 等。并且苹果也已经在转向自己的 M1 芯片了(有意思的是,OS X 的汇编器手册最新更新时间是 2020 年6月23日,而这就是 M1 更新的日子,不知道苹果会不会在稳定之后再次更新)。

第二个推荐的是 Intel 的 《Introduction to x64 Assembly》,这是一篇汇编快速入门。新手可以看看,干货浓度极高。

第三个是:Intel 的开发手册。
这个手册虽然很长,但是一个很不错的资料,而且更新频率很高,如果需要了解新的 Intel 指令,那么这就是必须要看的了。所以这篇属于经常需要翻阅的。当然如果想见识见识也可以看看。链接是:Intel 64 和 IA-32 处理器相关的文档
可以直接下载合订本当作存档,不过这样不方便阅读。(关于这个,曾经 Intel 可以免费提供纸质版,只要你发邮件提供地址即可,有不少人定,不过大多都垫了显示器或者吃灰了。现在 Intel 不提供这个福利了,挺可惜的)
第一卷是一些大致的介绍,以及 Intel CPU 发展历程和区别,如果是初学者可以瞅瞅;第二卷开始就是指令的介绍了。第2卷的日常使用率比较高,需要经常查阅。具体区别在之前的链接中的界面可以看到。

这里需要注意的是,虽然常说 Intel 挤牙膏,但是 Intel 几乎每一代都会有指令、寄存器的更新和更改。所以文档的更新频率比较高,如果你需要使用最新的指令,那么请及时更新自己存储的文档。

第四个是一组关于 ARM 汇编的文档。
首先因为 Mac 现在以及转向自研的 M1 芯片了,M1 芯片是 ARM 架构的。
其次因为 ARM 现在并不像 Intel 一样提供了汇编指南《ARM ® DeveloperSuite Assembler Guide
Version 1.2》
(这篇指南最新的b版本是2001年的),而是分散开了。如果想入门,阅读这本2001年的指南即可。
如果想深入了解最新的指令,那么查看这里https://developer.arm.com/architectures/instruction-sets

汇编器以及实用工具介绍

汇编器(Assembler)和编译器(Complier)是不同的。如果搞不明白的话,可以简单理解成汇编器是将汇编指令转换成程序的;编译器是将高级语言的代码(例如 C/C++、Java 等的代码)转换成程序的。

这里介绍一下汇编器以及可能用得到的实用工具们(包括编辑器)。

as:

Mac OS X 的汇编器,需要在“终端”中使用,类似于 Windows 的 masm。支持 Intel 处理器的汇编以及 Power PC 处理器的汇编。放在/usr/bin/as目录下。在《Mac OS X Assembler Guide》中有使用方法的介绍。

ld:

连接器。

clang:

clang 有个状态就是汇编器,而且可以 C 语言等代码预处理成汇编语言代码,详细操作请看这里《clang 如何产生汇编代码文件》。使用 clang 可能对你更方便,不过需要自己判断了。

gcc:

gcc 和 clang具体区别这里不赘述,gcc 有个选项:-nostartfiles,使用这个命令可以直接忽略被链接的标准库文件和初始化行为。这是因为有时候会出现不需要连接 C 标准库,直接汇编即可,那么就需要这样。最后会有相关演示。

size:

输出对象文件的各部分大小,如下:

请添加图片描述

otool:

查看某部分大小和内容,类似 Windows 中的 debug.exe。例如查看__TEXT,__text部分的内容:
请添加图片描述
我们也可以使用otool来查看其他部分的内容,用以下命令格式:

$ otool -v -s __TEXT __cstring a.out
a.out:
Contents of (__TEXT,__cstring) section
0000000000000025  hello world!\n

clang 或 gcc:

Mac OS X 上,默认的 C、C++、Objective-C 编译器。如果在“终端”里使用命令cc,那么会调用 clang,而不是 gcc。

Xcode:

苹果自己的 IDE,有图形界面,也有一些终端命令行工具,可以编写 C、C++、Objective-C 和 Swift 语言的程序。可以利用 Xcode 来编写含有汇编代码的程序和 App。
请添加图片描述

语法区别

很多人可能对汇编有所了解,但可能都是基于 Windows 的。但是在 Mac OS X 或者 C 语言(这里 C 语言编译器是 clang)内嵌汇编语言的语法中是不一样的。前者是 Intel 语法,后者是 AT&T 语法。这里来说一下一些重要的不同之处:

1. 寄存器写法

事先声明一下,如果不是用as汇编,而是在 C 语言中或者其他情况下使用 clang 或者 gcc 来处理,那么不用管这条内容。
在 Windows 和 C 的汇编语法中,寄存器的名称是直接写的,例如mov ax,2。但是在as中,为了不与标识符(identifier,其实就是常说的变量)搞混,需要在寄存器前加上百分号%,例如mov %ax,2。并且所有的寄存器都得写成小写字母,不能像 Windows 或 C 里一样不分大小写。

十六进制写法

在 Windows 中,十六进制被写成以Hh结尾的,例如5c0dH。但是在 Mac OS X 中,需要写成0x开头的,例如0x1234。而在 C 语言中,二者皆可。

来用汇编写一个 Hello World 吧!

写代码

最后会给出完整代码,现在先分步讲解。

首先新建一个名为helloworld.s的文件,汇编代码文件的后缀一般是.asm或者.s。这时候就可以来写第一部分的内容啦。
先是需要指明第一部分是——可执行命令

.section	__TEXT,__text,regular,pure_instructions

然后指明创建的目标平台:

.build_version macos, 12, 0	sdk_version 12, 1

然后新建一个外部符号名称_main用作程序的入口,就像 C 语言程序必须要有main()函数一样。注意这里的下划线不可以省略。

.globl	_main                           ## -- Begin function main

然后使用对齐(align)命令将位置计数器移到下一个边界4, 0x90(一般来说都是地址)。

.p2align	4, 0x90

然后我们就可以开始写_main部分包含的内容啦,也就是开始写main()函数了:

_main:                                  ## @main
	.cfi_startproc					 ##表示函数的开头。会初始化一些内部的数据结构,发出架构依赖的初始CFI 指令
## %bb.0:
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	subq	$16, %rsp
	movl	$0, -4(%rbp)
	leaq	L_.str(%rip), %rdi
	movb	$0, %al
	callq	_printf
	xorl	%eax, %eax
	addq	$16, %rsp
	popq	%rbp
	retq
	.cfi_endproc				##结束函数

这部分包含一些 cfi 相关的指令,可以在这里看到:https://web.mit.edu/rhel-doc/3/rhel-as-en-3/cfi-directives.html
此外,CFA是规范框架地址(Canonical Frame Address),相关资料可以看这里:
https://www.keil.com/support/man/docs/armasm/armasm_dom1361290010153.htm

接下来开始第二部分——可执行命令包含的字符串

.section	__TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
	.asciz	"hello world!\n"

这里就是准备输出的字符串:hello world!\n了。
如果这里使用的是.ascii命令,那么这行应该改成.asciz "hello world!\n\0"
因为.asciz自动补上了字符串末尾的\0,如果被用于 C 程序的话,就使用这个。

最后我们可以加上这么一行:

.subsections_via_symbols

这行命令会告诉静态连接编辑器,这部分对象文件(object file)能被分割成几块。不过这里用不用无所谓,不影响结果。但是出于严谨可以加上。

完整代码如下:

	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 12, 0	sdk_version 12, 1
	.globl	_main                           ## -- Begin function main
	.p2align	4, 0x90
_main:                                  ## @main
    .cfi_startproc                     ## 表示函数的开头。会初始化一些内部的数据结构,发出架构依赖的初始CFI 指令
## %bb.0:
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    subq    $16, %rsp
    movl    $0, -4(%rbp)
    leaq    L_.str(%rip), %rdi
    movb    $0, %al
    callq    _printf
    xorl    %eax, %eax
    addq    $16, %rsp
    popq    %rbp
    retq
    .cfi_endproc                ## 结束函数
	.section	__TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
	.asciz	"hello world!\n"

.subsections_via_symbols

汇编成可执行文件

在 Windows 中,现在主流的可执行文件的格式是 PE(Portable Executable File Format),而其中的“Executable”缩写就是很多人熟知的 EXE。在 macOS 中,可执行文件被称为Mach-O executable file
如果使用file命令来查看一个可执行文件的话就可以看到,这里我们查看 Xcode:

# 可执行文件
$ file /Applications/Xcode.app/Contents/MacOS/Xcode 
Xcode: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64]
Xcode (for architecture x86_64):	Mach-O 64-bit executable x86_64
Xcode (for architecture arm64):	Mach-O 64-bit executable arm64

所以最终目标是要把hello.s这个汇编文件,“变”成一个Mach-O executable file

这里有两种方法:

方法 1

由于这个程序十分简单,也没有链接特殊的库,可以直接使用以下命令来:

$ gcc -nostartfiles helloworld.s
$ chmod 755 a.out
$ file a.out 
a.out: Mach-O 64-bit executable x86_64

这时候可以看到,a.out便是需要的Mach-O executable file
运行一下看看:

$ ./a.out 
hello world!

非常不错。

方法 2

这种方法是常规做法,首先使用as来汇编代码,并且修改权限:

$ as hello.s
$ chmod 755 a.out 

这里需要注意一点,as生成的是 Mach-O 对象文件(Mach-O object file),而不是Mach-O executable file,是不能直接运行的,如果直接运行会提示cannot execute binary file。在 Windows 中,对象文件被称为 COFF(Common Object File Format)。

这时候需要使用连接器ld来处理对象文件。但是,如果简单的使用ld 会出现以下情况:

$ ld a.out
Undefined symbols for architecture x86_64:
  "_printf", referenced from:
      _main in a.out
ld: symbol(s) not found for architecture x86_64

这是因为使用了 C 标准库的_printf,但是ld默认搜不到。所以加上库地址就可以,如下:

$ ld a.out -o hello -lSystem -L/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib

特别注意!一般Mach-O executable file是不加任何后缀的。
这时候得到的hello便是需要的Mach-O executable file。使用file命令来检查一下:

$ file hello
hello: Mach-O 64-bit executable x86_64

然后运行一下看看:

$ ./hello
hello world!

运行完美~

结尾

本文还会时不时更新一下,修改一些问题,添加一些内容。

总之,希望能帮到有需要的人~

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值