android/kernel/kernel_utils.c: Support probing version for ARM64 kernel images. The ARM64 kernel images are not compressed but contain a GZip header that appears _after_ the actual 'Linux version ' string. This patch modifies the probing code to handle this gracefully. + Update unit tests. Change-Id: Id486e9ba29c894bc6f7f863789f120c7c8ab22a0
diff --git a/android/kernel/kernel_utils.cpp b/android/kernel/kernel_utils.cpp index cbc4f4e..2237357 100755 --- a/android/kernel/kernel_utils.cpp +++ b/android/kernel/kernel_utils.cpp
@@ -25,7 +25,7 @@ #include <string.h> #include <strings.h> -#define DEBUG_KERNEL 0 +#define DEBUG_KERNEL 1 #define KERNEL_LOG LOG_IF(INFO, DEBUG_KERNEL) #define KERNEL_PLOG PLOG_IF(INFO, DEBUG_KERNEL) @@ -34,14 +34,13 @@ using android::base::PodVector; -namespace android { -namespace kernel { +namespace { -static const char kVersionStringPrefix[] = "Linux version "; -static const size_t kVersionStringPrefixLen = sizeof(kVersionStringPrefix) - 1U; +const char kLinuxVersionStringPrefix[] = "Linux version "; +const size_t kLinuxVersionStringPrefixLen = + sizeof(kLinuxVersionStringPrefix) - 1U; -} // namespace kernel -} // namespace android +} // namespace #ifndef __APPLE__ size_t strlcpy(char* dst, const char * src, size_t size) @@ -84,13 +83,13 @@ bool android_parseLinuxVersionString(const char* versionString, KernelVersion* kernelVersion) { - if (strncmp(versionString, android::kernel::kVersionStringPrefix, - android::kernel::kVersionStringPrefixLen)) { + if (strncmp(versionString, kLinuxVersionStringPrefix, + kLinuxVersionStringPrefixLen)) { KERNEL_ERROR << "unsupported kernel version string:" << versionString; return false; } // skip past the prefix to the version number - versionString += android::kernel::kVersionStringPrefixLen; + versionString += kLinuxVersionStringPrefixLen; uint32_t temp = 0; for (int i = 0; i < 3; i++) { @@ -139,6 +138,8 @@ return false; } + const char* versionStringStart = NULL; + if (0 == memcmp(kElfHeader, kernelFileData, sizeof(kElfHeader))) { // this is an uncompressed ELF file (probably mips) uncompressedKernel = kernelFileData; @@ -156,37 +157,49 @@ return false; } - size_t compressedKernelLen = kernelFileSize - - (compressedKernel - kernelFileData); + // Special case: certain images, like the ARM64 one, contain a GZip + // header _after_ the actual Linux version string. So first try to + // see if there is something before the header. + versionStringStart = (const char*)memmem( + kernelFileData, + (compressedKernel - kernelFileData), + kLinuxVersionStringPrefix, + kLinuxVersionStringPrefixLen); - // inflate ratios for all prebuilt kernels on 2014-07-14 is 1.9:1 ~ - // 3.43:1 not sure how big the uncompressed size is, so make an - // absurdly large buffer - uncompressedKernelLen = compressedKernelLen * 10; - uncompressed.resize(uncompressedKernelLen); - uncompressedKernel = uncompressed.begin(); + if (!versionStringStart) { + size_t compressedKernelLen = kernelFileSize - + (compressedKernel - kernelFileData); - bool zOk = uncompress_gzipStream(uncompressed.begin(), - &uncompressedKernelLen, - compressedKernel, - compressedKernelLen); - if (!zOk) { - KERNEL_ERROR << "Kernel decompression error"; - // it may have been partially decompressed, so we're going to - // try to find the version string anyway + // inflate ratios for all prebuilt kernels on 2014-07-14 is 1.9:1 ~ + // 3.43:1 not sure how big the uncompressed size is, so make an + // absurdly large buffer + uncompressedKernelLen = compressedKernelLen * 10; + uncompressed.resize(uncompressedKernelLen); + uncompressedKernel = uncompressed.begin(); + + bool zOk = uncompress_gzipStream(uncompressed.begin(), + &uncompressedKernelLen, + compressedKernel, + compressedKernelLen); + if (!zOk) { + KERNEL_ERROR << "Kernel decompression error"; + // it may have been partially decompressed, so we're going to + // try to find the version string anyway + } } } - // okay, now we have a pointer to an uncompressed kernel, let's find the - // version string - const char* versionStringStart = (const char*)memmem( - uncompressedKernel, - uncompressedKernelLen, - android::kernel::kVersionStringPrefix, - android::kernel::kVersionStringPrefixLen); if (!versionStringStart) { - KERNEL_ERROR << "Could not find 'Linux version ' in kernel!"; - return false; + versionStringStart = (const char*)memmem( + uncompressedKernel, + uncompressedKernelLen, + kLinuxVersionStringPrefix, + kLinuxVersionStringPrefixLen); + + if (!versionStringStart) { + KERNEL_ERROR << "Could not find 'Linux version ' in kernel!"; + return false; + } } strlcpy(dst, versionStringStart, dstLen);
diff --git a/android/kernel/kernel_utils_unittest.cpp b/android/kernel/kernel_utils_unittest.cpp index 40ad06c..f24845a 100755 --- a/android/kernel/kernel_utils_unittest.cpp +++ b/android/kernel/kernel_utils_unittest.cpp
@@ -85,6 +85,27 @@ 0x00, 0x00, 0x00 }; + // a mock semi-compressed kernel + // an unspecified number of bytes, followed by a version string, + // followed by a gzip header + static const unsigned char kMockKernelSemiCompressed[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x39, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x33, 0x2e, 0x31, 0x30, 0x2e, + 0x30, 0x2b, 0x20, 0x28, 0x76, 0x68, 0x61, 0x72, 0x72, 0x6f, 0x6e, 0x40, + 0x74, 0x69, 0x66, 0x61, 0x2e, 0x6d, 0x74, 0x76, 0x2e, 0x63, 0x6f, 0x72, + 0x70, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x29, 0x20, 0x28, 0x67, 0x63, 0x63, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x20, 0x34, 0x2e, 0x37, 0x20, 0x28, 0x47, 0x43, 0x43, 0x29, + 0x20, 0x29, 0x20, 0x23, 0x31, 0x20, 0x50, 0x52, 0x45, 0x45, 0x4d, 0x50, + 0x54, 0x20, 0x53, 0x61, 0x74, 0x20, 0x4a, 0x61, 0x6e, 0x20, 0x35, 0x20, + 0x32, 0x3a, 0x34, 0x35, 0x3a, 0x32, 0x34, 0x20, 0x50, 0x44, 0x54, 0x20, + 0x32, 0x30, 0x30, 0x38, 0x0a, 0x00, 0x31, 0x32, 0x33, 0x34, 0x35, 0x35, + 0x37, 0x38, 0x39, 0x1f, 0x8b, 0x08, 0x00, 0x3e, 0xa9, 0xc7, 0x53, 0x00, + 0x03, 0x0b, 0xf7, 0x70, 0x0c, 0x71, 0x0d, 0x73, 0x0d, 0x02, 0x00, 0xff, + 0xdf, 0x22, 0x88, 0x08, 0x00, 0x00, 0x00 + }; + char kernelVersionString[256]; kernelVersionString[0] = 0; @@ -103,8 +124,16 @@ sizeof(kernelVersionString))); EXPECT_STREQ(kMockKernelVersion, kernelVersionString); + kernelVersionString[0] = 0; + EXPECT_EQ(true, android_imageProbeKernelVersionString( + kMockKernelSemiCompressed, + sizeof(kMockKernelSemiCompressed), + kernelVersionString, + sizeof(kernelVersionString))); + EXPECT_STREQ(kMockKernelVersion, kernelVersionString); + kernelVersionString[0] = 127; - EXPECT_EQ(false, android_imageProbeKernelVersionString( + EXPECT_FALSE(android_imageProbeKernelVersionString( 0, 0, kernelVersionString, @@ -112,7 +141,7 @@ EXPECT_EQ(127, kernelVersionString[0]); kernelVersionString[0] = 127; - EXPECT_EQ(false, android_imageProbeKernelVersionString( + EXPECT_FALSE(android_imageProbeKernelVersionString( kMockKernelElfNoString, sizeof(kMockKernelElfNoString), kernelVersionString,
diff --git a/android/kernel/testing/print_mock_kernel_data.sh b/android/kernel/testing/print_mock_kernel_data.sh index 1a1a5cb..c6ce113 100755 --- a/android/kernel/testing/print_mock_kernel_data.sh +++ b/android/kernel/testing/print_mock_kernel_data.sh
@@ -26,6 +26,15 @@ cat $TMP_FILE | xxd -i printf "};\n\n" +printf "// a mock semi-compressed kernel\n" +printf "// an unspecified number of bytes, followed by a version string,\n" +printf "// followed by a gzip header\n" +printf "static const unsigned char kMockKernelSemiCompressed[] = {\n" +printf "01234567899abcdef${LINUX_VERSION}0123455789" > $TMP_FILE +printf "WHATEVER" | gzip >> $TMP_FILE +cat $TMP_FILE | xxd -i +printf "};\n\n" + rm $TMP_FILE