哪个编程语言实现hello world最烦琐?
说明:
·由于汇编是一种直接面向底层的语言,所以最简单的程序也会涉及到许多底层的细节从而显得晦涩(不像C直接一个printf搞定);
·本篇文章通过最简单的hello world程序,理解寄存器、内存、节、指令、系统调用,在程序的简单运作原理;
Talk is cheap, show me your code!
;源代码文件名:test.asm
;执行文件名:test
;编译方法:
;nasm -f elf64 -g -F dwarf test.asm -l test.lst
;gcc -o test test.o -no-pie
section .data;在.data节写入数据
msg: db "hello world",10;10对应的ascii是换行符
msgLen equ $-msg;equ是伪指令,这行代码的意思是msgLen指代着msg字符串的占位长度(字节)
section .text;在.text写入代码
global main;程序的代码入口标签为main(使程序执行的时候能够找到第一个指令)
main:;这个标签实质指代了.text节的第一个指令的内存地址
;屏幕打印
mov rax, 1;sys_write(x86-64)
mov rdi, 1;1是标准输出
mov rsi, msg
mov rdx, msgLen;字符串长度,不包括0
syscall;64位的int 0x80指令
;退出程序
mov rax, 60;exit(x86-64)
mov rdi, 0;参数0,与上条指令结合是exit(0)
section语句
要了解section语句的作用,首先我们要先初步大概了解一下程序的执行原理,如下,
·编译器在编译汇编代码的时候,会按照Linux的ELF(linux的可执行文件格式)进行编译(例如插入ELF header、program header、section header、got等);
·程序运行的时候,则把代码和一些已经初始化的数据装载按照格式装载到内存(分布到各个section);
·stack节,在程序运行的过程中会根据程序逻辑压入或者弹出数据(stack节一般情况下不存储代码);
·heap节,用于程序的最自由的数据存储的区域(例如当C的malloc函数申请内存时使用的就是这个区域);
·bss节,用于存储定义了但是没有初始化的数据(例如C的int i、char ch[10]),常用于程序的数据接收缓冲区;
·data节,用于存储已经定义了并且已经初始化的数据(类似于C的常量);
·text节,用于存储代码(函数的代码也是存储在这个区域,而不是存储在stack区域);
综上所述,section语句的作用是区分汇编代码的区域。
syscall和中断向量表
在介绍syscall指令之前,需要先介绍linux的几个关键的概念,如下,
·用户空间(用户空间的本质是指定的内存空间。这些空间用于运行用户的程序,例如nginx、apache);
·内核空间(内核空间的本质也是内存空间,内核空间用于运行操作系统的代码,用户空间的应用程序原则上不能访问内核空间,又或者说不能直接访问内核空间,于是引入下面的概念——中断向量表);
·中断向量表(中断向量表的本质是一个数组,数组的元素是内存地址,这些地址指向内核空间)
syscall指令和中断向量表的关系如下,
用户进程想要调用系统服务(例如输出到屏幕、打开文件),需要统一通过向第128个中断向量,根据既定的数据结构发送系统调用服务请求
·应用程序是不能随意访问内核空间,需要通过既定的规则来进行访问,所以设计出了中断向量表(这样设计有安全意义);
·因为中断向量表有“指示牌”的方向意义,所以叫做“向量”表;
综上所述,在终端(一般是屏幕)打印hello world,实质上是一次调用系统服务的过程。
linux系统调用(sys_write)
上文中提到,想要调用系统服务,需要按照这个服务的既定数据结构,然后组织这些数据,向第128号的中断向量发送服务调用请求,如下图,
把需要打印的内容组织好,通过syscall指令向第128号中断发送“标准输出”的服务调用请求
·寄存器rax的内容要设置成1。在系统调用的过程中,这个寄存器一般都是用于存储系统服务的调用编号。而打印的服务编号是“1”(要查看系统调用服务编号可查看文件/usr/include/x86_64-linux-gnu/asm/unistd_64.h)
·寄存器rdi(destination index),用于指定打印输出的文件描述符(屏幕的文件描述符是1);
·寄存器rsi(source index),用于指定输出内容的地址(字符串的存储地址);
·寄存器rdx,用于指定输出内容的长度(这个可以自定义,你想打印多长自己决定;上一篇文章提过,字符串在汇编的角度来看,只不过是连续的内存存储空间)
综上所述,通俗地描述打印hello world的过程(系统服务调用过程),如下,
·rax寄存器告诉操作系统,需要调用什么系统服务(1号服务是“标准输出”服务);
·rdi寄存器告诉操作系统,要在那里打印(一般打印在终端,也就是屏幕);
·rsi寄存器告诉操作系统,需要打印的内容在哪里可以找到;
·rdx寄存器告诉操作系统,需要打印的内容有多长(字节);
最后,打印服务调用完毕后,需要结束程序,相当于C的exit(0)函数,原理不再赘述,具体可参见上述提供的代码。
热门跟贴