/**
 * Validate a target process' address pointer's availability via @a mobj, verifying that @a length bytes can be read
 * from @a mobj at @a address, and return the pointer from which a @a length read may be performed.
 *
 * @param mobj An initialized memory object.
 * @param address The base address to be read. This address should be relative to the target task's address space.
 * @param offset An offset to be applied to @a address prior to verifying the address range.
 * @param length The total number of bytes that should be readable at @a address.
 *
 * @return Returns the validated pointer, or NULL if the requested bytes are not within @a mobj's range.
 */
void *plcrash_async_mobject_remap_address (plcrash_async_mobject_t *mobj, pl_vm_address_t address, pl_vm_off_t offset, size_t length) {
    /* Map into our memory space */
    pl_vm_address_t remapped = address - mobj->vm_slide;

    if (!plcrash_async_mobject_verify_local_pointer(mobj, (uintptr_t) remapped, offset, length))
        return NULL;

    return (void *) remapped + offset;
}
/**
 * Iterate over the available Mach-O LC_CMD entries.
 *
 * @param image The image to iterate
 * @param previous The previously returned LC_CMD address value, or 0 to iterate from the first LC_CMD.
 * @return Returns the address of the next load_command on success, or NULL on failure.
 *
 * @note A returned command is gauranteed to be readable, and fully within mapped address space. If the command
 * command can not be verified to have available MAX(sizeof(struct load_command), cmd->cmdsize) bytes, NULL will be
 * returned.
 */
void *plcrash_async_macho_next_command (plcrash_async_macho_t *image, void *previous) {
    struct load_command *cmd;

    /* On the first iteration, determine the LC_CMD offset from the Mach-O header. */
    if (previous == NULL) {
        /* Sanity check */
        if (image->byteorder->swap32(image->header.sizeofcmds) < sizeof(struct load_command)) {
            PLCF_DEBUG("Mach-O sizeofcmds is less than sizeof(struct load_command) in %s", image->name);
            return NULL;
        }

        return plcrash_async_mobject_remap_address(&image->load_cmds, image->header_addr, image->header_size, sizeof(struct load_command));
    }

    /* We need the size from the previous load command; first, verify the pointer. */
    cmd = previous;
    if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) cmd, 0, sizeof(*cmd))) {
        PLCF_DEBUG("Failed to map LC_CMD at address %p in: %s", cmd, image->name);
        return NULL;
    }

    /* Advance to the next command */
    uint32_t cmdsize = image->byteorder->swap32(cmd->cmdsize);
    void *next = ((uint8_t *)previous) + cmdsize;

    /* Avoid walking off the end of the cmd buffer */
    if ((uintptr_t)next >= image->load_cmds.address + image->load_cmds.length)
        return NULL;

    /* Verify that it holds at least load_command */
    if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) next, 0, sizeof(struct load_command))) {
        PLCF_DEBUG("Failed to map LC_CMD at address %p in: %s", cmd, image->name);
        return NULL;
    }

    /* Verify the actual size. */
    cmd = next;
    if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) next, 0, image->byteorder->swap32(cmd->cmdsize))) {
        PLCF_DEBUG("Failed to map LC_CMD at address %p in: %s", cmd, image->name);
        return NULL;
    }

    return next;
}
/**
 * Find the first LC_CMD matching the given @a cmd type.
 *
 * @param image The image to search.
 * @param expectedCommand The LC_CMD type to find.
 *
 * @return Returns the address of the matching load_command on success, or 0 on failure.
 *
 * @note A returned command is gauranteed to be readable, and fully within mapped address space. If the command
 * command can not be verified to have available MAX(sizeof(struct load_command), cmd->cmdsize) bytes, NULL will be
 * returned.
 */
void *plcrash_async_macho_find_command (plcrash_async_macho_t *image, uint32_t expectedCommand) {
    struct load_command *cmd = NULL;

    /* Iterate commands until we either find a match, or reach the end */
    while ((cmd = plcrash_async_macho_next_command(image, cmd)) != NULL) {
        /* Read the load command type */
        if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) cmd, 0, sizeof(*cmd))) {
            PLCF_DEBUG("Failed to map LC_CMD at address %p in: %s", cmd, image->name);
            return NULL;
        }

        /* Return a match */
        if (image->byteorder->swap32(cmd->cmd) == expectedCommand) {
            return cmd;
        }
    }
    
    /* No match found */
    return NULL;
}
/**
 * Fetch the entry corresponding to @a index.
 *
 * @param reader The reader from which @a table was mapped.
 * @param symtab The symbol table to read.
 * @param index The index of the entry to return.
 *
 * @warning The implementation implements no bounds checking on @a index, and it is the caller's responsibility to ensure
 * that they do not read an invalid entry.
 */
plcrash_async_macho_symtab_entry_t plcrash_async_macho_symtab_reader_read (plcrash_async_macho_symtab_reader_t *reader, void *symtab, uint32_t index) {
    const plcrash_async_byteorder_t *byteorder = reader->image->byteorder;

    /* nlist_64 and nlist are identical other than the trailing address field, so we use
     * a union to share a common implementation of symbol lookup. The following asserts
     * provide a sanity-check of that assumption, in the case where this code is moved
     * to a new platform ABI. */
    {
#define pl_m_sizeof(type, field) sizeof(((type *)NULL)->field)
        
        PLCF_ASSERT(__offsetof(struct nlist_64, n_type) == __offsetof(struct nlist, n_type));
        PLCF_ASSERT(pl_m_sizeof(struct nlist_64, n_type) == pl_m_sizeof(struct nlist, n_type));
        
        PLCF_ASSERT(__offsetof(struct nlist_64, n_un.n_strx) == __offsetof(struct nlist, n_un.n_strx));
        PLCF_ASSERT(pl_m_sizeof(struct nlist_64, n_un.n_strx) == pl_m_sizeof(struct nlist, n_un.n_strx));
        
        PLCF_ASSERT(__offsetof(struct nlist_64, n_value) == __offsetof(struct nlist, n_value));
        
#undef pl_m_sizeof
    }

#define pl_sym_value(image, nl) (image->m64 ? image->byteorder->swap64((nl)->n64.n_value) : image->byteorder->swap32((nl)->n32.n_value))

    /* Perform 32-bit/64-bit dependent aliased pointer math. */
    pl_nlist_common *symbol;
    if (reader->image->m64) {
        symbol = (pl_nlist_common *) &(((struct nlist_64 *) symtab)[index]);
    } else {
        symbol = (pl_nlist_common *) &(((struct nlist *) symtab)[index]);
    }
    
    plcrash_async_macho_symtab_entry_t entry = {
        .n_strx = byteorder->swap32(symbol->n32.n_un.n_strx),
        .n_type = symbol->n32.n_type,
        .n_sect = symbol->n32.n_sect,
        .n_desc = byteorder->swap16(symbol->n32.n_desc),
        .n_value = pl_sym_value(reader->image, symbol)
    };
    
    entry.normalized_value = entry.n_value;
    
    /* Normalize the symbol address. We have to set the low-order bit ourselves for ARM THUMB functions. */
    if (entry.n_desc & N_ARM_THUMB_DEF)
        entry.normalized_value = (entry.n_value|1);
    else
        entry.normalized_value = entry.n_value;
    
#undef pl_sym_value
    
    return entry;
}

/**
 * Given a string table offset for @a reader, returns the pointer to the validated NULL terminated string, or returns
 * NULL if the string does not fall within the reader's mapped string table.
 *
 * @param reader The reader containing a mapped string table.
 * @param n_strx The index within the @a reader string table to a symbol name.
 */
const char *plcrash_async_macho_symtab_reader_symbol_name (plcrash_async_macho_symtab_reader_t *reader, uint32_t n_strx) {
    /* 
     * It's possible, though unlikely, that the n_strx index value is invalid. To handle this,
     * we walk the string until \0 is hit, verifying that it can be found in its entirety within
     *
     * TODO: Evaluate effeciency of per-byte calling of plcrash_async_mobject_verify_local_pointer(). We should
     * probably validate whole pages at a time instead.
     */
    const char *sym_name = reader->string_table + n_strx;
    const char *p = sym_name;
    do {
        if (!plcrash_async_mobject_verify_local_pointer(&reader->linkedit.mobj, (uintptr_t) p, 0, 1)) {
            PLCF_DEBUG("End of mobject reached while walking string\n");
            return NULL;
        }
        p++;
    } while (*p != '\0');

    return sym_name;
}

/**
 * Free all mapped reader resources.
 *
 * @note Unlike most free() functions in this API, this function is async-safe.
 */
void plcrash_async_macho_symtab_reader_free (plcrash_async_macho_symtab_reader_t *reader) {
    plcrash_async_macho_mapped_segment_free(&reader->linkedit);
}
/**
 * Initialize a new Mach-O binary image parser.
 *
 * @param image The image structure to be initialized.
 * @param name The file name or path for the Mach-O image.
 * @param header The task-local address of the image's Mach-O header.
 *
 * @return PLCRASH_ESUCCESS on success. PLCRASH_EINVAL will be returned in the Mach-O file can not be parsed,
 * or PLCRASH_EINTERNAL if an error occurs reading from the target task.
 *
 * @warning This method is not async safe.
 */
plcrash_error_t plcrash_nasync_macho_init (plcrash_async_macho_t *image, mach_port_t task, const char *name, pl_vm_address_t header) {
    plcrash_error_t ret;

    /* Defaults checked in the  error cleanup handler */
    bool mobj_initialized = false;
    bool task_initialized = false;
    image->name = NULL;

    /* Basic initialization */
    image->task = task;
    image->header_addr = header;
    image->name = strdup(name);

    mach_port_mod_refs(mach_task_self(), image->task, MACH_PORT_RIGHT_SEND, 1);
    task_initialized = true;

    /* Read in the Mach-O header */
    kern_return_t kt;
    if ((kt = plcrash_async_read_addr(image->task, image->header_addr, &image->header, sizeof(image->header))) != KERN_SUCCESS) {
        /* NOTE: The image struct must be fully initialized before returning here, as otherwise our _free() function
         * will crash */
        PLCF_DEBUG("Failed to read Mach-O header from 0x%" PRIx64 " for image %s, kern_error=%d", (uint64_t) image->header_addr, name, kt);
        ret = PLCRASH_EINTERNAL;
        goto error;
    }
    
    /* Set the default byte order*/
    image->byteorder = &plcrash_async_byteorder_direct;

    /* Parse the Mach-O magic identifier. */
    switch (image->header.magic) {
        case MH_CIGAM:
            // Enable byte swapping
            image->byteorder = &plcrash_async_byteorder_swapped;
            // Fall-through

        case MH_MAGIC:
            image->m64 = false;
            break;            
            
        case MH_CIGAM_64:
            // Enable byte swapping
            image->byteorder = &plcrash_async_byteorder_swapped;
            // Fall-through
            
        case MH_MAGIC_64:
            image->m64 = true;
            break;

        case FAT_CIGAM:
        case FAT_MAGIC:
            PLCF_DEBUG("%s called with an unsupported universal Mach-O archive in: %s", __func__, image->name);
            return PLCRASH_EINVAL;
            break;

        default:
            PLCF_DEBUG("Unknown Mach-O magic: 0x%" PRIx32 " in: %s", image->header.magic, image->name);
            return PLCRASH_EINVAL;
    }

    /* Save the header size */
    if (image->m64) {
        image->header_size = sizeof(struct mach_header_64);
    } else {
        image->header_size = sizeof(struct mach_header);
    }
    
    /* Map in header + load commands */
    pl_vm_size_t cmd_len = image->byteorder->swap32(image->header.sizeofcmds);
    pl_vm_size_t cmd_offset = image->header_addr + image->header_size;
    image->ncmds = image->byteorder->swap32(image->header.ncmds);

    ret = plcrash_async_mobject_init(&image->load_cmds, image->task, cmd_offset, cmd_len, true);
    if (ret != PLCRASH_ESUCCESS) {
        PLCF_DEBUG("Failed to map Mach-O load commands in image %s", image->name);
        goto error;
    } else {
        mobj_initialized = true;
    }

    /* Now that the image has been sufficiently initialized, determine the __TEXT segment size */
    void *cmdptr = NULL;
    image->text_size = 0x0;
    bool found_text_seg = false;
    while ((cmdptr = plcrash_async_macho_next_command_type(image, cmdptr, image->m64 ? LC_SEGMENT_64 : LC_SEGMENT)) != 0) {
        if (image->m64) {
            struct segment_command_64 *segment = cmdptr;
            if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) segment, 0, sizeof(*segment))) {
                PLCF_DEBUG("LC_SEGMENT command was too short");
                ret = PLCRASH_EINVAL;
                goto error;
            }
            
            if (plcrash_async_strncmp(segment->segname, SEG_TEXT, sizeof(segment->segname)) != 0)
                continue;

            image->text_size = image->byteorder->swap64(segment->vmsize);
            image->text_vmaddr = image->byteorder->swap64(segment->vmaddr);
            found_text_seg = true;
            break;
        } else {
            struct segment_command *segment = cmdptr;
            if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) segment, 0, sizeof(*segment))) {
                PLCF_DEBUG("LC_SEGMENT command was too short");
                ret = PLCRASH_EINVAL;
                goto error;
            }
            
            if (plcrash_async_strncmp(segment->segname, SEG_TEXT, sizeof(segment->segname)) != 0)
                continue;
            
            image->text_size = image->byteorder->swap32(segment->vmsize);
            image->text_vmaddr = image->byteorder->swap32(segment->vmaddr);
            found_text_seg = true;
            break;
        }
    }

    if (!found_text_seg) {
        PLCF_DEBUG("Could not find __TEXT segment!");
        ret = PLCRASH_EINVAL;
        goto error;
    }

    /* Compute the vmaddr slide */
    if (image->text_vmaddr < header) {
        image->vmaddr_slide = header - image->text_vmaddr;
    } else if (image->text_vmaddr > header) {
        image->vmaddr_slide = -((pl_vm_off_t) (image->text_vmaddr - header));
    } else {
        image->vmaddr_slide = 0;
    }

    return PLCRASH_ESUCCESS;
    
error:
    if (mobj_initialized)
        plcrash_async_mobject_free(&image->load_cmds);
    
    if (image->name != NULL)
        free(image->name);
    
    if (task_initialized)
        mach_port_mod_refs(mach_task_self(), image->task, MACH_PORT_RIGHT_SEND, -1);

    return ret;
}
/**
 * Find and map a named section within a named segment, initializing @a mobj.
 * It is the caller's responsibility to dealloc @a mobj after a successful
 * initialization
 *
 * @param image The image to search for @a segname.
 * @param segname The name of the segment to search.
 * @param sectname The name of the section to map.
 * @param mobj The mobject to be initialized with a mapping of the section's data. It is the caller's responsibility to dealloc @a mobj after
 * a successful initialization.
 *
 * @return Returns PLCRASH_ESUCCESS on success, PLCRASH_ENOTFOUND if the section is not found, or an error result on failure.
 */
plcrash_error_t plcrash_async_macho_map_section (plcrash_async_macho_t *image, const char *segname, const char *sectname, plcrash_async_mobject_t *mobj) {
    struct segment_command *cmd_32;
    struct segment_command_64 *cmd_64;
    
    void *segment =  plcrash_async_macho_find_segment_cmd(image, segname);
    if (segment == NULL)
        return PLCRASH_ENOTFOUND;

    cmd_32 = segment;
    cmd_64 = segment;
    
    uint32_t nsects;
    uintptr_t cursor = (uintptr_t) segment;

    if (image->m64) {
        nsects = image->byteorder->swap32(cmd_64->nsects);
        cursor += sizeof(*cmd_64);
    } else {
        nsects = image->byteorder->swap32(cmd_32->nsects);
        cursor += sizeof(*cmd_32);
    }

    for (uint32_t i = 0; i < nsects; i++) {        
        struct section *sect_32 = NULL;
        struct section_64 *sect_64 = NULL;
       
        if (image->m64) {
            if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, cursor, 0, sizeof(*sect_64))) {
                PLCF_DEBUG("Section table entry outside of expected range; searching for (%s,%s)", segname, sectname);
                return PLCRASH_EINVAL;
            }
            
            sect_64 = (void *) cursor;
            cursor += sizeof(*sect_64);
        } else {
            if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, cursor, 0, sizeof(*sect_32))) {
                PLCF_DEBUG("Section table entry outside of expected range; searching for (%s,%s)", segname, sectname);
                return PLCRASH_EINVAL;
            }
            
            sect_32 = (void *) cursor;
            cursor += sizeof(*sect_32);
        }
        
        const char *image_sectname = image->m64 ? sect_64->sectname : sect_32->sectname;
        if (plcrash_async_strncmp(sectname, image_sectname, sizeof(sect_64->sectname)) == 0) {
            /* Calculate the in-memory address and size */
            pl_vm_address_t sectaddr;
            pl_vm_size_t sectsize;
            if (image->m64) {
                sectaddr = image->byteorder->swap64(sect_64->addr) + image->vmaddr_slide;
                sectsize = image->byteorder->swap32(sect_64->size);
            } else {
                sectaddr = image->byteorder->swap32(sect_32->addr) + image->vmaddr_slide;
                sectsize = image->byteorder->swap32(sect_32->size);
            }
            
            
            /* Perform and return the mapping */
            return plcrash_async_mobject_init(mobj, image->task, sectaddr, sectsize, true);
        }
    }
    
    return PLCRASH_ENOTFOUND;
}