让内存错误无所遁形|GCC-fsanitize=address实战解析

原创 Linux二进制 2024-09-22 08:20

引言

在 C/C++ 编程中,内存错误如越界访问、内存泄漏、未初始化内存的读取等问题十分常见,这些错误通常难以排查和调试。为了帮助开发者检测这类内存问题,GCC 提供了 -fsanitize=address 选项,它通过集成 AddressSanitizer(地址消毒器)对内存问题进行动态检测。本文将介绍如何使用 GCC 的 -fsanitize=address 选项来定位内存问题,深入探讨其工作原理,并给出一个具体的代码演示。

工作原理

-fsanitize=address 是 AddressSanitizer(简称 ASan)的一部分,它通过编译时插入检测代码,在程序运行时进行内存访问监控。具体工作原理如下:

  • 内存区域标记ASan 会在程序启动时创建一个特殊的“影子内存”区域,该区域用来记录和标记程序的实际内存访问状态。每当程序分配或释放内存时,ASan 会更新影子内存中的状态。

  • 动态检测:程序运行时,ASan 会检测每次内存访问的合法性。比如,访问数组时会检测是否越界,访问已释放的内存时会检测是否为“悬空指针”。

  • 错误报告:一旦检测到内存访问违规,ASan 会立刻报告错误,并输出详细的调试信息,包括出错位置、违规的内存地址、堆栈信息等。这使得开发者可以快速定位并修复问题。

日常工作中, -fsanitize=address 主要用来监测以下几类问题:

  1. 堆缓冲区溢出:如访问数组超出范围。
  2. 栈缓冲区溢出:栈上的变量被非法访问。
  3. 全局缓冲区溢出:访问了超出全局变量定义的范围。
  4. 使用已释放的内存:使用了已经通过 free 释放的指针。
  5. 双重释放:对同一块内存重复调用 free
  6. 未初始化的堆内存使用

安装集成 libasan

在使用 -fsanitize=address 时,libasan 是关键组件。它是一个运行时库,负责在程序执行时动态检测内存错误并生成报告。安装和集成 libasan 是确保 AddressSanitizer 正常工作的必要步骤。

在大多数现代 Linux 发行版中,libasan 都已经预装在默认的 GCC 工具链中,因此开发者只需确保使用支持 AddressSanitizer 的 GCC 版本。通常来说,GCC 从 4.8 版本开始就支持 AddressSanitizer(对于 C/C++)。你可以通过运行 gcc --version 来查看你当前使用的 GCC 版本。若系统中未安装,您可以通过包管理器安装:

  • 对于 Ubuntu 或 Debian 系统,可以运行以下命令:
sudo apt-get install libasanX -y

其中,X 代表特定的 GCC 版本,例如 libasan6 适用于 GCC 10libasan5 适用于 GCC 9

  • 对于 Fedora 或 CentOS 系统,可以运行:
sudo dnf install libasan

可以通过以下命令确认安装的版本:

gcc --version

只要编译器支持 ASan 并且安装了相应的库,您就可以使用 -fsanitize=address 选项了。

代码演示

为了演示 -fsanitize=address 的实际效果,我们来看一个简单的示例代码,存在缓冲区溢出的问题。

源文件 overflow_example.c :

#include 
#include

int main() {
int *arr = (int*)malloc(5 * sizeof(int));

// 错误:数组越界访问
for (int i = 0; i <= 5; i++) {
arr[i] = i;
}

printf("Array write complete!\n");

free(arr);
return 0;
}

我们首先不使用 -fsanitize=address ,直接编译该程序:

[root@localhost gcc_sanitize]# gcc -o overflow_example overflow_example.c

接着运行该程序:

[root@localhost gcc_sanitize]# ./overflow_example
Array write complete!

根据上面运行结果可知,即使程序中发生了数组访问越界,但是程序依然正常运行,如果不借助相关工具进行检测,我们将无法发现这个问题。这在无形中给我们的程序带来了安全隐患,这种错误可能会引起未定义行为,包括程序崩溃、数据被意外修改或安全漏洞。在一些情况下,它可能不会立即导致问题,但在其他情况下,它可能会破坏程序状态或者让攻击者利用这个漏洞执行恶意代码。

我们再使用 -fsanitize=address 选项编译该程序:

[root@localhost gcc_sanitize]# gcc -fsanitize=address -g overflow_example.c -o overflow_example

在这里,-g 用于生成调试信息,以便 ASan 在输出错误信息时能够提供源代码的行号。

接着运行程序:

[root@localhost gcc_sanitize]# ./overflow_example

运行该程序后,ASan 会检测到数组越界的问题,并输出以下的错误信息:

=================================================================
==10251==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x603000000054 at pc 0x0000004008e1 bp 0x7ffc71bf1010 sp 0x7ffc71bf1000
WRITE of size 4 at 0x603000000054 thread T0
#0 0x4008e0 in main /home/example/gcc_sanitize/overflow_example.c:9
#1 0x7fec0c592d84 in __libc_start_main (/lib64/libc.so.6+0x3ad84)
#2 0x4007bd in _start (/home/example/gcc_sanitize/overflow_example+0x4007bd)

0x603000000054 is located 0 bytes to the right of 20-byte region [0x603000000040,0x603000000054)
allocated by thread T0 here:
#0 0x7fec0ca0cba8 in __interceptor_malloc (/lib64/libasan.so.5+0xefba8)
#1 0x400887 in main /home/example/gcc_sanitize/overflow_example.c:5
#2 0x7fec0c592d84 in __libc_start_main (/lib64/libc.so.6+0x3ad84)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/example/gcc_sanitize/overflow_example.c:9 in main
Shadow bytes around the buggy address:
0x0c067fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c067fff8000: fa fa 00 00 00 fa fa fa 00 00[04]fa fa fa fa fa
0x0c067fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==10251==ABORTING

在这段输出中,AddressSanitizer (ASan) 报告了 堆缓冲区溢出heap-buffer-overflow)的问题。下面,我将详细解析这个错误报告的每一部分。

  1. 错误摘要
=================================================================
==10251==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x603000000054 at pc 0x0000004008e1 bp 0x7ffc71bf1010 sp 0x7ffc71bf1000
WRITE of size 4 at 0x603000000054 thread T0
  • ERROR: AddressSanitizer: heap-buffer-overflow

    • heap-buffer-overflow 是一种错误,意味着程序试图访问堆上分配的内存之外的地址。这里具体发生的是缓冲区溢出,通常是由于对数组或缓冲区的索引越界访问。
  • address 0x603000000054

    • 发生溢出的地址是 0x603000000054,它是程序试图访问的内存地址。
  • at pc 0x0000004008e1

    • pc 表示程序计数器(Program Counter),即程序执行时的指令地址。地址 0x0000004008e1 是发生错误的程序指令的内存地址。
  • bp 0x7ffc71bf1010 sp 0x7ffc71bf1000

    • bp 是基指针(Base Pointer),sp 是栈指针(Stack Pointer),它们指向当前栈帧和栈顶,帮助调试器跟踪函数调用和局部变量。对栈帧分析有帮助,但对解决这个内存问题用处不大。
  • WRITE of size 4 at 0x603000000054

    • 程序试图向地址 0x603000000054 写入 4 字节 的数据。通常,写入 4 字节表示程序在写入一个 int 类型的数据(因为大多数系统中,int 是 4 字节)。
  • thread T0

    • T0 表示这是主线程(主程序)的错误,因为在这个简单的程序中只有一个线程。
  1. 调用栈(Call Stack)
#0 0x4008e0 in main /home/example/gcc_sanitize/overflow_example.c:9
#1 0x7fec0c592d84 in __libc_start_main (/lib64/libc.so.6+0x3ad84)
#2 0x4007bd in _start (/home/example/gcc_sanitize/overflow_example+0x4007bd)

这是程序执行时发生错误的调用栈信息。

  • #0 0x4008e0 in main /home/example/gcc_sanitize/overflow_example.c:9
    • 错误发生在 main 函数中, overflow_example.c 文件的第 9 行 (overflow_example.c:9)。这条栈帧指向程序的核心问题,即试图访问一个越界的数组元素或缓冲区。
  • #1 0x7fec0c592d84 in __libc_start_main (/lib64/libc.so.6+0x3ad84)
    • 这个栈帧属于标准 C 库的启动函数 __libc_start_main,它是用来启动 main 函数的。
  • #2 0x4007bd in _start (/home/example/gcc_sanitize/overflow_example+0x4007bd)
    • 这是程序启动时的底层汇编调用栈,位于 _start 函数中。通常不需要深入分析这个栈帧。
  1. 堆内存分配的细节
0x603000000054 is located 0 bytes to the right of 20-byte region [0x603000000040,0x603000000054)
allocated by thread T0 here:
#0 0x7fec0ca0cba8 in __interceptor_malloc (/lib64/libasan.so.5+0xefba8)
#1 0x400887 in main /home/example/gcc_sanitize/overflow_example.c:5
#2 0x7fec0c592d84 in __libc_start_main (/lib64/libc.so.6+0x3ad84)

这里显示了错误发生时相关内存的分配情况:

  • 0x603000000054 is located 0 bytes to the right of 20-byte region [0x603000000040,0x603000000054)

    • 地址 0x603000000054 刚好是一个 20 字节堆内存区域的边界([0x603000000040, 0x603000000054))。这意味着程序试图访问数组或缓冲区的下一个元素,而实际上它正位于分配内存的末尾。这个行为导致了 越界写入 错误。
  • allocated by thread T0 here

    • 该段内存是由 malloc 分配的。

    • #0 0x7fec0ca0cba8 in __interceptor_malloc (/lib64/libasan.so.5+0xefba8):堆内存是在 __interceptor_malloc 中分配的,这是 AddressSanitizer 用来拦截和跟踪内存分配的函数。

    • #1 0x400887 in main /home/example/gcc_sanitize/overflow_example.c:5:堆内存的分配发生在 main 函数中,overflow_example.c 文件的第 5 行 (overflow_example.c:5)。这通常是一个 malloc 函数调用,用于分配数组或缓冲区。

  1. 错误总结
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/example/gcc_sanitize/overflow_example.c:9 in main

这一行总结了错误:

  • SUMMARY: AddressSanitizer: heap-buffer-overflow

    • ASan 检测到堆缓冲区溢出。
  • /home/example/gcc_sanitize/overflow_example.c:9 in main

    • 问题发生在 main 函数中,overflow_example.c 文件的第 9 行。
  1. Shadow Bytes(影子字节)
Shadow bytes around the buggy address:
0x0c067fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c067fff8000: fa fa 00 00 00 fa fa fa 00 00[04]fa fa fa fa fa
0x0c067fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

影子字节 显示了程序访问的内存区域的状态。ASan 将内存分成影子字节(Shadow Bytes),用于标记内存的有效性和分配状态。

  • fa fa 表示这个区域是堆内存的 "左边界" 或 "红区"(redzone),即不可访问的内存区域。AddressSanitizer 在堆内存块周围设置 "红区",当程序试图访问红区时,它会检测到非法访问。

  • [04] 标记了导致错误的具体字节位置。这是程序试图访问的越界地址。

  1. Shadow Byte Legend(影子字节含义)
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb

这是 AddressSanitizer 提供的影子字节含义说明,用来帮助分析不同类型的内存错误。我们这里的 fa 表示 "堆左红区"(heap left redzone),这是由 ASan 添加的保护区域,用于检测越界访问。

  1. ==10251==ABORTING

这一句的含义是程序因为严重错误而 终止执行。这是 AddressSanitizer 检测到内存问题(在本例中是 heap-buffer-overflow,即堆缓冲区溢出)之后主动中止程序执行的标志。

详细解释如下:

  • ==10251==: 这是程序的进程 IDPID),在这次运行中,程序的 PID 是 10251。每个正在运行的程序都会被操作系统分配一个唯一的 PID,方便识别和管理。

  • ABORTING: 表示程序因为遇到了无法继续执行的错误而被中止。在这种情况下,AddressSanitizer 发现了严重的内存问题(如缓冲区溢出),如果不终止程序,继续运行可能会导致崩溃、数据损坏或其他不可预期的后果。为保证程序的安全性,AddressSanitizer 决定直接中止执行。

因此,==10251==ABORTING 表示 PID 为 10251 的程序因为 AddressSanitizer 检测到的错误而被迫终止,防止进一步执行造成更严重的问题。


从以上分析可以推测,代码试图在超出堆分配的缓冲区或数组边界时写入数据。这个问题通常是因为程序员试图访问超过数组或指针有效范围的元素。可能的修复方法如下:

  • 检查数组的大小和下标,确保访问不超过分配的范围。
  • 如果使用了 malloc 动态分配内存,请确保为数组或缓冲区分配足够的空间。
  • 使用像 calloc 这样的方法来分配并初始化内存,有助于避免一些潜在的未初始化问题。

我们可以很容易地修复这个问题,确保循环范围正确:

for (int i = 0; i < 5; i++) {
arr[i] = i;
}

修复后,使用 -fsanitize=address 再次编译并运行程序,不会再出现溢出错误。结果如下:

[root@localhost gcc_sanitize]# gcc -fsanitize=address -g overflow_example.c -o overflow_example
[root@localhost gcc_sanitize]# ./overflow_example
Array write complete!

总结

GCC 提供的 -fsanitize=address 是一个强大的工具,可以帮助开发者检测和定位各种内存相关的 Bug。通过实时监控程序的内存操作,ASan 能够快速发现问题并生成详细的错误报告,极大地提高了调试的效率。尤其是在处理复杂的内存操作时,ASan 是不可或缺的调试助手。

如果你在开发过程中遇到了难以复现或定位的内存 Bug,不妨试试 -fsanitize=address,相信它会为你节省大量的时间和精力。


Linux二进制 Linux编程、内核模块、网络原创文章分享,欢迎关注"Linux二进制"微信公众号
评论
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 119浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 106浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 84浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 108浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 50浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 98浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 101浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 68浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 120浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 100浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 71浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 122浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦