nasm手写汇编指南

一般来说编译汇编代码(即汇编助记符)使用最多的工具为gas(即GNU as)和nasm,但用gas编译汇编代码给我留下了不太美好的回忆,所以这里只记录用nasm编译手写的汇编代码并运行的流程。

预备知识

我们从手写的汇编代码到最后实际运行,不仅需要把汇编代码编译为机器码,还需要转化为可执行文件的格式,文件格式默认为elf。elf可执行文件的程序入口点(ENTRY)一般是符号_start ,也就是由elf头中的e_entry域决定的。使用_start作为elf文件的入口点只是约定俗称,我们可以自己写linker的链接脚本来决定使用什么符号作为入口点。默认的链接脚本可以通过ld来查看:

$ ld --verbose
...
/* Script for -z combreloc -z separate-code */  
/* Copyright (C) 2014-2023 Free Software Foundation, Inc.  
  Copying and distribution of this script, with or without modification,  
  are permitted in any medium without royalty provided the copyright  
  notice and this notice are preserved.  */  
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",  
             "elf64-x86-64")  
OUTPUT_ARCH(i386:x86-64)  
ENTRY(_start)  
SEARCH_DIR("/usr/x86_64-pc-linux-gnu/lib64"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/local/lib"); SEARCH_DIR("/usr/x86_64-pc  
-linux-gnu/lib");  
SECTIONS  
{  
 PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF  
_HEADERS;
...

可以看到,ENTRY(_start)决定了elf入口点使用的符号。我们实际写汇编代码时不一定要使用_start符号作为函数的标签名,虽然链接成可执行文件时linker会告诉你无法找到符号_start就是了,但这并不会影响程序的运行,没有_start符号时程序默认使用0x401000即.text段的开头作为入口点。

nasm汇编

本文不会提及关于x86汇编的知识,这里我们只指出关于使用nasm汇编的一些必要知识。

首先是注释,nasm使用;作为注释的开头,一些汇编器或者语法也会使用#作为注释的标记。其次是代码段的定义,我们常用的有.text.data.bss段等,这些代码段与程序实际运行时一致,例如.text段的读写权限为r-x,在内存中存放实际运行的机器码。最后是常量的定义,多说无益,直接上一段容易理解的实例:

; Sections:  
section .data  
  hello: db "Hello !", 10  
  len_of_hello equ $−hello
  
section .bss  
  input: resb 16
  
section .text  
  global _start
; Functions

_start:  
  mov rax, 60  
  mov rdi, 0  
  syscall

section用于定义程序的.data.text段,globalextern相对立,根据nasmdoc所描述的:

The GLOBAL directive applying to a symbol must appear before the definition of the symbol.

即,在写汇编函数前需要先用global声明,但这也不影响最后linker链接生成的elf文件的执行(linker同样会告诉你无法找到符号_start),只要不作死把section .text这段声明去掉就行。

.data段中,nasm使用db来定义数据,db的使用比较灵活,既可以定义多个字节也可以定义字符串,nasmdoc没有去描述db的细节(或者说只是我没找到)。与db作用类似的命令还有dwdd等,秉着多一事不如少一事的原则,db已经足够应对大部分场景,下面是nasmdoc中的几个例子:

db 0x55 ; just the byte 0x55 
db 0x55,0x56,0x57 ; three bytes in succession 
db ’a’,0x55 ; character constants are OK 
db ’hello’,13,10,’$’ ; so are string constants

equ也可以用于定义数据,关于dbequ的区别,这篇文章给了一个很好的解释,equ对应C中的宏定义,db对应C中的变量声明。除此之后值得注意的一个点是$符号的使用,nasmdoc同样给出了说明:

NASM supports two special tokens in expressions, allowing calculations to involve the current assembly position: the and \$ tokens. $ evaluates to the assembly position at the beginning of the line containing the expression; so you can code an infinite loop using JMP $. $$ evaluates to the beginning of the current section; so you can tell how far into the section you are by using ($−$$).

$用于标识当前这行汇编指令的起始位置,$$用于标识当前程序段的起始位置,$-hello表示当前位置减去hello符号的位置,即字符串的长度。
而在.bss段中,nasm使用resbreswresq定义未初始化的数据,nasmdoc同样也举了例子:

buffer:     resb 64   ; reserve 64 bytes
wordvar:    resw 1    ; reserve a word
realarray:  resq 10   ; array of ten reals

nasmdoc同样只有举例没有说明,不过这不算很重要,目前够用就行。

编译与链接

将刚刚的汇编代码保存为名为test.s的文本文件,我们只需要两行命令就能生成elf可执行文件:

nasm -f elf64 test.s # f option refers to format of output
ld test.o -o test    # use linker `ld` to generate executable

如此,我们生成了一个.o目标文件和格式为64为elf的可执行文件test,这时可能就有人要问了,你这手写汇编也没有调libc和外部函数,为什么非要链接一遍?答案解释起来比较麻烦,我的建议是参考这个回答,简单来说C相当于就是高级汇编,编译C要过一遍的事情用汇编写同样要过一遍,所以链接在大部分场景也是必须的。那如果一定要用一行命令把汇编代码编译成可执行文件,我只能推荐gcc给出的解决方案,但是写汇编的话就需要按照as的方式来写:

gcc -nostdlib -static -no-pie start.s -o static_executable

以上就是用nasm手写汇编的全部内容,更多的细节这里不会补充,有兴趣可以跑一下以下程序:

section .data  
  hello: db "Hello Assembly!", 10  
  len_of_hello equ $-hello
  non_exist: db "I am void"
  
section .text  
  global _start

_start:  
  mov eax, 2
  mov rdi, non_exist
  xor esi, esi
  xor edx, edx
  syscall
  mov eax, 1
  mov rdi, 1
  mov rsi, hello
  mov rdx, len_of_hello
  syscall

References

  1. https://stackoverflow.com/questions/4272316/in-an-elf-file-how-does-the-address-for-start-get-detemined
  2. https://stackoverflow.com/questions/70009183/if-an-object-file-defines-start-and-doesnt-use-any-libraries-why-do-i-still-n
  3. How programs get run - part one
  4. How programs get run - part two
  5. The Basics Of Writing Assembly
  6. What's the difference between equ and db in NASM?
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容