首页 > WinDriver > WD-线程的创建流程(WRK)
2015四月8

WD-线程的创建流程(WRK)

[隐藏]

1.线程的创建

1.1.NtCreateThread 

线程的创建是从NtCreateThread 函数开始,其原型如下:

1
2
3
4
5
6
7
8
9
10
11
NTSTATUS
NtCreateThread(
    __out PHANDLE ThreadHandle,
    __in ACCESS_MASK DesiredAccess,
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
    __in HANDLE ProcessHandle,
    __out PCLIENT_ID ClientId,
    __in PCONTEXT ThreadContext,
    __in PINITIAL_TEB InitialTeb,
    __in BOOLEAN CreateSuspended
    )

NtCreateThread 所做的操作很简单,主要针对用户模式传入的参数可读可写做检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    try {
        if (KeGetPreviousMode () != KernelMode) {
            ProbeForWriteHandle (ThreadHandle);
 
            if (ARGUMENT_PRESENT (ClientId)) {
                ProbeForWriteSmallStructure (ClientId, sizeof (CLIENT_ID), sizeof (ULONG));
            }
 
            if (ARGUMENT_PRESENT (ThreadContext) ) {
                ProbeForReadSmallStructure (ThreadContext, sizeof (CONTEXT), CONTEXT_ALIGN);
            } else {
                return STATUS_INVALID_PARAMETER;
            }
            ProbeForReadSmallStructure (InitialTeb, sizeof (InitialTeb->OldInitialTeb), sizeof (ULONG));
        }
 
        CapturedInitialTeb.OldInitialTeb = InitialTeb->OldInitialTeb;
        if (CapturedInitialTeb.OldInitialTeb.OldStackBase == NULL &&
            CapturedInitialTeb.OldInitialTeb.OldStackLimit == NULL) {
            //
            // Since the structure size here is less than 64k we don't need to reprobe
            //
            CapturedInitialTeb = *InitialTeb;
        }
    } except (ExSystemExceptionFilter ()) {
        return GetExceptionCode ();
    }

之后,开始调用PspCreateThread,它是把InitialTeb放到了局部变量CapturedInitialTeb中,然后传入,其他多余参数全传NULL

  Status = PspCreateThread (ThreadHandle,
                              DesiredAccess,
                              ObjectAttributes,
                              ProcessHandle,
                              NULL,
                              ClientId,
                              ThreadContext,
                              &CapturedInitialTeb,
                              CreateSuspended,
                              NULL,
                              NULL);

   

1.2.PspCreateThread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NTSTATUS  
PspCreateThread(  
OUT PHANDLE ThreadHandle, // 如创建成功,返回线程句柄
IN ACCESS_MASK DesiredAccess,  // 线程的访问权限
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,//可选(可为NULL),指定了新线程对象的属性
IN HANDLE ProcessHandle,  // 线程所属进程的句柄
IN PEPROCESS ProcessPointer,  // 仅创建系统线程时指向PsInitialSystemProcess(system进程的EPROCESS),其余为NULL
OUT PCLIENT_ID ClientId OPTIONAL, // 返回线程的CLIENT_ID
IN PCONTEXT ThreadContext OPTIONAL,  // 用户模式线程的初始执行环境,系统线程此值为NULL
IN PINITIAL_TEB InitialTeb OPTIONAL,  // 为线程的TEB结构提供初始值
IN BOOLEAN CreateSuspended,  // 线程创建后是否挂起
IN PKSTART_ROUTINE StartRoutine OPTIONAL,  // 系统线程启动函数的地址
IN PVOID StartContext  // 指定系统线程启动函数的执行环境
);

PspCreateThread 函数仅仅被NtCreateThread 和PsCreateSystemThread 这两个函数调用,分别用于创建用户线程和系统线程对象。

用户线程的调用如上面NtCreateThread所述,系统线程的调用如下:

  Status = PspCreateThread (ThreadHandle,
                              DesiredAccess,
                              ObjectAttributes,
                              SystemProcess,
                              ProcessPointer,// 用户线程此处为NULL
                              ClientId,
                              NULL,          // 用户线程此处为ThreadContext
                              NULL,          // 用户线程此处为 &CapturedInitialTeb
                              FALSE,         // 用户线程此处可设置为TRUE来挂起线程
                              StartRoutine,  // 用户线程此处为NULL
                              StartContext); // 用户线程此处为NULL

ThreadContext 和InitialTeb 参数针对用户线程的创建操作,而StartRoutine 和StartContext参数则针对系统线程的创建操作。

它的流程如下:

1.首先获得当前线程对象,以及此次创建操作来自于内核模式还是用户模式(即PreviousMode)。然后根据进程句柄参数ProcessHandle获得相应的进程对象,放到局部变量Process中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 CurrentThread = PsGetCurrentThread ();
 
    if (StartRoutine != NULL) {
        PreviousMode = KernelMode;
    } else {
        PreviousMode = KeGetPreviousModeByThread (&CurrentThread->Tcb);
    }
    Teb = NULL;
 
    Thread = NULL;
    Process = NULL;
 
    if (ProcessHandle != NULL) {
        //
        // Process object reference count is biased by one for each thread.
        // This accounts for the pointer given to the kernel that remains
        // in effect until the thread terminates (and becomes signaled)
        //
 
        Status = ObReferenceObjectByHandle (ProcessHandle,
                                            PROCESS_CREATE_THREAD,
                                            PsProcessType,
                                            PreviousMode,
                                            &Process,
                                            NULL);
    } else {
        if (StartRoutine != NULL) {
            ObReferenceObject (ProcessPointer);
            Process = ProcessPointer;
            Status = STATUS_SUCCESS;
        } else {
            Status = STATUS_INVALID_HANDLE;
        }
    }
 
    if (!NT_SUCCESS (Status)) {
        return Status;
    }

  

2.如果用户模式传入,而Process为system进程的EPROCESS,则返回错误

1
2
3
4
5
6
7
8
9
  //
    // If the previous mode is user and the target process is the system
    // process, then the operation cannot be performed.
    //
 
    if ((PreviousMode != KernelMode) && (Process == PsInitialSystemProcess)) {
        ObDereferenceObject (Process);
        return STATUS_INVALID_HANDLE;
    }

  

3.调用ObCreateObject 函数创建一个线程对象ETHREAD。并把整个ETHREAD 结构清零。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Status = ObCreateObject (PreviousMode,
                             PsThreadType,
                             ObjectAttributes,
                             PreviousMode,
                             NULL,
                             sizeof(ETHREAD),
                             0,
                             0,
                             &Thread);
 
    if (!NT_SUCCESS (Status)) {
        ObDereferenceObject (Process);
        return Status;
    }
 
    RtlZeroMemory (Thread, sizeof (ETHREAD));

   

4.初始化ETHREAD的停止保护锁RundownProtect,引用计数值0

1
2
3
4
    //
    // Initialize rundown protection for cross thread TEB refs etc.
    //
    ExInitializeRundownProtection (&Thread->RundownProtect);

    

5.初始化ETHREAD的ThreadsProcess(指向由进程句柄参数解出来的EPROCESS 对象),Cid

1
2
3
4
5
    Thread->ThreadsProcess = Process;
    Thread->Cid.UniqueProcess = Process->UniqueProcessId;
    CidEntry.Object = Thread;
    CidEntry.GrantedAccess = 0;
    Thread->Cid.UniqueThread = ExCreateHandle (PspCidTable, &CidEntry);

  

6.初始化ETHREAD的其他域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
   // Initialize Mm
    //
 
    Thread->ReadClusterSize = MmReadClusterSize;
 
    //
    // Initialize LPC
    //
 
    KeInitializeSemaphore (&Thread->LpcReplySemaphore, 0L, 1L);
    InitializeListHead (&Thread->LpcReplyChain);
 
    //
    // Initialize Io
    //
 
    InitializeListHead (&Thread->IrpList);
 
    //
    // Initialize Registry
    //
 
    InitializeListHead (&Thread->PostBlockList);
 
    //
    // Initialize the thread lock
    //
 
    PspInitializeThreadLock (Thread);
 
    KeInitializeSpinLock (&Thread->ActiveTimerListLock);
    InitializeListHead (&Thread->ActiveTimerListHead);

  

7.获得进程的RundownProtect锁,以避免在创建过程中该进程被停掉(rundown)。直到该线程被插入到进程的线程链表中(通过调用KeStartThread 函数)以后,PspCreateThread 才释放该锁。

1
2
3
4
5
6
if (!ExAcquireRundownProtection (&Process->RundownProtect)) {
        ObDereferenceObject (Thread);
        return STATUS_PROCESS_IS_TERMINATING;
    }
...
ExReleaseRundownProtection (&Process->RundownProtect);

  

8.如果ThreadContext 非NULL,则此次创建的是用户模式线程,于是创建一个TEB,并且用InitialTeb 进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
if (ARGUMENT_PRESENT (ThreadContext)) {
 
        //
        // User-mode thread. Create TEB etc
        //
 
        Status = MmCreateTeb (Process, InitialTeb, &Thread->Cid, &Teb);
        if (!NT_SUCCESS (Status)) {
            ExReleaseRundownProtection (&Process->RundownProtect);
            ObDereferenceObject (Thread);
            return Status;
        }

 

9.用户模式线程,接着利用ThreadContext 中的程序指针(Eip 寄存器)来设置线程的启动地址StartAddress 域,并且将ThreadContext 中的Eax 寄存器设置到线程的Win32StartAddress域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
       try {
            //
            // Initialize kernel thread object for user mode thread.
            //
 
            Thread->StartAddress = (PVOID)CONTEXT_TO_PROGRAM_COUNTER(ThreadContext);
 
#if defined(_AMD64_)
 
            Thread->Win32StartAddress = (PVOID)ThreadContext->Rdx;
 
#elif defined(_X86_)
 
            Thread->Win32StartAddress = (PVOID)ThreadContext->Eax;
 
#else
 
#error "no target architecture"
 
#endif
 
        } except (EXCEPTION_EXECUTE_HANDLER) {
 
            Status = GetExceptionCode();
        }

  

10.用户模式线程,接着调用KeInitThread 函数,根据进程对象中的信息来初始化新线程的一些属性,包括跟同步相关的各个域,例如同步头(Header域)、WaitBlock 的初始化,等等。线程的系统服务表(ServiceTable 域)以及与APC 和定时器相关的域也在这个函数中被初始化。此外,线程的内核栈也在这个函数中被创建并初始化。最后,KeInitThread 函数根据所提供的参数信息调用KiInitializeContextThread,以便完成与特定处理器相关的执行环境的初始化。因此,当这个函数返回时,新线程的状态是“已初始化(Initialized)”。

1
2
3
4
5
6
7
8
9
10
   if (NT_SUCCESS (Status)) {
            Status = KeInitThread (&Thread->Tcb,
                                   NULL,
                                   PspUserThreadStartup,
                                   (PKSTART_ROUTINE)NULL,
                                   Thread->StartAddress,
                                   ThreadContext,
                                   Teb,
                                   &Process->Pcb);
       }

 

11.系统线程,首先在CrossThreadFlags 标志中设置系统线程位。然后将线程的启动地址设置为StartRoutine 参数。最后同样地调用KeInitThread 函数。所以,KeInitThread 函数既可以被用来初始化用户模式线程,也可以被用于初始化系统线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
else {
 
        Teb = NULL;
        //
        // Set the system thread bit thats kept for all time
        //
        PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_SYSTEM);
 
        //
        // Initialize kernel thread object for kernel mode thread.
        //
 
        Thread->StartAddress = (PKSTART_ROUTINE) StartRoutine;
        Status = KeInitThread (&Thread->Tcb,
                               NULL,
                               PspSystemThreadStartup,
                               StartRoutine,
                               StartContext,
                               NULL,
                               NULL,
                               &Process->Pcb);
    }

 

12.锁住进程,并确保此进程并不是在退出或终止过程中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    PspLockProcessExclusive (Process, CurrentThread);
    //
    // Process is exiting or has had delete process called
    // We check the calling threads termination status so we
    // abort any thread creates while ExitProcess is being called --
    // but the call is blocked only if the new thread would be created
    // in the terminating thread's process.
    //
    if ((Process->Flags&PS_PROCESS_FLAGS_PROCESS_DELETE) != 0 ||
        (((CurrentThread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_TERMINATED) != 0) &&
        (ThreadContext != NULL) &&
        (THREAD_TO_PROCESS(CurrentThread) == Process))) {
 
        PspUnlockProcessExclusive (Process, CurrentThread);
 
        KeUninitThread (&Thread->Tcb);
 
        if (Teb != NULL) {
            MmDeleteTeb(Process, Teb);
        }
        ExReleaseRundownProtection (&Process->RundownProtect);
        ObDereferenceObject(Thread);
 
        return STATUS_PROCESS_IS_TERMINATING;
    }

 

13.进程的活动线程数加1,并且将新线程加入到进程的线程链表中。

1
2
 OldActiveThreads = Process->ActiveThreads++;
 InsertTailList (&Process->ThreadListHead, &Thread->ThreadListEntry);

 

14.调用KeStartThread 函数,初始化剩余的域,尤其是跟调度相关的域,比如优先级、时限设置、处理器亲和性,等等。经过这一步以后,新线程就可以开始运行了,所以可以解锁了

1
2
3
    KeStartThread (&Thread->Tcb);
    PspUnlockProcessExclusive (Process, CurrentThread);
    ExReleaseRundownProtection (&Process->RundownProtect);

 

15.注意:在KeStartThread中同样有InsertTailList(&Process->ThreadListHead, &Thread->ThreadListEntry);代码和13点相同,但KeStartThread用的是PKPROCESS Process,而本函数使用的是PEPROCESS Process,这是分别在执行体层和内核层维护线程与进程之间的关系。

  

16.若这是进程中的第一个线程,则触发该进程的创建通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 if (OldActiveThreads == 0) {
        PERFINFO_PROCESS_CREATE (Process);
 
        if (PspCreateProcessNotifyRoutineCount != 0) {
            ULONG i;
            PEX_CALLBACK_ROUTINE_BLOCK CallBack;
            PCREATE_PROCESS_NOTIFY_ROUTINE Rtn;
 
            for (i=0; i<PSP_MAX_CREATE_PROCESS_NOTIFY; i++) {
                CallBack = ExReferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i]);
                if (CallBack != NULL) {
                    Rtn = (PCREATE_PROCESS_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack);
                    Rtn (Process->InheritedFromUniqueProcessId,
                         Process->UniqueProcessId,
                         TRUE);
                    ExDereferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i],
                                                CallBack);
                }
            }
        }
    }

 

17.如果新线程的进程在一个作业中,则需要做特定的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
   // If the process has a job with a completion port,
    // AND if the process is really considered to be in the Job, AND
    // the process has not reported, report in
    //
    // This should really be done in add process to job, but can't
    // in this path because the process's ID isn't assigned until this point
    // in time
    //
    Job = Process->Job;
    if (Job != NULL && Job->CompletionPort &&
        !(Process->JobStatus & (PS_JOB_STATUS_NOT_REALLY_ACTIVE|PS_JOB_STATUS_NEW_PROCESS_REPORTED))) {
 
        PS_SET_BITS (&Process->JobStatus, PS_JOB_STATUS_NEW_PROCESS_REPORTED);
 
        KeEnterCriticalRegionThread (&CurrentThread->Tcb);
        ExAcquireResourceSharedLite (&Job->JobLock, TRUE);
        if (Job->CompletionPort != NULL) {
            IoSetIoCompletion (Job->CompletionPort,
                               Job->CompletionKey,
                               (PVOID)Process->UniqueProcessId,
                               STATUS_SUCCESS,
                               JOB_OBJECT_MSG_NEW_PROCESS,
                               FALSE);
        }
        ExReleaseResourceLite (&Job->JobLock);
        KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
    }

 

18.通知那些接收线程创建事件的回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  //
    // Notify registered callout routines of thread creation.
    //
 
    if (PspCreateThreadNotifyRoutineCount != 0) {
        ULONG i;
        PEX_CALLBACK_ROUTINE_BLOCK CallBack;
        PCREATE_THREAD_NOTIFY_ROUTINE Rtn;
 
        for (i = 0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i++) {
            CallBack = ExReferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i]);
            if (CallBack != NULL) {
                Rtn = (PCREATE_THREAD_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack);
                Rtn (Thread->Cid.UniqueProcess,
                     Thread->Cid.UniqueThread,
                     TRUE);
                ExDereferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i],
                                            CallBack);
            }
        }
    }

 

19.线程对象的引用计数加2,一个针对当前的创建操作,另一个针对要返回的线程句柄。

1
2
3
4
5
   //
    // Reference count of thread is biased once for itself and once for the handle if we create it.
    //
 
    ObReferenceObjectEx (Thread, 2);

 

20.如果CreateSuspended 参数指示新线程立即被挂起,则调用KeSuspendThread 挂起新线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    if (CreateSuspended) {
        try {
            KeSuspendThread (&Thread->Tcb);
        } except ((GetExceptionCode () == STATUS_SUSPEND_COUNT_EXCEEDED)?
                     EXCEPTION_EXECUTE_HANDLER :
                     EXCEPTION_CONTINUE_SEARCH) {
        }
        //
        // If deletion was started after we suspended then wake up the thread
        //
        if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_TERMINATED) {
            KeForceResumeThread (&Thread->Tcb);
        }
    }

 

21.根据指定的期望访问权限,调用SeCreateAccessStateEx 函数创建一个访问状态结构(ACCESS_STATE)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    AccessState = NULL;
    if (!PsUseImpersonationToken) {
        AccessState = &LocalAccessState;
        Status = SeCreateAccessStateEx (NULL,
                                        ARGUMENT_PRESENT (ThreadContext)?PsGetCurrentProcessByThread (CurrentThread) : Process,
                                        AccessState,
                                        &AuxData,
                                        DesiredAccess,
                                        &PsThreadType->TypeInfo.GenericMapping);
 
        if (!NT_SUCCESS (Status)) {
            PS_SET_BITS (&Thread->CrossThreadFlags,
                         PS_CROSS_THREAD_FLAGS_DEADTHREAD);
 
            if (CreateSuspended) {
                (VOID) KeResumeThread (&Thread->Tcb);
            }
            KeReadyThread (&Thread->Tcb);
            ObDereferenceObjectEx (Thread, 2);
 
            return Status;
        }
    }

 

22.调用ObInsertObject 函数把新线程对象插入到当前进程的句柄表中。

1
2
3
4
5
6
Status = ObInsertObject (Thread,
                             AccessState,
                             DesiredAccess,
                             0,
                             NULL,
                             &LocalThreadHandle);

  

23.设置线程的创建时间。

1
2
3
  KeQuerySystemTime(&CreateTime);
    ASSERT ((CreateTime.HighPart & 0xf0000000) == 0);
    PS_SET_THREAD_CREATE_TIME(Thread, CreateTime);

 

24.设置线程的访问权限,即GrantedAccess 域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
 if ((Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD) == 0) {
        Status = ObGetObjectSecurity (Thread,
                                      &SecurityDescriptor,
                                      &MemoryAllocated);
        if (!NT_SUCCESS (Status)) {
            //
            // This trick us used so that Dbgk doesn't report
            // events for dead threads
            //
            PS_SET_BITS (&Thread->CrossThreadFlags,
                         PS_CROSS_THREAD_FLAGS_DEADTHREAD);
 
            if (CreateSuspended) {
                KeResumeThread(&Thread->Tcb);
            }
            KeReadyThread (&Thread->Tcb);
            ObDereferenceObject (Thread);
            ObCloseHandle (LocalThreadHandle, PreviousMode);
            return Status;
        }
 
        //
        // Compute the subject security context
        //
 
        SubjectContext.ProcessAuditId = Process;
        SubjectContext.PrimaryToken = PsReferencePrimaryToken(Process);
        SubjectContext.ClientToken = NULL;
 
        AccessCheck = SeAccessCheck (SecurityDescriptor,
                                     &SubjectContext,
                                     FALSE,
                                     MAXIMUM_ALLOWED,
                                     0,
                                     NULL,
                                     &PsThreadType->TypeInfo.GenericMapping,
                                     PreviousMode,
                                     &Thread->GrantedAccess,
                                     &accesst);
 
        PsDereferencePrimaryTokenEx (Process, SubjectContext.PrimaryToken);
 
        ObReleaseObjectSecurity (SecurityDescriptor,
                                 MemoryAllocated);
 
        if (!AccessCheck) {
            Thread->GrantedAccess = 0;
        }
 
        Thread->GrantedAccess |= (THREAD_TERMINATE | THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION);
 
    } else {
        Thread->GrantedAccess = THREAD_ALL_ACCESS;
    }

   

25.调用KeReadyThread 函数,使新线程进入“就绪(ready)”状态,准备马上执行;或者,若此时进程尚未在内存中,则新线程的状态为“转移(transition)”,以等待换入内存后再执行。

1
 KeReadyThread (&Thread->Tcb);

 

26.引用计数减1,返回。

1
 ObDereferenceObject (Thread);

文章作者:hgy413
本文地址:http://hgy413.com/2937.html
版权所有 © 转载时必须以链接形式注明作者和原始出处!

19 Responses to “WD-线程的创建流程(WRK)”

  1. #1 minecraft 回复 | 引用 Post:2018-10-04 20:51

    Excellent blog right here! Also your site so much up
    fast! What host are you the use of? Can I am getting your
    associate hyperlink for your host? I desire my web site loaded up as fast as yours lol

  2. #2 minecraft 回复 | 引用 Post:2018-10-06 04:20

    Hi! Do you know if they make any plugins to assist with Search Engine Optimization? I’m trying to get my blog to rank
    for some targeted keywords but I’m not seeing very good results.

    If you know of any please share. Thanks!

  3. #3 minecraft 回复 | 引用 Post:2018-10-06 19:20

    Pretty! This was an incredibly wonderful article.
    Many thanks for providing these details.

  4. Thanks for one’s marvelous posting! I seriously enjoyed reading it, you’re a great author.

    I will always bookmark your blog and will eventually come
    back sometime soon. I want to encourage one to continue your great writing,
    have a nice morning!

  5. Hey just wanted to give you a quick heads up. The words in your post
    seem to be running off the screen in Safari. I’m not sure if this is
    a formatting issue or something to do with web browser compatibility but I figured I’d post to
    let you know. The style and design look great though!
    Hope you get the problem fixed soon. Kudos

  6. #6 Coconut Oil Benefits 回复 | 引用 Post:2018-10-21 07:10

    I do consider all the concepts you have offered for your post.

    They’re very convincing and will definitely work. Nonetheless, the posts are too quick for beginners.
    May you please prolong them a little from subsequent time?
    Thanks for the post.

  7. It’s amazing to pay a visit this website and reading the views of
    all mates about this post, while I am also keen of getting experience.

  8. #8 Benefits of Coconut Oil 回复 | 引用 Post:2018-10-28 21:45

    I love what you guys tend to be up too. Such clever work and reporting!
    Keep up the amazing works guys I’ve incorporated you guys to blogroll.

  9. #9 quest bars cheap 回复 | 引用 Post:2018-11-03 19:27

    Pretty! This has been a really wonderful article.
    Thanks for supplying this information.

  10. #10 quest bars cheap 回复 | 引用 Post:2018-11-04 05:40

    Thank you for any other informative blog.
    Where else could I am getting that type of information written in such a perfect manner?
    I have a project that I’m just now running
    on, and I’ve been on the look out for such info.

  11. #11 quest bars 回复 | 引用 Post:2018-11-06 11:33

    Thanks very nice blog!

  12. #12 Sling TV 回复 | 引用 Post:2018-11-10 02:17

    Heya! I’m at work browsing your blog from my new iphone
    3gs! Just wanted to say I love reading your blog and look forward to all your posts!
    Keep up the fantastic work!

  13. I have read so many articles or reviews about the blogger lovers except this article is really a good paragraph, keep it up.

  14. naturally like your web-site however you have to test the spelling on quite a few of your
    posts. Many of them are rife with spelling problems and I
    in finding it very bothersome to tell the truth on the other hand I
    will definitely come again again.

  15. Hurrah! After all I got a webpage from where I be capable of
    really take helpful information concerning my study and
    knowledge.

  16. Great web site you have got here.. It’s difficult
    to find high quality writing like yours these days.

    I seriously appreciate individuals like you! Take care!!

  17. My brother recommended I may like this blog.
    He used to be totally right. This put up actually made my day.
    You cann’t consider simply how a lot time I had spent for this information! Thank you!

  18. Hi there! This post could not be written any better! Reading through
    this post reminds me of my good old room mate! He always kept chatting
    about this. I will forward this page to him. Pretty sure he
    will have a good read. Thanks for sharing!

  19. Hi there all, here every person is sharing these kinds of know-how,
    so it’s fastidious to read this website, and I used
    to go to see this webpage all the time.

发表评论