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
- 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??
1 Answer 1
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!
cs.disasm()
call consuming the time? It might as well be therunOnUiThread()
or some other part of your loop. \$\endgroup\$