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\$ Commented 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\$ Commented 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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.