当前位置: 首页 > news >正文

汇编系列02-借助操作系统输出Hello World

说明:本节的程序使用的是x86_64指令集的。

汇编语言是可以编译成机器指令的,机器指令是可以直接在CPU上面执行的。我们编写的汇编程序既可以直接在操作系统的帮助下执行,也可以绕过操作系统,直接在硬件上执行。

如果你打算编写的汇编程序在操作系统帮助下执行,那么你的汇编可以调用很多操作系统暴露的系统调用,或者别的语言的函数库,比如C语言的标准库中的函数。但是如果你打算编写绕过操作系统执行的汇编,会比较累,比如往显示器上显示一串字符串这件事情,如果你打算让操作系统帮你执行,你可以在汇编程序中直接调用系统调用(这一节我们打算这么做),来控制硬件(操作系统能帮助我们控制硬件),如果你打算自己去控制硬件,就比较麻烦点(下一节我们打算这么做)。

Hello World汇编文件编写

hello.asm文件的内容(等一会再详细剖析代码):

;hello.asm  交给nasm汇编器汇编的程序,通过英文分号开头表示注释
section .datamsg db "Hello, wolrd",0,0xA     ;db表示define byte,即字节类型,逗号后边的0表示字符串结尾;字符串中的每个字符占用一个字节,0xA是十六进制数,对应的换行符的ASCII码;这样后边输出字符串的时候,就可以还行了
section .bss
section .textglobal main
main:mov rax,1       ;为了后续使用syscall,先将系统调用号1(含义是write)存到寄存器rax中mov rdi,1       ;rdi寄存器通常表示目的地,1表示标准输出mov rsi,msg     ;rsi寄存器通常表示来源地,需要显示的是字符串,存放在rsi中mov rdx,14      ;字符串的长度和末尾的0及换行符的长度。注意,汇编中的数字,如果不加0x前缀或者h后缀,表示的是十进制syscall         ;发起x86_64的系统调用,显示字符串mov rax,60      ;程序的返回结果放在rax寄存器中,60表示退出,如果为负数表示发生了错误mov rdi,0       ;0表示成功退出码syscall         ;退出

hello.asm同一个目录下的makefile文件内容:

#hello.asm的makefile makefile的注释用#开头
hello: hello.o
# no-pie选项是为了关闭pie,从而生成可调试的可执行文件
# pie是position independent executable的意思,表示与地址无关的
# 如果编译器在生成可执行程序的过程中使用了pie,那么当可执行程序被加载到内存中时,其加载地址存在不可预知性gcc -o hello hello.o -no-pie
hello.o: hello.asm
# nasm -f 后边表示的是输出的格式
# elf64表示的是64位可执行和可链接的格式nasm -f elf64 hello.asm

编译、执行

pilaf@pilaf-thinkpad:~/assembly_x64/hello_asm$ pwd
/home/pilaf/assembly_x64/hello_asm
pilaf@pilaf-thinkpad:~/assembly_x64/hello_asm$ ls
hello.asm  makefile
pilaf@pilaf-thinkpad:~/assembly_x64/hello_asm$ make 
nasm -f elf64 hello.asm
gcc -o hello hello.o -no-pie
pilaf@pilaf-thinkpad:~/assembly_x64/hello_asm$ ls
hello  hello.asm  hello.o  makefile
pilaf@pilaf-thinkpad:~/assembly_x64/hello_asm$ ./hello 
Hello, wolrd
pilaf@pilaf-thinkpad:~/assembly_x64/hello_asm$ 

make命令后生成了一个可执行的hello文件,然后执行它,在shell中输出了Hello, world。

反编译可执行文件

下面我们通过gdb看一下hello可执行程序反汇编的结果:
在这里插入图片描述

dissassemble/r main,可以显示出main函数(global main定义)的十六进制指令及其汇编指令关系,最左边的蓝色的一列是二进制可执行程序的内存地址(逻辑地址)。可以看到syscall指令的机器码是0f05。
上边很有意思的是这几行:

   0x0000000000401110 <+0>:	    b8 01 00 00 00	mov    eax,0x10x0000000000401115 <+5>:	    bf 01 00 00 00	mov    edi,0x10x000000000040111a <+10>:	48 be 28 40 40 00 00 00 00 00	movabs rsi,0x4040280x0000000000401124 <+20>:	ba 0e 00 00 00	mov    edx,0xe0x0000000000401129 <+25>:	0f 05	syscall 0x000000000040112b <+27>:	b8 3c 00 00 00	mov    eax,0x3c0x0000000000401130 <+32>:	bf 00 00 00 00	mov    edi,0x0

内存地址后边跟着的尖括号中的"+数字"表示相对第一个指令的内存地址的偏移字节数
第二条指令mov edi,0x1相对于第一条指令mov eax,0x1偏移了5个字节,正好就是b8 01 00 00 00这5个字节所占的内存长度。而mov edi,0x1也占用了5个字节的内存,所以它后便的内存中的指令movabs rsi,0x404028相对于它又增加了5个字节(10-5=5)。

还有我们发现有mov eax的地方,机器码都是b8开头,有mov edi的地方,机器码都是bf开头,后便跟着4个字节,就是mov指令有操作数。这也就是说了mov eax,mov edi在指令中一共才占用1个字节,这1个字节就表示了mov和寄存器两个信息了
我们还可以发现,b8、bf后边跟着的4个字节(32位整数的十六进制表示),和mov指令右操作数好像不一样啊,其实这是因为Intel CPU采用的是小端字节序,对应的还有大端字节序。所谓的大端还是小端,指的是将一个多字节的数存放到内存的时候,这个数字的权重大的字节(比如,十进制的102这个数字的1的权重比2大)放在内存低地址还是权重小的字节放在内存低地址。如果权重低的字节放在内存低地址,就是小端字节序,如果权重大的字节放在内存低地址就是大端字节序
我们的0x01是0x00000001这个四字节数的简写,但是却把01这个字节放在内存低地址,所以它是小端字节序

等等,不知道你有没有发现一个问题,我们hello.asm中用的不是rax么?怎么反编译完了变成了eax了?好多r开头的寄存器怎么都变成e开头的了?这是因为r开头的寄存器是64位的,e开头的是32位的,汇编器发现mov右操作数没有必要用64位寄存器存储,就优化了。不知你是否还记得,8086的几个通用寄存器,ax、bx、cx、dx(它们又可以拆分为两个8位寄存器,如ah、al,h表示high,l表示low),80386开始有eax、ebx、ecx、edx,e表示extend扩展的意思,它们都是32位的寄存器了,到了64位,有了rax、rbx、rcx、rdx这些寄存器。类似的还有ip、eip、rip表示存放下一个指令内存地址的寄存器。

系统调用方式之syscall

好了,让我们再来看看syscall这个汇编指令
书上说,进行系统调用的可以通过下面三种方式:

  1. INT(中断Interrupt,旧的方式,不太推荐用了)
  2. syscall指令(syscall是汇编指令,我们hello.asm中用到了)
  3. 程序库API(如C语言的标准库中的printf就对write系统调用进行了封装)

作为菜鸟的我,看到《x64汇编语言:从新手到AVX专家》中给出的这段汇编,有很多地方不明白的,比如mov rax,1mov rdi,1中的1有什么联系?为什么要往rax中mov,不往别的寄存器mov?
带着这些疑问,我man了一下syscall(Linux shell中执行了man syscall):
在这里插入图片描述

内容很长,我只截了一部分。从中我们可以看到,C头文件中也定义了一个syscall的函数(名字和汇编的syscall指令相同),它是一个小的库函数,它用于调用一些系统调用,这些系统调用(系统调用是操作系统对硬件操作的封装,对硬件操作少不了汇编指令,Linux操作系统的系统调用的实现都是C语言中嵌套着汇编)的汇编接口有特定的号码和参数。syscall函数可以调用其它的系统调用,只需要按照调用约定将系统调用号和参数传递给它即可。
也就是说,汇编提供了一个syscall指令用于调用操作系统的系统调用,只需要将操作系统的系统调用号和参数准备好就可以了。
我们可以通过汇编直接调用syscall指令,也可以用C语言提供的syscall函数(这个函数内部实现肯定是包了汇编指令syscall的)。
并且系统调用号的数字和符号表示关系在<sys/syscall.h>中有定义。

syscall汇编指令的用法

我继续阅读man syscall给出的信息,发现下面这些有用的信息。
表一
在这里插入图片描述

文档直接给出了x86-64syscall指令的使用方法,也就是说调用syscall之前,需要将系统调用号(System call #,英文中经常用井号表示Number的缩写)放到rax寄存器,返回值1放到rax中,返回值2放到rdx中。
还有下面的入参信息。
表二
在这里插入图片描述

说明了x86-64的系统调用的参数1放在rdi寄存器中,参数2放在rsi寄存器中,参数3放在rdx寄存器中,参数4放在r10寄存器中,参数5放在r8寄存器中,参数6放在r9寄存器中

回到我们的hello.asm,我们用了汇编的syscall指令调用了操作系统的系统调用,按照上面截图中说的x86-64使用syscall汇编指令的时候,会将操作系统的系统调用号放到rax寄存器中,我们的程序写的是mov rax,1,也就是说系统调用号是1,我们刚才看到说系统调用号和它的符号表示在<sys/syscall.h>头文件中的,我们在CLion(Jetbrains全家桶中的C语言集成开发环境IDE)中随便打开一个c文件,引入头文件:

在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述

一路追踪下来,终于发现,操作系统调用号为1的系统调用名称是write!废话不多说,直接执行man 2 write看看:
在这里插入图片描述

可以看到Linux操作系统的write系统调用的参数有三个,第一个是文件描述符(Linux中一切皆文件,0表示标准输入,1表示的标准输出文件描述符,2表示错误输出),第二个参数是一个指针,即输入内容起始内存地址,第三个参数表示输入的字节数。
上面表二中说了,Linux操作系统的系统调用的入参,第一个放到rdi寄存器,第二个放到rsi寄存器,第三个放到rdx寄存器等等。结合man 2 write得到的知识,传递给write系统调用的三个参数分别是1,msg的地址,14(Hello, world字符及其结尾0加上换行一共14字节)。
也就是说我们的hello.asm干了这么件事情:通过汇编的syscall指令,调用了Linux系统调用write,让write往标准输出上面输出14个字节,内容是"Hello, world"然后换行。

总结

作为菜鸟的我在运行书中的一段hello world程序的时候,书中很多地方没有写明为什么这样做,笔者通过一点点摸索,终于搞清楚其底层的运行逻辑,顿感欢喜,以此记录,希望能帮到和我一样菜的菜鸟。也希望大家不要浮躁,沉下心来,夯实基础,做一个有工匠精神的码农,也许某一天平时积累的点滴就派上了大用场。加油!


参考资料
1.《x64汇编语言:从新手到AVX专家》
2.《汇编程序设计与计算机体系结构 软件工程师教程》P211

相关文章:

汇编系列02-借助操作系统输出Hello World

说明:本节的程序使用的是x86_64指令集的。 汇编语言是可以编译成机器指令的&#xff0c;机器指令是可以直接在CPU上面执行的。我们编写的汇编程序既可以直接在操作系统的帮助下执行&#xff0c;也可以绕过操作系统&#xff0c;直接在硬件上执行。 如果你打算编写的汇编程序在…...

【2023unity游戏制作-mango的冒险】-前六章API,细节,BUG总结小结

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;unity游戏制作 ⭐mango的冒险前六章总结⭐ 文章目录⭐mango的冒险前六章总结⭐&#x1f468;‍&a…...

进程控制及其操作

进程创建1.1 fork()函数1.2 fork()函数的返回值进程等待2.1 进程等待的必要性1.之前讲过&#xff0c;子进程退出&#xff0c;父进程如果不管不顾&#xff0c;就可能造成‘僵尸进程’的问题&#xff0c;进而造成内存泄漏。 2.另外&#xff0c;进程一旦变成僵尸状态&#xff0c;那…...

Git常用命令复习笔记

1. Git与SVN区别&#xff0c;各自优缺点 Git&#xff1a; 分布式&#xff0c;每个参与开发的人的电脑上都有一个完整的仓库&#xff0c;不担心硬盘出问题&#xff1b;在不联网的情况下&#xff0c;照样可以提交到本地仓库&#xff0c;可以查看以往的所有log&#xff0c;等到有…...

代码随想录算法训练营day49 | 动态规划 123.买卖股票的最佳时机III 188.买卖股票的最佳时机IV

day49123.买卖股票的最佳时机III1.确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组188.买卖股票的最佳时机IV1.确定dp数组以及下标的含义2.确定递推公式4.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组123.买卖股票的最佳时机III …...

【教学典型案例】14.课程推送页面整理-增加定时功能

目录一&#xff1a;背景介绍1、代码可读性差&#xff0c;结构混乱2、逻辑边界不清晰&#xff0c;封装意识缺乏![在这里插入图片描述](https://img-blog.csdnimg.cn/bbfc5f04902541db993944ced6b62793.png)3、展示效果不美观二&#xff1a;案例问题分析以及解决过程1、代码可读性…...

【算法基础】DFS BFS 进阶训练

DFS与BFS的基础篇详见:https://blog.csdn.net/m0_51339444/article/details/129301451?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22129301451%22%2C%22source%22%3A%22m0_51339444%22%7D 一、案例分析1 (树的重心 —— D…...

GO语言中的回调函数

0.前言 回调函数是一种在编程中常见的技术&#xff0c;通常在异步编程中使用。简单来说&#xff0c;回调函数是一个被传递给另一个函数的函数&#xff0c;它在该函数的某个时间点被调用&#xff0c;以完成某些特定的操作或任务。 在Go语言中&#xff0c;可以将函数直接作为参…...

28个案例问题分析---014课程推送页面逻辑整理--vue

一&#xff1a;背景介绍 项目开发过程中&#xff0c;前端出现以下几类问题&#xff1a; 代码结构混乱代码逻辑不清晰页面细节问题 二&#xff1a;问题分析 代码结构混乱问题 <template><top/><div style"position: absolute;top: 10px"><C…...

佛科院单片机原理2——80C51单片机结构

一、程序存储器的入口地址&#xff1a;程序入口地址&#xff1a;0000H外部中断0入口地址&#xff1a;0003H定时器0溢出中断入口地址&#xff1a;000BH外部中断1入口地址&#xff1a;00013H定时器1溢出中断入口地址&#xff1a;001BH串行口中断入口地址&#xff1a;0023H定时器2…...

数据结构与算法_动态顺序表

顺序表是线性表的一种。 线性表是n个具有相同特性的数据元素的有限序列。 逻辑上&#xff0c;它们是线性结构&#xff0c;是一条连续的直线&#xff1b;但是在物理上&#xff0c;它们通常以数组和链式结构存储。 常见的线性表有顺序表、栈、队列、字符串等。 顺序表是用一段…...

逃避浏览器JS检测打开开发者工具

20230304 - 0. 引言 看到一些视频网站之后&#xff0c;想把视频离线下载下来怎么办&#xff1f;直接的方法是通过开发者工具来查看网络流量&#xff0c;一般可以在传输的请求类型中搜索m3u8&#xff0c;然后找到这部分请求&#xff0c;然后利用某些播放器或者下载器直接下载。…...

ceph介绍、原理、架构、算法...个人学习记录

前言 之前公司安排出差支援非结构化项目&#xff0c;采用springcloud(redismysql数据冷热处理)s3escephkafka还涉及一些区块链技术等等…&#xff0c;在与大佬的沟通交流下对ceph产生了兴趣&#xff0c;私下学习记录一下&#xff1b;后续工作之余会采用上面相关技术栈手动实现不…...

Spring MVC源码解析——HandlerMapping(处理器映射器)

Sping MVC 源码解析——HandlerMapping处理器映射器1. 什么是HandlerMapping2. HandlerMapping2.1 HandlerMapping初始化2.2 getHandler解析3. getHandlerInternal()子类实现3.1 AbstractUrlHandlerMapping与AbstractHandlerMethodMapping的区别3.2 AbstractUrlHandlerMapping3…...

【Word/word2007】将标题第1章改成第一章

问题&#xff1a;设置多级列表没有其他格式选的解决办法和带来的插入图注解的问题&#xff0c;将标题第1章改成第一章的问题其他方案。 按照百度搜索的方法设置第一章&#xff0c;可以是没有相应的样式可以选。 那就换到编号选项 设置新的编号值 先选是 然就是变得很丑 这时打开…...

NLP预训练模型

Models Corpus RoBERTa: A Robustly Optimized BERT Pretraining Approach 与BERT主要区别在于&#xff1a; large mini-batches 保持总训练tokens数一致&#xff0c;使用更大的学习率、更大的batch size&#xff0c;adam β20.98\beta_20.98β2​0.98&#xff1b;dynamic ma…...

Typora上传文档图片链接失效的问题+PicGo布置图床在Github

文章目录typora图片链接失效原因PicGO开源图床布置先配置Github2.1先创建新仓库、用于存放图片2.2生成一个token&#xff0c;用picGo访问github3.下载picGo,并进行配置3.1 配置v4.1typora图片链接失效原因 因为你是保存在本地的&#xff0c;因此图片是不能访问&#xff0c;可以…...

win10安装oracle

文件放到最后。我的电脑是win11的&#xff0c;因为老师让写下安装笔记&#xff0c;在11上安装的时候没有截屏&#xff0c;所以在虚拟机上重新安装下吧。室友说要把文件夹放到c盘才能打开。我试了下&#xff0c;具体的是要把Oracle11g文件夹放到c盘根目录下。如果解压后不是这个…...

AQS为什么用双向链表?

首先&#xff0c;在AQS中&#xff0c;等待队列是通过Node类来表示的&#xff0c;每个Node节点包含了等待线程的信息以及等待状态。下面是Node类的部分源码&#xff1a;static final class Node {// 等待状态volatile int waitStatus;// 前驱节点volatile Node prev;// 后继节点…...

AtCoder Beginner Contest 292——A-E题讲解

蒟蒻来讲题&#xff0c;还望大家喜。若哪有问题&#xff0c;大家尽可提&#xff01; Hello, 大家好哇&#xff01;本初中生蒟蒻讲解一下AtCoder Beginner Contest 292这场比赛的A-E题&#xff01; A题 原题 Problem Statement You are given a string SSS consisting of lo…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)

笔记整理&#xff1a;刘治强&#xff0c;浙江大学硕士生&#xff0c;研究方向为知识图谱表示学习&#xff0c;大语言模型 论文链接&#xff1a;http://arxiv.org/abs/2407.16127 发表会议&#xff1a;ISWC 2024 1. 动机 传统的知识图谱补全&#xff08;KGC&#xff09;模型通过…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

前端中slice和splic的区别

1. slice slice 用于从数组中提取一部分元素&#xff0c;返回一个新的数组。 特点&#xff1a; 不修改原数组&#xff1a;slice 不会改变原数组&#xff0c;而是返回一个新的数组。提取数组的部分&#xff1a;slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...

【UE5 C++】通过文件对话框获取选择文件的路径

目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 &#xff0c;这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器&#xff0c;右键点击 .uproject 文件&#xff0c;选择 "Generate Visual Studio project files"&#xff0c;重…...