Skip to content

Instantly share code, notes, and snippets.

@qunwang6
Forked from DerekSelander/objc_description.m
Created March 20, 2024 01:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save qunwang6/5f092378f795a8020343e111cad0771e to your computer and use it in GitHub Desktop.
Save qunwang6/5f092378f795a8020343e111cad0771e to your computer and use it in GitHub Desktop.
Dumps Objective-C class/instance info at runtime
//
// 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