GP-141 handle non-standard RTTI type_info

This commit is contained in:
ghidra007 2020-11-04 12:58:49 -05:00 committed by ghidra1
parent 4300bec382
commit 0f6d3cbc35
9 changed files with 332 additions and 122 deletions

View file

@ -30,6 +30,7 @@ import ghidra.util.datastruct.ListAccumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
import utility.function.TerminatingConsumer;
/**
* <CODE>ProgramMemoryUtil</CODE> contains some static methods for
@ -741,8 +742,35 @@ public class ProgramMemoryUtil {
List<MemoryBlock> blocks, AddressSetView set, TaskMonitor monitor)
throws CancelledException {
monitor.setMessage("Finding \"" + searchString + "\".");
List<Address> addresses = new ArrayList<>();
// just add each found location to the list, no termination of search
TerminatingConsumer<Address> collector = (i) -> addresses.add(i);
locateString(searchString, collector, program, blocks, set, monitor);
return addresses;
}
/**
* Finds the string in memory indicated by the searchString limited to the indicated
* memory blocks and address set. Each found location calls the foundLocationConsumer.consume(addr)
* method. If the search should terminate, (ie. enough results found), then terminateRequested() should
* return true. Requesting termination is different than a cancellation from the task monitor.
*
* @param searchString the string to find
* @param foundLocationConsumer location consumer with consumer.accept(Address addr) routine defined
* @param program the program to search
* @param blocks the only blocks to search
* @param set a set of the addresses to limit the results
* @param monitor a task monitor to allow
* @throws CancelledException if the user cancels
*/
public static void locateString(String searchString, TerminatingConsumer<Address> foundLocationConsumer, Program program,
List<MemoryBlock> blocks, AddressSetView set, TaskMonitor monitor)
throws CancelledException {
monitor.setMessage("Finding \"" + searchString + "\".");
int length = searchString.length();
byte[] bytes = searchString.getBytes();
Memory memory = program.getMemory();
@ -759,7 +787,10 @@ public class ProgramMemoryUtil {
break; // no more found in block.
}
if (set.contains(foundAddress)) {
addresses.add(foundAddress);
foundLocationConsumer.accept(foundAddress);
if (foundLocationConsumer.terminationRequested()) {
return; // termination of search requested
}
}
try {
startAddress = foundAddress.add(length);
@ -770,7 +801,6 @@ public class ProgramMemoryUtil {
}
while (startAddress.compareTo(endAddress) <= 0);
}
return addresses;
}
/**

View file

@ -25,10 +25,15 @@ import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.UndefinedValueException;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.*;
import ghidra.program.model.mem.DumbMemBufferImpl;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.util.AddressSetPropertyMap;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import mdemangler.*;
import mdemangler.datatype.MDDataType;
import mdemangler.datatype.complex.MDComplexType;
@ -248,16 +253,19 @@ public class TypeDescriptorModel extends AbstractCreateDataTypeModel {
* @return true if the data type has a vf table pointer. Otherwise, it has a hash value.
*/
private static boolean hasVFPointer(Program program) {
// Should be true when 64 bit or RTTI.
if (MSDataTypeUtils.is64Bit(program)) {
Address typeInfoVftableAddress = null;
try {
typeInfoVftableAddress = RttiUtil.findTypeInfoVftableAddress(program, TaskMonitor.DUMMY);
}
catch (CancelledException e) {
throw new AssertException(e);
}
if (typeInfoVftableAddress != null) {
return true;
}
Address address = RttiUtil.getTypeInfoTypeDescriptorAddress(program);
if (address == null) {
return false;
}
return RttiUtil.isTypeInfoTypeDescriptorAddress(program, address);
}
/**
* Gets the TypeDescriptor structure for this model's program.
@ -350,9 +358,11 @@ public class TypeDescriptorModel extends AbstractCreateDataTypeModel {
throw new UndefinedValueException(
"No vf table pointer is defined for this TypeDescriptor model.");
}
Address vfTableAddress;
// component 0 is either vf table pointer or hash value.
Address vfTableAddress =
EHDataTypeUtilities.getAddress(getDataType(), VF_TABLE_OR_HASH_ORDINAL, getMemBuffer());
vfTableAddress = EHDataTypeUtilities.getAddress(getDataType(), VF_TABLE_OR_HASH_ORDINAL, getMemBuffer());
return vfTableAddress.getOffset() != 0 ? vfTableAddress : null;
}

View file

@ -187,7 +187,7 @@ public class CreateVfTableBackgroundCmd extends AbstractCreateDataBackgroundCmd<
// Create functions that are referred to by the vf table.
if (applyOptions.shouldCreateFunction()) {
int elementCount = model.getCount();
int elementCount = model.getElementCount();
for (int tableElementIndex = 0; tableElementIndex < elementCount; tableElementIndex++) {
monitor.checkCanceled();
Address vfPointer = model.getVirtualFunctionPointer(tableElementIndex);

View file

@ -15,16 +15,14 @@
*/
package ghidra.app.cmd.data.rtti;
import static ghidra.app.util.datatype.microsoft.MSDataTypeUtils.getAbsoluteAddress;
import static ghidra.app.util.datatype.microsoft.MSDataTypeUtils.*;
import java.io.IOException;
import java.util.List;
import java.util.*;
import ghidra.app.cmd.data.TypeDescriptorModel;
import ghidra.app.util.NamespaceUtils;
import ghidra.app.util.PseudoDisassembler;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.MemoryByteProvider;
import ghidra.app.util.datatype.microsoft.MSDataTypeUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.GhidraClass;
@ -34,20 +32,23 @@ import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramMemoryUtil;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
import utility.function.TerminatingConsumer;
/**
* RttiUtil provides constants and static methods for processing RTTI information.
*/
public class RttiUtil {
private static final String TYPE_INFO_NAMESPACE = "type_info";
private static final int MIN_MATCHING_VFTABLE_PTRS = 5;
static final String CONST_PREFIX = "const ";
public static final String TYPE_INFO_STRING = ".?AVtype_info@@";
public static final String TYPE_INFO_LABEL = "class_type_info_RTTI_Type_Descriptor";
private static final String MANGLED_TYPE_INFO_SYMBOL = "??_R0?AVtype_info@@@8";
public static final String TYPE_INFO_STRING = ".?AVtype_info@@";
private static final String CLASS_PREFIX_CHARS = ".?A";
private static Map<Program, Address> vftableMap = new WeakHashMap<>();
private RttiUtil() {
// utility class; can't create
@ -151,6 +152,12 @@ public class RttiUtil {
: false) {
break; // Not pointing to text section.
}
// any references after the first one ends the table
if (tableSize > 0 && program.getReferenceManager().hasReferencesTo(currentVfPointerAddress)) {
break;
}
if (!pseudoDisassembler.isValidSubroutine(referencedAddress, true)) {
break; // Not pointing to possible function.
}
@ -178,63 +185,185 @@ public class RttiUtil {
}
/**
* Gets the address of the base type_info structure in the provided program.
* The descriptor will only be manually located if {@value TYPE_INFO_STRING} is present.
* @param program the program
* @return the address of the type_info structure or null if not found
* Identify common TypeInfo address through examination of discovered VtTables
*/
public static Address getTypeInfoTypeDescriptorAddress(Program program) {
SymbolTable table = program.getSymbolTable();
List<Symbol> symbols = table.getGlobalSymbols(TYPE_INFO_LABEL);
if (symbols.isEmpty()) {
symbols = table.getGlobalSymbols(MANGLED_TYPE_INFO_SYMBOL);
private static class CommonRTTIMatchCounter implements TerminatingConsumer<Address> {
int matchingAddrCount = 0;
int defaultPointerSize = 4;
boolean terminationRequest = false;
Address commonVftableAddress = null;
Program program;
public CommonRTTIMatchCounter(Program program) {
this.program = program;
defaultPointerSize = program.getDefaultPointerSize();
}
if (!symbols.isEmpty()) {
for (Symbol symbol : symbols) {
if (isTypeInfoTypeDescriptorAddress(program, symbol.getAddress())) {
return symbol.getAddress();
public Address getinfoVfTable() {
return commonVftableAddress;
}
@Override
public boolean terminationRequested() {
return terminationRequest;
}
@Override
public void accept(Address foundAddress) {
Address mangledClassNameAddress = foundAddress;
Address pointerToTypeInfoVftable =
mangledClassNameAddress.subtract(2 * defaultPointerSize);
Address possibleVftableAddress =
MSDataTypeUtils.getAbsoluteAddress(program, pointerToTypeInfoVftable);
if (possibleVftableAddress == null) {
return; // valid address not found
}
if (possibleVftableAddress.getOffset() == 0) {
return; // don't want zero_address to count
}
// if ever we find one that doesn't match, start count over
if (!possibleVftableAddress.equals(commonVftableAddress)) {
if (matchingAddrCount > 2) {
return; // already have more than one match, assume this one was outlier, ignore
}
matchingAddrCount = 0;
}
commonVftableAddress = possibleVftableAddress;
matchingAddrCount++;
if (matchingAddrCount > MIN_MATCHING_VFTABLE_PTRS) {
// done finding good addresses have at Minimum matching number
terminationRequest = true;
return;
}
return;
}
return locateTypeInfoAddress(program);
}
/**
* Checks if the provided address is a TypeDescriptor containing the
* {@value TYPE_INFO_STRING} component
* @param program the program
* @param address the descriptor address
* @return true if {@value TYPE_INFO_STRING} is present in the descriptor at the address
* Method to figure out the type_info vftable address using pointed to value by all RTTI classes
* @param program the current program
* @param monitor the TaskMonitor
* @return the type_info address or null if it cannot be determined
* @throws CancelledException if cancelled
*/
public static boolean isTypeInfoTypeDescriptorAddress(Program program, Address address) {
MemoryByteProvider provider = new MemoryByteProvider(program.getMemory(), address);
try {
BinaryReader reader = new BinaryReader(provider, !program.getLanguage().isBigEndian());
String value = reader.readAsciiString(program.getDefaultPointerSize() * 2);
return TYPE_INFO_STRING.equals(value);
} catch (IOException e) {
return false;
}
public static Address findTypeInfoVftableAddress(Program program, TaskMonitor monitor)
throws CancelledException {
// Checked for cached value
if (vftableMap.containsKey(program)) {
return vftableMap.get(program);
}
private static Address locateTypeInfoAddress(Program program) {
Memory memory = program.getMemory();
try {
List<MemoryBlock> dataBlocks = ProgramMemoryUtil.getMemoryBlocksStartingWithName(
program, program.getMemory(), ".data", TaskMonitor.DUMMY);
for (MemoryBlock memoryBlock : dataBlocks) {
Address typeInfoAddress =
memory.findBytes(memoryBlock.getStart(), memoryBlock.getEnd(),
TYPE_INFO_STRING.getBytes(), null, true, TaskMonitor.DUMMY);
if (typeInfoAddress != null) {
return TypeDescriptorModel.getBaseAddress(program, typeInfoAddress);
// if type info vftable already a symbol, just use the address of the symbol
Address infoVftableAddress = findTypeInfoVftableLabel(program);
if (infoVftableAddress == null) {
// search for mangled class prefix names, and locate the vftable pointer relative to some
// minimum number that all point to the same location which should be the vftable
AddressSetView set = program.getMemory().getLoadedAndInitializedAddressSet();
List<MemoryBlock> dataBlocks =
ProgramMemoryUtil.getMemoryBlocksStartingWithName(program, set, ".data", monitor);
CommonRTTIMatchCounter vfTableAddrChecker = new CommonRTTIMatchCounter(program);
ProgramMemoryUtil.locateString(CLASS_PREFIX_CHARS, vfTableAddrChecker, program,
dataBlocks, set, monitor);
infoVftableAddress = vfTableAddrChecker.getinfoVfTable();
}
// cache result of search
vftableMap.put(program, infoVftableAddress);
return infoVftableAddress;
}
} catch (CancelledException e) {
// impossible
throw new AssertException(e);
/**
* find type info vftable by existing type_info::vftable symbol
* @param program program to check
* @return return vftable addr if symbol exists
*/
private static Address findTypeInfoVftableLabel(Program program) {
SymbolTable symbolTable = program.getSymbolTable();
Namespace typeinfoNamespace =
symbolTable.getNamespace(TYPE_INFO_NAMESPACE, program.getGlobalNamespace());
Symbol vftableSymbol =
symbolTable.getLocalVariableSymbol("vftable", typeinfoNamespace);
if (vftableSymbol != null) {
return vftableSymbol.getAddress();
}
vftableSymbol = symbolTable.getLocalVariableSymbol("`vftable'", typeinfoNamespace);
if (vftableSymbol != null) {
return vftableSymbol.getAddress();
}
vftableSymbol = symbolTable.getLocalVariableSymbol("type_info", typeinfoNamespace);
if (vftableSymbol != null) {
return vftableSymbol.getAddress();
}
return null;
}
/**
* Method to create type_info vftable label (and namespace if needed) at the given address
* @param program the current program
* @param address the given address
*/
public static void createTypeInfoVftableSymbol(Program program, Address address) {
SymbolTable symbolTable = program.getSymbolTable();
Namespace typeinfoNamespace =
symbolTable.getNamespace(TYPE_INFO_NAMESPACE, program.getGlobalNamespace());
if (typeinfoNamespace == null) {
try {
typeinfoNamespace = symbolTable.createClass(program.getGlobalNamespace(),
TYPE_INFO_NAMESPACE, SourceType.IMPORTED);
}
catch (DuplicateNameException e) {
Msg.error(RttiUtil.class, "Duplicate type_info class namespace at " +
program.getName() + " " + address + ". " + e.getMessage());
return;
}
catch (InvalidInputException e) {
Msg.error(RttiUtil.class, "Invalid input creating type_info class namespace " +
program.getName() + " " + address + ". " + e.getMessage());
return;
}
}
// check to see if symbol already exists both non-pdb and pdb versions
Symbol vftableSymbol = symbolTable.getSymbol(TYPE_INFO_NAMESPACE, address, typeinfoNamespace);
if (vftableSymbol != null) {
return;
}
vftableSymbol = symbolTable.getSymbol("`vftable'", address, typeinfoNamespace);
if (vftableSymbol != null) {
return;
}
try {
vftableSymbol =
symbolTable.createLabel(address, "vftable", typeinfoNamespace, SourceType.IMPORTED);
if (vftableSymbol == null) {
Msg.error(RttiUtil.class,
program.getName() + " Couldn't create type_info vftable symbol. ");
return;
}
}
catch (InvalidInputException e) {
Msg.error(RttiUtil.class,
program.getName() + " Couldn't create type_info vftable symbol. " + e.getMessage());
return;
}
}
}

View file

@ -44,6 +44,7 @@ public class VfTableModel extends AbstractCreateDataTypeModel {
private Program lastProgram;
private DataType lastDataType;
private int lastElementCount = -1;
private int elementCount = 0;
/**
* Creates the model for the vf table data.
@ -54,8 +55,18 @@ public class VfTableModel extends AbstractCreateDataTypeModel {
*/
public VfTableModel(Program program, Address vfTableAddress,
DataValidationOptions validationOptions) {
super(program, RttiUtil.getVfTableCount(program, vfTableAddress), vfTableAddress,
// use one for the data type element count, because there is only one array of some element size
super(program, 1, vfTableAddress,
validationOptions);
elementCount= RttiUtil.getVfTableCount(program, vfTableAddress);
}
/**
* Get the number of vftable elements in this vftable
* @return number of elements
*/
public int getElementCount() {
return elementCount;
}
@Override
@ -79,7 +90,7 @@ public class VfTableModel extends AbstractCreateDataTypeModel {
long entrySize = individualEntryDataType.getLength();
// Each entry is a pointer to where a function can possibly be created.
long numEntries = getCount();
long numEntries = elementCount;
if (numEntries == 0) {
throw new InvalidDataTypeException(
getName() + " data type at " + getAddress() + " doesn't have a valid vf table.");
@ -120,9 +131,8 @@ public class VfTableModel extends AbstractCreateDataTypeModel {
lastProgram = program;
lastDataType = null;
lastElementCount = -1;
lastElementCount = elementCount;
lastElementCount = getCount();
if (lastElementCount > 0) {
DataTypeManager dataTypeManager = program.getDataTypeManager();
PointerDataType pointerDt = new PointerDataType(dataTypeManager);

View file

@ -19,18 +19,14 @@ import java.util.*;
import ghidra.app.cmd.data.CreateTypeDescriptorBackgroundCmd;
import ghidra.app.cmd.data.TypeDescriptorModel;
import ghidra.app.cmd.data.rtti.CreateRtti4BackgroundCmd;
import ghidra.app.cmd.data.rtti.Rtti4Model;
import ghidra.app.cmd.data.rtti.RttiUtil;
import ghidra.app.cmd.data.rtti.*;
import ghidra.app.services.*;
import ghidra.app.util.datatype.microsoft.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.*;
import ghidra.program.model.data.InvalidDataTypeException;
import ghidra.program.model.lang.UndefinedValueException;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.util.ProgramMemoryUtil;
import ghidra.util.bytesearch.*;
import ghidra.util.exception.*;
@ -77,48 +73,47 @@ public class RttiAnalyzer extends AbstractAnalyzer {
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException {
Address typeInfoRtti0Address = RttiUtil.getTypeInfoTypeDescriptorAddress(program);
if (typeInfoRtti0Address == null) {
log.appendMsg(this.getName(), "Couldn't find RTTI type info structure.");
Address commonVfTableAddress = RttiUtil.findTypeInfoVftableAddress(program, monitor);
if (commonVfTableAddress == null) {
return true;
}
// ensure the label is present
try {
program.getSymbolTable().createLabel(
typeInfoRtti0Address, RttiUtil.TYPE_INFO_LABEL, SourceType.ANALYSIS);
} catch (InvalidInputException e) {
// not invalid
throw new AssertException(e);
}
RttiUtil.createTypeInfoVftableSymbol(program,commonVfTableAddress);
// Get the address of the vf table data in common for all RTTI 0.
TypeDescriptorModel typeDescriptorModel =
new TypeDescriptorModel(program, typeInfoRtti0Address, validationOptions);
try {
Address commonVfTableAddress = typeDescriptorModel.getVFTableAddress();
if (commonVfTableAddress == null) {
log.appendMsg(this.getName(),
"Couldn't get vf table address for RTTI 0 @ " + typeInfoRtti0Address + ". ");
return false;
Set<Address> possibleTypeAddresses = locatePotentialRTTI0Entries(program, set, monitor);
if (possibleTypeAddresses == null) {
return true;
}
int alignment = program.getDefaultPointerSize();
List<MemoryBlock> dataBlocks = ProgramMemoryUtil.getMemoryBlocksStartingWithName(
program, program.getMemory(), ".data", TaskMonitor.DUMMY);
Set<Address> possibleTypeAddresses = ProgramMemoryUtil.findDirectReferences(program,
dataBlocks, alignment, commonVfTableAddress, monitor);
// We now have a list of potential rtti0 addresses.
processRtti0(possibleTypeAddresses, program, monitor);
return true;
}
catch (InvalidDataTypeException | UndefinedValueException e) {
log.appendMsg(this.getName(), "Couldn't get vf table address for RTTI 0 @ " +
typeInfoRtti0Address + ". " + e.getMessage());
return false;
/**
* locate any potential RTTI0 based on pointers to the type_info vftable
* @param program proram to locate within
* @param set restricted set to locate within
* @param monitor monitor for canceling
* @return set of potential RTTI0 entries
* @throws CancelledException
*/
private Set<Address> locatePotentialRTTI0Entries(Program program, AddressSetView set,
TaskMonitor monitor) throws CancelledException {
Address commonVfTableAddress = RttiUtil.findTypeInfoVftableAddress(program, monitor);
if (commonVfTableAddress == null) {
return null;
}
// use the type_info vftable address to find a list of potential RTTI0 addresses
int alignment = program.getDefaultPointerSize();
List<MemoryBlock> dataBlocks = ProgramMemoryUtil.getMemoryBlocksStartingWithName(
program, program.getMemory(), ".data", TaskMonitor.DUMMY);
Set<Address> possibleTypeAddresses = ProgramMemoryUtil.findDirectReferences(program,
dataBlocks, alignment, commonVfTableAddress, monitor);
return possibleTypeAddresses;
}
private void processRtti0(Collection<Address> possibleRtti0Addresses, Program program,
@ -168,6 +163,9 @@ public class RttiAnalyzer extends AbstractAnalyzer {
dataBlocks.addAll(ProgramMemoryUtil.getMemoryBlocksStartingWithName(program,
program.getMemory(), ".data", monitor));
dataBlocks.addAll(ProgramMemoryUtil.getMemoryBlocksStartingWithName(program,
program.getMemory(), ".text", monitor));
List<Address> rtti4Addresses =
getRtti4Addresses(program, dataBlocks, rtti0Locations, validationOptions, monitor);

View file

@ -269,7 +269,7 @@ class AbstractRttiTest extends AbstractCreateDataTypeModelTest {
vfTableModel.validate();
assertEquals(addressVfTable, vfTableModel.getAddress());
int numVfTableEntries = vfAddresses.length;
assertEquals(numVfTableEntries, vfTableModel.getCount());
assertEquals(numVfTableEntries, vfTableModel.getElementCount());
for (int i = 0; i < numVfTableEntries; i++) {
assertEquals(addr(program, vfAddresses[i]), vfTableModel.getVirtualFunctionPointer(i));
}

View file

@ -51,7 +51,7 @@ public class RttiModelTest extends AbstractRttiTest {
setupRtti4_32(builder, 0x01001340L, 0, 0, 0, "0x01005364", "0x0100137c");
Address address = builder.addr(0x01001340L);
checkInvalidModel(new Rtti4Model(program, address, defaultValidationOptions),
"TypeDescriptor data type at 01005364 doesn't point to a vfTable address in a loaded and initialized memory block.");
"No vf table pointer is defined for this TypeDescriptor model.");
}
@Test
@ -62,7 +62,7 @@ public class RttiModelTest extends AbstractRttiTest {
setupRtti0_32(builder, 0x01001364, "0x01007700", "0x0", "stuff");
Address address = builder.addr(0x01001340L);
checkInvalidModel(new Rtti4Model(program, address, defaultValidationOptions),
"TypeDescriptor data type at 01001364 doesn't point to a vfTable address in a loaded and initialized memory block.");
"No vf table pointer is defined for this TypeDescriptor model.");
}
@Test

View file

@ -0,0 +1,33 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package utility.function;
import java.util.function.Consumer;
/**
* TerminatingConsumer is a Consumer {@link Consumer} that can request termination
* of the supplier once some condition is reached, for example some number of consumed results
* accepted. If termination is required override the terminationRequested()
* method to return true when termination state is reached.
*
* @param <T> the type of the input to the operation
*/
public interface TerminatingConsumer<T> extends Consumer<T> {
default boolean terminationRequested() {
return false;
}
}