App crashes while using lzo with the argument -DCMAKE_BUILD_TYPE:STRING=Release on arm-based Android devices

265 Views Asked by At

I created an Android project containing c++ code, I use CMake to compile c++ and use JNI to communicate between c++ and Java. In the cpp file, I use miniLZO to compress a file. Here is the code.

TestMiniLzo.cpp

#include <jni.h>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <iterator>
#include "minilzo.h"

std::vector<unsigned char> readFile(const std::string& file);

/* Work-memory needed for compression. Allocate memory in units
 * of 'lzo_align_t' (instead of 'char') to make sure it is properly aligned.
 */


#define HEAP_ALLOC(var,size) \
    lzo_align_t __LZO_MMODEL var [ ((size) + (sizeof(lzo_align_t) - 1)) / sizeof(lzo_align_t) ]

static HEAP_ALLOC(wrkmem, LZO1X_1_MEM_COMPRESS);

std::string testMiniLzo() {
    if (lzo_init() != LZO_E_OK) {
        return "init failed";
    }
    std::vector<unsigned char> fileContents = readFile("/sdcard/testFile");

    // compress with LZO1X-1
    std::vector<unsigned char> compressOutput(fileContents.size() + fileContents.size() / 16 + 64 + 3);
    lzo_uint outSize;
    int r = lzo1x_1_compress(fileContents.data(), fileContents.size(), compressOutput.data(), &outSize, wrkmem);
    if (r != LZO_E_OK) {
        return "fail";
    }
    return "success";
}

std::vector<unsigned char> readFile(const std::string& file) {
    std::ifstream input(file, std::ios::binary);

    // copies all data into buffer
    std::vector<unsigned char> buffer(std::istreambuf_iterator<char>(input), {});
    return buffer;
}

It actually worked. No error was thrown. But things changed after I added a CMake argument -DCMAKE_BUILD_TYPE:STRING=Release in build.gradle(app).

externalNativeBuild {
    cmake {
        arguments "-DCMAKE_BUILD_TYPE:STRING=Release"
        cppFlags ""
   }
}

After adding this argument, TestMiniLzo.cpp shown above continued to work on x86 Android Emulator, but the app crashed on a real Android device(Nexus 6), which has an arm architecture. Here is the error.

06-23 10:03:40.962 11229 11229 F libc    : Fatal signal 7 (SIGBUS), code 1, fault addr 0xaad38b85 in tid 11229 (y.myapplication)        
06-23 10:03:40.963   261   261 W         : debuggerd: handling request: pid=11229 uid=10109 gid=10109 tid=11229                         
06-23 10:03:41.033 11276 11276 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***                              
06-23 10:03:41.033 11276 11276 F DEBUG   : Build fingerprint: 'google/shamu/shamu:7.1.1/NGI77B/4345728:user/release-keys'               
06-23 10:03:41.033 11276 11276 F DEBUG   : Revision: '0'                                                                                
06-23 10:03:41.033 11276 11276 F DEBUG   : ABI: 'arm'                                                                                   
06-23 10:03:41.034 11276 11276 F DEBUG   : pid: 11229, tid: 11229, name: y.myapplication  >>> com.body.myapplication <<<                
06-23 10:03:41.034 11276 11276 F DEBUG   : signal 7 (SIGBUS), code 1 (BUS_ADRALN), fault addr 0xaad38b85                                
06-23 10:03:41.034 11276 11276 F DEBUG   :     r0 0a64696f  r1 00000004  r2 a9abb2c0  r3 aad38b85                                       
06-23 10:03:41.034 11276 11276 F DEBUG   :     r4 0000000c  r5 aad38b7d  r6 aad38b85  r7 beedd800                                       
06-23 10:03:41.034 11276 11276 F DEBUG   :     r8 a9abb2a0  r9 a9abb2c0  sl 00000000  fp a9abb2c0                                       
06-23 10:03:41.034 11276 11276 F DEBUG   :     ip 0000001c  sp beedd7c8  lr aad38b70  pc ac71eab0  cpsr 60052430                        
06-23 10:03:41.037 11276 11276 F DEBUG   :                                                                                              
06-23 10:03:41.037 11276 11276 F DEBUG   : backtrace:                                                                                   
06-23 10:03:41.037 11276 11276 F DEBUG   :     #00 pc 00032ab0  /data/app/com.body.myapplication-1/lib/arm/libnative-lib.so (lzo1x_1_com
press+261)                                                                                                                              
06-23 10:03:41.037 11276 11276 F DEBUG   :     #01 pc 00031435  /data/app/com.body.myapplication-1/lib/arm/libnative-lib.so (_Z11testMin
iLzov+128)                                                                                                                              
06-23 10:03:41.038 11276 11276 F DEBUG   :     #02 pc 000325f7  /data/app/com.body.myapplication-1/lib/arm/libnative-lib.so (Java_com_bo
dy_myapplication_MainActivity_compressAndDecompressUsingLzo+26)                                                                         
06-23 10:03:41.038 11276 11276 F DEBUG   :     #03 pc 00276b71  /data/app/com.body.myapplication-1/oat/arm/base.odex (offset 0x249000)
06-23 10:03:41.210 11253 11258 I art     : Do partial code cache collection, code=28KB, data=30KB
06-23 10:03:41.210 11253 11258 I art     : After code cache collection, code=28KB, data=30KB
06-23 10:03:41.210 11253 11258 I art     : Increasing code cache capacity to 128KB

Just to give more information, here is my CMakeLists.txt.

CMakeLists.txt

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_subdirectory(minilzo-2.10 "${CMAKE_CURRENT_BINARY_DIR}/minilzo-build")
include_directories(minilzo-2.10)
include_directories(include)

add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        TestMiniLzo.cpp
        # Provides a relative path to your source file(s).
        native-lib.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib}
        minilzo)

This is /sdcard/testFile, the file to be compressed. It only contains five lines.

android
android
android
android
android

Why does the app crash after adding that CMake argument? How to fix it?

1

There are 1 best solutions below

0
Dan Albert On

06-23 10:03:41.034 11276 11276 F DEBUG : signal 7 (SIGBUS), code 1 (BUS_ADRALN), fault addr 0xaad38b85

This line is saying that your code is performing an illegally aligned access of some sort. This is undefined behavior in C/C++. The stack trace tells you where it happened (06-23 10:03:41.037 11276 11276 F DEBUG : #00 pc 00032ab0 /data/app/com.body.myapplication-1/lib/arm/libnative-lib.so (lzo1x_1_com press+261)).

Why does the app crash after adding that CMake argument?

Because the argument changed the optimization mode for the compiler, which caused it to emit different code that exposed this error. Typically the reason this error is only sometimes visible is because some ARM instructions are valid for unaligned reads/writes, but others aren't. The aligned reads are faster when they are valid, so the compiler will use those if it can "prove" that they are (from the compiler's point of view, undefined behavior is impossible, so it can "safely" use these instructions if it appears that an access should be aligned).

How to fix it?

UBSan can sometimes catch these issues, but unfortunately not always. Add set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined") to your CMakeLists.txt to enable it, then look at logcat when your application is running.

If that doesn't help, you need to audit your code for undefined behavior. The most common cause of this kind of issue that I've seen is a buffer that is allocated as one type but accessed as another. i.e.

char buf[sizeof(Foo) * 10]; // Allocate space for 10 Foo structs.
Foo* foo_array = reinterpret_cast<Foo>(buf);

The code above has undefined behavior because the char buffer and the array it is cast to have different alignment requirements. It's rarely so obvious because generally these mistakes aren't made on two adjacent lines. To fix that case, allocate the buffer using the correct type, i.e. Foo buf[10];.

For more information, see https://developer.apple.com/documentation/code_diagnostics/undefined_behavior_sanitizer/misaligned_pointer