1143 lines
31 KiB
C++
1143 lines
31 KiB
C++
// read Mach-O symbols
|
|
|
|
#include <pro.h>
|
|
#include <diskio.hpp>
|
|
#include "debmod.h"
|
|
#include "../../ldr/ar/ar.hpp"
|
|
#include "../../ldr/ar/aixar.hpp"
|
|
#include "../../ldr/ar/arcmn.cpp" // for is_ar_file
|
|
#define BUILD_DEBUGGER
|
|
#include "../../ldr/mach-o/common.cpp"
|
|
#include "../../ldr/mach-o/dsym.cpp"
|
|
#include "symmacho.hpp"
|
|
|
|
//--------------------------------------------------------------------------
|
|
macho_utils_t::macho_utils_t(debmod_t *_dbgmod, int _arch)
|
|
: dbgmod(_dbgmod),
|
|
arch(_arch),
|
|
addrsize(DEF_ADDRSIZE),
|
|
is64(false),
|
|
warned(false)
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void macho_utils_t::clear(void)
|
|
{
|
|
addrsize = DEF_ADDRSIZE;
|
|
is64 = false;
|
|
warned = false;
|
|
strcache.clear();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
int macho_utils_t::get_cputype(void) const
|
|
{
|
|
switch ( arch )
|
|
{
|
|
case PLFM_386:
|
|
return is64 ? CPU_TYPE_X86_64 : CPU_TYPE_I386;
|
|
case PLFM_ARM:
|
|
return is64 ? CPU_TYPE_ARM64 : CPU_TYPE_ARM;
|
|
default:
|
|
break;
|
|
}
|
|
return CPU_TYPE_ANY;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
size_t macho_utils_t::read_mem(ea_t ea, void *buf, size_t bufsize)
|
|
{
|
|
return dbgmod->dbg_read_memory(ea, buf, bufsize, NULL);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void macho_utils_t::update_bitness()
|
|
{
|
|
debapp_attrs_t attrs;
|
|
dbgmod->dbg_get_debapp_attrs(&attrs);
|
|
addrsize = attrs.addrsize;
|
|
is64 = addrsize > 4;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
linput_t *macho_utils_t::create_mem_input(ea_t base)
|
|
{
|
|
struct ida_local meminput_t : public generic_linput_t
|
|
{
|
|
ea_t base;
|
|
macho_utils_t *mu;
|
|
meminput_t(ea_t _base, macho_utils_t *_mu) : base(_base), mu(_mu)
|
|
{
|
|
// macho images in memory have indeterminate size.
|
|
// set it to the max possible size to keep anybody from complaining.
|
|
filesize = INT_MAX;
|
|
blocksize = 0;
|
|
}
|
|
virtual ssize_t idaapi read(qoff64_t off, void *buffer, size_t nbytes) override
|
|
{
|
|
return mu->read_mem(base+off, buffer, nbytes);
|
|
}
|
|
};
|
|
meminput_t *pmi = new meminput_t(base, this);
|
|
return create_generic_linput(pmi);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void macho_utils_t::get_ptr_value(ea_t *val, const uchar *buf) const
|
|
{
|
|
if ( addrsize == 8 )
|
|
*val = *(const uint64 *)buf;
|
|
else
|
|
*val = *(const uint32 *)buf;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static void visit_macho_segments(macho_file_t &mfile, macho_visitor_t &mv)
|
|
{
|
|
const segcmdvec_t &segcmds = mfile.get_segcmds();
|
|
const secvec_t §ions = mfile.get_sections();
|
|
|
|
for ( size_t i = 0, size = segcmds.size(); i < size; i++ )
|
|
{
|
|
const segment_command_64 &sg = segcmds[i];
|
|
if ( sg.vmsize == 0 )
|
|
continue;
|
|
|
|
qstring segname(sg.segname, sizeof(sg.segname));
|
|
if ( (mv.flags & MV_SEGMENTS) != 0 )
|
|
{
|
|
mv.visit_segment(
|
|
sg.vmaddr + mv.slide,
|
|
sg.vmaddr + sg.vmsize + mv.slide,
|
|
segname,
|
|
(sg.flags & VM_PROT_EXECUTE) != 0 || segname == SEG_TEXT);
|
|
}
|
|
|
|
if ( (mv.flags & MV_SECTIONS) != 0 )
|
|
{
|
|
// special check for the header
|
|
if ( sections.size() > 0
|
|
&& strneq(segcmds[i].segname, "__TEXT", 6)
|
|
&& segcmds[i].vmaddr < sections[0].addr )
|
|
{
|
|
mv.visit_section(
|
|
segcmds[i].vmaddr + mv.slide,
|
|
sections[0].addr + mv.slide,
|
|
"HEADER",
|
|
segname,
|
|
false);
|
|
}
|
|
|
|
// visit all sections in the segment
|
|
for ( size_t j = 0; j < sg.nsects; j++ )
|
|
{
|
|
const section_64 § = sections[mfile.get_seg2section(i) + j];
|
|
qstring sectname(sect.sectname, sizeof(sect.sectname));
|
|
mv.visit_section(
|
|
sect.addr + mv.slide,
|
|
sect.addr + sect.size + mv.slide,
|
|
sectname,
|
|
segname,
|
|
(sect.flags & (S_ATTR_PURE_INSTRUCTIONS|S_ATTR_SOME_INSTRUCTIONS)) != 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static void visit_macho_symbols(
|
|
const nlistvec_t &symbols,
|
|
const qstring &strings,
|
|
macho_visitor_t &mv,
|
|
int cputype)
|
|
{
|
|
for ( size_t i=0; i < symbols.size(); i++ )
|
|
{
|
|
const struct nlist_64 &nl = symbols[i];
|
|
if ( nl.n_un.n_strx > strings.size() )
|
|
continue;
|
|
|
|
const char *name = &strings[nl.n_un.n_strx];
|
|
if ( qstrlen(name) == 0 )
|
|
continue;
|
|
|
|
ea_t ea = nl.n_value + mv.slide;
|
|
|
|
int type = nl.n_type & N_TYPE;
|
|
switch ( type )
|
|
{
|
|
case N_UNDF:
|
|
case N_PBUD:
|
|
case N_ABS:
|
|
case N_INDR:
|
|
break;
|
|
case N_SECT:
|
|
// process exported and private symbols
|
|
if ( ((nl.n_type & (N_EXT|N_PEXT)) == N_EXT) || nl.n_sect != NO_SECT )
|
|
{
|
|
// only set thumb-ness for arm32 symbols in the __text section (n_sect=1)
|
|
if ( cputype == CPU_TYPE_ARM && nl.n_sect == 1 )
|
|
mv.handle_thumb(ea, name, (nl.n_desc & 0xF) == N_ARM_THUMB_DEF);
|
|
|
|
mv.visit_symbol(ea, name);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static void visit_macho_function_starts(
|
|
macho_file_t &mfile,
|
|
macho_visitor_t &mv,
|
|
int cputype)
|
|
{
|
|
struct ida_local symmacho_fsv_t : public function_starts_visitor_t
|
|
{
|
|
macho_visitor_t &mv;
|
|
int cputype;
|
|
|
|
symmacho_fsv_t(macho_visitor_t &_mv, int _cputype)
|
|
: mv(_mv), cputype(_cputype) {}
|
|
|
|
virtual int visit_start(uint64_t address) override
|
|
{
|
|
// create a debugger-friendly address
|
|
if ( cputype == CPU_TYPE_ARM && (address & 1) != 0 )
|
|
address ^= 1;
|
|
mv.visit_function_start(address + mv.slide);
|
|
return 0;
|
|
}
|
|
virtual void handle_error() override
|
|
{
|
|
mv.handle_function_start_error();
|
|
}
|
|
};
|
|
|
|
symmacho_fsv_t fsv(mv, cputype);
|
|
mfile.visit_function_starts(fsv);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static void visit_macho_uuid(macho_file_t &mfile, macho_visitor_t &mv)
|
|
{
|
|
uint8 uuid[16];
|
|
if ( mfile.get_uuid(uuid) )
|
|
mv.uuid = bytevec_t(uuid, sizeof(uuid));
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool macho_utils_t::parse_macho_file(
|
|
const char *path,
|
|
ea_t base,
|
|
macho_visitor_t &mv,
|
|
const bytevec_t &uuid) const
|
|
{
|
|
linput_t *li = open_linput(path, false);
|
|
if ( li == NULL )
|
|
return false;
|
|
linput_janitor_t janitor(li);
|
|
if ( !match_macho_uuid(li, uuid) )
|
|
return false;
|
|
return parse_macho_input(li, base, mv);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static bool get_platform_version(
|
|
macho_visitor_t &mv,
|
|
const macho_platform_version_t &mpv)
|
|
{
|
|
if ( mpv.plfm != 0 && mpv.major != 0 )
|
|
{
|
|
if ( mpv.plfm == PLATFORM_MACOS )
|
|
mv.version = mpv.minor; // major is always 10 for OSX, which is assumed
|
|
else
|
|
mv.version = mpv.major;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static bool visit_macho_platform_info(macho_file_t &mfile, macho_visitor_t &mv)
|
|
{
|
|
macho_platform_version_info_t mpvi;
|
|
if ( !mfile.get_platform_version_info(&mpvi) )
|
|
return false;
|
|
|
|
// platform version could be encoded in multiple different load commands.
|
|
// the sdk versions usually provide the most accurate info.
|
|
return get_platform_version(mv, mpvi.build_sdk)
|
|
|| get_platform_version(mv, mpvi.min_sdk)
|
|
|| get_platform_version(mv, mpvi.build_minos)
|
|
|| get_platform_version(mv, mpvi.min_version);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static bool parse_macho_common(
|
|
macho_file_t &mfile,
|
|
macho_visitor_t &mv,
|
|
ea_t base,
|
|
int cputype)
|
|
{
|
|
if ( !mfile.parse_header() || !mfile.select_subfile(cputype) )
|
|
return false;
|
|
|
|
mv.subtype = mfile.get_mach_header().filetype;
|
|
|
|
ea_t maxea = 0;
|
|
ea_t imagebase = BADADDR;
|
|
const segcmdvec_t &segcmds = mfile.get_segcmds();
|
|
|
|
// since mac os x can scatter application segments over the memory
|
|
// we calculate only the text segment size
|
|
for ( size_t i = 0; i < segcmds.size(); i++ )
|
|
{
|
|
const segment_command_64 &sg = segcmds[i];
|
|
if ( is_text_segment(sg) )
|
|
{
|
|
if ( imagebase == BADADDR )
|
|
imagebase = sg.vmaddr;
|
|
ea_t end = sg.vmaddr + sg.vmsize;
|
|
if ( maxea < end )
|
|
maxea = end;
|
|
}
|
|
}
|
|
|
|
if ( imagebase == BADADDR )
|
|
return false;
|
|
|
|
mv.size = maxea - imagebase;
|
|
if ( base != BADADDR )
|
|
mv.slide = base - imagebase;
|
|
|
|
if ( (mv.flags & MV_UUID) != 0 )
|
|
visit_macho_uuid(mfile, mv);
|
|
|
|
if ( (mv.flags & MV_FUNCTION_STARTS) != 0 )
|
|
visit_macho_function_starts(mfile, mv, cputype);
|
|
|
|
if ( (mv.flags & (MV_SEGMENTS|MV_SECTIONS)) != 0 )
|
|
visit_macho_segments(mfile, mv);
|
|
|
|
if ( (mv.flags & MV_PLATFORM_INFO) != 0 )
|
|
visit_macho_platform_info(mfile, mv);
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool macho_utils_t::parse_macho_input(
|
|
linput_t *li,
|
|
ea_t base,
|
|
macho_visitor_t &mv) const
|
|
{
|
|
macho_file_t mfile(li);
|
|
int cputype = get_cputype();
|
|
|
|
if ( !parse_macho_common(mfile, mv, base, cputype) )
|
|
return false;
|
|
|
|
if ( (mv.flags & MV_SYMBOLS) != 0 )
|
|
{
|
|
qstring strings;
|
|
nlistvec_t symbols;
|
|
mfile.get_symbol_table_info(&symbols, &strings);
|
|
visit_macho_symbols(symbols, strings, mv, cputype);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool macho_utils_t::parse_macho_mem(ea_t base, macho_visitor_t &mv, uint32 hints)
|
|
{
|
|
linput_t *li = create_mem_input(base);
|
|
if ( li == NULL )
|
|
return false;
|
|
|
|
linput_janitor_t janitor(li);
|
|
|
|
macho_file_t mfile(li, 0, MACHO_HINT_MEM_IMAGE | hints);
|
|
int cputype = get_cputype();
|
|
|
|
if ( !parse_macho_common(mfile, mv, base, cputype) )
|
|
return false;
|
|
|
|
if ( (mv.flags & MV_SYMBOLS) != 0 )
|
|
{
|
|
struct symtab_command st = { 0 };
|
|
if ( !mfile.get_symtab_command(&st) )
|
|
return false;
|
|
|
|
nlistvec_t symbols;
|
|
mfile.get_symbol_table(st, &symbols);
|
|
|
|
// check if this is a new string table
|
|
strings_cache_t::const_iterator i = strcache.find(st.stroff);
|
|
if ( i == strcache.end() )
|
|
{
|
|
qstring buf;
|
|
mfile.get_string_table(st, &buf);
|
|
const qstring &strings = strcache.insert(std::make_pair(st.stroff, buf)).first->second;
|
|
visit_macho_symbols(symbols, strings, mv, cputype);
|
|
}
|
|
else
|
|
{
|
|
// if not, use the existing one
|
|
visit_macho_symbols(symbols, i->second, mv, cputype);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool macho_utils_t::calc_macho_uuid(bytevec_t *out, linput_t *li) const
|
|
{
|
|
uint8 uuid[16];
|
|
macho_file_t mfile(li);
|
|
|
|
if ( mfile.parse_header()
|
|
&& mfile.select_subfile(get_cputype())
|
|
&& mfile.get_uuid(uuid) )
|
|
{
|
|
*out = bytevec_t(uuid, sizeof(uuid));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool macho_utils_t::match_macho_uuid(linput_t *li, const bytevec_t &uuid) const
|
|
{
|
|
macho_file_t mfile(li);
|
|
|
|
return mfile.parse_header()
|
|
&& mfile.select_subfile(get_cputype())
|
|
&& mfile.match_uuid(uuid);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool macho_utils_t::is_exe_header(ea_t base)
|
|
{
|
|
macho_visitor_t v;
|
|
parse_macho_mem(base, v);
|
|
return v.subtype == MH_EXECUTE;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool macho_utils_t::calc_image_info(
|
|
uint32 *subtype,
|
|
asize_t *size,
|
|
bytevec_t *uuid,
|
|
linput_t *li,
|
|
ea_t base) const
|
|
{
|
|
macho_visitor_t v(MV_UUID);
|
|
if ( !parse_macho_input(li, base, v) )
|
|
return false;
|
|
|
|
if ( subtype != NULL )
|
|
*subtype = v.subtype;
|
|
if ( size != NULL )
|
|
*size = v.size;
|
|
if ( uuid != NULL )
|
|
*uuid = v.uuid;
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool macho_utils_t::calc_image_info(
|
|
uint32 *subtype,
|
|
asize_t *size,
|
|
bytevec_t *uuid,
|
|
const char *path) const
|
|
{
|
|
linput_t *li = open_linput(path, false);
|
|
if ( li == NULL )
|
|
return false;
|
|
bool ok = calc_image_info(subtype, size, uuid, li, BADADDR);
|
|
close_linput(li);
|
|
return ok;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool macho_utils_t::calc_image_info(
|
|
uint32 *subtype,
|
|
asize_t *size,
|
|
bytevec_t *uuid,
|
|
ea_t base)
|
|
{
|
|
linput_t *li = create_mem_input(base);
|
|
if ( li == NULL )
|
|
return false;
|
|
bool ok = calc_image_info(subtype, size, uuid, li, base);
|
|
close_linput(li);
|
|
return ok;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
qstring macho_utils_t::expand_home_dir(const char *path)
|
|
{
|
|
qstring home;
|
|
qstring tmp(path);
|
|
|
|
char buf[QMAXPATH];
|
|
if ( tmp.length() > 0 && tmp[0] == '~' && qgetenv("HOME", &home) )
|
|
return qmakepath(buf, sizeof(buf), home.c_str(), tmp.substr(1).c_str(), NULL);
|
|
|
|
return path;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// merge 2 range sets
|
|
// high priority overrides what is defined in low priority
|
|
// FIXME: this function was stolen from ui/memregs.cpp. Since memregs.cpp is
|
|
// part of the ui, we can't use it when building the mac_server, and I don't
|
|
// think this code is worth moving into dumb.a.
|
|
void macho_utils_t::merge(
|
|
meminfo_vec_t &res,
|
|
const meminfo_vec_t &low,
|
|
const meminfo_vec_t &high)
|
|
{
|
|
if ( low.empty() )
|
|
{
|
|
res = high;
|
|
return;
|
|
}
|
|
|
|
if ( high.empty() )
|
|
{
|
|
res = low;
|
|
return;
|
|
}
|
|
|
|
int l = 0;
|
|
meminfo_vec_t tmp;
|
|
ea_t end = 0;
|
|
ea_t lea = low[0].start_ea;
|
|
for ( int h=0; ; h++ )
|
|
{
|
|
ea_t hea = h < high.size() ? high[h].start_ea : BADADDR;
|
|
if ( hea != end )
|
|
{
|
|
while ( l < low.size() && lea < hea )
|
|
{ // add ranges before the high priority range
|
|
if ( low[l].end_ea > end )
|
|
{
|
|
tmp.push_back(low[l]);
|
|
if ( end >= lea )
|
|
tmp.back().start_ea = end;
|
|
end = low[l].end_ea;
|
|
if ( end > hea )
|
|
{
|
|
end = lea = hea;
|
|
tmp.back().end_ea = end;
|
|
break;
|
|
}
|
|
}
|
|
l++;
|
|
if ( l < low.size() )
|
|
lea = low[l].start_ea;
|
|
}
|
|
}
|
|
if ( h == high.size() )
|
|
break;
|
|
tmp.push_back(high[h]);
|
|
end = high[h].end_ea;
|
|
}
|
|
tmp.swap(res);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
dyld_utils_t::dyld_utils_t(debmod_t *_dbgmod, int _arch)
|
|
: inherited(_dbgmod, _arch)
|
|
{
|
|
base_ea = BADADDR;
|
|
entry_ea = BADADDR;
|
|
infos_ea = BADADDR;
|
|
ranges_ea = BADADDR;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void dyld_utils_t::clear(void)
|
|
{
|
|
inherited::clear();
|
|
infos.clear();
|
|
shared_cache_ranges.clear();
|
|
base_ea = BADADDR;
|
|
entry_ea = BADADDR;
|
|
infos_ea = BADADDR;
|
|
ranges_ea = BADADDR;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void dyld_all_image_infos_t::clear()
|
|
{
|
|
version = 0;
|
|
num_info = 0;
|
|
info_array = 0;
|
|
dyld_notify = 0;
|
|
dyld_image_load_address = 0;
|
|
dyld_image_infos_address = 0;
|
|
shared_cache_slide = 0;
|
|
shared_cache_base_address = 0;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool dyld_utils_t::untag(ea_t *ea) const
|
|
{
|
|
if ( !is64 )
|
|
return false;
|
|
|
|
uint64 mask = 0;
|
|
switch ( arch )
|
|
{
|
|
case PLFM_ARM:
|
|
// MACH_VM_MAX_ADDRESS = 0x0000000FC0000000, tag stored in bits 36-63
|
|
mask = uint64(0xFFFFFFF) << 36;
|
|
break;
|
|
case PLFM_386:
|
|
// MACH_VM_MAX_ADDRESS = 0x00007FFFFFE00000, tag stored in bits 48-63
|
|
mask = uint64(0xFFFF) << 48;
|
|
break;
|
|
}
|
|
|
|
ea_t orig = *ea;
|
|
ea_t addr = uint64(orig) & ~mask;
|
|
if ( addr != orig )
|
|
{
|
|
*ea = addr;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool dyld_utils_t::update_infos()
|
|
{
|
|
// read the important fields from _dyld_all_image_infos in a version-independent
|
|
// and bitness-independent manner.
|
|
infos.clear();
|
|
if ( infos_ea == BADADDR )
|
|
return false;
|
|
ea_t off = infos_ea;
|
|
if ( !read(off, &infos.version, sizeof(infos.version)) || infos.version < 1 )
|
|
return false;
|
|
off += sizeof(infos.version);
|
|
if ( !read(off, &infos.num_info, sizeof(infos.num_info)) )
|
|
return false;
|
|
off += sizeof(infos.num_info);
|
|
if ( !read(off, &infos.info_array, addrsize) )
|
|
return false;
|
|
off += addrsize;
|
|
if ( !read(off, &infos.dyld_notify, addrsize) || infos.version < 2 )
|
|
return false;
|
|
off += addrsize // dyld_notify
|
|
+ sizeof(uint8) // processDetachedFromSharedRegion
|
|
+ sizeof(uint8) // libSystemInitialized
|
|
+ addrsize - 2; // padding
|
|
if ( !read(off, &infos.dyld_image_load_address, addrsize) || infos.version < 9 )
|
|
return false;
|
|
off += addrsize // dyldImageLoadAddress
|
|
+ addrsize // jitInfo
|
|
+ addrsize // dyldVersion
|
|
+ addrsize // errorMessage
|
|
+ addrsize // terminationFlags
|
|
+ addrsize // coreSymbolicationShmPage
|
|
+ addrsize // systemOrderFlag
|
|
+ addrsize // uuidArrayCount
|
|
+ addrsize; // uuidArray
|
|
if ( !read(off, &infos.dyld_image_infos_address, addrsize) )
|
|
return false;
|
|
// we will need to know the base address of the shared cache for OSX 10.13/iOS 11 and later
|
|
if ( infos.version >= 15 )
|
|
{
|
|
off += addrsize // dyldAllImageInfosAddress
|
|
+ addrsize // initialImageCount
|
|
+ addrsize // errorKind
|
|
+ addrsize // errorClientOfDylibPath
|
|
+ addrsize // errorTargetDylibPath
|
|
+ addrsize; // errorSymbol
|
|
if ( !read(off, &infos.shared_cache_slide, addrsize) )
|
|
return false;
|
|
off += addrsize // sharedCacheSlide
|
|
+ 16; // sharedCacheUUID
|
|
if ( !read(off, &infos.shared_cache_base_address, addrsize) )
|
|
return false;
|
|
}
|
|
// remove any tags from the pointer values
|
|
untag(&infos.info_array);
|
|
untag(&infos.dyld_notify);
|
|
untag(&infos.dyld_image_load_address);
|
|
untag(&infos.dyld_image_infos_address);
|
|
untag(&infos.shared_cache_base_address);
|
|
// it's possible that dyld has been relocated but the fields in dyld_all_image_infos
|
|
// haven't been updated yet - do it now.
|
|
if ( infos_ea != infos.dyld_image_infos_address )
|
|
{
|
|
adiff_t slide = infos_ea - infos.dyld_image_infos_address;
|
|
infos.dyld_image_load_address += slide;
|
|
infos.dyld_image_infos_address += slide;
|
|
infos.dyld_notify += slide;
|
|
infos.info_array += slide;
|
|
}
|
|
// gdb_image_notifier could be a thumb function - clear the thumb bit if needed
|
|
if ( arch == PLFM_ARM && !is64 && (infos.dyld_notify & 1) != 0 )
|
|
infos.dyld_notify ^= 1;
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool dyld_utils_t::update_ranges()
|
|
{
|
|
if ( !shared_cache_ranges.empty() )
|
|
return true;
|
|
|
|
if ( ranges_ea != BADADDR )
|
|
{
|
|
// parse the _dyld_shared_cache_ranges symbol in memory
|
|
ea_t count = 0;
|
|
if ( !read(ranges_ea, &count, addrsize) )
|
|
return false;
|
|
|
|
for ( ea_t i = 0, ptr = ranges_ea + addrsize; i < count; i++, ptr += 2 * addrsize )
|
|
{
|
|
ea_t start = 0;
|
|
if ( !read(ptr, &start, addrsize) )
|
|
return false;
|
|
|
|
ea_t size = 0;
|
|
if ( !read(ptr+addrsize, &size, addrsize) )
|
|
return false;
|
|
|
|
range_t r(start, start+size);
|
|
dbgmod->dmsg("shared cache range: %a..%a\n", r.start_ea, r.end_ea);
|
|
shared_cache_ranges.add(r);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// _dyld_shared_cache_ranges is no longer present in dyld for OSX 10.13/iOS 11.
|
|
// fall back to parsing the mappings in the dyld cache header.
|
|
dbgmod->debdeb("parsing cache header: version=%d, shared_cache_base_address=%a, shared_cache_slide=%a\n",
|
|
infos.version,
|
|
infos.shared_cache_base_address,
|
|
infos.shared_cache_slide);
|
|
|
|
struct ida_local mapping_visitor_t : public dyld_cache_visitor_t
|
|
{
|
|
debmod_t *dm;
|
|
rangeset_t *ranges;
|
|
|
|
mapping_visitor_t(debmod_t *_dm, rangeset_t *_ranges)
|
|
: dyld_cache_visitor_t(DCV_MAPPINGS), dm(_dm), ranges(_ranges) {}
|
|
|
|
virtual void visit_mapping(ea_t start_ea, ea_t end_ea) override
|
|
{
|
|
dm->dmsg("shared cache mapping: %a..%a\n", start_ea, end_ea);
|
|
ranges->add(range_t(start_ea, end_ea));
|
|
}
|
|
};
|
|
|
|
mapping_visitor_t mapv(dbgmod, &shared_cache_ranges);
|
|
parse_dyld_cache_header(mapv);
|
|
}
|
|
|
|
return !shared_cache_ranges.empty();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool dyld_utils_t::parse_info_array(uint32 count, ea_t info_array, dll_visitor_t &dv)
|
|
{
|
|
size_t bufsize = count * addrsize * 3; // 3 pointers per dyld_image_info element
|
|
|
|
bytevec_t buf;
|
|
buf.resize(bufsize);
|
|
|
|
if ( !read(info_array, buf.begin(), bufsize) )
|
|
return false;
|
|
|
|
const uchar *ptr = buf.begin();
|
|
const uchar *end = buf.begin() + bufsize;
|
|
|
|
for ( ; ptr < end; ptr += addrsize * 3 )
|
|
{
|
|
// dyld_image_info::addr
|
|
ea_t base = 0;
|
|
get_ptr_value(&base, ptr);
|
|
|
|
// dyld_image_info::name
|
|
ea_t name_ptr = 0;
|
|
get_ptr_value(&name_ptr, ptr+addrsize);
|
|
|
|
char name[1024] = { 0 };
|
|
read(name_ptr, name, sizeof(name)); // may fail because we don't know exact size
|
|
name[sizeof(name)-1] = '\0';
|
|
|
|
asize_t size = 0;
|
|
bytevec_t uuid;
|
|
calc_image_info(NULL, &size, &uuid, base);
|
|
|
|
dv.visit_dll(base, size, name, uuid);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
template <typename H> bool dyld_utils_t::is_dyld_header(
|
|
ea_t base,
|
|
char *filename,
|
|
size_t namesize,
|
|
uint32 magic)
|
|
{
|
|
H mh;
|
|
if ( !read(base, &mh, sizeof(mh)) )
|
|
return false;
|
|
|
|
if ( mh.magic != magic || mh.filetype != MH_DYLINKER )
|
|
return false;
|
|
|
|
// seems to be dylib
|
|
// find its file name
|
|
filename[0] = '\0';
|
|
ea_t ea = base + sizeof(mh);
|
|
for ( int i=0; i < mh.ncmds; i++ )
|
|
{
|
|
struct load_command lc;
|
|
lc.cmd = 0;
|
|
read(ea, &lc, sizeof(lc));
|
|
if ( lc.cmd == LC_ID_DYLIB )
|
|
{
|
|
struct dylib_command dcmd;
|
|
read(ea, &dcmd, sizeof(dcmd));
|
|
read(ea+dcmd.dylib.name.offset, filename, namesize);
|
|
break;
|
|
}
|
|
else if ( lc.cmd == LC_ID_DYLINKER )
|
|
{
|
|
struct dylinker_command dcmd;
|
|
read(ea, &dcmd, sizeof(dcmd));
|
|
read(ea+dcmd.name.offset, filename, namesize);
|
|
break;
|
|
}
|
|
ea += lc.cmdsize;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool dyld_utils_t::is_dyld_header_64(ea_t base, char *filename, size_t namesize)
|
|
{
|
|
return is_dyld_header<mach_header_64>(base, filename, namesize, MH_MAGIC_64);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool dyld_utils_t::is_dyld_header_32(ea_t base, char *filename, size_t namesize)
|
|
{
|
|
return is_dyld_header<mach_header>(base, filename, namesize, MH_MAGIC);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool dyld_utils_t::is_dyld_header(ea_t base, char *filename, size_t namesize)
|
|
{
|
|
return is64
|
|
? is_dyld_header_64(base, filename, namesize)
|
|
: is_dyld_header_32(base, filename, namesize);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool dyld_utils_t::parse_dyld_cache_header(dyld_cache_visitor_t &dcv)
|
|
{
|
|
ea_t base = infos.shared_cache_base_address;
|
|
if ( base == 0 )
|
|
return false;
|
|
|
|
linput_t *li = create_mem_input(base);
|
|
if ( li == NULL )
|
|
return false;
|
|
|
|
dyld_cache_t dyldcache(li);
|
|
linput_janitor_t janitor(li);
|
|
|
|
uint32 hflags = (dcv.flags & DCV_MAPPINGS) != 0 ? PHF_MAPPINGS : 0;
|
|
|
|
if ( !dyldcache.parse_header(hflags) )
|
|
return false;
|
|
|
|
ea_t slide = infos.shared_cache_slide;
|
|
|
|
if ( (dcv.flags & DCV_MAPPINGS) != 0 )
|
|
{
|
|
for ( int i = 0, nmaps = dyldcache.get_nummappings(); i < nmaps; i++ )
|
|
{
|
|
const dyld_cache_mapping_info &mi = dyldcache.get_mapping_info(i);
|
|
ea_t start_ea = mi.address + slide;
|
|
ea_t end_ea = mi.address + slide + mi.size;
|
|
dcv.visit_mapping(start_ea, end_ea);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool dyld_utils_t::parse_macho_mem(ea_t base, macho_visitor_t &mv, uint32 hints)
|
|
{
|
|
if ( is_shared_cache_lib(base) )
|
|
hints |= MACHO_HINT_SHARED_CACHE_LIB;
|
|
|
|
return inherited::parse_macho_mem(base, mv, hints);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool dyld_utils_t::get_symbol_file_path(qstring *path, const char *module) const
|
|
{
|
|
if ( symbol_path.empty() )
|
|
return false;
|
|
|
|
char buf[QMAXPATH];
|
|
qmakepath(buf, sizeof(buf), symbol_path.c_str(), module, NULL);
|
|
*path = inherited::expand_home_dir(buf);
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool dyld_utils_t::parse_local_symbol_file(
|
|
ea_t base,
|
|
const char *module,
|
|
const bytevec_t &uuid,
|
|
macho_visitor_t &mv)
|
|
{
|
|
qstring path;
|
|
if ( !get_symbol_file_path(&path, module) )
|
|
{
|
|
// we only show a warning for the iOS debugger, since it's a more serious issue.
|
|
// symbol loading is almost unusably slow when the local symbol cache is not present,
|
|
// so we make sure the user has a hint of what's wrong.
|
|
if ( arch == PLFM_ARM && !warned )
|
|
{
|
|
msg("WARNING: No path to local symbol cache specified. Symbol loading might be very slow. "
|
|
"You can set SYMBOL_PATH in the debugger's cfg file for faster and more detailed debugging.\n");
|
|
warned = true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
linput_t *li = open_linput(path.c_str(), false);
|
|
linput_janitor_t lij(li);
|
|
|
|
if ( li == NULL )
|
|
{
|
|
// It is normal that a library on the remote machine does not have a corresponding local symbol file.
|
|
// Still, we print a message in case the user is looking for symbols in this missing module,
|
|
// or if they have accidentally set SYMBOL_PATH in the cfg to a bogus directory - in which case
|
|
// a ton of these messages will be printed and it should be pretty obvious what is wrong.
|
|
msg("WARNING: symbol file not found %s.\n", path.c_str());
|
|
return false;
|
|
}
|
|
|
|
if ( !match_macho_uuid(li, uuid) )
|
|
{
|
|
// This is a pretty serious issue and we should print a warning message for the user.
|
|
// Symbol files extracted from different iOS/OSX machines may not be compatible, even if
|
|
// they have the exact same iOS version. This stumped me for a little while
|
|
// and it would have been nice if I had this message.
|
|
msg("WARNING: UUID mismatch for symbol file %s. "
|
|
"Please make sure SYMBOL_PATH in the debugger's cfg file points to symbols that are compatible with the remote machine.\n",
|
|
path.c_str());
|
|
return false;
|
|
}
|
|
|
|
return parse_macho_input(li, base, mv);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void kernel_utils_t::clear(void)
|
|
{
|
|
inherited::clear();
|
|
|
|
kdk_path.clear();
|
|
prelinked_kexts.clear();
|
|
prelink_data_off = 0;
|
|
|
|
if ( kcache_li != NULL )
|
|
close_linput(kcache_li);
|
|
|
|
kcache_li = NULL;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void kernel_utils_t::set_kdk_path(const qstring &path)
|
|
{
|
|
kdk_path = macho_utils_t::expand_home_dir(path.c_str());
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
struct callback_info_t
|
|
{
|
|
qstring path;
|
|
uint32 flags;
|
|
callback_info_t(uint32 _flags) : flags(_flags) {}
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
static int kdk_callback(
|
|
macho_file_t &,
|
|
void *ud,
|
|
const char *path,
|
|
const macho_ident_t &ident)
|
|
{
|
|
callback_info_t *info = (callback_info_t *)ud;
|
|
|
|
if ( (info->flags & KDK_SEARCH_KCACHE) != 0 && (ident.flags & AUF_IS_KCACHE) == 0 )
|
|
return -1; // not a kernelcache
|
|
|
|
if ( (info->flags & KDK_SEARCH_DSYM) != 0 )
|
|
{
|
|
struct ida_local lambda_t
|
|
{
|
|
static int accept(macho_file_t &, void *, const char *, const macho_ident_t &)
|
|
{
|
|
return 0; // accept file
|
|
}
|
|
};
|
|
if ( find_dsym_macho(path, ident, lambda_t::accept, NULL) != 0 )
|
|
return -1; // no companion dsym
|
|
}
|
|
|
|
info->path = path;
|
|
return 0;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool kernel_utils_t::find_kdk_file(
|
|
qstring *path,
|
|
int cpu_subtype,
|
|
const bytevec_t &uuid,
|
|
const char *name,
|
|
uint32 flags) const
|
|
{
|
|
if ( kdk_path.empty() )
|
|
return false;
|
|
|
|
if ( uuid.size() != sizeof(macho_ident_t::uuid) )
|
|
return false;
|
|
|
|
macho_ident_t ident;
|
|
ident.cpu_type = get_cputype();
|
|
ident.cpu_subtype = cpu_subtype;
|
|
|
|
memcpy(ident.uuid, uuid.begin(), sizeof(ident.uuid));
|
|
|
|
int code = -1;
|
|
callback_info_t info(flags);
|
|
const char *dir = kdk_path.c_str();
|
|
|
|
switch ( cpu_subtype )
|
|
{
|
|
case MH_EXECUTE:
|
|
code = find_dsym_kernel(dir, ident, kdk_callback, &info);
|
|
break;
|
|
case MH_KEXT_BUNDLE:
|
|
code = find_dsym_kext(dir, ident, name, kdk_callback, &info);
|
|
break;
|
|
}
|
|
|
|
if ( code != 0 )
|
|
return false;
|
|
|
|
if ( path != NULL )
|
|
*path = info.path;
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
uint64 kernel_utils_t::find_kext_offset(const bytevec_t &uuid) const
|
|
{
|
|
for ( size_t i = 0, size = prelinked_kexts.size(); i < size; i++ )
|
|
{
|
|
if ( prelinked_kexts[i].uuid == uuid )
|
|
return prelinked_kexts[i].off;
|
|
}
|
|
return BADADDR64;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool kernel_utils_t::parse_prelinked_kext(
|
|
macho_visitor_t &mv,
|
|
ea_t base,
|
|
const bytevec_t &uuid)
|
|
{
|
|
if ( kcache_li == NULL )
|
|
return false;
|
|
|
|
uint64 start_off = find_kext_offset(uuid);
|
|
if ( start_off == BADADDR64 )
|
|
return false;
|
|
|
|
kcache_single_kmod_input_t *kli = new kcache_single_kmod_input_t(
|
|
kcache_li,
|
|
start_off,
|
|
prelink_data_off);
|
|
|
|
linput_t *li = create_generic_linput(kli);
|
|
QASSERT(1750, li != NULL);
|
|
linput_janitor_t lij(li);
|
|
|
|
return parse_macho_input(li, base, mv);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool kernel_utils_t::parse_kcache(const bytevec_t &kernel_uuid)
|
|
{
|
|
// look for a matching kernelcache in the KDK
|
|
qstring path;
|
|
if ( !find_kdk_file(&path, MH_EXECUTE, kernel_uuid, NULL, KDK_SEARCH_KCACHE) )
|
|
return false;
|
|
|
|
kcache_li = open_linput(path.c_str(), false);
|
|
if ( kcache_li == NULL )
|
|
return false;
|
|
|
|
macho_file_t kcache(kcache_li);
|
|
if ( !kcache.parse_header() || !kcache.select_subfile(get_cputype()) )
|
|
return false;
|
|
|
|
dbgmod->dmsg("found kernelcache: %s\n", path.c_str());
|
|
|
|
// extract info about the prelinked kexts. we will use it later.
|
|
const kmod_params_vec_t &kpv = kcache.get_kmod_info();
|
|
|
|
for ( size_t i = 0, size = kpv.size(); i < size; i++ )
|
|
{
|
|
kext_info_t &info = prelinked_kexts.push_back();
|
|
info.uuid = bytevec_t(kpv[i].uuid, sizeof(kpv[i].uuid));
|
|
info.off = kpv[i].off;
|
|
}
|
|
|
|
section_64 s;
|
|
if ( kcache.get_prelink_data(&s) )
|
|
prelink_data_off = s.offset;
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool kernel_utils_t::find_kext(const bytevec_t &uuid, const char *kext_name) const
|
|
{
|
|
return find_kext_offset(uuid) != BADADDR64
|
|
|| find_kdk_file(NULL, MH_KEXT_BUNDLE, uuid, kext_name);
|
|
}
|