Rust逆向学习 (4)

Reverse for Struct

Rust中的结构体是一个重要的内容,由于Rust中没有类的概念,因此其他编程语言中的封装、继承、多态与Rust中的表现都有较大差异。

我们使用参考书中的一个示例开始进行分析。

Struct 初始化

struct User {username: String,email: String,sign_in_count: u64,active: bool,
}pub fn main() {let mut user1 = User {email: String::from("someone@example.com"),username: String::from("someusername123"),active: true,sign_in_count: 1};println!("{}, {}", user1.email, user1.active);
}

上面这段在汇编层是如何处理的呢?

第一段

example::main:sub     rsp, 296lea     rsi, [rip + .L__unnamed_5]lea     rdi, [rsp + 120]mov     edx, 19call    <alloc::string::String as core::convert::From<&str>>::fromlea     rsi, [rip + .L__unnamed_6]lea     rdi, [rsp + 144]mov     edx, 15call    <alloc::string::String as core::convert::From<&str>>::fromjmp     .LBB17_3....L__unnamed_5:.ascii  "someone@example.com".L__unnamed_6:.ascii  "someusername123"

上面是第一段汇编内容,在源码中,我们是首先对email进行了初始化,在汇编中也是如此。这里分别将两个字符串实例保存到了[rsp+120][rsp+144]处。我们之前分析过,String实例在栈中的大小应该为0x18,可见这两个String实例是完全相邻的,中间没有其他的数据。

第二段

.LBB17_3:mov     rax, qword ptr [rsp + 160]mov     qword ptr [rsp + 64], raxmovups  xmm0, xmmword ptr [rsp + 144]movaps  xmmword ptr [rsp + 48], xmm0lea     rax, [rsp + 72]mov     rcx, qword ptr [rsp + 136]mov     qword ptr [rsp + 88], rcxmovups  xmm0, xmmword ptr [rsp + 120]movups  xmmword ptr [rsp + 72], xmm0mov     qword ptr [rsp + 96], 1mov     byte ptr [rsp + 104], 1mov     qword ptr [rsp + 280], raxlea     rax, [rip + <alloc::string::String as core::fmt::Display>::fmt]mov     qword ptr [rsp + 288], raxmov     rax, qword ptr [rsp + 280]mov     qword ptr [rsp + 32], raxmov     rax, qword ptr [rsp + 288]mov     qword ptr [rsp + 40], raxjmp     .LBB17_6

随后是第二段,这里有一个Rust 1.73与Rust 1.69的不同之处,在老版本中,对于宏将会调用core::fmt::ArgumentV1::new_display将中括号对应的内容转为字符串,而在新版本中,则只会将core::fmt::Display函数地址保存到栈而并不调用。并且结构体中各个元素的内存排列顺序也不相同,通过IDA分析可见在1.73版本中,元素排列与元素定义的顺序相同,但老版本中则不是。这里是因为String实例实现了Display这个Trait,所以能够直接输出。输出时调用的实际上也是DisplayTrait

需要注意的是,第一段中的字符串初始化并不是对结构体的字符串直接进行初始化,而是在栈中另外开辟了0x30大小的空间用于初始化这两个字符串,随后将这段内存的内容复制到结构体中。真正的结构体应该位于[rsp+48]。四个元素的保存地址分别为:[rsp+48][rsp+72][rsp+96][rsp+104],因此,中间的两条指令mov qword ptr [rsp + 96], 1mov byte ptr [rsp + 104], 1就是在对sign_in_countactive进行初始化,因为二者一个是整数类型,一个是布尔值,都是不需要通过new进行初始化的,因此可以直接赋值。

00000000 revlab::User struc ; (sizeof=0x40, align=0x8, copyof_91)
00000000                                         ; XREF: _ZN6revlab4main17h1e5ad0972ab6a820E/r
00000000                                         ; _ZN6revlab4main17h1e5ad0972ab6a820E/r
00000000 username alloc::string::String ?        ; XREF: revlab::main::h1e5ad0972ab6a820+65/w
00000000                                         ; revlab::main::h1e5ad0972ab6a820+72/w
00000018 email alloc::string::String ?           ; XREF: revlab::main::h1e5ad0972ab6a820+77/o
00000018                                         ; revlab::main::h1e5ad0972ab6a820+84/w ...
00000030 sign_in_count dq ?                      ; XREF: revlab::main::h1e5ad0972ab6a820+93/w
00000038 active db ?                             ; XREF: revlab::main::h1e5ad0972ab6a820+9C/w
00000038                                         ; revlab::main::h1e5ad0972ab6a820+11C/o
00000039 db ? ; undefined
0000003A db ? ; undefined
0000003B db ? ; undefined
0000003C db ? ; undefined
0000003D db ? ; undefined
0000003E db ? ; undefined
0000003F db ? ; undefined

第三段

.LBB17_6:mov     rax, qword ptr [rsp + 40]mov     rcx, qword ptr [rsp + 32]mov     qword ptr [rsp], rcxmov     qword ptr [rsp + 8], raxlea     rax, [rsp + 104]mov     qword ptr [rsp + 264], raxmov     rax, qword ptr [rip + <bool as core::fmt::Display>::fmt@GOTPCREL]mov     qword ptr [rsp + 272], raxmov     rax, qword ptr [rsp + 264]mov     qword ptr [rsp + 16], raxmov     rax, qword ptr [rsp + 272]mov     qword ptr [rsp + 24], raxmov     rax, qword ptr [rsp + 24]mov     rcx, qword ptr [rsp + 16]mov     rdx, qword ptr [rsp + 8]mov     rsi, qword ptr [rsp]mov     qword ptr [rsp + 216], rsimov     qword ptr [rsp + 224], rdxmov     qword ptr [rsp + 232], rcxmov     qword ptr [rsp + 240], raxlea     rsi, [rip + .L__unnamed_7]lea     rdi, [rsp + 168]mov     edx, 3lea     rcx, [rsp + 216]mov     r8d, 2call    core::fmt::Arguments::new_v1jmp     .LBB17_8.L__unnamed_7:.quad   .L__unnamed_2.zero   8.quad   .L__unnamed_11.asciz  "\002\000\000\000\000\000\000".quad   .L__unnamed_12.asciz  "\001\000\000\000\000\000\000".L__unnamed_11:.ascii  ", ".L__unnamed_12:.ascii  "\n"

这一段的工作主要就是输出,通过调试发现,新版rustc在使用println!宏时将不再将临时字符串切片参数保存在栈中,但通过IDA依然可以较为容易地辨别。

Struct 作为返回值

下面书中给出一个通过函数初始化结构体的实例:

struct User {username: String,email: String,sign_in_count: u64,active: bool,
}fn build_user(email: String, username: String) -> User {User {email,username,active: true,sign_in_count: 1}
}pub fn main() {let mut user1 = build_user(String::from("someone@example.com"), String::from("someusername123"));println!("{}, {}", user1.email, user1.active);
}
example::build_user:mov     rax, rdimov     rcx, qword ptr [rdx]mov     qword ptr [rdi], rcxmov     rcx, qword ptr [rdx + 8]mov     qword ptr [rdi + 8], rcxmov     rcx, qword ptr [rdx + 16]mov     qword ptr [rdi + 16], rcxmov     rcx, qword ptr [rsi]mov     qword ptr [rdi + 24], rcxmov     rcx, qword ptr [rsi + 8]mov     qword ptr [rdi + 32], rcxmov     rcx, qword ptr [rsi + 16]mov     qword ptr [rdi + 40], rcxmov     qword ptr [rdi + 48], 1mov     byte ptr [rdi + 56], 1ret

从函数的汇编可以看到,这个函数实际上是将第一个参数作为指针完成初始化的,可以将第一个指针理解为this,这与C++类方法的函数调用规则类似。

实现 Debug Trait

一个结构体可以通过#[derive(Debug)]完成对Debug Trait的默认实现:

#[derive(Debug)]
struct Rect {width: u32,height: u32,
}pub fn main() {let rect1 = Rect {width: 30, height: 50};println!("rect1 = {:?}", rect1);
}
example::main:sub     rsp, 88mov     dword ptr [rsp], 30mov     dword ptr [rsp + 4], 50mov     rax, rspmov     qword ptr [rsp + 72], raxmov     rax, qword ptr [rip + <example::Rect as core::fmt::Debug>::fmt@GOTPCREL]mov     qword ptr [rsp + 80], raxmov     rcx, qword ptr [rsp + 72]mov     rax, qword ptr [rsp + 80]mov     qword ptr [rsp + 56], rcxmov     qword ptr [rsp + 64], raxlea     rdi, [rsp + 8]lea     rsi, [rip + .L__unnamed_4]mov     edx, 2lea     rcx, [rsp + 56]mov     r8d, 1call    core::fmt::Arguments::new_v1lea     rdi, [rsp + 8]call    qword ptr [rip + std::io::stdio::_print@GOTPCREL]add     rsp, 88ret

可以看到,汇编代码中获取的就是Debug这个Trait的函数指针,说明不同的宏实际上调用的函数也不同。如果将{:?}修改为{:#?},则原先调用的core::fmt::Arguments::new_v1将会改为调用core::fmt::Arguments::new_v1_formatted。考虑到Rust的格式化字符串非常强大与灵活,有多种输出形式,后面将通过专门的分析对宏展开进行分析,这里不深入探讨。

Reverse for Methods

在Rust中,结构体充当了其他语言中类的功能,可以在结构体下定义方法,使这个方法专属于该结构体。

struct Rect {width: u32,height: u32,
}impl Rect {fn area(&self) -> u32 {self.width * self.height}
}pub fn main() {let rect1 = Rect {width: 30, height: 50};println!("area = {}", rect1.area());
}
example::Rect::area:push    raxmov     eax, dword ptr [rdi]mul     dword ptr [rdi + 4]mov     dword ptr [rsp + 4], eaxseto    altest    al, 1jne     .LBB1_2mov     eax, dword ptr [rsp + 4]pop     rcxret
.LBB1_2:lea     rdi, [rip + str.0]lea     rdx, [rip + .L__unnamed_4]mov     rax, qword ptr [rip + core::panicking::panic@GOTPCREL]mov     esi, 33call    raxud2example::main:sub     rsp, 104mov     dword ptr [rsp + 8], 30mov     dword ptr [rsp + 12], 50lea     rdi, [rsp + 8]call    example::Rect::areamov     dword ptr [rsp + 84], eaxlea     rax, [rsp + 84]mov     qword ptr [rsp + 88], raxmov     rax, qword ptr [rip + core::fmt::num::imp::<impl core::fmt::Display for u32>::fmt@GOTPCREL]mov     qword ptr [rsp + 96], raxmov     rcx, qword ptr [rsp + 88]mov     rax, qword ptr [rsp + 96]mov     qword ptr [rsp + 64], rcxmov     qword ptr [rsp + 72], raxlea     rdi, [rsp + 16]lea     rsi, [rip + .L__unnamed_5]mov     edx, 2lea     rcx, [rsp + 64]mov     r8d, 1call    core::fmt::Arguments::new_v1lea     rdi, [rsp + 16]call    qword ptr [rip + std::io::stdio::_print@GOTPCREL]add     rsp, 104ret

由上述汇编可知,这里还是将rdi作为self使用。

#[derive(Debug)]
struct Rect {width: u32,height: u32,
}impl Rect {fn area(&self) -> u32 {self.width * self.height}fn can_hold(&self, other: &Rect) -> bool {self.width > other.width && self.height > other.height}
}pub fn main() {let rect1 = Rect {width: 30, height: 50};let rect2 = Rect {width: 10, height: 40};println!("{}", rect1.can_hold(&rect2));
}

对于上面的代码,can_hold方法的参数有两个,都是指针,如果将第二个参数的&去掉,则参数有三个。经过试验发现,当一个结构体中的元素数量较少时,不加&可能会将结构体的每个元素分别作为参数传递,当元素数量较多时,则是首先复制然后传递指针。

对于关联函数,由于其第一个参数并不是self,类似于C++中的类静态函数,不需要首先获取结构体实例即可调用,参数传递与一般的函数相同。

Reverse for enum (Part 2)

对于枚举类型,我们在第二篇文章中已经进行了较为详细的解释,对于枚举类型的内存排布有了一定的了解。

下面对枚举类型中定义的方法进行测试。

use std::any::Any;pub enum Student {Freshman(String),Sophomore(String),Junior(String),Senior(String),
}pub fn get_student(grade: i32, name: String) -> Option<Student> {match grade {1 => Some(Student::Freshman(name)),2 => Some(Student::Sophomore(name)),3 => Some(Student::Junior(name)),4 => Some(Student::Senior(name)),_ => None}
}impl Student {fn test(&self) -> String {match self {Student::Freshman(name) => format!("{}", "Calculus").to_string(),Student::Sophomore(name) => format!("{}", "Data Structure").to_string(),Student::Junior(name) => format!("{}", "Computer Network").to_string(),Student::Senior(name) => format!("{}", "Graduation Design").to_string()}}
}pub fn main() {let x = get_student(4, "CoLin".to_string()).unwrap();println!("{}", x.test());
}

上面代码中对于test方法的调用如下:

        mov     rax, qword ptr [rip + core::option::Option<T>::unwrap@GOTPCREL]lea     rdi, [rsp + 40]mov     qword ptr [rsp + 32], rdicall    raxmov     rsi, qword ptr [rsp + 32]lea     rdi, [rsp + 192]call    example::Student::testjmp     .LBB26_3

可以看到方法的第一个参数依然是self,第二个参数则是等待初始化的String实例地址。在代码中是返回String实例,实际上是传入未初始化的指针。

Option<T>

针对Option<T>,Rust在汇编层有自己的处理方式。如果将Option<T>看做一个普通的枚举类型,且Some后面带的是另一个枚举类型,那么这样的话就会产生两层枚举对象,不太优雅。对于get_student函数,下面是部分反编译结果:

.text:0000000000009702 48 89 4C 24 18                mov     [rsp+108h+var_F0], rcx
.text:0000000000009707 83 E8 03                      sub     eax, 3
.text:000000000000970A 77 15                         ja      short def_971F                  ; jumptable 000000000000971F default case
.text:000000000000970A
.text:000000000000970C 48 8B 44 24 18                mov     rax, [rsp+108h+var_F0]
.text:0000000000009711 48 8D 0D B4 09 04 00          lea     rcx, jpt_971F
.text:0000000000009718 48 63 04 81                   movsxd  rax, ds:(jpt_971F - 4A0CCh)[rcx+rax*4]
.text:000000000000971C 48 01 C8                      add     rax, rcx
.text:000000000000971F FF E0                         jmp     rax                             ; switch jump
.text:000000000000971F
.text:0000000000009721                               ; ---------------------------------------------------------------------------
.text:0000000000009721
.text:0000000000009721                               def_971F:                               ; CODE XREF: revlab::get_student::h5c77d454e35cea03+3A↑j
.text:0000000000009721 48 8B 44 24 08                mov     rax, [rsp+108h+var_100]         ; jumptable 000000000000971F default case
.text:0000000000009726 48 C7 00 04 00 00 00          mov     qword ptr [rax], 4
.text:000000000000972D E9 43 02 00 00                jmp     loc_9975

下面的def_971F为默认分支,可以看到这里是将枚举类型的索引值赋值为4,但上面定义的枚举类型一共只有4个值,最大的索引值只能为3。将索引值设置为4实际上也就表示这个枚举类型是一个无效值,这样在内存中实际上并不存在二重枚举类型,而是只有一个Student枚举类型。由此可见,对泛型参数为枚举类型的Option,Rust进行了优化。

Reverse for if-let

if let语句是针对只有一个处理条件和一个默认条件的match语句的平替。由于只有一个特殊条件和默认条件,因此在实际实现中只需要使用类似于if的逻辑即可完成。

pub fn main() {let x = get_student(4, "CoLin".to_string());if let Some(Student::Senior(y)) = x {println!("{}", y);}
}
example::main:sub     rsp, 216mov     byte ptr [rsp + 183], 0lea     rdi, [rsp + 56]lea     rsi, [rip + .L__unnamed_5]mov     edx, 5call    <str as alloc::string::ToString>::to_stringlea     rdi, [rsp + 24]mov     esi, 4lea     rdx, [rsp + 56]call    qword ptr [rip + example::get_student@GOTPCREL]mov     byte ptr [rsp + 183], 1mov     eax, 1xor     ecx, ecxcmp     qword ptr [rsp + 24], 4cmove   rax, rcxcmp     rax, 1jne     .LBB18_2cmp     qword ptr [rsp + 24], 3je      .LBB18_3

可以发现,这里的判断逻辑和match是类似的,都是对枚举索引值进行比较。

总结

本文学习了:

  1. Rust 结构体的内存排布以及结构体方法的参数传递,结构体方法参数传递遵照this参数传递法
  2. Rust 枚举类型方法的参数传递与结构体方法的参数传递类似
  3. Rust if-let语句的判断逻辑,Option<T>的内存结构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/141453.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kubernetes介绍

Kubernetes介绍 1.应用部署方式演变 在部署应用程序的方式上&#xff0c;主要经历了三个时代&#xff1a; 传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其它技术的参与 缺点&#xff1a;不能为应用程序…

利用uni-app 开发的iOS app 发布到App Store全流程

1.0.3 20200927 更新官方对应用审核流程的状态。 注&#xff1a;最新审核后续将同步社区另一篇记录 AppStore 审核被拒原因记录及解决措施 &#xff1a;苹果开发上架常见问题 | appuploader使用教程 1.0.2 20200925 新增首次驳回拒绝邮件解决措施。 1.0.1 20200922 首次…

目标检测问题总结

目标检测问题总结 目标检测二阶段和一阶段的核心区别目标检测二阶段比一阶段的算法精度高的原因1. 正负样本不平衡2.样本的不一致性 如何解决目标检测中遮挡问题如何解决动态目标检测FPN的作用如何解决训练数据样本过少的问题IOU代码实现NMS代码实现NMS的改进思路 目标检测二阶…

C //例 7.13 有一个3*4的矩阵,求所有元素中的最大值。

C程序设计 &#xff08;第四版&#xff09; 谭浩强 例 7.13 例 7.13 有一个3*4的矩阵&#xff0c;求所有元素中的最大值。 IDE工具&#xff1a;VS2010 Note: 使用不同的IDE工具可能有部分差异。 代码块 方法&#xff1a;使用指针、动态分配内存 #include <stdio.h> …

设计模式-观察者模式(Observer)

设计模式-观察者模式&#xff08;Observer&#xff09; 一、观察者模式概述1.1 什么是观察者模式1.2 简单实现观察者模式1.3 使用观察者模式的注意事项 二、观察者模式的用途三、观察者模式实现方式3.1 使用接口实现观察者模式3.2 使用抽象类和具体子类实现观察者模式3.3 使用 …

Rust语言基础:从Hello World开始

大家好&#xff0c;我是[lincyang]。 我们将一起探索Rust语言的基础&#xff0c;从最经典的程序入手——“Hello, World!”。 Rust简介 Rust是一种系统编程语言&#xff0c;由Mozilla赞助开发&#xff0c;旨在提供内存安全、并发性和实用性。它的设计思想强调安全性和性能&…

CSS怎么选择除了第一个子元素外的其余同级子元素

使用 CSS 的:not()伪类选择器和:nth-child()伪类选择器 要通过CSS的代码选择某一个元素的除了第一个子元素外的其余的跟第一个子元素同级的子元素&#xff0c;可以结合使用CSS的:not()伪类选择器和:nth-child()伪类选择器进行选择。大致的语法如下&#xff1a; .parent > …

【EI会议征稿】第七届结构工程与工业建筑国际学术会议(ICSEIA 2024)

第七届结构工程与工业建筑国际学术会议&#xff08;ICSEIA 2024&#xff09; 2024 7th International Conference on Structural Engineering and Industrial Architecture 随着城市化进程的不断深入&#xff0c;建筑领域的需求也在优化、调整。结构工程的发展依旧受到重视&am…

c语言-数据结构-链表分割

链表分割实际上是给定一个值&#xff0c;遍历链表把链表中小于该值的节点与大于该值的节点分开&#xff0c;一般是将小于该值的节点放到链表的前面部分&#xff0c;大于该值的节点放在链表的后面部分。 链表分割示意图如下&#xff1a; 思路&#xff1a; 首先创建两条带哨兵位节…

CSDN每日一题学习训练——Java版(字符串相乘、子集、删除链表的倒数第 N 个结点)

版本说明 当前版本号[20231112]。 版本修改说明20231112初版 目录 文章目录 版本说明目录字符串相乘题目解题思路代码思路补充说明参考代码 子集题目解题思路代码思路参考代码 删除链表的倒数第 N 个结点题目解题思路代码思路参考代码 字符串相乘 题目 给定两个以字符串形…

【Linux】第十六站:进程地址空间

文章目录 一、程序地址空间1.内存的分布2.static修饰后为什么不会被释放3.一个奇怪的现象 二、进程地址空间1.前面现象的原因2.地址空间究竟是什么&#xff1f;3.为什么要有进程地址空间4.页表5.什么叫进程&#xff1f;6.进程具有独立性。为什么&#xff1f;怎么做到呢&#xf…

问题复盘|MySQL 数据记录中明明有值,使用 concat() 后得到的却一直是 null

背景 MySQL 的数据数据记录中明明有值&#xff0c;在使用 concat() 查询时却一直得到 null SELECT CONCAT(first_name, , last_name) FROM users;排查后发现 MySQL 的 concat 函数拼接规则是 当多个拼接的字段的字段值中存在 null 时&#xff0c;返回的一定是 null 解决方…

【算法】算法题-20231114

这里写目录标题 一、LCR 181. 字符串中的单词反转二、557. 反转字符串中的单词 III三、344. 反转字符串四、给定一个已按照升序排列的有序数组&#xff0c;找到两个数使得它们相加之和等于目标数。五、力扣第49题&#xff1a;字母异位词分组 一、LCR 181. 字符串中的单词反转 …

解决 Django 开发中的环境配置问题:Windows 系统下的实战指南20231113

简介&#xff1a; 在本文中&#xff0c;我想分享一下我最近在 Windows 环境下进行 Django 开发时遇到的一系列环境配置问题&#xff0c;以及我是如何一步步解决这些问题的。我的目标是为那些可能遇到类似困难的 Django 开发者提供一些指导和帮助。 问题描述&#xff1a; 最近…

2023.11.14-hive之表操作练习和文件导入练习

目录 需求1.数据库基本操作 需求2. 默认分隔符案例 需求1.数据库基本操作 -- 1.创建数据库test_sql,cs1,cs2,cs3 create database test_sql; create database cs1; create database cs2; create database cs3; -- 2.1删除数据库cs2 drop database cs2; -- 2.2在cs3库中创建…

optee4.0.0 qemu_v8环境搭建(支持Hafnium)

安全之安全(security)博客目录导读 目录 一、前提条件 二、拉取代码 三、下载工具链 四、编译 五、运行

了解防抖和节流:提升前端交互体验的实用策略

了解防抖和节流&#xff1a;提升前端交互体验的实用策略 前言什么是防抖&#xff1f;什么是节流&#xff1f;应用实例防抖实例节流实例 前言 本文将重点介绍前端性能优化方法之一的防抖和节流。首先解释了它们的概念和原理&#xff0c;然后探讨了它们在前端开发中的应用场景&a…

Spark数据倾斜优化

1 数据倾斜现象 1、现象 绝大多数task任务运行速度很快&#xff0c;但是就是有那么几个task任务运行极其缓慢&#xff0c;慢慢的可能就接着报内存溢出的问题。 2、原因 数据倾斜一般是发生在shuffle类的算子&#xff0c;比如distinct、groupByKey、reduceByKey、aggregateByKey…

链表相关部分OJ题

&#x1f493;作者简介&#x1f44f;&#xff1a;在校大二迷茫大学生 &#x1f496;个人主页&#x1f389;&#xff1a;小李很执着 &#x1f497;系列专栏&#xff1a;Leetcode经典题 每日分享&#xff1a;人总是在离开一个地方后开始原谅它❣️❣️❣️———————————…