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.