/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include namespace android { namespace bpf { #define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c)) static inline unsigned uncachedKernelVersion() { struct utsname buf; if (uname(&buf)) return 0; unsigned kver_major = 0; unsigned kver_minor = 0; unsigned kver_sub = 0; (void)sscanf(buf.release, "%u.%u.%u", &kver_major, &kver_minor, &kver_sub); return KVER(kver_major, kver_minor, kver_sub); } static inline unsigned kernelVersion() { static unsigned kver = uncachedKernelVersion(); return kver; } static inline bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) { return kernelVersion() >= KVER(major, minor, sub); } static inline bool isKernelVersion(unsigned major, unsigned minor) { return isAtLeastKernelVersion(major, minor, 0) && !isAtLeastKernelVersion(major, minor + 1, 0); } static inline bool __unused isLtsKernel() { return isKernelVersion(4, 4) || // minimum for Android R isKernelVersion(4, 9) || // minimum for Android S & T isKernelVersion(4, 14) || // minimum for Android U isKernelVersion(4, 19) || // minimum for Android V isKernelVersion(5, 4) || // first supported in Android R isKernelVersion(5, 10) || // first supported in Android S isKernelVersion(5, 15) || // first supported in Android T isKernelVersion(6, 1) || // first supported in Android U isKernelVersion(6, 6); // first supported in Android V } // Figure out the bitness of userspace. // Trivial and known at compile time. static constexpr bool isUserspace32bit() { return sizeof(void*) == 4; } static constexpr bool isUserspace64bit() { return sizeof(void*) == 8; } #if defined(__LP64__) static_assert(isUserspace64bit(), "huh? LP64 must have 64-bit userspace"); #elif defined(__ILP32__) static_assert(isUserspace32bit(), "huh? ILP32 must have 32-bit userspace"); #else #error "huh? must be either LP64 (64-bit userspace) or ILP32 (32-bit userspace)" #endif static_assert(isUserspace32bit() || isUserspace64bit(), "must be either 32 or 64 bit"); // Figure out the bitness of the kernel. static inline bool isKernel64Bit() { // a 64-bit userspace requires a 64-bit kernel if (isUserspace64bit()) return true; static bool init = false; static bool cache = false; if (init) return cache; // Retrieve current personality - on Linux this system call *cannot* fail. int p = personality(0xffffffff); // But if it does just assume kernel and userspace (which is 32-bit) match... if (p == -1) return false; // This will effectively mask out the bottom 8 bits, and switch to 'native' // personality, and then return the previous personality of this thread // (likely PER_LINUX or PER_LINUX32) with any extra options unmodified. int q = personality((p & ~PER_MASK) | PER_LINUX); // Per man page this theoretically could error out with EINVAL, // but kernel code analysis suggests setting PER_LINUX cannot fail. // Either way, assume kernel and userspace (which is 32-bit) match... if (q != p) return false; struct utsname u; (void)uname(&u); // only possible failure is EFAULT, but u is on stack. // Switch back to previous personality. // Theoretically could fail with EINVAL on arm64 with no 32-bit support, // but then we wouldn't have fetched 'p' from the kernel in the first place. // Either way there's nothing meaningful we can do in case of error. // Since PER_LINUX32 vs PER_LINUX only affects uname.machine it doesn't // really hurt us either. We're really just switching back to be 'clean'. (void)personality(p); // Possible values of utsname.machine observed on x86_64 desktop (arm via qemu): // x86_64 i686 aarch64 armv7l // additionally observed on arm device: // armv8l // presumably also might just be possible: // i386 i486 i586 // and there might be other weird arm32 cases. // We note that the 64 is present in both 64-bit archs, // and in general is likely to be present in only 64-bit archs. cache = !!strstr(u.machine, "64"); init = true; return cache; } static inline __unused bool isKernel32Bit() { return !isKernel64Bit(); } static constexpr bool isArm() { #if defined(__arm__) static_assert(isUserspace32bit(), "huh? arm must be 32 bit"); return true; #elif defined(__aarch64__) static_assert(isUserspace64bit(), "aarch64 must be LP64 - no support for ILP32"); return true; #else return false; #endif } static constexpr bool isX86() { #if defined(__i386__) static_assert(isUserspace32bit(), "huh? i386 must be 32 bit"); return true; #elif defined(__x86_64__) static_assert(isUserspace64bit(), "x86_64 must be LP64 - no support for ILP32 (x32)"); return true; #else return false; #endif } static constexpr bool isRiscV() { #if defined(__riscv) static_assert(isUserspace64bit(), "riscv must be 64 bit"); return true; #else return false; #endif } static_assert(isArm() || isX86() || isRiscV(), "Unknown architecture"); static __unused const char * describeArch() { // ordered so as to make it easier to compile time optimize, // only thing not known at compile time is isKernel64Bit() if (isUserspace64bit()) { if (isArm()) return "64-on-aarch64"; if (isX86()) return "64-on-x86-64"; if (isRiscV()) return "64-on-riscv64"; } else if (isKernel64Bit()) { if (isArm()) return "32-on-aarch64"; if (isX86()) return "32-on-x86-64"; } else { if (isArm()) return "32-on-arm32"; if (isX86()) return "32-on-x86-32"; } } } // namespace bpf } // namespace android