| /* ******************************************************************************* |
| * Copyright (c) 2012-2025 Google, Inc. All rights reserved. |
| * Copyright (c) 2011 Massachusetts Institute of Technology All rights reserved. |
| * Copyright (c) 2008-2010 VMware, Inc. All rights reserved. |
| * *******************************************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of VMware, Inc. nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| #include "../globals.h" |
| #include "../module_shared.h" |
| #include "module_private.h" |
| #include "os_private.h" |
| #include "../utils.h" |
| #include "instrument.h" |
| #include <stddef.h> /* offsetof */ |
| #ifdef LINUX |
| # include "rseq_linux.h" |
| #endif |
| |
| #ifdef NOT_DYNAMORIO_CORE_PROPER |
| # undef LOG |
| # define LOG(...) /* nothing */ |
| #else /* !NOT_DYNAMORIO_CORE_PROPER */ |
| |
| /* XXX; perhaps make a module_list interface to check for overlap? */ |
| extern vm_area_vector_t *loaded_module_areas; |
| |
| void |
| os_modules_init(void) |
| { |
| /* Nothing. */ |
| } |
| |
| void |
| os_modules_exit(void) |
| { |
| /* Nothing. */ |
| } |
| |
| /* view_size can be the size of the first mapping, to handle non-contiguous |
| * modules -- we'll update the module's size here |
| */ |
| void |
| os_module_area_init(module_area_t *ma, app_pc base, size_t view_size, bool at_map, |
| const char *filepath, uint64 inode HEAPACCT(which_heap_t which)) |
| { |
| app_pc mod_base, mod_end; |
| ptr_int_t load_delta; |
| char *soname = NULL; |
| ASSERT(module_is_header(base, view_size)); |
| |
| /* i#1589: use privload data if it exists (for client lib) */ |
| if (!privload_fill_os_module_info(base, &mod_base, &mod_end, &soname, &ma->os_data)) { |
| /* XXX i#1860: on Android we'll fail to fill in info from .dynamic, so |
| * we'll have incomplete data until the loader maps the segment with .dynamic. |
| * ma->os_data.have_dynamic_info indicates whether we have the info. |
| */ |
| module_walk_program_headers(base, view_size, at_map, |
| !at_map, /* i#1589: ld.so relocates .dynamic */ |
| &mod_base, NULL, &mod_end, &soname, &ma->os_data); |
| } |
| if (ma->os_data.contiguous) { |
| app_pc map_end = ma->os_data.segments[ma->os_data.num_segments - 1].end; |
| module_list_add_mapping(ma, base, map_end); |
| /* update, since may just be 1st segment size */ |
| ma->end = map_end; |
| } else { |
| /* Add the non-contiguous segments (i#160/PR 562667). We could just add |
| * them all separately but vmvectors are more efficient with fewer |
| * entries so we merge. We don't want general merging in our vector |
| * either. |
| */ |
| app_pc seg_base; |
| uint i; |
| ASSERT(ma->os_data.num_segments > 0 && ma->os_data.segments != NULL); |
| seg_base = ma->os_data.segments[0].start; |
| for (i = 1; i < ma->os_data.num_segments; i++) { |
| if (ma->os_data.segments[i].start > ma->os_data.segments[i - 1].end || |
| /* XXX: for shared we just add the first one. But if the first |
| * module is unloaded we'll be missing an entry for the others. |
| * We assume this won't happen b/c our only use of this now is |
| * the MacOS dyld shared cache's shared __LINKEDIT segment. If |
| * it could happen we should switch to a refcount in the vector. |
| */ |
| ma->os_data.segments[i - 1].shared) { |
| if (!ma->os_data.segments[i - 1].shared || |
| !vmvector_overlap(loaded_module_areas, seg_base, |
| ma->os_data.segments[i - 1].end)) { |
| module_list_add_mapping(ma, seg_base, |
| ma->os_data.segments[i - 1].end); |
| } |
| seg_base = ma->os_data.segments[i].start; |
| } |
| } |
| if (!ma->os_data.segments[i - 1].shared || |
| !vmvector_overlap(loaded_module_areas, seg_base, |
| ma->os_data.segments[i - 1].end)) |
| module_list_add_mapping(ma, seg_base, ma->os_data.segments[i - 1].end); |
| DOLOG(2, LOG_VMAREAS, { |
| LOG(GLOBAL, LOG_INTERP | LOG_VMAREAS, 2, "segment list\n"); |
| for (i = 0; i < ma->os_data.num_segments; i++) { |
| LOG(GLOBAL, LOG_INTERP | LOG_VMAREAS, 2, |
| "\tsegment %d: [" PFX "," PFX ") prot=%x\n", i, |
| ma->os_data.segments[i].start, ma->os_data.segments[i].end, |
| ma->os_data.segments[i].prot); |
| } |
| }); |
| /* update to max end (view_size may just be 1st segment end) */ |
| ma->end = ma->os_data.segments[ma->os_data.num_segments - 1].end; |
| } |
| |
| # ifdef LINUX |
| LOG(GLOBAL, LOG_SYMBOLS, 2, |
| "%s: hashtab=" PFX ", dynsym=" PFX ", dynstr=" PFX ", strsz=" SZFMT |
| ", symsz=" SZFMT "\n", |
| __func__, ma->os_data.hashtab, ma->os_data.dynsym, ma->os_data.dynstr, |
| ma->os_data.dynstr_size, ma->os_data.symentry_size); |
| # endif |
| |
| # ifdef LINUX /* on Mac the entire dyld shared cache has split __TEXT and __DATA */ |
| /* expect to map whole module */ |
| /* XREF 307599 on rounding module end to the next PAGE boundary */ |
| ASSERT_CURIOSITY(mod_end - mod_base == at_map ? ALIGN_FORWARD(view_size, PAGE_SIZE) |
| : view_size); |
| # endif |
| |
| ma->os_data.base_address = mod_base; |
| load_delta = base - mod_base; |
| |
| ma->entry_point = module_entry_point(base, load_delta); |
| |
| /* names - note os.c callers don't distinguish between no filename and an empty |
| * filename, we treat both as NULL, but leave the distinction for SONAME. */ |
| if (filepath == NULL || filepath[0] == '\0') { |
| ma->names.file_name = NULL; |
| # ifdef VMX86_SERVER |
| /* XXX: provide a targeted query to avoid full walk */ |
| void *iter = vmk_mmaps_iter_start(); |
| if (iter != NULL) { /* backward compatibility: support lack of iter */ |
| byte *start; |
| size_t length; |
| char name[MAXIMUM_PATH]; |
| while (vmk_mmaps_iter_next(iter, &start, &length, NULL, name, |
| BUFFER_SIZE_ELEMENTS(name))) { |
| if (base == start) { |
| ma->names.file_name = dr_strdup(name HEAPACCT(which)); |
| break; |
| } |
| } |
| vmk_mmaps_iter_stop(iter); |
| } |
| # endif |
| ma->full_path = NULL; |
| } else { |
| ma->names.file_name = dr_strdup(get_short_name(filepath) HEAPACCT(which)); |
| /* We could share alloc w/ names.file_name but simpler to separate */ |
| ma->full_path = dr_strdup(filepath HEAPACCT(which)); |
| } |
| ma->names.inode = inode; |
| if (soname == NULL) |
| ma->names.module_name = NULL; |
| else |
| ma->names.module_name = dr_strdup(soname HEAPACCT(which)); |
| |
| /* Fields for pcaches (PR 295534). These entries are not present in |
| * all libs: I see DT_CHECKSUM and the prelink field on FC12 but not |
| * on Ubuntu 9.04. |
| */ |
| if (ma->os_data.checksum == 0 && |
| (DYNAMO_OPTION(coarse_enable_freeze) || DYNAMO_OPTION(use_persisted))) { |
| /* Use something so we have usable pcache names */ |
| ma->os_data.checksum = d_r_crc32((const char *)ma->start, PAGE_SIZE); |
| } |
| /* Timestamp we just leave as 0 */ |
| |
| # ifdef LINUX |
| rseq_module_init(ma, at_map); |
| # endif |
| } |
| |
| void |
| free_module_names(module_names_t *mod_names HEAPACCT(which_heap_t which)) |
| { |
| ASSERT(mod_names != NULL); |
| |
| if (mod_names->module_name != NULL) |
| dr_strfree(mod_names->module_name HEAPACCT(which)); |
| if (mod_names->file_name != NULL) |
| dr_strfree(mod_names->file_name HEAPACCT(which)); |
| } |
| |
| void |
| module_copy_os_data(os_module_data_t *dst, os_module_data_t *src) |
| { |
| memcpy(dst, src, sizeof(*dst)); |
| if (src->segments != NULL) { |
| dst->segments = (module_segment_t *)HEAP_ARRAY_ALLOC( |
| GLOBAL_DCONTEXT, module_segment_t, src->alloc_segments, ACCT_OTHER, |
| PROTECTED); |
| memcpy(dst->segments, src->segments, |
| src->num_segments * sizeof(module_segment_t)); |
| } |
| } |
| |
| void |
| print_modules(file_t f, bool dump_xml) |
| { |
| module_iterator_t *mi; |
| |
| /* we walk our own module list that is populated on an initial walk through memory, |
| * and further kept consistent on memory mappings of likely modules */ |
| print_file(f, dump_xml ? "<loaded-modules>\n" : "\nLoaded modules:\n"); |
| |
| mi = module_iterator_start(); |
| while (module_iterator_hasnext(mi)) { |
| module_area_t *ma = module_iterator_next(mi); |
| print_file(f, |
| dump_xml ? "\t<so range=\"" PFX "-" PFX "\" " |
| "entry=\"" PFX "\" base_address=" PFX "\n" |
| "\tname=\"%s\" />\n" |
| : " " PFX "-" PFX " entry=" PFX " base_address=" PFX "\n" |
| "\tname=\"%s\" \n", |
| ma->start, ma->end - 1, /* inclusive */ |
| ma->entry_point, ma->os_data.base_address, |
| GET_MODULE_NAME(&ma->names) == NULL ? "(null)" |
| : GET_MODULE_NAME(&ma->names)); |
| } |
| module_iterator_stop(mi); |
| |
| if (dump_xml) |
| print_file(f, "</loaded-modules>\n"); |
| else |
| print_file(f, "\n"); |
| } |
| |
| void |
| os_module_area_reset(module_area_t *ma HEAPACCT(which_heap_t which)) |
| { |
| if (ma->os_data.contiguous) |
| module_list_remove_mapping(ma, ma->start, ma->end); |
| else { |
| /* Remove the non-contiguous segments (i#160/PR 562667) */ |
| app_pc seg_base; |
| uint i; |
| ASSERT(ma->os_data.num_segments > 0 && ma->os_data.segments != NULL); |
| seg_base = ma->os_data.segments[0].start; |
| for (i = 1; i < ma->os_data.num_segments; i++) { |
| if (ma->os_data.segments[i].start > ma->os_data.segments[i - 1].end) { |
| module_list_remove_mapping(ma, seg_base, ma->os_data.segments[i - 1].end); |
| seg_base = ma->os_data.segments[i].start; |
| } |
| } |
| module_list_remove_mapping(ma, seg_base, ma->os_data.segments[i - 1].end); |
| } |
| HEAP_ARRAY_FREE(GLOBAL_DCONTEXT, ma->os_data.segments, module_segment_t, |
| ma->os_data.alloc_segments, ACCT_OTHER, PROTECTED); |
| if (ma->full_path != NULL) |
| dr_strfree(ma->full_path HEAPACCT(which)); |
| } |
| |
| /* Returns the bounds of the first section with matching name. */ |
| bool |
| get_named_section_bounds(app_pc module_base, const char *name, app_pc *start /*OUT*/, |
| app_pc *end /*OUT*/) |
| { |
| /* XXX: not implemented */ |
| ASSERT(module_is_header(module_base, 0)); |
| if (start != NULL) |
| *start = NULL; |
| if (end != NULL) |
| *end = NULL; |
| return false; |
| } |
| |
| bool |
| rct_is_exported_function(app_pc tag) |
| { |
| /* XXX: not implemented */ |
| return false; |
| } |
| |
| /* TODO PR 295529: NYI, here so code origins policies aren't all ifdef WINDOWS */ |
| const char * |
| get_module_short_name(app_pc pc HEAPACCT(which_heap_t which)) |
| { |
| ASSERT_NOT_IMPLEMENTED(false); |
| return NULL; |
| } |
| |
| /* TODO PR 295529: NYI, here so moduledb code isn't all ifdef WINDOWS */ |
| bool |
| get_module_company_name(app_pc mod_base, char *out_buf, size_t out_buf_size) |
| { |
| ASSERT_NOT_IMPLEMENTED(false); |
| return false; |
| } |
| |
| app_pc |
| get_module_base(app_pc pc) |
| { |
| app_pc base = NULL; |
| module_area_t *ma; |
| os_get_module_info_lock(); |
| ma = module_pc_lookup(pc); |
| if (ma != NULL) |
| base = ma->start; |
| os_get_module_info_unlock(); |
| return base; |
| } |
| |
| /* TODO PR 212458: NYI, here so code origins policies aren't all ifdef WINDOWS */ |
| bool |
| is_range_in_code_section(app_pc module_base, app_pc start_pc, app_pc end_pc, |
| app_pc *sec_start /* OUT */, app_pc *sec_end /* OUT */) |
| { |
| ASSERT_NOT_IMPLEMENTED(false); |
| return false; |
| } |
| |
| /* TODO PR 212458: NYI, here so code origins policies aren't all ifdef WINDOWS */ |
| bool |
| is_in_code_section(app_pc module_base, app_pc addr, app_pc *sec_start /* OUT */, |
| app_pc *sec_end /* OUT */) |
| { |
| ASSERT_NOT_IMPLEMENTED(false); |
| return false; |
| } |
| |
| /* TODO PR 212458: NYI, here so code origins policies aren't all ifdef WINDOWS */ |
| bool |
| is_in_dot_data_section(app_pc module_base, app_pc addr, app_pc *sec_start /* OUT */, |
| app_pc *sec_end /* OUT */) |
| { |
| return false; |
| ASSERT_NOT_IMPLEMENTED(false); |
| } |
| |
| /* TODO PR 212458: NYI, here so code origins policies aren't all ifdef WINDOWS */ |
| bool |
| is_in_any_section(app_pc module_base, app_pc addr, app_pc *sec_start /* OUT */, |
| app_pc *sec_end /* OUT */) |
| { |
| ASSERT_NOT_IMPLEMENTED(false); |
| return false; |
| } |
| |
| bool |
| is_mapped_as_image(app_pc module_base) |
| { |
| return module_is_header(module_base, 0); |
| } |
| |
| /* Gets module information of module containing pc, cached from our module list. |
| * Returns false if not in module; none of the OUT arguments are set in that case. |
| * |
| * XXX: share code w/ win32/module.c's os_get_module_info() |
| * |
| * Note: this function returns only one module name using the rule established |
| * by GET_MODULE_NAME(); for getting all possible ones use |
| * os_get_module_info_all_names() directly. Part of fix for case 9842. |
| * |
| * If name != NULL, caller must acquire the module_data_lock beforehand |
| * and call os_get_module_info_unlock() when finished with the name |
| * (caller can use dr_strdup to make a copy first if necessary; validity of the |
| * name is guaranteed only as long as the caller holds module_data_lock). |
| * If name == NULL, this routine acquires and releases the lock and the |
| * caller has no obligations. |
| */ |
| bool |
| os_get_module_info(const app_pc pc, uint *checksum, uint *timestamp, size_t *size, |
| const char **name, size_t *code_size, uint64 *file_version) |
| { |
| module_area_t *ma; |
| if (!is_module_list_initialized()) |
| return false; |
| |
| /* read lock to protect custom data */ |
| if (name == NULL) |
| os_get_module_info_lock(); |
| ASSERT(os_get_module_info_locked()); |
| ; |
| |
| ma = module_pc_lookup(pc); |
| if (ma != NULL) { |
| if (checksum != NULL) |
| *checksum = ma->os_data.checksum; |
| if (timestamp != NULL) |
| *timestamp = ma->os_data.timestamp; |
| if (size != NULL) |
| *size = ma->end - ma->start; |
| if (name != NULL) |
| *name = GET_MODULE_NAME(&ma->names); |
| if (code_size != NULL) { |
| /* Using rx segment size since don't want to impl section |
| * iterator (i#76/PR 212458) |
| */ |
| uint i; |
| size_t rx_sz = 0; |
| ASSERT(ma->os_data.num_segments > 0 && ma->os_data.segments != NULL); |
| for (i = 0; i < ma->os_data.num_segments; i++) { |
| if (ma->os_data.segments[i].prot == (MEMPROT_EXEC | MEMPROT_READ)) { |
| rx_sz = ma->os_data.segments[i].end - ma->os_data.segments[i].start; |
| break; |
| } |
| } |
| *code_size = rx_sz; |
| } |
| if (file_version != NULL) { |
| /* TODO: NYI: make windows-only everywhere if no good linux source */ |
| *file_version = 0; |
| } |
| } |
| |
| if (name == NULL) |
| os_get_module_info_unlock(); |
| return (ma != NULL); |
| } |
| |
| bool |
| os_get_module_info_all_names(const app_pc pc, uint *checksum, uint *timestamp, |
| size_t *size, module_names_t **names, size_t *code_size, |
| uint64 *file_version) |
| { |
| ASSERT_NOT_IMPLEMENTED(false); |
| return false; |
| } |
| |
| # if defined(RETURN_AFTER_CALL) || defined(RCT_IND_BRANCH) |
| extern rct_module_table_t rct_global_table; |
| |
| /* Caller must hold module_data_lock */ |
| rct_module_table_t * |
| os_module_get_rct_htable(app_pc pc, rct_type_t which) |
| { |
| /* XXX: until we have a module list we use global rct and rac tables */ |
| if (which == RCT_RCT) |
| return &rct_global_table; |
| return NULL; /* we use rac_non_module_table */ |
| } |
| # endif |
| |
| /* Adds an entry for a segment to the out_data->segments array */ |
| void |
| module_add_segment_data(DR_PARAM_OUT os_module_data_t *out_data, |
| uint num_segments /*hint only*/, app_pc segment_start, |
| size_t segment_size, uint segment_prot, /* MEMPROT_ */ |
| size_t alignment, bool shared, uint64 offset) |
| { |
| uint seg, i; |
| LOG(GLOBAL, LOG_INTERP | LOG_VMAREAS, 3, "%s: #=%d " PFX "-" PFX " 0x%x\n", |
| __FUNCTION__, out_data->num_segments, segment_start, segment_start + segment_size, |
| segment_prot); |
| if (out_data->alignment == 0) { |
| out_data->alignment = alignment; |
| } else { |
| /* We expect all segments to have the same alignment for ELF. */ |
| IF_LINUX(ASSERT_CURIOSITY(out_data->alignment == alignment)); |
| } |
| /* Add segments to the module vector (i#160/PR 562667). |
| * For !HAVE_MEMINFO we should combine w/ the segment |
| * walk done in dl_iterate_get_areas_cb(). |
| */ |
| if (out_data->num_segments + 1 >= out_data->alloc_segments) { |
| /* over-allocate to avoid 2 passes to count PT_LOAD */ |
| uint newsz; |
| module_segment_t *newmem; |
| if (out_data->alloc_segments == 0) |
| newsz = (num_segments == 0 ? 4 : num_segments); |
| else |
| newsz = out_data->alloc_segments * 2; |
| newmem = (module_segment_t *)HEAP_ARRAY_ALLOC(GLOBAL_DCONTEXT, module_segment_t, |
| newsz, ACCT_OTHER, PROTECTED); |
| if (out_data->alloc_segments > 0) { |
| memcpy(newmem, out_data->segments, |
| out_data->alloc_segments * sizeof(*out_data->segments)); |
| HEAP_ARRAY_FREE(GLOBAL_DCONTEXT, out_data->segments, module_segment_t, |
| out_data->alloc_segments, ACCT_OTHER, PROTECTED); |
| } |
| out_data->segments = newmem; |
| out_data->alloc_segments = newsz; |
| out_data->contiguous = true; |
| } |
| /* Keep array sorted in addr order. I'm assuming segments are disjoint! */ |
| for (i = 0; i < out_data->num_segments; i++) { |
| if (out_data->segments[i].start > segment_start) |
| break; |
| } |
| seg = i; |
| /* Shift remaining entries */ |
| for (i = out_data->num_segments; i > seg; i--) { |
| out_data->segments[i] = out_data->segments[i - 1]; |
| } |
| out_data->num_segments++; |
| ASSERT(out_data->num_segments <= out_data->alloc_segments); |
| # ifdef MACOS |
| /* Some libraries have sub-page segments so do not page-align. We assume |
| * these are already aligned. |
| */ |
| out_data->segments[seg].start = segment_start; |
| out_data->segments[seg].end = segment_start + segment_size; |
| # else |
| /* ELF requires p_vaddr to already be aligned to p_align */ |
| out_data->segments[seg].start = (app_pc)ALIGN_BACKWARD(segment_start, PAGE_SIZE); |
| out_data->segments[seg].end = |
| (app_pc)ALIGN_FORWARD(segment_start + segment_size, PAGE_SIZE); |
| # endif |
| out_data->segments[seg].prot = segment_prot; |
| out_data->segments[seg].shared = shared; |
| out_data->segments[seg].offset = offset; |
| if (seg > 0) { |
| ASSERT(out_data->segments[seg].start >= out_data->segments[seg - 1].end); |
| if (out_data->segments[seg].start > out_data->segments[seg - 1].end) |
| out_data->contiguous = false; |
| } |
| if (seg < out_data->num_segments - 1) { |
| ASSERT(out_data->segments[seg + 1].start >= out_data->segments[seg].end); |
| if (out_data->segments[seg + 1].start > out_data->segments[seg].end) |
| out_data->contiguous = false; |
| } |
| } |
| |
| /* Returns true if the module has an nth segment, false otherwise. */ |
| bool |
| module_get_nth_segment(app_pc module_base, uint n, app_pc *start /*OPTIONAL OUT*/, |
| app_pc *end /*OPTIONAL OUT*/, uint *chars /*OPTIONAL OUT*/) |
| { |
| module_area_t *ma; |
| bool res = false; |
| if (!is_module_list_initialized()) |
| return false; |
| os_get_module_info_lock(); |
| ma = module_pc_lookup(module_base); |
| if (ma != NULL && n < ma->os_data.num_segments) { |
| LOG(GLOBAL, LOG_INTERP | LOG_VMAREAS, 3, "%s: [" PFX "-" PFX ") %x\n", |
| __FUNCTION__, ma->os_data.segments[n].start, ma->os_data.segments[n].end, |
| ma->os_data.segments[n].prot); |
| if (start != NULL) |
| *start = ma->os_data.segments[n].start; |
| if (end != NULL) |
| *end = ma->os_data.segments[n].end; |
| if (chars != NULL) |
| *chars = ma->os_data.segments[n].prot; |
| res = true; |
| } |
| os_get_module_info_unlock(); |
| return res; |
| } |
| |
| #endif /* !NOT_DYNAMORIO_CORE_PROPER */ |
| |
| /* XXX: We could implement import iteration of PE files in Wine, so we provide |
| * these stubs. |
| */ |
| dr_module_import_iterator_t * |
| dr_module_import_iterator_start(module_handle_t handle) |
| { |
| CLIENT_ASSERT(false, |
| "No imports on Linux, use " |
| "dr_symbol_import_iterator_t instead"); |
| return NULL; |
| } |
| |
| bool |
| dr_module_import_iterator_hasnext(dr_module_import_iterator_t *iter) |
| { |
| return false; |
| } |
| |
| dr_module_import_t * |
| dr_module_import_iterator_next(dr_module_import_iterator_t *iter) |
| { |
| return NULL; |
| } |
| |
| void |
| dr_module_import_iterator_stop(dr_module_import_iterator_t *iter) |
| { |
| } |
| |
| bool |
| at_dl_runtime_resolve_ret(dcontext_t *dcontext, app_pc source_fragment, int *ret_imm) |
| { |
| /* source_fragment is the start pc of the fragment to be run under DR |
| * offset is the location of the address dl_runtime_resolve |
| * will return to, relative to xsp |
| */ |
| |
| /* It works for the UNIX loader hack in _dl_runtime_resolve */ |
| /* The offending sequence in ld-linux.so is |
| * <_dl_runtime_resolve>: |
| * c270: 5a pop %edx |
| * c271: 59 pop %ecx |
| * c272: 87 04 24 xchg %eax,(%esp) |
| * c275: c2 08 00 ret $0x8 |
| */ |
| /* The same code also is in 0000c280 <_dl_runtime_profile> |
| * It maybe that either one or the other is ever used. |
| * Although performancewise this pattern matching is very cheap, |
| * for stricter security we assume only one is used in a session. |
| */ |
| /* XXX: This may change with future versions of libc, tested on |
| * RH8 and RH9 only. Also works for whatever libc was in ubuntu 7.10. |
| */ |
| /* However it does not work for ubuntu 8.04 where the code sequence has |
| * changed to the still similar : |
| * 2c50: 5a pop %edx |
| * 2c51: 8b 0c 24 mov (%esp) -> %ecx |
| * 2c54: 89 04 24 mov %eax -> (%esp) |
| * 2c57: 8b 44 24 04 mov 0x04(%esp) -> %eax |
| * 2c5b: c2 0c 00 ret $0xc |
| * So we check for that sequence too. |
| */ |
| static const byte DL_RUNTIME_RESOLVE_MAGIC_1[8] = |
| /* pop edx, pop ecx; xchg eax, (esp) ret 8 */ |
| { 0x5a, 0x59, 0x87, 0x04, 0x24, 0xc2, 0x08, 0x00 }; |
| static const byte DL_RUNTIME_RESOLVE_MAGIC_2[14] = |
| /* pop edx, mov (esp)->ecx, mov eax->(esp), mov 4(esp)->eax, ret 12 */ |
| { 0x5a, 0x8b, 0x0c, 0x24, 0x89, 0x04, 0x24, |
| 0x8b, 0x44, 0x24, 0x04, 0xc2, 0x0c, 0x00 }; |
| byte buf[MAX(sizeof(DL_RUNTIME_RESOLVE_MAGIC_1), |
| sizeof(DL_RUNTIME_RESOLVE_MAGIC_2))] = { 0 }; |
| |
| if (d_r_safe_read(source_fragment, sizeof(DL_RUNTIME_RESOLVE_MAGIC_1), buf) && |
| memcmp(buf, DL_RUNTIME_RESOLVE_MAGIC_1, sizeof(DL_RUNTIME_RESOLVE_MAGIC_1)) == |
| 0) { |
| *ret_imm = 0x8; |
| return true; |
| } |
| if (d_r_safe_read(source_fragment, sizeof(DL_RUNTIME_RESOLVE_MAGIC_2), buf) && |
| memcmp(buf, DL_RUNTIME_RESOLVE_MAGIC_2, sizeof(DL_RUNTIME_RESOLVE_MAGIC_2)) == |
| 0) { |
| LOG(THREAD, LOG_INTERP, 1, |
| "RCT: KNOWN exception this is " |
| "_dl_runtime_resolve --ok \n"); |
| *ret_imm = 0xc; |
| return true; |
| } |
| return false; |
| } |
| |
| bool |
| module_file_is_module64(file_t f, bool *is64 DR_PARAM_OUT, bool *also32 DR_PARAM_OUT) |
| { |
| dr_platform_t platform, alt_platform; |
| if (module_get_platform(f, &platform, &alt_platform)) { |
| *is64 = (platform == DR_PLATFORM_64BIT); |
| if (also32 != NULL) |
| *also32 = (platform == DR_PLATFORM_32BIT); |
| return true; |
| } |
| return false; |
| } |
| |
| bool |
| module_contains_addr(module_area_t *ma, app_pc pc) |
| { |
| if (ma->os_data.contiguous) |
| return (pc >= ma->start && pc < ma->end); |
| else { |
| uint i; |
| ASSERT(ma->os_data.num_segments > 0 && ma->os_data.segments != NULL); |
| for (i = 0; i < ma->os_data.num_segments; i++) { |
| if (pc >= ma->os_data.segments[i].start && pc < ma->os_data.segments[i].end) |
| return true; |
| } |
| } |
| return false; |
| } |