各种编程语言的hello world「运行hello world程序」

互联网 2023-02-19 12:39:09

今天给大家普及一下各种编程语言的hello world「运行hello world程序」相关知识,最近很多在问各种编程语言的hello world「运行hello world程序」,希望能帮助到您。

哪个编程语言实现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)函数,原理不再赘述,具体可参见上述提供的代码。