当前位置: 首页 > news >正文

Linux C++ realpath函数crash的解决方法

现象

调用realpath函数进程崩溃,可使用如下代码复现:

//test.cpp
#include "stdio.h"
#include "stdlib.h"
#include <limits.h>int main()
{char *p=new char[300];realpath("1.txt",p);printf("%s\n",p);delete[] p;return 0;
}

编译命令:g++ test.cpp -O1 -g

如果将O1改成O0则不复现,O2、O3都复现。说明这个BUG与gcc优化有关。

开发环境

ubuntu 22.04 x86_64虚拟机

gcc 11.4

笔者另有一个ubuntu 20.04 x86_64 + gcc 9.4的虚拟机也能复现。但在麒麟linux-arm64 + gcc 7.3的服务器上不能复现。目前不能确定是操作系统的差异导致的还是gcc还是CPU平台。

问题原因

realpath的第二个参数需要至少PATH_MAX字节的空间才能保证内存安全,即使路径没有那么长。

您可以阅读参考资料获取realpath和PATH_MAX的详细用法。

解决方案

申请PATH_MAX字节的内存即可,该值在多数linux系统中定义为4096。

//test.cpp
#include "stdio.h"
#include "stdlib.h"
#include <limits.h>int main()
{char *p=new char[PATH_MAX];realpath("1.txt",p);printf("%s\n",p);delete[] p;return 0;
}

为什么O0不复现

realpath内部是否优化与上述优化参数无关,因为它由glibc实现。通常都是有优化的,除非你手动编译glibc并关闭优化。

crash堆栈在realpath内部,为了搞清同一个环境下优化参数为什么会影响realpath内部,我们对main函数进行反汇编。

下面是O0和O1的反汇编结果:

Dump of assembler code for function main():
   0x00005555555551a9 <+0>:     endbr64
   0x00005555555551ad <+4>:     push   %rbp
   0x00005555555551ae <+5>:     mov    %rsp,%rbp
   0x00005555555551b1 <+8>:     sub    $0x10,%rsp
   0x00005555555551b5 <+12>:    mov    $0x12c,%edi
   0x00005555555551ba <+17>:    callq  0x555555555080 <_Znam@plt>
   0x00005555555551bf <+22>:    mov    %rax,-0x8(%rbp)
   0x00005555555551c3 <+26>:    mov    -0x8(%rbp),%rax
   0x00005555555551c7 <+30>:    mov    %rax,%rsi
   0x00005555555551ca <+33>:    lea    0xe33(%rip),%rax        # 0x555555556004
   0x00005555555551d1 <+40>:    mov    %rax,%rdi
=> 0x00005555555551d4 <+43>:    callq  0x555555555090 <realpath@plt>
   0x00005555555551d9 <+48>:    mov    -0x8(%rbp),%rax
   0x00005555555551dd <+52>:    mov    %rax,%rdi
   0x00005555555551e0 <+55>:    callq  0x5555555550b0 <puts@plt>
   0x00005555555551e5 <+60>:    cmpq   $0x0,-0x8(%rbp)
   0x00005555555551ea <+65>:    je     0x5555555551f8 <main()+79>
   0x00005555555551ec <+67>:    mov    -0x8(%rbp),%rax
   0x00005555555551f0 <+71>:    mov    %rax,%rdi
   0x00005555555551f3 <+74>:    callq  0x5555555550a0 <_ZdaPv@plt>
   0x00005555555551f8 <+79>:    mov    $0x0,%eax
   0x00005555555551fd <+84>:    leaveq
   0x00005555555551fe <+85>:    retq
End of assembler dump.

Dump of assembler code for function main():
   0x00005555555551a9 <+0>:     endbr64
   0x00005555555551ad <+4>:     push   %rbx
   0x00005555555551ae <+5>:     mov    $0x12c,%edi
   0x00005555555551b3 <+10>:    callq  0x555555555080 <_Znam@plt>
   0x00005555555551b8 <+15>:    mov    %rax,%rbx
   0x00005555555551bb <+18>:    mov    $0x12c,%edx
   0x00005555555551c0 <+23>:    mov    %rax,%rsi
   0x00005555555551c3 <+26>:    lea    0xe3a(%rip),%rdi        # 0x555555556004
=> 0x00005555555551ca <+33>:    callq  0x5555555550b0 <__realpath_chk@plt>
   0x00005555555551cf <+38>:    mov    %rbx,%rdi
   0x00005555555551d2 <+41>:    callq  0x5555555550a0 <puts@plt>
   0x00005555555551d7 <+46>:    mov    %rbx,%rdi
   0x00005555555551da <+49>:    callq  0x555555555090 <_ZdaPv@plt>
   0x00005555555551df <+54>:    mov    $0x0,%eax
   0x00005555555551e4 <+59>:    pop    %rbx
   0x00005555555551e5 <+60>:    retq
End of assembler dump.

可以看到优化后的代码调用了一个__realpath_chk函数,这个函数与realpath是不同的。

结论:开启优化后,编译器会用__realpath_chk代替realpath,这个函数的性能更好但容错更低,无论输出长度是多少都会使用PATH_MAX字节,长度不足会越界。

参考资料

realpath(3) - Linux man page (die.net)icon-default.png?t=N7T8https://linux.die.net/man/3/realpathC语言MAX_PATH和PATH_MAX的区别-CSDN博客文章浏览阅读40次。MAX_PATH和PATH_MAXhttps://blog.csdn.net/louObaichu/article/details/140496066

 -------------完--------------

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Spock单元测试框架使用介绍和实践
  • linux中当前目录、上级目录、上上级目录表示方法
  • python—爬虫爬取电影页面实例
  • AI 绘画|Midjourney设计Logo提示词
  • Unity | AssetBundle
  • 【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 卢小姐的生日礼物(200分) - 三语言AC题解(Python/Java/Cpp)
  • 哪些企业适合做ISO27001信息安全管理体系?
  • 定制QCustomPlot 带有ListView的QCustomPlot 全网唯一份
  • SpringAI简单使用(本地模型+自定义知识库)
  • Linux处理文件sed
  • Java 新手学习线路,Java 学习路线是怎样的?
  • uniapp自定义tabBar
  • unity2022 il2cpp 源码编译
  • 信息检索(39):Condenser: a Pre-training Architecture for Dense Retrieval
  • SpringBoot源码深度解析
  • [nginx文档翻译系列] 控制nginx
  • 【EOS】Cleos基础
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • GitUp, 你不可错过的秀外慧中的git工具
  • Hexo+码云+git快速搭建免费的静态Blog
  • Java 实战开发之spring、logback配置及chrome开发神器(六)
  • Java多线程(4):使用线程池执行定时任务
  • node学习系列之简单文件上传
  • Python连接Oracle
  • scrapy学习之路4(itemloder的使用)
  • Tornado学习笔记(1)
  • vagrant 添加本地 box 安装 laravel homestead
  • 电商搜索引擎的架构设计和性能优化
  • 复习Javascript专题(四):js中的深浅拷贝
  • 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 使用 Docker 部署 Spring Boot项目
  • 双管齐下,VMware的容器新战略
  • 网页视频流m3u8/ts视频下载
  • python最赚钱的4个方向,你最心动的是哪个?
  • Unity3D - 异步加载游戏场景与异步加载游戏资源进度条 ...
  • ​secrets --- 生成管理密码的安全随机数​
  • # C++之functional库用法整理
  • #进阶:轻量级ORM框架Dapper的使用教程与原理详解
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • $forceUpdate()函数
  • (07)Hive——窗口函数详解
  • (C语言)球球大作战
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (贪心) LeetCode 45. 跳跃游戏 II
  • (转)Android中使用ormlite实现持久化(一)--HelloOrmLite
  • *算法训练(leetcode)第三十九天 | 115. 不同的子序列、583. 两个字符串的删除操作、72. 编辑距离
  • .net 7 上传文件踩坑
  • .NET Micro Framework初体验
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .NET 跨平台图形库 SkiaSharp 基础应用
  • .NET开发人员必知的八个网站
  • .NET开源、简单、实用的数据库文档生成工具
  • .NET企业级应用架构设计系列之结尾篇
  • @Bean注解详解