-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
std::threads: revisit stack address calculation on netbsd. #122002
Conversation
like older linux glibc versions, we need to get the guard size and increasing the stack's bottom address accordingly.
rustbot has assigned @workingjubilee. Use r? to explicitly pick a reviewer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR fixing this for NetBSD! I may be being a bit thick, so my apologies, but it would help if you could explain some context here.
cc @he32 It's not clear to me which versions of NetBSD we support based on our platform support docs for NetBSD. Can you offer some guidance on what we should do here, in light of things like https://gnats.netbsd.org/57721 |
The TL;DR; version is "NetBSD 9.0 or newer". We used to support rust on NetBSD/8.0, but recent-ish bumps of LLVM have upped the requirement for the C++ compiler to such an extent that supporting 8.0 was no longer feasible using the "native" C++ compiler. There are platform-dependent exceptions, e.g. the That said, I have sadly not yet been able to translate Taylor's investigations in the quoted NetBSD PR back into a workable platform-dependent patch for rust. However, rust 1.75.0 on NetBSD/amd64 builds a working firefox and thunderbird, and I have no reason to believe 1.76.0 is any worse in that regard. |
Is there an explanation somewhere of why Rust is querying the stack parameters, and what Rust does with them? Programs don't normally need to do this, so it would be helpful to know what Rust is trying to achieve. The layout of the stack in NetBSD is documented here: https://man.NetBSD.org/stack.7 Note that it is different for the main thread and for threads created with pthread_create. Also, the usable size of the main thread's stack -- and thus the exact least/greatest address of the stack, depending on whether it grows down or up -- may vary at runtime depending on the stack size rlimit. So if you ask for the least/greatest usable stack address at two different times, you may get two different answers, if the process called setrlimit in the middle. That's why it would be helpful to know what Rust wants this for before committing to a way to do it. |
@riastradh This information is used to make decisions on how to set up our guard pages, if we need to set them up, which itself is a very OS-specific decision. It is also used to inform error reporting on whether the reason we hit a signal handler is stack overflow or what. This function will be called at program startup: rust/library/std/src/sys/pal/unix/thread.rs Lines 856 to 866 in b77e018
|
Programs, strictly speaking, do not "need" to do anything useful, yet we demand they be useful anyways. Anyways, I don't think anything in std calls |
@devnexen Hmm. Is this fallthrough implementation correct for NetBSD? We don't have to also fix this in the same PR, but I'm feeling somewhat doubtful that the code becomes meaningfully more correct if we don't touch both places. rust/library/std/src/sys/pal/unix/thread.rs Lines 924 to 954 in b77e018
|
I m not sure it s incorrect but more do we really to do this with NetBSD ? I m fine reverting back this change if needed. |
Sorry, didn't mean to get philosophical here, just wanted to know what this query is in the service of. I only said that to explain it's such an unusual thing to do I didn't have a guess about why Rust might be doing it.
Great, so that means you don't have to worry about the pthread_attr_setstack bug with the guard region.
If you want to guarantee there is a guard region of at least a particular size in threads created with pthread_create, you can do that portably with pthread_attr_setguardsize, so there's no need to query it for that purpose. For the main thread, it may be a different story on other operating systems. But on NetBSD, there is always a guard region of a number of bytes given by sysctl vm.guard_size -- default one megabyte (1048576 bytes), and I don't think there's any supported way to change it. So unless you want a guard region larger than that, there's no need to query or do anything. (That's separate from the pages allocated for the difference between the soft and hard stack size rlimits on exec, which is what https://man.netbsd.org/stack.7 calls the inaccessible pages.)
In that case:
So I gathered, but what uses the results of that function? |
We do not "provide some way to call setrlimit" per se, however I should note that it is incorrect to reason about what std does as definitive for a process, because we do "provide a way" to call arbitrary extern "C" {
fn setrlimit(resource: i32, rlim: *const rlimit) -> i32;
} And a Rust-defined |
There's a difference between these two issues:
|
To answer this, it is called in the main runtime startup: Lines 71 to 107 in 1c580bc
Which then uses it to set these: rust/library/std/src/sys_common/thread_info.rs Lines 13 to 18 in 1c580bc
Which finally becomes an answer for a question way over here: rust/library/std/src/sys/pal/unix/stack_overflow.rs Lines 82 to 87 in 735f758
Wow that's a lot of hops to trace this logic. |
OK, cool, thanks for tracking this down! Is there another call for threads created with pthread_create, or is it only called for the main thread? If the code you quoted covers all uses of the outputs, then it looks like this logic is used only to determine a region of memory in which a SIGSEGV probably indicates a stack overflow. In that case: I suggest you just use pthread_attr_getstack to get p and n, and pthread_attr_getguardsize to get g (or use g = PAGE_SIZE), and use [p - g, p) on machines where the stack grows down, or [p + n, p + n + g) on machines where the stack grows up, as the guard region for the SIGSEGV handler's stack overflow detection. This is not quite accurate for the main thread, but the harm of the inaccuracy is just that some SIGSEGVs which actually are stack overflows -- in programs that have raised the soft rlimit -- may not be reported as stack overflows. Nothing else bad will happen. So these existing fragments are probably fine to use for NetBSD, on machines where stacks grow down: rust/library/std/src/sys/pal/unix/thread.rs Lines 806 to 833 in 79d2461
rust/library/std/src/sys/pal/unix/thread.rs Lines 860 to 873 in 79d2461
Or the OpenBSD case below, which is identical: rust/library/std/src/sys/pal/unix/thread.rs Lines 914 to 923 in 79d2461
Side notes about this code:
The above assumes you don't create pthreads with pthread_attr_setstack. If you do, then it is your responsibility to allocate guard pages and tell the signal handler what guard pages you allocated for it. (That's true for all operating systems; it's not NetBSD-specific.) |
@devnexen Why are you still defining a NetBSD-specific get_stack_start, when it looks like the existing one that is currently shared with android/freebsd/hurd/linux/l4re should work just fine (or, no worse than it already works for those other platforms, anyway)? You seem to be trying to work around the pthread_attr_setstack bug, but that bug doesn't apply here (and adjusting the stack address you get out of pthread_attr_getstack doesn't help with that bug). The pthread_attr_setstack bug applies only when you create a thread with pthread_attr_setstack, which this logic is not doing; it doesn't apply when you query an existing thread, which this logic is doing. |
18c7350
to
ffdd97f
Compare
Almost. The rust/library/std/src/thread/mod.rs Lines 459 to 541 in 8401645
From rust/library/std/src/sys/pal/unix/thread.rs Lines 964 to 1021 in 79d2461
We may wish to adjust how we set up threads, in light of this information (this has been quite illuminating, thank you! I wish the man pages for the pthread headers were as clear...) but I believe it's currently harmless, because this information still is only consumed by the signal handler.
Right, it sounds like for rust/library/std/src/sys/pal/unix/thread.rs Lines 914 to 923 in 79d2461
|
Based on my understanding of how things are so far, I think this is the correct change, and nothing in my last message actually changes things, so we can ship this, assuming @riastradh or @he32 also agrees. Thank you for bearing with us as we hashed this out, @devnexen. You do a lot of work for the BSD targets and it's nice to see. @bors delegate=@riastradh rollup=always |
✌️ @riastradh, you can now approve this pull request! If @workingjubilee told you to " |
OK, so it sounds like there is nothing else that uses sys::thread::guard::init, then? In other words: this is used exclusively to find the main thread's stack guard region, in order to recognize stack overflows in the SIGSEGV handler?
Interesting, so there's separate logic to find the stack guards of non-main threads? That logic looks fine too. For NetBSD (as well as Linux and probably every other system) it might be worthwhile, in the main thread's stack overflow recognition on SIGSEGV, to use the combination of what is called the guard and the inaccessible pages at https://man.NetBSD.org/stack.7 -- that is, the region between the soft rlimit and the hard rlimit, plus the guard. But that's a little more work -- and it's not really NetBSD-specific because other OSes also grow the stack automatically, so there's no need to put it in this PR, which, as I understand it, is really only needed to omit Rust's attempt to mmap/mprotect its own guard page at the process's minimum possible stack size when NetBSD already put one at the maximum possible stack size.
If you can point out any parts you found unclear in the pthread man pages in NetBSD, I'd be happy to take a look and see if we can improve them!
Yes, that looks good.
On most systems, I expect the main thread's stack will grow automatically up to the soft rlimit (which can, in turn, be grown up to the hard rlimit). So if that's what you mean by 'the stack isn't immutable', that's not NetBSD-specific.
Note that pthread_attr_setstack doesn't change any existing threads. It only sets up configuration (pthread attributes) that can be passed to pthread_create when creating new threads. If you do pthread_getattr_np(T, &A) and then pthread_attr_setstack(&A, ...), that doesn't change anything about the existing thread T -- pthread_getattr_np just makes a copy of T's configuration; the resulting pthread_attr_t A has no lingering connection to T. And if someone does write Rust code to create a pthread with a custom stack using pthread_attr_setstack and pthread_create, they will have to arrange their own guard pages (mapping them and informing the SIGSEGV handler of them) on every OS, not just NetBSD. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current state of this PR -- with a one-line change to use the existing OpenBSD code for sys::thread::guard::init on NetBSD too -- looks good to me by code inspection, assuming you've tested it and verified it works (which I have not done).
@@ -911,9 +911,10 @@ pub mod guard { | |||
} | |||
}) * page_size; | |||
Some(guard) | |||
} else if cfg!(target_os = "openbsd") { | |||
} else if cfg!(any(target_os = "openbsd", target_os = "netbsd")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I was being a bit dense earlier, it seems!
honestly no idea what the comment that says "the stack is immutable" really means. And while I said that OpenBSD and NetBSD should share the logic in question for @bors rollup=always r=workingjubilee,riastradh |
…r=workingjubilee,riastradh std::threads: revisit stack address calculation on netbsd. like older linux glibc versions, we need to get the guard size and increasing the stack's bottom address accordingly.
Rollup of 8 pull requests Successful merges: - rust-lang#121148 (Add slice::try_range) - rust-lang#121573 (unix_sigpipe: Add test for SIGPIPE disposition in child processes) - rust-lang#121633 (Win10: Use `GetSystemTimePreciseAsFileTime` directly) - rust-lang#121840 (Expose the Freeze trait again (unstably) and forbid implementing it manually) - rust-lang#121907 (skip sanity check for non-host targets in `check` builds) - rust-lang#122002 (std::threads: revisit stack address calculation on netbsd.) - rust-lang#122108 (Add `target.*.runner` configuration for targets) - rust-lang#122298 (RawVec::into_box: avoid unnecessary intermediate reference) r? `@ghost` `@rustbot` modify labels: rollup
Rollup of 9 pull requests Successful merges: - rust-lang#121148 (Add slice::try_range) - rust-lang#121633 (Win10: Use `GetSystemTimePreciseAsFileTime` directly) - rust-lang#121840 (Expose the Freeze trait again (unstably) and forbid implementing it manually) - rust-lang#121907 (skip sanity check for non-host targets in `check` builds) - rust-lang#122002 (std::threads: revisit stack address calculation on netbsd.) - rust-lang#122108 (Add `target.*.runner` configuration for targets) - rust-lang#122298 (RawVec::into_box: avoid unnecessary intermediate reference) - rust-lang#122315 (Allow multiple `impl Into<{D,Subd}iagMessage>` parameters in a function.) - rust-lang#122326 (Optimize `process_heap_alloc`) r? `@ghost` `@rustbot` modify labels: rollup
Rollup merge of rust-lang#122002 - devnexen:thread_stack_netbsd_fix, r=workingjubilee,riastradh std::threads: revisit stack address calculation on netbsd. like older linux glibc versions, we need to get the guard size and increasing the stack's bottom address accordingly.
like older linux glibc versions, we need to get the guard size
and increasing the stack's bottom address accordingly.