GEM5入门学习2-添加模块和调试

上篇文章《GEM5入门学习1》介绍了如何使用GEM5来仿真,本文讲介绍如何在GEM5中添加一个自己的模块,并且调试GEM5。

1 添加一个简单模块

添加一个简单的模块需要添加如下几个文件(新添加的demo位于src/learning_gem5/part2目录下):

  • HelloObject.py:Python的模块接口文件,用于导出接口给仿真脚本使用。

  • hello_object.hh:自己实现的hello_object模块的头文件。

  • hello_object.cc:自己实现的hello_object模块的函数定义。

  • SConscript:编译脚本,在gem5编译系统中添加进去自己的模块。

先添加一个Python的文件,用于将C++模块导出,在python文件中指明新添加对象的名称以及对象的文件位置:

# file: src/learning_gem5/part2/HelloObject.py

from m5.params import *
from m5.SimObject import SimObject

class HelloObject(SimObject):
    type = 'HelloObject'
    cxx_header = "learning_gem5/part2/hello_object.hh"
    cxx_class = "gem5::HelloObject"

其次要实现hello_object.hh文件:

// file: src/learning_gem5/part2/hello_object.hh

#ifndef __LEARNING_GEM5_HELLO_OBJECT_HH__
#define __LEARNING_GEM5_HELLO_OBJECT_HH__
#include "params/HelloObject.hh"
#include "sim/sim_object.hh"

namespace gem5
{
    class HelloObject : public SimObject
    {
    public:
        HelloObject(const HelloObjectParams &p);
    };
} // namespace gem5

#endif // __LEARNING_GEM5_HELLO_OBJECT_HH__

以及模块的实现hello_object.cc文件:

// file src/learning_gem5/part2/hello_object.cc

#include "learning_gem5/part2/hello_object.hh"
#include <iostream>
namespace gem5
{
    HelloObject::HelloObject(const HelloObjectParams &params) : SimObject(params)
    {
        std::cout << "Hello World! From a SimObject!" << std::endl;
    }
} // namespace gem5

添加构建脚本:

# file: src/learning_gem5/part2/SConscript

Import('*')

SimObject('HelloObject.py', sim_objects=['HelloObject'])
Source('hello_object.cc')

上面四个文件时添加一个模块必要的文件,完成之后就可以重新构建gem5,新添加的文件就会被编译到gem的二进制文件中去

scons build/X86/gem5.opt

重新编译之后就可以写仿真脚本来运行我们自己写的模块了:

# file: configs/tutorial/part2/run_hello.py

import m5
from m5.objects import *

# 由于我们写了一个简单的验证模块,所以系统配置时不用指定CPU、内存、Cache等其他模块,单独的helloObject模块就可运行
root = Root(full_system = False)
root.hello = HelloObject()

m5.instantiate()
print("Beginning simulation!")
exit_event = m5.simulate()
print('Exiting @ tick {} because {}'.format(m5.curTick(), exit_event.getCause()))

运行:

./build/X86/gem5.opt configs/tutorial/part2/run_hello.py​

运行结果显示为:

qihangkong@ubuntu:~/git/gem5$ ./build/X86/gem5.opt ./configs/tutorial/part2/run_hello.py​ 
gem5 Simulator System.  https://www.gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 version 22.1.0.0
gem5 compiled Mar  6 2023 14:24:51
gem5 started Mar  6 2023 14:35:10
gem5 executing on ubuntu, pid 48426
command line: ./build/X86/gem5.opt './configs/tutorial/part2/run_hello.py​'

Global frequency set at 1000000000000 ticks per second
Hello World! From a SimObject!
Beginning simulation!
build/X86/sim/simulate.cc:192: info: Entering event queue @ 0.  Starting simulation...
Exiting @ tick 18446744073709551615 because simulate() limit reached

2 添加调试接口

GEM5中包含一套调试信息的实现接口,其支持自定义的log信息打印,可以在运行时指定 --debug-flags=XXX 来指定输出log的信息:

如输出DRAM的日志信息可以使用:

build/X86/gem5.opt --debug-flags=DRAM configs/learning_gem5/part1/simple.py | head -n 50

输出执行过程的日志信息可以使用:

build/X86/gem5.opt --debug-flags=Exec configs/learning_gem5/part1/simple.py | head -n 50

可以通过 --debug-help 来获取所有的debug信息:

build/X86/gem5.opt --debug-help

接下来我们在新添加的模块里面添加一个新的调试标签,就可以替换print输出。

实现需要先在前面创建的SConstruct文件添加申明一个Debug标签:

# file: src/learning_gem5/part2/SConstruct

Import("*")

DebugFlag("HelloExample")   # 这个声明必须在Import("*")后面

在SConstruct中声明了标签之后,构建系统将会自动生成一个 debug/HelloExample.hh 的头文件,在使用的时候直接包含这个头文件,就可以使用DPRINT宏来打印日志:

// file: src/learning_gem5/part2/hello_object.cc

#include "base/trace.hh"
#include "debug/HelloExample.hh"

#include "learning_gem5/part2/hello_object.hh"
#include <iostream>
namespace gem5
{
    HelloObject::HelloObject(const HelloObjectParams &params) : SimObject(params)
    {
        DPRINTF(HelloExample, "Created the hello object\n");
    }
} // namespace gem5

编译后运行命令:

./build/X86/gem5.opt --debug-flags=HelloExample configs/tutorial/part2/run_hello.py​

就会打印出来上面的日志:

gem5 Simulator System.  https://www.gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 version 22.1.0.0
gem5 compiled Mar  7 2023 11:27:08
gem5 started Mar  7 2023 11:41:32
gem5 executing on ubuntu, pid 66785
command line: ./build/X86/gem5.opt --debug-flags=HelloExample 'configs/tutorial/part2/run_hello.py​'

Global frequency set at 1000000000000 ticks per second
      0: hello: Create the hello object
Beginning simulation!
build/X86/sim/simulate.cc:192: info: Entering event queue @ 0.  Starting simulation...
Exiting @ tick 18446744073709551615 because simulate() limit reached

如果不加 --debug-flags就没有打印信息的输出。

3 事件驱动编程

GEM5是一个事件驱动的模型,在本节中将在上一节的demo基础上扩展为事件处理模型。

对于事件模型,每个事件将会触发钩子函数的执行,Gem5中提供一个简单的接口来创建事件模型。

首先要定义事件的钩子函数:

  • processEvent()函数:用来执行在每次事件发生的响应函数

  • EventFunctionWrapper event:执行任意事件函数的接口

  • startup()函数:用来使SimObjects调度内部事件。

// file: src/learning_gem5/part2/hello_object.hh

#ifndef __LEARNING_GEM5_HELLO_OBJECT_HH__
#define __LEARNING_GEM5_HELLO_OBJECT_HH__

#include "params/HelloObject.hh"
#include "sim/sim_object.hh"

namespace gem5
{
    class HelloObject : public SimObject
    {
    public:
        HelloObject(const HelloObjectParams &p);

        void startup();

    private:
        void processEvent();

        EventFunctionWrapper event;
    };
} // namespace gem5

#endif // __LEARNING_GEM5_HELLO_OBJECT_HH__

hello_object.cc文件中实现如下:

// file: src/learning_gem5/part2/hello_object.cc

#include "base/trace.hh"
#include "debug/HelloExample.hh"

#include "learning_gem5/part2/hello_object.hh"
#include <iostream>
namespace gem5
{
    HelloObject::HelloObject(const HelloObjectParams &params) :
        SimObject(params), 
        event([this]{processEvent();}, name())
    {
        DPRINTF(HelloExample, "this is function HelloObject\n");
    }

    void HelloObject::processEvent() {
        DPRINTF(HelloExample, "this is function processEvent()\n");
    }

    void HelloObject::startup() {
        DPRINTF(HelloExample, "this is function startup()\n");
        schedule(event, 100);
    }
} // namespace gem5

运行命令:

./build/X86/gem5.opt --debug-flags=HelloExample configs/tutorial/part2/run_hello.py

执行结果如下:

gem5 Simulator System.  https://www.gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 version 22.1.0.0
gem5 compiled Mar  7 2023 11:59:40
gem5 started Mar  7 2023 12:01:39
gem5 executing on ubuntu, pid 67893
command line: ./build/X86/gem5.opt --debug-flags=HelloExample 'configs/tutorial/part2/run_hello.py​'

Global frequency set at 1000000000000 ticks per second
      0: hello: this is function HelloObject
Beginning simulation!
      0: hello: this is function startup()
build/X86/sim/simulate.cc:192: info: Entering event queue @ 0.  Starting simulation...
    100: hello: this is function processEvent()
Exiting @ tick 18446744073709551615 because simulate() limit reached

现在可以添加一些变量来控制程序的Tick时延,在hello_object.hh文件中:

// file: src/learning_gem5/part2/hello_object.hh

#ifndef __LEARNING_GEM5_HELLO_OBJECT_HH__
#define __LEARNING_GEM5_HELLO_OBJECT_HH__
#include "params/HelloObject.hh"
#include "sim/sim_object.hh"

namespace gem5
{
    class HelloObject : public SimObject
    {
    public:
        HelloObject(const HelloObjectParams &p);

        void startup();
    private:
        void processEvent();

        EventFunctionWrapper event;

        const Tick latency;
        int timeLeft;
    };
} // namespace gem5


#endif // __LEARNING_GEM5_HELLO_OBJECT_HH__

hello_object.cc:

// file src/learning_gem5/part2/hello_object.cc

#include "base/trace.hh"
#include "debug/HelloExample.hh"

#include "learning_gem5/part2/hello_object.hh"
#include <iostream>
namespace gem5
{
    HelloObject::HelloObject(const HelloObjectParams &params) :
        SimObject(params), event([this]{processEvent();}, name()), latency(100), timeLeft(10)
    {
        DPRINTF(HelloExample, "this is function HelloObject\n");
    }

    void HelloObject::processEvent() {
        DPRINTF(HelloExample, "this is function processEvent()\n");

        timeLeft --;
        if (timeLeft <= 0) {
            DPRINTF(HelloExample, "Done\n");
        } else {
            schedule(event, curTick() + latency);
        }
    }

    void HelloObject::startup() {
        DPRINTF(HelloExample, "this is function startup()\n");
        schedule(event, latency);
    }
} // namespace gem5

运行输出如下:

qihangkong@ubuntu:~/git/gem5$ ./build/X86/gem5.opt --debug-flags=HelloExample configs/tutorial/part2/run_hello.py​ 
gem5 Simulator System.  https://www.gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 version 22.1.0.0
gem5 compiled Mar  7 2023 15:02:47
gem5 started Mar  7 2023 15:22:11
gem5 executing on ubuntu, pid 73107
command line: ./build/X86/gem5.opt --debug-flags=HelloExample 'configs/tutorial/part2/run_hello.py​'

Global frequency set at 1000000000000 ticks per second
      0: hello: this is function HelloObject
Beginning simulation!
      0: hello: this is function startup()
build/X86/sim/simulate.cc:192: info: Entering event queue @ 0.  Starting simulation...
    100: hello: this is function processEvent()
    200: hello: this is function processEvent()
    300: hello: this is function processEvent()
    400: hello: this is function processEvent()
    500: hello: this is function processEvent()
    600: hello: this is function processEvent()
    700: hello: this is function processEvent()
    800: hello: this is function processEvent()
    900: hello: this is function processEvent()
   1000: hello: this is function processEvent()
   1000: hello: Done
Exiting @ tick 18446744073709551615 because simulate() limit reached

4 从Python传参数

上面的例子中,参数是定义在程序中的,本节介绍如何通过python脚本传递参数。

首先要修改src/learning_gem5/part2/HelloObject.py添加参数:

# file: src/learning_gem5/part2/HelloObject.py

from m5.params import *
from m5.SimObject import SimObject

class HelloObject(SimObject):
    type = 'HelloObject'
    cxx_header = "learning_gem5/part2/hello_object.hh"
    cxx_class = "gem5::HelloObject"

    time_to_wait = Param.Latency("time before firing the event")
    number_of_fires = Param.Int(1, "Number of times to fire the event before goodbye")

修改hello_object.cc文件中的变量初始化方式,从params.time_to_wait和params.number_of_fires中初始化:

// file src/learning_gem5/part2/hello_object.cc

#include "base/trace.hh"
#include "debug/HelloExample.hh"

#include "learning_gem5/part2/hello_object.hh"
#include <iostream>
namespace gem5
{
    HelloObject::HelloObject(const HelloObjectParams &params) :
        SimObject(params), event([this]{processEvent();}, name()), latency(params.time_to_wait), timeLeft(params.number_of_fires)
    {
        DPRINTF(HelloExample, "this is function HelloObject\n");
    }

    void HelloObject::processEvent() {
        DPRINTF(HelloExample, "this is function processEvent()\n");

        timeLeft --;
        if (timeLeft <= 0) {
            DPRINTF(HelloExample, "Done\n");
        } else {
            schedule(event, curTick() + latency);
        }
    }

    void HelloObject::startup() {
        DPRINTF(HelloExample, "this is function startup()\n");
        schedule(event, latency);
    }
} // namespace gem5

因为是从python脚本中传的参数进来,所以要修改python脚本:

# file: configs/tutorial/part2/run_hello.py

import m5
from m5.objects import *

# 由于我们写了一个间的验证模块,所以系统配置时不用指定CPU、内存、Cache等其他模块,helloObject就可运行
root = Root(full_system = False)
root.hello = HelloObject()
root.hello.time_to_wait = '1ns'

m5.instantiate()
print("Beginning simulation!")
exit_event = m5.simulate()
print('Exiting @ tick {} because {}'.format(m5.curTick(), exit_event.getCause()))

运行结果如下:

qihangkong@ubuntu:~/git/gem5$ ./build/X86/gem5.opt --debug-flags=HelloExample configs/tutorial/part2/run_hello.py​ 
gem5 Simulator System.  https://www.gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 version 22.1.0.0
gem5 compiled Mar  7 2023 15:57:18
gem5 started Mar  7 2023 15:59:05
gem5 executing on ubuntu, pid 77889
command line: ./build/X86/gem5.opt --debug-flags=HelloExample 'configs/tutorial/part2/run_hello.py​'

Global frequency set at 1000000000000 ticks per second
      0: hello: this is function HelloObject
Beginning simulation!
      0: hello: this is function startup()
build/X86/sim/simulate.cc:192: info: Entering event queue @ 0.  Starting simulation...
   1000: hello: this is function processEvent()
   1000: hello: Done
Exiting @ tick 18446744073709551615 because simulate() limit reached

5 传更多的参数

还可以传递其他的SimObject作为参数,在这个例子中设置了一个HelloObject,HelloObject中包含一个GoodbyeObject的对象,其中GoodbyeObject通过参数传递给HelloObject。

实现HelloObject执行倒计时之后给GoodbyeObject发送消息,直到消息填满GoodbyeObject的缓冲区为止。

实现包含文件:

  • src/learning_gem5/part2/SConscript:构建工程,包含两个对象HelloObject和GoodbyeObject

  • src/learning_gem5/part2/goodbye_object:新增加一个goodbye_object对象的实现

  • src/learning_gem5/part2/hello_object:更新hello_object,给goodbye发送消息

  • src/learning_gem5/part2/HelloObject.py:导出GoodbyeObject

首先创建一个新的GoodbyeObject的实现goodbye_object.hh:

// file: src/learning_gem5/part2/goodbye_object.hh

#ifndef __LEARNING_GEM5_GOODBYE_OBJECT_HH__
#define __LEARNING_GEM5_GOODBYE_OBJECT_HH__

#include <string>
#include "params/GoodbyeObject.hh"
#include "sim/sim_object.hh"
namespace gem5
{
    class GoodbyeObject : public SimObject
    {
    private:
        void processEvent();

        // 在每一次迭代填充缓冲区,如果缓冲区未满,此函数将调用其他的事件,继续填充
        void fillBuffer();
        EventWrapper<GoodbyeObject, &GoodbyeObject::processEvent> event;

        float bandwidth;
        int bufferSize;
        char *buffer;
        std::string message;
        int bufferUsed;

    public:
        GoodbyeObject(const GoodbyeObjectParams &params);
        ~GoodbyeObject();

        void sayGoodBye(std::string name);
    };
}

#endif

goodbye_object.cc:

// file: src/learning_gem5/part2/goodbye_object.cc

#include "learning_gem5/part2/goodbye_object.hh"

#include "base/trace.hh"
#include "debug/Hello.hh"
#include "sim/sim_exit.hh"

namespace gem5 {

GoodbyeObject::GoodbyeObject(const GoodbyeObjectParams &params) :
    SimObject(params),
    event(*this),
    bandwidth(params.write_bandwidth),
    bufferSize(params.buffer_size),
    buffer(nullptr),
    bufferUsed(0)
{
    buffer = new char[bufferSize];
    DPRINTF(Hello, "Create the goodbye object\n");
}

GoodbyeObject::~GoodbyeObject()
{
    delete[] buffer;
}

void GoodbyeObject::processEvent()
{
    DPRINTF(Hello, "Processing the event\n");
    fillBuffer();
}

void GoodbyeObject::sayGoodBye(std::string other_name)
{
    DPRINTF(Hello, "Saying Goodbye to %s\n", other_name);
    message = "Goodbye " + other_name + "!!";

    fillBuffer();
}

void GoodbyeObject::fillBuffer()
{
    assert(message.length() > 0);

    int bytes_copied = 0;
    for (auto it = message.begin(); it<message.end() && bufferUsed < bufferSize - 1; it++, bufferUsed++, bytes_copied++) {
        buffer[bufferUsed] = *it;
    }

    if (bufferUsed < bufferSize -1) {
        // buffer 未填充满
        DPRINTF(Hello, "Scheduing another fillbuffer in %d ticks\n", bandwidth * bytes_copied);
        schedule(event, curTick() + bandwidth * bytes_copied);
    } else {
        DPRINTF(Hello, "Goodbye done copying~\n");
        exitSimLoop(buffer, 0, curTick() + bandwidth * bytes_copied);
    }
}

} // namespace gem5

修改HelloObject.py,声明GoodbyeObject,并在HelloObject中通过参数传递:

# file: src/learning_gem5/part2/HelloObject.py

from m5.params import *
from m5.SimObject import SimObject

class GoodbyeObject(SimObject):
    type = "GoodbyeObject"
    cxx_header = "learning_gem5/part2/goodbye_object.hh"
    cxx_class = "gem5::GoodbyeObject"

    buffer_size = Param.MemorySize('1kB', "size of buffer to fill with goodbye")
    write_bandwidth = Param.MemoryBandwidth('100MB/s', "Bandwidth to fill the buffer")

class HelloObject(SimObject):
    type = "HelloObject"
    cxx_header = "learning_gem5/part2/hello_object.hh"
    cxx_class = "gem5::HelloObject"

    time_to_wait = Param.Latency("2us", "Time before firing the event")
    number_of_fires = Param.Int(
        10, "Number of times to fire the event before googbye"
    )
    goodbye_object = Param.GoodbyeObject("A goodbye object")

对应的hello_object.hh也需要修改:

// file: src/learning_gem5/part2/hello_object.hh

#ifndef __LEARNING_GEM5_HELLO_OBJECT_HH__
#define __LEARNING_GEM5_HELLO_OBJECT_HH__

#include "learning_gem5/part2/goodbye_object.hh"
#include "params/HelloObject.hh"
#include "sim/sim_object.hh"

namespace gem5
{
    class HelloObject : public SimObject
    {
        public:
            HelloObject(const HelloObjectParams &p);

            void startup() override;

        private:
            void processEvent();

            EventFunctionWrapper event;
            GoodbyeObject *goodbye;
            const std::string myName;
            const Tick latency;
            int timesLeft;

    };
} // namespace gem5

#endif // __LEARNING_GEM5_HELLO_OBJECT_HH__

hello_object.cc:

// file: src/learning_gem5/part2/hello_object.cc

#include "base/trace.hh"
#include "debug/Hello.hh"

#include "learning_gem5/part2/hello_object.hh"
#include <iostream>

namespace gem5
{
    HelloObject::HelloObject(const HelloObjectParams &params) :
        SimObject(params),
        event([this]{processEvent();}, name()),
        goodbye(params.goodbye_object),
        myName(params.name),
        latency(params.time_to_wait),
        timesLeft(params.number_of_fires)
    {
        DPRINTF(Hello, "this is function HelloObject()\n");
    }

    void HelloObject::processEvent() {
        timesLeft --;
        DPRINTF(Hello, "Hello world! Processing the event! %d left\n",
                timesLeft);
        if (timesLeft <= 0) {
            DPRINTF(Hello, "Done Firing!\n");
            goodbye->sayGoodBye(myName);
        } else {
            schedule(event, curTick() + latency);
        }
    }

    void HelloObject::startup() {
        DPRINTF(Hello, "this is function startup()\n");
        schedule(event, latency);
    }

} // namespace gem5

SConscript构建脚本修改如下:

# file: src/learning_gem5/part2/SConstruct

Import('*')

DebugFlag('Hello')

SimObject('HelloObject.py', sim_objects=['HelloObject', 'GoodbyeObject'])
Source('hello_object.cc')
Source('goodbye_object.cc')

最后还需要修改 configs/tutorial/part2/run_hello.py 文件:

# file: configs/tutorial/part2/run_hello.py

import m5
from m5.objects import *

# 由于我们写了一个简单的验证模块,所以系统配置时不用指定CPU、内存、Cache等其他模块,单独的helloObject模块就可运行
root = Root(full_system=False)

root.hello = HelloObject(time_to_wait = '2ns', number_of_fires = 5)
root.hello.goodbye_object = GoodbyeObject(buffer_size='100B')

m5.instantiate()
print("Beginning simulation!")
exit_event = m5.simulate()
print(f"Exiting @ tick {m5.curTick()} because {exit_event.getCause()}")

添加完成之后编译运行:

scons build/X86/gem5.opt -j 32
./build/X86/gem5.opt --debug-flags=Hello configs/tutorial/part2/run_hello.py

打印信息如下:

zac@zac-us:~/rworkdir/gem5/gem5$ ./build/X86/gem5.opt --debug-flags=Hello configs/tutorial/part2/run_hello.py
gem5 Simulator System.  https://www.gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 version 23.1.0.0
gem5 compiled Mar 22 2024 11:32:46
gem5 started Mar 22 2024 12:04:09
gem5 executing on zac-us, pid 60993
command line: ./build/X86/gem5.opt --debug-flags=Hello configs/tutorial/part2/run_hello.py

Global frequency set at 1000000000000 ticks per second
warn: No dot file generated. Please install pydot to generate the dot file and pdf.
      0: hello.goodbye_object: Create the goodbye object
      0: hello: this is function HelloObject()
Beginning simulation!
      0: hello: this is function startup()
src/sim/simulate.cc:199: info: Entering event queue @ 0.  Starting simulation...
   2000: hello: Hello world! Processing the event! 4 left
   4000: hello: Hello world! Processing the event! 3 left
   6000: hello: Hello world! Processing the event! 2 left
   8000: hello: Hello world! Processing the event! 1 left
  10000: hello: Hello world! Processing the event! 0 left
  10000: hello: Done Firing!
  10000: hello.goodbye_object: Saying Goodbye to hello
  10000: hello.goodbye_object: Scheduing another fillbuffer in 143055 ticks
 153055: hello.goodbye_object: Processing the event
 153055: hello.goodbye_object: Scheduing another fillbuffer in 143055 ticks
 296110: hello.goodbye_object: Processing the event
 296110: hello.goodbye_object: Scheduing another fillbuffer in 143055 ticks
 439165: hello.goodbye_object: Processing the event
 439165: hello.goodbye_object: Scheduing another fillbuffer in 143055 ticks
 582220: hello.goodbye_object: Processing the event
 582220: hello.goodbye_object: Scheduing another fillbuffer in 143055 ticks
 725275: hello.goodbye_object: Processing the event
 725275: hello.goodbye_object: Scheduing another fillbuffer in 143055 ticks
 868330: hello.goodbye_object: Processing the event
 868330: hello.goodbye_object: Goodbye done copying~
Exiting @ tick 954163 because Goodbye hello!!Goodbye hello!!Goodbye hello!!Goodbye hello!!Goodbye hello!!Goodbye hello!!Goodbye h

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马师傅哈哈哈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值