Mobile Development 24 min read

How to Detect and Debug SIGSEGV Memory Corruption in Android Native Apps

This article explains the common causes of SIGSEGV crashes in Android native code, demonstrates classic memory‑corruption patterns such as Use‑After‑Free, Double‑Free and heap buffer overflow, and presents a practical memory‑debugging solution inspired by Gwp‑ASan with custom guard pages, hook strategies, and stack capture techniques.

Huolala Tech
Huolala Tech
Huolala Tech
How to Detect and Debug SIGSEGV Memory Corruption in Android Native Apps

Background

As mobile apps grow, developers increasingly need low‑level performance and direct memory manipulation, often using C/C++. While these languages offer speed, they also introduce frequent SIGSEGV crashes caused by invalid memory accesses, especially when third‑party native libraries are involved.

This article introduces the root causes of SIGSEGV, industry‑proven practices for diagnosing these crashes, and a design for a more efficient memory‑debugging tool.

Classic Memory Corruption Issues

UseAfterFree

Example of using a pointer after it has been freed (a "wild pointer"):

Java_com_example_scalpel_MemoryCorruptionTestActivity_testUseAfterFree(JNIEnv *env, jobject thiz) {
    int *pint = malloc(sizeof(int));
    *pint = 1;
    free(pint);
    *pint = 2; // use after free
}

After free, the pointer becomes undefined; it may still point to the original data or to other memory, leading to three possible outcomes:

Logical modification before free – no crash.

Modification of writable memory – data corruption without immediate crash.

Access to inaccessible memory – triggers a SIGSEGV.

DoubleFree

Freeing the same heap block multiple times:

JNIEXPORT void JNICALL Java_com_example_scalpel_MemoryCorruptionTestActivity_testDoubleFree(JNIEnv *env, jobject thiz) {
    int *pint = malloc(sizeof(int));
    *pint = 1;
    free(pint);
    free(pint); // double free
}

If the pointer is set to NULL after the first free, the second free causes a clean SIGSEGV; otherwise it behaves like Use‑After‑Free.

HeapBufferOverflow

Writing outside the allocated heap region:

JNIEXPORT void JNICALL Java_com_example_scalpel_MemoryCorruptionTestActivity_testBoundary(JNIEnv *env, jobject thiz) {
    int *pint = malloc(sizeof(int));
    pint = pint - 10; // or pint = pint + 10
    *pint = 1; // out‑of‑bounds write
}

Such writes can corrupt adjacent memory blocks and are not protected by the OS.

Root Causes

Real‑world crashes often combine multiple corruption patterns and become harder to reproduce in multithreaded environments, making post‑mortem stack traces insufficient for pinpointing the true origin.

Industry Practice – Gwp‑ASan

Google’s Gwp‑ASan (Guarded‑Page‑Allocator) protects memory by allocating guard pages around each allocation. When a protected page is accessed, the process crashes immediately, allowing precise detection of Use‑After‑Free, Double‑Free, and heap overflows.

Why Gwp‑ASan Detects Original Errors

Use‑After‑Free / Double‑Free: After free, the region is turned back into a guard page, so any subsequent access triggers SIGSEGV.

HeapBufferOverflow: Over‑ or under‑flows hit the adjacent guard page, causing an immediate crash.

Gwp‑ASan Stack Capture

Gwp‑ASan records allocation and deallocation stacks by hooking malloc, realloc, free via a DispatchTable hook, storing metadata for later analysis.

void AllocationMetadata::RecordAllocation(uintptr_t AllocAddr, size_t AllocSize) {
  Addr = AllocAddr;
  RequestedSize = AllocSize;
  IsDeallocated = false;
  AllocationTrace.ThreadID = getThreadID();
  DeallocationTrace.TraceSize = 0;
  DeallocationTrace.ThreadID = kInvalidThreadID;
}

Gwp‑ASan Limitations

It only detects fixed‑size allocations within a guard‑page pool, operates with a sampling rate (default 1/2500 on Android), and lacks per‑SO targeting, which reduces its bug‑finding efficiency for large applications.

Huolala Memory Debugging Tool

To overcome Gwp‑ASan’s shortcomings, we design a custom tool that supports:

Selective monitoring of specific native libraries (SO files).

Configurable sampling rates.

Dynamic guard‑page pool management.

Hook Strategy Selection

Two main hook mechanisms are considered:

DispatchTable Hook : Replaces the global allocation table, affecting all callers.

GOT Hook : Modifies the Global Offset Table of a specific SO, enabling per‑library monitoring.

Implementation of Memory Debugging Scheme

Key steps:

Initialize a GuardPool using mmap and split it into GuardPages.

During allocation, select a suitable slot, align the address, and set page permissions to read/write.

On deallocation, restore the page to PROT_NONE (guard page) and record metadata.

Register a SIGSEGV handler that checks whether the fault address lies within the GuardPool, then reports the stored allocation/deallocation stacks.

void *call_malloc(size_t byte_count) {
    pthread_mutex_lock(&pool_mutex);
    int index = get_free_slots_index();
    pthread_mutex_unlock(&pool_mutex);
    if (index == -1 || byte_count >= system_page_size) return NULL;
    // calculate slot boundaries, align, set permissions, record metadata
    ...
    return (void *)alloc_ptr;
}

int call_free(void *free_ptr) {
    if (free_ptr < global_ptr || free_ptr > global_ptr + length) return -1;
    size_t index = addr_to_slot((uintptr_t)free_ptr);
    if (meta[index].is_deallocated) {
        // double free detected
        raise(SIGSEGV);
        return 0;
    }
    // remap slot to guard page
    ...
    meta[index].is_deallocated = 1;
    return 0;
}

Signal Handling

void sig_func(int sig_num, siginfo_t *info, void *ptr_attr) {
    void *fault_addr = info->si_addr;
    if (fault_addr >= global_ptr && fault_addr < (global_ptr + length)) {
        size_t index = addr_to_slot((uintptr_t)fault_addr);
        // print allocation and deallocation traces
        ...
    }
    // chain to previous handler
    if (prev_action->sa_flags & SA_SIGINFO) {
        prev_action->sa_sigaction(sig_num, info, ptr_attr);
    } else if (prev_action->sa_handler != SIG_DFL && prev_action->sa_handler != SIG_IGN) {
        prev_action->sa_handler(sig_num);
    }
}

Stack Capture Choice

We use frame‑pointer (FP) unwinding for minimal overhead, walking the linked list of frame records until reaching the thread’s stack top.

size_t android_unsafe_frame_pointer_chase(uintptr_t *buf, size_t num_entries) {
    struct frame_record { uintptr_t next_frame, return_addr; };
    auto begin = (uintptr_t)__builtin_frame_address(0);
    auto end = __get_thread_stack_top();
    size_t num_frames = 0;
    while (1) {
        auto *frame = (frame_record *)begin;
        if (num_frames < num_entries) {
            uintptr_t addr = __bionic_clear_pac_bits(frame->return_addr);
            if (addr == 0) break;
            buf[num_frames] = addr;
        }
        ++num_frames;
        if (frame->next_frame < begin + sizeof(frame_record) ||
            frame->next_frame >= end ||
            frame->next_frame % sizeof(void*) != 0) {
            break;
        }
        begin = frame->next_frame;
    }
    return num_frames;
}

Partial Benefits

After integrating the tool, Huolala quickly identified a top‑priority native crash in the driver app caused by a multithreaded Use‑After‑Free in a third‑party map engine, enabling a targeted fix.

Conclusion

We reviewed common memory‑corruption patterns, examined Gwp‑ASan’s design and limitations, and presented a custom memory‑debugging solution that offers per‑SO targeting and configurable sampling, making native crash diagnosis in Android apps more effective.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AndroidNative Debuggingmemory corruptionGwp-ASanSIGSEGV
Huolala Tech
Written by

Huolala Tech

Technology reshapes logistics

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.