为了账号安全,请及时绑定邮箱和手机立即绑定

用x86汇编语言说声你好,世界是怎么回事?

为什么不呢?玩软件很有趣,对吧?

我知道这听起来很疯狂;为了好玩而聚在一起——但相信我,你会感觉活过来了。

我很幸运能够构建几乎所有类型的东西,所以我正在尝试开发一个底层系列,结合了Node.js和汇编作为共享库函数。

在本文中,我们将用x86 Linux汇编语言跟世界打招呼。

……

在汇编语言中的问候语

你需要一个Linux环境。在Windows上,你可以用WSL2,这很容易做到。

创建一个名为 hello.s 的文件,并在其中添加以下代码:

    .section  .data
    msg:
        .asciz "Hello, World!\n"   # 定义一个以空字符结尾并带有换行符的字符串字面量
    msg_len = . - msg              # 从当前位置到msg的偏移量

    .section .text
    .globl _start              # 将_start定义为全局符号(入口点)
    _start:
        # --- sys_write(int fd, const void *buf, size_t count) ---
        movl $4, %eax              # 将sys_write的系统调用号放入%eax
        movl $1, %ebx              # 将stdout文件描述符放入%ebx
        movl $msg, %ecx            # msg地址
        movl $msg_len, %edx        # msg长度
        int $0x80                  # 执行内核系统调用

        # --- sys_exit(int status) ---
        movl $1, %eax              # 将sys_exit的系统调用号放入%eax
        xorl %ebx, %ebx            # 清除%ebx寄存器(退出码为0)
        int $0x80                  # 发出内核系统调用

进入全屏 退出全屏

使用as来编译程序。在Linux上安装很简单,只需通过这个命令安装这些GNU工具:

执行以下命令以更新软件包列表并安装必要的开发工具:

sudo apt update && sudo apt install -y build-essential gdb libgtk-3-dev

点击全屏切换按钮

然后编译你的汇编代码文件。

汇编 ./hello.s -o hello.o  # 汇编 hello.s 文件并输出为 hello.o 文件 (Assembles the hello.s file and outputs it as hello.o)

点击这里进入全屏模式:全屏模式 点击这里退出:退出全屏

这条命令生成了目标文件。接下来,链接它一下。

ld ./hello.o -o hello  # 这里我们使用ld命令将hello.o链接到可执行文件hello

全屏 退出全屏

最后一步,运行一下你的程序:

./hello

运行这个命令来打招呼。

全屏模式 全屏退出

这可能是你见过的最复杂的"Hello World"程序之一,特别是如果你是汇编语言的新手。

我们把它拆开来看看怎么样?

……

装配中的部分

汇编程序通常分为几个部分:

  • .section .data → 我们在这里存储数据,比如 'Hello, World!'
  • .section .text → 我们在这里编写指令
  • .globl _start → 定义程序的起点

符号与标签

符号是用来表示内存中的位置的名字。

例如,msg 是一个符号。它告诉编译器或汇编器,“记住这个位置,我们之后会用到它。” 它就像一个变量一样,这样更符合中文的表达习惯。

当你加上一个 : 时,它就变成一个标签了,定义了这个符号的值。

    msg:
        .asciz "喂,地球!\n"  

切换到全屏模式,退出全屏

这表示:在内存位置 msg,存放字符串 "Hello, World!\n "。(在引号后加上一个空格以增强可读性)

这种模式无处不在。例如,_start 也是一个标签:

    .globl _start  // 全局符号_start,程序的入口点
    _start:

全屏模式 退出全屏

_start 设为全局,这样汇编器和链接器就能找到它了。

此处省略内容

内存监控

程序或代码不会自动管理内存——你需要手动完成。

消息长度 = 消息长度 - 消息

这里出了什么事?

msg只是一个指向。它指向了 "Hello, World!\n" 的开头部分,但并不知道它在哪里结束。\n

. 表示当前内存位置,在字符串之后。

所以,msg_len = . - msg 意味着:

"Hello, World!\n" 之后的地址值,然后减去起点地址。

这样我们就得到了字符串的长度,这是我们调用 sys_write 需要的。


CPU 架构

CPU图片 这是一张CPU的图片。

寄存器暂时存放数据,运行速度远超RAM。

在我们的程序里,%eax 是一个寄存器。

通用用途寄存器:

  • %eax — 这是EAX寄存器,用于存储和操作数据。
  • %ebx — 这是EBX寄存器,常用于存储地址或数据。
  • %ecx — 这是ECX寄存器,通常用于计数操作。
  • %edx — 这是EDX寄存器,常与EAX一起使用进行数据处理。
  • %edi — 这是EDI寄存器,通常用于指针操作。
  • %esi — 这是ESI寄存器,通常用于指针操作。

专用寄存器:

  • %ebp
  • %esp
  • %eip
  • %eflags

这是我们如何在寄存器之间传输数据的方法。

    movl $4, %eax    # 将 4 放入 %eax 并设置为 sys_write
    movl $1, %ebx    # 将文件描述符 1 放入 %ebx
    movl $msg, %ecx  # 消息指针
    movl $msg_len, %edx  # 长度
    int $0x80        # 调用内核中断 0x80

全屏模式 退出全屏

但在我们做任何事之前,我们得先谈谈……


核心部分

内核是你的程序和硬件资源之间的中介。例如,所有的系统调用(例如访问文件、分配内存、退出程序之类)都要通过内核。

每次你看见这个:

int 0x80  /* 系统调用 */

切换到全屏 退出全屏

你正在向内核寻求帮助,对吧?

但是内核有自己的规矩。在调用它之前,你必须用正确的值来配置寄存器。

例如,要关闭一个程序,内核需要:

    movl $1, %eax  # 退出系统调用
    movl $0, %ebx  # 退出码

全屏模式 退出全屏

The sys_exit需要:

  • %eax = 1 → "我想退出。"
  • %ebx = 0 → "成功退出。"

这就像在C语言中返回0一样

    int main() {
        return 0;  // Exit status 0 (success)
    }

进入全屏 退出全屏


控制台输出

sys_exit 不同,向终端写入需要更多的信息:

    movl $4, %eax    # sys_write系统调用
    movl $1, %ebx    # 标准输出文件描述符1
    movl $msg, %ecx  # 消息的地址
    movl $msg_len, %edx  # 消息长度:
    int $0x80        # 内核中断调用

点击全屏,再次点击退出全屏。

事情是这样的:

  1. movl $4, %eax → 数字 4 表示,我想写入数据。
  2. movl $1, %ebx → 数字 1 表示写入到标准输出。
  3. movl $msg, %ecx → 消息在哪里?消息就在 msg 这个位置。
  4. movl $msg_len, %edx → 消息的长度是多少?它在 msg_len 这个变量里。
  5. int $0x80 → 调用内核来执行写入操作。

写好消息后,我们就退出了:

    movl $1, %eax  # 退出系统调用
    xorl %ebx, %ebx  # 退出码为0
    int $0x80  # 中断调用

进入全屏 退出全屏


AT&T 与 Intel 的语法之争.

此程序使用AT&T语法格式,这种语法格式在GNU汇编器中很常见。

在 NASM 中使用的 Intel 语法看起来略有不同。一个主要的区别在于,AT&T 语法会在寄存器前加上 % 符号。

大多数书籍都使用这种 AT&T 语法,但有些人更喜欢这种 Intel 语法,所以学学还是有必要的。


太棒了!你做到了!

就这样就行了!你刚刚就写了个并理解了这个x86汇编的"Hello, World!"

我可能会开始一个初级的Node.js教程系列。如果这听起来不错,告诉我一声!

你可以在x上找到我。

同时,你可以看看这个系列,在这里我们将从头开始构建一个原生的 Node 消息中间件:


点击这里

超越API和端点:构建一个使用TCP的消息代理

图标 payhip.com,这是一张网站图标的图片

下面是你将学到的内容:

  • 带有心跳功能的 TCP 永远在线
  • 缓冲区
  • 队列确认与清理
  • 基于事件和事件队列的客户端驱动
  • 发布/订阅
  • 持久队列的 BSON 编码和解码
  • 握手和认证 🚀
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消