| /* |
| * This file is part of ltrace. |
| * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. |
| * Copyright (C) 2012 Edgar E. Iglesias, Axis Communications |
| * Copyright (C) 2008,2009 Juan Cespedes |
| * Copyright (C) 2006 Eric Vaitl, Cisco Systems, Inc. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of the |
| * License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA |
| * 02110-1301 USA |
| */ |
| |
| #include <string.h> |
| #include <errno.h> |
| #include <gelf.h> |
| #include <sys/ptrace.h> |
| |
| #include "common.h" |
| #include "debug.h" |
| #include "proc.h" |
| #include "library.h" |
| #include "breakpoint.h" |
| #include "backend.h" |
| |
| /** |
| \addtogroup mips |
| @{ |
| */ |
| |
| /* Are we in pure CPIC mode (the non-PIC ABI extension)? */ |
| static inline int |
| mips_elf_is_cpic(unsigned int elf_flags) |
| { |
| return (elf_flags & (EF_MIPS_PIC | EF_MIPS_CPIC)) == EF_MIPS_CPIC; |
| } |
| |
| /** |
| \param lte Structure containing link table entry information |
| \param ndx Index into .dynsym |
| \param rela Not used. |
| \return Address of GOT table entry |
| |
| MIPS ABI Supplement: |
| |
| DT_PLTGOT This member holds the address of the .got section. |
| |
| DT_MIPS_SYMTABNO This member holds the number of entries in the |
| .dynsym section. |
| |
| DT_MIPS_LOCAL_GOTNO This member holds the number of local global |
| offset table entries. |
| |
| DT_MIPS_GOTSYM This member holds the index of the first dyamic |
| symbol table entry that corresponds to an entry in the gobal offset |
| table. |
| |
| Called by read_elf when building the symbol table. |
| |
| */ |
| GElf_Addr |
| arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela *rela) |
| { |
| debug(1,"plt_addr %zx ndx %#zx",lte->arch.pltgot_addr, ndx); |
| |
| if (mips_elf_is_cpic(lte->ehdr.e_flags)) { |
| /* Return a pointer into the PLT. */ |
| return lte->plt_addr + 16 * 2 + (ndx * 16); |
| } |
| |
| /* Return a pointer to a GOT entry. */ |
| return lte->arch.pltgot_addr + |
| sizeof(void *) * (lte->arch.mips_local_gotno |
| + (ndx - lte->arch.mips_gotsym)); |
| } |
| /** |
| \param proc The process to work on. |
| \param sym The library symbol. |
| \return What is at the got table address |
| |
| The return value should be the address to put the breakpoint at. |
| |
| On the mips the library_symbol.enter_addr is the .got addr for the |
| symbol and the breakpoint.addr is the actual breakpoint address. |
| |
| Other processors use a plt, the mips is "special" in that is uses |
| the .got for both function and data relocations. Prior to program |
| startup, return 0. |
| |
| \warning MIPS relocations are lazy. This means that the breakpoint |
| may move after the first call. Ltrace dictionary routines don't |
| have a delete and symbol is one to one with breakpoint, so if the |
| breakpoint changes I just add a new breakpoint for the new address. |
| */ |
| void * |
| sym2addr(struct process *proc, struct library_symbol *sym) |
| { |
| long ret; |
| |
| if (sym->arch.pltalways |
| || (!sym->arch.gotonly && sym->plt_type == LS_TOPLT_NONE)) { |
| return sym->enter_addr; |
| } |
| |
| if(!proc->pid){ |
| return 0; |
| } |
| ret=ptrace(PTRACE_PEEKTEXT, proc->pid, sym->enter_addr, 0); |
| if(ret==-1){ |
| ret =0; |
| } |
| return (void *)ret;; |
| } |
| |
| /* Address of run time loader map, used for debugging. */ |
| #define DT_MIPS_RLD_MAP 0x70000016 |
| int |
| arch_find_dl_debug(struct process *proc, arch_addr_t dyn_addr, |
| arch_addr_t *ret) |
| { |
| arch_addr_t rld_addr; |
| int r; |
| |
| /* MIPS puts the address of the r_debug structure into the |
| * DT_MIPS_RLD_MAP entry instead of into the DT_DEBUG entry. */ |
| r = proc_find_dynamic_entry_addr(proc, dyn_addr, |
| DT_MIPS_RLD_MAP, &rld_addr); |
| if (r == 0) { |
| if (umovebytes(proc, rld_addr, |
| ret, sizeof *ret) != sizeof *ret) { |
| r = -1; |
| } |
| } |
| return r; |
| } |
| |
| |
| /* |
| * MIPS doesn't have traditional got.plt entries with corresponding |
| * relocations. |
| * |
| * sym_index is an offset into the external GOT entries. Filter out |
| * stuff that are not functions. |
| */ |
| int |
| arch_get_sym_info(struct ltelf *lte, const char *filename, |
| size_t sym_index, GElf_Rela *rela, GElf_Sym *sym) |
| { |
| if (mips_elf_is_cpic(lte->ehdr.e_flags)) { |
| return gelf_getsym(lte->dynsym, ELF64_R_SYM(rela->r_info), |
| sym) != NULL ? 0 : -1; |
| } |
| |
| /* Fixup the offset. */ |
| sym_index += lte->arch.mips_gotsym; |
| |
| if (gelf_getsym(lte->dynsym, sym_index, sym) == NULL) |
| return -1; |
| |
| if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC) { |
| const char *name = lte->dynstr + sym->st_name; |
| debug(2, "sym %s not a function", name); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| MIPS ABI Supplement: |
| |
| DT_PLTGOT This member holds the address of the .got section. |
| |
| DT_MIPS_SYMTABNO This member holds the number of entries in the |
| .dynsym section. |
| |
| DT_MIPS_LOCAL_GOTNO This member holds the number of local global |
| offset table entries. |
| |
| DT_MIPS_GOTSYM This member holds the index of the first dyamic |
| symbol table entry that corresponds to an entry in the gobal offset |
| table. |
| |
| */ |
| int |
| arch_elf_init(struct ltelf *lte, struct library *lib) |
| { |
| Elf_Scn *scn; |
| GElf_Shdr shdr; |
| |
| /* FIXME: for CPIC we should really scan both GOT tables |
| * to pick up relocations to external functions. Right now |
| * function pointers from the main binary to external functions |
| * can't be traced in CPIC mode. */ |
| if (mips_elf_is_cpic(lte->ehdr.e_flags)) { |
| return 0; /* We are already done. */ |
| } |
| |
| if (elf_get_section_type(lte, SHT_DYNAMIC, &scn, &shdr) < 0 |
| || scn == NULL) { |
| fail: |
| fprintf(stderr, "Couldn't get SHT_DYNAMIC: %s", |
| elf_errmsg(-1)); |
| return -1; |
| } |
| |
| Elf_Data *data = elf_loaddata(scn, &shdr); |
| if (data == NULL) |
| goto fail; |
| |
| size_t j; |
| for (j = 0; j < shdr.sh_size / shdr.sh_entsize; ++j) { |
| GElf_Dyn dyn; |
| if (gelf_getdyn(data, j, &dyn) == NULL) |
| goto fail; |
| |
| if(dyn.d_tag == DT_PLTGOT) { |
| lte->arch.pltgot_addr = dyn.d_un.d_ptr; |
| } |
| if(dyn.d_tag == DT_MIPS_LOCAL_GOTNO){ |
| lte->arch.mips_local_gotno = dyn.d_un.d_val; |
| } |
| if(dyn.d_tag == DT_MIPS_GOTSYM){ |
| lte->arch.mips_gotsym = dyn.d_un.d_val; |
| } |
| } |
| |
| /* Tell the generic code how many dynamic trace:able symbols |
| * we've got. */ |
| /* BEGIN android-changed */ |
| /* TODO(mkayyash): Investigate a fix for missing relplt_count. */ |
| /* lte->relplt_count = lte->dynsym_count - lte->arch.mips_gotsym; */ |
| /* END android-changed */ |
| return 0; |
| } |
| |
| void |
| arch_elf_destroy(struct ltelf *lte) |
| { |
| } |
| |
| /* When functions return we check if the symbol needs an updated |
| breakpoint with the resolved address. */ |
| void arch_symbol_ret(struct process *proc, struct library_symbol *libsym) |
| { |
| struct breakpoint *bp; |
| arch_addr_t resolved_addr; |
| struct process *leader = proc->leader; |
| |
| /* Only deal with unresolved symbols. */ |
| if (libsym->arch.type != MIPS_PLT_UNRESOLVED) |
| return; |
| |
| /* Get out if we are always using the PLT. */ |
| if (libsym->arch.pltalways) |
| return; |
| |
| resolved_addr = sym2addr(proc, libsym); |
| libsym->arch.resolved_addr = (uintptr_t) resolved_addr; |
| libsym->arch.type = MIPS_PLT_RESOLVED; |
| |
| if (libsym->arch.stub_addr == libsym->arch.resolved_addr) { |
| /* Prelinked symbol. No need to add new breakpoint. */ |
| return; |
| } |
| |
| bp = malloc(sizeof (*bp)); |
| if (bp == NULL) { |
| fprintf(stderr, "Failed to allocate bp for %s\n", |
| libsym->name); |
| return; |
| } |
| |
| if (breakpoint_init(bp, leader, resolved_addr, libsym) < 0) |
| goto err; |
| |
| if (proc_add_breakpoint(leader, bp) < 0) { |
| breakpoint_destroy(bp); |
| goto err; |
| } |
| |
| if (breakpoint_turn_on(bp, leader) < 0) { |
| proc_remove_breakpoint(leader, bp); |
| breakpoint_destroy(bp); |
| goto err; |
| } |
| return; |
| err: |
| free(bp); |
| } |
| |
| static enum callback_status |
| cb_enable_breakpoint_sym(struct library_symbol *libsym, void *data) |
| { |
| struct process *proc = data; |
| arch_addr_t bp_addr; |
| |
| if (!libsym->arch.gotonly) |
| return CBS_CONT; |
| |
| /* Update state. */ |
| bp_addr = sym2addr(proc, libsym); |
| /* XXX The cast to uintptr_t should be removed when |
| * arch_addr_t becomes integral type. keywords: double cast. */ |
| libsym->arch.resolved_addr = (uintptr_t) bp_addr; |
| |
| if (libsym->arch.resolved_addr == 0) |
| /* FIXME: What does this mean? */ |
| return CBS_CONT; |
| |
| libsym->arch.type = MIPS_PLT_RESOLVED; |
| |
| /* Now, activate the symbol causing a breakpoint to be added. */ |
| if (proc_activate_delayed_symbol(proc, libsym) < 0) { |
| fprintf(stderr, "Failed to activate delayed sym %s\n", |
| libsym->name); |
| } |
| return CBS_CONT; |
| } |
| |
| static enum callback_status |
| cb_enable_breakpoint_lib(struct process *proc, struct library *lib, void *data) |
| { |
| library_each_symbol(lib, NULL, cb_enable_breakpoint_sym, proc); |
| return CBS_CONT; |
| } |
| |
| void arch_dynlink_done(struct process *proc) |
| { |
| proc_each_library(proc->leader, NULL, cb_enable_breakpoint_lib, NULL); |
| } |
| |
| enum plt_status |
| arch_elf_add_plt_entry(struct process *proc, struct ltelf *lte, |
| const char *a_name, GElf_Rela *rela, size_t ndx, |
| struct library_symbol **ret) |
| { |
| char *name = NULL; |
| int sym_index = ndx + lte->arch.mips_gotsym; |
| |
| struct library_symbol *libsym = malloc(sizeof(*libsym)); |
| if (libsym == NULL) |
| return PLT_FAIL; |
| |
| GElf_Addr addr = arch_plt_sym_val(lte, sym_index, 0); |
| |
| name = strdup(a_name); |
| if (name == NULL) { |
| fprintf(stderr, "%s: failed %s(%#llx): %s\n", __func__, |
| name, addr, strerror(errno)); |
| goto fail; |
| } |
| |
| /* XXX The double cast should be removed when |
| * arch_addr_t becomes integral type. */ |
| if (library_symbol_init(libsym, |
| (arch_addr_t) (uintptr_t) addr, |
| name, 1, LS_TOPLT_EXEC) < 0) { |
| fprintf(stderr, "%s: failed %s : %llx\n", __func__, name, addr); |
| goto fail; |
| } |
| |
| arch_addr_t bp_addr = sym2addr(proc, libsym); |
| /* XXX This cast should be removed when |
| * arch_addr_t becomes integral type. keywords: double cast. */ |
| libsym->arch.stub_addr = (uintptr_t) bp_addr; |
| |
| if (bp_addr == 0) { |
| /* Function pointers without PLT entries. */ |
| libsym->plt_type = LS_TOPLT_NONE; |
| libsym->arch.gotonly = 1; |
| libsym->arch.type = MIPS_PLT_UNRESOLVED; |
| |
| /* Delay breakpoint activation until the symbol gets |
| * resolved. */ |
| libsym->delayed = 1; |
| } else if (mips_elf_is_cpic(lte->ehdr.e_flags)) { |
| libsym->arch.pltalways = 1; |
| } |
| |
| *ret = libsym; |
| return PLT_OK; |
| |
| fail: |
| free(name); |
| free(libsym); |
| return PLT_FAIL; |
| } |
| |
| int |
| arch_library_symbol_init(struct library_symbol *libsym) |
| { |
| libsym->arch.pltalways = 0; |
| libsym->arch.gotonly = 0; |
| libsym->arch.type = MIPS_PLT_UNRESOLVED; |
| if (libsym->plt_type == LS_TOPLT_NONE) { |
| libsym->arch.type = MIPS_PLT_RESOLVED; |
| } |
| return 0; |
| } |
| |
| void |
| arch_library_symbol_destroy(struct library_symbol *libsym) |
| { |
| } |
| |
| int |
| arch_library_symbol_clone(struct library_symbol *retp, |
| struct library_symbol *libsym) |
| { |
| retp->arch = libsym->arch; |
| return 0; |
| } |
| |
| /**@}*/ |