// uconversions tapset -- BPF version
// Copyright (C) 2018-2022 Red Hat Inc.
//
// This file is part of systemtap, and is free software.  You can
// redistribute it and/or modify it under the terms of the GNU General
// Public License (GPL); either version 2, or (at your option) any
// later version.

// TODO: Really need a solution for redundant docstrings.
// TODO: Use jump_to_catch/register_error for user_long{,_warn} type functions.

function user_string_n:string (addr:long, n:long)
{ /* pure */ /* bpf */
  // TODO: Does not provide address in error message.
  return user_string_n(addr, n, "user string copy fault")
}

function user_string_n:string (addr:long, n:long, err_msg:string)
%( 1 == 0 %?
{ /* pure */ /* bpf */
  /* !!! ACHTUNG !!!
   * bpf uses the same bpf_probe_read() helpers for kernel and user
   * addresses, on the assumption that the address spaces coincide.
   * Which only really works on x86_64 in Current Day.
   *
   * If the address space is changed, it may return the wrong data.
   * TODO PR25168: Fix this as soon as BPF ships proper, separate
   * bpf_probe_read_{user,kernel}() helpers.
   */
  %( arch == "x86_64" %?
     // TODO: Does not use the provided err_msg.
     return kernel_string_n(addr, n) // calls probe_read_str()
  %:
     // TODO: Use error() function.
     print("ERROR(unsupported): %s", err_msg)
     exit()
  %)
}
%:
%{ /* bpf */ /* pure */
  /* if (n > BPF_MAXSTRINGLEN) n = BPF_MAXSTRINGLEN; */
  jle $n, BPF_MAXSTRINGLEN, skip
  mov $n, BPF_MAXSTRINGLEN

  /* buf = bpf_stk_alloc(BPF_MAXSTRINGLEN);
     buf[0] = 0x0; // guarantee NUL byte
     rc = bpf_probe_read_user_str(buf, n, addr); */
  skip: alloc, $buf, BPF_MAXSTRINGLEN
  stw [$buf+0], 0 /* guarantee NUL byte */
  call $rc, probe_read_user_str, $buf, $n, $addr
  call -, printf, "hello from the new bpf assembler"
  /* TODO <- should work if the helper is named bpf_probe_read_user_str() too */
  /* TODO <- substitute probe_read_str automatically for older bpf? */

  /* if (rc < 0) error("...", addr); */
  jsge $rc, 0, done
  call -, printf, "ERROR: string copy fault at %p [man error::fault]\n", $addr
  /* TODO <- document stapbpf version of error::fault */
  /* TODO <- return value reg should be optional */
  /* TODO <- use jump_to_catch error handling? */
  call -, printf, "%s", $err_msg
  call -, exit

  /* return buf; */
  done: mov $$, $buf
%}
%)

function user_string_n_warn:string (addr:long, n:long, warn_msg:string)
%( 1 == 1 %?
{ /* pure */ /* bpf */
  /* !!! ACHTUNG !!!
   * bpf uses the same bpf_probe_read() helpers for kernel and user
   * addresses, on the assumption that the address spaces coincide.
   * Which only really works on x86_64 in Current Day.
   *
   * If the address space is changed, it may return the wrong data.
   * TODO PR25168: Fix this as soon as BPF ships proper, separate
   * bpf_probe_read_{user,kernel}() helpers.
   */
  %( arch == "x86_64" %?
     return kernel_string_n(addr, n, warn_msg) // calls probe_read_str()
  %:
     return warn_msg // don't even bother
  %)
}
%:
%{ /* bpf */ /* pure */
  /* buf = bpf_stk_alloc(BPF_MAXSTRINGLEN);
     buf[0] = 0x0; // guarantee NUL byte
     rc = bpf_probe_read_user_str(buf, BPF_MAXSTRINGLEN, addr); */
  alloc, $buf, BPF_MAXSTRINGLEN;
  0x62, $buf, -, -, 0x0; /* stw [$buf+0], 0x0 -- guarantee NUL byte */
  call, $rc, probe_read_user_str, $buf, BPF_MAXSTRINGLEN, $addr;

  /* if (rc < 0) return err_msg;
     return buf; */
  0xc5, $rc, -, _err, 0; /* jslt $rc, 0, _err */
  0xbf, $$, $buf, -, -; /* mov $$, $buf */
  0x05, -, -, _done, -; /* ja _done; */

  label, _err;
  0xbf, $$, $warn_msg, -, -; /* mov $$, $warn_msg */

  label, _done;
%}
%)

function __bpf_probe_read_user_error:long (addr:long, size:long)
%{ /* bpf */ /* pure */
  /* if (size > 8) error("...", addr, size); */
  0xb5, $size, -, _skip, 8; /* jle $n, 8, _skip */
  call, -, printf, "ERROR: attempted to read %ul (>8) bytes from %p into unsigned long\n", $size, $addr;
  call, -, exit;

  label, _skip;
  /* buf = bpf_stk_alloc(8); // size <= 8
     *buf = (unsigned long)0x0; // guarantee leading zeroes
     rc = bpf_probe_read(buf, size, addr);
   */
  alloc, $buf, 8;
  0x7a, $buf, -, -, 0x0; /* stdw [buf+0], 0 -- guarantee leading zeroes */
  /* TODO PR25168: probe_read is used for both kernel and user memory.
     BPF will soon deprecate these in favour of separate functions. */
  call, $rc, probe_read, $buf, $size, $addr;
  /* call, $rc, probe_read_user, $buf, $size, $addr; */
  /* XXX code for testing LITTLE ENDIAN / BIG ENDIAN */
  /* 0xb7, $rc, -, -, 0x0;
  0x62, $buf, -, -, 0xdeadbeef;
  0x62, $buf, -, 4, 0xea7b3375; /* end test */

  /* if (rc < 0) error("...", addr); */
  0x75, $rc, -, _done, 0; /* jsge $rc, 0, _done */
  call, -, printf, "ERROR: user copy fault at %p [man error::fault]\n", $addr; /* TODO document stapbpf version of error::fault */
  call, -, exit;

  label, _done;
  /* return *(unsigned long *)buf; */
  0x79, $$, $buf, 0x0, -; /* ldxdw $$, buf */
%}
// XXX I assumed I would need to return buf >> 8*(8-size) here,
// but buf is already correctly aligned for smaller sizes. Hmm.

function __signext:long (src:long, size:long) { /* pure */ /* bpf */
  // XXX 2'S COMPLEMENT; assumes upper bits of size are zero
  // as provided by __bpf_probe_read_user_error()
  // thanks to https://graphics.stanford.edu/~seander/bithacks
  bits = 8*size
  mask = 1 << (bits-1)
  return (src ^ mask) - mask
}

function __bpf_probe_read_user_error_signed (addr:long, size:long) { /* pure */ /* bpf */
  return __signext(__bpf_probe_read_user_error(addr, size), size)
}

// TODO We can't be sure of exact lengths of {char,short,int,long}
// so the best guess for now is 1,2,4,8 to match the BPF definitions.
// However, I'm pretty sure sizeof.stp uses some @cast() trickery to
// get the size of a data type. Should we capture it as a macro?

function user_char_error:long (addr:long) { /* pure */ /* bpf */
  // XXX: Note that the lkm version is also reading a signed char,
  // which gets sign extended to a long just like this. I was surprised.
  return __bpf_probe_read_user_error_signed(addr, 1)
}

function user_short_error:long (addr:long) { /* pure */ /* bpf */
  return __bpf_probe_read_user_error_signed(addr, 2)
}

function user_ushort_error:long (addr:long) { /* pure */ /* bpf */
  return __bpf_probe_read_user_error(addr, 2)
}

function user_int_error:long (addr:long) { /* pure */ /* bpf */
  return __bpf_probe_read_user_error_signed(addr, 4)
}

function user_long_error:long (addr:long) { /* pure */ /* bpf */
  /* XXX code for testing LITTLE ENDIAN / BIG ENDIAN */
  /* printf("1: %lx\n2: %x\n4: %lx\n8: %lx\n",
    __bpf_probe_read_user_error(addr, 1),
    __bpf_probe_read_user_error(addr, 2),
    __bpf_probe_read_user_error(addr, 4),
    __bpf_probe_read_user_error(addr, 8)) /* end test */
  return __bpf_probe_read_user_error(addr, 8) // XXX no sign extension needed
}

function user_ulong_error:long (addr:long) { /* pure */ /* bpf */
  return __bpf_probe_read_user_error(addr, 8)
}

function user_int8_error:long (addr:long) { /* pure */ /* bpf */
  return __bpf_probe_read_user_error_signed(addr, 1)
}

function user_uint8_error:long (addr:long) { /* pure */ /* bpf */
  return __bpf_probe_read_user_error(addr, 1)
}

function user_int16_error:long (addr:long) { /* pure */ /* bpf */
  return __bpf_probe_read_user_error_signed(addr, 2)
}

function user_uint16_error:long (addr:long) { /* pure */ /* bpf */
  return __bpf_probe_read_user_error(addr, 2)
}

function user_int32_error:long (addr:long) { /* pure */ /* bpf */
  return __bpf_probe_read_user_error_signed(addr, 4)
}

function user_uint32_error:long (addr:long) { /* pure */ /* bpf */
  return __bpf_probe_read_user_error(addr, 4)
}

function user_int64_error:long (addr:long) { /* pure */ /* bpf */
  return __bpf_probe_read_user_error(addr, 8) // XXX no sign extension needed
}

function user_uint64_error:long (addr:long) { /* pure */ /* bpf */
  return __bpf_probe_read_user_error_(addr, 8)
}
