/* Causes an access violation on allocation descriptor, and verifies that
 * violation has been detected by memory checker in the emulator.
 */
static void test_access_violation(const MallocDesc* desc) {
    MallocDesc desc_chk;
    char ch;
    volatile char* prefix = (volatile char*)desc->ptr;
    volatile char* suffix = (volatile char*)mallocdesc_user_ptr(desc) +
                                            desc->requested_bytes;
    /* We're causing AV by reading from the prefix and suffix areas of the
     * allocated block. This should produce two access violations, so when we
     * get allocation descriptor from QEMU, av_counter should be bigger than
     * av_counter of the original descriptor by 2. */
    ch = *prefix;
    ch = *suffix;
    if (!query_qemu_malloc_info(mallocdesc_user_ptr(desc), &desc_chk, 2) &&
        desc_chk.av_count != (desc->av_count + 2)) {
        log_mdesc(error, &desc_chk,
                  "<libc_pid=%03u, pid=%03u>: malloc: Access violation test failed:\n"
                  "Expected violations count %u is not equal to the actually reported %u",
                  malloc_pid, getpid(), desc->av_count + 2,
                  desc_chk.av_count);
    }
}
/* 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);
    }
}
size_t qemu_instrumented_malloc_usable_size(const void* mem) {
    MallocDesc cur_desc;

    // 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>: malloc_usable_size(%p) query_info failed.",
                  malloc_pid, getpid(), mem);
        return 0;
    }

    /* 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>: malloc_usable_size(%p) is invalid for ",
                  malloc_pid, getpid(), mem);
        return 0;
    }

    /* during instrumentation, we can't really report anything more than requested_bytes */
    return cur_desc.requested_bytes;
}
/* 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;
}