[gdb/testsuite] Update gdb.arch/i386-mpx-call.exp for -m32
authorTom de Vries <tdevries@suse.de>
Fri, 11 Dec 2020 17:26:40 +0000 (18:26 +0100)
committerTom de Vries <tdevries@suse.de>
Fri, 11 Dec 2020 17:26:40 +0000 (18:26 +0100)
commit346e7e192370a4d602e14466825c329ed5920c8f
treeb1037663f86a98cb734ea402b79e97943c3c4392
parent02c727013cc3ae08b86ad360429f9a0dbdc0ec92
[gdb/testsuite] Update gdb.arch/i386-mpx-call.exp for -m32

When running test-case gdb.arch/i386-mpx-call.exp with target board unix/-m32,
we run into:
...
(gdb) continue^M
Continuing.^M
(gdb) FAIL: gdb.arch/i386-mpx-call.exp: upper_bnd0: continue to a bnd violation
...

Let's look first for reference at -m64, where the test passes.

The test-case uses -mmpx -fcheck-pointer-bounds to generate pointer checks in
the exec.  Effectively, -fcheck-pointer-bounds modifies the calling ABI: a
call passes pointer bounds as well as arguments.  The call to upper (with
four pointer arguments and an int argument, passed in 5 registers) is modified
like this:
...
   lea    -0xa0(%rbp),%rcx
   lea    -0x80(%rbp),%rdx
   lea    -0x60(%rbp),%rsi
   lea    -0x40(%rbp),%rax
   mov    $0x0,%r8d
+  bndmov -0x110(%rbp),%bnd3
+  bndmov -0x100(%rbp),%bnd2
+  bndmov -0xf0(%rbp),%bnd1
+  bndmov -0xe0(%rbp),%bnd0
   mov    %rax,%rdi
-  callq  <upper>
+  bnd callq <upper>
...
passsing the four pointer bounds in bounds registers BND0-3.

The top-level mechanism of the test is as follows:
- run the exec to after all mallocs are done, such that all pointer variables
  are valid
- do inferior calls, similar to those present in the program

The inferior call mechanism doesn't differentiate between a call to a function
compiled with -fcheck-pointer-bounds, and one without.  It merely resets the
bound registers to all-allowed state (see amd64_push_dummy_call), to make sure
the checks don't trigger during the inferior call.  [ This is the same as what
happens when executing a call without bnd prefix when the BNDPRESERVE bit of
the BNDCFG register is set to 0, a provision for calling an instrumented
function using a non-instrumented call. ]

First, two inferior calls are done (default_run and verify_default_values)
with the bound registers unmodified by the test.  So, the memory accesses are
performed with the bounds registers set by amd64_push_dummy_call to
all-allowed, and the bounds checks do not trigger.

Then we try to do an inferior call with modified bounds registers, set to
none-allowed.  In order to do that, we set a breakpoint at *upper before
doing the inferior call.  Once we hit the breakpoint during the inferior call,
the bounds registers are set to none-allowed, and we continue expecting to run
into an triggered bounds check, which takes the shape of a sigsegv.

Back to -m32.  Here, the pointer arguments are passed in memory rather than
registers, so with -fcheck-pointer-bounds, the pointer bounds are placed in
the Bounds Table using bndstx:
...
  movl   $0x0,0x10(%eax)
  lea    -0x70(%ebp),%edx
  mov    %edx,0xc(%eax)
  lea    -0x5c(%ebp),%edx
  mov    %edx,0x8(%eax)
  lea    -0x48(%ebp),%edx
  mov    %edx,0x4(%eax)
  lea    -0x34(%ebp),%edx
  mov    %edx,(%eax)
  lea    0xc(%eax),%edx
  mov    0xc(%eax),%ecx
  bndmov -0xa8(%ebp),%bnd1
  bndstx %bnd1,(%edx,%ecx,1)
  lea    0x8(%eax),%edx
  mov    0x8(%eax),%ecx
  bndmov -0xa0(%ebp),%bnd3
  bndstx %bnd3,(%edx,%ecx,1)
  lea    0x4(%eax),%edx
  mov    0x4(%eax),%ecx
  bndmov -0x98(%ebp),%bnd1
  bndstx %bnd1,(%edx,%ecx,1)
  mov    (%eax),%edx
  bndmov -0x90(%ebp),%bnd3
  bndstx %bnd3,(%eax,%edx,1)
  bnd call 804893f <upper>
...

Again, the bounds registers are reset at the start of the inferior call by
amd64_push_dummy_call, and modified by the test-case, but neither has any
effect.  The code in upper reads the pointer bounds from the Bounds Table, not
from the bounds registers.

Note that for a test.c with an out-of-bounds access:
...
$ cat test.c
void foo (int *a) { volatile int v = a[1]; }
int main (void) { int a; foo (&a); return 0; }
$ gcc test.c -mmpx -fcheck-pointer-bounds -g -m32
$ ./a.out
Saw a #BR! status 1 at 0x804848d
...
and inferior call foo (&a) right before "bnd call foo" (at the point that the
bounds for a are setup in the bounds table) doesn't trigger a bounds violation:
...
(gdb) call foo (&a)
(gdb)
...
This is because the bounds table doesn't associate a pointer with bounds, but
rather a pair of pointer and pointer location.  So, the bound is setup for &a,
with as location the pushed argument in the frame.  The inferior call however
executes in a dummy frame, so the bound is checked for &a with as location the
pushed argument in the dummy frame, which is different, so the bounds check
doesn't trigger.

In conclusion, this is expected behaviour.

Update the test-case to not expect to override effective pointer bounds using
the bounds registers when the bounds passing is done via the Bounds Table.

Tested on x86_64-linux.

gdb/testsuite/ChangeLog:

2020-12-11  Tom de Vries  <tdevries@suse.de>

PR testsuite/26991
* gdb.arch/i386-mpx-call.exp: Don't expect to trigger bounds
        violations by setting bounds registers if the bounds are passed in the
        Bounds Table.
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.arch/i386-mpx-call.exp