-
-
Save qunwang6/5f092378f795a8020343e111cad0771e to your computer and use it in GitHub Desktop.
Dumps Objective-C class/instance info at runtime
This file contains 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. | |
// | |
// to compile for jb'd iOS: | |
// xcrun -sdk iphoneos clang -fmodules -o /tmp/dynadump /path/to/this/file/main.m | |
// | |
// 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 <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 - | |
/*********************************************************************/ | |
extern const char* dyld_image_path_containing_address(const void* addr); | |
typedef struct dyld_shared_cache_s* dyld_shared_cache_t; | |
typedef struct dyld_image_s* dyld_image_t; | |
extern void dyld_shared_cache_for_each_image(dyld_shared_cache_t cache, void (^block)(dyld_image_t image)); | |
extern const char* dyld_shared_cache_file_path(void); | |
extern bool dyld_shared_cache_for_file(const char* filePath, void (^block)(dyld_shared_cache_t cache)); | |
extern const char* dyld_image_get_installname(dyld_image_t image); | |
extern 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; | |
}; | |
/// returned xor'd with the mach_header for dyld from a dlopen | |
struct dyld_JustInTimeLoader { | |
const uint32_t magic; // kMagic, 'l4yd' | |
const uint16_t isPrebuilt : 1, // PrebuiltLoader vs JustInTimeLoader | |
dylibInDyldCache : 1, | |
hasObjC : 1, | |
mayHavePlusLoad : 1, | |
hasReadOnlyData : 1, // __DATA_CONST. Don't use directly. Use hasConstantSegmentsToProtect() | |
neverUnload : 1, // part of launch or has non-unloadable data (e.g. objc, tlv) | |
leaveMapped : 1, // RTLD_NODELETE | |
hasReadOnlyObjC : 1, // Has __DATA_CONST,__objc_selrefs section | |
pre2022Binary : 1, | |
padding : 6; | |
uint16_t index : 15, // index into PrebuiltLoaderSet | |
app : 1; // app vs dyld cache PrebuiltLoaderSet | |
const struct mach_header_64 * mappedAddress; | |
}; | |
/// 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* safely_load_image(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 bool g_debug = false; | |
static bool g_color = true; | |
static int g_verbose = 0; | |
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")) { | |
log_out("patching load address 0x%012lx\n", constructor_addresses[constructor_addresses_count-1]); | |
} | |
dyld_header = (uintptr_t)dyld_image_header_containing_address((void*)return_address); | |
} | |
@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")) { | |
log_out("patching load address 0x%012lx\n", constructor_addresses[constructor_addresses_count-1]); | |
} | |
} | |
@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")) { | |
log_out("patching load address 0x%012lx\n", constructor_addresses[constructor_addresses_count-1]); | |
} | |
} | |
@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")) { | |
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); | |
// 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); | |
} | |
return 0; | |
} | |
switch (typeEncoding[0]) | |
{ | |
case 'r': | |
append_content("const "); | |
break; | |
case 'o': | |
append_content("out "); | |
break; | |
case 'n': | |
append_content("in "); | |
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("@property "); | |
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(") "); | |
} | |
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 ", tmp); | |
} | |
else if (!strcmp(name, "V")) { | |
// Ignore this one, it's called a 'oneway' | |
} else { | |
} | |
} | |
// TODO: lookup setters | |
} | |
} | |
} | |
append_content("%s\n", property_getName(*property)); | |
free(attributes); | |
return 0; | |
} | |
static | |
const char* dsc_image_as_num(uint32_t num) { | |
const char *dsc_path = dyld_shared_cache_file_path(); | |
__block dyld_image_t load_image = 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 (++counter == num) { | |
load_image = image; | |
} | |
}); | |
}); | |
return dyld_image_get_installname(load_image); | |
} | |
static | |
const char* dsc_image_as_path(const char *path) { | |
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 = dyld_image_get_installname(image); | |
if (!strcmp(path, cur)) { | |
outparam = cur; | |
} | |
}); | |
}); | |
return outparam; | |
} | |
void dump_dsc_images(void) { | |
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, dyld_image_get_installname(image), DCOLOR_END); | |
}); | |
}); | |
} | |
static void thread_walkback_frames_to_safe_code(thread_t thread) { | |
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)); | |
log_debug("caught message\n pc: 0x%012llx\n lr: 0x%012llx\n", state.__pc, state.__lr); | |
#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)) | |
} | |
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)); | |
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)); | |
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))) { | |
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) { | |
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 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; | |
if (dsc_num) { | |
path = dsc_image_as_num(dsc_num); | |
} else { | |
path = arg; | |
} | |
if (!path) { | |
log_error("couldn't find a dsc image \"%s\"\n", arg); | |
return; | |
} | |
#if 0 | |
#warning you've got normal dlopen going, derek | |
handle = dlopen(path, RTLD_NOW); | |
#else | |
handle = safely_load_image(path); | |
#endif | |
if (!handle) { | |
log_error("couldn't open \"%s\"\n", arg); | |
return; | |
} | |
/* | |
struct dyld_JustInTimeLoader *dyld_loader = (void*)((uintptr_t)handle ^ (uintptr_t)dyld_header); | |
TODO: look up the mach-o sections for objc classes, categories instead of iterating all realized classes | |
*/ | |
if (clsName) { | |
Class cls = objc_getClass(clsName); | |
dump_objc_class_info(cls); | |
} else { | |
dump_all_objc_classes(do_classlist, path); | |
} | |
} | |
DYNAMIC_DUMP_VISIBILITY | |
void* safely_load_image(const char *image) { | |
// 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); | |
// we'll 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)); | |
return handle; | |
} | |
DYNAMIC_DUMP_VISIBILITY | |
void dump_method_description(id instanceOrCls) { | |
log_debug("executing %s:%d\n", __FUNCTION__, __LINE__); | |
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 ", 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("<") | |
} | |
log_out("%s", protocol_getName(protocols[i])); | |
if (cnt > 1 && i < cnt - 1) { | |
log_out(", ") | |
} | |
if (i == cnt - 1) { | |
log_out(">"); | |
} | |
} | |
if (protocols) { | |
free((void*)protocols); | |
} | |
bool has_done_newline = false; | |
// Properties | |
unsigned int propertyCount = 0; | |
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount); | |
if (propertyCount) { | |
log_out( "\n\n %s// \"%s\" properties:%s\n",DMAGENTA, 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", DMAGENTA, clsName, DCOLOR_END); | |
} | |
for (uint i = 0; i < metaMethodCount; i++) { | |
const char* returnType = method_copyReturnType(clsMethods[i]); | |
char buffer[GOOD_E_NUFF_BUFSIZE]; | |
buffer[0] = '\0'; | |
if (get_object_type_description(returnType, buffer)) { | |
log_error("\nerror!\n failed to parse \"%s\"", returnType); | |
} | |
#if __has_feature(ptrauth_calls) | |
uintptr_t meth = ptrauth_strip(my_method_getImplementation(clsMethods[i]), 0); | |
#else | |
uintptr_t meth = (uintptr_t)method_getImplementation(clsMethods[i]); | |
#endif | |
if (g_verbose) { | |
log_out( " %s/* +0x%08lx 0x%016lx %s */%s", DGRAY, meth - image_start, meth, clsName, DCOLOR_END); | |
} | |
log_out(" %s+(%s%s%s%s%s)%s", DGRAY, DCOLOR_END, DPARAM_COLOR, buffer, DCOLOR_END, DPUNC_COLOR, DCOLOR_END); | |
const char* method_name = sel_getName(method_getName(clsMethods[i])); | |
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(clsMethods[i], 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); | |
} | |
} | |
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", DMAGENTA, clsName, DCOLOR_END); | |
} | |
for (uint i = 0; i < methodCount; i++) { | |
const char* returnType = method_copyReturnType(instanceMethods[i]); | |
char buffer[GOOD_E_NUFF_BUFSIZE]; | |
buffer[0] = '\0'; | |
if (get_object_type_description(returnType, buffer)) { | |
log_error("\nerror!\n failed to parse \"%s\"", returnType); | |
} | |
#if __has_feature(ptrauth_calls) | |
uintptr_t meth = ptrauth_strip(my_method_getImplementation(instanceMethods[i]), 0); | |
#else | |
uintptr_t meth = (uintptr_t)method_getImplementation(instanceMethods[i]); | |
#endif | |
if (g_verbose) { | |
log_out( " %s/* +0x%08lx 0x%016lx %s */%s", DGRAY, meth - image_start, meth, clsName, DCOLOR_END); | |
} | |
log_out(" %s-(%s%s%s%s%s)%s", DPUNC_COLOR, DCOLOR_END, DPARAM_COLOR, buffer, DCOLOR_END, DPUNC_COLOR, DCOLOR_END); | |
const char* method_name = sel_getName(method_getName(instanceMethods[i])); | |
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(instanceMethods[i], index + 2); | |
get_object_type_description(argType, tmp); | |
free(argType); | |
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); | |
} | |
free((void*)returnType); | |
} | |
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__); | |
} | |
DYNAMIC_DUMP_VISIBILITY | |
void dump_ivar_description(id instanceOrCls) { | |
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 (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); | |
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%-4lx%s %s%s%s %s%s%s\n", DGRAY, offset, DCOLOR_END, DCYAN_LIGHT, typeEncoding, DCOLOR_END, DCYAN, name, DCOLOR_END); | |
} else { | |
log_out(" %s+0x%-4lx 0x%016lx%s %s %s \n", DGRAY, offset, *(uintptr_t*)((uintptr_t)instanceOrCls + offset), DCOLOR_END, buffer, name); | |
} | |
} | |
free(ivars); | |
if (ivarCount) { | |
log_out("\n"); | |
} | |
} | |
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_ivar_description(cls); | |
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 - (yet another) class-dump via dlopen & exception catching\n\n"); | |
log_out("\tParameters:\n") | |
log_out("\tlist list all the dylibs in the dyld shared cache (dsc)\n"); | |
log_out("\tolist $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("\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 - make more verbose output\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(); | |
} | |
// list the dsc images | |
if (!strcmp("list", argv[1])) { | |
dump_dsc_images(); | |
exit(0); | |
} | |
// list the classes in a particular image | |
if (!strcmp("olist", argv[1])) { | |
if (argc < 3) { | |
log_error("olist /PATH/TO/DYLIB\n"); | |
exit(1); | |
} | |
dlopen_n_dump_objc_classes(argv[2], NULL, true); | |
exit(0); | |
} | |
if (!strcmp("dump", argv[1])) { | |
if (argc < 3) { | |
log_error("dump <NUM|PATH_2_DYLIB\n"); | |
} | |
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