IDA查看std::string的各种操作

1 前言

相对C语言来说,C++中增加了很多特性,例如,构造函数、析构函数、异常处理等,因此,从汇编级别的语言看,C++就复杂很多。下面使用IDA工具查看std::string的一些操作背后的实现。

以下程序在Ubuntu 21.10(5.13.0-52-generic)编译,g++版本为gcc version 11.2.0 (Ubuntu 11.2.0-7ubuntu2)

2 创建std::string

#include <iostream>

int main() {

	std::string str("abc");

	return 0;
}

请添加图片描述

请添加图片描述

图中灰色的部分是调用的相关函数,基本就是函数的原型。

  • 调用std::allocator::allocator()
  • 将unk_2008的字符串(也就是给定的初始化字符串abc)地址给到rsi,将allocator()的返回值给到rdi,然后调用std::string的初始化函数
  • 然后分别调用allocator和std::string的析构函数,说明此时main函数已经执行结束,剩下的指令可以看做是main函数退出时执行的清理操作

程序结束时的清理逻辑包含两个部分:

  • 如果程序抛出异常,则会执行异常处理函数,此处的异常处理函数中调用了allocator的析构函数
  • 检查栈是否会溢出,如果是,则调用__stack_chk_fail进行检查

以下代码在分析时会忽略清理逻辑,直接查看程序主体逻辑。

3 std::string.find()

#include <iostream>

int main() {

	std::string str("abc");
	auto iter = str.find('a');
	if(iter == std::string::npos) {
		std::cout << "not found" << std::endl;
	}

	return 0;
}

请添加图片描述

  • 调用std::string.find()函数
  • 将返回值保存到临时变量,然后将该临时变量与全1比较,如果不为0判断成功,则直接结束,如果不为0判断失败,也就是为0,也就是相等,则执行中间的输出逻辑
  • 输出时,先将字符串地址通过rax中转后放到rsi,再将cout函数的地址放到rdi,然后创建ostream对象并调用ostream对象的operator<<函数进行输出

4 std::string.substr()

#include <iostream>
#include <string>

int main() {

	std::string str("abc");
	size_t pos = str.find('a');
	std::cout << str.substr(pos-2) << std::endl;

	return 0;
}

请添加图片描述

需要注意的是,如果直接使用g++ -o main main.cpp编译上述程序,在程序崩溃后,使用gdb查看core文件时,无法查看C++库的堆栈,如果要想看到上述的堆栈,需要使用静态编译:g++ -o main -static main.cpp

请添加图片描述

执行substr()时,先比较第一个参数和字符串长度,然后通过比较的结果进行跳转,ja指令表示大于则跳转,因此,这里的含义是,如果第一个参数大于字符串长度,则跳转到右侧的框内,然后组装好异常描述信息后抛出异常。因此,在执行substr()时需要先比较起始索引位置和字符串长度。

5 std::string.replace()

#include <iostream>
#include <string>

int main() {

	std::string str("abc");
	size_t pos = str.find('a');
	str.replace(pos, 2, "dd");
	std::cout << str << std::endl;

	return 0;
}

这里的调用跟上一个用例类似,先调用std::string的初始化函数,然后调用find()和replace()函数。

不过在使用replace()函数时,可能会出现下面这个堆栈:

请添加图片描述

这是因为,在调用std::string::replace()函数时,如果发现字符串的长度有变化,就会重新申请空间。

请添加图片描述

上面的汇编代码可以看出,首先调用_malloc函数分配内存,如果分配失败,则调用std::get_new_handler(),如果调用成功,则执行新std::get_new_handler()返回的函数,然后再次调用_malloc分配内存,如果还失败,则结束,如果调用std::get_new_handler()返回为空,则抛出异常退出。

因此,走到上述堆栈需要满足两个条件:

  • 调用_malloc分配内存失败
  • 没有设置new_handler,new_handler是在执行operator new失败时的处理函数,可以在里面销毁部分内存

6 总结

std::string提供了更高层次的字符串操作,并且提供了很多的方法,与C风格字符串相比,使用起来更加方便,但是,在这些方法的底层,编译器默默做了很多事情。当程序编译为二进制后,可以在Linux平台使用objdump工具查看汇编级别的代码,使用IDA则可以很直观的看到各操作之间的调用关系,因此,如果是定位core问题,或者想看某个操作具体做了什么,IDA工具是不二之选。当然,由于指令比较多,这里没有详细介绍每行指令的含义,但是,通过部分指令后面的函数名以及跳转关系,基本可以熟悉整体的调用流程。

本文使用IDA工具查看了std::string的创建、find()、substr()、replace()函数的流程。

  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值