static void event_module_load(void *drcontext, const module_data_t *data, bool loaded) { module_entry_t *entry = NULL; module_data_t *mod; int i; /* Some apps repeatedly unload and reload the same module, * so we will try to re-use the old one. */ ASSERT(data != NULL, "data must not be NULL"); drvector_lock(&module_table.vector); /* Assuming most recently loaded entries are most likely to be unloaded, * we iterate the module table in a backward way for better performance. */ for (i = module_table.vector.entries-1; i >= 0; i--) { entry = drvector_get_entry(&module_table.vector, i); mod = entry->data; if (entry->unload && /* If the same module is re-loaded at the same address, * we will try to use the existing entry. */ mod->start == data->start && mod->end == data->end && mod->entry_point == data->entry_point && #ifdef WINDOWS mod->checksum == data->checksum && mod->timestamp == data->timestamp && #endif /* If a module w/ no name (there are some) is loaded, we will * keep making new entries. */ dr_module_preferred_name(data) != NULL && dr_module_preferred_name(mod) != NULL && strcmp(dr_module_preferred_name(data), dr_module_preferred_name(mod)) == 0) { entry->unload = false; break; } entry = NULL; } if (entry == NULL) { entry = dr_global_alloc(sizeof(*entry)); entry->id = module_table.vector.entries; entry->unload = false; entry->data = dr_copy_module_data(data); drvector_append(&module_table.vector, entry); } drvector_unlock(&module_table.vector); global_module_cache_add(module_table.cache, entry); }
DR_EXPORT void dr_init(client_id_t id) { /* Look up start_monitor() and stop_monitor() in the target app. * These functions are dummy markers that tell us when to start * and stop printing syscalls. */ /* NOTE - we could use dr_module_lookup_by_name, but we use the iterator instead * to test it out. */ dr_module_iterator_t *iter = dr_module_iterator_start(); while (dr_module_iterator_hasnext(iter)) { module_data_t *data = dr_module_iterator_next(iter); if (strcmp(dr_module_preferred_name(data), TEST_NAME) == 0) { module_handle_t lib = data->handle; start_pc = (app_pc)dr_get_proc_address(lib, "start_monitor"); stop_pc = (app_pc)dr_get_proc_address(lib, "stop_monitor"); } dr_free_module_data(data); } dr_module_iterator_stop(iter); if (start_pc == NULL || stop_pc == NULL) { dr_fprintf(STDERR, "ERROR: did not find start/stop markers\n"); } /* Register the BB hook */ dr_register_bb_event(bb_event); #ifdef LINUX /* With early injection, libc won't be loaded until later. */ dr_register_module_load_event(event_module_load); #endif }
static void replace_routine(bool add, const module_data_t *mod, app_pc addr, int index) { IF_DEBUG(const char *modname = dr_module_preferred_name(mod);) /* look for partial map (i#730) */ if (addr >= mod->end) {
static void event_module_load(void *drcontext, const module_data_t *info, bool loaded) { uint64 early_inject; /* do some more dr_get_proc_address testing */ if (strncmp(dr_module_preferred_name(info), "libc.", 5) == 0) { module_handle_t lib = info->handle; dr_export_info_t fn_info; dr_fprintf(STDERR, "found libc\n"); if (dr_get_proc_address(lib, "malloc") == NULL) dr_fprintf(STDERR, "ERROR: can't find malloc in libc\n"); if (dr_get_proc_address(lib, "free") == NULL) dr_fprintf(STDERR, "ERROR: can't find free in libc\n"); if (dr_get_proc_address(lib, "printf") == NULL) dr_fprintf(STDERR, "ERROR: can't find printf in libc\n"); /* i#884: gettimeofday is indirect code on my system, and calling it * will crash unless we wait until libc is fully relocated. * dr_get_proc_address() wraps the fault in a try/except and returns * NULL. The _ex variant does not, so we use that to test the lookup. */ if (!dr_get_proc_address_ex(lib, "gettimeofday", &fn_info, sizeof(fn_info))) dr_fprintf(STDERR, "ERROR: can't find gettimeofday in libc\n"); dr_unregister_module_load_event(event_module_load); } }
/* assuming caller holds the lock */ static void module_table_entry_print(module_entry_t *entry, file_t log, bool print_all_info) { const char *name; module_data_t *data; const char *full_path = "<unknown>"; data = entry->data; name = dr_module_preferred_name(data); if (data->full_path != NULL && data->full_path[0] != '\0') full_path = data->full_path; if (print_all_info) { dr_fprintf(log, "%3u, "PFX", "PFX", "PFX", %s, %s", entry->id, data->start, data->end, data->entry_point, (name == NULL || name[0] == '\0') ? "<unknown>" : name, full_path); #ifdef WINDOWS dr_fprintf(log, ", 0x%08x, 0x%08x", data->checksum, data->timestamp); #endif /* WINDOWS */ dr_fprintf(log, "\n"); } else { dr_fprintf(log, " %u, %llu, %s\n", entry->id, (uint64)(data->end - data->start), full_path); } }
static void module_load_event(void *dcontext, const module_data_t *data, bool loaded) { if (string_match(dr_module_preferred_name(data), "client.flush.exe")) { start = data->start; end = data->end; } }
static bool module_data_same(const module_data_t *d1, const module_data_t *d2) { if (d1->start == d2->start && d1->end == d2->end && d1->entry_point == d2->entry_point && #ifdef WINDOWS d1->checksum == d2->checksum && d1->timestamp == d2->timestamp && #endif /* treat two modules w/ no name (there are some) as different */ dr_module_preferred_name(d1) != NULL && dr_module_preferred_name(d2) != NULL && strcmp(dr_module_preferred_name(d1), dr_module_preferred_name(d2)) == 0) return true; return false; }
static void module_unload_event(void *drcontext, const module_data_t *mod) { if (strstr(dr_module_preferred_name(mod), "client.drwrap-test.appdll.") != NULL) { bool ok; ok = drwrap_replace(addr_replace, NULL, true); CHECK(ok, "un-replace failed"); ok = drwrap_replace_native(addr_replace2, NULL, true, 0, NULL, true); CHECK(ok, "un-replace_native failed"); ok = drwrap_replace_native(addr_replace_callsite, NULL, false, 0, NULL, true); CHECK(ok, "un-replace_native failed"); unwrap_addr(addr_skip_flags, "skip_flags", mod, true, false); unwrap_addr(addr_level0, "level0", mod, true, true); unwrap_addr(addr_level1, "level1", mod, true, true); unwrap_addr(addr_level2, "level2", mod, true, true); unwrap_addr(addr_tailcall, "makes_tailcall", mod, true, true); unwrap_addr(addr_preonly, "preonly", mod, true, false); /* skipme, postonly, and runlots were already unwrapped */ /* test longjmp */ unwrap_unwindtest_addr(addr_long0, "long0", mod); unwrap_unwindtest_addr(addr_long1, "long1", mod); unwrap_unwindtest_addr(addr_long2, "long2", mod); unwrap_unwindtest_addr(addr_long3, "long3", mod); unwrap_unwindtest_addr(addr_longdone, "longdone", mod); drmgr_set_tls_field(drcontext, tls_idx, (void *)(ptr_uint_t)0); #ifdef WINDOWS /* test SEH */ if (load_count == 1) { ok = drwrap_unwrap(addr_long0, wrap_unwindtest_seh_pre, wrap_unwindtest_seh_post); CHECK(ok, "unwrap failed"); ok = drwrap_unwrap(addr_long1, wrap_unwindtest_seh_pre, wrap_unwindtest_seh_post); CHECK(ok, "unwrap failed"); ok = drwrap_unwrap(addr_long2, wrap_unwindtest_seh_pre, wrap_unwindtest_seh_post); CHECK(ok, "unwrap failed"); ok = drwrap_unwrap(addr_long3, wrap_unwindtest_seh_pre, wrap_unwindtest_seh_post); CHECK(ok, "unwrap failed"); ok = drwrap_unwrap(addr_longdone, wrap_unwindtest_seh_pre, wrap_unwindtest_seh_post); } CHECK(ok, "unwrap failed"); #endif } }
static void module_load_event_perm(void *drcontext, const module_data_t *info, bool loaded) { /* Test i#138 */ if (info->full_path == NULL || info->full_path[0] == '\0') dr_fprintf(STDERR, "ERROR: full_path empty for %s\n", dr_module_preferred_name(info)); #ifdef WINDOWS /* We do not expect \\server-style paths for this test */ else if (info->full_path[0] == '\\' || info->full_path[1] != ':') dr_fprintf(STDERR, "ERROR: full_path is not in DOS format: %s\n", info->full_path); #else else if (info->full_path[0] != '/') dr_fprintf(STDERR, "ERROR: full_path is not absolute: %s\n", info->full_path); #endif }
/* Lookup the module containing addr and see if we have a table entry for it */ static table_entry_t * get_entry_for_address(app_pc addr) { module_data_t *data = dr_lookup_module(addr); table_entry_t *entry = NULL; if (data != NULL) { entry = table; while (entry != NULL && _stricmp(dr_module_preferred_name(data), entry->value.module_name) != 0) { entry = entry->next; } dr_free_module_data(data); } return entry; }
void symcache_module_unload(void *drcontext, const module_data_t *mod) { mod_cache_t *modcache; const char *modname = dr_module_preferred_name(mod); if (modname == NULL) return; /* don't support caching */ ASSERT(initialized, "symcache was not initialized"); dr_mutex_lock(symcache_lock); modcache = (mod_cache_t *) hashtable_lookup(&symcache_table, (void *)mod->full_path); if (modcache != NULL) { symcache_write_symfile(modname, modcache); hashtable_remove(&symcache_table, (void *)mod->full_path); } dr_mutex_unlock(symcache_lock); }
static bool symcache_module_has_data(const module_data_t *mod, bool require_syms) { mod_cache_t *modcache; bool res = false; const char *modname = dr_module_preferred_name(mod); if (modname == NULL) return false; /* don't support caching */ ASSERT(initialized, "symcache was not initialized"); dr_mutex_lock(symcache_lock); modcache = (mod_cache_t *) hashtable_lookup(&symcache_table, (void *)mod->full_path); if (modcache != NULL) res = (modcache->table.entries > 0 && (!require_syms || modcache->has_debug_info)); dr_mutex_unlock(symcache_lock); return res; }
bool symcache_module_is_cached(const module_data_t *mod) { mod_cache_t *modcache; bool res = false; const char *modname = dr_module_preferred_name(mod); if (modname == NULL) return false; /* don't support caching */ ASSERT(initialized, "symcache was not initialized"); dr_mutex_lock(symcache_lock); modcache = (mod_cache_t *) hashtable_lookup(&symcache_table, (void *)mod->full_path); if (modcache != NULL) res = modcache->table.entries > 0; dr_mutex_unlock(symcache_lock); return res; }
static bool symcache_module_save_common(const module_data_t *mod, bool remove) { mod_cache_t *modcache; const char *modname = dr_module_preferred_name(mod); if (modname == NULL) return false; /* don't support caching */ ASSERT(initialized, "symcache was not initialized"); dr_mutex_lock(symcache_lock); modcache = (mod_cache_t *) hashtable_lookup(&symcache_table, (void *)mod->full_path); if (modcache != NULL) { symcache_write_symfile(modname, modcache); if (remove) hashtable_remove(&symcache_table, (void *)mod->full_path); } dr_mutex_unlock(symcache_lock); return true; }
/* If an entry already exists and is 0, replaces it; else adds a new * offset for that symbol. */ bool symcache_add(const module_data_t *mod, const char *symbol, size_t offs) { mod_cache_t *modcache; const char *modname = dr_module_preferred_name(mod); if (modname == NULL) return false; /* don't support caching */ ASSERT(initialized, "symcache was not initialized"); dr_mutex_lock(symcache_lock); modcache = (mod_cache_t *) hashtable_lookup(&symcache_table, (void *)mod->full_path); if (modcache == NULL) { LOG(2, "%s: there is no cache for %s\n", __FUNCTION__, modname); dr_mutex_unlock(symcache_lock); return false; } if (symcache_symbol_add(modname, &modcache->table, symbol, offs) && modcache->from_file) modcache->appended = true; dr_mutex_unlock(symcache_lock); return true; }
static void print_address(file_t f, app_pc addr, const char *prefix) { drsym_error_t symres; drsym_info_t sym; char name[MAX_SYM_RESULT]; char file[MAXIMUM_PATH]; module_data_t *data; data = dr_lookup_module(addr); if (data == NULL) { dr_fprintf(f, "%s "PFX" ? ??:0\n", prefix, addr); return; } sym.struct_size = sizeof(sym); sym.name = name; sym.name_size = MAX_SYM_RESULT; sym.file = file; sym.file_size = MAXIMUM_PATH; symres = drsym_lookup_address(data->full_path, addr - data->start, &sym, DRSYM_DEFAULT_FLAGS); if (symres == DRSYM_SUCCESS || symres == DRSYM_ERROR_LINE_NOT_AVAILABLE) { const char *modname = dr_module_preferred_name(data); if (modname == NULL) modname = "<noname>"; dr_fprintf(f, "%s "PFX" %s!%s+"PIFX, prefix, addr, modname, sym.name, addr - data->start - sym.start_offs); if (symres == DRSYM_ERROR_LINE_NOT_AVAILABLE) { dr_fprintf(f, " ??:0\n"); } else { dr_fprintf(f, " %s:%"UINT64_FORMAT_CODE"+"PIFX"\n", sym.file, sym.line, sym.line_offs); } } else dr_fprintf(f, "%s "PFX" ? ??:0\n", prefix, addr); dr_free_module_data(data); }
/* assuming caller holds the lock */ static int module_table_entry_print(module_entry_t *entry, char *buf, size_t size) { const char *name; module_data_t *data; const char *full_path = "<unknown>"; int len, total_len = 0; data = entry->data; name = dr_module_preferred_name(data); if (data->full_path != NULL && data->full_path[0] != '\0') full_path = data->full_path; len = dr_snprintf(buf, size, "%3u, " PFX ", " PFX ", " PFX "", entry->id, data->start, data->end, data->entry_point); if (len == -1) return -1; buf += len; total_len += len; size -= len; #ifdef WINDOWS len = dr_snprintf(buf, size, ", 0x%08x, 0x%08x", data->checksum, data->timestamp); if (len == -1) return -1; buf += len; total_len += len; size -= len; #endif len = dr_snprintf(buf, size, ", %s\n", full_path); if (len == -1) return -1; buf += len; total_len += len; size -= len; return total_len; }
static void event_exit(void) { int i; char msg[512]; int len; int j; uint64 xmod_xfer = 0; uint64 self_xfer = 0; for (i = 0; i < num_mods; i++) { dr_fprintf(logfile, "module %3d: %s\n", i, dr_module_preferred_name(mod_array[i].info) == NULL ? "<unknown>" : dr_module_preferred_name(mod_array[i].info)); dr_fprintf(logfile, "%20llu instruction executed\n", mod_cnt[i]); } if (mod_cnt[UNKNOW_MODULE_IDX] != 0) { dr_fprintf(logfile, "unknown modules:\n%20llu instruction executed\n", mod_cnt[UNKNOW_MODULE_IDX]); } for (i = 0; i < MAX_NUM_MODULES; i++) { for (j = 0; j < num_mods; j++) { if (xfer_cnt[i][j] != 0) { dr_fprintf(logfile, "mod %3d => mod %3d: %8u\n", i, j, xfer_cnt[i][j]); if (i == j) self_xfer += xfer_cnt[i][j]; else xmod_xfer += xfer_cnt[i][j]; } } } len = dr_snprintf(msg, sizeof(msg)/sizeof(msg[0]), "Instrumentation results:\n" "\t%10llu instructions executed\n" "\t%10llu (%2.3f%%) cross module indirect branches\n" "\t%10llu (%2.3f%%) intra-module indirect branches\n", ins_count, xmod_xfer, 100*(float)xmod_xfer/ins_count, self_xfer, 100*(float)self_xfer/ins_count); DR_ASSERT(len > 0); NULL_TERMINATE_BUFFER(msg); #ifdef SHOW_RESULTS DISPLAY_STRING(msg); #endif /* SHOW_RESULTS */ dr_fprintf(logfile, "%s\n", msg); dr_mutex_lock(mod_lock); for (i = 0; i < num_mods; i++) { DR_ASSERT(mod_array[i].info != NULL); dr_free_module_data(mod_array[i].info); } dr_mutex_unlock(mod_lock); dr_mutex_destroy(mod_lock); log_file_close(logfile); drx_exit(); if (!drmgr_unregister_bb_instrumentation_event(event_analyze_bb) || !drmgr_unregister_module_load_event(event_module_load) || !drmgr_unregister_module_unload_event(event_module_unload) || drreg_exit() != DRREG_SUCCESS) DR_ASSERT(false); drmgr_exit(); }
void symcache_module_load(void *drcontext, const module_data_t *mod, bool loaded) { /* look for cache file for this module. * fill in hashtable: key is string, value is list of offsets */ mod_cache_t *modcache; const char *modname = dr_module_preferred_name(mod); file_t f; if (modname == NULL) return; /* don't support caching */ /* if smaller than threshold, not worth caching */ if (mod->end - mod->start < op_modsize_cache_threshold) { LOG(1, "%s: module %s too small to cache\n", __FUNCTION__, modname); return; } ASSERT(initialized, "symcache was not initialized"); /* support initializing prior to module events => called twice */ dr_mutex_lock(symcache_lock); modcache = (mod_cache_t *) hashtable_lookup(&symcache_table, (void *)mod->full_path); dr_mutex_unlock(symcache_lock); if (modcache != NULL) return; modcache = (mod_cache_t *) global_alloc(sizeof(*modcache), HEAPSTAT_HASHTABLE); memset(modcache, 0, sizeof(*modcache)); hashtable_init_ex(&modcache->table, SYMCACHE_MODULE_TABLE_HASH_BITS, HASH_STRING, true/*strdup*/, true/*synch*/, symcache_free_list, NULL, NULL); /* store consistency fields */ f = dr_open_file(mod->full_path, DR_FILE_READ); if (f != INVALID_FILE) { bool ok = dr_file_size(f, &modcache->module_file_size); if (!ok) WARN("WARNING: unable to determine size of %s\n", mod->full_path); dr_close_file(f); } else WARN("WARNING: unable to open %s\n", mod->full_path); #ifdef WINDOWS modcache->file_version = mod->file_version; modcache->product_version = mod->product_version; modcache->checksum = mod->checksum; modcache->timestamp = mod->timestamp; modcache->module_internal_size = mod->module_internal_size; #endif modcache->modname = drmem_strdup(modname, HEAPSTAT_HASHTABLE); modcache->from_file = symcache_read_symfile(mod, modname, modcache); dr_mutex_lock(symcache_lock); if (!hashtable_add(&symcache_table, (void *)mod->full_path, (void *)modcache)) { /* this should be really rare to have dup paths (xref i#729) -- and * actually we now have a lookup up above so we should only get here * on a race while we let go of the lock */ WARN("WARNING: duplicate module paths: only caching symbols from first\n"); hashtable_delete(&modcache->table); global_free(modcache, sizeof(*modcache), HEAPSTAT_HASHTABLE); } dr_mutex_unlock(symcache_lock); }
*/ /* Some symbols have multiple offsets. The majority have just one. * Rather than allocate an array and have the caller free it, or use * callbacks or an iterator across which we hold our lock, we have the * caller pass us an index as a stateless iterator. The table is * unlikely to be changed in between so we avoid complexity there. */ bool symcache_lookup(const module_data_t *mod, const char *symbol, uint idx, size_t *offs OUT, uint *num OUT) { offset_list_t *olist; offset_entry_t *e; mod_cache_t *modcache; uint i; const char *modname = dr_module_preferred_name(mod); if (modname == NULL) return false; /* don't support caching */ ASSERT(initialized, "symcache was not initialized"); if (offs == NULL || num == NULL) { ASSERT(false, "invalid params"); return false; } dr_mutex_lock(symcache_lock); modcache = (mod_cache_t *) hashtable_lookup(&symcache_table, (void *)mod->full_path); if (modcache == NULL) { dr_mutex_unlock(symcache_lock); return false; } olist = (offset_list_t *) hashtable_lookup(&modcache->table, (void *)symbol); if (olist == NULL) {
static void module_load_event(void *drcontext, const module_data_t *mod, bool loaded) { if (strstr(dr_module_preferred_name(mod), "client.drwrap-test.appdll.") != NULL) { bool ok; instr_t inst; app_pc init_pc, pc, next_pc; load_count++; if (load_count == 2) { /* test no-frills */ drwrap_set_global_flags(DRWRAP_NO_FRILLS); } addr_replace = (app_pc) dr_get_proc_address(mod->handle, "replaceme"); CHECK(addr_replace != NULL, "cannot find lib export"); ok = drwrap_replace(addr_replace, (app_pc) replacewith, false); CHECK(ok, "replace failed"); addr_replace2 = (app_pc) dr_get_proc_address(mod->handle, "replaceme2"); CHECK(addr_replace2 != NULL, "cannot find lib export"); ok = drwrap_replace_native(addr_replace2, (app_pc) replacewith2, true/*at entry*/, 0, (void *)(ptr_int_t)DRWRAP_NATIVE_PARAM, false); CHECK(ok, "replace_native failed"); init_pc = (app_pc) dr_get_proc_address(mod->handle, "replace_callsite"); CHECK(init_pc != NULL, "cannot find lib export"); /* Find callsite: we assume we'll linearly hit a ret. We take final call * to skip any PIC call. */ instr_init(drcontext, &inst); pc = init_pc; do { instr_reset(drcontext, &inst); next_pc = decode(drcontext, pc, &inst); if (!instr_valid(&inst)) break; /* if initial jmp, follow it to handle ILT-indirection */ if (pc == init_pc && instr_is_ubr(&inst)) next_pc = opnd_get_pc(instr_get_target(&inst)); else if (instr_is_call(&inst)) addr_replace_callsite = pc; pc = next_pc; } while (instr_valid(&inst) && !instr_is_return(&inst)); CHECK(addr_replace_callsite != NULL, "cannot find lib export"); ok = drwrap_replace_native(addr_replace_callsite, (app_pc) replace_callsite, false/*!at entry*/, 0, (void *)(ptr_int_t)DRWRAP_NATIVE_PARAM, false); CHECK(ok, "replace_native failed"); instr_free(drcontext, &inst); wrap_addr(&addr_level0, "level0", mod, true, true); wrap_addr(&addr_level1, "level1", mod, true, true); wrap_addr(&addr_level2, "level2", mod, true, true); wrap_addr(&addr_tailcall, "makes_tailcall", mod, true, true); wrap_addr(&addr_skipme, "skipme", mod, true, true); wrap_addr(&addr_repeat, "repeatme", mod, true, true); wrap_addr(&addr_preonly, "preonly", mod, true, false); wrap_addr(&addr_postonly, "postonly", mod, false, true); wrap_addr(&addr_runlots, "runlots", mod, false, true); /* test longjmp */ wrap_unwindtest_addr(&addr_long0, "long0", mod); wrap_unwindtest_addr(&addr_long1, "long1", mod); wrap_unwindtest_addr(&addr_long2, "long2", mod); wrap_unwindtest_addr(&addr_long3, "long3", mod); wrap_unwindtest_addr(&addr_longdone, "longdone", mod); drmgr_set_tls_field(drcontext, tls_idx, (void *)(ptr_uint_t)0); #ifdef WINDOWS /* test SEH */ /* we can't do this test for no-frills b/c only one wrap per addr */ if (load_count == 1) { ok = drwrap_wrap_ex(addr_long0, wrap_unwindtest_seh_pre, wrap_unwindtest_seh_post, NULL, DRWRAP_UNWIND_ON_EXCEPTION); CHECK(ok, "wrap failed"); ok = drwrap_wrap_ex(addr_long1, wrap_unwindtest_seh_pre, wrap_unwindtest_seh_post, NULL, DRWRAP_UNWIND_ON_EXCEPTION); CHECK(ok, "wrap failed"); ok = drwrap_wrap_ex(addr_long2, wrap_unwindtest_seh_pre, wrap_unwindtest_seh_post, NULL, DRWRAP_UNWIND_ON_EXCEPTION); CHECK(ok, "wrap failed"); ok = drwrap_wrap_ex(addr_long3, wrap_unwindtest_seh_pre, wrap_unwindtest_seh_post, NULL, DRWRAP_UNWIND_ON_EXCEPTION); CHECK(ok, "wrap failed"); ok = drwrap_wrap_ex(addr_longdone, wrap_unwindtest_seh_pre, wrap_unwindtest_seh_post, NULL, DRWRAP_UNWIND_ON_EXCEPTION); CHECK(ok, "wrap failed"); } #endif /* test leaner wrapping */ if (load_count == 2) drwrap_set_global_flags(DRWRAP_NO_FRILLS | DRWRAP_FAST_CLEANCALLS); wrap_addr(&addr_skip_flags, "skip_flags", mod, true, false); } }