您尚未登录。

#1 2021-04-20 16:39:52

matrikslee
会员
注册时间: 2017-04-21
帖子: 290
个人网站

《程序员的自我修养:链接、装载与库》演示程序运行错误

我最近在学习《程序员的自我修养:链接、装载与库》这本书,在7.7运行时装载的演示程序处遇到了问题(我都有点怀疑我是不是买了盗版书了,书里真的好多错漏点),涉及到gcc的嵌入式汇编和函数调用平栈相关,有点头晕,搞了许久没有办法,到论坛来问问看是否有大手子帮忙解答。
下面是书里原始的程序代码,

#include <stdio.h>
#include <dlfcn.h>

#define SETUP_STACK \
i = 2; \
while(++i < argc-1)  { \
    switch(argv[i][0]) { \
    case 'i': \
        asm volatile("push %0" :: \
        "r"(atoi(&argv[i][1])) ); \
        esp += 4; \
        break; \
    case 'd': \
        atof(&argv[i][1]); \
        asm volatile("subl $8, %esp\n" \
        "fstpl (%esp)" ); \
        esp += 8; \
        break; \
     case 's': \
        asm volatile("push %0":: \
        "r"(&argv[i][1]) ); \
        esp += 4; \
        break; \
     default: \
        printf("error argument type"); \
        goto exit_runso; \
     } \
} \

#define RESTORE_STACK \
     asm volatile("add %0, %%esp":: "r"(esp))

int main(int argc, char *argv[])
{
    void *handle;
    char *error;
    int i;
    int esp = 0;
    void *func;

    handle = dlopen(argv[1], RTLD_NOW);
    if(handle == 0) {
        printf("Can't findlibrary: %s\n", argv[1]);
        return -1;
    }

    func = dlsym(handle, argv[2]);
    if( (error = dlerror()) != NULL) {
        printf("Find symbol %s error: %s\n", argv[2], error);
        goto exit_runso;
    }

    switch(argv[argc-1][0]) {
    case 'i':
    {
        int (*func_int)() = func;
        SETUP_STACK;
        int ret = func_int();
        RESTORE_STACK;
        printf("ret = %d\n", ret);
        break;
    }
    case 'd':
    {
        int (*func_double)() = func;
        SETUP_STACK;
        double ret = func_double();
        RESTORE_STACK;
        printf("ret = %f\n", ret);
        break;
    }
    case 's':
    {
        char* (*func_str)() = func;
        SETUP_STACK;
        char *ret = func_str();
        RESTORE_STACK;
        printf("ret = %s\n", ret);
        break;
    }
    case 'v':
    {
        void (*func_void)() = func;
        SETUP_STACK;
        func_void();
        RESTORE_STACK;
        printf("ret = void\n");
        break;
    }
    }

exit_runso:
    dlclose(handle);
}

一开始我把代码手敲到电脑上,然后遇到了编译错误,发现gcc内嵌汇编的寄存器写法是两个百分号,然后还有unused value warning,
然后我修了内嵌汇编的编译错误,忽视unused value warning,然后运行返回double值的case:./runso /lib32/libm-2.16.so sin d2.0 d
然后程序就crash掉了,用gdb debug加上objdump反汇编,结果发现下面的问题:
1.  atof(&argv[\i][1]); 的调用因为没有用到返回值被gcc优化去掉了,所以根本就没有将argv[\i][1]转成double数传给sin调用,

然后我把setup_stack宏里面的case 'd'部分代码按照我自己的理解改成了如下情况修了问题1:

case 'd':
        asm volatile("subl $8, %%esp\n"
        "fstpl (%%esp)"::
        "f"(atof(&argv[i][1])) );
        esp += 8;
        break;

然后遇到了下面的问题2:
2.  在执行了SETUP_STACK里面case 'd'部分的内嵌汇编代码之后,变量i,esp,func_double的值都跑飞了,i变成了一个非常大的数字(预期值是4),esp变成了9(预期值是8),func_table这个函数指针的值变成了0x5(预期值应该是外部模块libm的sin函数在当前elf的got地址)

最近编辑记录 matrikslee (2021-08-10 09:46:24)

离线

#2 2021-04-24 12:36:29

xtricman
エクス·トリクマン
注册时间: 2012-12-26
帖子: 1,249

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

因为作者写的就有很多错漏点


反社会,精神极其不稳定,随时可能炸碎身边所有人

离线

#3 2021-08-06 19:23:40

natsunoyoru97
会员
注册时间: 2021-08-06
帖子: 9
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

我跑了一下你说的那段程序,在我的Arch Linux里一个字都没改也能跑通(除了几个警告,不过也没看到你说的unused value warning)。不过我之前看《程序员的自我修养:链接、装载与库》那本书的时候也的确因为环境踩过不少坑(因为这本书的代码是针对i386架构的,该死的macOS Catalina不支持32位害我踩了很多坑,代码改来改去都是因为环境真是太伤了),是不是你的环境出了点问题啊?

附一张运行成功的图:
fuKSBj.png

我贴下我的运行环境和编译的命令行,程序命名就别在意了:
运行环境:

cat /proc/version
Linux version 5.13.7-arch1-1 (linux@archlinux) (gcc (GCC) 11.1.0, GNU ld (GNU Binutils) 2.36.1) #1 SMP PREEMPT Sat, 31 Jul 2021 13:18:52 +0000

cc -v         
使用内建 specs。
COLLECT_GCC=cc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/11.1.0/lto-wrapper
目标:x86_64-pc-linux-gnu
配置为:/build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++,d --with-isl --with-linker-hash-style=gnu --with-system-zlib --enable-__cxa_atexit --enable-cet=auto --enable-checking=release --enable-clocale=gnu --enable-default-pie --enable-default-ssp --enable-gnu-indirect-function --enable-gnu-unique-object --enable-install-libiberty --enable-linker-build-id --enable-lto --enable-multilib --enable-plugin --enable-shared --enable-threads=posix --disable-libssp --disable-libstdcxx-pch --disable-libunwind-exceptions --disable-werror gdc_include_dir=/usr/include/dlang/gdc
线程模型:posix
Supported LTO compression algorithms: zlib zstd
gcc 版本 11.1.0 (GCC) 

编译用的命令行:

cc -m32 stack.c -o runso -ldl
./runso /usr/lib32/libm.so sin d2.0 d

像树一样自由。

离线

#4 2021-08-09 09:37:55

matrikslee
会员
注册时间: 2017-04-21
帖子: 290
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

natsunoyoru97 说:

我跑了一下你说的那段程序,在我的Arch Linux里一个字都没改也能跑通(除了几个警告,不过也没看到你说的unused value warning)。]

哇,太感动了,之前也就是尝试性的发在这里,竟然真的有兄弟帮忙验证程序的完整!!
不过这里看了兄弟的回复后我得为我在一楼中忘记说明运行环境细节的情况做个道歉,
我是在公司的x86_64环境下做的验证的,是Ubunt 14.04并且奇葩的是kernel被升级到4.19版本,但是glibc版本却是2011前后的某个版本(这个情况我还发在Archlinux的中文TG群里讨论过,总之就是很离谱),这里印象非常的深刻,因为之前有个开发任务需要在x86环境下用memfd_create调用模拟kernel内的相关行为,发现没有glibc wrapper,只能自己往syscall里填调用号手动实现。题外话加上删除线xD

其实也是我后来因为在公司内网linux服务器上运行遇到问题之后,没有想过把相关代码在家里的archlinux个人机器上去编译试试水,因为书和看书的行为都发生在公司(对,就是上班摸鱼xD)

我回去一定要用Archlinux的PC机器试试情况!!(确信

最近编辑记录 matrikslee (2021-08-09 09:48:26)

离线

#5 2021-08-09 13:14:57

natsunoyoru97
会员
注册时间: 2021-08-06
帖子: 9
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

matrikslee 说:
natsunoyoru97 说:

我跑了一下你说的那段程序,在我的Arch Linux里一个字都没改也能跑通(除了几个警告,不过也没看到你说的unused value warning)。]

哇,太感动了,之前也就是尝试性的发在这里,竟然真的有兄弟帮忙验证程序的完整!!
不过这里看了兄弟的回复后我得为我在一楼中忘记说明运行环境细节的情况做个道歉,
我是在公司的x86_64环境下做的验证的,是Ubunt 14.04并且奇葩的是kernel被升级到4.19版本,但是glibc版本却是2011前后的某个版本(这个情况我还发在Archlinux的中文TG群里讨论过,总之就是很离谱),这里印象非常的深刻,因为之前有个开发任务需要在x86环境下用memfd_create调用模拟kernel内的相关行为,发现没有glibc wrapper,只能自己往syscall里填调用号手动实现。题外话加上删除线xD

其实也是我后来因为在公司内网linux服务器上运行遇到问题之后,没有想过把相关代码在家里的archlinux个人机器上去编译试试水,因为书和看书的行为都发生在公司(对,就是上班摸鱼xD)

我回去一定要用Archlinux的PC机器试试情况!!(确信

没关系啦,因为这两本书相关的讨论几乎不会特意提这种问题,一般不特意指出来似乎都不会意识到233 当时我在看《程序员的自我修养:链接、装载与库》和CSAPP的时候因为奇葩的环境问题多踩了不少坑,只有踩过环境大坑的人才知道这玩意能有多坑X

我忘了补充glibc的版本了,我的Arch Linux环境里glibc是2.33(也就是最新的稳定版本)。不过感觉问题1可能还是gcc和它下面一些工具的版本影响更大,我在寄存器前面加了两个%%,汇编器就报错bad register name(汇编器的版本我已经在之前的回复里贴出来了,就那个GNU Binutils),我这边复现不出来你说的这两个bug,那个unused value warning无论我换什么编译器都没看到。你的Arch Linux环境配置也可能跟我不一样,我把这些工具的版本都发出来以作参考,运行环境真的很重要,这是长期以来的血泪教训(掩面

另外我不是兄弟,非要说的话是女兄弟kkkk

最近编辑记录 natsunoyoru97 (2021-08-09 15:35:52)


像树一样自由。

离线

#6 2021-08-09 16:15:05

matrikslee
会员
注册时间: 2017-04-21
帖子: 290
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

natsunoyoru97 说:

另外我不是兄弟,非要说的话是女兄弟kkkk

哈,这样子,不要在意这么多,这里只是表达同道者的意思,没有性别之分hhhh

natsunoyoru97 说:

当时我在看《程序员的自我修养:链接、装载与库》和CSAPP的时候因为奇葩的环境问题多踩了不少坑

CSAPP由于我完成的时间已经比较久远了,不太记得当时是否踩坑了,我印象中还好(坑不多的样子)?抑或是我当时动手比较少也说不定
我对CSAPP印象最深的却是关于CPU指令流水那一部分,我当时是看不下去直接跳过了 QQ

natsunoyoru97 说:

不过感觉问题1可能还是gcc和它下面一些工具的版本影响更大,我在寄存器前面加了两个%%,汇编器就报错bad register name(汇编器的版本我已经在之前的回复里贴出来了,就那个GNU Binutils),我这边复现不出来你说的这两个bug,那个unused value warning无论我换什么编译器都没看到。

刚刚又在公司server上实验了一下,还是得两个%%表示寄存器才能编译,而且还是运行发生段错误,看起来这个情况随着四个月的时间并没有发生什么变化,
看了一下环境,gcc 4.8.4,glibc 2.19,都是Ubuntu 14.04下的,
算了,不管ubuntu这个奇葩环境了,反正意义不大,以后再也不碰这种奇葩环境了

natsunoyoru97 说:

运行环境真的很重要,这是长期以来的血泪教训(掩面

有了这次踩坑经验,不能再赞同了!

最近编辑记录 matrikslee (2021-08-09 16:20:50)

离线

#7 2021-08-09 19:00:46

依云
会员
所在地: a.k.a. 百合仙子
注册时间: 2011-08-21
帖子: 6,623
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

matrikslee 说:

刚刚又在公司server上实验了一下,还是得两个%%表示寄存器才能编译,而且还是运行发生段错误,看起来这个情况随着四个月的时间并没有发生什么变化,

还是那些个版本的软件,你再放七年也不会变的 🤣

最近编辑记录 依云 (2021-08-09 19:27:30)

离线

#8 2021-08-09 19:04:16

natsunoyoru97
会员
注册时间: 2021-08-06
帖子: 9
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

哈,这样子,不要在意这么多,这里只是表达同道者的意思,没有性别之分hhhh

2333

CSAPP由于我完成的时间已经比较久远了,不太记得当时是否踩坑了,我印象中还好(坑不多的样子)?抑或是我当时动手比较少也说不定

我是在macOS上做的实验,它和linux总归还是有些差异(毕竟部分基于freeBSD而不是linux),所以才会有那么几个不会出现在linux上的问题,不过相对于《程序员的修养》的确好多了。

刚刚又在公司server上实验了一下,还是得两个%%表示寄存器才能编译,而且还是运行发生段错误,看起来这个情况随着四个月的时间并没有发生什么变化,
看了一下环境,gcc 4.8.4,glibc 2.19,都是Ubuntu 14.04下的,
算了,不管ubuntu这个奇葩环境了,反正意义不大,以后再也不碰这种奇葩环境了

这个gcc的版本太低了(掩面,但似乎不是会导致编译异常的原因。因为这段代码没有把操作数和寄存器混用的指令,不需要%%来区分寄存器(除非汇编器只支持extended asm格式)。这个gcc的汇编器也不知道是整了什么奇葩设置,十有八九把这段代码当成extended asm格式生了成汇编文件。


像树一样自由。

离线

#9 2021-08-09 19:23:40

matrikslee
会员
注册时间: 2017-04-21
帖子: 290
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

用自己的Archlinux机器实验了一下,先看环境,看起来是与@natsunoyoru97的情况对齐的。

$ gcc -v
使用内建 specs。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/11.1.0/lto-wrapper
目标:x86_64-pc-linux-gnu
配置为:/build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++,d --with-isl --with-linker-hash-style=gnu --with-system-zlib --enable-__cxa_atexit --enable-cet=auto --enable-checking=release --enable-clocale=gnu --enable-default-pie --enable-default-ssp --enable-gnu-indirect-function --enable-gnu-unique-object --enable-install-libiberty --enable-linker-build-id --enable-lto --enable-multilib --enable-plugin --enable-shared --enable-threads=posix --disable-libssp --disable-libstdcxx-pch --disable-libunwind-exceptions --disable-werror gdc_include_dir=/usr/include/dlang/gdc
线程模型:posix
Supported LTO compression algorithms: zlib zstd
gcc 版本 11.1.0 (GCC) 
$ ldd --version
ldd (GNU libc) 2.33
Copyright (C) 2021 自由软件基金会。
这是一个自由软件;请见源代码的授权条款。本软件不含任何没有担保;甚至不保证适销性
或者适合某些特殊目的。
由 Roland McGrath 和 Ulrich Drepper 编写。

oh 这该死的中文locale(雾

然后我使用编译指令对源码进行编译,得到如下警告,但是并没有错误!

$ gcc -m32 runso.c -o runso -ldl
runso.c: 在函数‘main’中:
runso.c:10:13: 警告:隐式声明函数‘atoi’ [-Wimplicit-function-declaration]
   10 |         "r"(atoi(&argv[i][1])) ); 
      |             ^~~~
runso.c:57:9: 附注:in expansion of macro ‘SETUP_STACK’
   57 |         SETUP_STACK;
      |         ^~~~~~~~~~~
runso.c:14:9: 警告:隐式声明函数‘atof’ [-Wimplicit-function-declaration]
   14 |         atof(&argv[i][1]); 
      |         ^~~~
runso.c:57:9: 附注:in expansion of macro ‘SETUP_STACK’
   57 |         SETUP_STACK;
      |         ^~~~~~~~~~~
runso.c:76:21: 警告:initialization of ‘char *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
   76 |         char *ret = func_str();
      |                     ^~~~~~~~

而且看起来运行也是OK的 @@

λ matrikslee [~/Documents/sourcecode] → ./runso /usr/lib32/libm-2.33.so sin d2.0 d  
ret = 0.000000
natsunoyoru97 说:

这个gcc的版本太低了

破案了!这该死的远古版本编译器,看起来竟然比书里作者用的还要老!

依云 说:

PS: 原来论坛用的是不支持表情的 MySQL……

话说仙子这个话是从何说起的(好奇

不过还是有一个问题,好像

./runso /usr/lib32/libm.so sin d100.0 d

这条指令用下去,不管参数是d多少,返回结果都是0!
看起来细节上还是需要进行更多的考究

最近编辑记录 matrikslee (2021-08-10 09:46:58)

离线

#10 2021-08-09 19:36:06

natsunoyoru97
会员
注册时间: 2021-08-06
帖子: 9
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

破案了!这该死的远古版本编译器,看起来竟然比书里作者用的还要老!

这里我提出一点异议。我也试过gcc 4.8.4汇编这段代码(不过这次不是在本机上,而是用的Compiler Explorer),报错虽然和新版的gcc不一样,但也没有匪夷所思的unused value warning和bad register name报错。汇编结果如下:

<source>: In function 'main':
<source>:77:21: warning: initialization makes pointer from integer without a cast [enabled by default]
         char *ret = func_str();
                     ^
Compiler returned: 0

所以我怀疑更有可能是这个gcc下面的汇编器出了什么匪夷所思的问题/搞了匪夷所思的设置。不过这种问题还是别再纠结了,换个环境能正常运行就好了hhh

不过还是有一个问题,好像

./runso /usr/lib32/libm.so sin d100.0 d

这条指令用下去,不管参数是d多少,返回结果都是0!
看起来细节上还是需要进行更多的考究

真的!(深入沉思中
我跑了一下gdb,发现在这个用例中调用栈里的func_double没有传入任何参数,不管里面有没有值、值是什么结果都是0。其它情况同样存在这个问题(不过结果未必是0,反正都很奇怪)。

最近编辑记录 natsunoyoru97 (2021-08-09 22:17:02)


像树一样自由。

离线

#11 2021-08-09 19:42:46

依云
会员
所在地: a.k.a. 百合仙子
注册时间: 2011-08-21
帖子: 6,623
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

matrikslee 说:
依云 说:

PS: 原来论坛用的是不支持表情的 MySQL……

话说仙子这个话是从何说起的(好奇

刚修好了~

之前我尝试发 emoji 字符结果出来就变成 ?。

离线

#12 2021-08-10 09:53:22

matrikslee
会员
注册时间: 2017-04-21
帖子: 290
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

natsunoyoru97 说:

汇编结果如下:

<source>: In function 'main':
<source>:77:21: warning: initialization makes pointer from integer without a cast [enabled by default]
         char *ret = func_str();
                     ^
Compiler returned: 0

还有

runso.c:76:21: 警告:initialization of ‘char *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
   76 |         char *ret = func_str();
      |                     ^~~~~~~~

这个warning,是一个问题,我在主楼的代码里面,case 's'下的func_str的定义那里将char*敲成了int,原书是char*,已经在主楼内修正此问题,

另外,atoi/atof没有声明导致的warning我是通过include <stdlib.h>搞定的,不过看起来你那边没有这个warning?也许是不同系统下的头文件关系不一样吧。

natsunoyoru97 说:

我跑了一下gdb,发现在这个用例中调用栈里的func_double没有传入任何参数,不管里面有没有值、值是什么结果都是0。

看起来这里还是那个问题,SETUP_STACK宏里面的atof调用因为没有使用返回值,被编译器给优化掉了,我接下来会试试关掉编译器优化行为,来看看结果,review汇编代码。应该可以发现问题所在

最近编辑记录 matrikslee (2021-08-10 09:53:42)

离线

#13 2021-08-10 12:42:48

natsunoyoru97
会员
注册时间: 2021-08-06
帖子: 9
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

另外,atoi/atof没有声明导致的warning我是通过include <stdlib.h>搞定的,不过看起来你那边没有这个warning?也许是不同系统下的头文件关系不一样吧。

我在自己本机的Arch Linux上也是通过这种方式修正的问题。不过同样不带stdlib.h低版本的gcc却没有报错,也许是这么回事吧。

看起来这里还是那个问题,SETUP_STACK宏里面的atof调用因为没有使用返回值,被编译器给优化掉了,我接下来会试试关掉编译器优化行为,来看看结果,review汇编代码。应该可以发现问题所在

我试了一下runso的i分支(测试参数:[动态库libm.so路径] sqrt i4 i),结果错得更离谱,连0都不是,而是小到可以的负数,ret=-138264848 neutral
我对比了一下开了-O0和不开-O0的汇编代码,结果发现两者没有任何差异:) 我也怀疑问题在SETUP_STACK和RESTORE_STACK上面,而我找到了这样一段:

事实上随着操作系统发展,最新的调用方式并不会直接将参数压栈,而是先将参数存在寄存器中,因为直接操作寄存器总比操作内存效率要高,这样可以提高运行效率,这里涉及到调用约定问题,有兴趣的朋友可以自行了解。

因为这本书的Linux代码假定环境是i386架构,而现在很多机器都是x86_64架构,两种架构system call调用的寄存器和函数调用约定也变化不小(不要问我怎么知道的,我因此还重写了这本书里的TinyHelloWord.c),函数调用约定可以看看Calling Conventions,x86_64架构传参的函数调用是直接存寄存器里面而不是压栈(还没验证,这个我用gdb再看一下runso和RunsoSimple)。

最近编辑记录 natsunoyoru97 (2021-08-10 12:51:38)


像树一样自由。

离线

#14 2021-08-10 12:53:51

依云
会员
所在地: a.k.a. 百合仙子
注册时间: 2011-08-21
帖子: 6,623
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

natsunoyoru97 说:

x86_64架构传参的函数调用是直接存寄存器里面而不是压栈(还没验证,这个我用gdb再看一下runso和RunsoSimple)。

前几个存寄存器,放不下的压栈。

离线

#15 2021-08-10 13:45:23

matrikslee
会员
注册时间: 2017-04-21
帖子: 290
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

natsunoyoru97 说:

因为这本书的Linux代码假定环境是i386架构,而现在很多机器都是x86_64架构,两种架构system call调用的寄存器和函数调用约定也变化不小(不要问我怎么知道的,我因此还重写了这本书里的TinyHelloWord.c),函数调用约定可以看看Calling Conventions,x86_64架构传参的函数调用是直接存寄存器里面而不是压栈(还没验证,这个我用gdb再看一下runso和RunsoSimple)。

调用约定这个区别我倒是了解过,不过之前没想到是这里出了问题 Orz
不过应该也不会是这里的问题才对!-m32就是让编译器按照System V i386的调用约定去编译程序。
另外我有仔细读了一下这段代码的汇编指令,
看起来就是调用atof并将其返回值放到栈上以便传递给func这部分,被编译器直接丢弃了。
我在尝试应该要如何改代码来修正这部分(主要是试着将返回值放到寄存器里再去调用看看情况如何)

最近编辑记录 matrikslee (2021-08-10 15:34:40)

离线

页脚