深入了解Rust - Hello World汇编初探
引言
Hello World是每一个程序员学习新语言时的小小里程碑。今天,我们就来学习如何编写一个Rust的Hello World程序,并且探索一些底层汇编知识。
前期准备工作
首先,我们需要安装 Rust。可以访问 Rust 官方网站 https://www.rust-lang.org/tools/install 下载安装程序并按照指示进行安装。
1 | rustc --version |
创建 Rust 项目
安装完成后,可执行上述命令查看 Rust 版本,接下来,我们打开终端并输入以下命令来创建一个新的 Rust 项目:
1 | cargo new hello_world |
这将会创建一个名为 hello_world 的新文件夹,并在其中生成一个名为 Cargo.toml 文件。这个文件是 Rust 项目的配置文件,我们可以在这里添加依赖项和其他项目设置。
执行
cargo new hello_world命令后,会自动在src目录下创建一个main.rs源码文件,cat src/main.rs可以看到默认cargo已为您实现了Hello World程序:
1 | fn main() { |
这段代码定义了一个名为 main 的函数,这是 Rust 程序的入口点。在 main 函数中,我们使用 println! 宏来输出”Hello, world!”。
现在,我们可以使用 cargo 命令来构建并运行程序。在终端中输入以下命令:
1 cargo run
这将会在终端中输出”Hello, world!”。但是,这只是一个表面上的 Hello World 程序。如果我们想要深入了解 Rust,我们就需要了解一些底层汇编知识。
编译汇编源码
cargo工具包带有rustc命令封装,可以执行以下命令将rs源码翻译为汇编。
1 | cargo rustc -- --emit=asm |
生成的汇编代码会放在target/debug/deps目录下,名为hello_world-xxx.s,但是该汇编会包含很多和cargo工具相关的信息,不利于进一步学习分析,因此,可直接使用rustc工具来生成汇编代码main.s,命令如下:
1 | cd src |
汇编源码解读
主要组成介绍
由于main.s文件内容较长,这里只分析关键部分代码,首先,一起回顾一些基础知识,这是一个 ELF(可执行链接格式)文件的分析,它包含了程序的代码和数据段。以下是该 ELF 文件的主要组成部分:
.section指令:这些指令用于定义程序中的各个段,如代码段、数据段和栈。.text段:包含程序的代码。.data段:包含程序的数据。.bss段:包含程序的未初始化数据。.rodata段:包含只读数据。.rela段:包含程序的动态链接信息。.symtab段:包含程序的符号表。.strtab段:包含程序的字符串表。
在给定的 ELF 文件中,我们可以看到以下几个重要的函数和变量:
__ZN3std10sys_common9backtrace28__rust_begin_short_backtrace17h41d6339a4d6b9b77E:这是一个函数,用于开始调试。__ZN3std2rt10lang_start17h8b3af1e9d6ac7258E:这是一个函数,用于启动语言运行时。__ZN4core3fmt9Arguments9new_const17h71bd3e081e7c68d0E:这是一个函数,用于创建一个常量。__ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h136ee7478585e27cE:这是一个函数,用于调用一次。_main:这是程序的主入口点。
此外,还有一些全局变量和局部变量,如
x0、x1、x2、x3等。这些变量在程序中用于各种计算和数据存储。根据给定的 ELF 文件,我们可以看到程序的结构和功能。要深入了解程序的实现,我们需要分析每个函数和变量,以及它们之间的关系。
汇编代码解读
启动函数解读
1 | .section __TEXT,__text,regular,pure_instructions |
- .section 指示段落为文本段,.build_version 表示构建的版本为 macos 11.0。
__ZN3std2rt10lang_start17h8b3af1e9d6ac7258E是启动时调用的一个函数。这个函数的主要作用是初始化 Rust 运行时的各个组件,为程序的执行做好准备。下面是代码的主要步骤:- 保存寄存器:将 x29 和 x30 保存到栈上,为后面的操作腾出空间。
- 调用
__ZN4core3ops8function6FnOnce9call_once17hf10fe04e07b46ffbE函数:这个函数的作用是确保std::function::FnOnce类型的实例只被调用一次。 - 恢复寄存器:将 x29 和 x30 恢复到原来的值。
- 调用
__ZN3std2rt19lang_start_internal17hadaf077a6dd0140bE函数:这个函数是 Rust 运行时的内部函数,用于进行一些初始化工作。 - 保存寄存器:将 x29 和 x30 保存到栈上,为后面的操作腾出空间。
- 调用
__ZN3std2rt19lang_start_internal17hadaf077a6dd0140bE函数:这个函数是 Rust 运行时的内部函数,用于进行一些初始化工作。 - 恢复寄存器:将 x29 和 x30 恢复到原来的值。
- 返回:结束初始化过程,返回到调用者处。
整个代码的执行过程可以概括为:保存寄存器 -> 调用初始化函数 -> 恢复寄存器 -> 调用内部初始化函数 -> 恢复寄存器 -> 返回。这个过程为 Rust 程序的执行奠定基础,确保运行时的各个组件处于正确的状态。
解读main函数
1 | __ZN4main4main17hdd2613d9f6a2edd6E: |
- 这段代码是 Rust 程序的入口点,即
main函数。main函数是 Rust 程序的起始点,程序从这里开始执行。这段代码的主要任务是初始化 Rust 运行时,并调用真正的程序入口点。 - 代码主要步骤如下:
- 保存寄存器:将 x29 和 x30 保存到栈上,为后面的操作腾出空间。
- 计算栈指针:将栈指针减去 80,为后续操作腾出空间。
- 保存 x29 和 x30:将 x29 和 x30 保存到栈上,为后面的操作腾出空间。
- 恢复 x29 和 x30:将 x29 和 x30 恢复到原来的值。
- 调用
__ZN4core3fmt9Arguments9new_const17h71bd3e081e7c68d0E函数:这个函数用于创建一个Arguments实例,用于存储命令行参数。 - 调用
__ZN3std2io5stdio6_print17ha1fabf8eb351d161E函数:这个函数用于输出 “Hello, world!”。 - 恢复 x29 和 x30:将 x29 和 x30 恢复到原来的值。
- 返回:结束程序的执行,返回到调用者处。
整个代码的执行过程可以概括为:保存寄存器 -> 初始化运行时 -> 调用真正的程序入口点 -> 恢复寄存器 -> 返回。这个过程为 Rust 程序的执行奠定基础,确保运行时的各个组件处于正确的状态。
以上就是 Hello World 汇编初探解读,希望对你有用,祝大家玩得开心 ^_^
如果您喜欢这篇文章,欢迎关注微信公众号《猿禹宙》、点赞、转发和赞赏。每一位读者的认可都是我持续创作的动力。
