机器语言编写helloworld

kvmtool下载编译

git clone https://github.com/kvmtool/kvmtool.git 下载后进入到目录执行make即可。

补码

计算机怎么表示负数?以四位有符号数为例,使用高位作为符号位,最高位为0表示正数,为1表示负数,其余三位用来表示值。在计算机中,我们将这种表示方式称为原码。例如:

十进制 二进制 十进制 二进制
2 0010 -2 1010

使用原码表示时,数字0有两种编码:0000和1000。因此,如果使用原码,在设计系统时就需要额外的电路区分+0和-0更糟糕的是如果一个数的正数相加应为0,2 - 2为例:2 - 2 = 2 + (-2) = 0010 + 1010 = 1100 = -4 这明显不对,也就是说,原码不能正确计算减法。怎么办呢?聪明的人类发现使用补码表示数字就可以加减法了。

即:正数的反码和补码都是原码,负数的反码是除符号位以外按位取反,补码是在反码基础上+1。使用补码重新计算 2 - 2:2 + (-2) = 0010 + 1110 = 0000

ASCII码

计算机用高低电平分别表示0和1,所有的数据在存储和运算时都要使用二进制表示,如果我们想用数字表示文本,就要对每一个文本进行编码,目的就是文本转换成数值。具体用哪些二进制是数字表示哪个符号,美国国家标准协会制定了美国信息交换标准代码(American Standard Code for Information Interchange)简称ASCII。ASCII使用8位编码,最多可以表示256个字符,hello world使用的编码就是为:68 65 6C 6C 6F 77 6F 72 6C 64

写外设指令

操作码 指令 描述
EE out DX,AL 将寄存器AL中的字节输出到寄存器DX中的I/O端口地址

第一列为操作码,第二列为汇编语法描述的指令,第三列是指令意义的详细描述。从描述中我们可以得到,EE操作码它会自动到寄存器AX中读取源操作数,到寄存器DX中读取目的操作数。out为操作码的助记符,AL为8位寄存器,DX是16位寄存器,ASCII码是8位的,串口的地址0x3F8(IBM计算机外设地址)是需要使用16位的表示的。

根据写串口指令的格式,我们的程序流程细化为:在执行EE命令前,将串口地址0x3F8存入寄存器DX,将字符存入寄存器AL。

准备源操作数

由out指令可见,在运行指令之前需要将字符A存储到寄存器AL中。x86提供了数据复制指令mov,用于将数据从原操作数复制到目的操作数。原操作数是一个8位立即数,目的操作数是r8,表示一个8位寄存器。

操作码 指令 描述
B0+rb mov r8,imm8 将一个8位立即数复制到一个8位寄存器

指令编码为(B0+rb)ib。根据x86手册,其中rb表示使用操作码的低三位编码目的操作数,即将目的操作寄存器r8的编码嵌入操作吗的低三位。寄存器AL对应的编码为0,因此B0+rb最终编码为B0。

指令编码中的ib对应原操作数,其中i表示立即数,b表示立即数的宽度是一个字节。所以,ib表示跟在操作码之后的是一个8位立即数,和字符对应的ASCII码组合最终(B0+rb)ib的编码分别为:B068 B065 B06C B06C B06F B077 B06F B072 B06C B064。

准备目的操作数

我们需要将串口地址写到寄存器DX中,实质是将一个16位立即数复制到一个16位寄存器, 这显然还是一个数据复制操作

操作码 指令 描述
B8+rw mov r16,imm16 将一个16位立即数复制到一个16位寄存器

这个格式mov指令也接受两个操作数,只不过是16位的。指令编码中的rw表示使用操作码的低三位编码目的操作数,即将目的操作寄存器r16的编码嵌入操作码的低三位。根据x86手册,寄存器DX对应的编码为2,因此B8+rw编码为BA。

指令编码的iw中的i表示立即数,w表示立即数的宽度是一个字,即两个字节。所以iw表示跟在操作码之后的是一个16位立即数,这里即串口地址。不过x86处理器使用小端模式,所以最终(B8+rw)iw的编码为BAF803。

跳转指令

我们的程序循环向串口输出字符串helloworld,在向串口输出后,需要跳转到程序开始的位置,因此需要一个跳转指令jmp,格式如下。

操作码 指令 描述
EB cb jmp rel8 跳转到指令指针+rel8的位置

jmp指令后接一个rel8。rel是relative的缩写,表示相对的意思,8代表8位,因此可以跳转到相对这条指令-128~127的范围。在指令编码中操作码EB之后"cb"就是这个8位的相对偏移。

如果我们想让指令指针指向程序开头,即B0所在的内存地址,那么jmp需要向后跳转35个字节。因为是向后跳转,所以是-35,又因为计算机中使用的是补码,所以我们需要转换一下,根据原码转补码的规则-35的原码是10100011,补码就为11011101,使用16进制表示,即DD。

至此,我们就完成了这段程序的机器语言编码:

c 复制代码
ba f8 03
b0 68
ee
b0 65
ee
b0 6c
ee
b0 6c
ee
b0 6f
ee
b0 77
ee
b0 6f
ee
b0 72
ee
b0 6c
ee
b0 64
ee
eb dd

创建程序文件

我们使用vim以二进制模式打开一个文件,vim -b hello_world.bin。按i进入插入模式,然后输入我们的代码

。输入完成后,按下ESC返回标准模式。在标准模式下按下":"键,进入命令行模式。在":"的后面输入将16进制转为二进制命令:

bash 复制代码
:%! xxd -p -r

"%"表示整个文件内容,"%!"一起使用表示将整个文件内容作为后面xxd的输入,然后使用xxd从16进制转为二进制的输出替换整个文件内容。-p 表示不需要任何格式,-r意为反过来。最后输入:"wq"保存退出

使用kvmtool运行程序

bash 复制代码
sudo kvmtool/lkvm run -c 1 -k hello_world.bin
相关推荐
WZF-Sang9 小时前
Linux—进程学习-01
linux·服务器·数据库·学习·操作系统·vim·进程
Goboy21 小时前
0帧起步:3分钟打造个人博客,让技术成长与职业发展齐头并进
程序员·开源·操作系统
结衣结衣.1 天前
【Linux】Linux管道揭秘:匿名管道如何连接进程世界
linux·运维·c语言·数据库·操作系统
OpenAnolis小助手1 天前
龙蜥副理事长张东:加速推进 AI+OS 深度融合,打造最 AI 的服务器操作系统
ai·开源·操作系统·龙蜥社区·服务器操作系统·anolis os
小蜗的房子2 天前
SQL Server 2022安装要求(硬件、软件、操作系统等)
运维·windows·sql·学习·microsoft·sqlserver·操作系统
邂逅岁月4 天前
【多线程奇妙屋】 Java 的 Thread类必会小技巧,教你如何用多种方式快速创建线程,学并发编程必备(实践篇)
java·开发语言·操作系统·线程·进程·并发编程·javaee
CXDNW5 天前
【系统面试篇】进程和线程类(1)(笔记)——区别、通讯方式、同步、互斥、死锁
笔记·操作系统·线程·进程·互斥·死锁
Anemone_5 天前
MIT 6.S081 Lab3
操作系统
掘了6 天前
持久化内存 | Persistent Memory
c++·架构·操作系统
结衣结衣.7 天前
【Linux】掌握库的艺术:我的动静态库封装之旅
linux·运维·服务器·c语言·操作系统·