Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i#6417: restore registers before syscall. #6475

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

ivankyluk
Copy link
Contributor

@ivankyluk ivankyluk commented Nov 27, 2023

Restore the value of the output register for syscall before calling syscall. The output register might be used as an input parameter for the kernel. For example, ECX is used to store the length for syscall mmap2, as well as being used to store the output of the syscall.

The problem can be reproduced by running

bin32/drrun -debug -loglevel 4 -logdir . -stderr_mask 0xC -dumpcore_mask 0 -code_api -t drcachesim -ipc_name suite/tests/drtestpipe_miss_analyzer -simulator_type miss_analyzer -miss_count_threshold 5000 -miss_frac_threshold 0.25 -- suite/tests/bin/stride_benchmark

on AMD 32 bit system.

Add a new test syscall-mmap and use -record_syscall to verify the parameters to mmap/mmap2 are correct.

Issue: #6417

ext/drreg/drreg.c Outdated Show resolved Hide resolved
instr_writes_to_exact_reg(inst, reg_64_to_32(reg),
DR_QUERY_INCLUDE_COND_SRCS)))
/* Make sure we don't consider writes to sub-regs. i#6417: in case
* of a syscall, restore the value of the output register since it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"restore the value": this doesn't achieve that for an inlined syscall. It avoids treating it as dead, which will end up restoring the value for a fragment-final syscall (I think the drreg end of block restores come in before the syscall instr that ends the block instead of after b/c of DR's rules for block termination). But this doesn't solve the problem for a syscall inlined into a block or trace (==superblock, not a memtrace). For that, drreg should treat the syscall as a barrier and actively restore all app values prior to it. This may be as simple as adding an instr_is_syscall check where DR_NOTE_REG_BARRIER is identified today; but then test(s) need to be created too.

If you don't want to tackle that here, this change as you have it will fix drmemtrace when -disable_traces is on (which is not the case by default: maybe it should be; it is the case for our own internal uses), so one option is to put in TODO comments that clearly explain that more is needed. A new issue should be filed I would think to cover the syscall barrier.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually a little confused as to whether a trace will be built through a non-ignorable syscall (drmemtrace marks all syscalls as non-ignorable via the filter event) -- so I'm not certain what happens without -disable_traces in drmemtrace. I would think it can't build the trace as it needs to invoke the client callbacks; but at a glance I don't see FRAG_MUST_END_TRACE being added. Probably just missed it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the code to treat syscall as a barrier to restore register values. Please take another look,

@ivankyluk ivankyluk changed the title i#6417: restore output register for syscall. i#6417: restore registers before syscall. Dec 5, 2023
* DAMAGE.
*/

#include <sys/mman.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this file should be in suite/tests/linux/ as mmap is unix-specific (that's the directory that has the mmap.c test as well).

{
void *p;
const size_t size = 0x00008765;
printf("Calling mmap(0, 0x%x, 0x%x, 0x%x, -1, 0)\n", (unsigned int)size,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally we include tools.h and use print for reliable interleaving with client/DR output. Ditto below.

* they may be used as input parameters for the kernel. For example,
* ECX is used to store the length for mmap2. Register used to store the
* output of the syscall is marked as not spilled, so we need to check for
* syscall as well.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if drreg treated ecx as dead, it wouldn't have preserved the original app value, so it won't have that value to restore. (So why did the tests pass? Did drmemtrace happen to not clobber ecx b/c it picked other scratch regs?). I think we want to keep your original change to not treat a syscall dest reg as dead, and then we'll spill it and it will be available for restores. (And we won't then want this check below for instr_is_syscall).

@@ -486,6 +486,11 @@ drreg_insert_restore_all(void *drcontext, instrlist_t *bb, instr_t *inst,
(instr_is_label(inst) &&
((ptr_uint_t)instr_get_note(inst) == DR_NOTE_ANNOTATION ||
(ptr_uint_t)instr_get_note(inst) == DR_NOTE_REG_BARRIER)) ||
/* i#6417: in case of a syscall, restore the values of registers since
* they may be used as input parameters for the kernel. For example,
* ECX is used to store the length for mmap2.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this code is restoring the flags. I don't think they are an input to any syscall on any platform (they are an output on Mac).

* they may be used as input parameters for the kernel. For example,
* ECX is used to store the length for mmap2.
*/
instr_is_syscall(inst) ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good for pre-syscall. But after the syscall we need to update the spilled values with whatever the kernel wrote: so we need to also check for instr_is_syscall in the loop labeled /* After each app write, update spilled app values: */. Otherwise, a restore at the end of the block will bring in the old pre-syscall value.

To test, add an asm sequence to drreg-test (ifdef LINUX I guess) with a single block with a syscall in the middle and a pattern the test client can find (see existing DRREG_TEST_38_ASM e.g.) and the test client for that block can then reserve eax and ecx and clobber both register both before and after the syscall.
Verify the mmap succeeded and that the app's eax is correctly restored at the end of the block (maybe have the app check the syscall return value in the next block or sthg).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants