forked from Public-Mirror/Textractor
633 lines
16 KiB
C++
633 lines
16 KiB
C++
// memsearch.cc
|
|
// 4/20/2014 jichi
|
|
#include "memdbg/memsearch.h"
|
|
#include <windows.h>
|
|
|
|
// Helpers
|
|
|
|
namespace { // unnamed
|
|
|
|
enum : BYTE { byte_nop = 0x90 };
|
|
enum : BYTE { byte_int3 = 0xcc };
|
|
enum : WORD { word_2int3 = 0xcccc };
|
|
|
|
// jichi 4/19/2014: Return the integer that can mask the signature
|
|
DWORD sigMask(DWORD sig)
|
|
{
|
|
__asm
|
|
{
|
|
xor ecx,ecx
|
|
mov eax,sig
|
|
_mask:
|
|
shr eax,8
|
|
inc ecx
|
|
test eax,eax
|
|
jnz _mask
|
|
sub ecx,4
|
|
neg ecx
|
|
or eax,-1
|
|
shl ecx,3
|
|
shr eax,cl
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the address of the first matched pattern.
|
|
* The same as ITH SearchPattern(). KMP is used.
|
|
* Return 0 if failed. The return result is ambiguous if the pattern address is 0.
|
|
*
|
|
* @param startAddress search start address
|
|
* @param range search range
|
|
* @param pattern array of bytes to match
|
|
* @param patternSize size of the pattern array
|
|
* @return relative offset from the startAddress
|
|
*/
|
|
DWORD searchPattern(DWORD base, DWORD base_length, LPCVOID search, DWORD search_length) // KMP
|
|
{
|
|
__asm
|
|
{
|
|
mov eax,search_length
|
|
alloc:
|
|
push 0
|
|
sub eax,1
|
|
jnz alloc
|
|
|
|
mov edi,search
|
|
mov edx,search_length
|
|
mov ecx,1
|
|
xor esi,esi
|
|
build_table:
|
|
mov al,byte ptr [edi+esi]
|
|
cmp al,byte ptr [edi+ecx]
|
|
sete al
|
|
test esi,esi
|
|
jz pre
|
|
test al,al
|
|
jnz pre
|
|
mov esi,[esp+esi*4-4]
|
|
jmp build_table
|
|
pre:
|
|
test al,al
|
|
jz write_table
|
|
inc esi
|
|
write_table:
|
|
mov [esp+ecx*4],esi
|
|
|
|
inc ecx
|
|
cmp ecx,edx
|
|
jb build_table
|
|
|
|
mov esi,base
|
|
xor edx,edx
|
|
mov ecx,edx
|
|
matcher:
|
|
mov al,byte ptr [edi+ecx]
|
|
cmp al,byte ptr [esi+edx]
|
|
sete al
|
|
test ecx,ecx
|
|
jz match
|
|
test al,al
|
|
jnz match
|
|
mov ecx, [esp+ecx*4-4]
|
|
jmp matcher
|
|
match:
|
|
test al,al
|
|
jz pre2
|
|
inc ecx
|
|
cmp ecx,search_length
|
|
je finish
|
|
pre2:
|
|
inc edx
|
|
cmp edx,base_length // search_length
|
|
jb matcher
|
|
mov edx,search_length
|
|
dec edx
|
|
finish:
|
|
mov ecx,search_length
|
|
sub edx,ecx
|
|
lea eax,[edx+1]
|
|
lea ecx,[ecx*4]
|
|
add esp,ecx
|
|
}
|
|
}
|
|
|
|
/**
|
|
* jichi 2/5/2014: The same as SearchPattern except it uses 0xff to match everything
|
|
* According to @Andys, 0xff seldom appears in the source code: http://sakuradite.com/topic/124
|
|
*/
|
|
DWORD searchPatternEx(DWORD base, DWORD base_length, LPCVOID search, DWORD search_length, BYTE wildcard) // KMP
|
|
{
|
|
__asm
|
|
{
|
|
// jichi 2/5/2014 BEGIN
|
|
mov bl,wildcard
|
|
// jichi 2/5/2014 END
|
|
mov eax,search_length
|
|
alloc:
|
|
push 0
|
|
sub eax,1
|
|
jnz alloc // jichi 2/5/2014: this will also set %eax to zero
|
|
|
|
mov edi,search
|
|
mov edx,search_length
|
|
mov ecx,1
|
|
xor esi,esi
|
|
build_table:
|
|
mov al,byte ptr [edi+esi]
|
|
cmp al,byte ptr [edi+ecx]
|
|
sete al
|
|
test esi,esi
|
|
jz pre
|
|
test al,al
|
|
jnz pre
|
|
mov esi,[esp+esi*4-4]
|
|
jmp build_table
|
|
pre:
|
|
test al,al
|
|
jz write_table
|
|
inc esi
|
|
write_table:
|
|
mov [esp+ecx*4],esi
|
|
|
|
inc ecx
|
|
cmp ecx,edx
|
|
jb build_table
|
|
|
|
mov esi,base
|
|
xor edx,edx
|
|
mov ecx,edx
|
|
matcher:
|
|
mov al,byte ptr [edi+ecx] // search
|
|
// jichi 2/5/2014 BEGIN
|
|
mov bh,al // save loaded byte to reduce cache access. %ah is not used and always zero
|
|
cmp al,bl // %bl is the wildcard byte
|
|
sete al
|
|
test al,al
|
|
jnz wildcard_matched
|
|
mov al,bh // restore the loaded byte
|
|
// jichi 2/5/2014 END
|
|
cmp al,byte ptr [esi+edx] // base
|
|
sete al
|
|
// jichi 2/5/2014 BEGIN
|
|
wildcard_matched:
|
|
// jichi 2/5/2014 END
|
|
test ecx,ecx
|
|
jz match
|
|
test al,al
|
|
jnz match
|
|
mov ecx, [esp+ecx*4-4]
|
|
jmp matcher
|
|
match:
|
|
test al,al
|
|
jz pre2
|
|
inc ecx
|
|
cmp ecx,search_length
|
|
je finish
|
|
pre2:
|
|
inc edx
|
|
cmp edx,base_length // search_length
|
|
jb matcher
|
|
mov edx,search_length
|
|
dec edx
|
|
finish:
|
|
mov ecx,search_length
|
|
sub edx,ecx
|
|
lea eax,[edx+1]
|
|
lea ecx,[ecx*4]
|
|
add esp,ecx
|
|
}
|
|
}
|
|
|
|
// Modified from ITH findCallOrJmpAbs
|
|
// Example call:
|
|
// 00449063 |. ff15 5cf05300 call dword ptr ds:[<&gdi32.getglyphoutli>; \GetGlyphOutlineA
|
|
enum : WORD {
|
|
word_jmp = 0x25ff
|
|
, word_call = 0x15ff // far call
|
|
};
|
|
/***
|
|
* Return the absolute address of op. Op takes 1 parameter.
|
|
*
|
|
* @param op first half of the operator
|
|
* @param arg1 the function address
|
|
* @param start address
|
|
* @param search range
|
|
* @return absolute address or 0
|
|
*/
|
|
DWORD findWordCall(WORD op, DWORD arg1, DWORD start, DWORD size)
|
|
{
|
|
typedef WORD optype;
|
|
typedef DWORD argtype;
|
|
|
|
enum { START = 0x1000 }; // leading size to skip
|
|
for (DWORD i = START; i < size - sizeof(argtype); i++)
|
|
if (op == *(optype *)(start + i)) {
|
|
DWORD t = *(DWORD *)(start + i + sizeof(optype));
|
|
if (t > start && t < start + size) {
|
|
if (arg1 == *(argtype *)t)
|
|
return start + i;
|
|
else
|
|
i += sizeof(optype) + sizeof(argtype) - 1; // == 5
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Modified from ITH findCallOrJmpAbs
|
|
enum : BYTE {
|
|
byte_call = 0xe8 // near call
|
|
, byte_push_small = 0x6a // push byte operand
|
|
, byte_push_large = 0x68 // push operand > 0xff
|
|
};
|
|
|
|
/***
|
|
* Return the absolute address of op. Op takes 1 address parameter.
|
|
*
|
|
* @param op first half of the operator
|
|
* @param arg1 the function address
|
|
* @param start address
|
|
* @param search range
|
|
* @return absolute address or 0
|
|
*/
|
|
DWORD findByteCall(BYTE op, DWORD arg1, DWORD start, DWORD size)
|
|
{
|
|
typedef BYTE optype;
|
|
typedef DWORD argtype;
|
|
|
|
enum { START = 0x1000 }; // leading size to skip
|
|
for (DWORD i = START; i < size - sizeof(argtype); i++)
|
|
if (op == *(optype *)(start + i)) {
|
|
DWORD t = *(DWORD *)(start + i + sizeof(optype));
|
|
if (t > start && t < start + size) {
|
|
if (arg1 == *(argtype *)t)
|
|
return start + i;
|
|
else
|
|
i += sizeof(optype) + sizeof(argtype) - 1; // == 4
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* Return the absolute address of op. Op takes 1 parameter.
|
|
*
|
|
* @param op first half of the operator
|
|
* @param arg1 the first operand
|
|
* @param start address
|
|
* @param search range
|
|
* @return absolute address or 0
|
|
*/
|
|
//DWORD findByteOp1(BYTE op, DWORD arg1, DWORD start, DWORD size)
|
|
//{
|
|
// typedef BYTE optype;
|
|
// typedef DWORD argtype;
|
|
//
|
|
// enum { START = 0x1000 }; // leading size to skip
|
|
// for (DWORD i = START; i < size - sizeof(argtype); i++)
|
|
// if (op == *(optype *)(start + i)) {
|
|
// DWORD t = *(DWORD *)(start + i + sizeof(optype));
|
|
// if (t == arg1) {
|
|
// return start + i;
|
|
// else
|
|
// i += sizeof(optype) + sizeof(argtype) - 1; // == 4
|
|
// }
|
|
// }
|
|
// return 0;
|
|
//}
|
|
|
|
} // namespace unnamed
|
|
|
|
MEMDBG_BEGIN_NAMESPACE
|
|
|
|
DWORD findJumpAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound)
|
|
{ return findWordCall(word_jmp, funcAddr, lowerBound, upperBound - lowerBound); }
|
|
|
|
DWORD findFarCallAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound)
|
|
{ return findWordCall(word_call, funcAddr, lowerBound, upperBound - lowerBound); }
|
|
|
|
DWORD findNearCallAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound)
|
|
{ return findByteCall(byte_call, funcAddr, lowerBound, upperBound - lowerBound); }
|
|
|
|
DWORD findPushDwordAddress(DWORD value, DWORD lowerBound, DWORD upperBound)
|
|
{
|
|
//value = _byteswap_ulong(value); // swap to bigendian
|
|
const BYTE *p = (BYTE *)&value;
|
|
const BYTE bytes[] = {byte_push_large, p[0], p[1], p[2], p[3]};
|
|
return findBytes(bytes, sizeof(bytes), lowerBound, upperBound);
|
|
}
|
|
|
|
DWORD findPushByteAddress(BYTE value, DWORD lowerBound, DWORD upperBound)
|
|
{
|
|
const BYTE bytes[] = {byte_push_small, value};
|
|
return findBytes(bytes, sizeof(bytes), lowerBound, upperBound);
|
|
}
|
|
|
|
DWORD findCallerAddress(DWORD funcAddr, DWORD sig, DWORD lowerBound, DWORD upperBound, DWORD reverseLength)
|
|
{
|
|
enum { Start = 0x1000 };
|
|
enum { PatternSize = 4 };
|
|
const DWORD size = upperBound - lowerBound - PatternSize;
|
|
const DWORD fun = (DWORD)funcAddr;
|
|
// Example function call:
|
|
// 00449063 |. ff15 5cf05300 call dword ptr ds:[<&gdi32.getglyphoutli>; \GetGlyphOutlineA
|
|
//WCHAR str[0x40];
|
|
const DWORD mask = sigMask(sig);
|
|
for (DWORD i = Start; i < size; i++)
|
|
if (*(WORD *)(lowerBound + i) == word_call) {
|
|
DWORD t = *(DWORD *)(lowerBound + i + 2);
|
|
if (t >= lowerBound && t <= upperBound - PatternSize) {
|
|
if (*(DWORD *)t == fun)
|
|
//swprintf(str,L"CALL addr: 0x%.8X",lowerBound + i);
|
|
//OutputConsole(str);
|
|
for (DWORD j = i ; j > i - reverseLength; j--)
|
|
if ((*(DWORD *)(lowerBound + j) & mask) == sig) // Fun entry 1.
|
|
//swprintf(str,L"Entry: 0x%.8X",lowerBound + j);
|
|
//OutputConsole(str);
|
|
return lowerBound + j;
|
|
|
|
} else
|
|
i += 6;
|
|
}
|
|
//OutputConsole(L"Find call and entry failed.");
|
|
return 0;
|
|
}
|
|
|
|
DWORD findMultiCallerAddress(DWORD funcAddr, const DWORD sigs[], DWORD sigCount, DWORD lowerBound, DWORD upperBound, DWORD reverseLength)
|
|
{
|
|
enum { Start = 0x1000 };
|
|
enum { PatternSize = 4 };
|
|
const DWORD size = upperBound - lowerBound - PatternSize;
|
|
const DWORD fun = (DWORD)funcAddr;
|
|
// Example function call:
|
|
// 00449063 |. ff15 5cf05300 call dword ptr ds:[<&gdi32.getglyphoutli>; \GetGlyphOutlineA
|
|
//WCHAR str[0x40];
|
|
|
|
enum { MaxSigCount = 0x10 }; // mast be larger than maximum sigCount
|
|
DWORD masks[MaxSigCount];
|
|
for (DWORD k = 0; k < sigCount; k++)
|
|
masks[k] = sigMask(sigs[k]);
|
|
|
|
for (DWORD i = Start; i < size; i++)
|
|
if (*(WORD *)(lowerBound + i) == word_call) {
|
|
DWORD t = *(DWORD *)(lowerBound + i + 2);
|
|
if (t >= lowerBound && t <= upperBound - PatternSize) {
|
|
if (*(DWORD *)t == fun)
|
|
//swprintf(str,L"CALL addr: 0x%.8X",lowerBound + i);
|
|
//OutputConsole(str);
|
|
for (DWORD j = i ; j > i - reverseLength; j--) {
|
|
DWORD ret = lowerBound + j,
|
|
inst = *(DWORD *)ret;
|
|
for (DWORD k = 0; k < sigCount; k++)
|
|
if ((inst & masks[k]) == sigs[k]) // Fun entry 1.
|
|
//swprintf(str,L"Entry: 0x%.8X",lowerBound + j);
|
|
//OutputConsole(str);
|
|
return ret;
|
|
}
|
|
|
|
} else
|
|
i += 6;
|
|
}
|
|
//OutputConsole(L"Find call and entry failed.");
|
|
return 0;
|
|
}
|
|
|
|
DWORD findLastCallerAddress(DWORD funcAddr, DWORD sig, DWORD lowerBound, DWORD upperBound, DWORD reverseLength)
|
|
{
|
|
enum { Start = 0x1000 };
|
|
enum { PatternSize = 4 };
|
|
const DWORD size = upperBound - lowerBound - PatternSize;
|
|
const DWORD fun = (DWORD)funcAddr;
|
|
//WCHAR str[0x40];
|
|
DWORD ret = 0;
|
|
const DWORD mask = sigMask(sig);
|
|
for (DWORD i = Start; i < size; i++)
|
|
if (*(WORD *)(lowerBound + i) == word_call) {
|
|
DWORD t = *(DWORD *)(lowerBound + i + 2);
|
|
if (t >= lowerBound && t <= upperBound - PatternSize) {
|
|
if (*(DWORD *)t == fun)
|
|
//swprintf(str,L"CALL addr: 0x%.8X",lowerBound + i);
|
|
//OutputConsole(str);
|
|
for (DWORD j = i ; j > i - reverseLength; j--)
|
|
if ((*(DWORD *)(lowerBound + j) & mask) == sig) // Fun entry 1.
|
|
//swprintf(str,L"Entry: 0x%.8X",lowerBound + j);
|
|
//OutputConsole(str);
|
|
ret = lowerBound + j;
|
|
|
|
} else
|
|
i += 6;
|
|
}
|
|
//OutputConsole(L"Find call and entry failed.");
|
|
return ret;
|
|
}
|
|
|
|
DWORD findCallerAddressAfterInt3(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize)
|
|
{
|
|
DWORD addr = findCallerAddress(funcAddr, word_2int3, lowerBound, upperBound, callerSearchSize);
|
|
if (addr)
|
|
while (byte_int3 == *(BYTE *)++addr);
|
|
return addr;
|
|
}
|
|
|
|
DWORD findLastCallerAddressAfterInt3(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize)
|
|
{
|
|
DWORD addr = findLastCallerAddress(funcAddr, word_2int3, lowerBound, upperBound, callerSearchSize);
|
|
if (addr)
|
|
while (byte_int3 == *(BYTE *)++addr);
|
|
return addr;
|
|
}
|
|
|
|
DWORD findEnclosingAlignedFunction(DWORD start, DWORD back_range)
|
|
{
|
|
start &= ~0xf;
|
|
for (DWORD i = start, j = start - back_range; i > j; i-=0x10) {
|
|
DWORD k = *(DWORD *)(i-4);
|
|
if (k == 0xcccccccc
|
|
|| k == 0x90909090
|
|
|| k == 0xccccccc3
|
|
|| k == 0x909090c3
|
|
)
|
|
return i;
|
|
DWORD t = k & 0xff0000ff;
|
|
if (t == 0xcc0000c2 || t == 0x900000c2)
|
|
return i;
|
|
k >>= 8;
|
|
if (k == 0xccccc3 || k == 0x9090c3)
|
|
return i;
|
|
t = k & 0xff;
|
|
if (t == 0xc2)
|
|
return i;
|
|
k >>= 8;
|
|
if (k == 0xccc3 || k == 0x90c3)
|
|
return i;
|
|
k >>= 8;
|
|
if (k == 0xc3)
|
|
return i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
DWORD findBytes(const void *pattern, DWORD patternSize, DWORD lowerBound, DWORD upperBound)
|
|
{
|
|
DWORD reladdr = searchPattern(lowerBound, upperBound - lowerBound, pattern, patternSize);
|
|
return reladdr ? lowerBound + reladdr : 0;
|
|
}
|
|
|
|
DWORD matchBytes(const void *pattern, DWORD patternSize, DWORD lowerBound, DWORD upperBound, BYTE wildcard)
|
|
{
|
|
DWORD reladdr = searchPatternEx(lowerBound, upperBound - lowerBound, pattern, patternSize, wildcard);
|
|
return reladdr ? lowerBound + reladdr : 0;
|
|
}
|
|
|
|
#if 0 // not used
|
|
DWORD findBytesInPages(const void *pattern, DWORD patternSize, DWORD lowerBound, DWORD upperBound, SearchType search)
|
|
{
|
|
//enum { MinPageSize = 4 * 1024 }; // 4k
|
|
DWORD ret = 0;
|
|
DWORD start = lowerBound,
|
|
stop = start;
|
|
MEMORY_BASIC_INFORMATION mbi = {};
|
|
|
|
//lowerBound = 0x10000000;
|
|
//upperBound = 0x14000000;
|
|
//SIZE_T ok = ::VirtualQuery((LPCVOID)lowerBound, &mbi, sizeof(mbi));
|
|
//ITH_GROWL_DWORD7(1, start, stop, mbi.RegionSize, mbi.Protect, mbi.Type, mbi.State);
|
|
//return matchBytes(pattern, patternSize, lowerBound, upperBound, wildcard);
|
|
while (stop < upperBound) {
|
|
SIZE_T ok = ::VirtualQuery((LPCVOID)start, &mbi, sizeof(mbi));
|
|
if (!mbi.RegionSize)
|
|
break;
|
|
// Only visit readable and committed region
|
|
// Protect could be zero if not allowed to query
|
|
if (!ok || !mbi.Protect || mbi.Protect&PAGE_NOACCESS) {
|
|
if (stop > start && (ret = findBytes(pattern, patternSize, lowerBound, upperBound)))
|
|
return ret;
|
|
if (search != SearchAll)
|
|
return 0;
|
|
stop += mbi.RegionSize;
|
|
start = stop;
|
|
} else
|
|
stop += mbi.RegionSize;
|
|
}
|
|
if (stop > start)
|
|
ret = findBytes(pattern, patternSize, start, min(upperBound, stop));
|
|
return ret;
|
|
}
|
|
|
|
DWORD matchBytesInPages(const void *pattern, DWORD patternSize, DWORD lowerBound, DWORD upperBound, BYTE wildcard, SearchType search)
|
|
{
|
|
//enum { MinPageSize = 4 * 1024 }; // 4k
|
|
DWORD ret = 0;
|
|
DWORD start = lowerBound,
|
|
stop = start;
|
|
MEMORY_BASIC_INFORMATION mbi = {};
|
|
|
|
//lowerBound = 0x10000000;
|
|
//upperBound = 0x14000000;
|
|
//SIZE_T ok = ::VirtualQuery((LPCVOID)lowerBound, &mbi, sizeof(mbi));
|
|
//ITH_GROWL_DWORD7(1, start, stop, mbi.RegionSize, mbi.Protect, mbi.Type, mbi.State);
|
|
//return matchBytes(pattern, patternSize, lowerBound, upperBound, wildcard);
|
|
while (stop < upperBound) {
|
|
SIZE_T ok = ::VirtualQuery((LPCVOID)start, &mbi, sizeof(mbi));
|
|
if (!mbi.RegionSize)
|
|
break;
|
|
// Only visit readable and committed region
|
|
// Protect could be zero if not allowed to query
|
|
if (!ok || !mbi.Protect || mbi.Protect&PAGE_NOACCESS) {
|
|
if (stop > start && (ret = matchBytes(pattern, patternSize, lowerBound, upperBound, wildcard)))
|
|
return ret;
|
|
if (search != SearchAll)
|
|
return 0;
|
|
stop += mbi.RegionSize;
|
|
start = stop;
|
|
} else
|
|
stop += mbi.RegionSize;
|
|
}
|
|
if (stop > start)
|
|
ret = matchBytes(pattern, patternSize, start, min(upperBound, stop), wildcard);
|
|
return ret;
|
|
}
|
|
|
|
#endif // 0
|
|
|
|
MEMDBG_END_NAMESPACE
|
|
|
|
// EOF
|
|
|
|
#if 0 // disabled
|
|
|
|
/**
|
|
* Search from stopAddres back to startAddress - range
|
|
* This function is not well debugged
|
|
*/
|
|
DWORD reverseSearchPattern(DWORD base, DWORD base_length, LPCVOID search, DWORD search_length) // KMP
|
|
{
|
|
__asm
|
|
{
|
|
mov eax,search_length
|
|
alloc:
|
|
push 0
|
|
sub eax,1
|
|
jnz alloc
|
|
|
|
mov edi,search
|
|
mov edx,search_length
|
|
mov ecx,1
|
|
xor esi,esi
|
|
build_table:
|
|
mov al,byte ptr [edi+esi]
|
|
cmp al,byte ptr [edi+ecx]
|
|
sete al
|
|
test esi,esi
|
|
jz pre
|
|
test al,al
|
|
jnz pre
|
|
mov esi,[esp+esi*4-4]
|
|
jmp build_table
|
|
pre:
|
|
test al,al
|
|
jz write_table
|
|
inc esi
|
|
write_table:
|
|
mov [esp+ecx*4],esi
|
|
|
|
inc ecx
|
|
cmp ecx,edx
|
|
jb build_table
|
|
|
|
mov esi,base
|
|
xor edx,edx
|
|
mov ecx,edx
|
|
matcher:
|
|
mov al,byte ptr [edi+ecx]
|
|
cmp al,byte ptr [esi-edx] // jichi 6/1/2014: The only place that is modified
|
|
sete al
|
|
test ecx,ecx
|
|
jz match
|
|
test al,al
|
|
jnz match
|
|
mov ecx, [esp+ecx*4-4]
|
|
jmp matcher
|
|
match:
|
|
test al,al
|
|
jz pre2
|
|
inc ecx
|
|
cmp ecx,search_length
|
|
je finish
|
|
pre2:
|
|
inc edx
|
|
cmp edx,base_length // search_length
|
|
jb matcher
|
|
mov edx,search_length
|
|
dec edx
|
|
finish:
|
|
mov ecx,search_length
|
|
sub edx,ecx
|
|
lea eax,[edx+1]
|
|
lea ecx,[ecx*4]
|
|
add esp,ecx
|
|
}
|
|
}
|
|
|
|
#endif // 0, disabled
|
|
|