Repository navigation
Conversation
To replay an execve(), rr makes the task exec rr_exec_stub with Task::os_exec, which writes the arguments for that exec at the start of the page that the task's stack pointer is in: three words of the tracee's size (an argv array whose terminating NULL also serves as the empty envp) followed by the stub's file name. When the task shares its address space with another process, e.g. a vfork child, os_exec saves that memory first and restores it after the exec, since the other process still uses it. But it saved and restored only filename.size() + 1 + 2 * sizeof(size_t) bytes, with rr's own size_t. When that is the tracee's word size (a 64-bit tracee under a 64-bit rr, or a 32-bit tracee under a 32-bit build of rr), that is one word less than it writes, so the last word's worth of what it wrote, the end of the stub's file name, stayed in the memory the task shares with the other process. Recording doesn't write there, so the memory of the vfork parent's stack differs in replay. The parent usually doesn't read it again (it's below its stack pointer), but --checksum notices: Divergence in contents of memory segment after 'SYSCALL: vfork': 0x7ffd581d7000-0x7ffd581fa000 rw-p ... (recorded checksum:...; replaying checksum:...) This regressed in 7f3bb73 ("Set up an empty argv[0] string when we exec the rr stub, to avoid a kernel warning."), which added two words to what os_exec writes but only one to what it saves. So save three 8-byte words and the file name, which covers what os_exec writes for every tracee word size. Test: vfork_checksum records and replays the vfork test, whose vfork child execs, with --checksum=on-all-events. Results on x86_64 (Linux 7.0, glibc 2.31), with a 64-bit build of rr: - Without this change, the test fails in every run of the 64-bit variant without the syscall buffer (20 of 20). The other variants pass: a 32-bit tracee's three 4-byte words fit in the two 8-byte words that os_exec saved, and with the syscall buffer the child makes the syscall on its own stack in its scratch memory, which nothing else uses. - With this change, it passes in 50 of 50 runs in each of the four variants, as do vfork, vfork_done, vfork_exec, vfork_flush and vfork_shared. - The full test suite shows no new failures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[Encountered, debugged and patch by AI]
To replay an execve(), rr makes the task exec rr_exec_stub with Task::os_exec, which writes the arguments for that exec at the start of the page that the task's stack pointer is in: three words of the tracee's size (an argv array whose terminating NULL also serves as the empty envp) followed by the stub's file name. When the task shares its address space with another process, e.g. a vfork child, os_exec saves that memory first and restores it after the exec, since the other process still uses it. But it saved and restored only filename.size() + 1 + 2 * sizeof(size_t) bytes, with rr's own size_t. When that is the tracee's word size (a 64-bit tracee under a 64-bit rr, or a 32-bit tracee under a 32-bit build of rr), that is one word less than it writes, so the last word's worth of what it wrote, the end of the stub's file name, stayed in the memory the task shares with the other process. Recording doesn't write there, so the memory of the vfork parent's stack differs in replay. The parent usually doesn't read it again (it's below its stack pointer), but --checksum notices:
Divergence in contents of memory segment after 'SYSCALL: vfork':
0x7ffd581d7000-0x7ffd581fa000 rw-p ... (recorded checksum:...;
replaying checksum:...)
This regressed in 7f3bb73 ("Set up an empty argv[0] string when we exec the rr stub, to avoid a kernel warning."), which added two words to what os_exec writes but only one to what it saves.
So save three 8-byte words and the file name, which covers what os_exec writes for every tracee word size.