/* This routine serves as entry point for 'memalign'.
 * This routine behaves similarly to qemu_instrumented_malloc.
 */
void* qemu_instrumented_memalign(size_t alignment, size_t bytes) {
    MallocDesc desc;

    if (bytes == 0) {
        // Just let go zero bytes allocation.
        qemu_info_log("::: <libc_pid=%03u, pid=%03u>: memalign(%X, %u) redir to malloc",
                 malloc_pid, getpid(), alignment, bytes);
        return qemu_instrumented_malloc(0);
    }

    /* Prefix size for aligned allocation must be equal to the alignment used
     * for allocation in order to ensure proper alignment of the returned
     * pointer, in case that alignment requirement is greater than prefix
     * size. */
    desc.prefix_size = alignment > DEFAULT_PREFIX_SIZE ? alignment :
                                                         DEFAULT_PREFIX_SIZE;
    desc.requested_bytes = bytes;
    desc.suffix_size = DEFAULT_SUFFIX_SIZE;
    desc.ptr = dlmemalign(desc.prefix_size, mallocdesc_alloc_size(&desc));
    if (desc.ptr == NULL) {
        error_log("<libc_pid=%03u, pid=%03u> memalign(%X, %u): dlmalloc(%u) failed.",
                  malloc_pid, getpid(), alignment, bytes,
                  mallocdesc_alloc_size(&desc));
        return NULL;
    }
    if (notify_qemu_malloc(&desc)) {
        log_mdesc(error, &desc, "<libc_pid=%03u, pid=%03u>: memalign(%X, %u): notify_malloc failed for ",
                  malloc_pid, getpid(), alignment, bytes);
        dlfree(desc.ptr);
        return NULL;
    }

#if TEST_ACCESS_VIOLATIONS
    test_access_violation(&desc);
#endif  // TEST_ACCESS_VIOLATIONS

    log_mdesc(info, &desc, "@@@ <libc_pid=%03u, pid=%03u> memalign(%X, %u) -> ",
              malloc_pid, getpid(), alignment, bytes);
    return mallocdesc_user_ptr(&desc);
}
/* This routine serves as entry point for 'malloc'.
 * Primary responsibility of this routine is to free requested memory, and
 * report free block to the emulator.
 */
void qemu_instrumented_free(void* mem) {
    MallocDesc desc;

    if (mem == NULL) {
        // Just let go NULL free
        dlfree(mem);
        return;
    }

    // Query emulator for the freeing block information.
    if (query_qemu_malloc_info(mem, &desc, 1)) {
        error_log("<libc_pid=%03u, pid=%03u>: free(%p) query_info failed.",
                  malloc_pid, getpid(), mem);
        return;
    }

#if TEST_ACCESS_VIOLATIONS
    test_access_violation(&desc);
#endif  // TEST_ACCESS_VIOLATIONS

    /* Make sure that pointer that's being freed matches what we expect
     * for this memory block. Note that this violation should be already
     * caught in the emulator. */
    if (mem != mallocdesc_user_ptr(&desc)) {
        log_mdesc(error, &desc, "<libc_pid=%03u, pid=%03u>: free(%p) is invalid for ",
                  malloc_pid, getpid(), mem);
        return;
    }

    // Fire up event in the emulator and free block that was actually allocated.
    if (notify_qemu_free(mem)) {
        log_mdesc(error, &desc, "<libc_pid=%03u, pid=%03u>: free(%p) notify_free failed for ",
                  malloc_pid, getpid(), mem);
    } else {
        log_mdesc(info, &desc, "--- <libc_pid=%03u, pid=%03u> free(%p) -> ",
                  malloc_pid, getpid(), mem);
        dlfree(desc.ptr);
    }
}
/* This routine serves as entry point for 'realloc'.
 * This routine behaves similarly to qemu_instrumented_free +
 * qemu_instrumented_malloc. Note that this modifies behavior of "shrinking" an
 * allocation, but overall it doesn't seem to matter, as caller of realloc
 * should not expect that pointer returned after shrinking will remain the same.
 */
void* qemu_instrumented_realloc(void* mem, size_t bytes) {
    MallocDesc new_desc;
    MallocDesc cur_desc;
    size_t to_copy;
    void* ret;

    if (mem == NULL) {
        // Nothing to realloc. just do regular malloc.
        qemu_info_log("::: <libc_pid=%03u, pid=%03u>: realloc(%p, %u) redir to malloc",
                 malloc_pid, getpid(), mem, bytes);
        return qemu_instrumented_malloc(bytes);
    }

    if (bytes == 0) {
        // This is a "free" condition.
        qemu_info_log("::: <libc_pid=%03u, pid=%03u>: realloc(%p, %u) redir to free and malloc",
                 malloc_pid, getpid(), mem, bytes);
        qemu_instrumented_free(mem);

        // This is what dlrealloc does for a "free" realloc.
        return NULL;
    }

    // Query emulator for the reallocating block information.
    if (query_qemu_malloc_info(mem, &cur_desc, 2)) {
        // Note that this violation should be already caught in the emulator.
        error_log("<libc_pid=%03u, pid=%03u>: realloc(%p, %u) query_info failed.",
                  malloc_pid, getpid(), mem, bytes);
        return NULL;
    }

#if TEST_ACCESS_VIOLATIONS
    test_access_violation(&cur_desc);
#endif  // TEST_ACCESS_VIOLATIONS

    /* Make sure that reallocating pointer value is what we would expect
     * for this memory block. Note that this violation should be already caught
     * in the emulator.*/
    if (mem != mallocdesc_user_ptr(&cur_desc)) {
        log_mdesc(error, &cur_desc, "<libc_pid=%03u, pid=%03u>: realloc(%p, %u) is invalid for ",
                  malloc_pid, getpid(), mem, bytes);
        return NULL;
    }

    /* TODO: We're a bit inefficient here, always allocating new block from
     * the heap. If this realloc shrinks current buffer, we can just do the
     * shrinking "in place", adjusting suffix_size in the allocation descriptor
     * for this block that is stored in the emulator. */

    // Initialize descriptor for the new block.
    new_desc.prefix_size = DEFAULT_PREFIX_SIZE;
    new_desc.requested_bytes = bytes;
    new_desc.suffix_size = DEFAULT_SUFFIX_SIZE;
    new_desc.ptr = dlmalloc(mallocdesc_alloc_size(&new_desc));
    if (new_desc.ptr == NULL) {
        log_mdesc(error, &cur_desc, "<libc_pid=%03u, pid=%03u>: realloc(%p, %u): dlmalloc(%u) failed on ",
                  malloc_pid, getpid(), mem, bytes,
                  mallocdesc_alloc_size(&new_desc));
        return NULL;
    }
    ret = mallocdesc_user_ptr(&new_desc);

    // Copy user data from old block to the new one.
    to_copy = bytes < cur_desc.requested_bytes ? bytes :
                                                 cur_desc.requested_bytes;
    if (to_copy != 0) {
        memcpy(ret, mallocdesc_user_ptr(&cur_desc), to_copy);
    }

    // Register new block with emulator.
    if (notify_qemu_malloc(&new_desc)) {
        log_mdesc(error, &new_desc, "<libc_pid=%03u, pid=%03u>: realloc(%p, %u) notify_malloc failed -> ",
                  malloc_pid, getpid(), mem, bytes);
        log_mdesc(error, &cur_desc, "                                                                <- ");
        dlfree(new_desc.ptr);
        return NULL;
    }

#if TEST_ACCESS_VIOLATIONS
    test_access_violation(&new_desc);
#endif  // TEST_ACCESS_VIOLATIONS

    // Free old block.
    if (notify_qemu_free(mem)) {
        log_mdesc(error, &cur_desc, "<libc_pid=%03u, pid=%03u>: realloc(%p, %u): notify_free failed for ",
                  malloc_pid, getpid(), mem, bytes);
        /* Since we registered new decriptor with the emulator, we need
         * to unregister it before freeing newly allocated block. */
        notify_qemu_free(mallocdesc_user_ptr(&new_desc));
        dlfree(new_desc.ptr);
        return NULL;
    }
    dlfree(cur_desc.ptr);

    log_mdesc(info, &new_desc, "=== <libc_pid=%03u, pid=%03u>: realloc(%p, %u) -> ",
              malloc_pid, getpid(), mem, bytes);
    log_mdesc(info, &cur_desc, "                                               <- ");

    return ret;
}
/* This routine serves as entry point for 'calloc'.
 * This routine behaves similarly to qemu_instrumented_malloc.
 */
void* qemu_instrumented_calloc(size_t n_elements, size_t elem_size) {
    if (n_elements == 0 || elem_size == 0) {
        // Just let go zero bytes allocation.
        qemu_info_log("::: <libc_pid=%03u, pid=%03u>: Zero calloc redir to malloc",
                 malloc_pid, getpid());
        return qemu_instrumented_malloc(0);
    }

    /* Fail on overflow - just to be safe even though this code runs only
     * within the debugging C library, not the production one */
    if (n_elements && MAX_SIZE_T / n_elements < elem_size) {
        return NULL;
    }

    MallocDesc desc;

    /* Calculating prefix size. The trick here is to make sure that
     * first element (returned to the caller) is properly aligned. */
    if (DEFAULT_PREFIX_SIZE >= elem_size) {
        /* If default alignment is bigger than element size, we will
         * set our prefix size to the default alignment size. */
        desc.prefix_size = DEFAULT_PREFIX_SIZE;
        /* For the suffix we will use whatever bytes remain from the prefix
         * allocation size, aligned to the size of an element, plus the usual
         * default suffix size. */
        desc.suffix_size = (DEFAULT_PREFIX_SIZE % elem_size) +
                           DEFAULT_SUFFIX_SIZE;
    } else {
        /* Make sure that prefix, and suffix sizes is at least elem_size,
         * and first element returned to the caller is properly aligned. */
        desc.prefix_size = elem_size + DEFAULT_PREFIX_SIZE - 1;
        desc.prefix_size &= ~(malloc_alignment - 1);
        desc.suffix_size = DEFAULT_SUFFIX_SIZE;
    }
    desc.requested_bytes = n_elements * elem_size;
    size_t total_size = desc.requested_bytes + desc.prefix_size + desc.suffix_size;
    size_t total_elements = total_size / elem_size;
    total_size %= elem_size;
    if (total_size != 0) {
        // Add extra to the suffix area.
        total_elements++;
        desc.suffix_size += (elem_size - total_size);
    }
    desc.ptr = dlcalloc(total_elements, elem_size);
    if (desc.ptr == NULL) {
        error_log("<libc_pid=%03u, pid=%03u> calloc: dlcalloc(%u(%u), %u) (prx=%u, sfx=%u) failed.",
                   malloc_pid, getpid(), n_elements, total_elements, elem_size,
                   desc.prefix_size, desc.suffix_size);
        return NULL;
    }

    if (notify_qemu_malloc(&desc)) {
        log_mdesc(error, &desc, "<libc_pid=%03u, pid=%03u>: calloc(%u(%u), %u): notify_malloc failed for ",
                  malloc_pid, getpid(), n_elements, total_elements, elem_size);
        dlfree(desc.ptr);
        return NULL;
    } else {
#if TEST_ACCESS_VIOLATIONS
        test_access_violation(&desc);
#endif  // TEST_ACCESS_VIOLATIONS
        log_mdesc(info, &desc, "### <libc_pid=%03u, pid=%03u> calloc(%u(%u), %u) -> ",
                  malloc_pid, getpid(), n_elements, total_elements, elem_size);
        return mallocdesc_user_ptr(&desc);
    }
}