/* radare - LGPL - Copyright 2010-2016 - nibble, pancake */

#include <stdio.h>
#include <r_types.h>
#include <r_util.h>
#include "mach0.h"

typedef struct _ulebr {
	ut8 *p;
} ulebr;

static bool little_;

static ut64 read_uleb128(ulebr *r, ut8 *end) {
	ut64 result = 0;
	int bit = 0;
	ut64 slice = 0;
	ut8 *p = r->p;
	do {
		if (p == end) {
			eprintf ("malformed uleb128");
		}
		slice = *p & 0x7f;

		if (bit > 63) {
			eprintf ("uleb128 too big for uint64, bit=%d, result=0x%"PFMT64x, bit, result);
		} else {
			result |= (slice << bit);
			bit += 7;
		}
	} while (*p++ & 0x80);
	r->p = p;
	return result;
}

static st64 read_sleb128(ulebr *r, ut8 *end) {
	st64 result = 0;
	int bit = 0;
	ut8 byte;
	ut8 *p = r->p;
	do {
		if (p == end)
			eprintf ("malformed sleb128");
		byte = *p++;
		result |= (((st64)(byte & 0x7f)) << bit);
		bit += 7;
	} while (byte & 0x80);
	// sign extend negative numbers
	if ( (byte & 0x40) != 0 )
		result |= (-1LL) << bit;
	r->p = p;
	return result;
}


static ut64 entry_to_vaddr(struct MACH0_(obj_t)* bin) {
	switch (bin->main_cmd.cmd) {
	case LC_MAIN:
		return bin->entry + bin->baddr;
	case LC_UNIXTHREAD:
	case LC_THREAD:
		return bin->entry;
	default:
		return 0;
	}
}

static ut64 addr_to_offset(struct MACH0_(obj_t)* bin, ut64 addr) {
	ut64 segment_base, segment_size;
	int i;

	if (!bin->segs)
		return 0;

	for (i = 0; i < bin->nsegs; i++) {
		segment_base = (ut64)bin->segs[i].vmaddr;
		segment_size = (ut64)bin->segs[i].vmsize;
		if (addr >= segment_base && addr < segment_base + segment_size) {
			return bin->segs[i].fileoff + (addr - segment_base);
		}
	}
	return 0;
}

static int init_hdr(struct MACH0_(obj_t)* bin) {
	ut32 magic = 0;
	int len;

	if (r_buf_read_at (bin->b, 0, (ut8*)&magic, 4) < 1) {
		eprintf ("Error: read (magic)\n");
		return false;
	}
	if (magic == MACH0_(MH_MAGIC)) {
		bin->big_endian = false;
	} else if (magic == MACH0_(MH_CIGAM)) {
		bin->big_endian = true;
	} else if (magic == FAT_CIGAM) {
		bin->big_endian = true;
	} else {
		return false; // object files are magic == 0, but body is different :?
	}
	len = r_buf_fread_at (bin->b, 0, (ut8*)&bin->hdr,
#if R_BIN_MACH064
		bin->big_endian?"8I":"8i", 1
#else
		bin->big_endian?"7I":"7i", 1
#endif
	);

	sdb_set (bin->kv, "mach0_header.format",
		"xxxxddx "
		"magic cputype cpusubtype filetype ncmds sizeofcmds flags", 0);
	sdb_num_set (bin->kv, "mach0_header.offset", 0, 0); // wat about fatmach0?
	sdb_set (bin->kv, "mach_filetype.cparse", "enum mach_filetype{MH_OBJECT=1,"
			"MH_EXECUTE=2, MH_FVMLIB=3, MH_CORE=4, MH_PRELOAD=5, MH_DYLIB=6,"
			"MH_DYLINKER=7, MH_BUNDLE=8, MH_DYLIB_STUB=9, MH_DSYM=10,"
			"MH_KEXT_BUNDLE=11}"
			,0);
	sdb_set (bin->kv, "mach_flags.cparse", "enum mach_flags{MH_NOUNDEFS=1,"
			"MH_INCRLINK=2,MH_DYLDLINK=4,MH_BINDATLOAD=8,MH_PREBOUND=0x10,"
			"MH_SPLIT_SEGS=0x20,MH_LAZY_INIT=0x40,MH_TWOLEVEL=0x80,"
			"MH_FORCE_FLAT=0x100,MH_NOMULTIDEFS=0x200,MH_NOFIXPREBINDING=0x400,"
			"MH_PREBINDABLE=0x800, MH_ALLMODSBOUND=0x1000,"
			"MH_SUBSECTIONS_VIA_SYMBOLS=0x2000,"
			"MH_CANONICAL=0x4000,MH_WEAK_DEFINES=0x8000,"
			"MH_BINDS_TO_WEAK=0x10000,MH_ALLOW_STACK_EXECUTION=0x20000,"
			"MH_ROOT_SAFE=0x40000,MH_SETUID_SAFE=0x80000,"
			"MH_NO_REEXPORTED_DYLIBS=0x100000,MH_PIE=0x200000,"
			"MH_DEAD_STRIPPABLE_DYLIB=0x400000,"
			"MH_HAS_TLV_DESCRIPTORS=0x800000,"
			"MH_NO_HEAP_EXECUTION=0x1000000 }",0);
	if (len < 1) {
		eprintf ("Error: read (hdr)\n");
		return false;
	}
	return true;
}

static int parse_segments(struct MACH0_(obj_t)* bin, ut64 off) {
	int sect, len, seg = bin->nsegs - 1;
	ut32 size_sects;

	if (!UT32_MUL (&size_sects, bin->nsegs, sizeof (struct MACH0_(segment_command))))
		return false;
	if (!size_sects || size_sects > bin->size)
		return false;
	if (off > bin->size || off + sizeof (struct MACH0_(segment_command)) > bin->size)
		return false;
	if (!(bin->segs = realloc (bin->segs, bin->nsegs * sizeof(struct MACH0_(segment_command))))) {
		perror ("realloc (seg)");
		return false;
	}
#if R_BIN_MACH064
	len = r_buf_fread_at (bin->b, off, (ut8*)&bin->segs[seg], bin->big_endian?"2I16c4L4I":"2i16c4l4i", 1);
#else
	len = r_buf_fread_at (bin->b, off, (ut8*)&bin->segs[seg], bin->big_endian?"2I16c8I":"2i16c8i", 1);
#endif
	if (len < 1)
		return false;
	sdb_num_set (bin->kv, sdb_fmt (0, "mach0_segment_%d.offset", seg), off, 0);
	sdb_num_set (bin->kv, "mach0_segments.count", 0, 0);
	sdb_set (bin->kv, "mach0_segment.format",
		"xd[16]zxxxxoodx "
		"cmd cmdsize segname vmaddr vmsize "
		"fileoff filesize maxprot initprot nsects flags", 0);

	if (len < 1) {
		eprintf ("Error: read (seg)\n");
		return false;
	}
	if (bin->segs[seg].nsects > 0) {
		sect = bin->nsects;
		bin->nsects += bin->segs[seg].nsects;
		if (bin->nsects > 128) {
			int new_nsects = bin->nsects & 0xf;
			eprintf ("WARNING: mach0 header contains too many sections (%d). Wrapping to %d\n",
				 bin->nsects, new_nsects);
			bin->nsects = new_nsects;
		}
		if ((int)bin->nsects > 0) {
			if (!UT32_MUL (&size_sects, bin->nsects-sect, sizeof (struct MACH0_(section)))){
				bin->nsects = sect;
				return false;
			}
			if (!size_sects || size_sects > bin->size){
				bin->nsects = sect;
				return false;
			}

			if (bin->segs[seg].cmdsize != sizeof (struct MACH0_(segment_command)) \
					  + (sizeof (struct MACH0_(section))*bin->segs[seg].nsects)){
				bin->nsects = sect;
				return false;
			}

			if (off + sizeof (struct MACH0_(segment_command)) > bin->size ||\
					off + sizeof (struct MACH0_(segment_command)) + size_sects > bin->size){
				bin->nsects = sect;
				return false;
			}

			if (!(bin->sects = realloc (bin->sects, bin->nsects * sizeof (struct MACH0_(section))))) {
				perror ("realloc (sects)");
				bin->nsects = sect;
				return false;
			}
			len = r_buf_fread_at (bin->b, off + sizeof (struct MACH0_(segment_command)),
				(ut8*)&bin->sects[sect],
#if R_BIN_MACH064
				bin->big_endian?"16c16c2L8I":"16c16c2l8i",
#else
				bin->big_endian?"16c16c9I":"16c16c9i",
#endif
				bin->nsects - sect);
			if (len < 1) {
				eprintf ("Error: read (sects)\n");
				bin->nsects = sect;
				return false;
			}
		} else {
			eprintf ("Warning: Invalid number of sections\n");
			bin->nsects = sect;
			return false;
		}
	}
	return true;
}

static int parse_symtab(struct MACH0_(obj_t)* bin, ut64 off) {
	struct symtab_command st;
	ut32 size_sym;

	if (off > bin->size || off + sizeof (struct symtab_command) > bin->size)
		return false;
	int len = r_buf_fread_at (bin->b, off, (ut8*)&st,
		bin->big_endian?"6I":"6i", 1);
	if (len < 1) {
		eprintf ("Error: read (symtab)\n");
		return false;
	}
	bin->symtab = NULL;
	bin->nsymtab = 0;
	if (st.strsize > 0 && st.strsize < bin->size && st.nsyms > 0) {
		bin->nsymtab = st.nsyms;
		if (st.stroff > bin->size || st.stroff + st.strsize > bin->size)
			return false;
		if (!UT32_MUL (&size_sym, bin->nsymtab, sizeof (struct MACH0_(nlist))))
			return false;
		if (!size_sym)
			return false;
		if (st.symoff > bin->size || st.symoff + size_sym > bin->size)
			return false;
		if (!(bin->symstr = calloc (1, st.strsize + 2))) {
			perror ("calloc (symstr)");
			return false;
		}
		bin->symstrlen = st.strsize;
		len = r_buf_read_at (bin->b, st.stroff, (ut8*)bin->symstr,
				st.strsize);
		if (len < 1) {
			eprintf ("Error: read (symstr)\n");
			R_FREE (bin->symstr);
			return false;
		}
		if (!(bin->symtab = calloc (bin->nsymtab,
				sizeof (struct MACH0_(nlist))))) {
			perror ("calloc (symtab)");
			return false;
		}
#if R_BIN_MACH064
		len = r_buf_fread_at (bin->b, st.symoff, (ut8*)bin->symtab,
			bin->big_endian?"I2cSL":"i2csl", bin->nsymtab);
#else
		len = r_buf_fread_at (bin->b, st.symoff, (ut8*)bin->symtab,
			bin->big_endian?"I2cSI":"i2csi", bin->nsymtab);
#endif
		if (len < 1) {
			eprintf ("Error: read (nlist)\n");
			R_FREE (bin->symtab);
			return false;
		}
	}
	return true;
}

static int parse_dysymtab(struct MACH0_(obj_t)* bin, ut64 off) {
	int len;
	ut32 size_tab;

	if (off > bin->size || off + sizeof (struct dysymtab_command) > bin->size)
		return false;

	len = r_buf_fread_at(bin->b, off, (ut8*)&bin->dysymtab, bin->big_endian?"20I":"20i", 1);
	if (len < 1) {
		eprintf ("Error: read (dysymtab)\n");
		return false;
	}
	bin->ntoc = bin->dysymtab.ntoc;
	if (bin->ntoc > 0) {
		if (!(bin->toc = calloc (bin->ntoc, sizeof(struct dylib_table_of_contents)))) {
			perror ("calloc (toc)");
			return false;
		}
		if (!UT32_MUL (&size_tab, bin->ntoc, sizeof (struct dylib_table_of_contents))){
			R_FREE (bin->toc);
			return false;
		}
		if (!size_tab){
			R_FREE (bin->toc);
			return false;
		}
		if (bin->dysymtab.tocoff > bin->size || bin->dysymtab.tocoff + size_tab > bin->size){
			R_FREE (bin->toc);
			return false;
		}
		len = r_buf_fread_at(bin->b, bin->dysymtab.tocoff,
			(ut8*)bin->toc, bin->big_endian?"2I":"2i", bin->ntoc);
		if (len < 1) {
			eprintf ("Error: read (toc)\n");
			R_FREE (bin->toc);
			return false;
		}
	}
	bin->nmodtab = bin->dysymtab.nmodtab;
	if (bin->nmodtab > 0) {
		if (!(bin->modtab = calloc (bin->nmodtab, sizeof(struct MACH0_(dylib_module))))) {
			perror ("calloc (modtab)");
			return false;
		}
		if (!UT32_MUL (&size_tab, bin->nmodtab, sizeof (struct MACH0_(dylib_module)))){
			R_FREE (bin->modtab);
			return false;
		}
		if (!size_tab){
			R_FREE (bin->modtab);
			return false;
		}
		if (bin->dysymtab.modtaboff > bin->size || \
		  bin->dysymtab.modtaboff + size_tab > bin->size){
			R_FREE (bin->modtab);
			return false;
		}
#if R_BIN_MACH064
		len = r_buf_fread_at (bin->b, bin->dysymtab.modtaboff,
			(ut8*)bin->modtab, bin->big_endian?"12IL":"12il", bin->nmodtab);
#else
		len = r_buf_fread_at (bin->b, bin->dysymtab.modtaboff,
			(ut8*)bin->modtab, bin->big_endian?"13I":"13i", bin->nmodtab);
#endif
		if (len == -1) {
			eprintf ("Error: read (modtab)\n");
			R_FREE (bin->modtab);
			return false;
		}
	}
	bin->nindirectsyms = bin->dysymtab.nindirectsyms;
	if (bin->nindirectsyms > 0) {
		if (!(bin->indirectsyms = calloc (bin->nindirectsyms, sizeof(ut32)))) {
			perror ("calloc (indirectsyms)");
			return false;
		}
		if (!UT32_MUL (&size_tab, bin->nindirectsyms, sizeof (ut32))){
			R_FREE (bin->indirectsyms);
			return false;
		}
		if (!size_tab){
			R_FREE (bin->indirectsyms);
			return false;
		}
		if (bin->dysymtab.indirectsymoff > bin->size || \
				bin->dysymtab.indirectsymoff + size_tab > bin->size){
			R_FREE (bin->indirectsyms);
			return false;
		}

		len = r_buf_fread_at (bin->b, bin->dysymtab.indirectsymoff,
				(ut8*)bin->indirectsyms, bin->big_endian?"I":"i", bin->nindirectsyms);
		if (len == -1) {
			eprintf ("Error: read (indirect syms)\n");
			R_FREE (bin->indirectsyms);
			return false;
		}
	}
	/* TODO extrefsyms, extrel, locrel */
	return true;
}

static bool parse_signature(struct MACH0_(obj_t) *bin, ut64 off) {
    	int i, len;
	ut32 count, data;
	struct linkedit_data_command link = {};
	if (off > bin->size || off + sizeof (struct linkedit_data_command) > bin->size) {
		return false;
	}
	len = r_buf_fread_at (bin->b, off, (ut8*)&link, bin->big_endian ? "4I" : "4i", 1);
	if (len < 1) {
		eprintf ("Failed to get data while parsing LC_CODE_SIGNATURE command\n");
		return false;
	}
	data = link.dataoff;
	if (data > bin->size || data + sizeof (struct super_blob_t) > bin->size) {
		return false;
	}
	struct super_blob_t *super = (struct super_blob_t *) (bin->b->buf + data);
	count = r_read_ble32 (&super->count, little_);
	for (i = 0; i < count; ++i) {
		if ((ut8 *)(super->index + i + 1) > (ut8 *)(bin->b->buf + bin->size)) {
			break;
		}
		//int slot = r_read_ble32 (&super->index[i].type, little_);
		if (r_read_ble32 (&super->index[i].type, little_) == CSSLOT_ENTITLEMENTS) {
			ut32 begin = r_read_ble32 (&super->index[i].offset, little_);
			if (begin > bin->size || begin + sizeof (struct blob_t) > bin->size) {
				break;
			}
			struct blob_t *entitlements = (struct blob_t *) ((ut8*)super + begin);
			len = r_read_ble32 (&entitlements->length, little_) - sizeof(struct blob_t);
			if (len <= bin->size && len > 1) {
				bin->signature = calloc (1, len + 1);
				if (bin->signature) {
					memcpy (bin->signature, entitlements + 1, len);
					bin->signature[len] = '\0';
					return true;
				}
			}
			break;
		}
	}
	return false;
}

static int parse_thread(struct MACH0_(obj_t)* bin, struct load_command *lc, ut64 off, bool is_first_thread) {
	ut64 ptr_thread, pc = UT64_MAX, pc_offset = UT64_MAX;
	ut32 flavor, count;
	ut8 *arw_ptr = NULL;
	int arw_sz, len = 0;

	if (off > bin->size || off + sizeof (struct thread_command) > bin->size)
		return false;

	len = r_buf_fread_at (bin->b, off, (ut8*)&bin->thread,
		bin->big_endian?"2I":"2i", 1);
	if (len < 1)
		goto wrong_read;

	len = r_buf_fread_at(bin->b, off + sizeof(struct thread_command),
		(ut8*)&flavor, bin->big_endian?"1I":"1i", 1);
	if (len == -1)
		goto wrong_read;

	if (off + sizeof(struct thread_command) + sizeof(flavor) > bin->size || \
	  off + sizeof(struct thread_command) + sizeof(flavor) + sizeof (ut32) > bin->size)
		return false;

	// TODO: use count for checks
	len = r_buf_fread_at(bin->b, off + sizeof(struct thread_command) + sizeof(flavor),
		(ut8*)&count, bin->big_endian?"1I":"1i", 1);
	if (len == -1)
		goto wrong_read;

	ptr_thread = off + sizeof(struct thread_command) + sizeof(flavor) + sizeof(count);

	if (ptr_thread > bin->size)
		return false;

	switch (bin->hdr.cputype) {
	case CPU_TYPE_I386:
	case CPU_TYPE_X86_64:
		switch (flavor) {
		case X86_THREAD_STATE32:
			if (ptr_thread + sizeof (struct x86_thread_state32) > bin->size)
				return false;
			if ((len = r_buf_fread_at (bin->b, ptr_thread,
				(ut8*)&bin->thread_state.x86_32, "16i", 1)) == -1) {
				eprintf ("Error: read (thread state x86_32)\n");
				return false;
			}
			pc = bin->thread_state.x86_32.eip;
			pc_offset = ptr_thread + r_offsetof(struct x86_thread_state32, eip);
			arw_ptr = (ut8 *)&bin->thread_state.x86_32;
			arw_sz = sizeof (struct x86_thread_state32);
			break;
		case X86_THREAD_STATE64:
			if (ptr_thread + sizeof (struct x86_thread_state64) > bin->size)
				return false;
			if ((len = r_buf_fread_at (bin->b, ptr_thread,
				(ut8*)&bin->thread_state.x86_64, "32l", 1)) == -1) {
				eprintf ("Error: read (thread state x86_64)\n");
				return false;
			}
			pc = bin->thread_state.x86_64.rip;
			pc_offset = ptr_thread + r_offsetof(struct x86_thread_state64, rip);
			arw_ptr = (ut8 *)&bin->thread_state.x86_64;
			arw_sz = sizeof (struct x86_thread_state64);
			break;
		//default: eprintf ("Unknown type\n");
		}
		break;
	case CPU_TYPE_POWERPC:
	case CPU_TYPE_POWERPC64:
		if (flavor == X86_THREAD_STATE32) {
			if (ptr_thread + sizeof (struct ppc_thread_state32) > bin->size)
				return false;
			if ((len = r_buf_fread_at (bin->b, ptr_thread,
				(ut8*)&bin->thread_state.ppc_32, bin->big_endian?"40I":"40i", 1)) == -1) {
				eprintf ("Error: read (thread state ppc_32)\n");
				return false;
			}
			pc = bin->thread_state.ppc_32.srr0;
			pc_offset = ptr_thread + r_offsetof(struct ppc_thread_state32, srr0);
			arw_ptr = (ut8 *)&bin->thread_state.ppc_32;
			arw_sz = sizeof (struct ppc_thread_state32);
		} else if (flavor == X86_THREAD_STATE64) {
			if (ptr_thread + sizeof (struct ppc_thread_state64) > bin->size)
				return false;
			if ((len = r_buf_fread_at (bin->b, ptr_thread,
				(ut8*)&bin->thread_state.ppc_64, bin->big_endian?"34LI3LI":"34li3li", 1)) == -1) {
				eprintf ("Error: read (thread state ppc_64)\n");
				return false;
			}
			pc = bin->thread_state.ppc_64.srr0;
			pc_offset = ptr_thread + r_offsetof(struct ppc_thread_state64, srr0);
			arw_ptr = (ut8 *)&bin->thread_state.ppc_64;
			arw_sz = sizeof (struct ppc_thread_state64);
		}
		break;
	case CPU_TYPE_ARM:
		if (ptr_thread + sizeof (struct arm_thread_state32) > bin->size)
			return false;
		if ((len = r_buf_fread_at (bin->b, ptr_thread,
				(ut8*)&bin->thread_state.arm_32, bin->big_endian?"17I":"17i", 1)) == -1) {
			eprintf ("Error: read (thread state arm)\n");
			return false;
		}
		pc = bin->thread_state.arm_32.r15;
		pc_offset = ptr_thread + r_offsetof (struct arm_thread_state32, r15);
		arw_ptr = (ut8 *)&bin->thread_state.arm_32;
		arw_sz = sizeof (struct arm_thread_state32);
		break;
	case CPU_TYPE_ARM64:
		if (ptr_thread + sizeof (struct arm_thread_state64) > bin->size)
			return false;
		if ((len = r_buf_fread_at(bin->b, ptr_thread,
				(ut8*)&bin->thread_state.arm_64, bin->big_endian?"34LI1I":"34Li1i", 1)) == -1) {
			eprintf ("Error: read (thread state arm)\n");
			return false;
		}
		pc = bin->thread_state.arm_64.pc;
		pc_offset = ptr_thread + r_offsetof(struct arm_thread_state64, pc);
		arw_ptr = (ut8*)&bin->thread_state.arm_64;
		arw_sz = sizeof (struct arm_thread_state64);
		break;
	default:
		eprintf ("Error: read (unknown thread state structure)\n");
		return false;
	}

	// TODO: this shouldnt be an eprintf...
	if (arw_ptr && arw_sz > 0) {
		int i;
		ut8 *p = arw_ptr;
		eprintf ("arw ");
		for (i=0; i< arw_sz; i++) {
			eprintf ("%02x", 0xff & p[i]);
		}
		eprintf ("\n");
	}

	if (is_first_thread) {
		bin->main_cmd = *lc;
		if (pc != UT64_MAX)
			bin->entry = pc;
		if (pc_offset != UT64_MAX)
			sdb_num_set (bin->kv, "mach0.entry.offset", pc_offset, 0);
	}

	return true;

wrong_read:
	eprintf("Error: read (thread)\n");
	return false;
}

static int parse_function_starts (struct MACH0_(obj_t)* bin, ut64 off) {
	struct linkedit_data_command fc;
	ut8 *buf;
	int len;
	if (off > bin->size || off + sizeof (struct linkedit_data_command) > bin->size) {
		eprintf ("Likely overflow while parsing"
			" LC_FUNCTION_STARTS command\n");
	}
	bin->func_start = NULL;
	len = r_buf_fread_at (bin->b, off, (ut8*)&fc, bin->big_endian ? "4I" : "4i", 1);
	if (len < 1) {
		eprintf ("Failed to get data while parsing"
			" LC_FUNCTION_STARTS command\n");
	}
	buf = calloc (1, fc.datasize + 1);
	if (!buf) {
		eprintf ("Failed to allocate buffer\n");
		return false;
	}
	bin->func_size = fc.datasize;
	if (fc.dataoff > bin->size || fc.dataoff + fc.datasize > bin->size) {
		free (buf);
		eprintf ("Likely overflow while parsing "
			"LC_FUNCTION_STARTS command\n");
		return false;
	}
	len = r_buf_read_at (bin->b, fc.dataoff, buf, fc.datasize);
	if (len != fc.datasize) {
		free (buf);
		eprintf ("Failed to get data while parsing"
			" LC_FUNCTION_STARTS\n");
		return false;
	}
	buf[fc.datasize] = 0; // null-terminated buffer
	bin->func_start = buf;
	return true;
}

static int parse_dylib(struct MACH0_(obj_t)* bin, ut64 off) {
	struct dylib_command dl;
	int lib, len;

	if (off > bin->size || off + sizeof (struct dylib_command) > bin->size)
		return false;
	lib = bin->nlibs - 1;

	if (!(bin->libs = realloc (bin->libs, bin->nlibs * R_BIN_MACH0_STRING_LENGTH))) {
		perror ("realloc (libs)");
		return false;
	}
	len = r_buf_fread_at (bin->b, off, (ut8*)&dl, bin->big_endian?"6I":"6i", 1);
	if (len < 1) {
		eprintf ("Error: read (dylib)\n");
		return false;
	}

	if (off + dl.dylib.name.offset > bin->size ||\
	  off + dl.dylib.name.offset + R_BIN_MACH0_STRING_LENGTH > bin->size)
		return false;

	len = r_buf_read_at (bin->b, off+dl.dylib.name.offset, (ut8*)bin->libs[lib], R_BIN_MACH0_STRING_LENGTH);
	if (len < 1) {
		eprintf ("Error: read (dylib str)");
		return false;
	}
	return true;
}

static int init_items(struct MACH0_(obj_t)* bin) {
	struct load_command lc = {0, 0};
	bool is_first_thread = true;
	ut64 off = 0LL;
	int i, len;

	bin->uuidn = 0;
	bin->os = 0;
	bin->has_crypto = 0;
	if (bin->hdr.sizeofcmds > bin->size) {
		eprintf ("Warning: chopping hdr.sizeofcmds\n");
		bin->hdr.sizeofcmds = bin->size - 128;
		//return false;
	}
	//eprintf ("Commands: %d\n", bin->hdr.ncmds);
	for (i = 0, off = sizeof (struct MACH0_(mach_header)); \
			i < bin->hdr.ncmds; i++, off += lc.cmdsize) {
		if (off > bin->size || off + sizeof (struct load_command) > bin->size){
			eprintf ("mach0: out of bounds command\n");
			return false;
		}
		len = r_buf_fread_at (bin->b, off, (ut8*)&lc, bin->big_endian?"2I":"2i", 1);
		if (len < 1) {
			eprintf ("Error: read (lc) at 0x%08"PFMT64x"\n", off);
			return false;
		}
		if (lc.cmdsize < 1 || off + lc.cmdsize > bin->size) {
			eprintf ("Warning: mach0_header %d = cmdsize<1.\n", i);
			break;
		}

		// TODO: a different format for each cmd
		sdb_num_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.offset", i), off, 0);
		sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.format", i), "xd cmd size", 0);

		//eprintf ("%d\n", lc.cmd);
		switch (lc.cmd) {
		case LC_DATA_IN_CODE:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "data_in_code", 0);
			// TODO table of non-instructions in __text
			break;
		case LC_RPATH:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "rpath", 0);
			//eprintf ("--->\n");
			break;
		case LC_SEGMENT_64:
		case LC_SEGMENT:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "segment", 0);
			bin->nsegs++;
			if (!parse_segments (bin, off)) {
				eprintf ("error parsing segment\n");
				bin->nsegs--;
				return false;
			}
			break;
		case LC_SYMTAB:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "symtab", 0);
			if (!parse_symtab (bin, off)) {
				eprintf ("error parsing symtab\n");
				return false;
			}
			break;
		case LC_DYSYMTAB:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "dysymtab", 0);
			if (!parse_dysymtab(bin, off)) {
				eprintf ("error parsing dysymtab\n");
				return false;
			}
			break;
		case LC_DYLIB_CODE_SIGN_DRS:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "dylib_code_sign_drs", 0);
			//eprintf ("[mach0] code is signed\n");
			break;
		case LC_VERSION_MIN_MACOSX:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "version_min_macosx", 0);
			bin->os = 1;
			// set OS = osx
			//eprintf ("[mach0] Requires OSX >= x\n");
			break;
		case LC_VERSION_MIN_IPHONEOS:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "version_min_iphoneos", 0);
			bin->os = 2;
			// set OS = ios
			//eprintf ("[mach0] Requires iOS >= x\n");
			break;
		case LC_VERSION_MIN_TVOS:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "version_min_tvos", 0);
			bin->os = 4;
			break;
		case LC_VERSION_MIN_WATCHOS:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "version_min_watchos", 0);
			bin->os = 3;
			break;
		case LC_UUID:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "uuid", 0);
			{
			struct uuid_command uc = {0};
			if (off + sizeof (struct uuid_command) > bin->size) {
				eprintf ("UUID out of obunds\n");
				return false;
			}
			if (r_buf_fread_at (bin->b, off, (ut8*)&uc, "24c", 1) != -1) {
				char key[128];
				char val[128];
				snprintf (key, sizeof (key)-1, "uuid.%d", bin->uuidn++);
				r_hex_bin2str ((ut8*)&uc.uuid, 16, val);
				sdb_set (bin->kv, key, val, 0);
				//for (i=0;i<16; i++) eprintf ("%02x%c", uc.uuid[i], (i==15)?'\n':'-');
			}
			}
			break;
		case LC_ENCRYPTION_INFO_64:
			/* TODO: the struct is probably different here */
		case LC_ENCRYPTION_INFO:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "encryption_info", 0);
			{
			struct MACH0_(encryption_info_command) eic = {0};
			if (off + sizeof (struct MACH0_(encryption_info_command)) > bin->size) {
				eprintf ("encryption info out of bounds\n");
				return false;
			}
			if (r_buf_fread_at (bin->b, off, (ut8*)&eic, bin->big_endian?"5I":"5i", 1) != -1) {
				bin->has_crypto = eic.cryptid;
				sdb_set (bin->kv, "crypto", "true", 0);
				sdb_num_set (bin->kv, "cryptid", eic.cryptid, 0);
				sdb_num_set (bin->kv, "cryptoff", eic.cryptoff, 0);
				sdb_num_set (bin->kv, "cryptsize", eic.cryptsize, 0);
				sdb_num_set (bin->kv, "cryptheader", off, 0);
			} }
			break;
		case LC_LOAD_DYLINKER:
			{
				sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "dylinker", 0);
				free (bin->intrp);
				bin->intrp = NULL;
				//eprintf ("[mach0] load dynamic linker\n");
				struct dylinker_command dy = {0};
				if (off + sizeof (struct dylinker_command) > bin->size){
					eprintf ("Warning: Cannot parse dylinker command\n");
					return false;
				}
				if (r_buf_fread_at (bin->b, off, (ut8*)&dy,
							bin->big_endian?"3I":"3i", 1) == -1) {
					eprintf ("Warning: read (LC_DYLD_INFO) at 0x%08"PFMT64x"\n", off);
				} else {
					int len = dy.cmdsize;
					char *buf = malloc (len+1);
					if (buf) {
						r_buf_read_at (bin->b, off + 0xc, (ut8*)buf, len);
						buf[len] = 0;
						free (bin->intrp);
						bin->intrp = buf;
					}
				}
			}
			break;
		case LC_MAIN:
			{
			struct {
				ut64 eo;
				ut64 ss;
			} ep = {0};
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "main", 0);

			if (!is_first_thread) {
				eprintf("Error: LC_MAIN with other threads\n");
				return false;
			}
			if (off+8 > bin->size || off + sizeof (ep) > bin->size) {
				eprintf ("invalid command size for main\n");
				return false;
			}
			r_buf_fread_at (bin->b, off+8, (void*)&ep,
				bin->big_endian?"2L": "2l", 1);
			bin->entry = ep.eo;
			bin->main_cmd = lc;

			sdb_num_set (bin->kv, "mach0.entry.offset", off+8, 0);
			sdb_num_set (bin->kv, "stacksize", ep.ss, 0);

			is_first_thread = false;
			}
			break;
		case LC_UNIXTHREAD:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "unixthread", 0);
			if (!is_first_thread) {
				eprintf("Error: LC_UNIXTHREAD with other threads\n");
				return false;
			}
		case LC_THREAD:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "thread", 0);
			if (!parse_thread(bin, &lc, off, is_first_thread)) {
				eprintf ("Cannot parse thread\n");
				return false;
			}
			is_first_thread = false;
			break;
		case LC_LOAD_DYLIB:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "load_dylib", 0);
			bin->nlibs++;
			if (!parse_dylib(bin, off)){
				eprintf ("Cannot parse dylib\n");
				bin->nlibs--;
				return false;
			}
			break;
		case LC_DYLD_INFO:
		case LC_DYLD_INFO_ONLY:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "dyld_info", 0);
			bin->dyld_info = malloc (sizeof(struct dyld_info_command));
			if (off + sizeof (struct dyld_info_command) > bin->size){
				eprintf ("Cannot parse dyldinfo\n");
				free (bin->dyld_info);
				return false;
			}
			if (r_buf_fread_at (bin->b, off, (ut8*)bin->dyld_info, bin->big_endian?"12I":"12i", 1) == -1) {
				free (bin->dyld_info);
				bin->dyld_info = NULL;
				eprintf ("Error: read (LC_DYLD_INFO) at 0x%08"PFMT64x"\n", off);
			}
			break;
		case LC_CODE_SIGNATURE:
			parse_signature (bin, off);
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "signature", 0);
			/* ut32 dataoff
			// ut32 datasize */
			break;
		case LC_SOURCE_VERSION:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "version", 0);
			/* uint64_t  version;  */
			/* A.B.C.D.E packed as a24.b10.c10.d10.e10 */
			//eprintf ("mach0: TODO: Show source version\n");
			break;
		case LC_SEGMENT_SPLIT_INFO:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "split_info", 0);
			/* TODO */
			break;
		case LC_FUNCTION_STARTS:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "function_starts", 0);
			if (!parse_function_starts (bin, off)) {
				eprintf ("Cannot parse LC_FUNCTION_STARTS\n");
			}
			break;
		case LC_REEXPORT_DYLIB:
			sdb_set (bin->kv, sdb_fmt (0, "mach0_cmd_%d.cmd", i), "dylib", 0);
			/* TODO */
			break;
		default:
			//eprintf ("mach0: Unknown header command %x\n", lc.cmd);
			break;
		}
	}
	return true;
}

static int init(struct MACH0_(obj_t)* bin) {
	union {
		ut16 word;
		ut8 byte[2];
	} endian = { 1 };
	little_ = endian.byte[0];
	if (!init_hdr(bin)) {
		eprintf ("Warning: File is not MACH0\n");
		return false;
	}
	if (!init_items(bin))
		eprintf ("Warning: Cannot initialize items\n");

	bin->baddr = MACH0_(get_baddr)(bin);
	return true;
}

void* MACH0_(mach0_free)(struct MACH0_(obj_t)* bin) {
	if (!bin) return NULL;
	free (bin->segs);
	free (bin->sects);
	free (bin->symtab);
	free (bin->symstr);
	free (bin->indirectsyms);
	free (bin->imports_by_ord);
	free (bin->dyld_info);
	free (bin->toc);
	free (bin->modtab);
	free (bin->libs);
	free (bin->func_start);
	free (bin->signature);
	r_buf_free (bin->b);
	free (bin);
	return NULL;
}

struct MACH0_(obj_t)* MACH0_(mach0_new)(const char* file) {
	ut8 *buf;
	struct MACH0_(obj_t) *bin;

	if (!(bin = malloc (sizeof (struct MACH0_(obj_t)))))
		return NULL;
	memset (bin, 0, sizeof (struct MACH0_(obj_t)));
	bin->file = file;
	if (!(buf = (ut8*)r_file_slurp(file, &bin->size)))
		return MACH0_(mach0_free)(bin);
	bin->b = r_buf_new ();
	if (!r_buf_set_bytes(bin->b, buf, bin->size)) {
		free (buf);
		return MACH0_(mach0_free)(bin);
	}
	free (buf);

	bin->dyld_info = NULL;

	if (!init(bin))
		return MACH0_(mach0_free)(bin);

	bin->imports_by_ord_size = 0;
	bin->imports_by_ord = NULL;

	return bin;
}

struct MACH0_(obj_t)* MACH0_(new_buf)(RBuffer *buf) {
	struct MACH0_(obj_t) *bin = R_NEW0 (struct MACH0_(obj_t));
	if (!bin) return NULL;
	bin->kv = sdb_new (NULL, "bin.mach0", 0);
	bin->b = r_buf_new ();
	bin->size = buf->length;
	if (!r_buf_set_bytes (bin->b, buf->buf, bin->size)){
		return MACH0_(mach0_free) (bin);
	}
	if (!init(bin))
		return MACH0_(mach0_free)(bin);
	return bin;
}

// prot: r = 1, w = 2, x = 4
// perm: r = 4, w = 2, x = 1
static int prot2perm (int x) {
	int r = 0;
	if (x&1) r |= 4;
	if (x&2) r |= 2;
	if (x&4) r |= 1;
	return r;
}

struct section_t* MACH0_(get_sections)(struct MACH0_(obj_t)* bin) {
	struct section_t *sections;
	char segname[32], sectname[32];
	int i, j, to;

	if (!bin)
		return NULL;
	/* for core files */
	if (bin->nsects <1 && bin->nsegs > 0) {
		struct MACH0_(segment_command) *seg;
		if (!(sections = calloc ((bin->nsegs + 1), sizeof (struct section_t)))) {
			return NULL;
		}
		for (i = 0; i < bin->nsegs; i++) {
			seg = &bin->segs[i];
			sections[i].addr = seg->vmaddr;
			sections[i].offset = seg->fileoff;
			sections[i].size = seg->vmsize;
			sections[i].align = 4096;
			sections[i].flags = seg->flags;
			r_str_ncpy (sectname, seg->segname, sizeof (sectname)-1);
			// hack to support multiple sections with same name
			sections[i].srwx = prot2perm (seg->initprot);
			sections[i].last = 0;
		}
		sections[i].last = 1;
		return sections;
	}

	if (!bin->sects)
		return NULL;
	to = R_MIN (bin->nsects, 128); // limit number of sections here to avoid fuzzed bins
	if (to < 1)
		return NULL;
	if (!(sections = malloc ((bin->nsects + 1) * sizeof (struct section_t))))
		return NULL;
	for (i = 0; i < to; i++) {
		sections[i].offset = (ut64)bin->sects[i].offset;
		sections[i].addr = (ut64)bin->sects[i].addr;
		sections[i].size = (ut64)bin->sects[i].size;
		sections[i].align = bin->sects[i].align;
		sections[i].flags = bin->sects[i].flags;
		r_str_ncpy (sectname, bin->sects[i].sectname, sizeof (sectname)-1);
		// hack to support multiple sections with same name
		snprintf (segname, sizeof (segname), "%d", i); // wtf
		for (j=0; j<bin->nsegs; j++) {
			if (sections[i].addr >= bin->segs[j].vmaddr &&
				sections[i].addr < (bin->segs[j].vmaddr + bin->segs[j].vmsize)) {
				sections[i].srwx = prot2perm (bin->segs[j].initprot);
				break;
			}
		}
		// XXX: if two sections have the same name are merged :O
		// XXX: append section index in flag name maybe?
		// XXX: do not load out of bound sections?
		// XXX: load segments instead of sections? what about PAGEZERO and ...
		snprintf (sections[i].name, sizeof (sections[i].name), "%s.%s", segname, sectname);
		sections[i].last = 0;
	}
	sections[i].last = 1;
	return sections;
}

static int parse_import_stub(struct MACH0_(obj_t)* bin, struct symbol_t *symbol, int idx) {
	int i, j, nsyms, stridx;
	const char *symstr;

	if (idx<0)
		return 0;
	symbol->offset = 0LL;
	symbol->addr = 0LL;
	symbol->name[0] = '\0';

	if (!bin || !bin->sects) return false;
	for (i = 0; i < bin->nsects; i++) {
		if ((bin->sects[i].flags & SECTION_TYPE) == S_SYMBOL_STUBS &&
				bin->sects[i].reserved2 > 0) {
			nsyms = (int)(bin->sects[i].size / bin->sects[i].reserved2);
			if (nsyms > bin->size) {
				eprintf ("mach0: Invalid symbol table size\n");
			}
			for (j = 0; j < nsyms; j++) {
				if (bin->sects) {
					if (bin->sects[i].reserved1 + j >= bin->nindirectsyms)
						continue;
				}
				if (bin->indirectsyms) {
					if (idx != bin->indirectsyms[bin->sects[i].reserved1 + j])
						continue;
				}
				if (idx > bin->nsymtab) {
					continue;
				}
				symbol->type = R_BIN_MACH0_SYMBOL_TYPE_LOCAL;
				symbol->offset = bin->sects[i].offset + j * bin->sects[i].reserved2;
				symbol->addr = bin->sects[i].addr + j * bin->sects[i].reserved2;
				symbol->size = 0;
				stridx = bin->symtab[idx].n_un.n_strx;
				if (stridx >= 0 && stridx < bin->symstrlen) {
					symstr = (char *)bin->symstr+stridx;
				} else {
					symstr = "???";
				}

				// Remove the extra underscore that every import seems to have in Mach-O.
				if (*symstr == '_') symstr++;

				snprintf (symbol->name, R_BIN_MACH0_STRING_LENGTH,
					"imp.%s", symstr);
				return true;
			}
		}
	}
	return false;
}

#if 0
static ut64 get_text_base(struct MACH0_(obj_t)* bin) {
	ut64 ret = 0LL;
	struct section_t *sections;
	if ((sections = MACH0_(get_sections) (bin))) {
		int i;
		for (i = 0; !sections[i].last; i++) {
			if (strstr(sections[i].name, "text")) {
				ret =  sections[i].offset;
				break;
			}
		}
		free (sections);
	}
	return ret;
}
#endif

static int inSymtab (Sdb *db, struct symbol_t *symbols, int last, const char *name, ut64 addr) {
	const char *key = sdb_fmt (0, "%s.%"PFMT64x, name, addr);
	if (sdb_const_get (db, key, NULL))
		return true;
	sdb_set (db, key, "1", 0);
	return false;
}

struct symbol_t* MACH0_(get_symbols)(struct MACH0_(obj_t)* bin) {
	const char *symstr;
	struct symbol_t *symbols;
	int from, to, i, j, s, stridx, symbols_size, symbols_count;
	Sdb *db;
	//ut64 text_base = get_text_base (bin);

	if (!bin || !bin->symtab || !bin->symstr)
		return NULL;
	/* parse symbol table */
	/* parse dynamic symbol table */
	symbols_count = (bin->dysymtab.nextdefsym + \
			bin->dysymtab.nlocalsym + \
			bin->dysymtab.nundefsym );
	symbols_count += bin->nsymtab;
	//symbols_count = bin->nsymtab;
	symbols_size = (symbols_count+1)*2 * sizeof (struct symbol_t);

	if (symbols_size < 1)
		return NULL;

	if (!(symbols = calloc (1, symbols_size)))
		return NULL;
	db = sdb_new0 ();

	j = 0; // symbol_idx
	for (s = 0; s < 2; s++) {
		switch (s) {
		case 0:
			from = bin->dysymtab.iextdefsym;
			to = from + bin->dysymtab.nextdefsym;
			break;
		case 1:
			from = bin->dysymtab.ilocalsym;
			to = from + bin->dysymtab.nlocalsym;
			break;
#if NOT_USED
		case 2:
			from = bin->dysymtab.iundefsym;
			to = from + bin->dysymtab.nundefsym;
			break;
#endif
		}
		if (from == to)
			continue;
#define OLD 1
#if OLD
		from = R_MIN (R_MAX (0, from), symbols_size / sizeof (struct symbol_t));
		to = R_MIN (to , symbols_size / sizeof (struct symbol_t));
		to = R_MIN (to, bin->nsymtab);
#else
		from = R_MIN (R_MAX (0, from), symbols_size/sizeof(struct symbol_t));
		to = symbols_count; //symbols_size/sizeof(struct symbol_t);
#endif
		int maxsymbols = symbols_size / sizeof(struct symbol_t);
		if (to>0x500000) {
			eprintf ("WARNING: corrupted mach0 header: symbol table is too big %d\n", to);
			free (symbols);
			sdb_free (db);
			return NULL;
		}
		if (symbols_count >= maxsymbols) {
			symbols_count = maxsymbols - 1;
		}
		for (i = from; i < to && j < symbols_count; i++, j++) {
			symbols[j].offset = addr_to_offset (bin, bin->symtab[i].n_value);
			symbols[j].addr = bin->symtab[i].n_value;
			symbols[j].size = 0; /* TODO: Is it anywhere? */
			if (bin->symtab[i].n_type & N_EXT)
				symbols[j].type = R_BIN_MACH0_SYMBOL_TYPE_EXT;
			else symbols[j].type = R_BIN_MACH0_SYMBOL_TYPE_LOCAL;
			stridx = bin->symtab[i].n_un.n_strx;
			if (stridx>=0 && stridx<bin->symstrlen)
				symstr = (char*)bin->symstr+stridx;
			else symstr = "???";
			{
				int i = 0;
				int len = 0;
				len = bin->symstrlen - stridx;
				if (len>0) {
					for (i = 0; i<len; i++) {
						if ((ut8)(symstr[i]&0xff) == 0xff || !symstr[i]) {
							len = i;
							break;
						}
					}
					char *symstr_dup = NULL;
					if (len>0) symstr_dup = r_str_ndup (symstr, len);
					if (!symstr_dup) {
						symbols[j].name[0] = 0;
					} else {
						strncpy (symbols[j].name, symstr_dup, R_BIN_MACH0_STRING_LENGTH-1);
						symbols[j].name[R_BIN_MACH0_STRING_LENGTH - 2] = 0;
					}
					free (symstr_dup);
				} else {
					symbols[j].name[0] = 0;
				}
				symbols[j].last = 0;
			}
			if (inSymtab (db, symbols, j, symbols[j].name, symbols[j].addr)) {
				symbols[j].name[0] = 0;
				j--;
			}
		}
	}
	to = R_MIN (bin->nsymtab, bin->dysymtab.iundefsym + bin->dysymtab.nundefsym);
	for (i = bin->dysymtab.iundefsym; i < to; i++) {
		if (j > symbols_count) {
			eprintf ("Error: %s at %d\n", __FILE__,__LINE__);
			break;
		}
		if (parse_import_stub(bin, &symbols[j], i))
			symbols[j++].last = 0;
	}

#if 1
// symtab is wrongly parsed and produces dupped syms with incorrect vaddr */
	for (i=0; i < bin->nsymtab; i++) {
		struct MACH0_(nlist) *st = &bin->symtab[i];
#if 0
		eprintf ("stridx %d -> section %d type %d value = %d\n",
			st->n_un.n_strx, st->n_sect, st->n_type, st->n_value);
#endif
		stridx = st->n_un.n_strx;
		if (stridx>=0 && stridx<bin->symstrlen)
			symstr = (char*)bin->symstr+stridx;
		else symstr = "???";
		// 0 is for imports
		// 1 is for symbols
		// 2 is for func.eh (exception handlers?)
		int section = st->n_sect;
		if (section == 1 && j < symbols_count) { // text ??st->n_type == 1)
			/* is symbol */
			symbols[j].addr = st->n_value; // + text_base;
			symbols[j].offset = addr_to_offset (bin, symbols[j].addr);
			symbols[j].size = 0; /* find next symbol and crop */
			if (st->n_type & N_EXT)
				symbols[j].type = R_BIN_MACH0_SYMBOL_TYPE_EXT;
			else symbols[j].type = R_BIN_MACH0_SYMBOL_TYPE_LOCAL;
			strncpy (symbols[j].name, symstr, R_BIN_MACH0_STRING_LENGTH);
			symbols[j].name[R_BIN_MACH0_STRING_LENGTH-1] = 0;
			symbols[j].last = 0;
			if (inSymtab (db, symbols, j, symbols[j].name, symbols[j].addr)) {
				symbols[j].name[0] = 0;
			} else {
				j++;
			}
		}
	}
#endif
	sdb_free (db);
	symbols[j].last = 1;
	return symbols;
}

static int parse_import_ptr(struct MACH0_(obj_t)* bin, struct reloc_t *reloc, int idx) {
	int i, j, sym, wordsize;
	ut32 stype;
	wordsize = MACH0_(get_bits)(bin) / 8;
	if (idx<0 || idx>= bin->nsymtab)
		return 0;
	if ((bin->symtab[idx].n_desc & REFERENCE_TYPE) == REFERENCE_FLAG_UNDEFINED_LAZY)
		stype = S_LAZY_SYMBOL_POINTERS;
	else stype = S_NON_LAZY_SYMBOL_POINTERS;

	reloc->offset = 0;
	reloc->addr = 0;
	reloc->addend = 0;
#define CASE(T) case (T / 8): reloc->type = R_BIN_RELOC_ ## T; break
	switch (wordsize) {
		CASE(8);
		CASE(16);
		CASE(32);
		CASE(64);
		default: return false;
	}
#undef CASE

	for (i = 0; i < bin->nsects; i++) {
		if ((bin->sects[i].flags & SECTION_TYPE) == stype) {
			for (j=0, sym=-1; bin->sects[i].reserved1+j < bin->nindirectsyms; j++)
				if (idx == bin->indirectsyms[bin->sects[i].reserved1 + j]) {
					sym = j;
					break;
				}

			reloc->offset = sym == -1 ? 0 : bin->sects[i].offset + sym * wordsize;
			reloc->addr = sym == -1 ? 0 : bin->sects[i].addr + sym * wordsize;
			return true;
		}
	}
	return false;
}

struct import_t* MACH0_(get_imports)(struct MACH0_(obj_t)* bin) {
	struct import_t *imports;
	int i, j, idx, stridx;
	const char *symstr;

	if (!bin->symtab || !bin->symstr || !bin->sects || !bin->indirectsyms)
		return NULL;
	if (bin->dysymtab.nundefsym<1 || bin->dysymtab.nundefsym>0xfffff) {
		return NULL;
	}
	if (!(imports = malloc ((bin->dysymtab.nundefsym + 1) * sizeof(struct import_t))))
		return NULL;
	for (i = j = 0; i < bin->dysymtab.nundefsym; i++) {
		idx = bin->dysymtab.iundefsym +i;
		if (idx<0 || idx>=bin->nsymtab) {
			eprintf ("WARNING: Imports index out of bounds. Ignoring relocs\n");
			free (imports);
			return NULL;
		}
		stridx = bin->symtab[idx].n_un.n_strx;
		if (stridx >= 0 && stridx < bin->symstrlen)
			symstr = (char *)bin->symstr + stridx;
		else symstr = "";
		if (!*symstr)
			continue;
		{
			int i = 0;
			int len = 0;
			char *symstr_dup = NULL;
			len = bin->symstrlen - stridx;
			imports[j].name[0] = 0;
			if (len > 0) {
				for (i = 0; i < len; i++) {
					if ((unsigned char)symstr[i] == 0xff || !symstr[i]) {
						len = i;
						break;
					}
				}
				symstr_dup = r_str_ndup (symstr, len);
				if (symstr_dup) {
					strncpy (imports[j].name, symstr_dup, R_BIN_MACH0_STRING_LENGTH - 1);
					imports[j].name[R_BIN_MACH0_STRING_LENGTH - 2] = 0;
					free (symstr_dup);
				}
			}
		}
		imports[j].ord = i;
		imports[j++].last = 0;
	}
	imports[j].last = 1;

	if (!bin->imports_by_ord_size) {
		if (j > 0) {
			bin->imports_by_ord_size = j;
			bin->imports_by_ord = (RBinImport**)calloc (j, sizeof (RBinImport*));
		} else {
			bin->imports_by_ord_size = 0;
			bin->imports_by_ord = NULL;
		}
	}

	return imports;
}



struct reloc_t* MACH0_(get_relocs)(struct MACH0_(obj_t)* bin) {
	struct reloc_t *relocs;
	int i = 0, len;
	ulebr ur = {NULL};
	int wordsize = MACH0_(get_bits)(bin) / 8;
	if (bin->dyld_info) {
		ut8 *opcodes,*end, type = 0, rel_type = 0;
		int lib_ord, seg_idx = -1, sym_ord = -1;
		size_t j, count, skip, bind_size, lazy_size;
		st64 addend = 0;
		ut64 segmentAddress = 0LL;
		ut64 addr = 0LL;
		ut8 done = 0;

#define CASE(T) case (T / 8): rel_type = R_BIN_RELOC_ ## T; break
		switch (wordsize) {
		CASE(8);
		CASE(16);
		CASE(32);
		CASE(64);
		default: return NULL;
		}
#undef CASE
		bind_size = bin->dyld_info->bind_size;
		lazy_size = bin->dyld_info->lazy_bind_size;

		if (!bind_size || !lazy_size) {
			return NULL;
		}

		if ((bind_size + lazy_size)<1) {
			return NULL;
		}
		if (bin->dyld_info->bind_off > bin->size || bin->dyld_info->bind_off + bind_size > bin->size)
			return NULL;
		if (bin->dyld_info->lazy_bind_off > bin->size || \
			bin->dyld_info->lazy_bind_off + lazy_size > bin->size)
			return NULL;
		if (bin->dyld_info->bind_off+bind_size+lazy_size > bin->size)
			return NULL;
		// NOTE(eddyb) it's a waste of memory, but we don't know the actual number of relocs.
		if (!(relocs = calloc (1, (1 + bind_size + lazy_size) * sizeof (struct reloc_t))))
			return NULL;

		opcodes = calloc (1, bind_size + lazy_size + 1);
		if (!opcodes) {
			free (relocs);
			return NULL;
		}
		len = r_buf_read_at (bin->b, bin->dyld_info->bind_off, opcodes, bind_size);
		i = r_buf_read_at (bin->b, bin->dyld_info->lazy_bind_off, opcodes + bind_size, lazy_size);
		if (len < 1 || i < 1) {
			eprintf ("Error: read (dyld_info bind) at 0x%08"PFMT64x"\n",
			(ut64)(size_t)bin->dyld_info->bind_off);
			free (opcodes);
			relocs[i].last = 1;
			return relocs;
		}
		i = 0;
		// that +2 is a minimum required for uleb128, this may be wrong,
		// the correct fix would be to make ULEB() must use rutil's
		// implementation that already checks for buffer boundaries
		for (ur.p = opcodes, end = opcodes + bind_size + lazy_size ; (ur.p+2 < end) && !done; ) {
			ut8 imm = *ur.p & BIND_IMMEDIATE_MASK, op = *ur.p & BIND_OPCODE_MASK;
			++ur.p;
			switch (op) {
#define ULEB() read_uleb128 (&ur,end)
#define SLEB() read_sleb128 (&ur,end)
			case BIND_OPCODE_DONE:
				done = 1;
				break;
			case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
				lib_ord = imm;
				break;
			case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
				lib_ord = ULEB();
				break;
			case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
				lib_ord = imm? (st8)(BIND_OPCODE_MASK | imm) : 0;
				break;
			case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: {
				char *sym_name = (char*)ur.p;
				//ut8 sym_flags = imm;
				while (*ur.p++ && ur.p<end) {
					/* empty loop */
				}
				sym_ord = -1;
				if (bin->symtab && bin->dysymtab.nundefsym < 0xffff)
				for (j = 0; j < bin->dysymtab.nundefsym; j++) {
					int stridx = 0;
					int iundefsym = bin->dysymtab.iundefsym;
					if (iundefsym>=0 && iundefsym < bin->nsymtab) {
						int sidx = iundefsym +j;
						if (sidx<0 || sidx>= bin->nsymtab)
							continue;
						stridx = bin->symtab[sidx].n_un.n_strx;
						if (stridx < 0 || stridx >= bin->symstrlen)
							continue;
					}
					if (!strcmp ((char *)bin->symstr + stridx, sym_name)) {
						sym_ord = j;
						break;
					}
				}
				break;
			}
			case BIND_OPCODE_SET_TYPE_IMM:
				type = imm;
				break;
			case BIND_OPCODE_SET_ADDEND_SLEB:
				addend = SLEB();
				break;
			case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
				seg_idx = imm;
				if (seg_idx < 0 || seg_idx >= bin->nsegs) {
					eprintf ("Error: BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"
						" has unexistent segment %d\n", seg_idx);
					addr = 0LL;
					return 0; // early exit to avoid future mayhem
				} else {
					addr = bin->segs[seg_idx].vmaddr + ULEB();
					segmentAddress = bin->segs[seg_idx].vmaddr \
							+ bin->segs[seg_idx].vmsize;
				}
				break;
			case BIND_OPCODE_ADD_ADDR_ULEB:
				addr += ULEB();
				break;
#define DO_BIND() do {\
if (sym_ord < 0 || seg_idx < 0 ) break;\
if (i >= (bind_size + lazy_size)) break;\
relocs[i].addr = addr;\
relocs[i].offset = addr - bin->segs[seg_idx].vmaddr + bin->segs[seg_idx].fileoff;\
if (type == BIND_TYPE_TEXT_PCREL32)\
	relocs[i].addend = addend - (bin->baddr + addr);\
else relocs[i].addend = addend;\
/* library ordinal ??? */ \
relocs[i].ord = lib_ord;\
relocs[i].ord = sym_ord;\
relocs[i].type = rel_type;\
relocs[i++].last = 0;\
} while (0)
			case BIND_OPCODE_DO_BIND:
				if (addr >= segmentAddress) {
					eprintf ("Error: Malformed DO bind opcode\n");
					goto beach;
				}
				DO_BIND();
				addr += wordsize;
				break;
			case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
				if (addr >= segmentAddress) {
					eprintf ("Error: Malformed ADDR ULEB bind opcode\n");
					goto beach;
				}
				DO_BIND();
				addr += ULEB() + wordsize;
				break;
			case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
				if (addr >= segmentAddress) {
					eprintf ("Error: Malformed IMM SCALED bind opcode\n");
					goto beach;
				}
				DO_BIND();
				addr += (ut64)imm * (ut64)wordsize + wordsize;
				break;
			case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
				count = ULEB();
				skip = ULEB();
				for (j = 0; j < count; j++) {
					if (addr >= segmentAddress) {
						eprintf ("Error: Malformed ULEB TIMES bind opcode\n");
						goto beach;
					}
					DO_BIND();
					addr += skip + wordsize;
				}
				break;
#undef DO_BIND
#undef ULEB
#undef SLEB
			default:
				eprintf ("Error: unknown bind opcode 0x%02x in dyld_info\n", *ur.p);
				free (opcodes);
				relocs[i].last = 1;
				return relocs;
			}
		}
		free (opcodes);
	} else {
		int j;
		if (!bin->symtab || !bin->symstr || !bin->sects || !bin->indirectsyms)
			return NULL;
		if (!(relocs = malloc ((bin->dysymtab.nundefsym + 1) * sizeof(struct reloc_t))))
			return NULL;
		for (j = 0; j < bin->dysymtab.nundefsym; j++) {
			if (parse_import_ptr(bin, &relocs[i], bin->dysymtab.iundefsym + j)) {
				relocs[i].ord = j;
				relocs[i++].last = 0;
			}
		}
	}
beach:
	relocs[i].last = 1;

	return relocs;
}

struct addr_t* MACH0_(get_entrypoint)(struct MACH0_(obj_t)* bin) {
	struct addr_t *entry;
	int i;

	if (!bin->entry && !bin->sects)
		return NULL;
	if (!(entry = calloc (1, sizeof (struct addr_t))))
		return NULL;

	if (bin->entry) {
		entry->addr = entry_to_vaddr(bin);
		entry->offset = addr_to_offset (bin, entry->addr);
	}

	if (!bin->entry || entry->offset == 0) {
		// XXX: section name doesnt matters at all.. just check for exec flags
		for (i = 0; i < bin->nsects; i++) {
			if (!strncmp (bin->sects[i].sectname, "__text", 6)) {
				entry->offset = (ut64)bin->sects[i].offset;
				sdb_num_set (bin->kv, "mach0.entry", entry->offset, 0);
				entry->addr = (ut64)bin->sects[i].addr;
				if (!entry->addr) // workaround for object files
					entry->addr = entry->offset;
				break;
			}
		}
		bin->entry = entry->addr;
	}

	return entry;
}

struct lib_t* MACH0_(get_libs)(struct MACH0_(obj_t)* bin) {
	struct lib_t *libs;
	int i;

	if (!bin->nlibs)
		return NULL;
	if (!(libs = calloc ((bin->nlibs + 1), sizeof(struct lib_t))))
		return NULL;
	for (i = 0; i < bin->nlibs; i++) {
		strncpy (libs[i].name, bin->libs[i], R_BIN_MACH0_STRING_LENGTH);
		libs[i].name[R_BIN_MACH0_STRING_LENGTH-1] = '\0';
		libs[i].last = 0;
	}
	libs[i].last = 1;
	return libs;
}

ut64 MACH0_(get_baddr)(struct MACH0_(obj_t)* bin) {
	int i;

	if (bin->hdr.filetype != MH_EXECUTE && bin->hdr.filetype != MH_DYLINKER)
		return 0;

	for (i = 0; i < bin->nsegs; ++i)
		if (bin->segs[i].fileoff == 0 && bin->segs[i].filesize != 0)
			return bin->segs[i].vmaddr;
	return 0;
}

char* MACH0_(get_class)(struct MACH0_(obj_t)* bin) {
#if R_BIN_MACH064
	return r_str_new ("MACH064");
#else
	return r_str_new ("MACH0");
#endif
}

//XXX we are mixing up bits from cpu and opcodes
//since thumb use 16 bits opcode but run in 32 bits
//cpus  so here we should only return 32 or 64
int MACH0_(get_bits)(struct MACH0_(obj_t)* bin) {
	if (bin) {
		int bits = MACH0_(get_bits_from_hdr) (&bin->hdr);
		if (bin->hdr.cputype == CPU_TYPE_ARM && bin->entry & 1) {
			return 16;
		}
		return bits;
	} 
	return 32;
}

int MACH0_(get_bits_from_hdr)(struct MACH0_(mach_header)* hdr) {
	if (hdr->magic == MH_MAGIC_64 || hdr->magic == MH_CIGAM_64) {
		return 64;
	}
	if ((hdr->cpusubtype & CPU_SUBTYPE_MASK) == (CPU_SUBTYPE_ARM_V7K << 24)) {
		return 16;
	}
	return 32;
}

bool MACH0_(is_big_endian)(struct MACH0_(obj_t)* bin) {
	if (bin) {
		const int cpu = bin->hdr.cputype;
		return cpu == CPU_TYPE_POWERPC || cpu == CPU_TYPE_POWERPC64;
	}
	return false;
}

const char* MACH0_(get_intrp)(struct MACH0_(obj_t)* bin) {
	return bin? bin->intrp: NULL;
}

const char* MACH0_(get_os)(struct MACH0_(obj_t)* bin) {
	if (bin)
	switch (bin->os) {
	case 1: return "osx";
	case 2: return "ios";
	case 3: return "watchos";
	case 4: return "tvos";
	}
	return "darwin";
}

char* MACH0_(get_cputype_from_hdr)(struct MACH0_(mach_header) *hdr) {
	const char *archstr = "unknown";
	switch (hdr->cputype) {
	case CPU_TYPE_VAX:
		archstr = "vax";
		break;
	case CPU_TYPE_MC680x0:
		archstr = "mc680x0";
		break;
	case CPU_TYPE_I386:
	case CPU_TYPE_X86_64:
		archstr = "x86";
		break;
	case CPU_TYPE_MC88000:
		archstr = "mc88000";
		break;
	case CPU_TYPE_MC98000:
		archstr = "mc98000";
		break;
	case CPU_TYPE_HPPA:
		archstr = "hppa";
		break;
	case CPU_TYPE_ARM:
	case CPU_TYPE_ARM64:
		archstr = "arm";
		break;
	case CPU_TYPE_SPARC:
		archstr = "sparc";
		break;
	case CPU_TYPE_MIPS:
		archstr = "mips";
		break;
	case CPU_TYPE_I860:
		archstr = "i860";
		break;
	case CPU_TYPE_POWERPC:
	case CPU_TYPE_POWERPC64:
		archstr = "ppc";
	}
	return strdup (archstr);
}

char* MACH0_(get_cputype)(struct MACH0_(obj_t)* bin) {
	if (bin) {
		return MACH0_(get_cputype_from_hdr) (&bin->hdr);
	}
	return strdup ("unknown");
}

// TODO: use const char*

char* MACH0_(get_cpusubtype_from_hdr)(struct MACH0_(mach_header) *hdr) {
	if (hdr) {
		switch (hdr->cputype) {
		case CPU_TYPE_VAX:
			switch (hdr->cpusubtype) {
			case CPU_SUBTYPE_VAX_ALL:	return strdup ("all");
			case CPU_SUBTYPE_VAX780:	return strdup ("vax780");
			case CPU_SUBTYPE_VAX785:	return strdup ("vax785");
			case CPU_SUBTYPE_VAX750:	return strdup ("vax750");
			case CPU_SUBTYPE_VAX730:	return strdup ("vax730");
			case CPU_SUBTYPE_UVAXI:		return strdup ("uvaxI");
			case CPU_SUBTYPE_UVAXII:	return strdup ("uvaxII");
			case CPU_SUBTYPE_VAX8200:	return strdup ("vax8200");
			case CPU_SUBTYPE_VAX8500:	return strdup ("vax8500");
			case CPU_SUBTYPE_VAX8600:	return strdup ("vax8600");
			case CPU_SUBTYPE_VAX8650:	return strdup ("vax8650");
			case CPU_SUBTYPE_VAX8800:	return strdup ("vax8800");
			case CPU_SUBTYPE_UVAXIII:	return strdup ("uvaxIII");
			default:			return strdup ("Unknown vax subtype");
			}
		case CPU_TYPE_MC680x0:
			switch (hdr->cpusubtype) {
			case CPU_SUBTYPE_MC68030:	return strdup ("mc68030");
			case CPU_SUBTYPE_MC68040:	return strdup ("mc68040");
			case CPU_SUBTYPE_MC68030_ONLY:	return strdup ("mc68030 only");
			default:			return strdup ("Unknown mc680x0 subtype");
			}
		case CPU_TYPE_I386:
			switch (hdr->cpusubtype) {
			case CPU_SUBTYPE_386: 			return strdup ("386");
			case CPU_SUBTYPE_486: 			return strdup ("486");
			case CPU_SUBTYPE_486SX: 		return strdup ("486sx");
			case CPU_SUBTYPE_PENT: 			return strdup ("Pentium");
			case CPU_SUBTYPE_PENTPRO: 		return strdup ("Pentium Pro");
			case CPU_SUBTYPE_PENTII_M3: 		return strdup ("Pentium 3 M3");
			case CPU_SUBTYPE_PENTII_M5: 		return strdup ("Pentium 3 M5");
			case CPU_SUBTYPE_CELERON: 		return strdup ("Celeron");
			case CPU_SUBTYPE_CELERON_MOBILE:	return strdup ("Celeron Mobile");
			case CPU_SUBTYPE_PENTIUM_3:		return strdup ("Pentium 3");
			case CPU_SUBTYPE_PENTIUM_3_M:		return strdup ("Pentium 3 M");
			case CPU_SUBTYPE_PENTIUM_3_XEON:	return strdup ("Pentium 3 Xeon");
			case CPU_SUBTYPE_PENTIUM_M:		return strdup ("Pentium Mobile");
			case CPU_SUBTYPE_PENTIUM_4:		return strdup ("Pentium 4");
			case CPU_SUBTYPE_PENTIUM_4_M:		return strdup ("Pentium 4 M");
			case CPU_SUBTYPE_ITANIUM:		return strdup ("Itanium");
			case CPU_SUBTYPE_ITANIUM_2:		return strdup ("Itanium 2");
			case CPU_SUBTYPE_XEON:			return strdup ("Xeon");
			case CPU_SUBTYPE_XEON_MP:		return strdup ("Xeon MP");
			default:				return strdup ("Unknown i386 subtype");
			}
		case CPU_TYPE_X86_64:
			switch (hdr->cpusubtype & 0xff) {
			case CPU_SUBTYPE_X86_64_ALL:	return strdup ("x86 64 all");
			case CPU_SUBTYPE_X86_ARCH1:	return strdup ("x86 arch 1");
			default:			return strdup ("Unknown x86 subtype");
			}
		case CPU_TYPE_MC88000:
			switch (hdr->cpusubtype & 0xff) {
			case CPU_SUBTYPE_MC88000_ALL:	return strdup ("all");
			case CPU_SUBTYPE_MC88100:	return strdup ("mc88100");
			case CPU_SUBTYPE_MC88110:	return strdup ("mc88110");
			default:			return strdup ("Unknown mc88000 subtype");
			}
		case CPU_TYPE_MC98000:
			switch (hdr->cpusubtype & 0xff) {
			case CPU_SUBTYPE_MC98000_ALL:	return strdup ("all");
			case CPU_SUBTYPE_MC98601:	return strdup ("mc98601");
			default:			return strdup ("Unknown mc98000 subtype");
			}
		case CPU_TYPE_HPPA:
			switch (hdr->cpusubtype & 0xff) {
			case CPU_SUBTYPE_HPPA_7100:	return strdup ("hppa7100");
			case CPU_SUBTYPE_HPPA_7100LC:	return strdup ("hppa7100LC");
			default:			return strdup ("Unknown hppa subtype");
			}
		case CPU_TYPE_ARM64:
			return strdup ("v8");
		case CPU_TYPE_ARM:
			switch (hdr->cpusubtype & 0xff) {
			case CPU_SUBTYPE_ARM_ALL:
				return strdup ("all");
			case CPU_SUBTYPE_ARM_V4T:
				return strdup ("v4t");
			case CPU_SUBTYPE_ARM_V6:
				return strdup ("v6");
			case CPU_SUBTYPE_ARM_V5TEJ:
				return strdup ("v5tej");
			case CPU_SUBTYPE_ARM_XSCALE:
				return strdup ("xscale");
			case CPU_SUBTYPE_ARM_V7:
				return strdup ("v7");
			case CPU_SUBTYPE_ARM_V7F:
				return strdup ("v7f");
			case CPU_SUBTYPE_ARM_V7K:
				return strdup ("v7k");
			default:
				return r_str_newf ("unknown ARM subtype %d", hdr->cpusubtype & 0xff);
			}
		case CPU_TYPE_SPARC:
			switch (hdr->cpusubtype & 0xff) {
			case CPU_SUBTYPE_SPARC_ALL:	return strdup ("all");
			default:			return strdup ("Unknown sparc subtype");
			}
		case CPU_TYPE_MIPS:
			switch (hdr->cpusubtype & 0xff) {
			case CPU_SUBTYPE_MIPS_ALL:	return strdup ("all");
			case CPU_SUBTYPE_MIPS_R2300:	return strdup ("r2300");
			case CPU_SUBTYPE_MIPS_R2600:	return strdup ("r2600");
			case CPU_SUBTYPE_MIPS_R2800:	return strdup ("r2800");
			case CPU_SUBTYPE_MIPS_R2000a:	return strdup ("r2000a");
			case CPU_SUBTYPE_MIPS_R2000:	return strdup ("r2000");
			case CPU_SUBTYPE_MIPS_R3000a:	return strdup ("r3000a");
			case CPU_SUBTYPE_MIPS_R3000:	return strdup ("r3000");
			default:			return strdup ("Unknown mips subtype");
			}
		case CPU_TYPE_I860:
			switch (hdr->cpusubtype & 0xff) {
			case CPU_SUBTYPE_I860_ALL:	return strdup ("all");
			case CPU_SUBTYPE_I860_860:	return strdup ("860");
			default:			return strdup ("Unknown i860 subtype");
			}
		case CPU_TYPE_POWERPC:
		case CPU_TYPE_POWERPC64:
			switch (hdr->cpusubtype & 0xff) {
			case CPU_SUBTYPE_POWERPC_ALL:	return strdup ("all");
			case CPU_SUBTYPE_POWERPC_601:	return strdup ("601");
			case CPU_SUBTYPE_POWERPC_602:	return strdup ("602");
			case CPU_SUBTYPE_POWERPC_603:	return strdup ("603");
			case CPU_SUBTYPE_POWERPC_603e:	return strdup ("603e");
			case CPU_SUBTYPE_POWERPC_603ev:	return strdup ("603ev");
			case CPU_SUBTYPE_POWERPC_604:	return strdup ("604");
			case CPU_SUBTYPE_POWERPC_604e:	return strdup ("604e");
			case CPU_SUBTYPE_POWERPC_620:	return strdup ("620");
			case CPU_SUBTYPE_POWERPC_750:	return strdup ("750");
			case CPU_SUBTYPE_POWERPC_7400:	return strdup ("7400");
			case CPU_SUBTYPE_POWERPC_7450:	return strdup ("7450");
			case CPU_SUBTYPE_POWERPC_970:	return strdup ("970");
			default:			return strdup ("Unknown ppc subtype");
			}
		}
	}
	return strdup ("Unknown cputype");
}


char* MACH0_(get_cpusubtype)(struct MACH0_(obj_t)* bin) { 
	if (bin) {
		return MACH0_(get_cpusubtype_from_hdr) (&bin->hdr);
	}
	return strdup ("Unknown");
}

int MACH0_(is_pie)(struct MACH0_(obj_t)* bin) {
	return (bin && bin->hdr.filetype == MH_EXECUTE && bin->hdr.flags & MH_PIE);
}

char* MACH0_(get_filetype_from_hdr)(struct MACH0_(mach_header) *hdr) {
	const char *mhtype = "Unknown";
	switch (hdr->filetype) {
	case MH_OBJECT:		mhtype = "Relocatable object";
	case MH_EXECUTE:	mhtype = "Executable file";
	case MH_FVMLIB:		mhtype = "Fixed VM shared library";
	case MH_CORE:		mhtype = "Core file";
	case MH_PRELOAD:	mhtype = "Preloaded executable file";
	case MH_DYLIB:		mhtype = "Dynamically bound shared library";
	case MH_DYLINKER:	mhtype = "Dynamic link editor";
	case MH_BUNDLE:		mhtype = "Dynamically bound bundle file";
	case MH_DYLIB_STUB:	mhtype = "Shared library stub for static linking (no sections)";
	case MH_DSYM:		mhtype = "Companion file with only debug sections";
	}
	return strdup (mhtype);
}

char* MACH0_(get_filetype)(struct MACH0_(obj_t)* bin) {
	if (bin) {
		return MACH0_(get_filetype_from_hdr) (&bin->hdr);
	}
	return strdup ("Unknown");
}

ut64 MACH0_(get_main)(struct MACH0_(obj_t)* bin) {
	ut64 addr = 0LL;
	struct symbol_t *symbols;
	int i;

	if (!(symbols = MACH0_(get_symbols) (bin))) {
		return 0;
	}
	for (i = 0; !symbols[i].last; i++) {
		if (!strcmp (symbols[i].name, "_main")) {
			addr = symbols[i].addr;
			break;
		}
	}
	free (symbols);

	if (!addr && bin->main_cmd.cmd == LC_MAIN)
		addr = bin->entry + bin->baddr;

	if (!addr) {
		ut8 b[128];
		ut64 entry = addr_to_offset(bin, bin->entry);
		// XXX: X86 only and hacky!
		if (entry > bin->size || entry + sizeof (b) > bin->size)
			return 0;
		i = r_buf_read_at (bin->b, entry, b, sizeof (b));
		if (i < 1)
			return 0;
		for (i=0; i<64; i++) {
			if (b[i] == 0xe8 && !b[i+3] && !b[i+4]) {
				int delta = b[i+1] | (b[i+2]<<8) | (b[i+3]<<16) | (b[i+4]<<24);
				return bin->entry + i + 5 + delta;

			}
		}
	}
	return addr;
}

struct MACH0_(mach_header) * MACH0_(get_hdr_from_bytes)(RBuffer *buf) {
	ut32 magic = 0;
	int len;
	struct MACH0_(mach_header) *macho_hdr = R_NEW0 (struct MACH0_(mach_header));
	int big_endian;

	if (!macho_hdr) {
		return NULL;
	}
	if (r_buf_read_at (buf, 0, (ut8*)&magic, 4) < 1) {
		eprintf ("Error: read (magic)\n");
		free (macho_hdr);
		return false;
	}
	if (magic == MACH0_(MH_MAGIC)) {
		big_endian = false;
	} else if (magic == MACH0_(MH_CIGAM)) { 
		big_endian = true;
	} else if (magic == FAT_CIGAM) {
		big_endian = true;
	} else if (magic == 0xfeedfacf) {
		big_endian = false;
	} else {
		free (macho_hdr);
		return NULL;
	}
	len = r_buf_fread_at (buf, 0, (ut8*)macho_hdr,
#if R_BIN_MACH064
		big_endian?"8I":"8i", 1
#else
		big_endian?"7I":"7i", 1
#endif
	);
	if (len != sizeof(struct MACH0_(mach_header))) {
		free (macho_hdr);
		return NULL;
	}
	return macho_hdr;
}
