root/usr/src/lib/libsaveargs/amd64/saveargs.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright 2019 Joyent, Inc.
 */

/*
 * The Sun Studio and GCC (patched for opensolaris/illumos) compilers
 * implement a argument saving scheme on amd64 via the -Wu,save-args or
 * options.  When the option is specified, INTEGER type function arguments
 * passed via registers will be saved on the stack immediately after %rbp, and
 * will not be modified through out the life of the routine.
 *
 *                              +--------+
 *              %rbp    -->     |  %rbp  |
 *                              +--------+
 *              -0x8(%rbp)      |  %rdi  |
 *                              +--------+
 *              -0x10(%rbp)     |  %rsi  |
 *                              +--------+
 *              -0x18(%rbp)     |  %rdx  |
 *                              +--------+
 *              -0x20(%rbp)     |  %rcx  |
 *                              +--------+
 *              -0x28(%rbp)     |  %r8   |
 *                              +--------+
 *              -0x30(%rbp)     |  %r9   |
 *                              +--------+
 *
 *
 * For example, for the following function,
 *
 * void
 * foo(int a1, int a2, int a3, int a4, int a5, int a6, int a7)
 * {
 * ...
 * }
 *
 * Disassembled code will look something like the following:
 *
 *     pushq    %rbp
 *     movq     %rsp, %rbp
 *     subq     $imm8, %rsp                     **
 *     movq     %rdi, -0x8(%rbp)
 *     movq     %rsi, -0x10(%rbp)
 *     movq     %rdx, -0x18(%rbp)
 *     movq     %rcx, -0x20(%rbp)
 *     movq     %r8, -0x28(%rbp)
 *     movq     %r9, -0x30(%rbp)
 *     ...
 * or
 *     pushq    %rbp
 *     movq     %rsp, %rbp
 *     subq     $imm8, %rsp                     **
 *     movq     %r9, -0x30(%rbp)
 *     movq     %r8, -0x28(%rbp)
 *     movq     %rcx, -0x20(%rbp)
 *     movq     %rdx, -0x18(%rbp)
 *     movq     %rsi, -0x10(%rbp)
 *     movq     %rdi, -0x8(%rbp)
 *     ...
 * or
 *     pushq    %rbp
 *     movq     %rsp, %rbp
 *     pushq    %rdi
 *     pushq    %rsi
 *     pushq    %rdx
 *     pushq    %rcx
 *     pushq    %r8
 *     pushq    %r9
 *
 * **: The space being reserved is in addition to what the current
 *     function prolog already reserves.
 *
 * We loop through the first SAVEARGS_INSN_SEQ_LEN bytes of the function
 * looking for each argument saving instruction we would expect to see.
 *
 * If there are odd number of arguments to a function, additional space is
 * reserved on the stack to maintain 16-byte alignment.  For example,
 *
 *     argc == 0: no argument saving.
 *     argc == 3: save 3, but space for 4 is reserved
 *     argc == 7: save 6.
 */

#include <sys/sysmacros.h>
#include <sys/types.h>
#include <libdisasm.h>
#include <string.h>

#include <saveargs.h>

/*
 * Size of the instruction sequence arrays.  It should correspond to
 * the maximum number of arguments passed via registers.
 */
#define INSTR_ARRAY_SIZE        6

#define INSTR1(ins, off) (ins[(off)])
#define INSTR2(ins, off) (ins[(off)] + (ins[(off) + 1] << 8))
#define INSTR3(ins, off)        \
        (ins[(off)] + (ins[(off) + 1] << 8) + (ins[(off + 2)] << 16))
#define INSTR4(ins, off)        \
        (ins[(off)] + (ins[(off) + 1] << 8) + (ins[(off + 2)] << 16) + \
        (ins[(off) + 3] << 24))

/*
 * Sun Studio 10 patch implementation saves %rdi first;
 * GCC 3.4.3 Sun branch implementation saves them in reverse order.
 */
static const uint32_t save_instr[INSTR_ARRAY_SIZE] = {
        0xf87d8948,     /* movq %rdi, -0x8(%rbp) */
        0xf0758948,     /* movq %rsi, -0x10(%rbp) */
        0xe8558948,     /* movq %rdx, -0x18(%rbp) */
        0xe04d8948,     /* movq %rcx, -0x20(%rbp) */
        0xd845894c,     /* movq %r8, -0x28(%rbp) */
        0xd04d894c      /* movq %r9, -0x30(%rbp) */
};

static const uint16_t save_instr_push[] = {
        0x57,   /* pushq %rdi */
        0x56,   /* pushq %rsi */
        0x52,   /* pushq %rdx */
        0x51,   /* pushq %rcx */
        0x5041, /* pushq %r8 */
        0x5141  /* pushq %r9 */
};

/*
 * If the return type of a function is a structure greater than 16 bytes in
 * size, %rdi will contain the address to which it should be stored, and
 * arguments will begin at %rsi.  Studio will push all of the normal argument
 * registers anyway, GCC will start pushing at %rsi, so we need a separate
 * pattern.
 */
static const uint32_t save_instr_sr[INSTR_ARRAY_SIZE-1] = {
        0xf8758948,     /* movq %rsi,-0x8(%rbp) */
        0xf0558948,     /* movq %rdx,-0x10(%rbp) */
        0xe84d8948,     /* movq %rcx,-0x18(%rbp) */
        0xe045894c,     /* movq %r8,-0x20(%rbp) */
        0xd84d894c      /* movq %r9,-0x28(%rbp) */
};

static const uint8_t save_fp_pushes[] = {
        0x55,   /* pushq %rbp */
        0xcc    /* int $0x3 */
};
#define NUM_FP_PUSHES (sizeof (save_fp_pushes) / sizeof (save_fp_pushes[0]))

static const uint32_t save_fp_movs[] = {
        0x00e58948,     /* movq %rsp,%rbp, encoding 1 */
        0x00ec8b48,     /* movq %rsp,%rbp, encoding 2 */
};
#define NUM_FP_MOVS (sizeof (save_fp_movs) / sizeof (save_fp_movs[0]))

typedef struct {
        uint8_t *data;
        size_t size;
} text_t;

static int
do_read(void *data, uint64_t addr, void *buf, size_t len)
{
        text_t  *t = data;

        if (addr >= t->size)
                return (-1);

        len = MIN(len, t->size - addr);

        (void) memcpy(buf, (char *)t->data + addr, len);

        return (len);
}

/* ARGSUSED */
int
do_lookup(void *data, uint64_t addr, char *buf, size_t buflen, uint64_t *start,
    size_t *symlen)
{
        /* We don't actually need lookup info */
        return (-1);
}

static int
instr_size(dis_handle_t *dhp, uint8_t *ins, unsigned int i, size_t size)
{
        text_t  t;

        t.data = ins;
        t.size = size;

        dis_set_data(dhp, &t);
        return (dis_instrlen(dhp, i));
}

static boolean_t
has_saved_fp(dis_handle_t *dhp, uint8_t *ins, int size)
{
        int             i, j;
        uint32_t        n;
        boolean_t       found_push = B_FALSE;
        ssize_t         sz = 0;

        for (i = 0; i < size; i += sz) {
                if ((sz = instr_size(dhp, ins, i, size)) < 1)
                        return (B_FALSE);

                if (found_push == B_FALSE) {
                        if (sz != 1)
                                continue;

                        n = INSTR1(ins, i);
                        for (j = 0; j < NUM_FP_PUSHES; j++)
                                if (save_fp_pushes[j] == n) {
                                        found_push = B_TRUE;
                                        break;
                                }
                } else {
                        if (sz != 3)
                                continue;
                        n = INSTR3(ins, i);
                        for (j = 0; j < NUM_FP_MOVS; j++)
                                if (save_fp_movs[j] == n)
                                        return (B_TRUE);
                }
        }

        return (B_FALSE);
}

int
saveargs_has_args(uint8_t *ins, size_t size, uint_t argc, int start_index)
{
        int             i, j;
        uint32_t        n;
        uint8_t         found = 0;
        ssize_t         sz = 0;
        dis_handle_t    *dhp = NULL;
        int             ret = SAVEARGS_NO_ARGS;

        argc = MIN((start_index + argc), INSTR_ARRAY_SIZE);

        if ((dhp = dis_handle_create(DIS_X86_SIZE64, NULL, do_lookup,
            do_read)) == NULL)
                return (SAVEARGS_NO_ARGS);

        if (!has_saved_fp(dhp, ins, size)) {
                dis_handle_destroy(dhp);
                return (SAVEARGS_NO_ARGS);
        }

        /*
         * For each possible style of argument saving, walk the insn stream as
         * we've been given it, and set bit N in 'found' if we find the
         * instruction saving the Nth argument.
         */

        /*
         * Compare against regular implementation
         */
        found = 0;
        for (i = 0; i < size; i += sz) {
                sz = instr_size(dhp, ins, i, size);

                if (sz < 1)
                        break;
                else if (sz != 4)
                        continue;

                n = INSTR4(ins, i);

                for (j = 0; j < argc; j++) {
                        if (n == save_instr[j]) {
                                found |= (1 << j);

                                if (found == ((1 << argc) - 1)) {
                                        ret = start_index ?
                                            SAVEARGS_STRUCT_ARGS :
                                            SAVEARGS_TRAD_ARGS;
                                        goto done;
                                }

                                break;
                        }
                }
        }

        /*
         * Compare against GCC push-based implementation
         */
        found = 0;
        for (i = 0; i < size; i += sz) {
                if ((sz = instr_size(dhp, ins, i, size)) < 1)
                        break;

                for (j = start_index; j < argc; j++) {
                        if (sz == 2) /* Two byte */
                                n = INSTR2(ins, i);
                        else if (sz == 1)
                                n = INSTR1(ins, i);
                        else
                                continue;

                        if (n == save_instr_push[j]) {
                                found |= (1 << (j - start_index));

                                if (found ==
                                    ((1 << (argc - start_index)) - 1)) {
                                        ret = SAVEARGS_TRAD_ARGS;
                                        goto done;
                                }

                                break;
                        }
                }
        }

        /*
         * Look for a GCC-style returned structure.
         */
        found = 0;
        if (start_index != 0) {
                for (i = 0; i < size; i += sz) {
                        sz = instr_size(dhp, ins, i, size);

                        if (sz < 1)
                                break;
                        else if (sz != 4)
                                continue;

                        n = INSTR4(ins, i);

                        /* argc is inclusive of start_index, allow for that */
                        for (j = 0; j < (argc - start_index); j++) {
                                if (n == save_instr_sr[j]) {
                                        found |= (1 << j);

                                        if (found ==
                                            ((1 << (argc - start_index)) - 1)) {
                                                ret = SAVEARGS_TRAD_ARGS;
                                                goto done;
                                        }

                                        break;
                                }
                        }
                }
        }

done:
        dis_handle_destroy(dhp);
        return (ret);
}