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

Should Android use linux_raw by default? #1095

Open
sunfishcode opened this issue Jul 24, 2024 · 4 comments
Open

Should Android use linux_raw by default? #1095

sunfishcode opened this issue Jul 24, 2024 · 4 comments

Comments

@sunfishcode
Copy link
Member

#1090 proposes to switch the default backend on Android from libc to linux_raw. I myself don't have enough context to determine whether this is a good idea. I'm opening this issue in case anyone who sees this has any insight to add.

@maurer
Copy link

maurer commented Aug 13, 2024

As a data point, in the platform, we use the libc backend explicitly. Changing the default won't cause us any trouble, but we chose to do it this way because:

  1. It makes it possible to automatically use newer syscalls on newer platforms. For example, open will use openat, but on older systems, it would use open directly. This means that using libc allows you to get new kernel features as they're available, rather than being stuck with the lowest common denominator of system call options of all the devices you want to target.
  2. There are various sanitizer and tracking tools that plug into bionic. Continuing with the open example, fdtrack is implemented inside bionic - by using the system calls directly, code using Rustix will be hidden from these. Opting out of them is fine, but I would expect most folks to want interaction to work by default.
  3. That's the ABI we actually require to work. We don't technically require that OEMs ship Linux, that's just by far the easiest way to meet the requirements. libc on the other hand has to work, and has a defined stable ABI. This one is likely of less interest to you, since in practice, nearly every Android device runs Linux.

@joshtriplett
Copy link
Member

For 1: rustix is in some ways a lower-level interface than the platform libc or the standard library, and many users of rustix want to directly use specific syscalls and know which ones they're using. I think people already understand that by using rustix they may create dependencies on newer kernels for the functionality they use, and may have to write their own fallbacks if they want to support older kernels.

For 3: the Rust targets are e.g. aarch64-linux-android, and do assume Linux in various ways already. If someone wanted to run Rust binaries on an Android target that didn't use Linux, they'd have to use a different Rust target.

Point 2 is the one that seems like a tradeoff where people might want either behavior. This is a standard argument that reoccurs regularly. Some debugging tools operate on syscalls, some operate on library calls, and anything that uses syscalls directly will bypass debugging tools that operate on library calls. Operating on syscalls also bypasses LD_PRELOAD hacks; such hacks are useful for some purposes, but never guaranteed to intercept everything comprehensively. The Rust standard library already has some cases where it bypasses libc and calls syscalls directly; there's no ABI guarantee that any particular call corresponds to any particular set of libc calls or syscalls.

I would argue that any debugging tool relying on all operations passing through libc calls is always going to miss some things, and is only useful for debugging cooperative programs. And if you're counting on programs to be cooperative, I think it's reasonable to need to build the program with options that target debugging.

Based on that, I'd argue that rustix should default to linux_raw on Android, but that it should document that on both Linux and Android systems, you may want to use the libc backend if you want your program to have better (though still not comprehensive) support for certain debugging tools or LD_PRELOAD-based interceptors.

@maurer
Copy link

maurer commented Nov 4, 2024

For 1: rustix is in some ways a lower-level interface than the platform libc or the standard library, and many users of rustix want to directly use specific syscalls and know which ones they're using. I think people already understand that by using rustix they may create dependencies on newer kernels for the functionality they use, and may have to write their own fallbacks if they want to support older kernels.

I've more frequently seen it used as a way to get a more Rust-ergonomic interface to features exported by libc - one of the most common uses I see is people trying to issue ioctl - issuing that directly through the libc crate is a giant pain, but rustix makes it fairly easy. Another similar example is epoll. Maybe this doesn't match the rest of the ecosystem, but what I've seen in Android has been that rustix gets used by folks that want to call lower level functionality than is bound by libstd, but without the manual effort needed to use libc. This is especially exacerbated in cases where the C-version of the libc interface expects the user to use macros, which the libc crate does not meaningfully provide, but the rustix crate essentially replaces.

Re: "if they want to support older kernels", Android apps ship against an SDK version, not against a kernel version, and need to work on all kernels that vendors were allowed to ship with that SDK version. (I'm assuming apps here, because for platform or vendor work, folks are mostly grabbing packages from us, at which point the defaults become largely irrelevant.) The table for which kernel versions you must support for which API versions is somewhat complex, but libc effectively "solves" this for you by having a list of functions available at different API levels, with the implementation on each device doing what works on their kernel.

I would argue that any debugging tool relying on all operations passing through libc calls is always going to miss some things, and is only useful for debugging cooperative programs. And if you're counting on programs to be cooperative, I think it's reasonable to need to build the program with options that target debugging.

This is correct, but everything built with the SDK/NDK via C goes through libc by default, because doing raw system calls is rarely if ever going to end up being more efficient. This isn't a "debug mode", it's the normal mode that non-Rust native code uses. Usually direct system call access means that something is either malware or being obfuscated/packed - a normal program is usually analyzable by things like this.

All that said, this doesn't cause direct difficulties for our work if the default is raw system calls since as I pointed out, we explicitly set the backend for platform builds; I just think it is likely to be a hazard for app developers, as it may result in them accidentally producing apps which don't actually run on the target API level they describe, which may result in devices failing to run them that they expected would work, or trouble with store analyses etc.

@joshtriplett
Copy link
Member

I've more frequently seen it used as a way to get a more Rust-ergonomic interface to features exported by libc

Clarification: I meant that rustix is in some ways lower level (or at the same level as) the platform C library, in that it isn't necessarily expected to call a libc function to do its work, and often calls a syscall directly just as libc does. I didn't mean that rustix is lower level than the libc crate.

because doing raw system calls is rarely if ever going to end up being more efficient

It's not at all an issue of efficiency. If this were just about efficiency, I would absolutely agree that that's not a good enough reason to bypass libc. (Though sometimes size or dependencies are useful kinds of efficiency.) It's more about control: sometimes you want to invoke a specific syscall, or have the direct syscall interface rather than the libc abstraction (e.g. to get access to kernel functionality that libc doesn't expose).

Any time you can get the functionality in a higher-level way (e.g. through std or through your app framework), you probably should. If you're using rustix directly, it's probably for a reason.

That said, I'd also expect Rust app frameworks for Android to provide things such as feature flags for what Android SDK version your app supports, and those could in turn depend on any bits rustix might have for supporting older kernels.

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

No branches or pull requests

3 participants