1
\$\begingroup\$

I am developing Android-Disassembler. I need to optimize the loop below.

It is currectly calling native method for each instructions(about 4 bytes). So this loop will be looped about millions of times while disassembling the entire code section.

The code below is what I was using.

private void DisassembleFile()
    {
        Toast.makeText(this, "started", 2).show();
        Log.v(TAG, "Strted disassm");

        //final ProgressDialog dialog= showProgressDialog("Disassembling...");
        disasmResults.clear();
        mNotifyManager =
            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        mBuilder = new Notification.Builder(this);
        mBuilder.setContentTitle("Disassembler")
            .setContentText("Disassembling in progress")
            .setSmallIcon(R.drawable.cell_shape)
            .setOngoing(true)
            .setProgress(100, 0, false);
        workerThread = new Thread(new Runnable(){
                @Override
                public void run()
                {
                    long start=elfUtil.getCodeSectionOffset();
                    long index=start;
                    long limit=elfUtil.getCodeSectionLimit();
                    long addr=elfUtil.getCodeSectionVirtAddr();
                    Log.v(TAG, "code section point :" + Long.toHexString(index));
                    HashMap xrefComments=new HashMap();
                    for (;;)
                    {
                        Capstone.CsInsn[] insns=cs.disasm(filecontent,index,addr,1);
                        Capstone.CsInsn insn=insns[0];
                        final ListViewItem lvi=new ListViewItem(insn);
                        if (insn.size == 0)
                        {
                            insn.size = 4;
                            insn.mnemonic = "db";
                            //insn.bytes = new byte[]{filecontent[(int)index],filecontent[(int)index + 1],filecontent[(int)index + 2],filecontent[(int)index + 3]};
                            insn.opStr = "";
                            Log.e(TAG, "Dar.size==0, breaking?");
                            //break;
                        }

                        runOnUiThread(new Runnable(){
                                @Override
                                public void run()
                                {
                                    adapter.addItem(lvi);
                                    adapter.notifyDataSetChanged();
                                    return ;
                                }
                            });
                        //Log.v(TAG, "i=" + index + "lvi=" + lvi.toString());
                        if (index >= limit)
                        {
                            Log.i(TAG, "index is " + index + ", breaking");
                            break;
                        }
                        Log.i(TAG, "" + index + " out of " + (limit - start));
                        if ((index - start) % 320 == 0)
                        {
                            mBuilder.setProgress((int)(limit - start), (int)(index - start), false);
                            // Displays the progress bar for the first time.
                            mNotifyManager.notify(0, mBuilder.build());                 
                            runOnUiThread(new Runnable(){
                                    @Override
                                    public void run()
                                    {
                                        //adapter.notifyDataSetChanged();
                                        listview.requestLayout();
                                    }
                                });
                        }
                        index += insn.size;
                        addr += insn.size;          
                        //dialog.setProgress((int)((float)(index-start) * 100 / (float)(limit-start)));
                        //dialog.setTitle("Disassembling.."+(index-start)+" out of "+(limit-start));
                    }
                    mNotifyManager.cancel(0);
                    final int len=disasmResults.size();
                    runOnUiThread(new Runnable(){
                            @Override
                            public void run()
                            {
                                listview.requestLayout();
                                tab2.invalidate();
                                Toast.makeText(MainActivity.this, "done", 2).show();            
                            }
                        });
                    Log.v(TAG, "disassembly done");     
                }
            });
        workerThread.start();
    }

Information

  • The entire source is here, but I think it not needed.

  • Prototype of cs.disasm: cs.disasm(bytes,file_offset,virtual_address_to_be_displayed,num_of_instructions_to_be_disassembled);

  • returns: Array_of_disassembled_info.

  • The code of cs_disasm(I modified the method to let it support file_offset)

    public CsInsn[] disasm(byte[] code,long offset, long length,long address, long count)
     {
      PointerByReference insnRef = new PointerByReference();
    
      NativeLong c = cs.cs_disasm2(ns.csh, code,new NativeLong(offset), new NativeLong(length), address, new NativeLong(count), insnRef);
    
        if (0 == c.intValue())
        {
            return EMPTY_INSN;
        }
    
        Pointer p = insnRef.getValue();
        _cs_insn byref = new _cs_insn(p);
    
        CsInsn[] allInsn = fromArrayRaw((_cs_insn[]) byref.toArray(c.intValue()));
    
        // free allocated memory
        // cs.cs_free(p, c);
        // FIXME(danghvu): Can't free because memory is still inside CsInsn
        return allInsn; 
    }
    

Problem

  • The cs.disasm is called millions of times with file_offset increasing by 4 every loop.
  • It is a very EXPENSIVE call, as it uses JNA without direct mapping.

What I tried

  • Call cs.disasm with last argument 256, and increase file_offset and virtual_address by processed_bytes, which is calculated while processing the returned array.
  • Use final Runnable so that it doesn't get created every loop.

If you want to see the code I wrote to achieve above, please comment or see edit history. I deleted to make this question more easily readable.

Conditions

  1. I need to show progress so that users won't get annoyed.

Goal

Is to optimize the above loop.

Just to add, which is faster, calling C from java or calling java from C??

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Did you profile your code? Is it really the cs.disasm() call consuming the time? It might as well be the runOnUiThread() or some other part of your loop. \$\endgroup\$ Oct 3, 2018 at 19:31
  • \$\begingroup\$ @RalfKleberhoff Actually when I used cs_disasm of JNI version, the loop was about 15x faster. Now version is JNA version. It took about 3~4 minutes to disassemble all the code, but now It takes more than 15minutes. \$\endgroup\$ Oct 3, 2018 at 22:23

1 Answer 1

0
\$\begingroup\$

It was better not to use JNA in speed-critical loops.

I built a JNI function that performs the loop in JNI, throwing away JNA. And it saved 12 minutes.

JNIEXPORT void JNICALL Java_com_kyhsgeekcode_disassembler_DisasmIterator_getAll(JNIEnv * env, jobject thiz,jbyteArray bytes, jlong offset, jlong size,jlong virtaddr, jobject arr)
        {
            int bytelen=env->GetArrayLength(bytes);
            jbyte *byte_buf;
            byte_buf = env->GetByteArrayElements(bytes, NULL);
            //__android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "bytearrayelems");
            jclass arrcls = env->FindClass("java/util/ArrayList");
                        //__android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "ArrayListcls");
            jclass darcls = env->FindClass("com/kyhsgeekcode/disassembler/DisasmResult");
                        //__android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "Disasmresult");
            jclass lvicls = env->FindClass("com/kyhsgeekcode/disassembler/ListViewItem");
                        //__android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "Listviewitem");
            jclass thecls = env->GetObjectClass(thiz);
                        //__android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "thizclass");
            jmethodID ctor = env->GetMethodID(darcls,"<init>","()V");
                        //__android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "darinit");
            jmethodID ctorLvi = env->GetMethodID(lvicls,"<init>","(Lcom/kyhsgeekcode/disassembler/DisasmResult;)V");
                        //__android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "lviinit");
            jmethodID java_util_ArrayList_add  = env->GetMethodID(arrcls, "add", "(Ljava/lang/Object;)Z");
                        //__android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "arraylistaddmethod");
            jmethodID notify = env->GetMethodID(thecls,"showNoti","(I)I");
                        //__android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "shownotimethod");
            jmethodID additem = env->GetMethodID(thecls,"AddItem","(Lcom/kyhsgeekcode/disassembler/ListViewItem;)V");
            int done=0;
            // allocate memory cache for 1 instruction, to be used by cs_disasm_iter later.
            cs_insn *insn = cs_malloc(handle);
            const uint8_t *code = (uint8_t *)(byte_buf+offset);
            size_t code_size = size-offset; // size of @code buffer above
            uint64_t addr = virtaddr;   // address of first instruction to be disassembled
             // disassemble one instruction a time & store the result into @insn variable above
            while(cs_disasm_iter(handle, &code, &code_size, &addr, insn)) {
                  // analyze disassembled instruction in @insn variable ...
                  // NOTE: @code, @code_size & @address variables are all updated
                  // to point to the next instruction after each iteration.
                                        __android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "inloop");
                jobject dar=env->NewObject(darcls,ctor);
                jfieldID fid = env->GetFieldID(darcls, "mnemonic","Ljava/lang/String;");
                if (fid == NULL) {
                    return; /* failed to find the field */
                }
                /* Create a new string and overwrite the instance field */
                jstring jstr = env->NewStringUTF( insn->mnemonic);
                if (jstr == NULL) {
                    return; /* out of memory */
                }
                env->SetObjectField(dar, fid, jstr);
                env->DeleteLocalRef(jstr);
                fid = env->GetFieldID(darcls, "op_str","Ljava/lang/String;");
                if (fid == NULL) {
                    return; /* failed to find the field */
                }
                /* Create a new string and overwrite the instance field */
                jstr = env->NewStringUTF(insn->op_str);
                if (jstr == NULL) {
                    return; /* out of memory */
                }
                env->SetObjectField(dar, fid, jstr);
                env->DeleteLocalRef(jstr);
                fid = env->GetFieldID( darcls, "address","J");
                if (fid == NULL) {
                    return; /* failed to find the field */
                }
                env->SetLongField(dar, fid, insn->address);

                fid = env->GetFieldID( darcls, "id","I");
                if (fid == NULL) {
                    return; /* failed to find the field */
                }
                env->SetIntField(dar, fid, insn->id);

                fid = env->GetFieldID(darcls, "size","I");
                if (fid == NULL) {
                    return; /* failed to find the field */
                }
                env->SetIntField(dar, fid, insn->size);

                fid = env->GetFieldID( darcls, "bytes","[B");
                if (fid == NULL) {
                    return; /* failed to find the field */
                }
                jobject job=env->GetObjectField(dar,fid);
                jbyteArray *jba = reinterpret_cast<jbyteArray*>(&job);
                int sz=env->GetArrayLength(*jba);
                // Get the elements (you probably have to fetch the length of the array as well  
                jbyte * data = env->GetByteArrayElements(*jba, NULL);
                int min=insn->size > sz ? sz : insn->size;
                for(int i=0;i<min;++i)
                {
                    data[i]=insn->bytes[i];
                }
                // Don't forget to release it 
                env->ReleaseByteArrayElements(*jba, data, 0);
                env->DeleteLocalRef(job);
                        //__android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "beforedetail");
                if(insn[0].detail!=NULL)
                {
                    fid = env->GetFieldID( darcls, "groups","[B");
                    if (fid == NULL) {
                        return; /* failed to find the field */
                    }
                    jobject job2=env->GetObjectField(dar,fid);
                    jbyteArray *jba2 = reinterpret_cast<jbyteArray*>(&job2);
                    int sz2=env->GetArrayLength(*jba2);
                    // Get the elements (you probably have to fetch the length of the array as well  
                    jbyte * data2 = env->GetByteArrayElements(*jba2, NULL);
                    int min=insn->detail->groups_count > sz2 ? sz2 : insn->detail->groups_count;
                    for(int i=0;i<min;++i)
                    {
                        data2[i]=insn->detail->groups[i];
                    }
                    // Don't forget to release it 
                    env->ReleaseByteArrayElements(*jba2, data2, 0);
                    env->DeleteLocalRef(job2);
                    fid = env->GetFieldID(darcls, "groups_count","B");
                    if (fid == NULL) {
                        return; /* failed to find the field */
                    }
                    env->SetByteField(dar, fid, insn->detail->groups_count);    
                }
                                        //__android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "afterdetail");
                jobject lvi=env->NewObject(lvicls,ctorLvi,dar);
                                        //__android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "created lvi");
                //jstring element = env->NewStringUTF(s.c_str());
                env->CallBooleanMethod(arr, java_util_ArrayList_add, dar);
                env->CallVoidMethod(thiz,additem,lvi);
                __android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "added lvi");

                env->DeleteLocalRef(lvi);
                env->DeleteLocalRef(dar);
                //env->DeleteLocalRef(jstr);
                //env->DeleteLocalRef(dar);
                if(done%1024==0)
                {
                                            __android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "calling noti");
                     int ret=env->CallIntMethod(thiz, notify, done);
                                            __android_log_print(ANDROID_LOG_VERBOSE, "Disassembler", "end call noti");
                    if(ret==-1)
                    {
                        //thread interrupted
                        break;
                    }
                }
                ++done;
             }
             // release the cache memory when done
             cs_free(insn, 1);
            //DisasmOne_sub(env,thiz,(unsigned char*)(byte_buf+shift)/*bytes*/,bytelen-shift,address);
            env->ReleaseByteArrayElements(bytes, byte_buf, JNI_ABORT);
        }

Caller:

public void run()
                {
                    long start=elfUtil.getCodeSectionOffset();
                    long index=start;
                    long limit=elfUtil.getCodeSectionLimit();
                    long addr=elfUtil.getCodeSectionVirtAddr();
                    Log.v(TAG, "code section point :" + Long.toHexString(index));
                    //ListViewItem lvi;
                    //  getFunctionNames();
                    long size=limit - start;
                    long leftbytes=size;
                    DisasmIterator dai=new DisasmIterator(MainActivity.this,mNotifyManager,mBuilder,adapter,size);
                    dai.getAll(filecontent,start,size,addr, disasmResults);

DisasmIterator:

package com.kyhsgeekcode.disassembler;
import android.app.*;
import java.util.*;

public class DisasmIterator
{
    public DisasmIterator(MainActivity activity, NotificationManager mNotifyManager, Notification.Builder mBuilder, ListViewAdapter adapter,  long total)
    {
        this.activity = activity;
        this.mNotifyManager = mNotifyManager;
        this.mBuilder = mBuilder;
        this.total = total;
        this.adapter=adapter;
    }
    public native void getAll(byte[] bytes, long offset, long size,long virtaddr,ArrayList<DisasmResult> arr);
    public void AddItem(final ListViewItem lvi)
    {
        activity.runOnUiThread(new Runnable(){
                @Override
                public void run()
                {
                    adapter.addItem(lvi);
                    adapter.notifyDataSetChanged();
                    return ;
                }
        });
    }

    public int showNoti(int progress)
    {
        mBuilder.setProgress((int)total,progress, false);
        // Displays the progress bar for the first time.
        mNotifyManager.notify(0, mBuilder.build());                 
        activity.runOnUiThread(activity.runnableRequestLayout);
        if(Thread.interrupted())
        {
            return -1;
        }
        return 0;
    }
    public native int CSoption(int type, int vslue);

    MainActivity activity;
    NotificationManager mNotifyManager;
    Notification.Builder mBuilder;
    long total;
    ListViewAdapter adapter;
}

Anyway, thanks for your comments!

\$\endgroup\$

This site is temporarily in read-only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .