• 哪个编程语言实现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语句的作用,首先我们要先初步大概了解一下程序的执行原理,如下,

    4972d19e0552f9b00c0f2a9a55a304a8.png

    ·编译器在编译汇编代码的时候,会按照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指令和中断向量表的关系如下,

    2aa1d7bcec15f0d55cd2ecba67d3dd33.png用户进程想要调用系统服务(例如输出到屏幕、打开文件),需要统一通过向第128个中断向量,根据既定的数据结构发送系统调用服务请求

    ·应用程序是不能随意访问内核空间,需要通过既定的规则来进行访问,所以设计出了中断向量表(这样设计有安全意义);

    ·因为中断向量表有“指示牌”的方向意义,所以叫做“向量”表;

    综上所述,在终端(一般是屏幕)打印hello world,实质上是一次调用系统服务的过程。

    linux系统调用(sys_write)

    上文中提到,想要调用系统服务,需要按照这个服务的既定数据结构,然后组织这些数据,向第128号的中断向量发送服务调用请求,如下图,

    1924508e86a561269a1bbc50cc73e162.png把需要打印的内容组织好,通过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)函数,原理不再赘述,具体可参见上述提供的代码。

  • 相关阅读:
    mybatis
    List接口(集合)
    如何使用Excel进行设备管理:巡检、维修、保养、备件管理
    美团基于 Flink 的实时数仓平台建设新进展
    .Net 8与硬件设备能碰撞出怎么样的火花(使用ImageSharp和Protobuf协议通过HidApi与设备通讯)
    【前端知识点】深浅拷贝
    Kube-OVN子网
    LFS学习系列3 — 前言
    普中51单片机学习(EEPROM)
    Java中级面试题及答案(120道Java中级面试题大汇总)
  • 原文地址:https://blog.csdn.net/danpianji777/article/details/125453674