Rust逆向学习 (6)

文章目录

  • Reverse for String
    • `push_str` 与 `+`
    • `format!`
    • `bytes`方法
    • `chars`方法
  • 总结

Reverse for String

上一篇文章简单分析了Vec变长数组的结构,今天来介绍String。实际上Rust的字符串类型对于我们并不陌生,在前面几篇文章的几乎任何一个示例中都可以找到它们。

我们曾经提到过,String类型在栈中占0x18大小,其中包括字符串的指针、字符串长度、字符串容量。看上去好像什么问题都没有,但如果你使用Python或C/C++开发过一些项目,你可能会遇到一些与字符串编码有关的问题。在C++中,由于需要考虑多种字符编码方式,字符被分为char、wchar_t、tchar等等,它们占用的字节数量还不相同,如果需要转换还需要使用特定的函数完成,对于一些需要进行编码转换的场景来说,稍有一个不注意,可能就是一串乱码怼在你的脸上,让人深恶痛绝。

但对于Rust而言,它规定,只要是我Rust写的程序,程序里面的所有字符串全都用UTF-8编码。这样就从根本上杜绝了编码转换的问题。

不过,这也产生了一些问题,其中影响最大的可能就是字符串不可索引了。由于使用UTF-8编码,对于不同的字符,其占用的字节数量可能不同,而Rust又不能将字符串单纯地看做单字节数组,因此Rust无法知道在一个既有中文又有英文又有其他语言的字符串中,第某个有效字符在字符串中的偏移地址到底是多少。对于一个Rust字符串,它的长度指的是占用的内存空间大小,因此对于1个中文字符组成的字符串,它的长度实际上是3。

下面介绍一下Rust中String的常用操作。

push_str+

在Rust中,push_str方法与运算符+都能够将一个字符串拼接到另一个字符串的后面。让我们看一下二者在汇编上有什么区别。

pub fn main(){let mut s = String::from("CoLin");s += "666";println!("{}", s);
}example::main:sub     rsp, 152lea     rsi, [rip + .L__unnamed_7]lea     rdi, [rsp + 32]mov     qword ptr [rsp + 24], rdimov     edx, 5call    <alloc::string::String as core::convert::From<&str>>::frommov     rdi, qword ptr [rsp + 24]lea     rsi, [rip + .L__unnamed_8]mov     edx, 3call    <alloc::string::String as core::ops::arith::AddAssign<&str>>::add_assignjmp     .LBB36_3

首先看下+。这里的+运算符实际上是调用了String的方法,String这个结构重载了+这个运算符。这与C++的运算符重载类似。在汇编中,显示出调用的函数为<alloc::string::String as core::ops::arith::AddAssign<&str>>::add_assign。实际上,Rust运算符重载的本质就是对“加”这个操作的Trait的impl,它与Rust中其他Trait并没有太大的区别,只有在使用的时候能够直接用运算符代替显式的方法调用罢了。需要注意的是,使用+运算符或push_str时,参数只能是字符串切片而不能是字符串,这是因为这两个方法不需要获取String的所有权,如果能够传入String,那么在这个函数执行后参数实际上就被销毁了,这当然是不希望看到的。另外,由于有解引用强制转换,我们传入String的引用也是被允许的。

对于上面的示例,一开始的字符串创建时,其指针指向的实际上并不是堆地址空间,而是字符串切片CoLin中保存的字符串常量地址。此时s中的字符串长度与字符串容量相同,均为5。随后使用+运算符增加字符串长度时,由于检测到字符串没有多余容量,因此会在堆空间分配一块更大的空间,将字符串拼接的结果保存到这块空间中,与realloc有相似之处。

pub fn main(){let mut s = String::from("CoLin");s.push_str("666");println!("{}", s);
}example::main:sub     rsp, 152lea     rsi, [rip + .L__unnamed_7]lea     rdi, [rsp + 32]mov     qword ptr [rsp + 24], rdimov     edx, 5call    <alloc::string::String as core::convert::From<&str>>::frommov     rdi, qword ptr [rsp + 24]lea     rsi, [rip + .L__unnamed_8]mov     edx, 3call    alloc::string::String::push_strjmp     .LBB36_3

上面是使用push_str的汇编结果,可以看到只有函数调用发生了改变,甚至二者传入的参数都是一样的,分别是:原来的String栈地址,看做this、字符串指针、字符串长度。

format!

当需要拼接的字符串较多,或符合某种格式时,使用format!宏是一种更加简洁的方法。对于format!宏,我们实际上已经分析过了,因为println!的前半部分就是format!,也就是core::fmt::Arguments::new_v1方法的调用流程。这个在第一篇文章中已经介绍过了,这里不再赘述。

bytes方法

这个方法返回的是字符串中的所有字节。不过需要注意的是这个方法返回的是一个不可变借用,除非这个方法的返回值被删除,否则字符串不能修改。

pub fn main(){let s = String::from("CoLin");let t = String::from("666");let mut u = format!("{s} is {t}");let mut x = u.bytes();for b in x{println!("{}", b);}
}
...
.LBB27_9:mov     rax, qword ptr [rsp + 216]mov     qword ptr [rsp + 192], raxmovups  xmm0, xmmword ptr [rsp + 200]movaps  xmmword ptr [rsp + 176], xmm0lea     rdi, [rsp + 176]call    <alloc::string::String as core::ops::deref::Deref>::derefmov     qword ptr [rsp + 64], rdxmov     qword ptr [rsp + 72], raxjmp     .LBB27_12...
.LBB27_12:mov     rsi, qword ptr [rsp + 64]mov     rdi, qword ptr [rsp + 72]call    core::str::<impl str>::bytesmov     qword ptr [rsp + 48], rdxmov     qword ptr [rsp + 56], raxjmp     .LBB27_13
.LBB27_13:mov     rsi, qword ptr [rsp + 48]mov     rdi, qword ptr [rsp + 56]mov     rax, qword ptr [rip + <I as core::iter::traits::collect::IntoIterator>::into_iter@GOTPCREL]call    raxmov     qword ptr [rsp + 32], rdxmov     qword ptr [rsp + 40], raxjmp     .LBB27_14
.LBB27_14:mov     rax, qword ptr [rsp + 32]mov     rcx, qword ptr [rsp + 40]mov     qword ptr [rsp + 304], rcxmov     qword ptr [rsp + 312], rax
.LBB27_15:lea     rdi, [rsp + 304]call    <core::str::iter::Bytes as core::iter::traits::iterator::Iterator>::nextmov     byte ptr [rsp + 30], dlmov     byte ptr [rsp + 31], aljmp     .LBB27_16

可以看到,上面的代码中,首先对String类型进行deref解引用获取字符串切片,然后调用bytes方法,这个方法的第一个参数是字符串指针,第二个参数是字符串长度。这个方法的返回值有两个,rax为字符串开头的地址,rdx为字符串末尾的地址。后面是into_iter方法,这个方法的参数和返回值一样。下面就是正常的迭代器迭代流程,在前面的文章中有分析。

chars方法

这个方法返回的是字符串中所有字符的集合。由于字符串中每个字符占用的字节数量可能不同,那么如何表示字符的集合就很值得我们研究了。

pub fn main(){let s = String::from("CoLin");let t = String::from("太6了!");let mut u = format!("{s} {t}");let mut x = u.chars();for b in x{println!("{}", b);}
}
.LBB27_9:mov     rax, qword ptr [rsp + 216]mov     qword ptr [rsp + 192], raxmovups  xmm0, xmmword ptr [rsp + 200]movaps  xmmword ptr [rsp + 176], xmm0lea     rdi, [rsp + 176]call    <alloc::string::String as core::ops::deref::Deref>::derefmov     qword ptr [rsp + 64], rdxmov     qword ptr [rsp + 72], raxjmp     .LBB27_12...
.LBB27_12:mov     rsi, qword ptr [rsp + 64]mov     rdi, qword ptr [rsp + 72]call    core::str::<impl str>::charsmov     qword ptr [rsp + 48], rdxmov     qword ptr [rsp + 56], raxjmp     .LBB27_13
.LBB27_13:mov     rsi, qword ptr [rsp + 48]mov     rdi, qword ptr [rsp + 56]mov     rax, qword ptr [rip + <I as core::iter::traits::collect::IntoIterator>::into_iter@GOTPCREL]call    raxmov     qword ptr [rsp + 32], rdxmov     qword ptr [rsp + 40], raxjmp     .LBB27_14
.LBB27_14:mov     rax, qword ptr [rsp + 32]mov     rcx, qword ptr [rsp + 40]mov     qword ptr [rsp + 304], rcxmov     qword ptr [rsp + 312], rax
.LBB27_15:lea     rdi, [rsp + 304]call    <core::str::iter::Chars as core::iter::traits::iterator::Iterator>::nextmov     dword ptr [rsp + 28], eaxjmp     .LBB27_16
...

可以看到,这里与bytes类似。经过调试发现,chars方法返回的也是两个地址,开始地址和结尾地址。因为chars返回的类型是迭代器,所以Rust可以通过调用next方法动态地判断下一个字符占用的字节数量,因此不需要返回每一个字符占用的字节数。但是,我们有方法让Rust返回真正的字符数组,那就是使用collect方法将迭代器转换为Vec

pub fn main(){let s = String::from("CoLin");let t = String::from("太6了!");let mut u = format!("{s} {t}");let mut x = u.chars();let y: Vec<char> = x.collect();println!("{}", y[0]);
}
pwndbg> tele 0x5555555b6c00
00:0000│  0x5555555b6c00 ◂— 0x6f00000043 /* 'C' */
01:0008│  0x5555555b6c08 ◂— 0x690000004c /* 'L' */
02:0010│  0x5555555b6c10 ◂— 0x200000006e /* 'n' */
03:0018│  0x5555555b6c18 ◂— 0x360000592a /* '*Y' */
04:0020│  0x5555555b6c20 ◂— 0x2100004e86

collect方法在一个栈地址中保存了一个堆地址,而这个堆地址的内容就如上面所示。可以看到,Rust为每一个字符分配了4个字节的空间,虽然大多数字符都占不到4个字节,但是为了索引的需要,Rust必须分配一个足够容纳所有字符的空间,也就是UTF-8的一个字符可能占用的最大字节数。

总结

本文我们学习了:

  1. 字符数组在内存中的结构
  2. 字符串遍历过程的逆向
  3. Rust字符串的相关知识

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

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

相关文章

项目开发的大致流程

一、需求分析 功能界面… 二、前后端沟通 传递参数类型接口请求方式&#xff08;GET POST PUT DELETE.&#xff09;传参的方式… 三、后端编写接口文档&#xff08;包括但不限于以下几个方面&#xff09; 接口名称是否清晰 接口地址是否完整 接口请求方式是否正确 请求参…

HarmonyOS应用开发者基础认证【满分答案】

系列文章 HarmonyOS应用开发者基础认证【闯关习题 满分答案】 HarmonyOS应用开发者基础认证【满分答案】 HarmonyOS云开发基础认证【最新题库 满分答案】 目录 系列文章一、判断题二、单选题三、多选题 一、判断题 在Column和Row容器组件中&#xff0c;justifyContent用于设置…

处理数据中的缺失值--删除缺少值的行

两个最主要的处理缺失值的方法是&#xff1a; ❏ 删除缺少值的行&#xff1b; ❏ 填充缺失值&#xff1b; 我们首先将serum_insulin的中的字段值0替换为None&#xff0c;可以看到缺失值的数量为374个&#xff1b; print(pima[serum_insulin].isnull().sum()) pima[serum_insu…

HarmonyOS(七)——@BuilderParam装饰器

前言&#xff1a; 前面我们认识了Builder装饰器&#xff1a;自定义构建函数&#xff0c;今天我们继续认识下一个装饰器——BuilderParam装饰器。 当开发者创建了自定义组件&#xff0c;并想对该组件添加特定功能时&#xff0c;例如在自定义组件中添加一个点击跳转操作。若直接…

常见树种(贵州省):022绣线菊、月月青、金合欢、胡枝子、白刺花

摘要&#xff1a;本专栏树种介绍图片来源于PPBC中国植物图像库&#xff08;下附网址&#xff09;&#xff0c;本文整理仅做交流学习使用&#xff0c;同时便于查找&#xff0c;如有侵权请联系删除。 图片网址&#xff1a;PPBC中国植物图像库——最大的植物分类图片库 一、绣线菊…

100天精通Python(可视化篇)——第109天:Pyecharts绘制各种常用地图(参数说明+代码实战)

文章目录 专栏导读一、地图应用场景二、参数说明1. 导包2. add函数 三、地图绘制实战1. 省市地图2. 中国地图3. 中国地图&#xff08;带城市&#xff09;4. 中国地图&#xff08;分段型&#xff09;5. 中国地图&#xff08;连续型&#xff09;6. 世界地图7. 行程轨迹地图8. 人口…

WebUI自动化学习(Selenium+Python+Pytest框架)001

开启另一篇学习之路_WebUI自动化 先来一波基础概念 1.自动化适合什么类型的项目: 重复性高,迭代频率高的回归测试。数据量大、手工难以实现的压力测试&#xff0c;手工执行效率低的兼容测试 2.自动化的优点: 高效率、可重复、减少人为错误、克服手工测试的局限性 3.自动化…

光线追踪-Peter Shirley的RayTracingInOneWeekend系列教程(book1-book3)代码分章节整理

自己码完了一遍了&#xff0c;把代码分章节整理了一下&#xff0c;可以按章节独立编译&#xff0c;运行, 也可以直接下载编译好的release版本直接运行。 项目地址&#xff1a; Github: https://github.com/disini/RayTracingInOneWeekendChaptByChapt ​ ​ ​ ​

17 Go的文件操作

概述 在上一节的内容中&#xff0c;我们介绍了Go的反射&#xff0c;包括&#xff1a;reflect.TypeOf、reflect.ValueOf、reflect.Value等。在本节中&#xff0c;我们将介绍Go的文件操作。在Go语言中&#xff0c;文件是一个抽象的概念&#xff0c;表示的是一段连续的字节序列。文…

iframe实现跨域通信的方法

在前端开发中&#xff0c;我们经常会遇到跨域通信的问题。跨域通信是指在浏览器的同源策略下&#xff0c;不同源之间的通信。本文将介绍如何通过 iframe 实现跨域通信&#xff0c;以及不跨域通信的方法和代码示例。 引言 跨域通信是指在浏览器的同源策略下&#xff0c;不同源之…

Rust语言入门教程(八) - 引用与借用

上一章的内容中我们讨论了Rust的所有权系统&#xff0c;当我们不想移动值的所有权时&#xff0c;我们可以使用引用和借用&#xff0c;而这正是本章想要讨论的问题。 引用&#xff08;References&#xff09; 引用允许你访问或修改数据而无需获取数据的所有权。在 Rust 中&…

阿里云MQTT: 子设备上线流程

0. 背景 阿里云网关子设备上平台的资料很少。有些厂家直接配置每个子设备的DeviceSecret到网关里&#xff0c;显然太麻烦了&#xff01;我经过阅读阿里文档&#xff0c;发现有些简化的方法&#xff0c;更便于客户使用&#xff0c;因此分享给大家。 1. 主要信息片段 子设备 $…

屏蔽WordPress评论中长URL地址方法

由于WordPress是比较常见的CMS程序之一&#xff0c;所以很多网络营销推广也会基于WP去群发外链和广告信息。这里&#xff0c;我们可以通过屏蔽特定关键字、屏蔽特定字符的方式&#xff0c;或者是屏蔽评论内容的长短来限制评论。还有一个我们可以通过评论内容的URL地址的长度来屏…

基于springboot+mysql实现的小区物业管理系统

基于springbootmysql实现的小区物业管理系统,演示地址:登录 演示账号&#xff1a;用户名:744621980qq.com 密码:123456,主要包含房屋管理(楼栋管理&#xff0c;单元管理&#xff0c;房屋管理)&#xff0c;车位管理&#xff0c;缴费管理&#xff0c;社区服务( 公告管理&#xf…

Linux socket编程(6):IO复用之select原理及例子

文章目录 1 五种I/O模型1.1 阻塞I/O模型1.2 非阻塞I/O模型1.3 I/O复用模型1.4 信号驱动I/O模型1.5 异步I/O模型 2 select函数3 select实战&#xff1a;实现多个套接字监听3.1 客户端3.2 服务端3.3 实验结果3.4 完整代码 在之前的网络编程中&#xff0c;我们遇到了一个问题&…

CentOS7 安装配置SFTP服务器详解

1、SFTP简介 SSH文件传输协议(英语:SSH File Transfer Protocol,也称Secret File Transfer Protocol,中文:安全文件传送协议,英文:Secure FTP或字母缩写:SFTP)是一种数据流连接,提供文件访问、传输和管理功能的

Qt TCP网络上位机的设计(通过网络编程与下位机结合)

目录 TCP 协议基础 QTcpServer 和 QAbstractSocket 主要接口函数 TCP 应用程序 1.服务端 2.客户端 上位机通过网络编程与下位机实现通信 TCP 协议基础 传输控制协议&#xff08;TCP&#xff0c;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的、基于…

mysql从库设置为只读

直奔主题&#xff0c;mysql设置为只读后&#xff0c;无法增删改。 设置命令&#xff1a; mysql> set global read_only1; #1是只读&#xff0c;0是读写 mysql> show global variables like %read_only%; 以下是相关说明&#xff1a; 1、对于数据库读写状态&#xf…

详解RT-DETR网络结构/数据集获取/环境搭建/训练/推理/验证/导出/部署

论文地址&#xff1a;RT-DETR论文地址 代码地址&#xff1a;RT-DETR官方下载地址 目录 一、本文介绍 二、RT-DETR的网络结构 2.1、模型概览 2.2、高效混合编码器 2.3、IoU感知查询选择 2.4、 可扩展的RT-DETR 三、RT-DERT的环境搭建 四、免费数据集获取 五、获取RT-D…

Linux基本命令一

Linux基本命令一 1、mkdir 命令 mkdir ​ **作用&#xff1a;**命令用来创建指定的名称的目录&#xff0c;要求创建目录的用户在当前目录中具有写权限&#xff0c;并且指定的目录名不能是当前目录中已有的目录 ​ **语法&#xff1a;**mkdir [选项] 目录 ​ **命令功能&am…