Created
May 21, 2024 01:43
-
-
Save dlevi309/4fbe67e7118aded41a6ef3205ee8d9fb to your computer and use it in GitHub Desktop.
Dumps Objective-C class/instance info at runtime
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// MIT License | |
// | |
// Copyright (c) 2024 Derek Selander | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in all | |
// copies or substantial portions of the Software. Attribution is requested but not | |
// required if Software is public facing | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// SOFTWARE. | |
// | |
// dynadump: | |
// Quick'n'Dirty imp of an ObjC class-dump primarily for inspecting dyld shared | |
// cache libraries on the host machine done via dlopen and public ObjC APIs. So hopefully, | |
// this code will better withstand breaking changes to objc/dyld internal changes. | |
// On ARM64, this will use hardware breakpoints to prevent the constructors from firing | |
// which will sometimes crash the process. On x86_64, this is disabled. | |
// | |
// to compile for jb'd iOS: | |
// xcrun -sdk iphoneos clang -fmodules -arch arm64 -Wl,-U,_dyld_shared_cache_for_each_image,-U,_dyld_image_path_containing_address -o /tmp/dynadump /path/to/this/file/main.m | |
// codesign -f -s - /tmp/dynadump | |
// | |
// to compile for macOS | |
// xcrun -sdk macos clang -arch arm64 -arch x86_64 -fmodules -o /usr/local/bin/dynadump /path/to/this/file/main.m -Wl,-U,_dyld_shared_cache_for_each_image,-U,_dyld_image_path_containing_address | |
// codesign -f -s - /usr/local/bin/dynadump | |
// | |
// to compile as a shared framework | |
// xcrun -sdk iphoneos clang -fmodules -o /tmp/dynadump.dylib /path/to/this/file/main.m -shared -DSHARED_DYNAMIC_DUMP | |
@import ObjectiveC; | |
@import Foundation; | |
@import Darwin; | |
@import OSLog; | |
@import MachO; | |
#import <mach-o/dyld_images.h> | |
/*********************************************************************/ | |
# pragma mark - defines - | |
/*********************************************************************/ | |
/// Use SHARED_DYNAMIC_DUMP to make a shared library | |
#ifndef SHARED_DYNAMIC_DUMP | |
#define DYNAMIC_DUMP_VISIBILITY static | |
#else | |
#define DYNAMIC_DUMP_VISIBILITY __attribute__((visibility("default"))) | |
#endif | |
#define DYLD_LOADER_CLASS_MAGIC 'l4yd' | |
#define GOOD_E_NUFF_BUFSIZE 1000 | |
#define do_copy_n_return(STR) { strcpy(buffer, (STR)); return 0; } | |
#define append_content(FORMAT, ...) { buff_offset += snprintf(buffer + buff_offset, GOOD_E_NUFF_BUFSIZE - buff_offset, FORMAT, ##__VA_ARGS__); } | |
#define ARM64_OPCODE_SIZE sizeof(uint32_t) | |
// error handling | |
#define HANDLE_ERR(E) {if ((E)) { log_error("Error: %d, %s \n", (E), mach_error_string((E)));}} | |
// stolen from objc4 | |
# if __arm64__ | |
// ARM64 simulators have a larger address space, so use the ARM64e | |
// scheme even when simulators build for ARM64-not-e. | |
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR | |
# define ISA_MASK 0x007ffffffffffff8ULL | |
# else | |
# define ISA_MASK 0x0000000ffffffff8ULL | |
# endif | |
# elif __x86_64__ | |
# define ISA_MASK 0x00007ffffffffff8ULL | |
# else | |
# error unknown architecture for packed isa | |
# endif | |
/// -DUSE_CONSOLE for NSLog, else fprintf | |
#ifdef USE_CONSOLE | |
#define log_out(S, ...) NSLog(@ S, ##__VA_ARGS__); | |
#define log_error(S, ...) if (g_debug) { NSLog(@ "ERR: %5d", __LINE__);} NSLog(@ S, ##__VA_ARGS__); | |
#define log_debug(S, ...) if (g_debug) {NSLog(@ "dbg %4d: " S, __LINE__, ##__VA_ARGS__); } | |
#else | |
#define log_out(S, ...) fprintf(stdout, S, ##__VA_ARGS__); | |
#define log_error(S, ...) if (g_debug) { fprintf(stderr, "ERR: %5d", __LINE__);} fprintf(stderr, S, ##__VA_ARGS__); | |
#define log_debug(S, ...) if (g_debug) { fprintf(stdout, "dbg %4d: " S, __LINE__, ##__VA_ARGS__); } | |
#endif | |
/// arm64 debug stuff | |
#define S_USER ((uint32_t)(2u << 1)) | |
#define BCR_ENABLE ((uint32_t)(1u)) | |
#define SS_ENABLE ((uint32_t)(1u)) | |
#define BCR_BAS ((uint32_t)(15u << 5)) | |
#define DCYAN (g_color ? "\e[36m" : "") | |
#define DYELLOW (g_color ? "\e[33m" : "") | |
#define DMAGENTA (g_color ? "\e[95m" : "") | |
#define DRED (g_color ? "\e[91m" : "") | |
#define DPURPLE (g_color ? "\e[35m" : "") | |
#define DBLUE (g_color ? "\e[34m" : "") | |
#define DGRAY (g_color ? "\e[90m" : "") | |
#define DGREEN (g_color ? "\e[92m" : "") | |
#define DDARK_GREEN (g_color ? "\e[32m" : "") | |
#define DBOLD (g_color ? "\e[1m" : "") | |
#define DCYAN_UNDERLINE (g_color ? "\033[36;1;4m" : "") | |
#define DPURPLE_BOLD (g_color ? "\e[35;1m" : "") | |
#define DCYAN_LIGHT (g_color ? "\e[96m" : "") | |
#define DYELLOW_LIGHT (g_color ? "\e[93m" : "") | |
#define DSTRONG_RED (g_color ? "\e[31;4m" : "") | |
#define DLIGHT_BLUE (g_color ? "\e[94m" : "") | |
#define DCOLOR_END (g_color ? "\e[0m" : "") | |
#define DMETHOD_COLOR DCYAN | |
#define DPUNC_COLOR DGRAY | |
#define DPARAM_COLOR DYELLOW | |
#if __has_feature(ptrauth_calls) | |
#define DO_SIGN(X) (void*)__builtin_ptrauth_sign_unauthenticated((X), ptrauth_key_asia, 0) | |
#define DO_PROC_SIGN(X) (void*)__builtin_ptrauth_sign_unauthenticated((X), ptrauth_key_asib, 0) | |
#define DO_STRIP(X) X = (__typeof__((X)))ptrauth_strip((void*)(X), 0); | |
#define DO_STRIP_OBJC(X) X = (__bridge id)ptrauth_strip((__bridge void*)(X), 0); | |
#else | |
#define DO_SIGN(X) (X) | |
#define DO_PROC_SIGN(X) (X) | |
#define DO_STRIP(X) | |
#define DO_STRIP_OBJC(X) | |
#endif | |
/*********************************************************************/ | |
# pragma mark - internal testing | |
/*********************************************************************/ | |
#if 0 | |
@interface TestInterface : NSObject | |
@property (nonatomic, strong) NSURL *someURL; | |
@end | |
@implementation TestInterface (MyCategory) | |
- (void)categoryMethod {} | |
@end | |
@implementation TestInterface | |
- (void)booopbeepbop{} | |
@end | |
#endif | |
/*********************************************************************/ | |
# pragma mark - dyld declarations - | |
/*********************************************************************/ | |
typedef struct dyld_shared_cache_s* dyld_shared_cache_t; | |
typedef struct dyld_image_s* dyld_image_t; | |
extern __attribute__((weak)) const char* dyld_image_path_containing_address(const void* addr); | |
extern __attribute__((weak)) void dyld_shared_cache_for_each_image(dyld_shared_cache_t cache, void (^block)(dyld_image_t image)); | |
extern __attribute__((weak)) const char* dyld_shared_cache_file_path(void); | |
extern __attribute__((weak)) bool dyld_shared_cache_for_file(const char* filePath, void (^block)(dyld_shared_cache_t cache)); | |
const char* my_dyld_image_get_installname(dyld_image_t image) { | |
extern __attribute__((weak)) const char* dyld_image_get_installname(dyld_image_t image); | |
if (dyld_image_get_installname) { | |
return dyld_image_get_installname(image); | |
} | |
return "???"; | |
} | |
extern __attribute__((weak)) const struct mach_header* dyld_image_header_containing_address(const void* addr); | |
/*********************************************************************/ | |
# pragma mark - struct declarations / globals - | |
/*********************************************************************/ | |
/// For unwinding stack frames in an exception handler | |
struct fp_ptr { | |
struct fp_ptr *next; | |
void* address; | |
}; | |
/// mig generated structs | |
#pragma pack(push, 4) | |
typedef struct { | |
mach_msg_header_t Head; | |
/* start of the kernel processed data */ | |
mach_msg_body_t msgh_body; | |
mach_msg_port_descriptor_t thread; | |
mach_msg_port_descriptor_t task; | |
/* end of the kernel processed data */ | |
NDR_record_t NDR; | |
exception_type_t exception; | |
mach_msg_type_number_t codeCnt; | |
int64_t code[2]; | |
} exc_req; | |
typedef struct { | |
mach_msg_header_t Head; | |
NDR_record_t NDR; | |
kern_return_t RetCode; | |
} exc_resp; | |
#pragma pack(pop) | |
static void* safe_dlopen(const char *image) ; | |
extern Class objc_opt_class(id _Nullable obj); | |
void* server_thread(void *arg); | |
static void dump_objc_class_info(Class cls); | |
static void dump_objc_protocol_info(Protocol *prot); | |
static bool g_debug = false; | |
static bool g_color = true; | |
static int g_verbose = 0; | |
static bool can_use_dyld_apis = true; | |
static uintptr_t constructor_addresses[10] = { }; | |
static uint8_t constructor_addresses_count = 0; | |
static mach_port_t exc_port = MACH_PORT_NULL; | |
static pthread_mutex_t exception_mutex = PTHREAD_MUTEX_INITIALIZER; | |
static pthread_cond_t exception_cond = PTHREAD_COND_INITIALIZER; | |
static uintptr_t dyld_header = 0; | |
/*********************************************************************/ | |
# pragma mark - internal - | |
/*********************************************************************/ | |
static void* strip_pac(void* addr) { | |
#if defined(__arm64__) | |
static uint32_t g_addressing_bits = 0; | |
if (g_addressing_bits == 0) { | |
size_t len = sizeof(uint32_t); | |
if (sysctlbyname("machdep.virtual_address_size", &g_addressing_bits, &len, | |
NULL, 0) != 0) { | |
g_addressing_bits = -1; // if err, f it, just assume anything goes | |
} | |
} | |
uintptr_t mask = ((1UL << g_addressing_bits) - 1) ; | |
return (void*)((uintptr_t)addr & mask); | |
#else | |
return addr; | |
#endif | |
} | |
__attribute__((constructor)) static void grab_caller_address(void) { | |
uintptr_t return_address = (uintptr_t)strip_pac((void*)__builtin_return_address(0)); | |
constructor_addresses[constructor_addresses_count++] = return_address - ARM64_OPCODE_SIZE; | |
if (getenv("DEBUG")) { | |
Dl_info info; | |
dladdr((void*)constructor_addresses[constructor_addresses_count-1], &info); | |
log_out("patching load address 0x%012lx %s\n", constructor_addresses[constructor_addresses_count-1], info.dli_sname); | |
} | |
dyld_header = (uintptr_t)dyld_image_header_containing_address((void*)return_address); | |
if (!dyld_shared_cache_for_each_image || !dyld_image_path_containing_address) { | |
can_use_dyld_apis = false; | |
} | |
} | |
@interface MERPLERPBURPDERP : NSObject | |
@end | |
@implementation MERPLERPBURPDERP | |
+ (void)load { | |
constructor_addresses[constructor_addresses_count++] = (uintptr_t)strip_pac((void*)__builtin_return_address(0)) - ARM64_OPCODE_SIZE; | |
if (getenv("DEBUG")) { | |
Dl_info info; | |
dladdr((void*)constructor_addresses[constructor_addresses_count-1], &info); | |
log_out("patching load address 0x%012lx %s\n", constructor_addresses[constructor_addresses_count-1], info.dli_sname); | |
} | |
} | |
@end | |
@implementation NSObject (BOWMEOWYAYHEY) | |
+ (void)load { | |
constructor_addresses[constructor_addresses_count++] = (uintptr_t)strip_pac((void*)__builtin_return_address(0)) - ARM64_OPCODE_SIZE; | |
if (getenv("DEBUG")) { | |
Dl_info info; | |
dladdr((void*)constructor_addresses[constructor_addresses_count-1], &info); | |
log_out("patching load address 0x%012lx %s\n", constructor_addresses[constructor_addresses_count-1], info.dli_sname); | |
} | |
} | |
@end | |
__attribute__((always_inline)) | |
static uintptr_t get_cls_isa(Class cls) { | |
if (!cls) { | |
return 0; | |
} | |
uintptr_t *isa_packed = (__bridge void*)cls; | |
uintptr_t isa = (*isa_packed) & ISA_MASK; | |
return (uintptr_t)strip_pac((void*)isa); | |
} | |
__attribute__((always_inline)) | |
static int get_object_type_description(const char *typeEncoding, char *buffer) { | |
int buff_offset = 0; | |
if (!typeEncoding) { | |
do_copy_n_return("void*"); | |
} | |
if (!strcmp(typeEncoding, "@")) { | |
do_copy_n_return("id"); | |
} else if (!strcmp(typeEncoding, "v")) { | |
do_copy_n_return("void"); | |
} else if (!strcmp(typeEncoding, "^v")) { | |
do_copy_n_return("void*"); | |
} else if (!strcmp(typeEncoding, ":")) { | |
do_copy_n_return("SEL"); | |
} else if (!strcmp(typeEncoding, "B")) { // TODO there are 2 of these? B/b | |
do_copy_n_return("BOOL"); | |
} else if (!strcmp(typeEncoding, "b")) { | |
do_copy_n_return("BOOL"); | |
} else if (!strcmp(typeEncoding, "c")) { | |
do_copy_n_return("char"); | |
} else if (!strcmp(typeEncoding, "i")) { | |
do_copy_n_return("int"); | |
} else if (!strcmp(typeEncoding, "s")) { | |
do_copy_n_return("short"); | |
} else if (!strcmp(typeEncoding, "q")) { | |
do_copy_n_return("long"); | |
} else if (!strcmp(typeEncoding, "C")){ | |
do_copy_n_return("unsigned char"); | |
} else if (!strcmp(typeEncoding, "I")) { | |
do_copy_n_return("unsigned int"); | |
} else if (!strcmp(typeEncoding, "S")) { | |
do_copy_n_return("unsigned short"); | |
} else if (!strcmp(typeEncoding, "Q")) { | |
do_copy_n_return("unsigned long"); | |
} else if (!strcmp(typeEncoding, "f")) { | |
do_copy_n_return("float"); | |
} else if (!strcmp(typeEncoding, "d")) { | |
do_copy_n_return("double"); | |
} else if (!strcmp(typeEncoding, "*")) { | |
do_copy_n_return("char*"); | |
} else if (!strcmp(typeEncoding, "#")) { | |
do_copy_n_return("Class"); | |
} else if (!strcmp(typeEncoding, "@?")) { | |
do_copy_n_return("^block"); | |
} | |
size_t len = strlen(typeEncoding); | |
// Normal C struct type {_NSZone=} >>> NSZone* | |
if (typeEncoding[0] == '{' && len >= 4 && typeEncoding[len -1] == '}') { | |
if (g_verbose) { // print the complete struct if verbose else just the first name | |
snprintf(buffer, GOOD_E_NUFF_BUFSIZE, "struct %.*s", (int)(len - 3), &typeEncoding[1]); | |
} else { | |
char* found = strchr(&typeEncoding[1], '='); | |
snprintf(buffer, GOOD_E_NUFF_BUFSIZE, "struct %.*s", (int)(len - ((char*)&typeEncoding[1] - found)), &typeEncoding[1]); | |
} | |
return 0; | |
} | |
// handle objc instance @"some_objc_here" | |
if (typeEncoding[0] == '@' && len >= 4) { | |
if (typeEncoding[2] == '<') { | |
snprintf(buffer, GOOD_E_NUFF_BUFSIZE, "id%.*s", (int)(len - 3), &typeEncoding[2]); | |
return 0; | |
} | |
snprintf(buffer, GOOD_E_NUFF_BUFSIZE, "%.*s*", (int)(len - 3), &typeEncoding[2]); | |
return 0; | |
} | |
// handle pointers | |
if (typeEncoding[0] == '^') { | |
if (len > 1) { | |
char tmp[GOOD_E_NUFF_BUFSIZE]; | |
get_object_type_description(&typeEncoding[1], tmp); | |
append_content("%s*", tmp); | |
return 0; | |
} | |
return 1; | |
} | |
if ( len >= 2 ) | |
{ | |
if (typeEncoding[0] <= 'm') | |
{ | |
switch (typeEncoding[0]) | |
{ | |
case 'N': | |
append_content("inout "); | |
break; | |
case 'O': | |
append_content("bycopy "); | |
break; | |
case 'R': | |
append_content("byref "); | |
break; | |
case 'V': | |
append_content("oneway "); | |
break; | |
default: | |
do_copy_n_return(typeEncoding); | |
} | |
get_object_type_description(&typeEncoding[1], buffer + buff_offset); | |
return 0; | |
} | |
switch (typeEncoding[0]) | |
{ | |
case 'r': | |
append_content("const "); | |
break; | |
case 'o': | |
append_content("out "); | |
break; | |
case 'n': | |
append_content("in "); | |
break; | |
} | |
get_object_type_description(&typeEncoding[1], buffer + buff_offset); | |
return 0; | |
} | |
return 1; | |
} | |
static | |
int get_property_description(objc_property_t *property, char *buffer) | |
{ | |
uint buff_offset = 0; | |
unsigned int attributeCount = 0; | |
objc_property_attribute_t *attributes = property_copyAttributeList(*property, &attributeCount); | |
append_content("%s@property ", DGREEN); | |
if (attributeCount >= 2) { | |
append_content("(") | |
} | |
for (int i = attributeCount - 1; i >= 0; i--) { | |
const char *name = attributes[i].name; | |
if (i == 0 && attributeCount >= 2) { | |
append_content(")%s ", DCOLOR_END); | |
} | |
if (!strcmp(name, "R")) { | |
append_content("readonly"); | |
if (i != 1) { | |
append_content(", ") | |
} | |
} else if (!strcmp(name, "C")) { | |
append_content("copy"); | |
if (i != 1) { | |
append_content(", ") | |
} | |
} else if (!strcmp(name, "&")) { | |
append_content("retain"); | |
if (i != 1) { | |
append_content(", ") | |
} | |
} else if (!strcmp(name, "N")) { | |
append_content("nonatomic"); | |
if (i != 1) { | |
append_content(", ") | |
} | |
} else { | |
if (!strcmp(name, "G")) { | |
append_content("getter=%s ", &name[2]); | |
} | |
else { | |
if (strcmp(name, "S")) { | |
log_debug("/* TODO DEREK S */") | |
// TODO: look up this one | |
if (!strcmp(name, "D")) { | |
// TODO: look up this one | |
log_debug("/* TODO DEREK D */") | |
} else if (!strcmp(name, "W")) { | |
append_content("weak "); | |
} else if (!strcmp(name, "T")) { | |
char tmp[GOOD_E_NUFF_BUFSIZE]; | |
get_object_type_description(attributes->value, tmp); | |
append_content("%s%s%s ", DRED, tmp, DCOLOR_END); | |
} | |
else if (!strcmp(name, "V")) { | |
// Ignore this one, it's called a 'oneway' | |
} else { | |
} | |
} | |
// TODO: lookup setters | |
} | |
} | |
} | |
append_content("%s%s%s\n", DBOLD, property_getName(*property), DCOLOR_END); | |
free(attributes); | |
return 0; | |
} | |
static | |
void extract_and_print_method(Method method, const char *name, uintptr_t image_start, BOOL isClassMethod, BOOL pretendMethod) { | |
char buffer[GOOD_E_NUFF_BUFSIZE]; | |
buffer[0] = '\0'; | |
const char* returnType = method_copyReturnType(method); | |
if (get_object_type_description(returnType, buffer)) { | |
log_error("\nerror!\n failed to parse \"%s\"", returnType); | |
} | |
free((void*)returnType); | |
// ugly hack eeeeeeeeeeek | |
// pretendMethod means the method param is actually a objc_method_description* that | |
// is masquerading as a Method_t. objc_method_description are missing the IMP, but are | |
// identical to a "large" Method_t. So... if we ignore any IMP APIs, we get the same | |
// helpful APIs for pulling out type encodings | |
if (g_verbose && !pretendMethod) { | |
uintptr_t implementation = (uintptr_t)strip_pac((void*)method_getImplementation(method)); | |
log_out( " %s/* +0x%08lx 0x%016lx %s */%s", DGRAY, implementation - image_start, implementation, name, DCOLOR_END); | |
} | |
log_out(" %s%c(%s%s%s%s%s)%s", DGRAY, isClassMethod ? '+' : '-', DCOLOR_END, DPARAM_COLOR, buffer, DCOLOR_END, DPUNC_COLOR, DCOLOR_END); | |
const char* method_name = sel_getName(method_getName(method)); | |
char *cur_param = strchr(method_name, ':'); | |
if (cur_param) { | |
char *prev_param = (char*)method_name; | |
int index = 0; | |
char tmp[GOOD_E_NUFF_BUFSIZE]; | |
do { | |
log_out( "%s%.*s:%s", DMETHOD_COLOR, (int)(cur_param - prev_param), prev_param, DCOLOR_END); | |
// method_copyArgumentType 0 == self, 1 == SEL, 2... actual methods | |
char *argType = method_copyArgumentType(method, index + 2); | |
if (get_object_type_description(argType, tmp)) { | |
log_error("\nerror!\n failed to parse \"%s\"", argType); | |
} | |
free(argType); | |
// printts a type and param ie. (id)a2 | |
log_out( "%s(%s%s%s%s%s)%s%sa%d%s", DGRAY, DCOLOR_END, DPARAM_COLOR, tmp, DCOLOR_END, DGRAY, DCOLOR_END, DGRAY, index + 1, DCOLOR_END); | |
prev_param = cur_param + 1; | |
cur_param = strchr(cur_param + 1, ':'); | |
if (cur_param) { | |
log_out( " "); | |
} | |
index++; | |
} while (cur_param); | |
log_out( "%s;%s\n", DPUNC_COLOR, DCOLOR_END); | |
} else { | |
log_out( "%s%s%s%s;%s\n", DMETHOD_COLOR, method_name, DCOLOR_END, DPUNC_COLOR, DCOLOR_END); | |
} | |
} | |
static | |
const char* dsc_image_as_num(uint32_t num) { | |
if (!can_use_dyld_apis) { | |
return NULL; | |
} | |
const char *dsc_path = dyld_shared_cache_file_path(); | |
__block const char *str = NULL; | |
dyld_shared_cache_for_file(dsc_path, ^(dyld_shared_cache_t cache) { | |
__block uint32_t counter = 0; | |
dyld_shared_cache_for_each_image(cache, ^(dyld_image_t image) { | |
if (str) { | |
return; | |
} | |
if (++counter == num) { | |
str = strdup(my_dyld_image_get_installname(image)); | |
} | |
}); | |
}); | |
return str; | |
} | |
static | |
const char* dsc_image_as_path(const char *path) { | |
if (!can_use_dyld_apis) { | |
return NULL; | |
} | |
const char *dsc_path = dyld_shared_cache_file_path(); | |
__block const char *outparam = NULL; | |
dyld_shared_cache_for_file(dsc_path, ^(dyld_shared_cache_t cache) { | |
dyld_shared_cache_for_each_image(cache, ^(dyld_image_t image) { | |
if (outparam) { | |
return; | |
} | |
const char* cur = my_dyld_image_get_installname(image); | |
if (!strcmp(path, cur)) { | |
outparam = cur; | |
} | |
}); | |
}); | |
return outparam; | |
} | |
static | |
const char* dsc_image_as_name(const char *name) { | |
if (!can_use_dyld_apis) { | |
return NULL; | |
} | |
const char *dsc_path = dyld_shared_cache_file_path(); | |
__block const char *outparam = NULL; | |
dyld_shared_cache_for_file(dsc_path, ^(dyld_shared_cache_t cache) { | |
dyld_shared_cache_for_each_image(cache, ^(dyld_image_t image) { | |
const char* cur = my_dyld_image_get_installname(image); | |
if (outparam) { | |
return; | |
} | |
if (!strcmp(name, basename((char*)cur))) { | |
outparam = strdup(cur); | |
} | |
}); | |
}); | |
return outparam; | |
} | |
void dump_dsc_images(void) { | |
if (!can_use_dyld_apis) { | |
return; | |
} | |
const char *dsc_path = dyld_shared_cache_file_path(); | |
dyld_shared_cache_for_file(dsc_path, ^(dyld_shared_cache_t cache) { | |
__block uint32_t counter = 0; | |
dyld_shared_cache_for_each_image(cache, ^(dyld_image_t image) { | |
log_out("%s%5d%s %s%s%s\n", DYELLOW, ++counter, DCOLOR_END, DCYAN, my_dyld_image_get_installname(image), DCOLOR_END); | |
}); | |
}); | |
} | |
static void thread_walkback_frames_to_safe_code(thread_t thread) { | |
#if defined(__arm64__) | |
arm_thread_state64_t state = {}; | |
mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT; | |
HANDLE_ERR(thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&state, &count)); | |
Dl_info pinfo, linfo; | |
dladdr((void*)state.__pc, &pinfo); | |
dladdr((void*)state.__lr, &linfo); | |
log_debug("caught message\n pc: 0x%012llx %s\n lr: 0x%012llx %s\n", state.__pc, pinfo.dli_sname, state.__lr, linfo.dli_sname); | |
#ifdef __arm64__ | |
#elif __x86_64__ | |
TODO: implement geriatric CPU arch | |
#endif | |
arm_debug_state64_t dbg = {}; | |
mach_msg_type_number_t dbg_cnt = ARM_DEBUG_STATE64_COUNT; | |
HANDLE_ERR(thread_get_state(thread, ARM_DEBUG_STATE64, (thread_state_t)&dbg, &dbg_cnt)); | |
// lldb puts a breakpoint on _dyld_debugger_notification, so catch & release | |
Dl_info info = {}; | |
uintptr_t pc = (uintptr_t)strip_pac((void*)state.__pc); | |
if (dladdr((void*)pc, &info) != 0) { | |
if (!strcmp(info.dli_sname, "_dyld_debugger_notification")) { | |
log_debug("it's _dyld_debugger_notification\n"); | |
state.__pc = state.__lr; | |
HANDLE_ERR(thread_set_state(thread, ARM_THREAD_STATE64, (thread_state_t)&state, count)); | |
return; | |
} | |
} | |
for (uint8_t i = 0; i < constructor_addresses_count; i++) { | |
if (constructor_addresses[i] == ((uintptr_t)strip_pac((void*)state.__pc))) { | |
if (g_debug) { | |
log_out("found caller 0x%012lx\n", constructor_addresses[i]) | |
} | |
state.__pc += ARM64_OPCODE_SIZE; | |
HANDLE_ERR(thread_set_state(thread, ARM_THREAD_STATE64, (thread_state_t)&state, count)) | |
return; | |
} | |
} | |
// This is for all other code that I've missed | |
uintptr_t sp = (uintptr_t)strip_pac((void*)state.__sp); | |
struct fp_ptr *frame = strip_pac((void*)state.__fp); | |
while (frame && frame->next != NULL) { | |
off_t offset = ((uintptr_t)frame - sp) + sizeof(struct fp_ptr); | |
sp += offset; | |
// walk back the stack frames looking for libobjc / [lib]dyld | |
const void* addr = strip_pac(frame->address); | |
const char* path = dyld_image_path_containing_address(addr); | |
if (!strcmp("/usr/lib/libobjc.A.dylib", path) || | |
!strcmp("/usr/lib/system/libdyld.dylib", path) || | |
!strcmp("/usr/lib/dyld", path)) { | |
break; | |
} | |
frame = strip_pac(frame->next); | |
} | |
state.__lr = (uintptr_t)strip_pac(frame->address); | |
state.__pc = (uintptr_t)strip_pac(frame->address); | |
state.__fp = (uintptr_t)strip_pac(frame->next); | |
state.__sp = sp; | |
HANDLE_ERR(thread_set_state(thread, ARM_THREAD_STATE64, (thread_state_t)&state, count)) | |
#endif | |
} | |
void* server_thread(void *arg) { | |
pthread_setname_np("Exception Handler"); | |
thread_t thread = (thread_t)(uintptr_t)arg; | |
mach_port_options_t options = {.flags = MPO_INSERT_SEND_RIGHT}; | |
HANDLE_ERR(mach_port_construct(mach_task_self(), &options, 0, &exc_port)); | |
HANDLE_ERR(thread_set_exception_ports(thread, EXC_MASK_ALL, exc_port, EXCEPTION_DEFAULT|MACH_EXCEPTION_CODES, THREAD_STATE_NONE)); | |
#if defined(__arm64__) | |
arm_debug_state64_t dbg = {}; | |
mach_msg_type_number_t cnt = ARM_DEBUG_STATE64_COUNT; | |
HANDLE_ERR(thread_get_state(thread, ARM_DEBUG_STATE64, (thread_state_t)&dbg, &cnt)); | |
for (int i = 0; i < constructor_addresses_count; i++) { | |
dbg.__bvr[i] = (__int64_t)constructor_addresses[i]; | |
dbg.__bcr[i] = S_USER|BCR_ENABLE|BCR_BAS; | |
} | |
HANDLE_ERR(thread_set_state(thread, ARM_DEBUG_STATE64, (thread_state_t)&dbg, cnt)); | |
#elif defined(__x86_64__) | |
#else | |
#error "da fuck you compiling?" | |
#endif | |
pthread_cond_signal(&exception_cond); // Signal the main thread | |
pthread_mutex_unlock(&exception_mutex); | |
kern_return_t kr = KERN_SUCCESS; | |
while(1) { | |
char buffer[GOOD_E_NUFF_BUFSIZE]; | |
mach_msg_header_t *msg = (void*)buffer; | |
msg->msgh_remote_port = MACH_PORT_NULL; | |
msg->msgh_id = 2405; | |
msg->msgh_local_port = exc_port; | |
msg->msgh_size = GOOD_E_NUFF_BUFSIZE; | |
if ((kr = mach_msg_receive(msg))) { | |
// other thread will mod -1 the port so ignore MACH_RCV_PORT_CHANGED | |
if (kr != MACH_RCV_PORT_CHANGED) { | |
fprintf(stderr, "recv err %s %x\n", mach_error_string(kr), kr); | |
HANDLE_ERR(kr); | |
} | |
break; | |
} | |
exc_req* req = ((exc_req*)msg); | |
thread_t thread = req->thread.name; | |
thread_walkback_frames_to_safe_code(thread); | |
msg->msgh_local_port = MACH_PORT_NULL; | |
msg->msgh_bits = MACH_RCV_MSG | MACH_SEND_TIMEOUT; | |
msg->msgh_id = 2505; | |
msg->msgh_size = sizeof(exc_resp); | |
exc_resp *resp = (exc_resp*)msg; | |
resp->NDR = NDR_record; | |
resp->RetCode = KERN_SUCCESS; | |
if ((kr = mach_msg_send(msg))) { | |
HANDLE_ERR(kr); | |
break; | |
} | |
} | |
return NULL; | |
} | |
/*********************************************************************/ | |
# pragma mark - public - | |
/*********************************************************************/ | |
DYNAMIC_DUMP_VISIBILITY | |
void dump_all_objc_classes(bool do_classlist, const char *path, const struct mach_header_64* header_pac) { | |
struct mach_header_64 *header = strip_pac((void*)header_pac); | |
// if we have the mach header we don't have to iterate all classes | |
if (header) { | |
unsigned long size = 0; | |
const char *segments[] = { "__DATA", "__DATA_CONST"}; | |
for (int z = 0; z < 2; z++) { | |
// dirty knowledge of the layout but we need the protocol names | |
struct objc_protocol_t { | |
uintptr_t isa; | |
const char* name; | |
//.. more, but whatever | |
}; | |
size = 0; | |
if (!do_classlist) { | |
struct objc_protocol_t** protocols = (void*)getsectiondata(header, segments[z], "__objc_protolist", &size); | |
for (int i = 0; i < (size / sizeof(uintptr_t)); i++) { | |
struct objc_protocol_t *prot = protocols[i]; | |
if (prot->name) { | |
Protocol *p = objc_getProtocol(prot->name); | |
if (!p) { | |
continue; | |
} | |
dump_objc_protocol_info(p); | |
log_out("\n"); | |
} | |
} | |
} | |
} | |
// at runtime all implementations are realized so we'll capture all classes | |
// and if there's a category that references the class, we'll note it. | |
// if the class hasn't been dumped at the category stage, we'll dump it there | |
NSMutableSet <Class>*classSet = [NSMutableSet set]; | |
for (int z = 0; z < 2; z++) { | |
size = 0; | |
Class *classes = (__unsafe_unretained Class*)(void*)getsectiondata(header, segments[z], "__objc_classlist", &size); | |
for (int i = 0; i < size / sizeof(uintptr_t); i++) { | |
Class cls = classes[i]; | |
if (class_respondsToSelector(cls, @selector(doesNotRecognizeSelector:))) { | |
[classSet addObject:cls]; | |
} else { | |
log_error("non-NSObject root class, \"%s\", skipping\n", class_getName(cls)); | |
continue; | |
} | |
if (do_classlist) { | |
Class supercls = class_getSuperclass(cls); | |
log_out("%s0x%016lx%s %s%s%s : %s%s%s\n", DGRAY, (uintptr_t)cls, DCOLOR_END, DYELLOW, class_getName(cls), DCOLOR_END, DGREEN, class_getName(supercls), DCOLOR_END); | |
} else { | |
dump_objc_class_info(cls); | |
} | |
} | |
} | |
if (!do_classlist) { | |
for (int z = 0; z < 2; z++) { | |
size = 0; | |
// Internal header eeeeeeeeek, but no APIs for categories : [ | |
struct category_t { | |
const char *name; | |
Class cls; | |
void* instanceMethods; | |
void* classMethods; | |
struct protocol_list_t *protocols; | |
struct property_list_t *instanceProperties; | |
// Fields below this point are not always present on disk. | |
struct property_list_t *_classProperties; | |
}; | |
struct category_t **categories = (struct category_t**)getsectiondata(header, segments[z], "__objc_catlist", &size); | |
for (int i = 0; i < size / sizeof(uintptr_t); i++) { | |
struct category_t *cat = categories[i]; | |
// TODO: not quite accurate, think of a better way of describing this to user, objc categories don't have the APIs to show which category they | |
// are coming from, so check if we've printed the cls already or see if | |
// they are in the same image as we're inspecting | |
if (![classSet containsObject:cat->cls]) { | |
if (dyld_image_header_containing_address((__bridge const void *)(cat->cls)) != (void*)header) { | |
log_out("%s// category for %s, which is declared in \"%s\"%s\n", DRED, class_getName(cat->cls), dyld_image_path_containing_address((__bridge const void *)(cat->cls)), DCOLOR_END); | |
} | |
dump_objc_class_info(cat->cls); | |
} else { | |
log_out("%s@interface%s %s%s (%s) %s// category%s\n", DMAGENTA, DCOLOR_END, DYELLOW, class_getName(cat->cls), cat->name, DGRAY, DCOLOR_END); | |
log_out("%s@end%s\n", DYELLOW, DCOLOR_END); | |
} | |
} | |
} | |
} | |
} else { // plan B is to just load everything and dump it | |
unsigned int count = 0; | |
Class *classes = objc_copyClassList(&count); | |
log_debug("found %d classes...\n", count); | |
for (int i = 0; i < count; i++) { | |
Class cls = classes[i]; | |
void* isa = (void*)get_cls_isa(cls); | |
const char * curpath = dyld_image_path_containing_address(isa); | |
log_debug("%s %s\n", class_getName(cls), curpath); | |
if (!curpath) { | |
continue; | |
} | |
if (strcmp(curpath, path)) { | |
continue; | |
} | |
if (do_classlist) { | |
Class supercls = class_getSuperclass(cls); | |
log_out("%s0x%016lx%s %s%s%s : %s%s%s\n", DGRAY, (uintptr_t)cls, DCOLOR_END, DYELLOW, class_getName(cls), DCOLOR_END, DGREEN, class_getName(supercls), DCOLOR_END); | |
} else { | |
dump_objc_class_info(cls); | |
} | |
} | |
} | |
} | |
DYNAMIC_DUMP_VISIBILITY | |
void dump_objc_protocol_info(Protocol *p) { | |
unsigned int count = 0; | |
struct objc_method_description *descriptions = NULL; | |
if (!p) { | |
return; | |
} | |
Protocol *prot = (__bridge Protocol*)strip_pac((__bridge void *)(p)); | |
const char* name = protocol_getName(prot); | |
log_out(" %s@protocol %s%s", DYELLOW_LIGHT, name, DCOLOR_END); | |
if (g_verbose) { | |
log_out(" %s// 0x%012lx%s", DGRAY, (uintptr_t)prot, DCOLOR_END); | |
} | |
log_out("\n"); | |
// required class | |
descriptions = protocol_copyMethodDescriptionList(prot, YES, NO, &count); | |
for (uint i = 0; i < count; i++) { | |
struct objc_method_description *desc = &descriptions[i]; | |
extract_and_print_method((Method)desc, name, 0, YES, YES); | |
} | |
free(descriptions); | |
count = 0; | |
// required instance | |
descriptions = protocol_copyMethodDescriptionList(prot, YES, YES, &count); | |
for (uint i = 0; i < count; i++) { | |
struct objc_method_description *desc = &descriptions[i]; | |
extract_and_print_method((Method)desc, name, 0, NO, YES); | |
} | |
free(descriptions); | |
count = 0; | |
// optional class | |
descriptions = protocol_copyMethodDescriptionList(prot, NO, NO, &count); | |
bool did_print_optional = false; | |
if (count) { | |
did_print_optional = true; | |
log_out(" %s@optional%s\n", DYELLOW_LIGHT, DCOLOR_END); | |
} | |
for (uint i = 0; i < count; i++) { | |
struct objc_method_description *desc = &descriptions[i]; | |
extract_and_print_method((Method)desc, name, 0, YES, YES); | |
} | |
free(descriptions); | |
count = 0; | |
// optional instance | |
descriptions = protocol_copyMethodDescriptionList(prot, NO, YES, &count); | |
if (count && did_print_optional == false) { | |
log_out(" %s@optional%s\n", DYELLOW_LIGHT, DCOLOR_END); | |
} | |
for (uint i = 0; i < count; i++) { | |
struct objc_method_description *desc = &descriptions[i]; | |
extract_and_print_method((Method)desc, name, 0, NO, YES); | |
} | |
log_out(" %s@end%s\n", DYELLOW_LIGHT, DCOLOR_END); | |
} | |
DYNAMIC_DUMP_VISIBILITY | |
void dlopen_n_dump_objc_classes(const char *arg, const char*clsName, bool do_classlist) { | |
void* handle = NULL; | |
uint32_t dsc_num = strtod(arg, NULL); | |
const char *path = NULL; | |
// can we even open this thing? i.e. for cases like dynadump dump Foundation | |
handle = dlopen(arg, RTLD_NOLOAD); | |
if (dsc_num) { | |
path = dsc_image_as_num(dsc_num); | |
} else if (handle) { | |
path = strdup(arg); | |
} else { | |
path = dsc_image_as_name(arg); | |
log_debug("changing arg %s -> %s\n", arg, path); | |
// if we can't find a path we'll just start with the OG and fail later if needed | |
if (!path) { | |
path = strdup(arg); | |
} | |
} | |
// if (!path) { | |
// log_error("couldn't find a dsc image \"%s\"\n", arg); | |
// return; | |
// } | |
// use stderr, so everything else is still grep-able | |
fprintf(stderr, "%s%s%s\n", DCYAN, path, DCOLOR_END); | |
#if 0 | |
#warning you've got normal dlopen going, derek | |
handle = dlopen(path, RTLD_NOW); | |
#else | |
if (getenv("NOEXC")) { | |
handle = dlopen(path, RTLD_NOW); | |
} else { | |
handle = safe_dlopen(path); | |
} | |
#endif | |
if (!handle) { | |
log_error("couldn't open \"%s\"\n", arg); | |
return; | |
} | |
task_dyld_info_data_t info; | |
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; | |
HANDLE_ERR(task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t)&info, &count)); | |
struct dyld_all_image_infos *all_image_infos = (void*)info.all_image_info_addr; | |
struct dyld_image_info *imageArray = (void*)all_image_infos->infoArray; | |
const struct mach_header_64* header = NULL; | |
for (uint i = 0; i < all_image_infos->infoArrayCount; i++) { | |
struct dyld_image_info *info = &imageArray[i]; | |
if (!strcmp(info->imageFilePath, path)) { | |
header = (void*)info->imageLoadAddress; | |
break; | |
} | |
} | |
// Try again with a different path in the case dlopen views the lib as a different nmae | |
if (!header) { | |
for (uint i = 0; i < all_image_infos->infoArrayCount; i++) { | |
struct dyld_image_info *info = &imageArray[i]; | |
if (!strcmp(basename((char *)info->imageFilePath), basename((char *)path))) { | |
header = (void*)info->imageLoadAddress; | |
break; | |
} | |
} | |
} | |
if (clsName) { | |
Class cls = objc_getClass(clsName); | |
dump_objc_class_info(cls); | |
} else { | |
dump_all_objc_classes(do_classlist, path, header); | |
} | |
if (path) { | |
free((void*)path); | |
} | |
} | |
DYNAMIC_DUMP_VISIBILITY | |
void* safe_dlopen(const char *image) { | |
#if defined(__arm64__) | |
// setup a handler in case a constructor tries to take us down | |
pthread_mutex_lock(&exception_mutex); | |
static pthread_t exception_thread; | |
if (pthread_create(&exception_thread, NULL, server_thread, (void*)(uintptr_t)mach_thread_self())) { | |
return NULL; | |
} | |
pthread_detach(exception_thread); | |
pthread_cond_wait(&exception_cond, &exception_mutex); | |
void* handle = dlopen(image, RTLD_NOW); | |
// remove breakpoints | |
arm_debug_state64_t dbg = {}; | |
mach_msg_type_number_t cnt = ARM_DEBUG_STATE64_COUNT; | |
HANDLE_ERR(thread_get_state(mach_thread_self(), ARM_DEBUG_STATE64, (thread_state_t)&dbg, &cnt)); | |
for (int i = 0; i < constructor_addresses_count; i++) { | |
dbg.__bcr[i] &= ~BCR_ENABLE; | |
} | |
HANDLE_ERR(thread_set_state(mach_thread_self(), ARM_DEBUG_STATE64, (thread_state_t)&dbg, cnt)); | |
// remove the handler so debuggers can catch f ups better | |
HANDLE_ERR(thread_set_exception_ports(mach_thread_self(), EXC_MASK_ALL, MACH_PORT_NULL, EXCEPTION_DEFAULT|MACH_EXCEPTION_CODES, THREAD_STATE_NONE)); | |
// yanking the port will break the while loop on in the server_thread | |
mach_port_mod_refs(mach_task_self(), exc_port, MACH_PORT_RIGHT_RECEIVE, -1); | |
return handle; | |
#else | |
return NULL; | |
#endif | |
} | |
static void _dump_ivar_description(id instanceOrCls, bool standaloneDescription) { | |
const char *imagePath = dyld_image_path_containing_address((__bridge const void * _Nonnull)(instanceOrCls)); | |
Class cls = objc_opt_class(instanceOrCls); | |
bool isClass = (instanceOrCls == cls) ? true: false; | |
unsigned int ivarCount = 0; | |
if (standaloneDescription) { | |
if (isClass) { | |
log_out("%s", class_getName(cls)); | |
} else { | |
log_out("%s <%p>", class_getName(cls), instanceOrCls); | |
} | |
Class superCls = class_getSuperclass(cls); | |
if (superCls) { | |
log_out(": %s ", class_getName(superCls)); | |
} | |
if (imagePath) { | |
log_out(" %s(%s)%s\n", DYELLOW_LIGHT, imagePath, DCOLOR_END); | |
} else { | |
log_out(" %s(?)%s\n", DYELLOW_LIGHT, DCOLOR_END); | |
} | |
} | |
Ivar *ivars = class_copyIvarList(cls, &ivarCount); | |
if (ivarCount) { | |
log_out("\n {\n"); | |
} | |
for (uint i = 0; i < ivarCount; i++) { | |
const char *typeEncoding = ivar_getTypeEncoding(ivars[i]); | |
typeEncoding = typeEncoding ? typeEncoding : ""; | |
const char *name = ivar_getName(ivars[i]); | |
long int offset = ivar_getOffset(ivars[i]); | |
char buffer[GOOD_E_NUFF_BUFSIZE]; | |
get_object_type_description(typeEncoding, buffer); | |
if (isClass) { | |
log_out(" %s/* +0x%04lx */%s %s%s%s %s%s%s\n", DGRAY, offset, DCOLOR_END, DCYAN_LIGHT, buffer, DCOLOR_END, DCYAN, name, DCOLOR_END); | |
} else { | |
log_out(" %s/* +0x%04lx 0x%016lx */%s %s%s%s %s%s%s \n", DGRAY, offset, *(uintptr_t*)((uintptr_t)instanceOrCls + offset), DCOLOR_END, DCYAN_LIGHT, buffer, DCOLOR_END, DCYAN, name, DCOLOR_END); | |
} | |
} | |
free(ivars); | |
if (ivarCount) { | |
log_out(" }\n"); | |
} | |
} | |
DYNAMIC_DUMP_VISIBILITY | |
void dump_ivar_description(id instanceOrCls) { | |
_dump_ivar_description(instanceOrCls, true); | |
} | |
DYNAMIC_DUMP_VISIBILITY | |
void dump_method_description(id instanceOrCls) { | |
Class cls = objc_opt_class(instanceOrCls); | |
const char* clsName = class_getName(cls); | |
uintptr_t image_start = (uintptr_t)dyld_image_header_containing_address((__bridge const void * _Nonnull)(cls)); | |
unsigned int metaMethodCount = 0; | |
Class metaCls = objc_getMetaClass(clsName); | |
Class superCls = class_getSuperclass(cls); | |
if (!superCls) { | |
log_out( "%sNS_ROOT_CLASS%s ", DYELLOW, DCOLOR_END); | |
} | |
log_out( "%s@interface%s %s%s%s ", DMAGENTA, DCOLOR_END, DYELLOW, clsName, DCOLOR_END); | |
// superclass | |
if (superCls) { | |
const char* superClsName = class_getName(superCls); | |
if (superClsName) { | |
log_out( "%s: %s%s ", DYELLOW, superClsName, DCOLOR_END); | |
} | |
} | |
// protocols | |
unsigned int cnt = 0; | |
Protocol * __unsafe_unretained _Nonnull * _Nullable protocols = class_copyProtocolList(cls, &cnt); | |
for (int i = 0; i< cnt; i++) { | |
if (i == 0) { | |
log_out("%s<", DGREEN) | |
} | |
log_out("%s", protocol_getName(protocols[i])); | |
if (cnt > 1 && i < cnt - 1) { | |
log_out(", ") | |
} | |
if (i == cnt - 1) { | |
log_out(">%s", DCOLOR_END); | |
} | |
} | |
if (protocols) { | |
free((void*)protocols); | |
} | |
// ivars | |
_dump_ivar_description(cls, false); | |
// Properties | |
bool has_done_newline = false; | |
unsigned int propertyCount = 0; | |
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount); | |
if (propertyCount) { | |
log_out( "\n\n %s// \"%s\" properties:%s\n", DGRAY, clsName, DCOLOR_END); | |
has_done_newline = true; | |
} | |
for (uint i = 0; i < propertyCount; i++) { | |
char buffer[GOOD_E_NUFF_BUFSIZE]; | |
if (get_property_description(&properties[i], buffer)) { | |
log_error( "\nfailed to parse \"%s\"", property_getName(properties[i])); | |
} | |
log_out( " %s", buffer); | |
} | |
free(properties); | |
if (propertyCount) { | |
log_out( "\n"); | |
} | |
// Class methods | |
if (metaCls) { | |
if (has_done_newline == false) { | |
has_done_newline = true; | |
log_out("\n\n"); | |
} | |
Method *clsMethods = class_copyMethodList(metaCls, &metaMethodCount); | |
if (metaMethodCount) { | |
log_out( " %s// \"%s\" class methods:%s\n", DGRAY, clsName, DCOLOR_END); | |
} | |
for (uint i = 0; i < metaMethodCount; i++) { | |
extract_and_print_method(clsMethods[i], clsName, image_start, YES, NO); | |
} | |
if (metaMethodCount) { | |
log_out( "\n"); | |
} | |
free(clsMethods); | |
} | |
// Instance methods | |
if (cls) { | |
unsigned int methodCount = 0; | |
Method *instanceMethods = class_copyMethodList(cls, &methodCount); | |
if (has_done_newline == false) { | |
log_out("\n\n"); | |
has_done_newline = true; | |
} | |
if (methodCount) { | |
log_out( " %s// \"%s\" instance methods:%s\n", DGRAY, clsName, DCOLOR_END); | |
} | |
for (uint i = 0; i < methodCount; i++) { | |
extract_and_print_method(instanceMethods[i], clsName, image_start, NO, NO); | |
} | |
free(instanceMethods); | |
} | |
if (has_done_newline) { | |
log_out("\n"); | |
} | |
log_out( "%s@end%s\n\n", DYELLOW, DCOLOR_END); | |
log_debug("leaving %s:%d\n", __FUNCTION__, __LINE__); | |
} | |
void dump_objc_class_info(Class cls) { | |
uintptr_t isa = get_cls_isa(cls); | |
const char* path = dyld_image_path_containing_address((const void*)isa); | |
log_out("%s//> %s %s%s\n", DGRAY, class_getName(cls), path, DCOLOR_END); | |
dump_method_description(cls); | |
} | |
__attribute__((constructor)) static void setup(void) { | |
g_color = getenv("NOCOLOR") ? false : true; | |
g_debug = getenv("DEBUG") ? true : false; | |
g_verbose = getenv("VERBOSE") ? 5 : 0; | |
if (isatty(STDOUT_FILENO) == 0) { | |
g_color = false; | |
} | |
if (getenv("COLOR")) { | |
g_color = true; | |
} | |
} | |
#ifndef SHARED_DYNAMIC_DUMP | |
void print_help(void) { | |
log_out("\n dynadump (built: %s, %s) - yet another class-dump done via dlopen & exception catching\n\n", __DATE__, __TIME__); | |
log_out("\tParameters:\n") | |
log_out("\tlist list all the dylibs in the dyld shared cache (dsc)\n"); | |
log_out("\tlist $DYLIB list all the objc classes in a dylib $DYLIB\n"); | |
log_out("\tdump $DYLIB dump all the ObjC classes found in a dylib on disk\n"); | |
log_out("\tdump $DYLIB $CLASS dump a specific ObjC class found in dylib $DYLIB\n"); | |
log_out("\tlist $DYLIB $CLASS Same cmd as above (convenience for listing then dumping)\n"); | |
log_out("\n\tEnvironment Variables:\n"); | |
log_out("\tNOCOLOR - Forces no color, color will be on by default unless piped\n"); | |
log_out("\tCOLOR - Forces color, regardless of stdout destination\n"); | |
log_out("\tVERBOSE - Verbose output\n"); | |
log_out("\tNOEXC - Don't use an exception handler (on in x86_64)\n"); | |
log_out("\tDEBUG - Used internally to hunt down f ups\n"); | |
exit(1); | |
} | |
int main(int argc, const char * argv[]) { | |
if (argc == 1) { | |
print_help(); | |
} | |
#if defined(__x86_64__) | |
setenv("NOEXC", "meow", 1); | |
#endif | |
// list the dsc images | |
if (!strcmp("list", argv[1])) { | |
if (argc == 2) { | |
dump_dsc_images(); | |
} else if (argc == 3) { | |
dlopen_n_dump_objc_classes(argv[2], NULL, true); | |
} else if (argc == 4) { | |
dlopen_n_dump_objc_classes(argv[2], argv[3], true); | |
} | |
exit(0); | |
} | |
if (!strcmp("dump", argv[1])) { | |
if (argc < 3) { | |
log_error("dump <NUM|PATH_2_DYLIB\n"); | |
exit(1); | |
} | |
dlopen_n_dump_objc_classes(argv[2], argc > 2 ? argv[3] : NULL, false); | |
exit(0); | |
} | |
print_help(); | |
return 0; | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment