Anti-Anti-Dump_and_No...(二)

标 题: [翻译]Anti-Anti-Dump_and_No...(下)
作 者: aalloverred
时 间: 2006-05-14,18:34
链 接: http://bbs.pediy.com/showthread.php?threadid=25564

[翻译]Anti-Anti-Dump_and_Nonintrusive_tracers(下)

[出处及相关]
kanxue的ARTeam团队优秀文章汇总帖:
http://bbs1.pediy.com:8081/showthread.php?s=&threadid=24875
原文地址:
http://bbs1.pediy.com:8081/attachment.php?s=&attachmentid=1236
上半部分地址(由kkbing翻译):
http://bbs1.pediy.com:8081/showthread.php?s=&threadid=25001
[重要的废话]
实在不好意思贴出来,因为这篇文章着实的超出了我的能力,我只是一只小小小小鸟
因此大家就当帮忙找错误看好了,谢谢!只希望千万不要误人子弟就好了:)

最后的附件是做成了pdf格式的译文,因为我是按照原文的格式翻译的,贴出来的可能看着不舒服.
[译者]aalloverred

[译文]


4. 用于内存管理器的非侵入式跟踪器
一旦我们利用代码强制性的将所有的东西存到了一个可以转存的地方,我们就可以准备访问这些地方了。一些保护壳会在分配的缓存中存储多态的oep,所以我们还要将eip访问权交给这些缓存。有很多方法实现这一点,我这里要使用的是
PAGE_GUARD, 和我在文献[6]中的oepfinder X.Y.Z中介绍的方法一样。
4.1. 编写非侵入式跟踪器
有关非侵入式跟踪器,有很多文章中都有所涉及,而在文献[5,6]中讲解的尤其得多。但我还是要提一些比较重要的东西;不久以后,也就是你读完了这一部分以后,你就会体会到理解这些东西是多么的重要以及它们是多么的有用。我在[5,6]中已经讲述过非侵入式跟踪器和loaders了,所以这里我将只是简要的提及它们。
非侵入式跟踪器的相关概念包括KiUserExceptionDispatcher的hooking,及其我们自己处理所有的异常。如果我们处理异常,我们只是简单的调用NtContinue ;如果我们不处理异常,我们就返回KiUserExceptionDispatcher。我们先来看一个非侵入式跟踪器样板示例:
nonintrusive: mov ecx, [esp+4]
mov ebx, [esp]
pushad
call deltakiuser
deltakiuser:  pop ebp
sub ebp, offset deltakiuser
...
retkiuser0:   popad
mov [ecx.context_dr0], 0
retkiuser:    push 0deadc0deh
ret
不要因为看到了mov [ecx.context_dr0], 0这一句而困惑;当讲述道第4.3节的时候你自然就会理解它。
现在我们先看一看KiUserExceptionDispatcher这个函数:
.text:7C90EAEC mov ecx, [esp+arg_0]
.text:7C90EAF0 mov ebx, [esp+0]
.text:7C90EAF3 push ecx
.text:7C90EAF4 push ebx
.text:7C90EAF5 call _RtlDispatchException@8
.text:7C90EAFA or al, al
.text:7C90EAFC jz short loc_7C90EB0A
.text:7C90EAFE pop ebx
.text:7C90EAFF pop ecx
.text:7C90EB00 push 0
.text:7C90EB02 push ecx
.text:7C90EB03 call _ZwContinue@8
.text:7C90EB08 jmp short loc_7C90EB15
.text:7C90EB0A
.text:7C90EB0A loc_7C90EB0A:
.text:7C90EB0A pop ebx
.text:7C90EB0B pop ecx
.text:7C90EB0C push 0
.text:7C90EB0E push ecx
.text:7C90EB0F push ebx
.text:7C90EB10 call _ZwRaiseException@12
你可能注意到了这与我在[6]中所讲述的东西不是一样的么?别急,接着往下走,你会看到为什么会这样。我们将要hook KiUserExceptionDispatcher的前两条指令,注意如果我们要得到stealth code我们可以hook _RtlDispatchException并将我们的跟踪器插入到那里。有无数种可能。我们hook KiUserExceptionDispatcher 时必须模仿被覆盖的字节它们是:
.text:7C90EAEC mov ecx, [esp+4]
.text:7C90EAF0 mov ebx, [esp]
ecx = 指向CONTEXT
ebx = 指向EXCEPTION_CODE
我们可以通过检查的ebx的值轻松的决定我们是要处理这个异常还是要将控制权返回给KiUserExceptionDispatcher:
nonintrusive: mov ecx, [esp+4]
mov ebx, [esp]
pushad
call deltakiuser
deltakiuser:  pop ebp
sub ebp, offset deltakiuser
cmp dword ptr[ebx], EXCEPTION_BREAKPOINT
je __bp_conditions
cmp dword ptr[ebx], EXCEPTION_GUARD_PAGE
jne retkiuser0
...
retkiuser0:   popad
mov [ecx.context_dr0], 0
retkiuser:     push 0deadc0deh <--- 在hook引擎中改变此值使其指向正确的值
ret           
因为KiUserExceptionDispatcher是ntdll.dll的输出函数,我们可以简单的使用GetProcAddress定位它并且找到我们要hook的地址。同样注意,我一直使用的都是偏移地址,因为所有的代码都是在我注入的代码中执行的:
mov eax, [ebp+KiUserExceptionDispatcher]
lea ebx, [ebp+nonintrusive]
mov byte ptr[eax], 0e9h
mov ecx, eax
add ecx, 5
sub ebx, ecx
mov dword ptr[eax+1], ebx
add eax, 7
mov dword ptr[ebp+retkiuser+1], eax
有了这个钩子,你也许就可以肯定了,所有的异常都会经过你的钩子,你可以处理它们。当然非侵入式跟踪器也有弊端,因为这些理论不适用于两个进程比如当我们使用调试器/被调试程序的时候,因为这些异常可能会被调试器处理。另一方面,如果所有的异常都传递为DBG_EXCEPTION_NOT_HANDLED我们的跟踪器就会毫无问题的工作。只是一个想法,但是为什么不hook WaitForDebugEvent并且将所有的异常传递为DBG_EXCEPTION_NOT_HANDLED呢。只是设想,思考一下,读一下5.3章。 

非侵入式跟踪的第二个也是最大的一个缺陷就是处理堆栈时大量的垃圾代码。如果我们仔细的看看Exception异常的处理过程我们会看到CONTEXT和Exception代码由ring0拷贝到了用户堆栈。如果某些多态指令混淆了堆栈,拷贝就不会被执行,程序也就随之崩溃了。这是一种特别的情况,当我们单步跟踪代码的时候我们必须检查下一条指令是不是要改变堆栈,这样我们就可以阻止单步跟踪或者在我们的跟踪器中模仿这样的指令。这一点,我要推荐一下z0mbie的XDE engine[7]。

好了,希望你了解了基本思路。
4.2. 非侵入式跟踪器中使用PAGE_GUARD
PAGE_GUARD被用来提供简单好用的访问警告,但是也要注意到一旦异常发生了,PAGE_GUARD不会被移除。调试器 (也就是我们的非侵入是跟踪器) 应该在跳转到异常发生的地方执行前应该先改变页的的保护属性。我们也可以使用PAGE_NOACCESS,我们要使用这样或者那样的方法改变错误页的保护属性。但是使用PAGE_GUARD的优点源于这样一个事实:我们同样知道了错误代码传给了我们的跟踪器,这是我们使用的PAGE_GUARD唯一原因。同样也可以在保护器中使用对地址0x00000000h处的内存访问,通常代码是下面这个样子:
xor eax, eax
push offset sehhandle
push dword ptr fs:[eax]
mov dword ptr fs:[eax], esp
mov [eax], eax <-- 异常
我们必须手动确定异常是由于寄存器的错误使用而产生的,还是由于我们的页被设置成了page_noaccess(不可访问属性)而产生的。在 4.3 我还会涉及到如何区分寄存器的错误使用和读/写我们的内存区域。要设置PAGE_GUARD我们得回到内存管理器部分并且将这个属性设置到一定的内存范围,使用下面的方法:
__setpageguard:   or memprotection, PAGE_GUARD
lea eax, dummy_var
          push eax
          push memprotection
          push range
          push virtualaddress
          call [edi+VirtualProtect]
太简单了是吧? 这部分要存储在我们每个内存管理器的后面。很简单,现在你应该也明白了为什么所有的分配的内存缓冲区的都是以页为边界的。因为不管你怎么做都VirtualProtect将可访问的起始地址设为:
Address to set access = address and 0FFFFF000h
访问错误的页简单的使用FPI,多亏有了FPI,我们可以容易的找到VirtualAddress以及任何给定区域的大小:
getfpi: push esi
 mov esi, [ebp+memstart]
     add esi, NEW_MEM_RANGE-4000h
     sub eax, [ebp+memstart]
     shr eax, 0ch ;获得当前页的索引
     mov eax, [esi+eax*4]
     shr eax, 2 ;获得 FPI
     pop esi
     retn
;eax=FPI?,返回页的基址
getvafromfpi: 
     shl eax, 0ch
     add eax, [ebp+memstart]
     retn
;eax FPI, 由getfpi中返回。本函数返回大小
getsizefromfpi:
     push esi ecx edx ebx
     mov esi, [ebp+memstart]
     add esi, NEW_MEM_RANGE-4000h
     mov ecx, eax
     xor ebx, ebx
__cycle_fpi: 
     mov edx, [esi+ecx*4]
     shr edx, 2
     cmp edx, eax ;比较各FPI
     jne __gotsizefromfpi
     inc ebx
     inc ecx
     jmp __cycle_fpi
__gotsizefromfpi: 
     shl ebx, 0ch
     mov eax, ebx
     pop ebx edx ecx esi
     retn
现在,要是你不喜欢我的方法,如何实现不同的内存管理也可以取决于你,完全取决于你,本文的主要目的只是想让你理解这个方法的思路。如果你正在像我一样思考,你应该会问了:怎样才能知道是由于对受保护页的执行引发了EXCEPTION_GUARD_PAGE还是对受保护页的读写引发了异常呢?不使用ring0这几乎是不可能的。我说几乎是因为如果肯定了是对受保护页的读写引发了异常,你就必须在你的内存管理器中跟踪受保护页的信息。 
4.3.特别情况下的PAGE_GUARD
就像我在编写自己的小程序的时候一样。我意识到由KiUserExceptionDispatcher传回的数据已经不足以满足我的需要了。我的处境很可能是一个程序员能够碰到的最糟糕的处境了。我想做的是迫使使用了Advanced OEP protection(高级OEP保护手段)的 ASProtect SKE 2.2将分配的所有缓存都存储到一个大缓存以方便以后转储。为了找到ASPR混淆后的多态变形的oep,我计划在每个新分配的缓存上使用PAGE_GUARD,这样我就可以确定什么时候EIP到达缓存并在这个时候登入,但此时一个大问题来了。

因为所有的缓存都位于一个大内存缓冲区中,而我的代码用作了内存管理器(释放/分配内存页),我无法确定什么时候eip到达了某个可能的范围中,因为在缓冲内存中这样的范围实在是太多了。实际上我所需要的不是作为信息传递给KiUserExceptionDispatcher的东西,对,我需要的是cr2寄存器的内容,这样就能得到错误地址,这样如果EIP和cr2的内容相匹配,就说明我们到达了自己的受保护页。
Ring3中这一点的解决方案就是保存每个内存页的信息,这样一旦发生了PAGE_GUARD异常,我们就可以确定这个PAGE_GUARD异常发生时,EIP是在一个没有PAGE_GUARD属性的页中还是在一个标记了PAGE_GUARD的内存页中。是个不错的主意,但是需要编写更多的代码,还需要重新组织我的ring3的内存管理器,增加对每个内存页的保护功能。因为我懒得重新编写,所以我就又为KiUserExceptionDispatcher添加了一小段代码。

当然也可能hook KiTrap0E 并且在context结构中返回cr2寄存器的内容。嗯,不错的想法。cr2寄存器将会保存有错误发生的va(虚拟地址),因为对受保护页的访问只不过是触发异常,我们除了得到cr2寄存器的值还要确定异常是由于执行产生的还是由于读写产生的。当然,KiUserExceptionDispatcher是不会替我们返回出错地址的,就是因为糟糕的这一点我们必须改进它,使它能返回cr2寄存器的值:
cr2 = faulting_address0 //aal注:出错地址
eip = faulting_address0 //aal注:出错地址
exceptioncode = EXCEPTION_GUARD_PAGE//错误代码 = EXCEPTION_GUARD_PAGE
爽,登入它,去除这一页的保护,然后等待下次访问。但是如何得到cr2寄存器的内容呢???是啊,这的确是个头痛的问题,但是实际上确实是可以做到的。来看看KiTrap0E是怎么做的:
.text:804DAF25 _KiTrap0E:
.text:804DAF25 mov word ptr [esp+2], 0
.text:804DAF2C push ebp
.text:804DAF2D push ebx
.text:804DAF2E push esi
...
.text:804DB0ED mov ecx, 3
.text:804DB0F2 mov edi, eax
.text:804DB0F4 mov eax, 0C0000006h
.text:804DB0F9 call CommonDispatchException

到这里所有的KiTrap调用将会调用CommonDispatchException,这个函数会将CONTEXT 和ERROR_CODE的内容保存到堆栈中并且将EIP重新引导到KiUserExceptionDispatcher。继续跟踪CommonDispatchException,我们来到这里:
.text:804D8A8D CommonDispatchException proc near
.text:804D8A8D
.text:804D8A8D
.text:804D8A8D
.text:804D8A8D sub esp, 50h
.text:804D8A90 mov [esp+50h+var_50], eax
.text:804D8A93 xor eax, eax
.text:804D8A95 mov [esp+50h+var_4C], eax
.text:804D8A99 mov [esp+50h+var_48], eax
.text:804D8A9D mov [esp+50h+var_44], ebx
.text:804D8AA1 mov [esp+50h+var_40], ecx
.text:804D8AA5 cmp ecx, 0
.text:804D8AA8 jz short loc_804D8AB6
.text:804D8AAA lea ebx, [esp+50h+var_3C]
.text:804D8AAE mov [ebx], edx
.text:804D8AB0 mov [ebx+4], esi
.text:804D8AB3 mov [ebx+8], edi
.text:804D8AB6
.text:804D8AB6 loc_804D8AB6:
.text:804D8AB6 mov ecx, esp
.text:804D8AB8 test dword ptr [ebp+70h], 20000h
.text:804D8ABF jz short loc_804D8AC8
.text:804D8AC1 mov eax, 0FFFFh
.text:804D8AC6 jmp short loc_804D8ACB
.text:804D8AC8
---------------------------------------------------------------------
.text:804D8AC8
.text:804D8AC8 loc_804D8AC8:
.text:804D8AC8 mov eax, [ebp+6Ch]
.text:804D8ACB
.text:804D8ACB loc_804D8ACB:
.text:804D8ACB and eax, 1
.text:804D8ACE push 1
.text:804D8AD0 push eax
.text:804D8AD1 push ebp
.text:804D8AD2 push 0
.text:804D8AD4 push ecx
.text:804D8AD5 call _KiDispatchException@20
.text:804D8ADA mov esp, ebp
.text:804D8ADC jmp Kei386EoiHelper@0
.text:804D8ADC CommonDispatchException endp
好了,因为知道早晚都会调用_KiDispatchException所以我们就直接跟到这里了:
.text:804F318D ; __stdcall KiDispatchException(x,x,x,x,x)
.text:804F318D _KiDispatchException@20 proc near
.text:804F318D
.text:804F318D push 390h
.text:804F3192 push offset dword_804F3278
.text:804F3197 call __SEH_prolog
.text:804F319C mov eax, ds:___security_cookie
.text:804F31A1 mov [ebp-1Ch], eax
.text:804F31A4 mov esi, [ebp+8]
.text:804F31A7 mov [ebp-2ECh], esi

.text:804F31AD mov ecx, [ebp+0Ch]
.text:804F31B0 mov [ebp-2F0h], ecx
.text:804F31B6 mov ebx, [ebp+10h]
.text:804F31B9 mov [ebp-2F8h], ebx
.text:804F31BF db 3Eh
.text:804F31BF mov eax, ds:0FFDFF020h
.text:804F31C5 inc dword ptr [eax+504h]
.text:804F31CB mov dword ptr [ebp-2E8h], 10017h
.text:804F31D5 cmp byte ptr [ebp+14h], 1
.text:804F31D9 jz loc_804F5A76
.text:804F31DF cmp ds:_KdDebuggerEnabled, 0
.text:804F31E6 jnz loc_804F5A76
.text:804F31EC
.text:804F31EC loc_804F31EC:
.text:804F31EC
.text:804F31EC lea eax, [ebp-2E8h]
.text:804F31F2 push eax
.text:804F31F3 push ecx
.text:804F31F4 push ebx
.text:804F31F5 call _KeContextFromKframes@12
.text:804F31FA mov eax, [esi]
.text:804F31FC cmp eax, 80000003h
.text:804F3201 jnz loc_804F5A20
.text:804F3207 dec dword ptr [ebp-230h]
.text:804F320D
.text:804F320D loc_804F320D:
.text:804F320D
.text:804F320D xor edi, edi
.text:804F320F
.text:804F320F loc_804F320F:
.text:804F320F cmp byte ptr [ebp+14h], 0
.text:804F3213 jnz loc_804F58C3
.text:804F3219 cmp byte ptr [ebp+18h], 1
.text:804F321D jnz loc_80516D98
.text:804F3223 mov eax, ds:_KiDebugRoutine
.text:804F3228 cmp eax, edi
.text:804F322A jz loc_80505721
.text:804F3230 push edi
.text:804F3231 push edi
.text:804F3232 lea ecx, [ebp-2E8h]
.text:804F3238 push ecx
.text:804F3239 push esi
.text:804F323A push dword ptr [ebp-2F0h]
.text:804F3240 push ebx
.text:804F3241 call eax
.text:804F3243 test al, al
.text:804F3245 jz loc_80505721
.text:804F324B
.text:804F324B loc_804F324B:
.text:804F324B push dword ptr [ebp+14h]
.text:804F324E push dword ptr [ebp-2E8h]
.text:804F3254 lea eax, [ebp-2E8h]
.text:804F325A push eax
.text:804F325B push dword ptr [ebp-2F0h]
.text:804F3261 push ebx
.text:804F3262 call _KeContextToKframes@20
.text:804F3267
.text:804F3267 loc_804F3267:
.text:804F3267
.text:804F3267 mov ecx, [ebp-1Ch]
.text:804F326A call @xHalReferenceHandler@4

.text:804F326F call __SEH_epilog
.text:804F3274 retn 14h
.text:804F3274 _KiDispatchException@20 endp ; sp = -14h
.text:804F3274
如果不实际的跟踪一下,就看不出这些代码有什么意义,所以我们可以使用Softice开始跟踪,很快我们就会发现我们想要的字节:
.text:804F5959 call _ProbeForWrite@12
.text:804F595E mov ecx, 0B3h
.text:804F5963 lea esi, [ebp+var_2E8]
.text:804F5969 rep movsd
804F5969处的指令负责将CONTEXT拷贝到用户堆栈,当然是为KiUserExceptionDispatcher做这个工作。如果我们能够hook那条指令,我们当然也就能够保存cr2寄存器的内容,并将它的值放到CONTEXT结构的某个域中传回ring3。这些域就是dr0/4 regs。如果我们不处理它,我们的非侵入式跟踪器应该清除cr2的值。
我们将要hook "lea esi, [ebp+var_2E8]" 处,因为它的大小足够保存hook-也就是push/ret组合,注意千万不要hook rep movsd处,否则驱动卸载的时候有可能发生BSOD(aal注:Blue Screen of Death,蓝屏死机)的错误。只是有可能发生,正像Mark Russinovich所描述的,当驱动的地址空间中的代码在执行时,如果打断了某个线程,而我们在这个时候卸载驱动,当被中断的线程打算再次执行时就会返回到空内存而你会得到一个内存页错误(PAGE_FAULT),看到的就是BSOD。我还没有遇到过这种情况,但是这的确是有可能发生的。只要你遭遇到BSOD却一点也不知道是什么原因造成的时候,脑子中想到这一点就行。
同样我们必须标识我们的进程,完成这一点,我使用的是cr3技巧,因为cr3中存储了PDE的物理地址,因为每个进程都有自己的内存空间,那样我们就能毫无问题的标识进程了 [11]。注意我使用的KeStackAttachProcess是不必要的, 我们可以直接在KPROCESS结构(EPROCESS的一部分)中得到cr3的值:
kd> dt nt!_KPROCESS
+0x000 Header : _DISPATCHER_HEADER
+0x010 ProfileListHead : _LIST_ENTRY
+0x018 DirectoryTableBase : [2] Uint4B <-- 就是这里了 
+0x020 LdtDescriptor : _KGDTENTRY
ntoskrnl.exe基址的偏移地址25963h处,我们现在就可以准备完成这个技巧了。开始干吧!!!!你必须在你自己的ntoskrnl.exe中看看这个偏移地址,因为这个偏移地址可能会有所不同。
另外还有一种条件非常非常非常重要,这就是cr2的保存!!!
cr2的值没有被保存,如果其它的内存页错误,cr2就会保存那个错误地址。 这对我们来说使非常危险的,因为内存页错误的处理程序同样也用来将内存页存储在页面文件中。有一种存储cr2的值的方法就是hook int 0eh,它能够在当且仅当cr2的值属于我们的进程时存储它。接下来的代码中,我们的hook将会得到已经存储了的cr2的值,并且将它的值放到context.context_dr0中传给我们的代码。
hookint0eh label dword
push eax
mov eax, cr3
cmp eax, c_cr3
jne __exit_int0e
mov eax, cr2
mov c_cr2, eax
__exit_int0e: 
pop eax
jmp cs:[oldint0eh]
;将 cr2 的内容保存在 context.context_dr0 中
hookmycode label dword
push eax
mov eax, cr3
cmp eax, c_cr3
jne __exithook
lea esi, [ebp-2e8h]
mov eax, c_cr2
mov [esi.context_dr0], eax
__exithook: 
pop eax
lea esi, [ebp-2e8h]
retaddr: 
push 0deac0deh
ret
现在在你的非侵入式跟踪器中你只要测试是否EIP = DR0,如果相等的话我们就登入访问,而如果不是我们就去除PAGE_GUARD然后在错误指令后设置int 3h。我们有改进后的KiUserExceptionDispatcher替我们作我们想做的事。 
同样这也将帮助我们分别访问我们的内存页时产生的ACCESS_VIOLATION时我们是不是在使用PAGE_NOACCESS而不是在使用PAGE_GUARD。
4.4. 进入内存区域并访问
一旦我们知道了如何解决问题,我们就得进入我们的内存页面。这可以通过向文件进行写入操作来实现,或者更好一点的方法是使用www.sysinternals.com 的OutputDebugStringA和DbgView得到输出:
示例代码:
__log: lea ecx, [ebp+format5]
    lea ebx, [ebp+buffer]
    push eax
    push ecx
    push ebx
    call [ebp+wsprintfA]
    add esp, 0ch
    push ebx
    call [ebp+OutputDebugStringA] ;登入 访问
    ...
    format5 db "eip log : 0x%.08X", 13, 10, 0
现在,你应该在DbgView中得到了许多数据,保存下日志数据,研究一下地址,排出掉重复的部分,并且通过再次运行跟踪器缩减日志文件的大小。
4.5. 从跟踪器中触发驱动
这个实际上用来通知驱动要关心什么样PID,此时驱动已经安装好了,但是我们还得告诉它开始跟踪我们:
push 0
push 0
push OPEN_EXISTING
push 0
push 0
push GENERIC_READ or GENERIC_WRITE
push offset driver
callW CreateFileW
mov dhandle, eax
call DeviceIoControl, eax, 20h, o pid, 4, o pid, 4, o dwbytes, 0
push dhandle
callW CloseHandle
...
driver: unis <//./ring0>
pid = pid of process we are tracing//我们所跟踪程序的pid
我还使用了文章Loader from ring0 [11]中讲述的相同的方法来标识进程。
4.6. 制作stealth非侵入式跟踪器
好,这里假定我们对付的是个新壳,它会在KiUserExceptionDispatcher 中寻找我们的hook,我们要做的就是打败它。怎样才能做到这一点呢?的确不是简单的任务,多亏有了yates展示的技巧[8]我们能过继续下去。
KiUserExceptionDispatcher是一个永远永远都不会返回的过程,它将要调用NtContinue或者是NtRaiseException。我们看看都发生了什么:
- 异常发生
- ntoskrnl.exe通过KiTrapXX接手控制权
- KiTraps实际上是IDT的入口(?),并且根据不同的异常在KiTrapXX的入口处有两种可能的堆栈布局:
+---------------+    +---------------+
|     EFLAGS      |    |      EFLAGS     |
+---------------+    +---------------+
|       CS         |   |         CS       |
+---------------+   +---------------+
|       EIP        |   |        EIP     |
+---------------+   +---------------+
            |   Error Code   |
            +---------------+

因为一些异常并不引发错误,同时也为了从ring0中退出来时更容易些,不管什么异常发生了一些KiTrapXX都将0压入堆栈模仿代码,比如KiTrap01和KiTrap03:
_KiTrap01
0008:804D8D7C PUSH 00 <--- dummy Error Code
0008:804D8D7E MOV WORD PTR [ESP+02],0000
0008:804D8D85 PUSH EBP
0008:804D8D86 PUSH EBX
0008:804D8D87 PUSH ESI
0008:804D8D88 PUSH EDI
0008:804D8D89 PUSH FS
_KiTrap03
0008:804D915B PUSH 00 <--- dummy Error Code
0008:804D915D MOV WORD PTR [ESP+02],0000
0008:804D9164 PUSH EBP
0008:804D9165 PUSH EBX
0008:804D9166 PUSH ESI
0008:804D9167 PUSH EDI
0008:804D9168 PUSH FS
但是KiTrap0E (内存页错误处理程序) 并没有将0压入堆栈因为错误代码存在了堆栈中。
_KiTrap0E
0008:804DAF25 MOV WORD PTR [ESP+02],0000
0008:804DAF2C PUSH EBP
0008:804DAF2D PUSH EBX
0008:804DAF2E PUSH ESI
0008:804DAF2F PUSH EDI
0008:804DAF30 PUSH FS
0008:804DAF32 MOV EBX,00000030
从中断中返回是由一个简单的IRETD指令完成的,它与ret指令相近,也是跳转到堆栈中所保存的EIP。异常处理完毕之后,ring0确定要调用KiUserExceptionDispatcher时它就会将KiUserExceptionDispatcher的地址存储在堆栈中,所以IRETD只是简单的返回了KiUserExceptionDispatcher :
0008:804F5A0F MOV EAX,[_KeUserExceptionDispatcher]
0008:804F5A14 MOV [EBX+68],EAX
:dd ebx+68
0010:EEC21DCC 7C90EAEC 0000001B 00000246 0013FCD0 ìê |....F.......
0010:EEC21DDC 00000023 00000000 00000000 00000000 #...............
正如你所看到的,EIP被KiUserExceptionDispatcher的地址覆盖了以及堆栈中保存的CS,Eflags,esp 和 SS。因为我们要做的是hook这些指令,所以他会指向ntdll.dll中其他的代码,就是我们使用yates展示的方法所存储的那些代码。 同样,也有更好的方法应该尽量不要扫描磁盘上的ntdll.dll,而是使用内存中已经载入的文件直接重新引导至UserSharedData,在用户模式下被设置成了只读:
kd> ? SharedUserData
Evaluate expression: 2147352576 = 7ffe0000
kd>
但是在ring0它被映射到了:
#define KI_USER_SHARED_DATA 0xffdf0000
所以我们可以在ring0中向那里写入,并且重新引导我们的异常(Exceptions)到达负责跳转到KiUserExceptionDispatcher 的地方,或者只是简单的调用我们存储在被调试程序内存空间某处的非侵入式跟踪器。见[ring0stealthtracer]文件夹。在你运行之前确定你理解了这些代码将要做什么。我这次使用的是PID标识进程。我们首先定位ntoskrnl.exe的基址: 
iMOV esi, ZwCreateFile
and esi, 0FFFFF000h
__find_base: cmp word ptr[esi],'ZM'
je __ntoskrnlbase
sub esi, 1000h
jmp __find_base
__ntoskrnlbase: mov ntoskrnlbase, esi
然后我们必须定位_KeUserExceptionDispatcherVariable(变量),它没有被输出。对我们来说,幸运的是它被存储在一个以双字(dword)对齐的边界处,而且在ntoskrnl.exe中KiUserExceptionDispatcher只出现了一次,所以我们可以使用KiUserExceptionDispacther的地址搜索它:
mov edi, esi
mov ebx, esi
add ebx, dword ptr[ebx+3ch]
mov ecx, [ebx.NT_OptionalHeader.OH_SizeOfImage]
shr ecx, 2
cld
mov eax, kiuser
repnz scasd
sub edi, 4
一旦我们定位在未输出的的_KeUserExceptionDispatcher我们就可以将我们的代码保存在UserSharedData然后将_KeUserExceptionDispatcher覆盖成我们代码的地址:
push edi
mov KiUserExceptionDispatcher, kiuser
mov edi, kiusershareddata+100h
mov esi, offset shareddatahook
mov ecx, shareddatahooksize
cld
rep movsb
pop edi
mov dword ptr[edi], kiusershareddataring3+100h
...
kiusershareddata equ 0ffdf0000h
kiusershareddataring3 equ 07ffe0000h

完工!
让我们看看如果用softICE跟踪的话是什么样子:
_KiTrap03
0008:804D915B PUSH 00
0008:804D915D MOV WORD PTR [ESP+02],0000
0008:804D9164 PUSH EBP
0008:804D9165 PUSH EBX
0008:804D9166 PUSH ESI
0008:804D9167 PUSH EDI
0008:804D9168 PUSH FS
...
0008:804F5A0F MOV EAX,[_KeUserExceptionDispatcher]
0008:804F5A14 MOV [EBX+68],EAX
0008:804F5A17 OR DWORD PTR [EBP-04],-01
… :
dd _KeUserExceptionDispatcher
0023:80552AF0 7FFE0100 7C90EAD0 7C90EAC0 00002626 ... .ê |.ê |&&..
这里是iretd之前的堆栈:
0023:F0E9DDCC 7FFE0100 0000001B 00000246 0013FCC8 ... ....F.......
0023:F0E9DDDC 00000023 00000000 00000000 00000000 #...............
然后我们就到了我们存在UserSharedData中的代码:
001B:7FFE0100 CALL 7FFE0105
001B:7FFE0105 POP EDX
001B:7FFE0106 SUB EDX,F7129409
001B:7FFE010C MOV ECX,FS:[0020] <--从TEB中得到PID
001B:7FFE0112 CMP DWORD PTR [EDX+F712943B],-01 <-- 不跟踪
001B:7FFE0119 JZ ntdll!KiUserExceptionDispatcher   到KiUser跳转
001B:7FFE011B CMP [EDX+F712943B],ECX <-- 被跟踪的程序的PID么?
001B:7FFE0121 JNZ ntdll!KiUserExceptionDispatcher 不是,我们调用KiUser
001B:7FFE0123 JMP [EDX+F7129437] <-- 我们调到nonintrusive
001B:7FFE0129 JMP [EDX+ntdll!KiUserExceptionDispatcher] <-- 回到KiUser
不是被跟踪的程序,所以我们继续对KiUserExceptionDispatcher的访问:
ntdll!KiUserExceptionDispatcher
001B:7C90EAEC MOV ECX,[ESP+04]
001B:7C90EAF0 MOV EBX,[ESP]
001B:7C90EAF3 PUSH ECX
001B:7C90EAF4 PUSH EBX
希望你理解了stealth技术? 祝未来的保护器好运!!
注意这个技巧是 Barnaby Jack 在他关于ring0shell code著名的文章 [9] 。.

4.7. 非侵入式跟踪器结论
到这里,希望你已经明白了基本思路,我提供的所有的代码和示例都是以在ntoskrnl.exe 中放置hook为目的的,所以你需要将我的驱动源代码中的偏移地址修改为你自己的所有的驱动代码都只是未经修正过的,如果没有提前修改而因为错误的偏移地址导致了蓝屏死机,系统崩溃,数据丢失等均由使用者本人负责。







5. 装载器的装载器
装载器的装载器实际上是处理双进程保护的装载器。一些软件使用装载器装载真正的游戏,我已经做了一个处理这种软件的装载器。因为这样就存在了两个装载器所以我把它称作装载器的装载器。网上很可能已经有我写的关于这方面的文章,但是在这里我还是想描述一下,主要是因为我们所对付的就有一些双进程的保护壳。我们必须获得第二个进程的控制权,为了达到这个目的我们将要hook的是CreateProcessA,这样等它完成了自己的任务时,我们就可以得到一个PROCESS_INFORMATION结构,这个结构中包含了ProcessID和ThreadID。
- ProcessId 有了它我们就可以打开进程然后在它的缓存地址空间写东西等等…
- ThreadId 有了它我们才能控制某些东西,Get/SetThreadContex,Resume/SuspendThread。
注意:如果我们是从注入的偏移地址无关代码中hook我们就不需要了,因为我们已经有了保存在PROCESS_INFORMATION结构中的ProcessHandle和ThreadHandle。
我将讲述3种方法,其中一种有可能还没有在RCE领域中被讨论过。
5.1. 使用注入代码实现的装载器的装载器
这种方法的思路是将我们的内存管理器注入到第二个进程中,整个过程涉及到在返回(ret)时hook CreateProcessA,此时我们会得到一个错误代码(如果进程的执行没有问题的话)。我们还会得到一个填充好了的PROCESS_INFORMATION结构,其中就包含了进程和主线程的句柄。从根本上讲,就会有两套偏移无关代码 。示例代码在[lflinjected]文件夹中。
我们来看一眼我们的例子,在第二个装载器的装载器中我还要用到这个测试(test)程序,所以脑子里记住这里我们是怎么处理的:
push offset mutexname
push 0
push MUTEX_ALL_ACCESS
callW OpenMutexA
test eax, eax
jnz __2ndprocess
push offset mutexname
push 0
push 0
callW CreateMutexA
push offset pinfo
push offset sinfo
push 0
push 0
push 0
push 0
push 0

push 0
push offset progy
push 0
callW CreateProcessA
push -1
push pinfo.pi_hProcess
callW WaitForSingleObject
jmp __exit
__2ndprocess: push 40h
push offset mtitle
push offset mtext
push 0
callW MessageBoxA
__exit: push 0
callW ExitProcess
看到了吧,这个小程序是要生成一个互斥对象(mutex),然后根据互斥对象的情况来决定是输出信息还是产生一个新的进程实例。为了实现装载器的装载器,我们用注入代码的第一部分hook函数CreateProcessA,第二部分用来注入。从根本上讲这里出现了两个偏移无关代码,索性的是获得kernel的基址(getkernelbase)和从kernel32.dll搜索输出函数的工作,我们只需要进行一次,因为kernel32.dll偏移地址是不会因为不同的进程而有所不同的。
思路:
- 使用CreateProcess生成挂起状态的进程
- 注入偏移无关代码
- 使用偏移无关代码hook我们目标进程中的CreateProcessA
- 一旦运行到了CreateProcessA返回(retn)处的hook就得到了进程和线程句柄
- 到这里我们的偏移无关代码就要使用WriteProcessMemory和VirtualAllocEx将一个新的装载器注入到第二个进程中,此处从根本上讲我们是在重复我们的原始的装载器中注入偏移无关代码的工作
由于第一个装载器的大小的问题,源代码就不在这里贴出了,你可以在[lflinjected]文件夹中找到已经经过注释的源代码文件。
5.2. 不使用注入代码实现的装载器的装载器
这是更为简单的一个解决方案,也仅需要较少的偏移无关代码的编写和理解。但是如果进程不是使用CREATE_SUSPENDED创建的,这种方法就不太好了,因为我们必须手动的加入CREATE_SUSPENDED标志,这样甚至反倒需要更多的代码。
思路:
- 使用CREATE_SUSPENDED标志载入第一个程序
- 在CreateProcessA中插入jmp $
- 运行到我们的hook时,从堆栈中读取PROCESS_INFORMATION的地址,dwCreationFlags和返回地址
- 将dwCreationFlags与CREATE_SUSPENDED进行或操作并且将它存回堆栈,清除前面的hook并在堆栈的所指向的返回地址插入新的指令
- 运行到我们的第二个hook时,读取PROCESS_INFORMATION我们会得到pid和tid
- 使用OpenProcess和OpenThread处理新进程
在[lflwoinjected]文件夹中查看完整的实现代码,已注释。
5.3.用于被调试进程的非侵入式跟踪器 
我这里用的是一个使用Armadillo 4.3的Standard Protection + Debug Blocker加壳的crackme来注入我的跟踪器。当然这对内存管理器也适用,但为了简单明了的说明问题,我只把非侵入式的跟踪器注入到其中并且中断在OEP处。. Lol, guess what you got DebugBlocker
armadillo oep finder with this tutorial.
其实实践起来并不难,你所需要的只是一点点想象力,以及一些如何使用Windows Debug APIs [10]实现ring3级的调试的知识,你可以参考一些调试装载器的教程。要实现这一点我们要hook WaitForDebugEvent以便我们第一时间得到它的输出值然后才能够检查DEBUG_EVENT结构的内容,我们只对传给我们的代码的EXCEPTION_DEBUG_EVENT感兴趣,而所有其他的事件都直接传回给程序。我使用带DBG_EXCEPTION_NOT_HANDLED参数的ContinueDebugEvent,它会在我们的被跟踪程序中调用KiUserExceptionDispatcher。为了达到目的,我们还得在armadillo中CreateProcessA的返回处(retn)hook,这样才能将我们的非侵入式跟踪器注入。

代码和目标程序都在文件夹[armadillo_oep]中,既然到了这里,还要感谢一下Teddy Rogers和他收集unpackme的网站http://www.tuts4you.com/unpackme/ ,因为我们将要使用的unpackme就来自这里
我们来看看调用了CreateProcessW后armadillo又干了什么:
004949EC . 52 PUSH EDX
004949ED . 6A 02 PUSH 2
004949EF . 68 A4B44C00 PUSH armadill.004CB4A4
004949F4 . 8B45 10 MOV EAX,DWORD PTR SS:[EBP+10]
004949F7 . 50 PUSH EAX
004949F8 . 8B4D 08 MOV ECX,DWORD PTR SS:[EBP+8]
004949FB . 8B11 MOV EDX,DWORD PTR DS:[ECX]
004949FD . 52 PUSH EDX
004949FE . FF15 D0304C00 CALL DWORD PTR DS:[<&KERNEL32.ReadProcessMem>
这里,Armadillo从入口处读取了2个字节,这样它就能够在这里插入jmp $ hook然后再使用DebugActiveProces附加进程了,当然,armadillo也使用Sleep/GetThreadContext来确定程序何时到达入口点,在无限循环中:
0048FD8D > /A1 A0B54C00 MOV EAX,DWORD PTR DS:[4CB5A0]
0048FD92 . 8B48 08 MOV ECX,DWORD PTR DS:[EAX+8]
0048FD95 . 51 PUSH ECX
0048FD96 . FF15 C4314C00 CALL DWORD PTR DS:[<&KERNEL32.DebugActivePro>

从这里开始我们就进入了一个使用WaitForDebugEvent和ContinueDebugEvent制造的调试循环(Debug Loop)。 “Hybrid” hooking of WaitForDebugEvent在这里是不行的,因为这里的ret附近没有填充的nop指令,所以我们要使用更巧妙一些的hook方法。
这个方法就是我们覆盖堆栈中所存储的返回地址,这样运行WaitForDebugEvent,当它试图返回时就会跳转到我们的代码了。


好,还没跑吧?或许一些图例将会帮助理解得更清楚些
+---------------+---------------+---------------+
|     返回地址     |     调试事件     |      毫秒      |
+-------+-------+---------------+---------------+
     |
  被钩子覆盖
     |
+-------+-------+---------------+---------------+
|      我的代码    |     调试事件     |      毫秒      |
+---------------+---------------+---------------+
现在一旦WaitForDebugEvent打算返回到它的调用者是就会到达我的代码,而我们就可以随心所欲的处理异常了: 
hooking代码示例:
; ;
首先我们需要几个字节插入我们的hook
;
mov esi, [edi+WaitForDebugEvent]
xor ecx, ecx
push esi
__get5bytes: push esi
call ldex86
add ecx, eax
add esi, eax
cmp ecx, 5
jb __get5bytes
pop esi
push edi
lea edi, [edi+rippedbytes]
cld
rep movsb
pop edi
mov dword ptr[edi+goback+1], esi
;;
将字节拷贝到可以安全运行的缓存区域
;
mov esi, [edi+WaitForDebugEvent]
mov ecx, esi
lea ebx, [edi+hookwaitfordebugevent]
mov byte ptr[esi], 0e9h
add ecx, 5
sub ebx, ecx
mov dword ptr[esi+1], ebx

被覆盖部分的字节保存在了rippedbytes变量中:
hookwaitfordebugevent label dword
pusha
call deltahook
deltahook: pop ebp
sub ebp, offset deltahook
mov eax, [esp+8*4]
;保存原来的返回地址
mov [ebp+waitfordebugeventretaddress], eax
mov dword ptr[ebp+retorig+1], eax
lea ebp, [ebp+mywaitfordebugevent]
mov [esp+8*4], ebp
popa
rippedbytes db 30 dup(90h)
goback: push 0deac0deh <- WaitForDebugEvent + ripped bytes
ret
经过我们的工作,WaitForDebugEvent完成后我们就会到达这里了: imywaitfordebugevent proc
mov ecx, [esp-8] ;这里是debugevent
pusha
同样要注意偏移地址是负值,因为ret和leave指令已经将堆栈对齐了,因为windows API使用的是stdcall约定(除了几个函数如wsprintfA,DbgPrint,还有其他几个???)。
这种情况下我访问变量的时候我们必须计算出负的偏移地址,同样还有小心,此时不要将任何值压入堆栈,因为每次入栈都将会破环存在堆栈中的数据,这些数据对我们使用的这种方法是至关重要的。
现在我们关心的就是异常的处理了,只处理EXCEPTION_DEBUG_EVENT:
mywaitfordebugevent proc
mov ecx, [esp-8] ;这里是debugevent
pusha
call deltamydebug
deltamydebug: pop edi
sub edi, offset deltamydebug
mov ebx, ecx
cmp [ebx.de_code], EXCEPTION_DEBUG_EVENT
jne __return_to_original
cmp [ebx.de_u.ER_ExceptionCode], EXCEPTION_BREAKPOINT
jne __passexception
cmp [edi+firstint3h], 1
je __passexception
;在远进程中设置页面保护
pushv <dd ?>
push PAGE_EXECUTE_READWRITE or PAGE_GUARD
push [edi+c_range]
push [edi+c_start]
push [edi+phandle]
call [edi+VirtualProtectEx]
push DBG_CONTINUE
push [ebx.de_ThreadId]
push [ebx.de_ProcessId]
call [edi+ContinueDebugEvent]
mov [edi+firstint3h], 1
jmp __l33t
__passexception: push DBG_EXCEPTION_NOT_HANDLED
push [ebx.de_ThreadId]
push [ebx.de_ProcessId]
call [edi+ContinueDebugEvent]
;;现在的问题是我们返回哪里呢?我们可以把修改过的错误代码保存在debug_event.code
;中,然后继续执行,因为我们知道再没有什么会在破坏我们的代码了。 

__l33t: mov [
ebx.de_code], 0deadc0deh ;l33t
__return_to_original: popa
retorig: push 0deadc0deh ;改成返回地址
ret
endp
我们只对第一个int 3h感兴趣,它产生于DebugBreak,它应该以ContinueDebugEvent(DBG_CONTINUE)的形式传递,其他的情况我们都使用ContinueDebugEvent(DBG_EXCEPTION_NOT_HANDLED)的形式将它传回被调试程序,让我们的非侵入式的跟踪器来处理被跟踪进程的内存区,同样,为了避免破坏我们的代码,我们将debug_event.code设置成0deadc0deh,这样它就不会再处理debug_event了, 或者我们也可以将ThreadId或ProcessId的值设成毫无意义的数值,这样ContinueDebugEvent就对我们程序什么都不做了。
现在一切都交给我们存在被调试程序的非侵入式的跟踪器了,等待知道你得到一个信息告诉你oep找到了。另外MessageBoxA的“Ok”按钮被点击后,我还在非侵入式跟踪器中插入了一条jmp $,所以现在你要做的就是转出进程并且,当然得从任务管理器或者是Mark Russinovish的进程管理器中结束它了。

祝你好运 
代码位于 [armadillo_oep]文件夹中。




6. 调试注入的代码 ? 几条建议
调试这种代码的要点就是能够看到第二个进程里的代码,尤其是我们面对的是loader的loader或者跟踪被调试程序的时候。
有几个技巧需要提一下:
- 在你的代码中使用int 3h或者在SoftICE中使用bpint 3 或者i3here on来中断在代码的可疑部分
- 对于装载器的装载器的情况,使用ADDR来看第二个进程中到底发生了什么
- 编写非侵入式跟踪器时使用jmp $和ctrl+d中断在softice,因为此时bpint 3和i3here on会因为非侵入式跟踪器的关系中断很多次,或者可以使用drX寄存器跳过错误指令然后代码中的int 3h就会起作用了

这些都是针对调试类似的装载器的几点建议,祝你好运当然这些技巧对调试注入的DLL时也适用。






























7. 结论
这里我展示我所能想到的一些技巧以及我编写的一些代码。希望这能够帮到一些人,特别希望能让那些入门级的逆向者明白在RCE的世界里编程的能力是多么重要。我的意见可能有人赞成,有人反对,有人喜欢,也有人唾弃,或者是破口大骂说我照你的代码怎么编译不成功?不管怎么说,我留下了许多东西都需要思考,要记住熟能生巧。如果你开始没有成功,那就再试一次,答案肯定就在附近的某个地方。为了搞清楚内存管理器和Delphi代码的关系,我用了两天的时间,而编译成功armadillo_oep的代码只花了4天。


我在这里展示的所有技巧的相关代码都同本文附在了一起,为了更好的理解文章,我建议你读以下源代码。另外,Tasm32 DDK也包含在了其中。















随同这篇教程的所有代码都可以随意公开使用,但使用时请在致谢里提及作者以及ARTeam。请不要使用文中的理论进行非法行为,文中的所有信息都是以学习以及帮助更好的理解程序代码的安全技术为目的的。


8. 参考文献
[1] Optimization of 32 bit code, Benny/29a, http://vx.netlux.org/29a/29a-4/29a-4.215
[2] Gaining important datas from PEB under NT boxes, Ratter/29a,
http://vx.netlux.org/29a/29a-6/29a-6.224
[3] Billy Belcebu Virus Writing Guide 1.0 for Win32, Billy Belcebu,
http://vx.netlux.org/29a/29a-4/29a-4.202
[4] Retrieving API’s addresses. LethalMind, http://vx.netlux.org/29a/29a-4/29a-4.227
[5] Solution to The Amazing Picture downloader, deroko,
http://www.crackmes.de/users/warrantyvoider/the_amazing_picture_downloader/
[6] Unpacking and Dumping ExeCryptor and coding loader for it, deroko,
http://tutorials.accessroot.com
[7] eXtended (XDE) disassembler engine, z0mbie, http://vx.netlux.org/29a/magazines/29a-8.rar
[8] Anti-Anti-Bpm, yates, http://www.yates2k.net/syscode/bpm.rar
[9] Remote Windows Kernel Exploatation - Step into ring0, Barnaby Jack,
http://www.eeye.com/~data/publish/whitepapers/research/OT20050205.FILE.pdf
[10] Win32 Debug API Part 1/2/3, Iczelion, http://win32asm.cjb.net/
[11] Loader from ring0, deroko, ARTeam eZine #1, http://ezine.accessroot.com
Some useful tutorials to learn about loaders theory:
[12] Shub-Niggurath and ThunderPwr coding loader series, http://tutorials.accessroot.com
[13] Createing Loaders & Dumpers ? Crackers Guide to Program Flow Control, yates,
http://www.yates2k.net/lad.txt
[14] Using Memory Breakpoints, Shub-Niggurath, http://tutorials.accessroot.com
一些很有用的文章:
[15] Dll Injection Tutorial, Darawk, http://www.edgeofnowhere.cc/viewtopic.php?p=2441382
[16] Three Ways to Inject Your Code into Another Process, Robert Kuster, The Code Project
http://www.codeproject.com/threads/winspy.asp
[17] InjLib ? A Library that implements remote code injection for all Windows versions,
Antonio Feijao, The Code Project, http://www.codeproject.com/library/InjLib.asp
有用的工具:
[18] OllyAdvanced plug-in, MaRKuS TH-DJM,
http://www.tuts4you.com/forum/index.php?showtopic=7092
http://omega.intechhosting.com/~access/forums/index.php?showtopic=2542
[19] LordPE plug-in, deroko, http://deroko.phearless.org/dumpdll/
[20] Ice-Ext, Stan, http://stenri.pisem.net/




9. 致谢
这里我要感谢所有的ARTeam的成员将他们的知识无私奉献,感谢29a病毒团队出品的最好的电子杂志,感谢我phearless电子杂志的朋友,感谢我在Reversing Labs论坛上的所有朋友,感谢所有伟大的程序员… …当然,还有感谢阅读本文的你。




 
http://cracking.accessroot.com
© [ARTeam] 2006 
#!/bin/bash datetime=`date +%Y%m%d-%H-%M-%S` OneWeekAgo=`date -d "1 week ago" +%Y%m%d` basePath=/usr/local/mysql/backup bakFileName=mysql_backup_$datetime.sql bakFile=$basePath/$bakFileName logfile=$basePath/full_$datetime.log pid=`ps -ef | grep -v "grep" |grep -i mysqldump|awk '{print $2}'|head -n 1` if [ -z $pid ] then echo " start full backup database " >> $logfile touch $bakFile #docker run --rm -v $basePath:/backups mysql:5.7.30 \ mysqldump -uroot -p'123456' --all-databases --quick --triggers --routines --events --single-transaction --result-file=/$basePath >> $logfile 2>&1 #docker exec -i mysql sh -c "exec mysqldump -uroot -p'skycomm@5342' --all-databases --quick --triggers" >$bakFile 2>&1 mysqldump -uroot -p'123456' --anti_fraud --quick --triggers --routines --events --single-transaction --result-file=/usr/local/mysql/backup/anti_fraud >> $logfile 2>&1 mysqldump -uroot -p'123456' --skycomm_base_service --quick --triggers --routines --events --single-transaction --result-file=/usr/local/mysql/backup/skycomm_base_service >> $logfile 2>&1 mysqldump -uroot -p'123456' --performance_schema --quick --triggers --routines --events --single-transaction --result-file=/usr/local/mysql/backup/performance_schema >> $logfile 2>&1 ret=`tail -n 1 $bakFile |grep "Dump completed on"|wc -l` if [ "$ret" = 1 ] ; then echo 'delete expired backup ' $basePath/mysql_backup_$OneWeekAgo*.sql >> $logfile rm -f $basePath/mysql_backup_$OneWeekAgo*.sql rm -f $basePath/full_$OneWeekAgo*.log else echo 'backup failure ,no delete expired backup' >> $logfile fi else echo "****** mysqldump in backup database ****** " >> $logfile fi 帮我优化这个sql
04-03
from data import COCODetection, get_label_map, MEANS, COLORS from yolact import Yolact from utils.augmentations import BaseTransform, FastBaseTransform, Resize from utils.functions import MovingAverage, ProgressBar from layers.box_utils import jaccard, center_size, mask_iou from utils import timer from utils.functions import SavePath from layers.output_utils import postprocess, undo_image_transformation import pycocotools from data import cfg, set_cfg, set_dataset import numpy as np import torch import torch.backends.cudnn as cudnn from torch.autograd import Variable import argparse import time import random import cProfile import pickle import json import os from collections import defaultdict from pathlib import Path from collections import OrderedDict from PIL import Image import matplotlib.pyplot as plt import cv2 def str2bool(v): if v.lower() in ('yes', 'true', 't', 'y', '1'): return True elif v.lower() in ('no', 'false', 'f', 'n', '0'): return False else: raise argparse.ArgumentTypeError('Boolean value expected.') def parse_args(argv=None): parser = argparse.ArgumentParser( description='YOLACT COCO Evaluation') parser.add_argument('--trained_model', default='weights/yolact_base_105_101798_interrupt.pth', type=str, help='Trained state_dict file path to open. If "interrupt", this will open the interrupt file.') parser.add_argument('--top_k', default=5, type=int, help='Further restrict the number of predictions to parse') parser.add_argument('--cuda', default=True, type=str2bool, help='Use cuda to evaulate model') parser.add_argument('--fast_nms', default=True, type=str2bool, help='Whether to use a faster, but not entirely correct version of NMS.') parser.add_argument('--cross_class_nms', default=False, type=str2bool, help='Whether compute NMS cross-class or per-class.') parser.add_argument('--display_masks', default=True, type=str2bool, help='Whether or not to display masks over bounding boxes') parser.add_argument('--display_bboxes', default=True, type=str2bool, help='Whether or not to display bboxes around masks') parser.add_argument('--display_text', default=True, type=str2bool, help='Whether or not to display text (class [score])') parser.add_argument('--display_scores', default=True, type=str2bool, help='Whether or not to display scores in addition to classes') parser.add_argument('--display', dest='display', action='store_true', help='Display qualitative results instead of quantitative ones.') parser.add_argument('--shuffle', dest='shuffle', action='store_true', help='Shuffles the images when displaying them. Doesn\'t have much of an effect when display is off though.') parser.add_argument('--ap_data_file', default='results/ap_data.pkl', type=str, help='In quantitative mode, the file to save detections before calculating mAP.') parser.add_argument('--resume', dest='resume', action='store_true', help='If display not set, this resumes mAP calculations from the ap_data_file.') parser.add_argument('--max_images', default=-1, type=int, help='The maximum number of images from the dataset to consider. Use -1 for all.') parser.add_argument('--output_coco_json', dest='output_coco_json', action='store_true', help='If display is not set, instead of processing IoU values, this just dumps detections into the coco json file.') parser.add_argument('--bbox_det_file', default='results/bbox_detections.json', type=str, help='The output file for coco bbox results if --coco_results is set.') parser.add_argument('--mask_det_file', default='results/mask_detections.json', type=str, help='The output file for coco mask results if --coco_results is set.') parser.add_argument('--config', default=None, help='The config object to use.') parser.add_argument('--output_web_json', dest='output_web_json', action='store_true', help='If display is not set, instead of processing IoU values, this dumps detections for usage with the detections viewer web thingy.') parser.add_argument('--web_det_path', default='web/dets/', type=str, help='If output_web_json is set, this is the path to dump detections into.') parser.add_argument('--no_bar', dest='no_bar', action='store_true', help='Do not output the status bar. This is useful for when piping to a file.') parser.add_argument('--display_lincomb', default=False, type=str2bool, help='If the config uses lincomb masks, output a visualization of how those masks are created.') parser.add_argument('--benchmark', default=False, dest='benchmark', action='store_true', help='Equivalent to running display mode but without displaying an image.') parser.add_argument('--no_sort', default=False, dest='no_sort', action='store_true', help='Do not sort images by hashed image ID.') parser.add_argument('--seed', default=None, type=int, help='The seed to pass into random.seed. Note: this is only really for the shuffle and does not (I think) affect cuda stuff.') parser.add_argument('--mask_proto_debug', default=False, dest='mask_proto_debug', action='store_true', help='Outputs stuff for scripts/compute_mask.py.') parser.add_argument('--no_crop', default=False, dest='crop', action='store_false', help='Do not crop output masks with the predicted bounding box.') parser.add_argument('--image', default=None, type=str, help='A path to an image to use for display.') parser.add_argument('--images', default='E:/yolact-master/coco/images/train2017', type=str, help='Input and output paths separated by a colon.') parser.add_argument('--video', default=None, type=str, help='A path to a video to evaluate on. Passing in a number will use that index webcam.') parser.add_argument('--video_multiframe', default=1, type=int, help='The number of frames to evaluate in parallel to make videos play at higher fps.') parser.add_argument('--score_threshold', default=0.15, type=float, help='Detections with a score under this threshold will not be considered. This currently only works in display mode.') parser.add_argument('--dataset', default=None, type=str, help='If specified, override the dataset specified in the config with this one (example: coco2017_dataset).') parser.add_argument('--detect', default=False, dest='detect', action='store_true', help='Don\'t evauluate the mask branch at all and only do object detection. This only works for --display and --benchmark.') parser.add_argument('--display_fps', default=False, dest='display_fps', action='store_true', help='When displaying / saving video, draw the FPS on the frame') parser.add_argument('--emulate_playback', default=False, dest='emulate_playback', action='store_true', help='When saving a video, emulate the framerate that you\'d get running in real-time mode.') parser.set_defaults(no_bar=False, display=False, resume=False, output_coco_json=False, output_web_json=False, shuffle=False, benchmark=False, no_sort=False, no_hash=False, mask_proto_debug=False, crop=True, detect=False, display_fps=False, emulate_playback=False) global args args = parser.parse_args(argv) if args.output_web_json: args.output_coco_json = True if args.seed is not None: random.seed(args.seed) iou_thresholds = [x / 100 for x in range(50, 100, 5)] coco_cats = {} # Call prep_coco_cats to fill this coco_cats_inv = {} color_cache = defaultdict(lambda: {}) def prep_display(dets_out, img, h, w, undo_transform=True, class_color=False, mask_alpha=0.45, fps_str=''): """ Note: If undo_transform=False then im_h and im_w are allowed to be None. """ if undo_transform: img_numpy = undo_image_transformation(img, w, h) img_gpu = torch.Tensor(img_numpy).cuda() else: img_gpu = img / 255.0 h, w, _ = img.shape with timer.env('Postprocess'): save = cfg.rescore_bbox cfg.rescore_bbox = True t = postprocess(dets_out, w, h, visualize_lincomb = args.display_lincomb, crop_masks = args.crop, score_threshold = args.score_threshold) cfg.rescore_bbox = save with timer.env('Copy'): idx = t[1].argsort(0, descending=True)[:args.top_k] if cfg.eval_mask_branch: # Masks are drawn on the GPU, so don't copy masks = t[3][idx] classes, scores, boxes = [x[idx].cpu().numpy() for x in t[:3]] num_dets_to_consider = min(args.top_k, classes.shape[0]) for j in range(num_dets_to_consider): if scores[j] < args.score_threshold: num_dets_to_consider = j break # Quick and dirty lambda for selecting the color for a particular index # Also keeps track of a per-gpu color cache for maximum speed def get_color(j, on_gpu=None): global color_cache color_idx = (classes[j] * 5 if class_color else j * 5) % len(COLORS) if on_gpu is not None and color_idx in color_cache[on_gpu]: return color_cache[on_gpu][color_idx] else: color = COLORS[color_idx] if not undo_transform: # The image might come in as RGB or BRG, depending color = (color[2], color[1], color[0]) if on_gpu is not None: color = torch.Tensor(color).to(on_gpu).float() / 255. color_cache[on_gpu][color_idx] = color return color # First, draw the masks on the GPU where we can do it really fast # Beware: very fast but possibly unintelligible mask-drawing code ahead # I wish I had access to OpenGL or Vulkan but alas, I guess Pytorch tensor operations will have to suffice if args.display_masks and cfg.eval_mask_branch and num_dets_to_consider > 0: # After this, mask is of size [num_dets, h, w, 1] masks = masks[:num_dets_to_consider, :, :, None] # Prepare the RGB images for each mask given their color (size [num_dets, h, w, 1]) colors = torch.cat([get_color(j, on_gpu=img_gpu.device.index).view(1, 1, 1, 3) for j in range(num_dets_to_consider)], dim=0) masks_color = masks.repeat(1, 1, 1, 3) * colors * mask_alpha # This is 1 everywhere except for 1-mask_alpha where the mask is inv_alph_masks = masks * (-mask_alpha) + 1 # I did the math for this on pen and paper. This whole block should be equivalent to: # for j in range(num_dets_to_consider): # img_gpu = img_gpu * inv_alph_masks[j] + masks_color[j] masks_color_summand = masks_color[0] if num_dets_to_consider > 1: inv_alph_cumul = inv_alph_masks[:(num_dets_to_consider-1)].cumprod(dim=0) masks_color_cumul = masks_color[1:] * inv_alph_cumul masks_color_summand += masks_color_cumul.sum(dim=0) img_gpu = img_gpu * inv_alph_masks.prod(dim=0) + masks_color_summand if args.display_fps: # Draw the box for the fps on the GPU font_face = cv2.FONT_HERSHEY_DUPLEX font_scale = 0.6 font_thickness = 1 text_w, text_h = cv2.getTextSize(fps_str, font_face, font_scale, font_thickness)[0] img_gpu[0:text_h+8, 0:text_w+8] *= 0.6 # 1 - Box alpha # Then draw the stuff that needs to be done on the cpu # Note, make sure this is a uint8 tensor or opencv will not anti alias text for whatever reason img_numpy = (img_gpu * 255).byte().cpu().numpy() if args.display_fps: # Draw the text on the CPU text_pt = (4, text_h + 2) text_color = [255, 255, 255] cv2.putText(img_numpy, fps_str, text_pt, font_face, font_scale, text_color, font_thickness, cv2.LINE_AA) if num_dets_to_consider == 0: return img_numpy if args.display_text or args.display_bboxes: for j in reversed(range(num_dets_to_consider)): x1, y1, x2, y2 = boxes[j, :] color = get_color(j) score = scores[j] if args.display_bboxes: cv2.rectangle(img_numpy, (x1, y1), (x2, y2), color, 1) if args.display_text: _class = cfg.dataset.class_names[classes[j]] text_str = '%s: %.2f' % (_class, score) if args.display_scores else _class font_face = cv2.FONT_HERSHEY_DUPLEX font_scale = 0.6 font_thickness = 1 text_w, text_h = cv2.getTextSize(text_str, font_face, font_scale, font_thickness)[0] text_pt = (x1, y1 - 3) text_color = [255, 255, 255] cv2.rectangle(img_numpy, (x1, y1), (x1 + text_w, y1 - text_h - 4), color, -1) cv2.putText(img_numpy, text_str, text_pt, font_face, font_scale, text_color, font_thickness, cv2.LINE_AA) return img_numpy def prep_benchmark(dets_out, h, w): with timer.env('Postprocess'): t = postprocess(dets_out, w, h, crop_masks=args.crop, score_threshold=args.score_threshold) with timer.env('Copy'): classes, scores, boxes, masks = [x[:args.top_k] for x in t] if isinstance(scores, list): box_scores = scores[0].cpu().numpy() mask_scores = scores[1].cpu().numpy() else: scores = scores.cpu().numpy() classes = classes.cpu().numpy() boxes = boxes.cpu().numpy() masks = masks.cpu().numpy() with timer.env('Sync'): # Just in case torch.cuda.synchronize() def prep_coco_cats(): """ Prepare inverted table for category id lookup given a coco cats object. """ for coco_cat_id, transformed_cat_id_p1 in get_label_map().items(): transformed_cat_id = transformed_cat_id_p1 - 1 coco_cats[transformed_cat_id] = coco_cat_id coco_cats_inv[coco_cat_id] = transformed_cat_id def get_coco_cat(transformed_cat_id): """ transformed_cat_id is [0,80) as indices in cfg.dataset.class_names """ return coco_cats[transformed_cat_id] def get_transformed_cat(coco_cat_id): """ transformed_cat_id is [0,80) as indices in cfg.dataset.class_names """ return coco_cats_inv[coco_cat_id] class Detections: def __init__(self): self.bbox_data = [] self.mask_data = [] def add_bbox(self, image_id:int, category_id:int, bbox:list, score:float): """ Note that bbox should be a list or tuple of (x1, y1, x2, y2) """ bbox = [bbox[0], bbox[1], bbox[2]-bbox[0], bbox[3]-bbox[1]] # Round to the nearest 10th to avoid huge file sizes, as COCO suggests bbox = [round(float(x)*10)/10 for x in bbox] self.bbox_data.append({ 'image_id': int(image_id), 'category_id': get_coco_cat(int(category_id)), 'bbox': bbox, 'score': float(score) }) def add_mask(self, image_id:int, category_id:int, segmentation:np.ndarray, score:float): """ The segmentation should be the full mask, the size of the image and with size [h, w]. """ rle = pycocotools.mask.encode(np.asfortranarray(segmentation.astype(np.uint8))) rle['counts'] = rle['counts'].decode('ascii') # json.dump doesn't like bytes strings self.mask_data.append({ 'image_id': int(image_id), 'category_id': get_coco_cat(int(category_id)), 'segmentation': rle, 'score': float(score) }) def dump(self): dump_arguments = [ (self.bbox_data, args.bbox_det_file), (self.mask_data, args.mask_det_file) ] for data, path in dump_arguments: with open(path, 'w') as f: json.dump(data, f) def dump_web(self): """ Dumps it in the format for my web app. Warning: bad code ahead! """ config_outs = ['preserve_aspect_ratio', 'use_prediction_module', 'use_yolo_regressors', 'use_prediction_matching', 'train_masks'] output = { 'info' : { 'Config': {key: getattr(cfg, key) for key in config_outs}, } } image_ids = list(set([x['image_id'] for x in self.bbox_data])) image_ids.sort() image_lookup = {_id: idx for idx, _id in enumerate(image_ids)} output['images'] = [{'image_id': image_id, 'dets': []} for image_id in image_ids] # These should already be sorted by score with the way prep_metrics works. for bbox, mask in zip(self.bbox_data, self.mask_data): image_obj = output['images'][image_lookup[bbox['image_id']]] image_obj['dets'].append({ 'score': bbox['score'], 'bbox': bbox['bbox'], 'category': cfg.dataset.class_names[get_transformed_cat(bbox['category_id'])], 'mask': mask['segmentation'], }) with open(os.path.join(args.web_det_path, '%s.json' % cfg.name), 'w') as f: json.dump(output, f) def _mask_iou(mask1, mask2, iscrowd=False): with timer.env('Mask IoU'): ret = mask_iou(mask1, mask2, iscrowd) return ret.cpu() def _bbox_iou(bbox1, bbox2, iscrowd=False): with timer.env('BBox IoU'): ret = jaccard(bbox1, bbox2, iscrowd) return ret.cpu() def prep_metrics(ap_data, dets, img, gt, gt_masks, h, w, num_crowd, image_id, detections:Detections=None): """ Returns a list of APs for this image, with each element being for a class """ if not args.output_coco_json: with timer.env('Prepare gt'): gt_boxes = torch.Tensor(gt[:, :4]) gt_boxes[:, [0, 2]] *= w gt_boxes[:, [1, 3]] *= h gt_classes = list(gt[:, 4].astype(int)) gt_masks = torch.Tensor(gt_masks).view(-1, h*w) if num_crowd > 0: split = lambda x: (x[-num_crowd:], x[:-num_crowd]) crowd_boxes , gt_boxes = split(gt_boxes) crowd_masks , gt_masks = split(gt_masks) crowd_classes, gt_classes = split(gt_classes) with timer.env('Postprocess'): classes, scores, boxes, masks = postprocess(dets, w, h, crop_masks=args.crop, score_threshold=args.score_threshold) if classes.size(0) == 0: return classes = list(classes.cpu().numpy().astype(int)) if isinstance(scores, list): box_scores = list(scores[0].cpu().numpy().astype(float)) mask_scores = list(scores[1].cpu().numpy().astype(float)) else: scores = list(scores.cpu().numpy().astype(float)) box_scores = scores mask_scores = scores masks = masks.view(-1, h*w).cuda() boxes = boxes.cuda() if args.output_coco_json: with timer.env('JSON Output'): boxes = boxes.cpu().numpy() masks = masks.view(-1, h, w).cpu().numpy() for i in range(masks.shape[0]): # Make sure that the bounding box actually makes sense and a mask was produced if (boxes[i, 3] - boxes[i, 1]) * (boxes[i, 2] - boxes[i, 0]) > 0: detections.add_bbox(image_id, classes[i], boxes[i,:], box_scores[i]) detections.add_mask(image_id, classes[i], masks[i,:,:], mask_scores[i]) return with timer.env('Eval Setup'): num_pred = len(classes) num_gt = len(gt_classes) mask_iou_cache = _mask_iou(masks, gt_masks) bbox_iou_cache = _bbox_iou(boxes.float(), gt_boxes.float()) if num_crowd > 0: crowd_mask_iou_cache = _mask_iou(masks, crowd_masks, iscrowd=True) crowd_bbox_iou_cache = _bbox_iou(boxes.float(), crowd_boxes.float(), iscrowd=True) else: crowd_mask_iou_cache = None crowd_bbox_iou_cache = None box_indices = sorted(range(num_pred), key=lambda i: -box_scores[i]) mask_indices = sorted(box_indices, key=lambda i: -mask_scores[i]) iou_types = [ ('box', lambda i,j: bbox_iou_cache[i, j].item(), lambda i,j: crowd_bbox_iou_cache[i,j].item(), lambda i: box_scores[i], box_indices), ('mask', lambda i,j: mask_iou_cache[i, j].item(), lambda i,j: crowd_mask_iou_cache[i,j].item(), lambda i: mask_scores[i], mask_indices) ] timer.start('Main loop') for _class in set(classes + gt_classes): ap_per_iou = [] num_gt_for_class = sum([1 for x in gt_classes if x == _class]) for iouIdx in range(len(iou_thresholds)): iou_threshold = iou_thresholds[iouIdx] for iou_type, iou_func, crowd_func, score_func, indices in iou_types: gt_used = [False] * len(gt_classes) ap_obj = ap_data[iou_type][iouIdx][_class] ap_obj.add_gt_positives(num_gt_for_class) for i in indices: if classes[i] != _class: continue max_iou_found = iou_threshold max_match_idx = -1 for j in range(num_gt): if gt_used[j] or gt_classes[j] != _class: continue iou = iou_func(i, j) if iou > max_iou_found: max_iou_found = iou max_match_idx = j if max_match_idx >= 0: gt_used[max_match_idx] = True ap_obj.push(score_func(i), True) else: # If the detection matches a crowd, we can just ignore it matched_crowd = False if num_crowd > 0: for j in range(len(crowd_classes)): if crowd_classes[j] != _class: continue iou = crowd_func(i, j) if iou > iou_threshold: matched_crowd = True break # All this crowd code so that we can make sure that our eval code gives the # same result as COCOEval. There aren't even that many crowd annotations to # begin with, but accuracy is of the utmost importance. if not matched_crowd: ap_obj.push(score_func(i), False) timer.stop('Main loop') class APDataObject: """ Stores all the information necessary to calculate the AP for one IoU and one class. Note: I type annotated this because why not. """ def __init__(self): self.data_points = [] self.num_gt_positives = 0 def push(self, score:float, is_true:bool): self.data_points.append((score, is_true)) def add_gt_positives(self, num_positives:int): """ Call this once per image. """ self.num_gt_positives += num_positives def is_empty(self) -> bool: return len(self.data_points) == 0 and self.num_gt_positives == 0 def get_ap(self) -> float: """ Warning: result not cached. """ if self.num_gt_positives == 0: return 0 # Sort descending by score self.data_points.sort(key=lambda x: -x[0]) precisions = [] recalls = [] num_true = 0 num_false = 0 # Compute the precision-recall curve. The x axis is recalls and the y axis precisions. for datum in self.data_points: # datum[1] is whether the detection a true or false positive if datum[1]: num_true += 1 else: num_false += 1 precision = num_true / (num_true + num_false) recall = num_true / self.num_gt_positives precisions.append(precision) recalls.append(recall) # Smooth the curve by computing [max(precisions[i:]) for i in range(len(precisions))] # Basically, remove any temporary dips from the curve. # At least that's what I think, idk. COCOEval did it so I do too. for i in range(len(precisions)-1, 0, -1): if precisions[i] > precisions[i-1]: precisions[i-1] = precisions[i] # Compute the integral of precision(recall) d_recall from recall=0->1 using fixed-length riemann summation with 101 bars. y_range = [0] * 101 # idx 0 is recall == 0.0 and idx 100 is recall == 1.00 x_range = np.array([x / 100 for x in range(101)]) recalls = np.array(recalls) # I realize this is weird, but all it does is find the nearest precision(x) for a given x in x_range. # Basically, if the closest recall we have to 0.01 is 0.009 this sets precision(0.01) = precision(0.009). # I approximate the integral this way, because that's how COCOEval does it. indices = np.searchsorted(recalls, x_range, side='left') for bar_idx, precision_idx in enumerate(indices): if precision_idx < len(precisions): y_range[bar_idx] = precisions[precision_idx] # Finally compute the riemann sum to get our integral. # avg([precision(x) for x in 0:0.01:1]) return sum(y_range) / len(y_range) def badhash(x): """ Just a quick and dirty hash function for doing a deterministic shuffle based on image_id. Source: https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key """ x = (((x >> 16) ^ x) * 0x045d9f3b) & 0xFFFFFFFF x = (((x >> 16) ^ x) * 0x045d9f3b) & 0xFFFFFFFF x = ((x >> 16) ^ x) & 0xFFFFFFFF return x def evalimage(net:Yolact, path:str, save_path:str=None): frame = torch.from_numpy(cv2.imread(path)).cuda().float() batch = FastBaseTransform()(frame.unsqueeze(0)) preds = net(batch) img_numpy = prep_display(preds, frame, None, None, undo_transform=False) if save_path is None: img_numpy = img_numpy[:, :, (2, 1, 0)] if save_path is None: plt.imshow(img_numpy) plt.title(path) plt.show() else: cv2.imwrite(save_path, img_numpy) def evalimages(net:Yolact, input_folder:str, output_folder:str): if not os.path.exists(output_folder): os.mkdir(output_folder) print() for p in Path(input_folder).glob('*'): path = str(p) name = os.path.basename(path) name = '.'.join(name.split('.')[:-1]) + '.png' out_path = os.path.join(output_folder, name) evalimage(net, path, out_path) print(path + ' -> ' + out_path) print('Done.') from multiprocessing.pool import ThreadPool from queue import Queue class CustomDataParallel(torch.nn.DataParallel): """ A Custom Data Parallel class that properly gathers lists of dictionaries. """ def gather(self, outputs, output_device): # Note that I don't actually want to convert everything to the output_device return sum(outputs, []) def evalvideo(net:Yolact, path:str, out_path:str=None): # If the path is a digit, parse it as a webcam index is_webcam = path.isdigit() # If the input image size is constant, this make things faster (hence why we can use it in a video setting). cudnn.benchmark = True if is_webcam: vid = cv2.VideoCapture(int(path)) else: vid = cv2.VideoCapture(path) if not vid.isOpened(): print('Could not open video "%s"' % path) exit(-1) target_fps = round(vid.get(cv2.CAP_PROP_FPS)) frame_width = round(vid.get(cv2.CAP_PROP_FRAME_WIDTH)) frame_height = round(vid.get(cv2.CAP_PROP_FRAME_HEIGHT)) if is_webcam: num_frames = float('inf') else: num_frames = round(vid.get(cv2.CAP_PROP_FRAME_COUNT)) net = CustomDataParallel(net).cuda() transform = torch.nn.DataParallel(FastBaseTransform()).cuda() frame_times = MovingAverage(100) fps = 0 frame_time_target = 1 / target_fps running = True fps_str = '' vid_done = False frames_displayed = 0 if out_path is not None: out = cv2.VideoWriter(out_path, cv2.VideoWriter_fourcc(*"mp4v"), target_fps, (frame_width, frame_height)) def cleanup_and_exit(): print() pool.terminate() vid.release() if out_path is not None: out.release() cv2.destroyAllWindows() exit() def get_next_frame(vid): frames = [] for idx in range(args.video_multiframe): frame = vid.read()[1] if frame is None: return frames frames.append(frame) return frames def transform_frame(frames): with torch.no_grad(): frames = [torch.from_numpy(frame).cuda().float() for frame in frames] return frames, transform(torch.stack(frames, 0)) def eval_network(inp): with torch.no_grad(): frames, imgs = inp num_extra = 0 while imgs.size(0) < args.video_multiframe: imgs = torch.cat([imgs, imgs[0].unsqueeze(0)], dim=0) num_extra += 1 out = net(imgs) if num_extra > 0: out = out[:-num_extra] return frames, out def prep_frame(inp, fps_str): with torch.no_grad(): frame, preds = inp return prep_display(preds, frame, None, None, undo_transform=False, class_color=True, fps_str=fps_str) frame_buffer = Queue() video_fps = 0 # All this timing code to make sure that def play_video(): try: nonlocal frame_buffer, running, video_fps, is_webcam, num_frames, frames_displayed, vid_done video_frame_times = MovingAverage(100) frame_time_stabilizer = frame_time_target last_time = None stabilizer_step = 0.0005 progress_bar = ProgressBar(30, num_frames) while running: frame_time_start = time.time() if not frame_buffer.empty(): next_time = time.time() if last_time is not None: video_frame_times.add(next_time - last_time) video_fps = 1 / video_frame_times.get_avg() if out_path is None: cv2.imshow(path, frame_buffer.get()) else: out.write(frame_buffer.get()) frames_displayed += 1 last_time = next_time if out_path is not None: if video_frame_times.get_avg() == 0: fps = 0 else: fps = 1 / video_frame_times.get_avg() progress = frames_displayed / num_frames * 100 progress_bar.set_val(frames_displayed) print('\rProcessing Frames %s %6d / %6d (%5.2f%%) %5.2f fps ' % (repr(progress_bar), frames_displayed, num_frames, progress, fps), end='') # This is split because you don't want savevideo to require cv2 display functionality (see #197) if out_path is None and cv2.waitKey(1) == 27: # Press Escape to close running = False if not (frames_displayed < num_frames): running = False if not vid_done: buffer_size = frame_buffer.qsize() if buffer_size < args.video_multiframe: frame_time_stabilizer += stabilizer_step elif buffer_size > args.video_multiframe: frame_time_stabilizer -= stabilizer_step if frame_time_stabilizer < 0: frame_time_stabilizer = 0 new_target = frame_time_stabilizer if is_webcam else max(frame_time_stabilizer, frame_time_target) else: new_target = frame_time_target next_frame_target = max(2 * new_target - video_frame_times.get_avg(), 0) target_time = frame_time_start + next_frame_target - 0.001 # Let's just subtract a millisecond to be safe if out_path is None or args.emulate_playback: # This gives more accurate timing than if sleeping the whole amount at once while time.time() < target_time: time.sleep(0.001) else: # Let's not starve the main thread, now time.sleep(0.001) except: # See issue #197 for why this is necessary import traceback traceback.print_exc() extract_frame = lambda x, i: (x[0][i] if x[1][i]['detection'] is None else x[0][i].to(x[1][i]['detection']['box'].device), [x[1][i]]) # Prime the network on the first frame because I do some thread unsafe things otherwise print('Initializing model... ', end='') first_batch = eval_network(transform_frame(get_next_frame(vid))) print('Done.') # For each frame the sequence of functions it needs to go through to be processed (in reversed order) sequence = [prep_frame, eval_network, transform_frame] pool = ThreadPool(processes=len(sequence) + args.video_multiframe + 2) pool.apply_async(play_video) active_frames = [{'value': extract_frame(first_batch, i), 'idx': 0} for i in range(len(first_batch[0]))] print() if out_path is None: print('Press Escape to close.') try: while vid.isOpened() and running: # Hard limit on frames in buffer so we don't run out of memory >.> while frame_buffer.qsize() > 100: time.sleep(0.001) start_time = time.time() # Start loading the next frames from the disk if not vid_done: next_frames = pool.apply_async(get_next_frame, args=(vid,)) else: next_frames = None if not (vid_done and len(active_frames) == 0): # For each frame in our active processing queue, dispatch a job # for that frame using the current function in the sequence for frame in active_frames: _args = [frame['value']] if frame['idx'] == 0: _args.append(fps_str) frame['value'] = pool.apply_async(sequence[frame['idx']], args=_args) # For each frame whose job was the last in the sequence (i.e. for all final outputs) for frame in active_frames: if frame['idx'] == 0: frame_buffer.put(frame['value'].get()) # Remove the finished frames from the processing queue active_frames = [x for x in active_frames if x['idx'] > 0] # Finish evaluating every frame in the processing queue and advanced their position in the sequence for frame in list(reversed(active_frames)): frame['value'] = frame['value'].get() frame['idx'] -= 1 if frame['idx'] == 0: # Split this up into individual threads for prep_frame since it doesn't support batch size active_frames += [{'value': extract_frame(frame['value'], i), 'idx': 0} for i in range(1, len(frame['value'][0]))] frame['value'] = extract_frame(frame['value'], 0) # Finish loading in the next frames and add them to the processing queue if next_frames is not None: frames = next_frames.get() if len(frames) == 0: vid_done = True else: active_frames.append({'value': frames, 'idx': len(sequence)-1}) # Compute FPS frame_times.add(time.time() - start_time) fps = args.video_multiframe / frame_times.get_avg() else: fps = 0 fps_str = 'Processing FPS: %.2f | Video Playback FPS: %.2f | Frames in Buffer: %d' % (fps, video_fps, frame_buffer.qsize()) if not args.display_fps: print('\r' + fps_str + ' ', end='') except KeyboardInterrupt: print('\nStopping...') cleanup_and_exit() def evaluate(net:Yolact, dataset, train_mode=False): net.detect.use_fast_nms = args.fast_nms net.detect.use_cross_class_nms = args.cross_class_nms cfg.mask_proto_debug = args.mask_proto_debug # TODO Currently we do not support Fast Mask Re-scroing in evalimage, evalimages, and evalvideo if args.image is not None: if ':' in args.image: inp, out = args.image.split(':') evalimage(net, inp, out) else: evalimage(net, args.image) return elif args.images is not None: inp, out = args.images.split('E:/yolact-master/coco/images/train2017: E:/yolact-master/results/output') evalimages(net, inp, out) return elif args.video is not None: if ':' in args.video: inp, out = args.video.split(':') evalvideo(net, inp, out) else: evalvideo(net, args.video) return frame_times = MovingAverage() dataset_size = len(dataset) if args.max_images < 0 else min(args.max_images, len(dataset)) progress_bar = ProgressBar(30, dataset_size) print() if not args.display and not args.benchmark: # For each class and iou, stores tuples (score, isPositive) # Index ap_data[type][iouIdx][classIdx] ap_data = { 'box' : [[APDataObject() for _ in cfg.dataset.class_names] for _ in iou_thresholds], 'mask': [[APDataObject() for _ in cfg.dataset.class_names] for _ in iou_thresholds] } detections = Detections() else: timer.disable('Load Data') dataset_indices = list(range(len(dataset))) if args.shuffle: random.shuffle(dataset_indices) elif not args.no_sort: # Do a deterministic shuffle based on the image ids # # I do this because on python 3.5 dictionary key order is *random*, while in 3.6 it's # the order of insertion. That means on python 3.6, the images come in the order they are in # in the annotations file. For some reason, the first images in the annotations file are # the hardest. To combat this, I use a hard-coded hash function based on the image ids # to shuffle the indices we use. That way, no matter what python version or how pycocotools # handles the data, we get the same result every time. hashed = [badhash(x) for x in dataset.ids] dataset_indices.sort(key=lambda x: hashed[x]) dataset_indices = dataset_indices[:dataset_size] try: # Main eval loop for it, image_idx in enumerate(dataset_indices): timer.reset() with timer.env('Load Data'): img, gt, gt_masks, h, w, num_crowd = dataset.pull_item(image_idx) # Test flag, do not upvote if cfg.mask_proto_debug: with open('scripts/info.txt', 'w') as f: f.write(str(dataset.ids[image_idx])) np.save('scripts/gt.npy', gt_masks) batch = Variable(img.unsqueeze(0)) if args.cuda: batch = batch.cuda() with timer.env('Network Extra'): preds = net(batch) # Perform the meat of the operation here depending on our mode. if args.display: img_numpy = prep_display(preds, img, h, w) elif args.benchmark: prep_benchmark(preds, h, w) else: prep_metrics(ap_data, preds, img, gt, gt_masks, h, w, num_crowd, dataset.ids[image_idx], detections) # First couple of images take longer because we're constructing the graph. # Since that's technically initialization, don't include those in the FPS calculations. if it > 1: frame_times.add(timer.total_time()) if args.display: if it > 1: print('Avg FPS: %.4f' % (1 / frame_times.get_avg())) plt.imshow(img_numpy) plt.title(str(dataset.ids[image_idx])) plt.show() elif not args.no_bar: if it > 1: fps = 1 / frame_times.get_avg() else: fps = 0 progress = (it+1) / dataset_size * 100 progress_bar.set_val(it+1) print('\rProcessing Images %s %6d / %6d (%5.2f%%) %5.2f fps ' % (repr(progress_bar), it+1, dataset_size, progress, fps), end='') if not args.display and not args.benchmark: print() if args.output_coco_json: print('Dumping detections...') if args.output_web_json: detections.dump_web() else: detections.dump() else: if not train_mode: print('Saving data...') with open(args.ap_data_file, 'wb') as f: pickle.dump(ap_data, f) return calc_map(ap_data) elif args.benchmark: print() print() print('Stats for the last frame:') timer.print_stats() avg_seconds = frame_times.get_avg() print('Average: %5.2f fps, %5.2f ms' % (1 / frame_times.get_avg(), 1000*avg_seconds)) except KeyboardInterrupt: print('Stopping...') def calc_map(ap_data): print('Calculating mAP...') aps = [{'box': [], 'mask': []} for _ in iou_thresholds] for _class in range(len(cfg.dataset.class_names)): for iou_idx in range(len(iou_thresholds)): for iou_type in ('box', 'mask'): ap_obj = ap_data[iou_type][iou_idx][_class] if not ap_obj.is_empty(): aps[iou_idx][iou_type].append(ap_obj.get_ap()) all_maps = {'box': OrderedDict(), 'mask': OrderedDict()} # Looking back at it, this code is really hard to read :/ for iou_type in ('box', 'mask'): all_maps[iou_type]['all'] = 0 # Make this first in the ordereddict for i, threshold in enumerate(iou_thresholds): mAP = sum(aps[i][iou_type]) / len(aps[i][iou_type]) * 100 if len(aps[i][iou_type]) > 0 else 0 all_maps[iou_type][int(threshold*100)] = mAP all_maps[iou_type]['all'] = (sum(all_maps[iou_type].values()) / (len(all_maps[iou_type].values())-1)) print_maps(all_maps) # Put in a prettier format so we can serialize it to json during training all_maps = {k: {j: round(u, 2) for j, u in v.items()} for k, v in all_maps.items()} return all_maps def print_maps(all_maps): # Warning: hacky make_row = lambda vals: (' %5s |' * len(vals)) % tuple(vals) make_sep = lambda n: ('-------+' * n) print() print(make_row([''] + [('.%d ' % x if isinstance(x, int) else x + ' ') for x in all_maps['box'].keys()])) print(make_sep(len(all_maps['box']) + 1)) for iou_type in ('box', 'mask'): print(make_row([iou_type] + ['%.2f' % x if x < 100 else '%.1f' % x for x in all_maps[iou_type].values()])) print(make_sep(len(all_maps['box']) + 1)) print() if __name__ == '__main__': parse_args() if args.config is not None: set_cfg(args.config) if args.trained_model == 'interrupt': args.trained_model = SavePath.get_interrupt('weights/') elif args.trained_model == 'latest': args.trained_model = SavePath.get_latest('weights/', cfg.name) if args.config is None: model_path = SavePath.from_str(args.trained_model) # TODO: Bad practice? Probably want to do a name lookup instead. args.config = model_path.model_name + '_config' print('Config not specified. Parsed %s from the file name.\n' % args.config) set_cfg(args.config) if args.detect: cfg.eval_mask_branch = False if args.dataset is not None: set_dataset(args.dataset) with torch.no_grad(): if not os.path.exists('results'): os.makedirs('results') if args.cuda: cudnn.fastest = True torch.set_default_tensor_type('torch.cuda.FloatTensor') else: torch.set_default_tensor_type('torch.FloatTensor') if args.resume and not args.display: with open(args.ap_data_file, 'rb') as f: ap_data = pickle.load(f) calc_map(ap_data) exit() if args.image is None and args.video is None and args.images is None: dataset = COCODetection(cfg.dataset.valid_images, cfg.dataset.valid_info, transform=BaseTransform(), has_gt=cfg.dataset.has_gt) prep_coco_cats() else: dataset = None print('Loading model...', end='') net = Yolact() net.load_weights(args.trained_model) net.eval() print(' Done.') if args.cuda: net = net.cuda() evaluate(net, dataset) Traceback (most recent call last): File "eval.py", line 1105, in <module> evaluate(net, dataset) File "eval.py", line 884, in evaluate inp, out = args.images.split('E:/yolact-master/coco/images/train2017: E:/yolact-master/results/output') ValueError: not enough values to unpack (expected 2, got 1)
最新发布
06-18
Starting Redis Server 1:C 09 Jun 2025 12:44:54.319 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. 1:C 09 Jun 2025 12:44:54.320 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 1:C 09 Jun 2025 12:44:54.320 * Redis version=8.0.2, bits=64, commit=00000000, modified=1, pid=1, just started 1:C 09 Jun 2025 12:44:54.320 * Configuration loaded 1:M 09 Jun 2025 12:44:54.320 * monotonic clock: POSIX clock_gettime _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis Open Source .-`` .-```. ```\/ _.,_ ''-._ 8.0.2 (00000000/1) 64 bit ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 | `-._ `._ / _.-' | PID: 1 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | https://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' 1:M 09 Jun 2025 12:44:54.321 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. 1:M 09 Jun 2025 12:44:54.321 * <bf> RedisBloom version 8.0.1 (Git=unknown) 1:M 09 Jun 2025 12:44:54.321 * <bf> Registering configuration options: [ 1:M 09 Jun 2025 12:44:54.321 * <bf> { bf-error-rate : 0.01 } 1:M 09 Jun 2025 12:44:54.321 * <bf> { bf-initial-size : 100 } 1:M 09 Jun 2025 12:44:54.321 * <bf> { bf-expansion-factor : 2 } 1:M 09 Jun 2025 12:44:54.321 * <bf> { cf-bucket-size : 2 } 1:M 09 Jun 2025 12:44:54.321 * <bf> { cf-initial-size : 1024 } 1:M 09 Jun 2025 12:44:54.321 * <bf> { cf-max-iterations : 20 } 1:M 09 Jun 2025 12:44:54.321 * <bf> { cf-expansion-factor : 1 } 1:M 09 Jun 2025 12:44:54.321 * <bf> { cf-max-expansions : 32 } 1:M 09 Jun 2025 12:44:54.321 * <bf> ] 1:M 09 Jun 2025 12:44:54.321 * Module 'bf' loaded from /usr/local/lib/redis/modules//redisbloom.so 1:M 09 Jun 2025 12:44:54.322 * <search> Redis version found by RedisSearch : 8.0.2 - oss 1:M 09 Jun 2025 12:44:54.322 * <search> RediSearch version 8.0.1 (Git=5688fcc) 1:M 09 Jun 2025 12:44:54.322 * <search> Low level api version 1 initialized successfully 1:M 09 Jun 2025 12:44:54.322 * <search> gc: ON, prefix min length: 2, min word length to stem: 4, prefix max expansions: 200, query timeout (ms): 500, timeout policy: return, cursor read size: 1000, cursor max idle (ms): 300000, max doctable size: 1000000, max number of search results: 1000000, 1:M 09 Jun 2025 12:44:54.322 * <search> Initialized thread pools! 1:M 09 Jun 2025 12:44:54.322 * <search> Disabled workers threadpool of size 0 1:M 09 Jun 2025 12:44:54.322 * <search> Subscribe to config changes 1:M 09 Jun 2025 12:44:54.322 * <search> Enabled role change notification 1:M 09 Jun 2025 12:44:54.322 * <search> Cluster configuration: AUTO partitions, type: 0, coordinator timeout: 0ms 1:M 09 Jun 2025 12:44:54.322 * <search> Register write commands 1:M 09 Jun 2025 12:44:54.322 * Module 'search' loaded from /usr/local/lib/redis/modules//redisearch.so 1:M 09 Jun 2025 12:44:54.323 * <timeseries> RedisTimeSeries version 80001, git_sha=577bfa8b5909e7ee572f0b651399be8303dc6641 1:M 09 Jun 2025 12:44:54.323 * <timeseries> Redis version found by RedisTimeSeries : 8.0.2 - oss 1:M 09 Jun 2025 12:44:54.323 * <timeseries> Registering configuration options: [ 1:M 09 Jun 2025 12:44:54.323 * <timeseries> { ts-compaction-policy : } 1:M 09 Jun 2025 12:44:54.323 * <timeseries> { ts-num-threads : 3 } 1:M 09 Jun 2025 12:44:54.323 * <timeseries> { ts-retention-policy : 0 } 1:M 09 Jun 2025 12:44:54.323 * <timeseries> { ts-duplicate-policy : block } 1:M 09 Jun 2025 12:44:54.323 * <timeseries> { ts-chunk-size-bytes : 4096 } 1:M 09 Jun 2025 12:44:54.323 * <timeseries> { ts-encoding : compressed } 1:M 09 Jun 2025 12:44:54.323 * <timeseries> { ts-ignore-max-time-diff: 0 } 1:M 09 Jun 2025 12:44:54.323 * <timeseries> { ts-ignore-max-val-diff : 0.000000 } 1:M 09 Jun 2025 12:44:54.323 * <timeseries> ] 1:M 09 Jun 2025 12:44:54.323 * <timeseries> Detected redis oss 1:M 09 Jun 2025 12:44:54.323 * Module 'timeseries' loaded from /usr/local/lib/redis/modules//redistimeseries.so 1:M 09 Jun 2025 12:44:54.323 * <ReJSON> Created new data type 'ReJSON-RL' 1:M 09 Jun 2025 12:44:54.323 * <ReJSON> version: 80001 git sha: unknown branch: unknown 1:M 09 Jun 2025 12:44:54.323 * <ReJSON> Exported RedisJSON_V1 API 1:M 09 Jun 2025 12:44:54.323 * <ReJSON> Exported RedisJSON_V2 API 1:M 09 Jun 2025 12:44:54.323 * <ReJSON> Exported RedisJSON_V3 API 1:M 09 Jun 2025 12:44:54.323 * <ReJSON> Exported RedisJSON_V4 API 1:M 09 Jun 2025 12:44:54.323 * <ReJSON> Exported RedisJSON_V5 API 1:M 09 Jun 2025 12:44:54.323 * <ReJSON> Enabled diskless replication 1:M 09 Jun 2025 12:44:54.323 * <ReJSON> Initialized shared string cache, thread safe: false. 1:M 09 Jun 2025 12:44:54.323 * Module 'ReJSON' loaded from /usr/local/lib/redis/modules//rejson.so 1:M 09 Jun 2025 12:44:54.323 * <search> Acquired RedisJSON_V5 API 1:M 09 Jun 2025 12:44:54.323 * Server initialized 1:M 09 Jun 2025 12:44:54.323 * <search> Loading event starts 1:M 09 Jun 2025 12:44:54.323 * <search> Enabled workers threadpool of size 4 1:M 09 Jun 2025 12:44:54.323 * Loading RDB produced by version 7.4.2 1:M 09 Jun 2025 12:44:54.323 * RDB age 11115 seconds 1:M 09 Jun 2025 12:44:54.323 * RDB memory usage when created 3.81 Mb 1:M 09 Jun 2025 12:44:54.327 # FATAL: Data file was created with a Redis server configured to handle more than 16 databases. Exiting
06-10
<think>我们面对的问题:Redis启动时出现致命错误,提示数据文件创建时配置的数据库数量超过16个错误信息:"Redisserverfatalerroronstartupduetodatafilecreatedwithmorethan16databasesconfigured"背景知识:1.Redis默认支持16个数据库(索引0-15),可以通过配置文件中的`databases`参数修改,但最大不超过10000(实际取决于系统,但一般建议不超过几百)[^1]。2.如果之前运行Redis实例时配置的数据库数量(比如32个)并生成了数据文件(如dump.rdb),而现在启动的Redis实例配置的数据库数量小于之前(比如默认16个),就会导致此错误。解决思路:方案一(推荐):调整Redis配置,使当前配置的数据库数量大于或等于之前创建数据文件时配置的数量。方案:如果不需要旧数据,可以删除现有的数据文件,然后重新启动Redis。方案三:使用Redis自带的工具redis-check-rdb检查数据文件,并尝试修复(但此方法不一定能解决数据库数量不一致的问题)。详细步骤:###方案一:修改Redis配置文件1.找到Redis的配置文件(通常是`redis.conf`)2.搜索`databases`配置项,默认可能是:```databases16```3.将其修改为一个更大的值(比如32,或者根据之前配置的值),例如:```databases32```4.保存配置文件并重启Redis。###方案:删除数据文件(适用于可以丢弃旧数据的场景)1.确定Redis数据文件的位置:在配置文件中查找`dir`和`dbfilename`配置项。默认情况下,数据文件为`dump.rdb`,位于Redis的工作目录(如./)。2.停止Redis服务。3.删除数据文件(如dump.rdb)。4.启动Redis,将会创建一个新的空数据文件。###方案三:使用redis-check-rdb检查数据文件1.在终端运行:```bashredis-check-rdb/path/to/dump.rdb```注意:将路径替换为实际的数据文件路径。2.如果工具报告任何错误,可以尝试修复(但此工具主要用于修复RDB文件的损坏,对于数据库数量不一致的问题可能无法修复)。重要提醒:如果之前的数据很重要,建议先备份数据文件再进行操作。问题分析:出现该错误的原因是Redis在启动时加载RDB文件,发现文件头中记录的数据库数量大于当前配置的数据库数量。为了避免数据丢失或损坏,Redis拒绝启动。引用[1]中提到Redis作为key-value存储,支持多个数据库。每个数据库相互隔离,但都在同一个Redis实例中。相关问题:§§相关问题§§1.如何配置Redis的持久化方式?2.如何安全地备份和恢复Redis数据?3.Redis集群模式下是否支持多个数据库?</think> ### Redis启动失败:数据文件与数据库配置冲突解决方案 您遇到的错误是由于Redis数据文件(`dump.rdb`)创建时的数据库数量配置超过当前Redis实例的默认值(16个)导致的。以下是系统性解决方案: --- #### **问题诊断** Redis通过配置文件中的`databases`参数控制最大数据库数量: - 默认配置:16个数据库(索引0-15)[^1] - 错误触发:当数据文件包含超过当前配置数量的数据库时(如旧配置设为32--- #### **解决方案** **方法一:修改Redis配置(推荐永久方案)** 1. 编辑Redis配置文件(通常为`redis.conf`): ```bash sudo nano /etc/redis/redis.conf ``` 2. 找到并修改数据库配置: ```conf databases 32 # 设为原创建文件时的数据库数量 ``` 3. 重启Redis服务: ```bash sudo systemctl restart redis-server ``` **方法:临时启动解决(需后续清理数据)** ```bash # 强制启动(忽略RDB文件错误) redis-server --ignore-datafile-errors # 启动后删除问题数据 127.0.0.1:6379> FLUSHALL ``` **方法三:升级RDB文件** 1. 使用Redis自带检测工具: ```bash redis-check-rdb /var/lib/redis/dump.rdb ``` 2. 若检测失败,备份后重建数据文件: ```bash cp /var/lib/redis/dump.rdb ~/redis_backup.rdb rm /var/lib/redis/dump.rdb sudo systemctl restart redis-server ``` --- #### **预防措施** 1. **配置标准化**: ```conf # 生产环境统一配置 databases 16 # 确保所有节点一致 maxmemory 2gb # 避免内存溢出 ``` 2. **使用Cluster替代多数据库**: ```bash # 创建集群(6节点,每个节点1主1从) redis-cli --cluster create 127.0.0.1:7000 ... --cluster-replicas 1 ``` --- #### **工作原理** - Redis数据库实质是逻辑分区(非物理隔离) - RDB文件头记录数据库数量配置值(`db_version`字段) - 启动时进行校验:`if (db_num > server.dbnum) exit(1)` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值