%{
/* Set to include regparm field in probe context in translate.cxx. */
#ifndef STAP_NEED_REGPARM
#define STAP_NEED_REGPARM 1
#endif
%}

@__private30 global _reg_offsets[21], _r32_offsets[10], _r16_offsets[13],
		_r8_offsets[8]

probe init {
	/* Same order as pt_regs */
	_reg_offsets["r15"] = 0
	_reg_offsets["r14"] = 8
	_reg_offsets["r13"] = 16
	_reg_offsets["r12"] = 24
	_reg_offsets["rbp"] = 32
	_reg_offsets["rbx"] = 40
	_reg_offsets["r11"] = 48
	_reg_offsets["r10"] = 56
	_reg_offsets["r9"]  = 64
	_reg_offsets["r8"]  = 72
	_reg_offsets["rax"] = 80
	_reg_offsets["rcx"] = 88
	_reg_offsets["rdx"] = 96
	_reg_offsets["rsi"] = 104
	_reg_offsets["rdi"] = 112
	_reg_offsets["orig_rax"] = 120
	_reg_offsets["rip"] = 128
	_reg_offsets["xcs"] = 136
	_reg_offsets["eflags"] = 144
	_reg_offsets["rsp"] = 152
	_reg_offsets["xss"] = 160

	_r32_offsets["ebp"] = 32
	_r32_offsets["ebx"] = 40
	_r32_offsets["eax"] = 80
	_r32_offsets["ecx"] = 88
	_r32_offsets["edx"] = 96
	_r32_offsets["esi"] = 104
	_r32_offsets["edi"] = 112
	_r32_offsets["orig_eax"] = 120
	_r32_offsets["eip"] = 128
	_r32_offsets["esp"] = 152

	_r16_offsets["bp"] = 32
	_r16_offsets["bx"] = 40
	_r16_offsets["ax"] = 80
	_r16_offsets["cx"] = 88
	_r16_offsets["dx"] = 96
	_r16_offsets["si"] = 104
	_r16_offsets["di"] = 112
	_r16_offsets["orig_ax"] = 120
	_r16_offsets["ip"] = 128
	_r16_offsets["cs"] = 136
	_r16_offsets["flags"] = 144
	_r16_offsets["sp"] = 152
	_r16_offsets["ss"] = 160

	_r8_offsets["al"] = 80
	_r8_offsets["ah"] = 81
	_r8_offsets["bl"] = 40
	_r8_offsets["bh"] = 41
	_r8_offsets["cl"] = 88
	_r8_offsets["ch"] = 89
	_r8_offsets["dl"] = 96
	_r8_offsets["dh"] = 97
}

function _stp_get_register_by_offset:long (offset:long) %{ /* pure */
	long value;
	struct pt_regs *regs;
	regs = (CONTEXT->user_mode_p ? CONTEXT->uregs : CONTEXT->kregs);
	if (!regs) {
		CONTEXT->last_error = "No registers available in this context";
		return;
	}
	if (STAP_ARG_offset < 0 || STAP_ARG_offset > sizeof(struct pt_regs) - sizeof(long)) {
		snprintf(CONTEXT->error_buffer, sizeof(CONTEXT->error_buffer),
				"Bad register offset: %lld", (long long) STAP_ARG_offset);
		CONTEXT->last_error = CONTEXT->error_buffer;
		return;
	}
	memcpy(&value, ((char *)regs) + STAP_ARG_offset, sizeof(value));
	STAP_RETVALUE = value;
%}

/*
 * _stp_sign_extend32() is callable from a script function.
 * __stp_sign_extend32() (in regs.c) is callable from a C function.
 */
function _stp_sign_extend32:long (value:long) %{ /* pure */
	STAP_RETVALUE = __stp_sign_extend32(STAP_ARG_value);
%}

/*
 * _stp_sign_extend16() is callable from a script function.
 * __stp_sign_extend16() (in regs.c) is callable from a C function.
 */
function _stp_sign_extend16:long (value:long) %{ /* pure */
	STAP_RETVALUE = __stp_sign_extend16(STAP_ARG_value);
%}

/*
 * _stp_sign_extend8() is callable from a script function.
 * __stp_sign_extend8() (in regs.c) is callable from a C function.
 */
function _stp_sign_extend8:long (value:long) %{ /* pure */
	STAP_RETVALUE = __stp_sign_extend8(STAP_ARG_value);
%}

function _stp_register:long (name:string, sign_extend:long) {
	reg32 = 0
	reg16 = 0
	reg8 = 0
	assert(registers_valid(), "cannot access CPU registers in this context")
	offset = _reg_offsets[name]
	if (offset == 0 && !(name in _reg_offsets)) {
		offset = _r32_offsets[name]
		if (offset == 0 && !(name in _r32_offsets)) {
			offset = _r16_offsets[name]
			if (offset == 0 && !(name in _r16_offsets)) {
				offset = _r8_offsets[name]
				if (offset == 0 && !(name in _r8_offsets)) {
					assert(0, "Unknown register: " . name)
				} else {
					reg8 = 1;
				}
			} else {
				reg16 = 1;
			}

		} else {
			reg32 = 1
		}
	}
	value =  _stp_get_register_by_offset(offset)
	if (reg32) {
		if (sign_extend)
			value = _stp_sign_extend32(value)
		else
			value &= 0xffffffff
	} else if (reg16) {
		if (sign_extend)
			value = _stp_sign_extend16(value)
		else
			value &= 0xffff
	} else if (reg8) {
		if (sign_extend)
			value = _stp_sign_extend8(value)
		else
			value &= 0xff
	}
	return value
}

/* Return the named register value as a signed value. */
function register:long (name:string) {
	return _stp_register(name, 1)
}

/*
 * Return the named register value as an unsigned value.  Specifically,
 * don't sign-extend the register value when promoting it to 64 bits.
 */
function u_register:long (name:string) {
	return _stp_register(name, 0)
}

/*
 * Return the value of function arg #argnum (1=first arg).
 * If truncate=1, mask off the top 32 bits.
 * If sign_extend=1 and (truncate=1 or the probepoint we've hit is in a
 * 32-bit app), sign-extend the 32-bit value.
 * If force64=1, return a 64-bit value even if we're in a 32-bit app.
 */
function _stp_arg:long (argnum:long, sign_extend:long, truncate:long)
{
	return _stp_arg2(argnum, sign_extend, truncate, 0)
}
function _stp_arg2:long (argnum:long, sign_extend:long, truncate:long,
			 force64:long)
%{ /* pure */
	__label__ deref_fault;
	long val;
	struct pt_regs *regs;
	int result, n, nr_regargs;
	size_t argsz = sizeof(long);

        if (CONTEXT->sregs) { /* syscall-in-pt_regs mode: kernel 4.17+ or similar */
          void* valptr;
          int is_32bit = _stp_is_compat_task();
          regs = CONTEXT->sregs;
          
          /* NB: kread() is probably unnecessary paranoia w.r.t. the sregs pointer. */
          if (is_32bit) switch (STAP_ARG_argnum) {
            /* SC_IA32_REGS_TO_ARGS / SC_X86_64_REGS_TO_ARGS */
            case 1: valptr = &(regs->bx); break;
            case 2: valptr = &(regs->cx); break;
            case 3: valptr = &(regs->dx); break;
            case 4: valptr = &(regs->si); break;
            case 5: valptr = &(regs->di); break;
            case 6: valptr = &(regs->bp); break;
            default:
              goto bad_argnum;
          }
          else switch (STAP_ARG_argnum) {
            /* SC_IA32_REGS_TO_ARGS / SC_X86_64_REGS_TO_ARGS */
            case 1: valptr = &(regs->di); break;
            case 2: valptr = &(regs->si); break;
            case 3: valptr = &(regs->dx); break;
            case 4: valptr = &(regs->r10); break;
            case 5: valptr = &(regs->r8); break;
            case 6: valptr = &(regs->r9); break;
            default:
              goto bad_argnum;
          }

          val = kread((long *)valptr);

          /* Here, we do sign extension / truncation, ignoring CONTEXT->user_mode_p. */
          if ((STAP_ARG_truncate || _stp_is_compat_task())
              && !STAP_ARG_force64) {
            if (STAP_ARG_sign_extend)
              STAP_RETVALUE = (int64_t) __stp_sign_extend32(val);
            else
              /* High bits may be garbage. */
              STAP_RETVALUE = (int64_t) (val & 0xffffffff);
          } else
            STAP_RETVALUE = (int64_t) val;
          
          return;
        }

        // fall through in the non-sycall-mode case
        
        regs = (CONTEXT->user_mode_p ? CONTEXT->uregs : CONTEXT->kregs);

	STAP_RETVALUE = 0;
	if (!regs) {
		snprintf(CONTEXT->error_buffer, sizeof(CONTEXT->error_buffer),
			"cannot access function args in this context");
		CONTEXT->last_error = CONTEXT->error_buffer;
		return;
	}
	if (STAP_ARG_argnum < 1)
		goto bad_argnum;
	n = (int) STAP_ARG_argnum;
	if (CONTEXT->regparm == 0) {
		/* Default */
		if (CONTEXT->user_mode_p && _stp_is_compat_task())
			nr_regargs = 0;
		else
			nr_regargs = 6;
	} else
		nr_regargs = (CONTEXT->regparm & _STP_REGPARM_MASK);
               
	if (!STAP_ARG_force64 && CONTEXT->user_mode_p && _stp_is_compat_task()) {
		argsz = sizeof(int);
		result = _stp_get_arg32_by_number(n, nr_regargs, regs, &val);
	} else {
		result = _stp_get_arg64_by_number(n, nr_regargs, regs, &val);
               }
	switch (result) {
	case 0:
		/* Arg is in register. */
		break;
	case 1:
		/* Arg is on kernel stack. */
		val = kread((long *) val);
		break;
	case 2:
	    {
		/* Arg is on user stack. */
	    	const char __user *vaddr = (const char __user*) val;
		if (_stp_copy_from_user((char*)&val, vaddr, argsz) != 0) {
			/* Stack page not resident. */
			_stp_warn("cannot access arg(%d) "
				"at user stack address %p\n", n, vaddr);
			STAP_RETVALUE = 0;
			return;
		}
		break;
	    }
	default:
		goto bad_argnum;
	}

        /* Sign-extend etc. as needed, depending on process 32-bitness. */
	if ((STAP_ARG_truncate || (CONTEXT->user_mode_p && _stp_is_compat_task()))
	    && !STAP_ARG_force64) {
		if (STAP_ARG_sign_extend)
			STAP_RETVALUE = (int64_t) __stp_sign_extend32(val);
		else
			/* High bits may be garbage. */
			STAP_RETVALUE = (int64_t) (val & 0xffffffff);
	} else
		STAP_RETVALUE = (int64_t) val;
	return;

bad_argnum:
	snprintf(CONTEXT->error_buffer, sizeof(CONTEXT->error_buffer),
				"cannot access arg(%lld)", STAP_ARG_argnum);
	CONTEXT->last_error = CONTEXT->error_buffer;
	return;

deref_fault: /* branched to from kread() */
	snprintf (CONTEXT->error_buffer, sizeof(CONTEXT->error_buffer),
		  "kernel fault at %#lx accessing arg(%lld)", val,
		  STAP_ARG_argnum);
	CONTEXT->last_error = CONTEXT->error_buffer;
%}

function probing_32bit_app:long() %{ /* pure */
	STAP_RETVALUE = (CONTEXT->user_mode_p && _stp_is_compat_task());
%}

function arch_bytes:long() %{ /* pure */
  STAP_RETVALUE = sizeof(long);
%}

function uarch_bytes:long() {
  assert(user_mode(), "requires user mode")
  return probing_32bit_app() ? 4 : 8
}

/* Return the value of function arg #argnum (1=first arg) as a signed int. */
function int_arg:long (argnum:long) {
	return _stp_arg2(argnum, 1, 1, 0)
}

/* Return the value of function arg #argnum (1=first arg) as an unsigned int. */
function uint_arg:long (argnum:long) {
	return _stp_arg2(argnum, 0, 1, 0)
}

function long_arg:long (argnum:long) {
	return _stp_arg2(argnum, 1, 0, 0)
}

function ulong_arg:long (argnum:long) {
	return _stp_arg2(argnum, 0, 0, 0)
}

function longlong_arg:long (argnum:long) {
	if (probing_32bit_app()) {
		lowbits = _stp_arg2(argnum, 0, 1, 0)
		highbits = _stp_arg2(argnum+1, 0, 1, 0)
		return ((highbits << 32) | lowbits)
	} else
		return _stp_arg2(argnum, 0, 0, 1)
}

function ulonglong_arg:long (argnum:long) {
	return longlong_arg(argnum)
}

function pointer_arg:long (argnum:long) {
	return _stp_arg2(argnum, 0, 0, 0)
}

function s32_arg:long (argnum:long) {
	return int_arg(argnum)
}

function u32_arg:long (argnum:long) {
	return uint_arg(argnum)
}

function s64_arg:long (argnum:long) {
	return longlong_arg(argnum)
}

function u64_arg:long (argnum:long) {
	return ulonglong_arg(argnum)
}

function asmlinkage() %{ /* pure */ %}

function fastcall() %{ /* pure */ %}

function regparm(n:long) %{
	if (CONTEXT->user_mode_p && _stp_is_compat_task()
            && (STAP_ARG_n < 0 || STAP_ARG_n > 3)) {
		snprintf(CONTEXT->error_buffer, sizeof(CONTEXT->error_buffer),
			"For -m32 programs, "
			"regparm value must be in the range 0-3.");
		CONTEXT->last_error = CONTEXT->error_buffer;
	} else if (STAP_ARG_n < 0 || STAP_ARG_n > 6) {
		snprintf(CONTEXT->error_buffer, sizeof(CONTEXT->error_buffer),
			"For x86_64, regparm value must be in the range 0-6.");
		CONTEXT->last_error = CONTEXT->error_buffer;
	} else
		CONTEXT->regparm = _STP_REGPARM | (int) STAP_ARG_n;
%}
