Android – Creating Shared Memory using Ashmem

In API 27 , Google added SharedMemory class so applications can create and use shared memory using asmem (/dev/ashmem). Today, less than 1% of mobile devices work with API 27 (Android 8.1) so It is useless. In this post I will show you how to work with ashmem, creating and using shared memory between 2 untrusted applications (applications from the Play store) and it is working for API 15 and up (Android 4)

To support this , we will create an Android library, Server application and Client Application , we will use JNI to create and map ashmem objects using native C++ code and we will use the binder to pass the file descriptors between processes.

If you don’t know how to create a service and use it in Android Application read this first

Start with creating a new Android Application, add C++ support, choose API 15 and keep other defaults

Add a new Module -> Android Library

Add a new aidl file:

// ISharedMem.aidl
package com.example.sharedmemlib;

import android.os.ParcelFileDescriptor;

interface ISharedMem {
    ParcelFileDescriptor OpenSharedMem(String name, int size, boolean create); 
}

The type ParcelFileDescriptor is used to pass the file object from one process to another.

The Server Application

The Server application host a service to create and return a file descriptor for the shared memory. It is also contains an Activity for testing the shared memory.

To create or open a shared memory we need to pass name and size. We create a simple class to handle it using native code.

The Java code:

public class ShmLib {
    static {
        System.loadLibrary("native-lib");
    }
    private static HashMap<String,Integer> memAreas = new HashMap<>();

    public static int OpenSharedMem(String name, int size, boolean create)  {
        Integer i = memAreas.get(name);
        if (create && i != null)
            return -1;
        if (i == null){
            i = new Integer(getFD(name, size));
            memAreas.put(name, i);
        }
        return i.intValue();

    }
    public static int setValue(String name, int pos, int val){
        Integer fd = memAreas.get(name);
        if(fd != null)
            return setVal(fd.intValue(),pos,val);
        return -1;
    }
    public static int getValue(String name, int pos ){
        Integer fd = memAreas.get(name);
        if(fd != null)
            return getVal(fd.intValue(),pos);
        return -1;
    }
    private static native int setVal(int fd,int pos, int val);
    private static native int getVal(int fd,int pos);
    private static native int getFD(String name , int size);

}

We create an HashMap to store all shared memory objects (the file descriptors) by name. We write 3 native C++ functions to create the shared memory , set a value and get a value

The C++ code

struct memArea{
    int *map;
    int fd;
    int size;
};

struct memArea maps[10];
int num = 0;

static jint getFD(JNIEnv *env, jclass cl, jstring path,jint size)
{
    const char *name = env->GetStringUTFChars(path,NULL);
    jint fd = open("/dev/ashmem",O_RDWR);

    ioctl(fd,ASHMEM_SET_NAME,name);
    ioctl(fd,ASHMEM_SET_SIZE,size);

    maps[num].size = size;
    maps[num].fd = fd;
    maps[num++].map = (int *)mmap(0,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

    env->ReleaseStringUTFChars(path,name);
    return fd;
}

static jint setNum(JNIEnv *env, jclass cl,jint fd, jint pos,jint num)
{
    for(int i = 0; i < num; i++)
    {
        if(maps[i].fd == fd)
        {
            if(pos < (maps[i].size/ sizeof(int)))
            {
                maps[i].map[pos] = num;
                return 0;
            }
            return -1;
        }
    }
    return -1;
}
static jint getNum(JNIEnv *env, jclass cl,jint fd, jint pos)
{
    for(int i = 0; i < num; i++)
    {
        if(maps[i].fd == fd)
        {
            if(pos < (maps[i].size/ sizeof(int)))
            {
                return maps[i].map[pos];
            }
            return -1;
        }
    }
    return -1;
}

On the server application you can use the above code to create and use shared memory

ShmLib.OpenSharedMem("sh1",1000,true);
ShmLib.setValue("sh1",10,200); //sh1[10] = 200
int v = ShmLib.getValue("sh1",10);

 

Creating The Service

To create the service we need to implement the interface defined by the aidl file.

public class SharedMemImp extends ISharedMem.Stub {
    @Override
    public ParcelFileDescriptor OpenSharedMem(String name, int size, boolean create) throws RemoteException {
        int fd =  ShmLib.OpenSharedMem(name,size,create);
        try {
            return ParcelFileDescriptor.fromFd(fd);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Using ParcelFileDescriptor we can pass any file descriptor we have to another process using the binder. It can be a file, device, socket, IPC object etc.

Add a class for a service:

public class ShmService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new SharedMemImp();
    }
}

Add update the manifest file:

<service android:name=".ShmService">
       <intent-filter>
                <action android:name="com.example.developer.testashmem.ShmService"/>
       </intent-filter>
</service>

Add Activity for testing

The code simply set and get values in the shared memory

 

Writing A Client Application

First we need to bind the service and get the file descriptor of the shared memory object:

bindService(new Intent("com.example.developer.testashmem.ShmService")
                     .setPackage("com.example.developer.testashmem"),
                             this,BIND_AUTO_CREATE);

...

public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        ShmMemService = ISharedMem.Stub.asInterface(iBinder);
        try {
            ParcelFileDescriptor p = ShmMemService.OpenSharedMem("sh1", 1000, false);
            int fd = p.getFd()
        } catch (RemoteException e) {
            e.printStackTrace();
        }

}

Now we need to write a native code to use mmap and access the shared memory. We create a simple JNI wrapper class:

public class ShmClientLib {
    static {
        System.loadLibrary("client-lib");
    }

    public static native int setVal(int pos, int val);
    public static native int getVal(int pos);
    public static native void setMap(int fd , int size);

}

And the C++ code

#include <jni.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

int *map ;
int size;

static void setmap(JNIEnv *env, jclass cl, jint fd, jint sz)
{
    size = sz;
    map = (int *)mmap(0,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
}

static jint setNum(JNIEnv *env, jclass cl, jint pos,jint num)
{
            if(pos < (size / sizeof(int)))
            {
                map[pos] = num;
                return 0;
            }
            return -1;
}
static jint getNum(JNIEnv *env, jclass cl, jint pos)
{
            if(pos < (size / sizeof(int)))
            {
                return map[pos];
            }
            return -1;
}

static JNINativeMethod method_table[] = {
        { "setVal", "(II)I", (void *) setNum },
        { "getVal", "(I)I", (void *) getNum },
        { "setMap", "(II)V", (void *)setmap }

};


extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    } else {
        jclass clazz = env->FindClass("com/example/testashmemclient/ShmClientLib");
        if (clazz) {
            jint ret = env->RegisterNatives(clazz, method_table, sizeof(method_table) / sizeof(method_table[0]));
            env->DeleteLocalRef(clazz);
            return ret == 0 ? JNI_VERSION_1_6 : JNI_ERR;
        } else {
            return JNI_ERR;
        }
    }
}

Now we can use setMap to mmap the memory and setVal/getVal to access it

You can download / clone the full code from my github

Stay Updated, Sign up to our List:

Loading
Tagged

6 thoughts on “Android – Creating Shared Memory using Ashmem

  1. Very nice

  2. Please someone provide me, Shared Memory sample code using class “android.os.SharedMemory” ?

    1. Hi ! Have you found how to use it ?

  3. Very nice, I used your article as inspiration for https://stackoverflow.com/a/53062250/192373

  4. How will this code possibly work:

    static jint setNum(JNIEnv *env, jclass cl,jint fd, jint pos,jint num)
    {
    for(int i = 0; i < num; i++)
    {
    if(maps[i].fd == fd)
    {
    if(pos < (maps[i].size/ sizeof(int)))
    {
    maps[i].map[pos] = num;
    return 0;
    }
    return -1;
    }
    }
    return -1;
    }

    The "num" in the first for loop is using the value of the last function parameter, which is masking the static int num declared above it.If it doesn't find the fd and num is large, the loop will go right off the end of the array, presumably leading to a memory fault. I suspect that the parameter should be called val to avoid this, or the static num renamed to something more meaningful. (There's also no range checking on num when creating a new map.)

    The code also seems to be assuming a size equivalency between int and jint, which I'm not sure is true on all platforms. In any case I think it's good practice to avoid using the raw primitive types in code like this.

    I know it's example code, but I think it could use some tightening up before being let loose into the world!

  5. […] ANDROID – CREATING SHARED MEMORY USING ASHMEM […]

Comments are closed.