macOS环境汇编语言教程(二):从Hello world 开始

高级语言教程从Hello world程序开始是惯例,但汇编语言不太一样,Hello world程序也需要更多知识才能写出,通常在整个教程的三分之一之后才会讲。这个教程我会把Hello world程序放在比较靠前的位置,作为第二个汇编程序。第一个程序在上一篇教程出现,运行后没有任何输出。我们现在回顾一下:

global _main

_main:
    mov rax, 0
    ret

我会先讲一些预备知识,再解释这段程序,再之后开始我们的Hello world程序。

预备知识——C和汇编程序的产生过程

生成过程

一个C语言源程序文件,需要经过两步才能转换成可执行文件。

  1. 使用C编译器对源文件进行编译,生成“目标文件”
  2. 使用“链接器”对一个或多个目标文件进行链接,生成可执行文件

汇编语言同样是两个步骤:

  1. 使用汇编器对源文件进行汇编,生成“目标文件”
  2. 使用“链接器”对一个或多个目标文件进行链接,生成可执行文件

除了第一步两处斜体字不同外,都是一样的。其实过程是相同的,只是术语不同,“编译”是针对高级语言的,所用的工具是“编译器”,“汇编”是针对汇编语言的,使用的工具是“汇编器”,我们使用的nasm是一种汇编器。事实上,你按高级语言的叫法把“汇编”和“汇编器”叫做“编译”和“编译器”也没什么不妥,大家也能听得懂。

C程序生成

我们开始实战C语言的程序生成,我们有C语言的Hello world程hello.c,如下

#include <stdio.h>
int main() {
    printf("Hello world\n");
    return 0;
}

第一步编译:
在终端输入命令

gcc -c hello.c

便会在当前目录下生成目标文件hello.o

第二步:链接
我们接着输入命令

gcc hello.o

便会在当前目录下生成可执行文件a.out
输入

./a.out

就可以看到显示出了Hello world,默认的可执行文件名a.out有点奇怪,我们可加上-o参数,指定生成的文件名

gcc hello.o -o hello

就生成了名为hello的可执行文件。

我们的编译器和链接器都是gcc,这种简单的程序通常我们把两步合成一步

gcc hello.c -o hello

这样会生成可执行文件hello,不会生成目标文件hello.o

汇编程序生成

之前写好的hello.s文件
第一步:汇编

nasm -f macho64 test.s

这个命令会生成目标文件test.o
-f macho64表示生成macOS平台x86_64格式的目标文件。如果你用-f macho会生成32位的目标文件,虽然可以完成汇编,但你在链接的时候会出错,因为macOS只支持64位程序, 无法链接32位的目标文件,这也是系统自带的nasm汇编器不能用的原因。

第二步:链接

gcc test.o -o test

这就会生成可执行文件test。

这里的工具仍是gcc,命令看起来也一样,看起和C语言的链接过程什么区别。是的,没有区别,我们用的是一个工具。
gcc在对目标文件链接的时候,并不知道目标文件是什么语言生成的,可能是汇编,也可能是C、Go、D等高级语言。通过这种方式可以很容易混合汇编和高级语言编程。后边的教程就会有汇编和C一起工作的例子。

执行程序

我们的test程序执行后虽然看不到输出,但是这个程序是有返回值的。
我们执行

./test

后,接着输入命令

echo $?

我们看到显示0,$?表示上一条命令的返回值,在类Unix系统,程序返回0表示成功,1到255表示程序失败。
你也可两条命令写在一行

./test ; echo $?

分号是命令分隔符。

第一个汇编程序解释

global _main 表示程序入口是_main: 处,你也可以把_main修改成别的名字,改后程序链接的时候要指明入口,要麻烦一些,最好不要改。

mov rax, 0 表示把0放入rax寄存器,寄存器你可以简单理解为在CPU内部的超高速内存。CPU有多个寄存器,rax是一个寄存器的名称,下一篇教程会讲。

ret表示返回

你可以试着把程序里的0改成1或者260,重新运行一下,并查看$?的值,看看是什么结果。

回顾一下我们学过的两条指令

mov 指令,把数据放入寄存器中
ret 指令,返回

Hello world程序

开始汇编语言的Hello world之前,先写一个C语言的Hello world,之后再转化成汇编,hello2.c如下:

#include <unistd.h>
int main() {
    char *msg = "Hello world\n";   // 定义要输出的文字msg
    write(1, msg, 12);             // 输出msg,12为msg的长度
    _exit(0);                      // 调用_exit函数返回
}

等等,之前不是写过了吗,为啥又写一个?之前的hello.c转化成汇编有点麻烦,所以要另写一个。
写一个等价的汇编程序hello1.s

msg: db "Hello World", 0x0a

global _main

_main:
    mov rax, 0x2000004
    mov rdi, 1
    mov rsi, msg
    mov rdx, 12
    syscall
    ret

    mov rax, 0x2000001
    mov rdi, 0
    syscall
    ret

汇编并链接

nasm -f macho64 hello1.s && gcc hello1.o -o hello1

&&的作用是连接多条命令,但某一条命令失败(返回值不为0),就不再执行后面的命令。和之前提到的分号(;)不同,分号不管成功与否都会依次执行命令。

会出现警告:

ld: warning: PIE disabled. Absolute addressing (perhaps -mdynamic-no-pic) not allowed in code signed PIE, but used in _main from hello1.o. To fix this warning, don't compile with -mdynamic-no-pic or link with -Wl,-no_pie

先不管,接着运行./hello1可以看到能正常输出Hello world。
程序的解释我先以注释形式放在程序内,汇编的注释以分号开头,到行末结束。

; 定义要输出的文字msg,db是data byte的意思
; 0x0a表示换行符,0x前缀表示十六进制,
; 也可以用h后缀表示十六进制,比如41h,0ch,以a~f开头的十六进制前面一定要加0
msg: db "Hello World", 0x0a

global _main

_main:
    ; 要调用的write函数,放入寄存器rax
    mov rax, 0x2000004
    mov rdi, 1     ; 第1个参数1,放入寄存器rdi
    mov rsi, msg   ; 第2个参数msg,放入寄存器rsi,此行链接时会报警告
    mov rdx, 12    ; 第3个参数12,放入寄存器rdx
    syscall        ; 调用rax寄存器中的函数
    ret            ; 函数调用返回

    ; 要调用的_exit函数,放入寄存器rax    
    mov rax, 0x2000001
    mov rdi, 0     ; 第1个参数0,放入寄存器rdi
    syscall        ; 调用rax寄存器中的函数
    ret            ; 函数调用返回

从注释中可以看到
要调用的函数需要放入寄存器rax中,参数要依次放入寄存器rdirsirdx中。

我们修复一下警告,并重构一下代码
警告是由指令mov rsi, msg引起的。
这条指令的意思是把msg的地址放到寄存器rsi中,而链接器认为你使用了绝对地址,不能直接使用msg的地址。
我们用lea rsi [rel msg]替换刚才的语句就可以了。

12是Hello World字符串的长度,改了字符串还要改这个值,可以自动计算字符串的长度

两段函数调用处都有,syscallret语句,这两句是调用系统内核,可以提取一个公用的代码段

修复了这三个问题的程序hello2.s如下:

SECTION .data           ; 数据代码段

msg: db "Hello World", 0x0a
len: equ $-msg          ; 计算msg的长度,赋值给len

SECTION .text           ; 程序代码段

global _main

kernal:
    syscall
    ret

_main:
    mov rax, 0x2000004
    mov rdi, 1
    lea rsi, [rel msg]
    mov rdx, len        ; 把len的值作为参数传入
    call kernal         ; 调用kernal处的代码

    mov rax, 0x2000001
    mov rdi, 0
    call kernal

这段hello2.s程序修复了上面所说的3个问题,并添加了SECTION .dataSECTION .text代码段说明,使程序看起来更明了,.data.text代码段的名称是汇编器定义的,不能更改。

好了,运行正常,格式规范的Hello world程序1.0版就完成了。汇编语言的Hello world这么复杂!你一定有些不明白的地方吧,比如leamov指令有什么区别,rax中存放的数据是什么意思?随着教程的继续,这些问题会逐渐明朗起来的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容